diff options
Diffstat (limited to 'src/core/hle')
81 files changed, 8191 insertions, 2490 deletions
diff --git a/src/core/hle/kernel/k_capabilities.cpp b/src/core/hle/kernel/k_capabilities.cpp index 90e4e8fb0..e7da7a21d 100644 --- a/src/core/hle/kernel/k_capabilities.cpp +++ b/src/core/hle/kernel/k_capabilities.cpp @@ -156,7 +156,6 @@ Result KCapabilities::MapIoPage_(const u32 cap, KPageTable* page_table) { const u64 phys_addr = MapIoPage{cap}.address.Value() * PageSize; const size_t num_pages = 1; const size_t size = num_pages * PageSize; - R_UNLESS(num_pages != 0, ResultInvalidSize); R_UNLESS(phys_addr < phys_addr + size, ResultInvalidAddress); R_UNLESS(((phys_addr + size - 1) & ~PhysicalMapAllowedMask) == 0, ResultInvalidAddress); diff --git a/src/core/hle/kernel/k_hardware_timer.cpp b/src/core/hle/kernel/k_hardware_timer.cpp index 4dcd53821..8e2e40307 100644 --- a/src/core/hle/kernel/k_hardware_timer.cpp +++ b/src/core/hle/kernel/k_hardware_timer.cpp @@ -35,7 +35,9 @@ void KHardwareTimer::DoTask() { } // Disable the timer interrupt while we handle this. - this->DisableInterrupt(); + // Not necessary due to core timing already having popped this event to call it. + // this->DisableInterrupt(); + m_wakeup_time = std::numeric_limits<s64>::max(); if (const s64 next_time = this->DoInterruptTaskImpl(GetTick()); 0 < next_time && next_time <= m_wakeup_time) { diff --git a/src/core/hle/kernel/k_memory_layout.cpp b/src/core/hle/kernel/k_memory_layout.cpp index af40092c0..bec714668 100644 --- a/src/core/hle/kernel/k_memory_layout.cpp +++ b/src/core/hle/kernel/k_memory_layout.cpp @@ -61,7 +61,7 @@ bool KMemoryRegionTree::Insert(u64 address, size_t size, u32 type_id, u32 new_at found->Reset(address, inserted_region_last, old_pair, new_attr, type_id); this->insert(*found); } else { - // If we can't re-use, adjust the old region. + // If we can't reuse, adjust the old region. found->Reset(old_address, address - 1, old_pair, old_attr, old_type); this->insert(*found); diff --git a/src/core/hle/kernel/k_page_table.cpp b/src/core/hle/kernel/k_page_table.cpp index 9bfc85b34..5b51edf30 100644 --- a/src/core/hle/kernel/k_page_table.cpp +++ b/src/core/hle/kernel/k_page_table.cpp @@ -5,6 +5,7 @@ #include "common/assert.h" #include "common/literals.h" #include "common/scope_exit.h" +#include "common/settings.h" #include "core/core.h" #include "core/hle/kernel/k_address_space_info.h" #include "core/hle/kernel/k_memory_block.h" @@ -337,11 +338,14 @@ Result KPageTable::InitializeForProcess(FileSys::ProgramAddressSpaceType as_type } void KPageTable::Finalize() { + auto HostUnmapCallback = [&](KProcessAddress addr, u64 size) { + if (Settings::IsFastmemEnabled()) { + m_system.DeviceMemory().buffer.Unmap(GetInteger(addr), size); + } + }; + // Finalize memory blocks. - m_memory_block_manager.Finalize(m_memory_block_slab_manager, - [&](KProcessAddress addr, u64 size) { - m_memory->UnmapRegion(*m_page_table_impl, addr, size); - }); + m_memory_block_manager.Finalize(m_memory_block_slab_manager, std::move(HostUnmapCallback)); // Release any insecure mapped memory. if (m_mapped_insecure_memory) { diff --git a/src/core/hle/kernel/k_process.cpp b/src/core/hle/kernel/k_process.cpp index 703049ede..4a099286b 100644 --- a/src/core/hle/kernel/k_process.cpp +++ b/src/core/hle/kernel/k_process.cpp @@ -96,6 +96,7 @@ Result KProcess::Initialize(KProcess* process, Core::System& system, std::string process->m_is_suspended = false; process->m_schedule_count = 0; process->m_is_handle_table_initialized = false; + process->m_is_hbl = false; // Open a reference to the resource limit. process->m_resource_limit->Open(); @@ -351,12 +352,14 @@ Result KProcess::SetActivity(ProcessActivity activity) { R_SUCCEED(); } -Result KProcess::LoadFromMetadata(const FileSys::ProgramMetadata& metadata, std::size_t code_size) { +Result KProcess::LoadFromMetadata(const FileSys::ProgramMetadata& metadata, std::size_t code_size, + bool is_hbl) { m_program_id = metadata.GetTitleID(); m_ideal_core = metadata.GetMainThreadCore(); m_is_64bit_process = metadata.Is64BitProgram(); m_system_resource_size = metadata.GetSystemResourceSize(); m_image_size = code_size; + m_is_hbl = is_hbl; if (metadata.GetAddressSpaceType() == FileSys::ProgramAddressSpaceType::Is39Bit) { // For 39-bit processes, the ASLR region starts at 0x800'0000 and is ~512GiB large. diff --git a/src/core/hle/kernel/k_process.h b/src/core/hle/kernel/k_process.h index 4fdeaf11a..146e07a57 100644 --- a/src/core/hle/kernel/k_process.h +++ b/src/core/hle/kernel/k_process.h @@ -338,7 +338,8 @@ public: * @returns ResultSuccess if all relevant metadata was able to be * loaded and parsed. Otherwise, an error code is returned. */ - Result LoadFromMetadata(const FileSys::ProgramMetadata& metadata, std::size_t code_size); + Result LoadFromMetadata(const FileSys::ProgramMetadata& metadata, std::size_t code_size, + bool is_hbl); /** * Starts the main application thread for this process. @@ -368,6 +369,10 @@ public: return GetProcessId(); } + bool IsHbl() const { + return m_is_hbl; + } + bool IsSignaled() const override; void DoWorkerTaskImpl(); @@ -525,6 +530,7 @@ private: bool m_is_immortal{}; bool m_is_handle_table_initialized{}; bool m_is_initialized{}; + bool m_is_hbl{}; std::atomic<u16> m_num_running_threads{}; diff --git a/src/core/hle/kernel/svc/svc_debug_string.cpp b/src/core/hle/kernel/svc/svc_debug_string.cpp index 4c14ce668..00b65429b 100644 --- a/src/core/hle/kernel/svc/svc_debug_string.cpp +++ b/src/core/hle/kernel/svc/svc_debug_string.cpp @@ -14,7 +14,7 @@ Result OutputDebugString(Core::System& system, u64 address, u64 len) { std::string str(len, '\0'); GetCurrentMemory(system.Kernel()).ReadBlock(address, str.data(), str.size()); - LOG_DEBUG(Debug_Emulated, "{}", str); + LOG_INFO(Debug_Emulated, "{}", str); R_SUCCEED(); } diff --git a/src/core/hle/kernel/svc/svc_exception.cpp b/src/core/hle/kernel/svc/svc_exception.cpp index 580cf2f75..c581c086b 100644 --- a/src/core/hle/kernel/svc/svc_exception.cpp +++ b/src/core/hle/kernel/svc/svc_exception.cpp @@ -3,6 +3,7 @@ #include "core/core.h" #include "core/debugger/debugger.h" +#include "core/hle/kernel/k_process.h" #include "core/hle/kernel/k_thread.h" #include "core/hle/kernel/svc.h" #include "core/hle/kernel/svc_types.h" @@ -107,7 +108,10 @@ void Break(Core::System& system, BreakReason reason, u64 info1, u64 info2) { system.ArmInterface(static_cast<std::size_t>(thread_processor_id)).LogBacktrace(); } - if (system.DebuggerEnabled()) { + const bool is_hbl = GetCurrentProcess(system.Kernel()).IsHbl(); + const bool should_break = is_hbl || !notification_only; + + if (system.DebuggerEnabled() && should_break) { auto* thread = system.Kernel().GetCurrentEmuThread(); system.GetDebugger().NotifyThreadStopped(thread); thread->RequestSuspend(Kernel::SuspendType::Debug); diff --git a/src/core/hle/result.h b/src/core/hle/result.h index 92a1439eb..dd0b27f47 100644 --- a/src/core/hle/result.h +++ b/src/core/hle/result.h @@ -62,7 +62,7 @@ enum class ErrorModule : u32 { XCD = 108, TMP451 = 109, NIFM = 110, - Hwopus = 111, + HwOpus = 111, LSM6DS3 = 112, Bluetooth = 113, VI = 114, diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp index da33f0e44..819dea6a7 100644 --- a/src/core/hle/service/am/am.cpp +++ b/src/core/hle/service/am/am.cpp @@ -8,6 +8,7 @@ #include "common/settings.h" #include "common/settings_enums.h" #include "core/core.h" +#include "core/core_timing.h" #include "core/file_sys/control_metadata.h" #include "core/file_sys/patch_manager.h" #include "core/file_sys/registered_cache.h" @@ -19,6 +20,8 @@ #include "core/hle/service/am/am.h" #include "core/hle/service/am/applet_ae.h" #include "core/hle/service/am/applet_oe.h" +#include "core/hle/service/am/applets/applet_cabinet.h" +#include "core/hle/service/am/applets/applet_mii_edit_types.h" #include "core/hle/service/am/applets/applet_profile_select.h" #include "core/hle/service/am/applets/applet_web_browser.h" #include "core/hle/service/am/applets/applets.h" @@ -32,11 +35,13 @@ #include "core/hle/service/filesystem/filesystem.h" #include "core/hle/service/ipc_helpers.h" #include "core/hle/service/ns/ns.h" +#include "core/hle/service/nvnflinger/fb_share_buffer_manager.h" #include "core/hle/service/nvnflinger/nvnflinger.h" #include "core/hle/service/pm/pm.h" #include "core/hle/service/server_manager.h" #include "core/hle/service/sm/sm.h" #include "core/hle/service/vi/vi.h" +#include "core/hle/service/vi/vi_results.h" #include "core/memory.h" namespace Service::AM { @@ -46,7 +51,7 @@ constexpr Result ResultNoMessages{ErrorModule::AM, 3}; constexpr Result ResultInvalidOffset{ErrorModule::AM, 503}; enum class LaunchParameterKind : u32 { - ApplicationSpecific = 1, + UserChannel = 1, AccountPreselectedUser = 2, }; @@ -189,8 +194,8 @@ IDisplayController::IDisplayController(Core::System& system_) {4, nullptr, "UpdateCallerAppletCaptureImage"}, {5, nullptr, "GetLastForegroundCaptureImageEx"}, {6, nullptr, "GetLastApplicationCaptureImageEx"}, - {7, nullptr, "GetCallerAppletCaptureImageEx"}, - {8, nullptr, "TakeScreenShotOfOwnLayer"}, + {7, &IDisplayController::GetCallerAppletCaptureImageEx, "GetCallerAppletCaptureImageEx"}, + {8, &IDisplayController::TakeScreenShotOfOwnLayer, "TakeScreenShotOfOwnLayer"}, {9, nullptr, "CopyBetweenCaptureBuffers"}, {10, nullptr, "AcquireLastApplicationCaptureBuffer"}, {11, nullptr, "ReleaseLastApplicationCaptureBuffer"}, @@ -207,8 +212,8 @@ IDisplayController::IDisplayController(Core::System& system_) {23, nullptr, "ReleaseLastApplicationCaptureSharedBuffer"}, {24, nullptr, "AcquireLastForegroundCaptureSharedBuffer"}, {25, nullptr, "ReleaseLastForegroundCaptureSharedBuffer"}, - {26, nullptr, "AcquireCallerAppletCaptureSharedBuffer"}, - {27, nullptr, "ReleaseCallerAppletCaptureSharedBuffer"}, + {26, &IDisplayController::AcquireCallerAppletCaptureSharedBuffer, "AcquireCallerAppletCaptureSharedBuffer"}, + {27, &IDisplayController::ReleaseCallerAppletCaptureSharedBuffer, "ReleaseCallerAppletCaptureSharedBuffer"}, {28, nullptr, "TakeScreenShotOfOwnLayerEx"}, }; // clang-format on @@ -218,6 +223,38 @@ IDisplayController::IDisplayController(Core::System& system_) IDisplayController::~IDisplayController() = default; +void IDisplayController::GetCallerAppletCaptureImageEx(HLERequestContext& ctx) { + LOG_WARNING(Service_AM, "(STUBBED) called"); + + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(ResultSuccess); + rb.Push(1u); + rb.Push(0); +} + +void IDisplayController::TakeScreenShotOfOwnLayer(HLERequestContext& ctx) { + LOG_WARNING(Service_AM, "(STUBBED) called"); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSuccess); +} + +void IDisplayController::AcquireCallerAppletCaptureSharedBuffer(HLERequestContext& ctx) { + LOG_WARNING(Service_AM, "(STUBBED) called"); + + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(ResultSuccess); + rb.Push(1U); + rb.Push(0); +} + +void IDisplayController::ReleaseCallerAppletCaptureSharedBuffer(HLERequestContext& ctx) { + LOG_WARNING(Service_AM, "(STUBBED) called"); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSuccess); +} + IDebugFunctions::IDebugFunctions(Core::System& system_) : ServiceFramework{system_, "IDebugFunctions"} { // clang-format off @@ -277,14 +314,14 @@ ISelfController::ISelfController(Core::System& system_, Nvnflinger::Nvnflinger& {20, nullptr, "SetDesirableKeyboardLayout"}, {21, nullptr, "GetScreenShotProgramId"}, {40, &ISelfController::CreateManagedDisplayLayer, "CreateManagedDisplayLayer"}, - {41, nullptr, "IsSystemBufferSharingEnabled"}, - {42, nullptr, "GetSystemSharedLayerHandle"}, - {43, nullptr, "GetSystemSharedBufferHandle"}, + {41, &ISelfController::IsSystemBufferSharingEnabled, "IsSystemBufferSharingEnabled"}, + {42, &ISelfController::GetSystemSharedLayerHandle, "GetSystemSharedLayerHandle"}, + {43, &ISelfController::GetSystemSharedBufferHandle, "GetSystemSharedBufferHandle"}, {44, &ISelfController::CreateManagedDisplaySeparableLayer, "CreateManagedDisplaySeparableLayer"}, {45, nullptr, "SetManagedDisplayLayerSeparationMode"}, {46, nullptr, "SetRecordingLayerCompositionEnabled"}, {50, &ISelfController::SetHandlesRequestToDisplay, "SetHandlesRequestToDisplay"}, - {51, nullptr, "ApproveToDisplay"}, + {51, &ISelfController::ApproveToDisplay, "ApproveToDisplay"}, {60, nullptr, "OverrideAutoSleepTimeAndDimmingTime"}, {61, nullptr, "SetMediaPlaybackState"}, {62, &ISelfController::SetIdleTimeDetectionExtension, "SetIdleTimeDetectionExtension"}, @@ -341,7 +378,7 @@ void ISelfController::Exit(HLERequestContext& ctx) { void ISelfController::LockExit(HLERequestContext& ctx) { LOG_DEBUG(Service_AM, "called"); - system.SetExitLock(true); + system.SetExitLocked(true); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(ResultSuccess); @@ -350,10 +387,14 @@ void ISelfController::LockExit(HLERequestContext& ctx) { void ISelfController::UnlockExit(HLERequestContext& ctx) { LOG_DEBUG(Service_AM, "called"); - system.SetExitLock(false); + system.SetExitLocked(false); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(ResultSuccess); + + if (system.GetExitRequested()) { + system.Exit(); + } } void ISelfController::EnterFatalSection(HLERequestContext& ctx) { @@ -479,6 +520,50 @@ void ISelfController::CreateManagedDisplayLayer(HLERequestContext& ctx) { rb.Push(*layer_id); } +void ISelfController::IsSystemBufferSharingEnabled(HLERequestContext& ctx) { + LOG_WARNING(Service_AM, "(STUBBED) called"); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(this->EnsureBufferSharingEnabled()); +} + +void ISelfController::GetSystemSharedLayerHandle(HLERequestContext& ctx) { + LOG_WARNING(Service_AM, "(STUBBED) called"); + + IPC::ResponseBuilder rb{ctx, 6}; + rb.Push(this->EnsureBufferSharingEnabled()); + rb.Push<s64>(system_shared_buffer_id); + rb.Push<s64>(system_shared_layer_id); +} + +void ISelfController::GetSystemSharedBufferHandle(HLERequestContext& ctx) { + LOG_WARNING(Service_AM, "(STUBBED) called"); + + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(this->EnsureBufferSharingEnabled()); + rb.Push<s64>(system_shared_buffer_id); +} + +Result ISelfController::EnsureBufferSharingEnabled() { + if (buffer_sharing_enabled) { + return ResultSuccess; + } + + if (system.GetAppletManager().GetCurrentAppletId() <= Applets::AppletId::Application) { + return VI::ResultOperationFailed; + } + + const auto display_id = nvnflinger.OpenDisplay("Default"); + const auto result = nvnflinger.GetSystemBufferManager().Initialize( + &system_shared_buffer_id, &system_shared_layer_id, *display_id); + + if (result.IsSuccess()) { + buffer_sharing_enabled = true; + } + + return result; +} + void ISelfController::CreateManagedDisplaySeparableLayer(HLERequestContext& ctx) { LOG_WARNING(Service_AM, "(STUBBED) called"); @@ -504,6 +589,13 @@ void ISelfController::SetHandlesRequestToDisplay(HLERequestContext& ctx) { rb.Push(ResultSuccess); } +void ISelfController::ApproveToDisplay(HLERequestContext& ctx) { + LOG_WARNING(Service_AM, "(STUBBED) called"); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSuccess); +} + void ISelfController::SetIdleTimeDetectionExtension(HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; idle_time_detection_extension = rp.Pop<u32>(); @@ -674,7 +766,8 @@ void AppletMessageQueue::OperationModeChanged() { ICommonStateGetter::ICommonStateGetter(Core::System& system_, std::shared_ptr<AppletMessageQueue> msg_queue_) - : ServiceFramework{system_, "ICommonStateGetter"}, msg_queue{std::move(msg_queue_)} { + : ServiceFramework{system_, "ICommonStateGetter"}, msg_queue{std::move(msg_queue_)}, + service_context{system_, "ICommonStateGetter"} { // clang-format off static const FunctionInfo functions[] = { {0, &ICommonStateGetter::GetEventHandle, "GetEventHandle"}, @@ -687,10 +780,10 @@ ICommonStateGetter::ICommonStateGetter(Core::System& system_, {7, nullptr, "GetCradleStatus"}, {8, &ICommonStateGetter::GetBootMode, "GetBootMode"}, {9, &ICommonStateGetter::GetCurrentFocusState, "GetCurrentFocusState"}, - {10, nullptr, "RequestToAcquireSleepLock"}, + {10, &ICommonStateGetter::RequestToAcquireSleepLock, "RequestToAcquireSleepLock"}, {11, nullptr, "ReleaseSleepLock"}, {12, nullptr, "ReleaseSleepLockTransiently"}, - {13, nullptr, "GetAcquiredSleepLockEvent"}, + {13, &ICommonStateGetter::GetAcquiredSleepLockEvent, "GetAcquiredSleepLockEvent"}, {14, nullptr, "GetWakeupCount"}, {20, nullptr, "PushToGeneralChannel"}, {30, nullptr, "GetHomeButtonReaderLockAccessor"}, @@ -720,7 +813,7 @@ ICommonStateGetter::ICommonStateGetter(Core::System& system_, {110, nullptr, "OpenMyGpuErrorHandler"}, {120, nullptr, "GetAppletLaunchedHistory"}, {200, nullptr, "GetOperationModeSystemInfo"}, - {300, nullptr, "GetSettingsPlatformRegion"}, + {300, &ICommonStateGetter::GetSettingsPlatformRegion, "GetSettingsPlatformRegion"}, {400, nullptr, "ActivateMigrationService"}, {401, nullptr, "DeactivateMigrationService"}, {500, nullptr, "DisableSleepTillShutdown"}, @@ -732,6 +825,12 @@ ICommonStateGetter::ICommonStateGetter(Core::System& system_, // clang-format on RegisterHandlers(functions); + + sleep_lock_event = service_context.CreateEvent("ICommonStateGetter::SleepLockEvent"); + + // Configure applets to be in foreground state + msg_queue->PushMessage(AppletMessageQueue::AppletMessage::FocusStateChanged); + msg_queue->PushMessage(AppletMessageQueue::AppletMessage::ChangeIntoForeground); } ICommonStateGetter::~ICommonStateGetter() = default; @@ -777,6 +876,24 @@ void ICommonStateGetter::GetCurrentFocusState(HLERequestContext& ctx) { rb.Push(static_cast<u8>(FocusState::InFocus)); } +void ICommonStateGetter::RequestToAcquireSleepLock(HLERequestContext& ctx) { + LOG_WARNING(Service_AM, "(STUBBED) called"); + + // Sleep lock is acquired immediately. + sleep_lock_event->Signal(); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSuccess); +} + +void ICommonStateGetter::GetAcquiredSleepLockEvent(HLERequestContext& ctx) { + LOG_WARNING(Service_AM, "called"); + + IPC::ResponseBuilder rb{ctx, 2, 1}; + rb.Push(ResultSuccess); + rb.PushCopyObjects(sleep_lock_event->GetReadableEvent()); +} + void ICommonStateGetter::IsVrModeEnabled(HLERequestContext& ctx) { LOG_DEBUG(Service_AM, "called"); @@ -863,6 +980,14 @@ void ICommonStateGetter::PerformSystemButtonPressingIfInFocus(HLERequestContext& rb.Push(ResultSuccess); } +void ICommonStateGetter::GetSettingsPlatformRegion(HLERequestContext& ctx) { + LOG_WARNING(Service_AM, "(STUBBED) called"); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(ResultSuccess); + rb.PushEnum(SysPlatformRegion::Global); +} + void ICommonStateGetter::SetRequestExitToLibraryAppletAtExecuteNextProgramEnabled( HLERequestContext& ctx) { LOG_WARNING(Service_AM, "(STUBBED) called"); @@ -1320,18 +1445,19 @@ void ILibraryAppletCreator::CreateHandleStorage(HLERequestContext& ctx) { ILibraryAppletSelfAccessor::ILibraryAppletSelfAccessor(Core::System& system_) : ServiceFramework{system_, "ILibraryAppletSelfAccessor"} { + // clang-format off static const FunctionInfo functions[] = { - {0, nullptr, "PopInData"}, - {1, nullptr, "PushOutData"}, + {0, &ILibraryAppletSelfAccessor::PopInData, "PopInData"}, + {1, &ILibraryAppletSelfAccessor::PushOutData, "PushOutData"}, {2, nullptr, "PopInteractiveInData"}, {3, nullptr, "PushInteractiveOutData"}, {5, nullptr, "GetPopInDataEvent"}, {6, nullptr, "GetPopInteractiveInDataEvent"}, - {10, nullptr, "ExitProcessAndReturn"}, - {11, nullptr, "GetLibraryAppletInfo"}, + {10, &ILibraryAppletSelfAccessor::ExitProcessAndReturn, "ExitProcessAndReturn"}, + {11, &ILibraryAppletSelfAccessor::GetLibraryAppletInfo, "GetLibraryAppletInfo"}, {12, nullptr, "GetMainAppletIdentityInfo"}, {13, nullptr, "CanUseApplicationCore"}, - {14, nullptr, "GetCallerAppletIdentityInfo"}, + {14, &ILibraryAppletSelfAccessor::GetCallerAppletIdentityInfo, "GetCallerAppletIdentityInfo"}, {15, nullptr, "GetMainAppletApplicationControlProperty"}, {16, nullptr, "GetMainAppletStorageId"}, {17, nullptr, "GetCallerAppletIdentityInfoStack"}, @@ -1357,10 +1483,180 @@ ILibraryAppletSelfAccessor::ILibraryAppletSelfAccessor(Core::System& system_) {140, nullptr, "SetApplicationMemoryReservation"}, {150, nullptr, "ShouldSetGpuTimeSliceManually"}, }; + // clang-format on RegisterHandlers(functions); + + switch (system.GetAppletManager().GetCurrentAppletId()) { + case Applets::AppletId::Cabinet: + PushInShowCabinetData(); + break; + case Applets::AppletId::MiiEdit: + PushInShowMiiEditData(); + break; + default: + break; + } } ILibraryAppletSelfAccessor::~ILibraryAppletSelfAccessor() = default; +void ILibraryAppletSelfAccessor::PopInData(HLERequestContext& ctx) { + LOG_INFO(Service_AM, "called"); + + if (queue_data.empty()) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultNoDataInChannel); + return; + } + + auto data = queue_data.front(); + queue_data.pop_front(); + + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(ResultSuccess); + rb.PushIpcInterface<IStorage>(system, std::move(data)); +} + +void ILibraryAppletSelfAccessor::PushOutData(HLERequestContext& ctx) { + LOG_WARNING(Service_AM, "(STUBBED) called"); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSuccess); +} + +void ILibraryAppletSelfAccessor::ExitProcessAndReturn(HLERequestContext& ctx) { + LOG_WARNING(Service_AM, "(STUBBED) called"); + + system.Exit(); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSuccess); +} + +void ILibraryAppletSelfAccessor::GetLibraryAppletInfo(HLERequestContext& ctx) { + struct LibraryAppletInfo { + Applets::AppletId applet_id; + Applets::LibraryAppletMode library_applet_mode; + }; + + LOG_WARNING(Service_AM, "(STUBBED) called"); + + const LibraryAppletInfo applet_info{ + .applet_id = system.GetAppletManager().GetCurrentAppletId(), + .library_applet_mode = Applets::LibraryAppletMode::AllForeground, + }; + + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(ResultSuccess); + rb.PushRaw(applet_info); +} + +void ILibraryAppletSelfAccessor::GetCallerAppletIdentityInfo(HLERequestContext& ctx) { + struct AppletIdentityInfo { + Applets::AppletId applet_id; + INSERT_PADDING_BYTES(0x4); + u64 application_id; + }; + + LOG_WARNING(Service_AM, "(STUBBED) called"); + + const AppletIdentityInfo applet_info{ + .applet_id = Applets::AppletId::QLaunch, + .application_id = 0x0100000000001000ull, + }; + + IPC::ResponseBuilder rb{ctx, 6}; + rb.Push(ResultSuccess); + rb.PushRaw(applet_info); +} + +void ILibraryAppletSelfAccessor::PushInShowCabinetData() { + const Applets::CommonArguments arguments{ + .arguments_version = Applets::CommonArgumentVersion::Version3, + .size = Applets::CommonArgumentSize::Version3, + .library_version = static_cast<u32>(Applets::CabinetAppletVersion::Version1), + .theme_color = Applets::ThemeColor::BasicBlack, + .play_startup_sound = true, + .system_tick = system.CoreTiming().GetClockTicks(), + }; + + const Applets::StartParamForAmiiboSettings amiibo_settings{ + .param_1 = 0, + .applet_mode = system.GetAppletManager().GetCabinetMode(), + .flags = Applets::CabinetFlags::None, + .amiibo_settings_1 = 0, + .device_handle = 0, + .tag_info{}, + .register_info{}, + .amiibo_settings_3{}, + }; + + std::vector<u8> argument_data(sizeof(arguments)); + std::vector<u8> settings_data(sizeof(amiibo_settings)); + std::memcpy(argument_data.data(), &arguments, sizeof(arguments)); + std::memcpy(settings_data.data(), &amiibo_settings, sizeof(amiibo_settings)); + queue_data.emplace_back(std::move(argument_data)); + queue_data.emplace_back(std::move(settings_data)); +} + +void ILibraryAppletSelfAccessor::PushInShowMiiEditData() { + struct MiiEditV3 { + Applets::MiiEditAppletInputCommon common; + Applets::MiiEditAppletInputV3 input; + }; + static_assert(sizeof(MiiEditV3) == 0x100, "MiiEditV3 has incorrect size."); + + MiiEditV3 mii_arguments{ + .common = + { + .version = Applets::MiiEditAppletVersion::Version3, + .applet_mode = Applets::MiiEditAppletMode::ShowMiiEdit, + }, + .input{}, + }; + + std::vector<u8> argument_data(sizeof(mii_arguments)); + std::memcpy(argument_data.data(), &mii_arguments, sizeof(mii_arguments)); + + queue_data.emplace_back(std::move(argument_data)); +} + +IAppletCommonFunctions::IAppletCommonFunctions(Core::System& system_) + : ServiceFramework{system_, "IAppletCommonFunctions"} { + // clang-format off + static const FunctionInfo functions[] = { + {0, nullptr, "SetTerminateResult"}, + {10, nullptr, "ReadThemeStorage"}, + {11, nullptr, "WriteThemeStorage"}, + {20, nullptr, "PushToAppletBoundChannel"}, + {21, nullptr, "TryPopFromAppletBoundChannel"}, + {40, nullptr, "GetDisplayLogicalResolution"}, + {42, nullptr, "SetDisplayMagnification"}, + {50, nullptr, "SetHomeButtonDoubleClickEnabled"}, + {51, nullptr, "GetHomeButtonDoubleClickEnabled"}, + {52, nullptr, "IsHomeButtonShortPressedBlocked"}, + {60, nullptr, "IsVrModeCurtainRequired"}, + {61, nullptr, "IsSleepRequiredByHighTemperature"}, + {62, nullptr, "IsSleepRequiredByLowBattery"}, + {70, &IAppletCommonFunctions::SetCpuBoostRequestPriority, "SetCpuBoostRequestPriority"}, + {80, nullptr, "SetHandlingCaptureButtonShortPressedMessageEnabledForApplet"}, + {81, nullptr, "SetHandlingCaptureButtonLongPressedMessageEnabledForApplet"}, + {90, nullptr, "OpenNamedChannelAsParent"}, + {91, nullptr, "OpenNamedChannelAsChild"}, + {100, nullptr, "SetApplicationCoreUsageMode"}, + }; + // clang-format on + + RegisterHandlers(functions); +} + +IAppletCommonFunctions::~IAppletCommonFunctions() = default; + +void IAppletCommonFunctions::SetCpuBoostRequestPriority(HLERequestContext& ctx) { + LOG_WARNING(Service_AM, "(STUBBED) called"); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSuccess); +} IApplicationFunctions::IApplicationFunctions(Core::System& system_) : ServiceFramework{system_, "IApplicationFunctions"}, service_context{system, @@ -1382,7 +1678,7 @@ IApplicationFunctions::IApplicationFunctions(Core::System& system_) {25, &IApplicationFunctions::ExtendSaveData, "ExtendSaveData"}, {26, &IApplicationFunctions::GetSaveDataSize, "GetSaveDataSize"}, {27, &IApplicationFunctions::CreateCacheStorage, "CreateCacheStorage"}, - {28, nullptr, "GetSaveDataSizeMax"}, + {28, &IApplicationFunctions::GetSaveDataSizeMax, "GetSaveDataSizeMax"}, {29, nullptr, "GetCacheStorageMax"}, {30, &IApplicationFunctions::BeginBlockingHomeButtonShortAndLongPressed, "BeginBlockingHomeButtonShortAndLongPressed"}, {31, &IApplicationFunctions::EndBlockingHomeButtonShortAndLongPressed, "EndBlockingHomeButtonShortAndLongPressed"}, @@ -1514,27 +1810,26 @@ void IApplicationFunctions::PopLaunchParameter(HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const auto kind = rp.PopEnum<LaunchParameterKind>(); - LOG_DEBUG(Service_AM, "called, kind={:08X}", kind); - - if (kind == LaunchParameterKind::ApplicationSpecific && !launch_popped_application_specific) { - const auto backend = BCAT::CreateBackendFromSettings(system, [this](u64 tid) { - return system.GetFileSystemController().GetBCATDirectory(tid); - }); - const auto build_id_full = system.GetApplicationProcessBuildID(); - u64 build_id{}; - std::memcpy(&build_id, build_id_full.data(), sizeof(u64)); - - auto data = - backend->GetLaunchParameter({system.GetApplicationProcessProgramID(), build_id}); - if (data.has_value()) { - IPC::ResponseBuilder rb{ctx, 2, 0, 1}; - rb.Push(ResultSuccess); - rb.PushIpcInterface<IStorage>(system, std::move(*data)); - launch_popped_application_specific = true; + LOG_INFO(Service_AM, "called, kind={:08X}", kind); + + if (kind == LaunchParameterKind::UserChannel) { + auto channel = system.GetUserChannel(); + if (channel.empty()) { + LOG_ERROR(Service_AM, "Attempted to load launch parameter but none was found!"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(AM::ResultNoDataInChannel); return; } + + auto data = channel.back(); + channel.pop_back(); + + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(ResultSuccess); + rb.PushIpcInterface<IStorage>(system, std::move(data)); } else if (kind == LaunchParameterKind::AccountPreselectedUser && !launch_popped_account_preselect) { + // TODO: Verify this is hw-accurate LaunchParameterAccountPreselectedUser params{}; params.magic = LAUNCH_PARAMETER_ACCOUNT_PRESELECTED_USER_MAGIC; @@ -1546,7 +1841,6 @@ void IApplicationFunctions::PopLaunchParameter(HLERequestContext& ctx) { params.current_user = *uuid; IPC::ResponseBuilder rb{ctx, 2, 0, 1}; - rb.Push(ResultSuccess); std::vector<u8> buffer(sizeof(LaunchParameterAccountPreselectedUser)); @@ -1554,12 +1848,11 @@ void IApplicationFunctions::PopLaunchParameter(HLERequestContext& ctx) { rb.PushIpcInterface<IStorage>(system, std::move(buffer)); launch_popped_account_preselect = true; - return; + } else { + LOG_ERROR(Service_AM, "Unknown launch parameter kind."); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(AM::ResultNoDataInChannel); } - - LOG_ERROR(Service_AM, "Attempted to load launch parameter but none was found!"); - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(AM::ResultNoDataInChannel); } void IApplicationFunctions::CreateApplicationAndRequestToStartForQuest(HLERequestContext& ctx) { @@ -1820,6 +2113,18 @@ void IApplicationFunctions::CreateCacheStorage(HLERequestContext& ctx) { rb.PushRaw(resp); } +void IApplicationFunctions::GetSaveDataSizeMax(HLERequestContext& ctx) { + LOG_WARNING(Service_AM, "(STUBBED) called"); + + constexpr u64 size_max_normal = 0xFFFFFFF; + constexpr u64 size_max_journal = 0xFFFFFFF; + + IPC::ResponseBuilder rb{ctx, 6}; + rb.Push(ResultSuccess); + rb.Push(size_max_normal); + rb.Push(size_max_journal); +} + void IApplicationFunctions::QueryApplicationPlayStatistics(HLERequestContext& ctx) { LOG_WARNING(Service_AM, "(STUBBED) called"); @@ -1851,14 +2156,22 @@ void IApplicationFunctions::ExecuteProgram(HLERequestContext& ctx) { } void IApplicationFunctions::ClearUserChannel(HLERequestContext& ctx) { - LOG_WARNING(Service_AM, "(STUBBED) called"); + LOG_DEBUG(Service_AM, "called"); + + system.GetUserChannel().clear(); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(ResultSuccess); } void IApplicationFunctions::UnpopToUserChannel(HLERequestContext& ctx) { - LOG_WARNING(Service_AM, "(STUBBED) called"); + LOG_DEBUG(Service_AM, "called"); + + IPC::RequestParser rp{ctx}; + const auto storage = rp.PopIpcInterface<IStorage>().lock(); + if (storage) { + system.GetUserChannel().push_back(storage->GetData()); + } IPC::ResponseBuilder rb{ctx, 2}; rb.Push(ResultSuccess); @@ -1920,9 +2233,6 @@ void IApplicationFunctions::PrepareForJit(HLERequestContext& ctx) { void LoopProcess(Nvnflinger::Nvnflinger& nvnflinger, Core::System& system) { auto message_queue = std::make_shared<AppletMessageQueue>(system); - // Needed on game boot - message_queue->PushMessage(AppletMessageQueue::AppletMessage::FocusStateChanged); - auto server_manager = std::make_unique<ServerManager>(system); server_manager->RegisterNamedService( @@ -2028,8 +2338,8 @@ IProcessWindingController::IProcessWindingController(Core::System& system_) : ServiceFramework{system_, "IProcessWindingController"} { // clang-format off static const FunctionInfo functions[] = { - {0, nullptr, "GetLaunchReason"}, - {11, nullptr, "OpenCallingLibraryApplet"}, + {0, &IProcessWindingController::GetLaunchReason, "GetLaunchReason"}, + {11, &IProcessWindingController::OpenCallingLibraryApplet, "OpenCallingLibraryApplet"}, {21, nullptr, "PushContext"}, {22, nullptr, "PopContext"}, {23, nullptr, "CancelWindingReservation"}, @@ -2043,4 +2353,47 @@ IProcessWindingController::IProcessWindingController(Core::System& system_) } IProcessWindingController::~IProcessWindingController() = default; + +void IProcessWindingController::GetLaunchReason(HLERequestContext& ctx) { + LOG_WARNING(Service_AM, "(STUBBED) called"); + + struct AppletProcessLaunchReason { + u8 flag; + INSERT_PADDING_BYTES(3); + }; + static_assert(sizeof(AppletProcessLaunchReason) == 0x4, + "AppletProcessLaunchReason is an invalid size"); + + AppletProcessLaunchReason reason{ + .flag = 0, + }; + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(ResultSuccess); + rb.PushRaw(reason); +} + +void IProcessWindingController::OpenCallingLibraryApplet(HLERequestContext& ctx) { + const auto applet_id = system.GetAppletManager().GetCurrentAppletId(); + const auto applet_mode = Applets::LibraryAppletMode::AllForeground; + + LOG_WARNING(Service_AM, "(STUBBED) called with applet_id={:08X}, applet_mode={:08X}", applet_id, + applet_mode); + + const auto& applet_manager{system.GetAppletManager()}; + const auto applet = applet_manager.GetApplet(applet_id, applet_mode); + + if (applet == nullptr) { + LOG_ERROR(Service_AM, "Applet doesn't exist! applet_id={}", applet_id); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultUnknown); + return; + } + + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(ResultSuccess); + rb.PushIpcInterface<ILibraryAppletAccessor>(system, applet); +} + } // namespace Service::AM diff --git a/src/core/hle/service/am/am.h b/src/core/hle/service/am/am.h index d68998f04..349482dcc 100644 --- a/src/core/hle/service/am/am.h +++ b/src/core/hle/service/am/am.h @@ -120,6 +120,12 @@ class IDisplayController final : public ServiceFramework<IDisplayController> { public: explicit IDisplayController(Core::System& system_); ~IDisplayController() override; + +private: + void GetCallerAppletCaptureImageEx(HLERequestContext& ctx); + void TakeScreenShotOfOwnLayer(HLERequestContext& ctx); + void AcquireCallerAppletCaptureSharedBuffer(HLERequestContext& ctx); + void ReleaseCallerAppletCaptureSharedBuffer(HLERequestContext& ctx); }; class IDebugFunctions final : public ServiceFramework<IDebugFunctions> { @@ -147,9 +153,13 @@ private: void SetRestartMessageEnabled(HLERequestContext& ctx); void SetOutOfFocusSuspendingEnabled(HLERequestContext& ctx); void SetAlbumImageOrientation(HLERequestContext& ctx); + void IsSystemBufferSharingEnabled(HLERequestContext& ctx); + void GetSystemSharedBufferHandle(HLERequestContext& ctx); + void GetSystemSharedLayerHandle(HLERequestContext& ctx); void CreateManagedDisplayLayer(HLERequestContext& ctx); void CreateManagedDisplaySeparableLayer(HLERequestContext& ctx); void SetHandlesRequestToDisplay(HLERequestContext& ctx); + void ApproveToDisplay(HLERequestContext& ctx); void SetIdleTimeDetectionExtension(HLERequestContext& ctx); void GetIdleTimeDetectionExtension(HLERequestContext& ctx); void ReportUserIsActive(HLERequestContext& ctx); @@ -161,6 +171,8 @@ private: void SaveCurrentScreenshot(HLERequestContext& ctx); void SetRecordVolumeMuted(HLERequestContext& ctx); + Result EnsureBufferSharingEnabled(); + enum class ScreenshotPermission : u32 { Inherit = 0, Enable = 1, @@ -176,7 +188,10 @@ private: u32 idle_time_detection_extension = 0; u64 num_fatal_sections_entered = 0; + u64 system_shared_buffer_id = 0; + u64 system_shared_layer_id = 0; bool is_auto_sleep_disabled = false; + bool buffer_sharing_enabled = false; ScreenshotPermission screenshot_permission = ScreenshotPermission::Inherit; }; @@ -212,9 +227,16 @@ private: CaptureButtonLongPressing, }; + enum class SysPlatformRegion : s32 { + Global = 1, + Terra = 2, + }; + void GetEventHandle(HLERequestContext& ctx); void ReceiveMessage(HLERequestContext& ctx); void GetCurrentFocusState(HLERequestContext& ctx); + void RequestToAcquireSleepLock(HLERequestContext& ctx); + void GetAcquiredSleepLockEvent(HLERequestContext& ctx); void GetDefaultDisplayResolutionChangeEvent(HLERequestContext& ctx); void GetOperationMode(HLERequestContext& ctx); void GetPerformanceMode(HLERequestContext& ctx); @@ -227,10 +249,13 @@ private: void GetDefaultDisplayResolution(HLERequestContext& ctx); void SetCpuBoostMode(HLERequestContext& ctx); void PerformSystemButtonPressingIfInFocus(HLERequestContext& ctx); + void GetSettingsPlatformRegion(HLERequestContext& ctx); void SetRequestExitToLibraryAppletAtExecuteNextProgramEnabled(HLERequestContext& ctx); std::shared_ptr<AppletMessageQueue> msg_queue; bool vr_mode_state{}; + Kernel::KEvent* sleep_lock_event; + KernelHelpers::ServiceContext service_context; }; class IStorageImpl { @@ -294,6 +319,27 @@ class ILibraryAppletSelfAccessor final : public ServiceFramework<ILibraryAppletS public: explicit ILibraryAppletSelfAccessor(Core::System& system_); ~ILibraryAppletSelfAccessor() override; + +private: + void PopInData(HLERequestContext& ctx); + void PushOutData(HLERequestContext& ctx); + void GetLibraryAppletInfo(HLERequestContext& ctx); + void ExitProcessAndReturn(HLERequestContext& ctx); + void GetCallerAppletIdentityInfo(HLERequestContext& ctx); + + void PushInShowCabinetData(); + void PushInShowMiiEditData(); + + std::deque<std::vector<u8>> queue_data; +}; + +class IAppletCommonFunctions final : public ServiceFramework<IAppletCommonFunctions> { +public: + explicit IAppletCommonFunctions(Core::System& system_); + ~IAppletCommonFunctions() override; + +private: + void SetCpuBoostRequestPriority(HLERequestContext& ctx); }; class IApplicationFunctions final : public ServiceFramework<IApplicationFunctions> { @@ -316,6 +362,7 @@ private: void ExtendSaveData(HLERequestContext& ctx); void GetSaveDataSize(HLERequestContext& ctx); void CreateCacheStorage(HLERequestContext& ctx); + void GetSaveDataSizeMax(HLERequestContext& ctx); void BeginBlockingHomeButtonShortAndLongPressed(HLERequestContext& ctx); void EndBlockingHomeButtonShortAndLongPressed(HLERequestContext& ctx); void BeginBlockingHomeButton(HLERequestContext& ctx); @@ -339,7 +386,6 @@ private: KernelHelpers::ServiceContext service_context; - bool launch_popped_application_specific = false; bool launch_popped_account_preselect = false; s32 previous_program_index{-1}; Kernel::KEvent* gpu_error_detected_event; @@ -378,6 +424,10 @@ class IProcessWindingController final : public ServiceFramework<IProcessWindingC public: explicit IProcessWindingController(Core::System& system_); ~IProcessWindingController() override; + +private: + void GetLaunchReason(HLERequestContext& ctx); + void OpenCallingLibraryApplet(HLERequestContext& ctx); }; void LoopProcess(Nvnflinger::Nvnflinger& nvnflinger, Core::System& system); diff --git a/src/core/hle/service/am/applet_ae.cpp b/src/core/hle/service/am/applet_ae.cpp index ee9d99a54..eb12312cc 100644 --- a/src/core/hle/service/am/applet_ae.cpp +++ b/src/core/hle/service/am/applet_ae.cpp @@ -27,7 +27,7 @@ public: {10, &ILibraryAppletProxy::GetProcessWindingController, "GetProcessWindingController"}, {11, &ILibraryAppletProxy::GetLibraryAppletCreator, "GetLibraryAppletCreator"}, {20, &ILibraryAppletProxy::OpenLibraryAppletSelfAccessor, "OpenLibraryAppletSelfAccessor"}, - {21, nullptr, "GetAppletCommonFunctions"}, + {21, &ILibraryAppletProxy::GetAppletCommonFunctions, "GetAppletCommonFunctions"}, {22, nullptr, "GetHomeMenuFunctions"}, {23, nullptr, "GetGlobalStateController"}, {1000, &ILibraryAppletProxy::GetDebugFunctions, "GetDebugFunctions"}, @@ -86,28 +86,36 @@ private: rb.PushIpcInterface<IProcessWindingController>(system); } - void GetDebugFunctions(HLERequestContext& ctx) { + void GetLibraryAppletCreator(HLERequestContext& ctx) { LOG_DEBUG(Service_AM, "called"); IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(ResultSuccess); - rb.PushIpcInterface<IDebugFunctions>(system); + rb.PushIpcInterface<ILibraryAppletCreator>(system); } - void GetLibraryAppletCreator(HLERequestContext& ctx) { + void OpenLibraryAppletSelfAccessor(HLERequestContext& ctx) { LOG_DEBUG(Service_AM, "called"); IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(ResultSuccess); - rb.PushIpcInterface<ILibraryAppletCreator>(system); + rb.PushIpcInterface<ILibraryAppletSelfAccessor>(system); } - void OpenLibraryAppletSelfAccessor(HLERequestContext& ctx) { + void GetAppletCommonFunctions(HLERequestContext& ctx) { LOG_DEBUG(Service_AM, "called"); IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(ResultSuccess); - rb.PushIpcInterface<ILibraryAppletSelfAccessor>(system); + rb.PushIpcInterface<IAppletCommonFunctions>(system); + } + + void GetDebugFunctions(HLERequestContext& ctx) { + LOG_DEBUG(Service_AM, "called"); + + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(ResultSuccess); + rb.PushIpcInterface<IDebugFunctions>(system); } Nvnflinger::Nvnflinger& nvnflinger; @@ -133,7 +141,7 @@ public: {20, &ISystemAppletProxy::GetHomeMenuFunctions, "GetHomeMenuFunctions"}, {21, &ISystemAppletProxy::GetGlobalStateController, "GetGlobalStateController"}, {22, &ISystemAppletProxy::GetApplicationCreator, "GetApplicationCreator"}, - {23, nullptr, "GetAppletCommonFunctions"}, + {23, &ISystemAppletProxy::GetAppletCommonFunctions, "GetAppletCommonFunctions"}, {1000, &ISystemAppletProxy::GetDebugFunctions, "GetDebugFunctions"}, }; // clang-format on @@ -182,14 +190,6 @@ private: rb.PushIpcInterface<IDisplayController>(system); } - void GetDebugFunctions(HLERequestContext& ctx) { - LOG_DEBUG(Service_AM, "called"); - - IPC::ResponseBuilder rb{ctx, 2, 0, 1}; - rb.Push(ResultSuccess); - rb.PushIpcInterface<IDebugFunctions>(system); - } - void GetLibraryAppletCreator(HLERequestContext& ctx) { LOG_DEBUG(Service_AM, "called"); @@ -222,6 +222,22 @@ private: rb.PushIpcInterface<IApplicationCreator>(system); } + void GetAppletCommonFunctions(HLERequestContext& ctx) { + LOG_DEBUG(Service_AM, "called"); + + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(ResultSuccess); + rb.PushIpcInterface<IAppletCommonFunctions>(system); + } + + void GetDebugFunctions(HLERequestContext& ctx) { + LOG_DEBUG(Service_AM, "called"); + + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(ResultSuccess); + rb.PushIpcInterface<IDebugFunctions>(system); + } + Nvnflinger::Nvnflinger& nvnflinger; std::shared_ptr<AppletMessageQueue> msg_queue; }; diff --git a/src/core/hle/service/am/applets/applet_cabinet.h b/src/core/hle/service/am/applets/applet_cabinet.h index b56427021..f498796f7 100644 --- a/src/core/hle/service/am/applets/applet_cabinet.h +++ b/src/core/hle/service/am/applets/applet_cabinet.h @@ -29,6 +29,15 @@ enum class CabinetAppletVersion : u32 { Version1 = 0x1, }; +enum class CabinetFlags : u8 { + None = 0, + DeviceHandle = 1 << 0, + TagInfo = 1 << 1, + RegisterInfo = 1 << 2, + All = DeviceHandle | TagInfo | RegisterInfo, +}; +DECLARE_ENUM_FLAG_OPERATORS(CabinetFlags) + enum class CabinetResult : u8 { Cancel = 0, TagInfo = 1 << 1, @@ -51,7 +60,7 @@ static_assert(sizeof(AmiiboSettingsStartParam) == 0x30, struct StartParamForAmiiboSettings { u8 param_1; Service::NFP::CabinetMode applet_mode; - u8 flags; + CabinetFlags flags; u8 amiibo_settings_1; u64 device_handle; Service::NFP::TagInfo tag_info; diff --git a/src/core/hle/service/am/applets/applet_general_backend.cpp b/src/core/hle/service/am/applets/applet_general_backend.cpp index 8b352020e..c0032f652 100644 --- a/src/core/hle/service/am/applets/applet_general_backend.cpp +++ b/src/core/hle/service/am/applets/applet_general_backend.cpp @@ -223,9 +223,9 @@ void StubApplet::Initialize() { const auto data = broker.PeekDataToAppletForDebug(); system.GetReporter().SaveUnimplementedAppletReport( - static_cast<u32>(id), common_args.arguments_version, common_args.library_version, - common_args.theme_color, common_args.play_startup_sound, common_args.system_tick, - data.normal, data.interactive); + static_cast<u32>(id), static_cast<u32>(common_args.arguments_version), + common_args.library_version, static_cast<u32>(common_args.theme_color), + common_args.play_startup_sound, common_args.system_tick, data.normal, data.interactive); LogCurrentStorage(broker, "Initialize"); } diff --git a/src/core/hle/service/am/applets/applet_mii_edit.cpp b/src/core/hle/service/am/applets/applet_mii_edit.cpp index d1f652c09..50adc7c02 100644 --- a/src/core/hle/service/am/applets/applet_mii_edit.cpp +++ b/src/core/hle/service/am/applets/applet_mii_edit.cpp @@ -7,7 +7,9 @@ #include "core/frontend/applets/mii_edit.h" #include "core/hle/service/am/am.h" #include "core/hle/service/am/applets/applet_mii_edit.h" +#include "core/hle/service/mii/mii.h" #include "core/hle/service/mii/mii_manager.h" +#include "core/hle/service/sm/sm.h" namespace Service::AM::Applets { @@ -56,6 +58,12 @@ void MiiEdit::Initialize() { sizeof(MiiEditAppletInputV4)); break; } + + manager = system.ServiceManager().GetService<Mii::MiiDBModule>("mii:e")->GetMiiManager(); + if (manager == nullptr) { + manager = std::make_shared<Mii::MiiManager>(); + } + manager->Initialize(metadata); } bool MiiEdit::TransactionComplete() const { @@ -78,22 +86,49 @@ void MiiEdit::Execute() { // This is a default stub for each of the MiiEdit applet modes. switch (applet_input_common.applet_mode) { case MiiEditAppletMode::ShowMiiEdit: - case MiiEditAppletMode::AppendMii: case MiiEditAppletMode::AppendMiiImage: case MiiEditAppletMode::UpdateMiiImage: MiiEditOutput(MiiEditResult::Success, 0); break; - case MiiEditAppletMode::CreateMii: - case MiiEditAppletMode::EditMii: { - Service::Mii::MiiManager mii_manager; + case MiiEditAppletMode::AppendMii: { + Mii::StoreData store_data{}; + store_data.BuildRandom(Mii::Age::All, Mii::Gender::All, Mii::Race::All); + store_data.SetNickname({u'y', u'u', u'z', u'u'}); + store_data.SetChecksum(); + const auto result = manager->AddOrReplace(metadata, store_data); + + if (result.IsError()) { + MiiEditOutput(MiiEditResult::Cancel, 0); + break; + } + + s32 index = manager->FindIndex(store_data.GetCreateId(), false); + + if (index == -1) { + MiiEditOutput(MiiEditResult::Cancel, 0); + break; + } + + MiiEditOutput(MiiEditResult::Success, index); + break; + } + case MiiEditAppletMode::CreateMii: { + Mii::CharInfo char_info{}; + manager->BuildRandom(char_info, Mii::Age::All, Mii::Gender::All, Mii::Race::All); - const MiiEditCharInfo char_info{ - .mii_info{applet_input_common.applet_mode == MiiEditAppletMode::EditMii - ? applet_input_v4.char_info.mii_info - : mii_manager.BuildDefault(0)}, + const MiiEditCharInfo edit_char_info{ + .mii_info{char_info}, }; - MiiEditOutputForCharInfoEditing(MiiEditResult::Success, char_info); + MiiEditOutputForCharInfoEditing(MiiEditResult::Success, edit_char_info); + break; + } + case MiiEditAppletMode::EditMii: { + const MiiEditCharInfo edit_char_info{ + .mii_info{applet_input_v4.char_info.mii_info}, + }; + + MiiEditOutputForCharInfoEditing(MiiEditResult::Success, edit_char_info); break; } default: @@ -110,6 +145,8 @@ void MiiEdit::MiiEditOutput(MiiEditResult result, s32 index) { .index{index}, }; + LOG_INFO(Input, "called, result={}, index={}", result, index); + std::vector<u8> out_data(sizeof(MiiEditAppletOutput)); std::memcpy(out_data.data(), &applet_output, sizeof(MiiEditAppletOutput)); diff --git a/src/core/hle/service/am/applets/applet_mii_edit.h b/src/core/hle/service/am/applets/applet_mii_edit.h index 3f46fae1b..7ff34af49 100644 --- a/src/core/hle/service/am/applets/applet_mii_edit.h +++ b/src/core/hle/service/am/applets/applet_mii_edit.h @@ -11,6 +11,11 @@ namespace Core { class System; } // namespace Core +namespace Service::Mii { +struct DatabaseSessionMetadata; +class MiiManager; +} // namespace Service::Mii + namespace Service::AM::Applets { class MiiEdit final : public Applet { @@ -40,6 +45,8 @@ private: MiiEditAppletInputV4 applet_input_v4{}; bool is_complete{false}; + std::shared_ptr<Mii::MiiManager> manager = nullptr; + Mii::DatabaseSessionMetadata metadata{}; }; } // namespace Service::AM::Applets diff --git a/src/core/hle/service/am/applets/applet_mii_edit_types.h b/src/core/hle/service/am/applets/applet_mii_edit_types.h index 4705d019f..f3d764073 100644 --- a/src/core/hle/service/am/applets/applet_mii_edit_types.h +++ b/src/core/hle/service/am/applets/applet_mii_edit_types.h @@ -7,7 +7,8 @@ #include "common/common_funcs.h" #include "common/common_types.h" -#include "core/hle/service/mii/types.h" +#include "common/uuid.h" +#include "core/hle/service/mii/types/char_info.h" namespace Service::AM::Applets { diff --git a/src/core/hle/service/am/applets/applets.cpp b/src/core/hle/service/am/applets/applets.cpp index 10afbc2da..89d5434af 100644 --- a/src/core/hle/service/am/applets/applets.cpp +++ b/src/core/hle/service/am/applets/applets.cpp @@ -199,6 +199,14 @@ const AppletFrontendSet& AppletManager::GetAppletFrontendSet() const { return frontend; } +NFP::CabinetMode AppletManager::GetCabinetMode() const { + return cabinet_mode; +} + +AppletId AppletManager::GetCurrentAppletId() const { + return current_applet_id; +} + void AppletManager::SetAppletFrontendSet(AppletFrontendSet set) { if (set.cabinet != nullptr) { frontend.cabinet = std::move(set.cabinet); @@ -237,6 +245,14 @@ void AppletManager::SetAppletFrontendSet(AppletFrontendSet set) { } } +void AppletManager::SetCabinetMode(NFP::CabinetMode mode) { + cabinet_mode = mode; +} + +void AppletManager::SetCurrentAppletId(AppletId applet_id) { + current_applet_id = applet_id; +} + void AppletManager::SetDefaultAppletFrontendSet() { ClearAll(); SetDefaultAppletsIfMissing(); diff --git a/src/core/hle/service/am/applets/applets.h b/src/core/hle/service/am/applets/applets.h index 12f374199..f02bbc450 100644 --- a/src/core/hle/service/am/applets/applets.h +++ b/src/core/hle/service/am/applets/applets.h @@ -34,6 +34,10 @@ class KEvent; class KReadableEvent; } // namespace Kernel +namespace Service::NFP { +enum class CabinetMode : u8; +} // namespace Service::NFP + namespace Service::AM { class IStorage; @@ -41,6 +45,8 @@ class IStorage; namespace Applets { enum class AppletId : u32 { + None = 0x00, + Application = 0x01, OverlayDisplay = 0x02, QLaunch = 0x03, Starter = 0x04, @@ -71,6 +77,32 @@ enum class LibraryAppletMode : u32 { AllForegroundInitiallyHidden = 4, }; +enum class CommonArgumentVersion : u32 { + Version0, + Version1, + Version2, + Version3, +}; + +enum class CommonArgumentSize : u32 { + Version3 = 0x20, +}; + +enum class ThemeColor : u32 { + BasicWhite = 0, + BasicBlack = 3, +}; + +struct CommonArguments { + CommonArgumentVersion arguments_version; + CommonArgumentSize size; + u32 library_version; + ThemeColor theme_color; + bool play_startup_sound; + u64_le system_tick; +}; +static_assert(sizeof(CommonArguments) == 0x20, "CommonArguments has incorrect size."); + class AppletDataBroker final { public: explicit AppletDataBroker(Core::System& system_, LibraryAppletMode applet_mode_); @@ -161,16 +193,6 @@ public: } protected: - struct CommonArguments { - u32_le arguments_version; - u32_le size; - u32_le library_version; - u32_le theme_color; - bool play_startup_sound; - u64_le system_tick; - }; - static_assert(sizeof(CommonArguments) == 0x20, "CommonArguments has incorrect size."); - CommonArguments common_args{}; AppletDataBroker broker; LibraryAppletMode applet_mode; @@ -219,8 +241,12 @@ public: ~AppletManager(); const AppletFrontendSet& GetAppletFrontendSet() const; + NFP::CabinetMode GetCabinetMode() const; + AppletId GetCurrentAppletId() const; void SetAppletFrontendSet(AppletFrontendSet set); + void SetCabinetMode(NFP::CabinetMode mode); + void SetCurrentAppletId(AppletId applet_id); void SetDefaultAppletFrontendSet(); void SetDefaultAppletsIfMissing(); void ClearAll(); @@ -228,6 +254,9 @@ public: std::shared_ptr<Applet> GetApplet(AppletId id, LibraryAppletMode mode) const; private: + AppletId current_applet_id{}; + NFP::CabinetMode cabinet_mode{}; + AppletFrontendSet frontend; Core::System& system; }; diff --git a/src/core/hle/service/aoc/aoc_u.cpp b/src/core/hle/service/aoc/aoc_u.cpp index 38c2138e8..7075ab800 100644 --- a/src/core/hle/service/aoc/aoc_u.cpp +++ b/src/core/hle/service/aoc/aoc_u.cpp @@ -22,6 +22,8 @@ namespace Service::AOC { +constexpr Result ResultNoPurchasedProductInfoAvailable{ErrorModule::NIMShop, 400}; + static bool CheckAOCTitleIDMatchesBase(u64 title_id, u64 base) { return FileSys::GetBaseTitleID(title_id) == base; } @@ -54,8 +56,8 @@ public: {0, &IPurchaseEventManager::SetDefaultDeliveryTarget, "SetDefaultDeliveryTarget"}, {1, &IPurchaseEventManager::SetDeliveryTarget, "SetDeliveryTarget"}, {2, &IPurchaseEventManager::GetPurchasedEventReadableHandle, "GetPurchasedEventReadableHandle"}, - {3, nullptr, "PopPurchasedProductInfo"}, - {4, nullptr, "PopPurchasedProductInfoWithUid"}, + {3, &IPurchaseEventManager::PopPurchasedProductInfo, "PopPurchasedProductInfo"}, + {4, &IPurchaseEventManager::PopPurchasedProductInfoWithUid, "PopPurchasedProductInfoWithUid"}, }; // clang-format on @@ -101,6 +103,20 @@ private: rb.PushCopyObjects(purchased_event->GetReadableEvent()); } + void PopPurchasedProductInfo(HLERequestContext& ctx) { + LOG_DEBUG(Service_AOC, "(STUBBED) called"); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultNoPurchasedProductInfoAvailable); + } + + void PopPurchasedProductInfoWithUid(HLERequestContext& ctx) { + LOG_DEBUG(Service_AOC, "(STUBBED) called"); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultNoPurchasedProductInfoAvailable); + } + KernelHelpers::ServiceContext service_context; Kernel::KEvent* purchased_event; diff --git a/src/core/hle/service/audio/audin_u.cpp b/src/core/hle/service/audio/audin_u.cpp index 526a39130..56fee4591 100644 --- a/src/core/hle/service/audio/audin_u.cpp +++ b/src/core/hle/service/audio/audin_u.cpp @@ -220,7 +220,7 @@ AudInU::AudInU(Core::System& system_) AudInU::~AudInU() = default; void AudInU::ListAudioIns(HLERequestContext& ctx) { - using namespace AudioCore::AudioRenderer; + using namespace AudioCore::Renderer; LOG_DEBUG(Service_Audio, "called"); @@ -240,7 +240,7 @@ void AudInU::ListAudioIns(HLERequestContext& ctx) { } void AudInU::ListAudioInsAutoFiltered(HLERequestContext& ctx) { - using namespace AudioCore::AudioRenderer; + using namespace AudioCore::Renderer; LOG_DEBUG(Service_Audio, "called"); diff --git a/src/core/hle/service/audio/audout_u.cpp b/src/core/hle/service/audio/audout_u.cpp index 23f84a29f..ca683d72c 100644 --- a/src/core/hle/service/audio/audout_u.cpp +++ b/src/core/hle/service/audio/audout_u.cpp @@ -228,7 +228,7 @@ AudOutU::AudOutU(Core::System& system_) AudOutU::~AudOutU() = default; void AudOutU::ListAudioOuts(HLERequestContext& ctx) { - using namespace AudioCore::AudioRenderer; + using namespace AudioCore::Renderer; std::scoped_lock l{impl->mutex}; diff --git a/src/core/hle/service/audio/audren_u.cpp b/src/core/hle/service/audio/audren_u.cpp index b723b65c8..2f09cade5 100644 --- a/src/core/hle/service/audio/audren_u.cpp +++ b/src/core/hle/service/audio/audren_u.cpp @@ -26,7 +26,7 @@ #include "core/hle/service/ipc_helpers.h" #include "core/memory.h" -using namespace AudioCore::AudioRenderer; +using namespace AudioCore::Renderer; namespace Service::Audio { diff --git a/src/core/hle/service/audio/audren_u.h b/src/core/hle/service/audio/audren_u.h index d8e9c8719..3d7993a16 100644 --- a/src/core/hle/service/audio/audren_u.h +++ b/src/core/hle/service/audio/audren_u.h @@ -28,7 +28,7 @@ private: void GetAudioDeviceServiceWithRevisionInfo(HLERequestContext& ctx); KernelHelpers::ServiceContext service_context; - std::unique_ptr<AudioCore::AudioRenderer::Manager> impl; + std::unique_ptr<AudioCore::Renderer::Manager> impl; u32 num_audio_devices{0}; }; diff --git a/src/core/hle/service/audio/errors.h b/src/core/hle/service/audio/errors.h index 3d3d3d97a..c41345f7e 100644 --- a/src/core/hle/service/audio/errors.h +++ b/src/core/hle/service/audio/errors.h @@ -20,4 +20,16 @@ constexpr Result ResultNotSupported{ErrorModule::Audio, 513}; constexpr Result ResultInvalidHandle{ErrorModule::Audio, 1536}; constexpr Result ResultInvalidRevision{ErrorModule::Audio, 1537}; +constexpr Result ResultLibOpusAllocFail{ErrorModule::HwOpus, 7}; +constexpr Result ResultInputDataTooSmall{ErrorModule::HwOpus, 8}; +constexpr Result ResultLibOpusInvalidState{ErrorModule::HwOpus, 6}; +constexpr Result ResultLibOpusUnimplemented{ErrorModule::HwOpus, 5}; +constexpr Result ResultLibOpusInvalidPacket{ErrorModule::HwOpus, 17}; +constexpr Result ResultLibOpusInternalError{ErrorModule::HwOpus, 4}; +constexpr Result ResultBufferTooSmall{ErrorModule::HwOpus, 3}; +constexpr Result ResultLibOpusBadArg{ErrorModule::HwOpus, 2}; +constexpr Result ResultInvalidOpusDSPReturnCode{ErrorModule::HwOpus, 259}; +constexpr Result ResultInvalidOpusSampleRate{ErrorModule::HwOpus, 1001}; +constexpr Result ResultInvalidOpusChannelCount{ErrorModule::HwOpus, 1002}; + } // namespace Service::Audio diff --git a/src/core/hle/service/audio/hwopus.cpp b/src/core/hle/service/audio/hwopus.cpp index fa77007f3..6a7bf9416 100644 --- a/src/core/hle/service/audio/hwopus.cpp +++ b/src/core/hle/service/audio/hwopus.cpp @@ -1,371 +1,506 @@ // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later -#include <chrono> -#include <cstring> #include <memory> #include <vector> -#include <opus.h> -#include <opus_multistream.h> - +#include "audio_core/opus/decoder.h" +#include "audio_core/opus/parameters.h" #include "common/assert.h" #include "common/logging/log.h" #include "common/scratch_buffer.h" +#include "core/core.h" #include "core/hle/service/audio/hwopus.h" #include "core/hle/service/ipc_helpers.h" namespace Service::Audio { -namespace { -struct OpusDeleter { - void operator()(OpusMSDecoder* ptr) const { - opus_multistream_decoder_destroy(ptr); +using namespace AudioCore::OpusDecoder; + +class IHardwareOpusDecoder final : public ServiceFramework<IHardwareOpusDecoder> { +public: + explicit IHardwareOpusDecoder(Core::System& system_, HardwareOpus& hardware_opus) + : ServiceFramework{system_, "IHardwareOpusDecoder"}, + impl{std::make_unique<AudioCore::OpusDecoder::OpusDecoder>(system_, hardware_opus)} { + // clang-format off + static const FunctionInfo functions[] = { + {0, &IHardwareOpusDecoder::DecodeInterleavedOld, "DecodeInterleavedOld"}, + {1, &IHardwareOpusDecoder::SetContext, "SetContext"}, + {2, &IHardwareOpusDecoder::DecodeInterleavedForMultiStreamOld, "DecodeInterleavedForMultiStreamOld"}, + {3, &IHardwareOpusDecoder::SetContextForMultiStream, "SetContextForMultiStream"}, + {4, &IHardwareOpusDecoder::DecodeInterleavedWithPerfOld, "DecodeInterleavedWithPerfOld"}, + {5, &IHardwareOpusDecoder::DecodeInterleavedForMultiStreamWithPerfOld, "DecodeInterleavedForMultiStreamWithPerfOld"}, + {6, &IHardwareOpusDecoder::DecodeInterleavedWithPerfAndResetOld, "DecodeInterleavedWithPerfAndResetOld"}, + {7, &IHardwareOpusDecoder::DecodeInterleavedForMultiStreamWithPerfAndResetOld, "DecodeInterleavedForMultiStreamWithPerfAndResetOld"}, + {8, &IHardwareOpusDecoder::DecodeInterleaved, "DecodeInterleaved"}, + {9, &IHardwareOpusDecoder::DecodeInterleavedForMultiStream, "DecodeInterleavedForMultiStream"}, + }; + // clang-format on + + RegisterHandlers(functions); } -}; -using OpusDecoderPtr = std::unique_ptr<OpusMSDecoder, OpusDeleter>; + Result Initialize(OpusParametersEx& params, Kernel::KTransferMemory* transfer_memory, + u64 transfer_memory_size) { + return impl->Initialize(params, transfer_memory, transfer_memory_size); + } -struct OpusPacketHeader { - // Packet size in bytes. - u32_be size; - // Indicates the final range of the codec's entropy coder. - u32_be final_range; -}; -static_assert(sizeof(OpusPacketHeader) == 0x8, "OpusHeader is an invalid size"); + Result Initialize(OpusMultiStreamParametersEx& params, Kernel::KTransferMemory* transfer_memory, + u64 transfer_memory_size) { + return impl->Initialize(params, transfer_memory, transfer_memory_size); + } -class OpusDecoderState { -public: - /// Describes extra behavior that may be asked of the decoding context. - enum class ExtraBehavior { - /// No extra behavior. - None, +private: + void DecodeInterleavedOld(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; - /// Resets the decoder context back to a freshly initialized state. - ResetContext, - }; + auto input_data{ctx.ReadBuffer(0)}; + output_data.resize_destructive(ctx.GetWriteBufferSize()); - enum class PerfTime { - Disabled, - Enabled, - }; + u32 size{}; + u32 sample_count{}; + auto result = + impl->DecodeInterleaved(&size, nullptr, &sample_count, input_data, output_data, false); + + LOG_DEBUG(Service_Audio, "bytes read 0x{:X} samples generated {}", size, sample_count); + + ctx.WriteBuffer(output_data); - explicit OpusDecoderState(OpusDecoderPtr decoder_, u32 sample_rate_, u32 channel_count_) - : decoder{std::move(decoder_)}, sample_rate{sample_rate_}, channel_count{channel_count_} {} - - // Decodes interleaved Opus packets. Optionally allows reporting time taken to - // perform the decoding, as well as any relevant extra behavior. - void DecodeInterleaved(HLERequestContext& ctx, PerfTime perf_time, - ExtraBehavior extra_behavior) { - if (perf_time == PerfTime::Disabled) { - DecodeInterleavedHelper(ctx, nullptr, extra_behavior); - } else { - u64 performance = 0; - DecodeInterleavedHelper(ctx, &performance, extra_behavior); - } + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(result); + rb.Push(size); + rb.Push(sample_count); } -private: - void DecodeInterleavedHelper(HLERequestContext& ctx, u64* performance, - ExtraBehavior extra_behavior) { - u32 consumed = 0; - u32 sample_count = 0; - samples.resize_destructive(ctx.GetWriteBufferNumElements<opus_int16>()); - - if (extra_behavior == ExtraBehavior::ResetContext) { - ResetDecoderContext(); - } - - if (!DecodeOpusData(consumed, sample_count, ctx.ReadBuffer(), samples, performance)) { - LOG_ERROR(Audio, "Failed to decode opus data"); - IPC::ResponseBuilder rb{ctx, 2}; - // TODO(ogniK): Use correct error code - rb.Push(ResultUnknown); - return; - } - - const u32 param_size = performance != nullptr ? 6 : 4; - IPC::ResponseBuilder rb{ctx, param_size}; - rb.Push(ResultSuccess); - rb.Push<u32>(consumed); - rb.Push<u32>(sample_count); - if (performance) { - rb.Push<u64>(*performance); - } - ctx.WriteBuffer(samples); + void SetContext(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + + LOG_DEBUG(Service_Audio, "called"); + + auto input_data{ctx.ReadBuffer(0)}; + auto result = impl->SetContext(input_data); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(result); } - bool DecodeOpusData(u32& consumed, u32& sample_count, std::span<const u8> input, - std::span<opus_int16> output, u64* out_performance_time) const { - const auto start_time = std::chrono::steady_clock::now(); - const std::size_t raw_output_sz = output.size() * sizeof(opus_int16); - if (sizeof(OpusPacketHeader) > input.size()) { - LOG_ERROR(Audio, "Input is smaller than the header size, header_sz={}, input_sz={}", - sizeof(OpusPacketHeader), input.size()); - return false; - } - - OpusPacketHeader hdr{}; - std::memcpy(&hdr, input.data(), sizeof(OpusPacketHeader)); - if (sizeof(OpusPacketHeader) + static_cast<u32>(hdr.size) > input.size()) { - LOG_ERROR(Audio, "Input does not fit in the opus header size. data_sz={}, input_sz={}", - sizeof(OpusPacketHeader) + static_cast<u32>(hdr.size), input.size()); - return false; - } - - const auto frame = input.data() + sizeof(OpusPacketHeader); - const auto decoded_sample_count = opus_packet_get_nb_samples( - frame, static_cast<opus_int32>(input.size() - sizeof(OpusPacketHeader)), - static_cast<opus_int32>(sample_rate)); - if (decoded_sample_count * channel_count * sizeof(u16) > raw_output_sz) { - LOG_ERROR( - Audio, - "Decoded data does not fit into the output data, decoded_sz={}, raw_output_sz={}", - decoded_sample_count * channel_count * sizeof(u16), raw_output_sz); - return false; - } - - const int frame_size = (static_cast<int>(raw_output_sz / sizeof(s16) / channel_count)); - const auto out_sample_count = - opus_multistream_decode(decoder.get(), frame, hdr.size, output.data(), frame_size, 0); - if (out_sample_count < 0) { - LOG_ERROR(Audio, - "Incorrect sample count received from opus_decode, " - "output_sample_count={}, frame_size={}, data_sz_from_hdr={}", - out_sample_count, frame_size, static_cast<u32>(hdr.size)); - return false; - } - - const auto end_time = std::chrono::steady_clock::now() - start_time; - sample_count = out_sample_count; - consumed = static_cast<u32>(sizeof(OpusPacketHeader) + hdr.size); - if (out_performance_time != nullptr) { - *out_performance_time = - std::chrono::duration_cast<std::chrono::milliseconds>(end_time).count(); - } - - return true; + void DecodeInterleavedForMultiStreamOld(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + + auto input_data{ctx.ReadBuffer(0)}; + output_data.resize_destructive(ctx.GetWriteBufferSize()); + + u32 size{}; + u32 sample_count{}; + auto result = impl->DecodeInterleavedForMultiStream(&size, nullptr, &sample_count, + input_data, output_data, false); + + LOG_DEBUG(Service_Audio, "bytes read 0x{:X} samples generated {}", size, sample_count); + + ctx.WriteBuffer(output_data); + + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(result); + rb.Push(size); + rb.Push(sample_count); } - void ResetDecoderContext() { - ASSERT(decoder != nullptr); + void SetContextForMultiStream(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + + LOG_DEBUG(Service_Audio, "called"); + + auto input_data{ctx.ReadBuffer(0)}; + auto result = impl->SetContext(input_data); - opus_multistream_decoder_ctl(decoder.get(), OPUS_RESET_STATE); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(result); } - OpusDecoderPtr decoder; - u32 sample_rate; - u32 channel_count; - Common::ScratchBuffer<opus_int16> samples; -}; + void DecodeInterleavedWithPerfOld(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; -class IHardwareOpusDecoderManager final : public ServiceFramework<IHardwareOpusDecoderManager> { -public: - explicit IHardwareOpusDecoderManager(Core::System& system_, OpusDecoderState decoder_state_) - : ServiceFramework{system_, "IHardwareOpusDecoderManager"}, decoder_state{ - std::move(decoder_state_)} { - // clang-format off - static const FunctionInfo functions[] = { - {0, &IHardwareOpusDecoderManager::DecodeInterleavedOld, "DecodeInterleavedOld"}, - {1, nullptr, "SetContext"}, - {2, nullptr, "DecodeInterleavedForMultiStreamOld"}, - {3, nullptr, "SetContextForMultiStream"}, - {4, &IHardwareOpusDecoderManager::DecodeInterleavedWithPerfOld, "DecodeInterleavedWithPerfOld"}, - {5, nullptr, "DecodeInterleavedForMultiStreamWithPerfOld"}, - {6, &IHardwareOpusDecoderManager::DecodeInterleaved, "DecodeInterleavedWithPerfAndResetOld"}, - {7, nullptr, "DecodeInterleavedForMultiStreamWithPerfAndResetOld"}, - {8, &IHardwareOpusDecoderManager::DecodeInterleaved, "DecodeInterleaved"}, - {9, nullptr, "DecodeInterleavedForMultiStream"}, - }; - // clang-format on + auto input_data{ctx.ReadBuffer(0)}; + output_data.resize_destructive(ctx.GetWriteBufferSize()); - RegisterHandlers(functions); + u32 size{}; + u32 sample_count{}; + u64 time_taken{}; + auto result = impl->DecodeInterleaved(&size, &time_taken, &sample_count, input_data, + output_data, false); + + LOG_DEBUG(Service_Audio, "bytes read 0x{:X} samples generated {} time taken {}", size, + sample_count, time_taken); + + ctx.WriteBuffer(output_data); + + IPC::ResponseBuilder rb{ctx, 6}; + rb.Push(result); + rb.Push(size); + rb.Push(sample_count); + rb.Push(time_taken); } -private: - void DecodeInterleavedOld(HLERequestContext& ctx) { - LOG_DEBUG(Audio, "called"); + void DecodeInterleavedForMultiStreamWithPerfOld(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + + auto input_data{ctx.ReadBuffer(0)}; + output_data.resize_destructive(ctx.GetWriteBufferSize()); + + u32 size{}; + u32 sample_count{}; + u64 time_taken{}; + auto result = impl->DecodeInterleavedForMultiStream(&size, &time_taken, &sample_count, + input_data, output_data, false); - decoder_state.DecodeInterleaved(ctx, OpusDecoderState::PerfTime::Disabled, - OpusDecoderState::ExtraBehavior::None); + LOG_DEBUG(Service_Audio, "bytes read 0x{:X} samples generated {} time taken {}", size, + sample_count, time_taken); + + ctx.WriteBuffer(output_data); + + IPC::ResponseBuilder rb{ctx, 6}; + rb.Push(result); + rb.Push(size); + rb.Push(sample_count); + rb.Push(time_taken); } - void DecodeInterleavedWithPerfOld(HLERequestContext& ctx) { - LOG_DEBUG(Audio, "called"); + void DecodeInterleavedWithPerfAndResetOld(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + + auto reset{rp.Pop<bool>()}; + + auto input_data{ctx.ReadBuffer(0)}; + output_data.resize_destructive(ctx.GetWriteBufferSize()); + + u32 size{}; + u32 sample_count{}; + u64 time_taken{}; + auto result = impl->DecodeInterleaved(&size, &time_taken, &sample_count, input_data, + output_data, reset); + + LOG_DEBUG(Service_Audio, "reset {} bytes read 0x{:X} samples generated {} time taken {}", + reset, size, sample_count, time_taken); + + ctx.WriteBuffer(output_data); + + IPC::ResponseBuilder rb{ctx, 6}; + rb.Push(result); + rb.Push(size); + rb.Push(sample_count); + rb.Push(time_taken); + } + + void DecodeInterleavedForMultiStreamWithPerfAndResetOld(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + + auto reset{rp.Pop<bool>()}; + + auto input_data{ctx.ReadBuffer(0)}; + output_data.resize_destructive(ctx.GetWriteBufferSize()); + + u32 size{}; + u32 sample_count{}; + u64 time_taken{}; + auto result = impl->DecodeInterleavedForMultiStream(&size, &time_taken, &sample_count, + input_data, output_data, reset); + + LOG_DEBUG(Service_Audio, "reset {} bytes read 0x{:X} samples generated {} time taken {}", + reset, size, sample_count, time_taken); - decoder_state.DecodeInterleaved(ctx, OpusDecoderState::PerfTime::Enabled, - OpusDecoderState::ExtraBehavior::None); + ctx.WriteBuffer(output_data); + + IPC::ResponseBuilder rb{ctx, 6}; + rb.Push(result); + rb.Push(size); + rb.Push(sample_count); + rb.Push(time_taken); } void DecodeInterleaved(HLERequestContext& ctx) { - LOG_DEBUG(Audio, "called"); + IPC::RequestParser rp{ctx}; + + auto reset{rp.Pop<bool>()}; + + auto input_data{ctx.ReadBuffer(0)}; + output_data.resize_destructive(ctx.GetWriteBufferSize()); + + u32 size{}; + u32 sample_count{}; + u64 time_taken{}; + auto result = impl->DecodeInterleaved(&size, &time_taken, &sample_count, input_data, + output_data, reset); + + LOG_DEBUG(Service_Audio, "reset {} bytes read 0x{:X} samples generated {} time taken {}", + reset, size, sample_count, time_taken); + + ctx.WriteBuffer(output_data); + + IPC::ResponseBuilder rb{ctx, 6}; + rb.Push(result); + rb.Push(size); + rb.Push(sample_count); + rb.Push(time_taken); + } + void DecodeInterleavedForMultiStream(HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - const auto extra_behavior = rp.Pop<bool>() ? OpusDecoderState::ExtraBehavior::ResetContext - : OpusDecoderState::ExtraBehavior::None; - decoder_state.DecodeInterleaved(ctx, OpusDecoderState::PerfTime::Enabled, extra_behavior); + auto reset{rp.Pop<bool>()}; + + auto input_data{ctx.ReadBuffer(0)}; + output_data.resize_destructive(ctx.GetWriteBufferSize()); + + u32 size{}; + u32 sample_count{}; + u64 time_taken{}; + auto result = impl->DecodeInterleavedForMultiStream(&size, &time_taken, &sample_count, + input_data, output_data, reset); + + LOG_DEBUG(Service_Audio, "reset {} bytes read 0x{:X} samples generated {} time taken {}", + reset, size, sample_count, time_taken); + + ctx.WriteBuffer(output_data); + + IPC::ResponseBuilder rb{ctx, 6}; + rb.Push(result); + rb.Push(size); + rb.Push(sample_count); + rb.Push(time_taken); } - OpusDecoderState decoder_state; + std::unique_ptr<AudioCore::OpusDecoder::OpusDecoder> impl; + Common::ScratchBuffer<u8> output_data; }; -std::size_t WorkerBufferSize(u32 channel_count) { - ASSERT_MSG(channel_count == 1 || channel_count == 2, "Invalid channel count"); - constexpr int num_streams = 1; - const int num_stereo_streams = channel_count == 2 ? 1 : 0; - return opus_multistream_decoder_get_size(num_streams, num_stereo_streams); -} +void HwOpus::OpenHardwareOpusDecoder(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; -// Creates the mapping table that maps the input channels to the particular -// output channels. In the stereo case, we map the left and right input channels -// to the left and right output channels respectively. -// -// However, in the monophonic case, we only map the one available channel -// to the sole output channel. We specify 255 for the would-be right channel -// as this is a special value defined by Opus to indicate to the decoder to -// ignore that channel. -std::array<u8, 2> CreateMappingTable(u32 channel_count) { - if (channel_count == 2) { - return {{0, 1}}; - } + auto params = rp.PopRaw<OpusParameters>(); + auto transfer_memory_size{rp.Pop<u32>()}; + auto transfer_memory_handle{ctx.GetCopyHandle(0)}; + auto transfer_memory{ + system.ApplicationProcess()->GetHandleTable().GetObject<Kernel::KTransferMemory>( + transfer_memory_handle)}; - return {{0, 255}}; + LOG_DEBUG(Service_Audio, "sample_rate {} channel_count {} transfer_memory_size 0x{:X}", + params.sample_rate, params.channel_count, transfer_memory_size); + + auto decoder{std::make_shared<IHardwareOpusDecoder>(system, impl.GetHardwareOpus())}; + + OpusParametersEx ex{ + .sample_rate = params.sample_rate, + .channel_count = params.channel_count, + .use_large_frame_size = false, + }; + auto result = decoder->Initialize(ex, transfer_memory.GetPointerUnsafe(), transfer_memory_size); + + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(result); + rb.PushIpcInterface(decoder); } -} // Anonymous namespace void HwOpus::GetWorkBufferSize(HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - const auto sample_rate = rp.Pop<u32>(); - const auto channel_count = rp.Pop<u32>(); + auto params = rp.PopRaw<OpusParameters>(); - LOG_DEBUG(Audio, "called with sample_rate={}, channel_count={}", sample_rate, channel_count); + u64 size{}; + auto result = impl.GetWorkBufferSize(params, size); - ASSERT_MSG(sample_rate == 48000 || sample_rate == 24000 || sample_rate == 16000 || - sample_rate == 12000 || sample_rate == 8000, - "Invalid sample rate"); - ASSERT_MSG(channel_count == 1 || channel_count == 2, "Invalid channel count"); + LOG_DEBUG(Service_Audio, "sample_rate {} channel_count {} -- returned size 0x{:X}", + params.sample_rate, params.channel_count, size); + + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(result); + rb.Push(size); +} + +void HwOpus::OpenHardwareOpusDecoderForMultiStream(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; - const u32 worker_buffer_sz = static_cast<u32>(WorkerBufferSize(channel_count)); - LOG_DEBUG(Audio, "worker_buffer_sz={}", worker_buffer_sz); + auto input{ctx.ReadBuffer()}; + OpusMultiStreamParameters params; + std::memcpy(¶ms, input.data(), sizeof(OpusMultiStreamParameters)); + + auto transfer_memory_size{rp.Pop<u32>()}; + auto transfer_memory_handle{ctx.GetCopyHandle(0)}; + auto transfer_memory{ + system.ApplicationProcess()->GetHandleTable().GetObject<Kernel::KTransferMemory>( + transfer_memory_handle)}; + + LOG_DEBUG(Service_Audio, + "sample_rate {} channel_count {} total_stream_count {} stereo_stream_count {} " + "transfer_memory_size 0x{:X}", + params.sample_rate, params.channel_count, params.total_stream_count, + params.stereo_stream_count, transfer_memory_size); + + auto decoder{std::make_shared<IHardwareOpusDecoder>(system, impl.GetHardwareOpus())}; + + OpusMultiStreamParametersEx ex{ + .sample_rate = params.sample_rate, + .channel_count = params.channel_count, + .total_stream_count = params.total_stream_count, + .stereo_stream_count = params.stereo_stream_count, + .use_large_frame_size = false, + .mappings{}, + }; + std::memcpy(ex.mappings.data(), params.mappings.data(), sizeof(params.mappings)); + auto result = decoder->Initialize(ex, transfer_memory.GetPointerUnsafe(), transfer_memory_size); - IPC::ResponseBuilder rb{ctx, 3}; - rb.Push(ResultSuccess); - rb.Push<u32>(worker_buffer_sz); + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(result); + rb.PushIpcInterface(decoder); } -void HwOpus::GetWorkBufferSizeEx(HLERequestContext& ctx) { - GetWorkBufferSize(ctx); +void HwOpus::GetWorkBufferSizeForMultiStream(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + + auto input{ctx.ReadBuffer()}; + OpusMultiStreamParameters params; + std::memcpy(¶ms, input.data(), sizeof(OpusMultiStreamParameters)); + + u64 size{}; + auto result = impl.GetWorkBufferSizeForMultiStream(params, size); + + LOG_DEBUG(Service_Audio, "size 0x{:X}", size); + + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(result); + rb.Push(size); } -void HwOpus::GetWorkBufferSizeForMultiStreamEx(HLERequestContext& ctx) { - OpusMultiStreamParametersEx param; - std::memcpy(¶m, ctx.ReadBuffer().data(), ctx.GetReadBufferSize()); +void HwOpus::OpenHardwareOpusDecoderEx(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; - const auto sample_rate = param.sample_rate; - const auto channel_count = param.channel_count; - const auto number_streams = param.number_streams; - const auto number_stereo_streams = param.number_stereo_streams; + auto params = rp.PopRaw<OpusParametersEx>(); + auto transfer_memory_size{rp.Pop<u32>()}; + auto transfer_memory_handle{ctx.GetCopyHandle(0)}; + auto transfer_memory{ + system.ApplicationProcess()->GetHandleTable().GetObject<Kernel::KTransferMemory>( + transfer_memory_handle)}; - LOG_DEBUG( - Audio, - "called with sample_rate={}, channel_count={}, number_streams={}, number_stereo_streams={}", - sample_rate, channel_count, number_streams, number_stereo_streams); + LOG_DEBUG(Service_Audio, "sample_rate {} channel_count {} transfer_memory_size 0x{:X}", + params.sample_rate, params.channel_count, transfer_memory_size); - ASSERT_MSG(sample_rate == 48000 || sample_rate == 24000 || sample_rate == 16000 || - sample_rate == 12000 || sample_rate == 8000, - "Invalid sample rate"); + auto decoder{std::make_shared<IHardwareOpusDecoder>(system, impl.GetHardwareOpus())}; - const u32 worker_buffer_sz = - static_cast<u32>(opus_multistream_decoder_get_size(number_streams, number_stereo_streams)); + auto result = + decoder->Initialize(params, transfer_memory.GetPointerUnsafe(), transfer_memory_size); - IPC::ResponseBuilder rb{ctx, 3}; - rb.Push(ResultSuccess); - rb.Push<u32>(worker_buffer_sz); + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(result); + rb.PushIpcInterface(decoder); } -void HwOpus::OpenHardwareOpusDecoder(HLERequestContext& ctx) { +void HwOpus::GetWorkBufferSizeEx(HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - const auto sample_rate = rp.Pop<u32>(); - const auto channel_count = rp.Pop<u32>(); - const auto buffer_sz = rp.Pop<u32>(); - - LOG_DEBUG(Audio, "called sample_rate={}, channel_count={}, buffer_size={}", sample_rate, - channel_count, buffer_sz); - - ASSERT_MSG(sample_rate == 48000 || sample_rate == 24000 || sample_rate == 16000 || - sample_rate == 12000 || sample_rate == 8000, - "Invalid sample rate"); - ASSERT_MSG(channel_count == 1 || channel_count == 2, "Invalid channel count"); - - const std::size_t worker_sz = WorkerBufferSize(channel_count); - ASSERT_MSG(buffer_sz >= worker_sz, "Worker buffer too large"); - - const int num_stereo_streams = channel_count == 2 ? 1 : 0; - const auto mapping_table = CreateMappingTable(channel_count); - - int error = 0; - OpusDecoderPtr decoder{ - opus_multistream_decoder_create(sample_rate, static_cast<int>(channel_count), 1, - num_stereo_streams, mapping_table.data(), &error)}; - if (error != OPUS_OK || decoder == nullptr) { - LOG_ERROR(Audio, "Failed to create Opus decoder (error={}).", error); - IPC::ResponseBuilder rb{ctx, 2}; - // TODO(ogniK): Use correct error code - rb.Push(ResultUnknown); - return; - } + auto params = rp.PopRaw<OpusParametersEx>(); - IPC::ResponseBuilder rb{ctx, 2, 0, 1}; - rb.Push(ResultSuccess); - rb.PushIpcInterface<IHardwareOpusDecoderManager>( - system, OpusDecoderState{std::move(decoder), sample_rate, channel_count}); + u64 size{}; + auto result = impl.GetWorkBufferSizeEx(params, size); + + LOG_DEBUG(Service_Audio, "size 0x{:X}", size); + + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(result); + rb.Push(size); } -void HwOpus::OpenHardwareOpusDecoderEx(HLERequestContext& ctx) { +void HwOpus::OpenHardwareOpusDecoderForMultiStreamEx(HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - const auto sample_rate = rp.Pop<u32>(); - const auto channel_count = rp.Pop<u32>(); - LOG_DEBUG(Audio, "called sample_rate={}, channel_count={}", sample_rate, channel_count); + auto input{ctx.ReadBuffer()}; + OpusMultiStreamParametersEx params; + std::memcpy(¶ms, input.data(), sizeof(OpusMultiStreamParametersEx)); - ASSERT_MSG(sample_rate == 48000 || sample_rate == 24000 || sample_rate == 16000 || - sample_rate == 12000 || sample_rate == 8000, - "Invalid sample rate"); - ASSERT_MSG(channel_count == 1 || channel_count == 2, "Invalid channel count"); + auto transfer_memory_size{rp.Pop<u32>()}; + auto transfer_memory_handle{ctx.GetCopyHandle(0)}; + auto transfer_memory{ + system.ApplicationProcess()->GetHandleTable().GetObject<Kernel::KTransferMemory>( + transfer_memory_handle)}; - const int num_stereo_streams = channel_count == 2 ? 1 : 0; - const auto mapping_table = CreateMappingTable(channel_count); + LOG_DEBUG(Service_Audio, + "sample_rate {} channel_count {} total_stream_count {} stereo_stream_count {} " + "use_large_frame_size {}" + "transfer_memory_size 0x{:X}", + params.sample_rate, params.channel_count, params.total_stream_count, + params.stereo_stream_count, params.use_large_frame_size, transfer_memory_size); - int error = 0; - OpusDecoderPtr decoder{ - opus_multistream_decoder_create(sample_rate, static_cast<int>(channel_count), 1, - num_stereo_streams, mapping_table.data(), &error)}; - if (error != OPUS_OK || decoder == nullptr) { - LOG_ERROR(Audio, "Failed to create Opus decoder (error={}).", error); - IPC::ResponseBuilder rb{ctx, 2}; - // TODO(ogniK): Use correct error code - rb.Push(ResultUnknown); - return; - } + auto decoder{std::make_shared<IHardwareOpusDecoder>(system, impl.GetHardwareOpus())}; + + auto result = + decoder->Initialize(params, transfer_memory.GetPointerUnsafe(), transfer_memory_size); IPC::ResponseBuilder rb{ctx, 2, 0, 1}; - rb.Push(ResultSuccess); - rb.PushIpcInterface<IHardwareOpusDecoderManager>( - system, OpusDecoderState{std::move(decoder), sample_rate, channel_count}); + rb.Push(result); + rb.PushIpcInterface(decoder); +} + +void HwOpus::GetWorkBufferSizeForMultiStreamEx(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + + auto input{ctx.ReadBuffer()}; + OpusMultiStreamParametersEx params; + std::memcpy(¶ms, input.data(), sizeof(OpusMultiStreamParametersEx)); + + u64 size{}; + auto result = impl.GetWorkBufferSizeForMultiStreamEx(params, size); + + LOG_DEBUG(Service_Audio, + "sample_rate {} channel_count {} total_stream_count {} stereo_stream_count {} " + "use_large_frame_size {} -- returned size 0x{:X}", + params.sample_rate, params.channel_count, params.total_stream_count, + params.stereo_stream_count, params.use_large_frame_size, size); + + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(result); + rb.Push(size); +} + +void HwOpus::GetWorkBufferSizeExEx(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + auto params = rp.PopRaw<OpusParametersEx>(); + + u64 size{}; + auto result = impl.GetWorkBufferSizeExEx(params, size); + + LOG_DEBUG(Service_Audio, "size 0x{:X}", size); + + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(result); + rb.Push(size); +} + +void HwOpus::GetWorkBufferSizeForMultiStreamExEx(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + + auto input{ctx.ReadBuffer()}; + OpusMultiStreamParametersEx params; + std::memcpy(¶ms, input.data(), sizeof(OpusMultiStreamParametersEx)); + + u64 size{}; + auto result = impl.GetWorkBufferSizeForMultiStreamExEx(params, size); + + LOG_DEBUG(Service_Audio, "size 0x{:X}", size); + + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(result); + rb.Push(size); } -HwOpus::HwOpus(Core::System& system_) : ServiceFramework{system_, "hwopus"} { +HwOpus::HwOpus(Core::System& system_) + : ServiceFramework{system_, "hwopus"}, system{system_}, impl{system} { static const FunctionInfo functions[] = { {0, &HwOpus::OpenHardwareOpusDecoder, "OpenHardwareOpusDecoder"}, {1, &HwOpus::GetWorkBufferSize, "GetWorkBufferSize"}, - {2, nullptr, "OpenOpusDecoderForMultiStream"}, - {3, nullptr, "GetWorkBufferSizeForMultiStream"}, + {2, &HwOpus::OpenHardwareOpusDecoderForMultiStream, "OpenOpusDecoderForMultiStream"}, + {3, &HwOpus::GetWorkBufferSizeForMultiStream, "GetWorkBufferSizeForMultiStream"}, {4, &HwOpus::OpenHardwareOpusDecoderEx, "OpenHardwareOpusDecoderEx"}, {5, &HwOpus::GetWorkBufferSizeEx, "GetWorkBufferSizeEx"}, - {6, nullptr, "OpenHardwareOpusDecoderForMultiStreamEx"}, + {6, &HwOpus::OpenHardwareOpusDecoderForMultiStreamEx, + "OpenHardwareOpusDecoderForMultiStreamEx"}, {7, &HwOpus::GetWorkBufferSizeForMultiStreamEx, "GetWorkBufferSizeForMultiStreamEx"}, - {8, nullptr, "GetWorkBufferSizeExEx"}, - {9, nullptr, "GetWorkBufferSizeForMultiStreamExEx"}, + {8, &HwOpus::GetWorkBufferSizeExEx, "GetWorkBufferSizeExEx"}, + {9, &HwOpus::GetWorkBufferSizeForMultiStreamExEx, "GetWorkBufferSizeForMultiStreamExEx"}, }; RegisterHandlers(functions); } diff --git a/src/core/hle/service/audio/hwopus.h b/src/core/hle/service/audio/hwopus.h index ece65c02c..d3960065e 100644 --- a/src/core/hle/service/audio/hwopus.h +++ b/src/core/hle/service/audio/hwopus.h @@ -3,6 +3,7 @@ #pragma once +#include "audio_core/opus/decoder_manager.h" #include "core/hle/service/service.h" namespace Core { @@ -11,16 +12,6 @@ class System; namespace Service::Audio { -struct OpusMultiStreamParametersEx { - u32 sample_rate; - u32 channel_count; - u32 number_streams; - u32 number_stereo_streams; - u32 use_large_frame_size; - u32 padding; - std::array<u32, 64> channel_mappings; -}; - class HwOpus final : public ServiceFramework<HwOpus> { public: explicit HwOpus(Core::System& system_); @@ -28,10 +19,18 @@ public: private: void OpenHardwareOpusDecoder(HLERequestContext& ctx); - void OpenHardwareOpusDecoderEx(HLERequestContext& ctx); void GetWorkBufferSize(HLERequestContext& ctx); + void OpenHardwareOpusDecoderForMultiStream(HLERequestContext& ctx); + void GetWorkBufferSizeForMultiStream(HLERequestContext& ctx); + void OpenHardwareOpusDecoderEx(HLERequestContext& ctx); void GetWorkBufferSizeEx(HLERequestContext& ctx); + void OpenHardwareOpusDecoderForMultiStreamEx(HLERequestContext& ctx); void GetWorkBufferSizeForMultiStreamEx(HLERequestContext& ctx); + void GetWorkBufferSizeExEx(HLERequestContext& ctx); + void GetWorkBufferSizeForMultiStreamExEx(HLERequestContext& ctx); + + Core::System& system; + AudioCore::OpusDecoder::OpusDecoderManager impl; }; } // namespace Service::Audio diff --git a/src/core/hle/service/es/es.cpp b/src/core/hle/service/es/es.cpp index 446f46b3c..9eaae4c4b 100644 --- a/src/core/hle/service/es/es.cpp +++ b/src/core/hle/service/es/es.cpp @@ -122,20 +122,18 @@ private: } void ImportTicket(HLERequestContext& ctx) { - const auto ticket = ctx.ReadBuffer(); + const auto raw_ticket = ctx.ReadBuffer(); [[maybe_unused]] const auto cert = ctx.ReadBuffer(1); - if (ticket.size() < sizeof(Core::Crypto::Ticket)) { + if (raw_ticket.size() < sizeof(Core::Crypto::Ticket)) { LOG_ERROR(Service_ETicket, "The input buffer is not large enough!"); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(ERROR_INVALID_ARGUMENT); return; } - Core::Crypto::Ticket raw{}; - std::memcpy(&raw, ticket.data(), sizeof(Core::Crypto::Ticket)); - - if (!keys.AddTicketPersonalized(raw)) { + Core::Crypto::Ticket ticket = Core::Crypto::Ticket::Read(raw_ticket); + if (!keys.AddTicket(ticket)) { LOG_ERROR(Service_ETicket, "The ticket could not be imported!"); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(ERROR_INVALID_ARGUMENT); diff --git a/src/core/hle/service/filesystem/fsp_srv.cpp b/src/core/hle/service/filesystem/fsp_srv.cpp index 6e4d26b1e..126cd6ffd 100644 --- a/src/core/hle/service/filesystem/fsp_srv.cpp +++ b/src/core/hle/service/filesystem/fsp_srv.cpp @@ -329,6 +329,7 @@ public: {13, &IFileSystem::CleanDirectoryRecursively, "CleanDirectoryRecursively"}, {14, &IFileSystem::GetFileTimeStampRaw, "GetFileTimeStampRaw"}, {15, nullptr, "QueryEntry"}, + {16, &IFileSystem::GetFileSystemAttribute, "GetFileSystemAttribute"}, }; RegisterHandlers(functions); } @@ -521,6 +522,46 @@ public: rb.PushRaw(vfs_timestamp); } + void GetFileSystemAttribute(HLERequestContext& ctx) { + LOG_WARNING(Service_FS, "(STUBBED) called"); + + struct FileSystemAttribute { + u8 dir_entry_name_length_max_defined; + u8 file_entry_name_length_max_defined; + u8 dir_path_name_length_max_defined; + u8 file_path_name_length_max_defined; + INSERT_PADDING_BYTES_NOINIT(0x5); + u8 utf16_dir_entry_name_length_max_defined; + u8 utf16_file_entry_name_length_max_defined; + u8 utf16_dir_path_name_length_max_defined; + u8 utf16_file_path_name_length_max_defined; + INSERT_PADDING_BYTES_NOINIT(0x18); + s32 dir_entry_name_length_max; + s32 file_entry_name_length_max; + s32 dir_path_name_length_max; + s32 file_path_name_length_max; + INSERT_PADDING_WORDS_NOINIT(0x5); + s32 utf16_dir_entry_name_length_max; + s32 utf16_file_entry_name_length_max; + s32 utf16_dir_path_name_length_max; + s32 utf16_file_path_name_length_max; + INSERT_PADDING_WORDS_NOINIT(0x18); + INSERT_PADDING_WORDS_NOINIT(0x1); + }; + static_assert(sizeof(FileSystemAttribute) == 0xc0, + "FileSystemAttribute has incorrect size"); + + FileSystemAttribute savedata_attribute{}; + savedata_attribute.dir_entry_name_length_max_defined = true; + savedata_attribute.file_entry_name_length_max_defined = true; + savedata_attribute.dir_entry_name_length_max = 0x40; + savedata_attribute.file_entry_name_length_max = 0x40; + + IPC::ResponseBuilder rb{ctx, 50}; + rb.Push(ResultSuccess); + rb.PushRaw(savedata_attribute); + } + private: VfsDirectoryServiceWrapper backend; SizeGetter size; @@ -698,7 +739,7 @@ FSP_SRV::FSP_SRV(Core::System& system_) {19, nullptr, "FormatSdCardFileSystem"}, {21, nullptr, "DeleteSaveDataFileSystem"}, {22, &FSP_SRV::CreateSaveDataFileSystem, "CreateSaveDataFileSystem"}, - {23, nullptr, "CreateSaveDataFileSystemBySystemSaveDataId"}, + {23, &FSP_SRV::CreateSaveDataFileSystemBySystemSaveDataId, "CreateSaveDataFileSystemBySystemSaveDataId"}, {24, nullptr, "RegisterSaveDataFileSystemAtomicDeletion"}, {25, nullptr, "DeleteSaveDataFileSystemBySaveDataSpaceId"}, {26, nullptr, "FormatSdCardDryRun"}, @@ -712,7 +753,7 @@ FSP_SRV::FSP_SRV(Core::System& system_) {35, nullptr, "CreateSaveDataFileSystemByHashSalt"}, {36, nullptr, "OpenHostFileSystemWithOption"}, {51, &FSP_SRV::OpenSaveDataFileSystem, "OpenSaveDataFileSystem"}, - {52, nullptr, "OpenSaveDataFileSystemBySystemSaveDataId"}, + {52, &FSP_SRV::OpenSaveDataFileSystemBySystemSaveDataId, "OpenSaveDataFileSystemBySystemSaveDataId"}, {53, &FSP_SRV::OpenReadOnlySaveDataFileSystem, "OpenReadOnlySaveDataFileSystem"}, {57, nullptr, "ReadSaveDataFileSystemExtraDataBySaveDataSpaceId"}, {58, nullptr, "ReadSaveDataFileSystemExtraData"}, @@ -814,6 +855,9 @@ FSP_SRV::FSP_SRV(Core::System& system_) if (Settings::values.enable_fs_access_log) { access_log_mode = AccessLogMode::SdCard; } + + // This should be true on creation + fsc.SetAutoSaveDataCreation(true); } FSP_SRV::~FSP_SRV() = default; @@ -870,6 +914,21 @@ void FSP_SRV::CreateSaveDataFileSystem(HLERequestContext& ctx) { rb.Push(ResultSuccess); } +void FSP_SRV::CreateSaveDataFileSystemBySystemSaveDataId(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + + auto save_struct = rp.PopRaw<FileSys::SaveDataAttribute>(); + [[maybe_unused]] auto save_create_struct = rp.PopRaw<std::array<u8, 0x40>>(); + + LOG_DEBUG(Service_FS, "called save_struct = {}", save_struct.DebugInfo()); + + FileSys::VirtualDir save_data_dir{}; + fsc.CreateSaveData(&save_data_dir, FileSys::SaveDataSpaceId::NandSystem, save_struct); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSuccess); +} + void FSP_SRV::OpenSaveDataFileSystem(HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; @@ -916,6 +975,11 @@ void FSP_SRV::OpenSaveDataFileSystem(HLERequestContext& ctx) { rb.PushIpcInterface<IFileSystem>(std::move(filesystem)); } +void FSP_SRV::OpenSaveDataFileSystemBySystemSaveDataId(HLERequestContext& ctx) { + LOG_WARNING(Service_FS, "(STUBBED) called, delegating to 51 OpenSaveDataFilesystem"); + OpenSaveDataFileSystem(ctx); +} + void FSP_SRV::OpenReadOnlySaveDataFileSystem(HLERequestContext& ctx) { LOG_WARNING(Service_FS, "(STUBBED) called, delegating to 51 OpenSaveDataFilesystem"); OpenSaveDataFileSystem(ctx); diff --git a/src/core/hle/service/filesystem/fsp_srv.h b/src/core/hle/service/filesystem/fsp_srv.h index 4f3c2f6de..280bc9867 100644 --- a/src/core/hle/service/filesystem/fsp_srv.h +++ b/src/core/hle/service/filesystem/fsp_srv.h @@ -39,7 +39,9 @@ private: void OpenFileSystemWithPatch(HLERequestContext& ctx); void OpenSdCardFileSystem(HLERequestContext& ctx); void CreateSaveDataFileSystem(HLERequestContext& ctx); + void CreateSaveDataFileSystemBySystemSaveDataId(HLERequestContext& ctx); void OpenSaveDataFileSystem(HLERequestContext& ctx); + void OpenSaveDataFileSystemBySystemSaveDataId(HLERequestContext& ctx); void OpenReadOnlySaveDataFileSystem(HLERequestContext& ctx); void OpenSaveDataInfoReaderBySaveDataSpaceId(HLERequestContext& ctx); void OpenSaveDataInfoReaderOnlyCacheStorage(HLERequestContext& ctx); diff --git a/src/core/hle/service/hid/controllers/npad.cpp b/src/core/hle/service/hid/controllers/npad.cpp index 3b349b4c4..bc822f19e 100644 --- a/src/core/hle/service/hid/controllers/npad.cpp +++ b/src/core/hle/service/hid/controllers/npad.cpp @@ -193,7 +193,7 @@ void Controller_NPad::InitNewlyAddedController(Core::HID::NpadIdType npad_id) { shared_memory->system_properties.use_minus.Assign(1); shared_memory->system_properties.is_charging_joy_dual.Assign( battery_level.dual.is_charging); - shared_memory->applet_nfc_xcd.applet_footer.type = AppletFooterUiType::SwitchProController; + shared_memory->applet_footer_type = AppletFooterUiType::SwitchProController; shared_memory->sixaxis_fullkey_properties.is_newly_assigned.Assign(1); break; case Core::HID::NpadStyleIndex::Handheld: @@ -216,8 +216,7 @@ void Controller_NPad::InitNewlyAddedController(Core::HID::NpadIdType npad_id) { shared_memory->system_properties.is_charging_joy_right.Assign( battery_level.right.is_charging); shared_memory->assignment_mode = NpadJoyAssignmentMode::Dual; - shared_memory->applet_nfc_xcd.applet_footer.type = - AppletFooterUiType::HandheldJoyConLeftJoyConRight; + shared_memory->applet_footer_type = AppletFooterUiType::HandheldJoyConLeftJoyConRight; shared_memory->sixaxis_handheld_properties.is_newly_assigned.Assign(1); break; case Core::HID::NpadStyleIndex::JoyconDual: @@ -247,19 +246,19 @@ void Controller_NPad::InitNewlyAddedController(Core::HID::NpadIdType npad_id) { shared_memory->assignment_mode = NpadJoyAssignmentMode::Dual; if (controller.is_dual_left_connected && controller.is_dual_right_connected) { - shared_memory->applet_nfc_xcd.applet_footer.type = AppletFooterUiType::JoyDual; + shared_memory->applet_footer_type = AppletFooterUiType::JoyDual; shared_memory->fullkey_color.fullkey = body_colors.left; shared_memory->battery_level_dual = battery_level.left.battery_level; shared_memory->system_properties.is_charging_joy_dual.Assign( battery_level.left.is_charging); } else if (controller.is_dual_left_connected) { - shared_memory->applet_nfc_xcd.applet_footer.type = AppletFooterUiType::JoyDualLeftOnly; + shared_memory->applet_footer_type = AppletFooterUiType::JoyDualLeftOnly; shared_memory->fullkey_color.fullkey = body_colors.left; shared_memory->battery_level_dual = battery_level.left.battery_level; shared_memory->system_properties.is_charging_joy_dual.Assign( battery_level.left.is_charging); } else { - shared_memory->applet_nfc_xcd.applet_footer.type = AppletFooterUiType::JoyDualRightOnly; + shared_memory->applet_footer_type = AppletFooterUiType::JoyDualRightOnly; shared_memory->fullkey_color.fullkey = body_colors.right; shared_memory->battery_level_dual = battery_level.right.battery_level; shared_memory->system_properties.is_charging_joy_dual.Assign( @@ -278,7 +277,7 @@ void Controller_NPad::InitNewlyAddedController(Core::HID::NpadIdType npad_id) { shared_memory->system_properties.use_minus.Assign(1); shared_memory->system_properties.is_charging_joy_left.Assign( battery_level.left.is_charging); - shared_memory->applet_nfc_xcd.applet_footer.type = AppletFooterUiType::JoyLeftHorizontal; + shared_memory->applet_footer_type = AppletFooterUiType::JoyLeftHorizontal; shared_memory->sixaxis_left_properties.is_newly_assigned.Assign(1); break; case Core::HID::NpadStyleIndex::JoyconRight: @@ -293,7 +292,7 @@ void Controller_NPad::InitNewlyAddedController(Core::HID::NpadIdType npad_id) { shared_memory->system_properties.use_plus.Assign(1); shared_memory->system_properties.is_charging_joy_right.Assign( battery_level.right.is_charging); - shared_memory->applet_nfc_xcd.applet_footer.type = AppletFooterUiType::JoyRightHorizontal; + shared_memory->applet_footer_type = AppletFooterUiType::JoyRightHorizontal; shared_memory->sixaxis_right_properties.is_newly_assigned.Assign(1); break; case Core::HID::NpadStyleIndex::GameCube: @@ -314,12 +313,12 @@ void Controller_NPad::InitNewlyAddedController(Core::HID::NpadIdType npad_id) { case Core::HID::NpadStyleIndex::SNES: shared_memory->style_tag.lucia.Assign(1); shared_memory->device_type.fullkey.Assign(1); - shared_memory->applet_nfc_xcd.applet_footer.type = AppletFooterUiType::Lucia; + shared_memory->applet_footer_type = AppletFooterUiType::Lucia; break; case Core::HID::NpadStyleIndex::N64: shared_memory->style_tag.lagoon.Assign(1); shared_memory->device_type.fullkey.Assign(1); - shared_memory->applet_nfc_xcd.applet_footer.type = AppletFooterUiType::Lagon; + shared_memory->applet_footer_type = AppletFooterUiType::Lagon; break; case Core::HID::NpadStyleIndex::SegaGenesis: shared_memory->style_tag.lager.Assign(1); @@ -347,6 +346,7 @@ void Controller_NPad::InitNewlyAddedController(Core::HID::NpadIdType npad_id) { } SignalStyleSetChangedEvent(npad_id); WriteEmptyEntry(controller.shared_memory); + hid_core.SetLastActiveController(npad_id); } void Controller_NPad::OnInit() { @@ -419,9 +419,17 @@ void Controller_NPad::RequestPadStateUpdate(Core::HID::NpadIdType npad_id) { std::scoped_lock lock{mutex}; auto& controller = GetControllerFromNpadIdType(npad_id); const auto controller_type = controller.device->GetNpadStyleIndex(); + + if (!controller.device->IsConnected() && controller.is_connected) { + DisconnectNpad(npad_id); + return; + } if (!controller.device->IsConnected()) { return; } + if (controller.device->IsConnected() && !controller.is_connected) { + InitNewlyAddedController(npad_id); + } // This function is unique to yuzu for the turbo buttons and motion to work properly controller.device->StatusUpdate(); @@ -468,6 +476,10 @@ void Controller_NPad::RequestPadStateUpdate(Core::HID::NpadIdType npad_id) { pad_entry.npad_buttons.l.Assign(button_state.zl); pad_entry.npad_buttons.r.Assign(button_state.zr); } + + if (pad_entry.npad_buttons.raw != Core::HID::NpadButton::None) { + hid_core.SetLastActiveController(npad_id); + } } void Controller_NPad::OnUpdate(const Core::Timing::CoreTiming& core_timing) { @@ -736,14 +748,6 @@ void Controller_NPad::SetSupportedStyleSet(Core::HID::NpadStyleTag style_set) { // Once SetSupportedStyleSet is called controllers are fully initialized is_controller_initialized = true; - - // Connect all active controllers - for (auto& controller : controller_data) { - const auto& device = controller.device; - if (device->IsConnected()) { - AddNewControllerAt(device->GetNpadStyleIndex(), device->GetNpadIdType()); - } - } } Core::HID::NpadStyleTag Controller_NPad::GetSupportedStyleSet() const { @@ -1116,7 +1120,7 @@ Result Controller_NPad::DisconnectNpad(Core::HID::NpadIdType npad_id) { .left = {}, .right = {}, }; - shared_memory->applet_nfc_xcd.applet_footer.type = AppletFooterUiType::None; + shared_memory->applet_footer_type = AppletFooterUiType::None; controller.is_dual_left_connected = true; controller.is_dual_right_connected = true; @@ -1508,6 +1512,31 @@ Core::HID::NpadButton Controller_NPad::GetAndResetPressState() { return static_cast<Core::HID::NpadButton>(press_state.exchange(0)); } +void Controller_NPad::ApplyNpadSystemCommonPolicy() { + Core::HID::NpadStyleTag styletag{}; + styletag.fullkey.Assign(1); + styletag.handheld.Assign(1); + styletag.joycon_dual.Assign(1); + styletag.system_ext.Assign(1); + styletag.system.Assign(1); + SetSupportedStyleSet(styletag); + + SetNpadHandheldActivationMode(NpadHandheldActivationMode::Dual); + + supported_npad_id_types.clear(); + supported_npad_id_types.resize(10); + supported_npad_id_types[0] = Core::HID::NpadIdType::Player1; + supported_npad_id_types[1] = Core::HID::NpadIdType::Player2; + supported_npad_id_types[2] = Core::HID::NpadIdType::Player3; + supported_npad_id_types[3] = Core::HID::NpadIdType::Player4; + supported_npad_id_types[4] = Core::HID::NpadIdType::Player5; + supported_npad_id_types[5] = Core::HID::NpadIdType::Player6; + supported_npad_id_types[6] = Core::HID::NpadIdType::Player7; + supported_npad_id_types[7] = Core::HID::NpadIdType::Player8; + supported_npad_id_types[8] = Core::HID::NpadIdType::Other; + supported_npad_id_types[9] = Core::HID::NpadIdType::Handheld; +} + bool Controller_NPad::IsControllerSupported(Core::HID::NpadStyleIndex controller) const { if (controller == Core::HID::NpadStyleIndex::Handheld) { const bool support_handheld = diff --git a/src/core/hle/service/hid/controllers/npad.h b/src/core/hle/service/hid/controllers/npad.h index 776411261..949e58a4c 100644 --- a/src/core/hle/service/hid/controllers/npad.h +++ b/src/core/hle/service/hid/controllers/npad.h @@ -190,6 +190,8 @@ public: // Specifically for cheat engine and other features. Core::HID::NpadButton GetAndResetPressState(); + void ApplyNpadSystemCommonPolicy(); + static bool IsNpadIdValid(Core::HID::NpadIdType npad_id); static Result IsDeviceHandleValid(const Core::HID::VibrationDeviceHandle& device_handle); static Result VerifyValidSixAxisSensorHandle( @@ -360,7 +362,7 @@ private: enum class AppletFooterUiType : u8 { None = 0, HandheldNone = 1, - HandheldJoyConLeftOnly = 1, + HandheldJoyConLeftOnly = 2, HandheldJoyConRightOnly = 3, HandheldJoyConLeftJoyConRight = 4, JoyDual = 5, @@ -382,13 +384,6 @@ private: Lagon = 21, }; - struct AppletFooterUi { - AppletFooterUiAttributes attributes{}; - AppletFooterUiType type{AppletFooterUiType::None}; - INSERT_PADDING_BYTES(0x5B); // Reserved - }; - static_assert(sizeof(AppletFooterUi) == 0x60, "AppletFooterUi is an invalid size"); - // This is nn::hid::NpadLarkType enum class NpadLarkType : u32 { Invalid, @@ -419,13 +414,6 @@ private: U, }; - struct AppletNfcXcd { - union { - AppletFooterUi applet_footer{}; - Lifo<NfcXcdDeviceHandleStateImpl, 0x2> nfc_xcd_device_lifo; - }; - }; - // This is nn::hid::detail::NpadInternalState struct NpadInternalState { Core::HID::NpadStyleTag style_tag{Core::HID::NpadStyleSet::None}; @@ -452,7 +440,9 @@ private: Core::HID::NpadBatteryLevel battery_level_dual{}; Core::HID::NpadBatteryLevel battery_level_left{}; Core::HID::NpadBatteryLevel battery_level_right{}; - AppletNfcXcd applet_nfc_xcd{}; + AppletFooterUiAttributes applet_footer_attributes{}; + AppletFooterUiType applet_footer_type{AppletFooterUiType::None}; + INSERT_PADDING_BYTES(0x5B); // Reserved INSERT_PADDING_BYTES(0x20); // Unknown Lifo<NpadGcTriggerState, hid_entry_count> gc_trigger_lifo{}; NpadLarkType lark_type_l_and_main{}; diff --git a/src/core/hle/service/hid/hid.cpp b/src/core/hle/service/hid/hid.cpp index fd466db7b..4d70006c1 100644 --- a/src/core/hle/service/hid/hid.cpp +++ b/src/core/hle/service/hid/hid.cpp @@ -231,8 +231,10 @@ std::shared_ptr<IAppletResource> Hid::GetAppletResource() { return applet_resource; } -Hid::Hid(Core::System& system_) - : ServiceFramework{system_, "hid"}, service_context{system_, service_name} { +Hid::Hid(Core::System& system_, std::shared_ptr<IAppletResource> applet_resource_) + : ServiceFramework{system_, "hid"}, applet_resource{applet_resource_}, service_context{ + system_, + service_name} { // clang-format off static const FunctionInfo functions[] = { {0, &Hid::CreateAppletResource, "CreateAppletResource"}, @@ -2543,8 +2545,9 @@ public: class HidSys final : public ServiceFramework<HidSys> { public: - explicit HidSys(Core::System& system_) - : ServiceFramework{system_, "hid:sys"}, service_context{system_, "hid:sys"} { + explicit HidSys(Core::System& system_, std::shared_ptr<IAppletResource> applet_resource_) + : ServiceFramework{system_, "hid:sys"}, service_context{system_, "hid:sys"}, + applet_resource{applet_resource_} { // clang-format off static const FunctionInfo functions[] = { {31, nullptr, "SendKeyboardLockKeyEvent"}, @@ -2756,9 +2759,12 @@ public: private: void ApplyNpadSystemCommonPolicy(HLERequestContext& ctx) { - // We already do this for homebrew so we can just stub it out LOG_WARNING(Service_HID, "called"); + GetAppletResource() + ->GetController<Controller_NPad>(HidController::NPad) + .ApplyNpadSystemCommonPolicy(); + IPC::ResponseBuilder rb{ctx, 2}; rb.Push(ResultSuccess); } @@ -2768,7 +2774,7 @@ private: IPC::ResponseBuilder rb{ctx, 3}; rb.Push(ResultSuccess); - rb.PushEnum(Core::HID::NpadIdType::Handheld); + rb.PushEnum(system.HIDCore().GetLastActiveController()); } void GetUniquePadsFromNpad(HLERequestContext& ctx) { @@ -2821,17 +2827,28 @@ private: rb.PushRaw(touchscreen_config); } + std::shared_ptr<IAppletResource> GetAppletResource() { + if (applet_resource == nullptr) { + applet_resource = std::make_shared<IAppletResource>(system, service_context); + } + + return applet_resource; + } + Kernel::KEvent* joy_detach_event; KernelHelpers::ServiceContext service_context; + std::shared_ptr<IAppletResource> applet_resource; }; void LoopProcess(Core::System& system) { auto server_manager = std::make_unique<ServerManager>(system); + std::shared_ptr<IAppletResource> applet_resource; - server_manager->RegisterNamedService("hid", std::make_shared<Hid>(system)); + server_manager->RegisterNamedService("hid", std::make_shared<Hid>(system, applet_resource)); server_manager->RegisterNamedService("hidbus", std::make_shared<HidBus>(system)); server_manager->RegisterNamedService("hid:dbg", std::make_shared<HidDbg>(system)); - server_manager->RegisterNamedService("hid:sys", std::make_shared<HidSys>(system)); + server_manager->RegisterNamedService("hid:sys", + std::make_shared<HidSys>(system, applet_resource)); server_manager->RegisterNamedService("irs", std::make_shared<Service::IRS::IRS>(system)); server_manager->RegisterNamedService("irs:sys", diff --git a/src/core/hle/service/hid/hid.h b/src/core/hle/service/hid/hid.h index f247b83c2..0ca43de93 100644 --- a/src/core/hle/service/hid/hid.h +++ b/src/core/hle/service/hid/hid.h @@ -95,7 +95,7 @@ private: class Hid final : public ServiceFramework<Hid> { public: - explicit Hid(Core::System& system_); + explicit Hid(Core::System& system_, std::shared_ptr<IAppletResource> applet_resource_); ~Hid() override; std::shared_ptr<IAppletResource> GetAppletResource(); diff --git a/src/core/hle/service/ldn/ldn.cpp b/src/core/hle/service/ldn/ldn.cpp index 9d149a7cd..7927f8264 100644 --- a/src/core/hle/service/ldn/ldn.cpp +++ b/src/core/hle/service/ldn/ldn.cpp @@ -23,19 +23,39 @@ public: explicit IMonitorService(Core::System& system_) : ServiceFramework{system_, "IMonitorService"} { // clang-format off static const FunctionInfo functions[] = { - {0, nullptr, "GetStateForMonitor"}, + {0, &IMonitorService::GetStateForMonitor, "GetStateForMonitor"}, {1, nullptr, "GetNetworkInfoForMonitor"}, {2, nullptr, "GetIpv4AddressForMonitor"}, {3, nullptr, "GetDisconnectReasonForMonitor"}, {4, nullptr, "GetSecurityParameterForMonitor"}, {5, nullptr, "GetNetworkConfigForMonitor"}, - {100, nullptr, "InitializeMonitor"}, + {100, &IMonitorService::InitializeMonitor, "InitializeMonitor"}, {101, nullptr, "FinalizeMonitor"}, }; // clang-format on RegisterHandlers(functions); } + +private: + void GetStateForMonitor(HLERequestContext& ctx) { + LOG_INFO(Service_LDN, "called"); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(ResultSuccess); + rb.PushEnum(state); + } + + void InitializeMonitor(HLERequestContext& ctx) { + LOG_INFO(Service_LDN, "called"); + + state = State::Initialized; + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSuccess); + } + + State state{State::None}; }; class LDNM final : public ServiceFramework<LDNM> { @@ -731,14 +751,81 @@ public: } }; +class ISfMonitorService final : public ServiceFramework<ISfMonitorService> { +public: + explicit ISfMonitorService(Core::System& system_) + : ServiceFramework{system_, "ISfMonitorService"} { + // clang-format off + static const FunctionInfo functions[] = { + {0, &ISfMonitorService::Initialize, "Initialize"}, + {288, &ISfMonitorService::GetGroupInfo, "GetGroupInfo"}, + {320, nullptr, "GetLinkLevel"}, + }; + // clang-format on + + RegisterHandlers(functions); + } + +private: + void Initialize(HLERequestContext& ctx) { + LOG_WARNING(Service_LDN, "(STUBBED) called"); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(ResultSuccess); + rb.Push(0); + } + + void GetGroupInfo(HLERequestContext& ctx) { + LOG_WARNING(Service_LDN, "(STUBBED) called"); + + struct GroupInfo { + std::array<u8, 0x200> info; + }; + + GroupInfo group_info{}; + + ctx.WriteBuffer(group_info); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSuccess); + } +}; + +class LP2PM final : public ServiceFramework<LP2PM> { +public: + explicit LP2PM(Core::System& system_) : ServiceFramework{system_, "lp2p:m"} { + // clang-format off + static const FunctionInfo functions[] = { + {0, &LP2PM::CreateMonitorService, "CreateMonitorService"}, + }; + // clang-format on + + RegisterHandlers(functions); + } + +private: + void CreateMonitorService(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const u64 reserved_input = rp.Pop<u64>(); + + LOG_INFO(Service_LDN, "called, reserved_input={}", reserved_input); + + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(ResultSuccess); + rb.PushIpcInterface<ISfMonitorService>(system); + } +}; + void LoopProcess(Core::System& system) { auto server_manager = std::make_unique<ServerManager>(system); server_manager->RegisterNamedService("ldn:m", std::make_shared<LDNM>(system)); server_manager->RegisterNamedService("ldn:s", std::make_shared<LDNS>(system)); server_manager->RegisterNamedService("ldn:u", std::make_shared<LDNU>(system)); + server_manager->RegisterNamedService("lp2p:app", std::make_shared<LP2PAPP>(system)); server_manager->RegisterNamedService("lp2p:sys", std::make_shared<LP2PSYS>(system)); + server_manager->RegisterNamedService("lp2p:m", std::make_shared<LP2PM>(system)); + ServerManager::RunServer(std::move(server_manager)); } diff --git a/src/core/hle/service/mii/mii.cpp b/src/core/hle/service/mii/mii.cpp index 65c11a2f3..c28eed926 100644 --- a/src/core/hle/service/mii/mii.cpp +++ b/src/core/hle/service/mii/mii.cpp @@ -7,17 +7,21 @@ #include "core/hle/service/ipc_helpers.h" #include "core/hle/service/mii/mii.h" #include "core/hle/service/mii/mii_manager.h" +#include "core/hle/service/mii/mii_result.h" +#include "core/hle/service/mii/types/char_info.h" +#include "core/hle/service/mii/types/store_data.h" +#include "core/hle/service/mii/types/ver3_store_data.h" #include "core/hle/service/server_manager.h" #include "core/hle/service/service.h" namespace Service::Mii { -constexpr Result ERROR_INVALID_ARGUMENT{ErrorModule::Mii, 1}; - class IDatabaseService final : public ServiceFramework<IDatabaseService> { public: - explicit IDatabaseService(Core::System& system_) - : ServiceFramework{system_, "IDatabaseService"} { + explicit IDatabaseService(Core::System& system_, std::shared_ptr<MiiManager> mii_manager, + bool is_system_) + : ServiceFramework{system_, "IDatabaseService"}, manager{mii_manager}, is_system{ + is_system_} { // clang-format off static const FunctionInfo functions[] = { {0, &IDatabaseService::IsUpdated, "IsUpdated"}, @@ -28,119 +32,122 @@ public: {5, &IDatabaseService::UpdateLatest, "UpdateLatest"}, {6, &IDatabaseService::BuildRandom, "BuildRandom"}, {7, &IDatabaseService::BuildDefault, "BuildDefault"}, - {8, nullptr, "Get2"}, - {9, nullptr, "Get3"}, - {10, nullptr, "UpdateLatest1"}, - {11, nullptr, "FindIndex"}, - {12, nullptr, "Move"}, - {13, nullptr, "AddOrReplace"}, - {14, nullptr, "Delete"}, - {15, nullptr, "DestroyFile"}, - {16, nullptr, "DeleteFile"}, - {17, nullptr, "Format"}, + {8, &IDatabaseService::Get2, "Get2"}, + {9, &IDatabaseService::Get3, "Get3"}, + {10, &IDatabaseService::UpdateLatest1, "UpdateLatest1"}, + {11, &IDatabaseService::FindIndex, "FindIndex"}, + {12, &IDatabaseService::Move, "Move"}, + {13, &IDatabaseService::AddOrReplace, "AddOrReplace"}, + {14, &IDatabaseService::Delete, "Delete"}, + {15, &IDatabaseService::DestroyFile, "DestroyFile"}, + {16, &IDatabaseService::DeleteFile, "DeleteFile"}, + {17, &IDatabaseService::Format, "Format"}, {18, nullptr, "Import"}, {19, nullptr, "Export"}, - {20, nullptr, "IsBrokenDatabaseWithClearFlag"}, + {20, &IDatabaseService::IsBrokenDatabaseWithClearFlag, "IsBrokenDatabaseWithClearFlag"}, {21, &IDatabaseService::GetIndex, "GetIndex"}, {22, &IDatabaseService::SetInterfaceVersion, "SetInterfaceVersion"}, {23, &IDatabaseService::Convert, "Convert"}, - {24, nullptr, "ConvertCoreDataToCharInfo"}, - {25, nullptr, "ConvertCharInfoToCoreData"}, - {26, nullptr, "Append"}, + {24, &IDatabaseService::ConvertCoreDataToCharInfo, "ConvertCoreDataToCharInfo"}, + {25, &IDatabaseService::ConvertCharInfoToCoreData, "ConvertCharInfoToCoreData"}, + {26, &IDatabaseService::Append, "Append"}, }; // clang-format on RegisterHandlers(functions); - } -private: - template <typename T> - std::vector<u8> SerializeArray(const std::vector<T>& values) { - std::vector<u8> out(values.size() * sizeof(T)); - std::size_t offset{}; - for (const auto& value : values) { - std::memcpy(out.data() + offset, &value, sizeof(T)); - offset += sizeof(T); - } - return out; + manager->Initialize(metadata); } +private: void IsUpdated(HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const auto source_flag{rp.PopRaw<SourceFlag>()}; LOG_DEBUG(Service_Mii, "called with source_flag={}", source_flag); + const bool is_updated = manager->IsUpdated(metadata, source_flag); + IPC::ResponseBuilder rb{ctx, 3}; rb.Push(ResultSuccess); - rb.Push(manager.CheckAndResetUpdateCounter(source_flag, current_update_counter)); + rb.Push<u8>(is_updated); } void IsFullDatabase(HLERequestContext& ctx) { LOG_DEBUG(Service_Mii, "called"); + const bool is_full_database = manager->IsFullDatabase(); + IPC::ResponseBuilder rb{ctx, 3}; rb.Push(ResultSuccess); - rb.Push(manager.IsFullDatabase()); + rb.Push<u8>(is_full_database); } void GetCount(HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const auto source_flag{rp.PopRaw<SourceFlag>()}; - LOG_DEBUG(Service_Mii, "called with source_flag={}", source_flag); + const u32 mii_count = manager->GetCount(metadata, source_flag); + + LOG_DEBUG(Service_Mii, "called with source_flag={}, mii_count={}", source_flag, mii_count); IPC::ResponseBuilder rb{ctx, 3}; rb.Push(ResultSuccess); - rb.Push<u32>(manager.GetCount(source_flag)); + rb.Push(mii_count); } void Get(HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const auto source_flag{rp.PopRaw<SourceFlag>()}; + const auto output_size{ctx.GetWriteBufferNumElements<CharInfoElement>()}; - LOG_DEBUG(Service_Mii, "called with source_flag={}", source_flag); + u32 mii_count{}; + std::vector<CharInfoElement> char_info_elements(output_size); + const auto result = manager->Get(metadata, char_info_elements, mii_count, source_flag); - const auto default_miis{manager.GetDefault(source_flag)}; - if (default_miis.size() > 0) { - ctx.WriteBuffer(SerializeArray(default_miis)); + if (mii_count != 0) { + ctx.WriteBuffer(char_info_elements); } + LOG_INFO(Service_Mii, "called with source_flag={}, out_size={}, mii_count={}", source_flag, + output_size, mii_count); + IPC::ResponseBuilder rb{ctx, 3}; - rb.Push(ResultSuccess); - rb.Push<u32>(static_cast<u32>(default_miis.size())); + rb.Push(result); + rb.Push(mii_count); } void Get1(HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const auto source_flag{rp.PopRaw<SourceFlag>()}; + const auto output_size{ctx.GetWriteBufferNumElements<CharInfo>()}; - LOG_DEBUG(Service_Mii, "called with source_flag={}", source_flag); + u32 mii_count{}; + std::vector<CharInfo> char_info(output_size); + const auto result = manager->Get(metadata, char_info, mii_count, source_flag); - const auto default_miis{manager.GetDefault(source_flag)}; - - std::vector<CharInfo> values; - for (const auto& element : default_miis) { - values.emplace_back(element.info); + if (mii_count != 0) { + ctx.WriteBuffer(char_info); } - ctx.WriteBuffer(SerializeArray(values)); + LOG_INFO(Service_Mii, "called with source_flag={}, out_size={}, mii_count={}", source_flag, + output_size, mii_count); IPC::ResponseBuilder rb{ctx, 3}; - rb.Push(ResultSuccess); - rb.Push<u32>(static_cast<u32>(default_miis.size())); + rb.Push(result); + rb.Push(mii_count); } void UpdateLatest(HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - const auto info{rp.PopRaw<CharInfo>()}; + const auto char_info{rp.PopRaw<CharInfo>()}; const auto source_flag{rp.PopRaw<SourceFlag>()}; - LOG_DEBUG(Service_Mii, "called with source_flag={}", source_flag); + LOG_INFO(Service_Mii, "called with source_flag={}", source_flag); CharInfo new_char_info{}; - const auto result{manager.UpdateLatest(&new_char_info, info, source_flag)}; - if (result != ResultSuccess) { + const auto result = manager->UpdateLatest(metadata, new_char_info, char_info, source_flag); + if (result.IsFailure()) { IPC::ResponseBuilder rb{ctx, 2}; rb.Push(result); return; @@ -148,12 +155,11 @@ private: IPC::ResponseBuilder rb{ctx, 2 + sizeof(CharInfo) / sizeof(u32)}; rb.Push(ResultSuccess); - rb.PushRaw<CharInfo>(new_char_info); + rb.PushRaw(new_char_info); } void BuildRandom(HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - const auto age{rp.PopRaw<Age>()}; const auto gender{rp.PopRaw<Gender>()}; const auto race{rp.PopRaw<Race>()}; @@ -162,28 +168,28 @@ private: if (age > Age::All) { IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ERROR_INVALID_ARGUMENT); - LOG_ERROR(Service_Mii, "invalid age={}", age); + rb.Push(ResultInvalidArgument); return; } if (gender > Gender::All) { IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ERROR_INVALID_ARGUMENT); - LOG_ERROR(Service_Mii, "invalid gender={}", gender); + rb.Push(ResultInvalidArgument); return; } if (race > Race::All) { IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ERROR_INVALID_ARGUMENT); - LOG_ERROR(Service_Mii, "invalid race={}", race); + rb.Push(ResultInvalidArgument); return; } + CharInfo char_info{}; + manager->BuildRandom(char_info, age, gender, race); + IPC::ResponseBuilder rb{ctx, 2 + sizeof(CharInfo) / sizeof(u32)}; rb.Push(ResultSuccess); - rb.PushRaw<CharInfo>(manager.BuildRandom(age, gender, race)); + rb.PushRaw(char_info); } void BuildDefault(HLERequestContext& ctx) { @@ -193,16 +199,249 @@ private: LOG_DEBUG(Service_Mii, "called with index={}", index); if (index > 5) { - LOG_ERROR(Service_Mii, "invalid argument, index cannot be greater than 5 but is {:08X}", - index); IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ERROR_INVALID_ARGUMENT); + rb.Push(ResultInvalidArgument); return; } + CharInfo char_info{}; + manager->BuildDefault(char_info, index); + IPC::ResponseBuilder rb{ctx, 2 + sizeof(CharInfo) / sizeof(u32)}; rb.Push(ResultSuccess); - rb.PushRaw<CharInfo>(manager.BuildDefault(index)); + rb.PushRaw(char_info); + } + + void Get2(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto source_flag{rp.PopRaw<SourceFlag>()}; + const auto output_size{ctx.GetWriteBufferNumElements<StoreDataElement>()}; + + u32 mii_count{}; + std::vector<StoreDataElement> store_data_elements(output_size); + const auto result = manager->Get(metadata, store_data_elements, mii_count, source_flag); + + if (mii_count != 0) { + ctx.WriteBuffer(store_data_elements); + } + + LOG_INFO(Service_Mii, "called with source_flag={}, out_size={}, mii_count={}", source_flag, + output_size, mii_count); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(result); + rb.Push(mii_count); + } + + void Get3(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto source_flag{rp.PopRaw<SourceFlag>()}; + const auto output_size{ctx.GetWriteBufferNumElements<StoreData>()}; + + u32 mii_count{}; + std::vector<StoreData> store_data(output_size); + const auto result = manager->Get(metadata, store_data, mii_count, source_flag); + + if (mii_count != 0) { + ctx.WriteBuffer(store_data); + } + + LOG_INFO(Service_Mii, "called with source_flag={}, out_size={}, mii_count={}", source_flag, + output_size, mii_count); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(result); + rb.Push(mii_count); + } + + void UpdateLatest1(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto store_data{rp.PopRaw<StoreData>()}; + const auto source_flag{rp.PopRaw<SourceFlag>()}; + + LOG_INFO(Service_Mii, "called with source_flag={}", source_flag); + + Result result = ResultSuccess; + if (!is_system) { + result = ResultPermissionDenied; + } + + StoreData new_store_data{}; + if (result.IsSuccess()) { + result = manager->UpdateLatest(metadata, new_store_data, store_data, source_flag); + } + + if (result.IsFailure()) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(result); + return; + } + + IPC::ResponseBuilder rb{ctx, 2 + sizeof(StoreData) / sizeof(u32)}; + rb.Push(ResultSuccess); + rb.PushRaw<StoreData>(new_store_data); + } + + void FindIndex(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto create_id{rp.PopRaw<Common::UUID>()}; + const auto is_special{rp.PopRaw<bool>()}; + + LOG_INFO(Service_Mii, "called with create_id={}, is_special={}", + create_id.FormattedString(), is_special); + + const s32 index = manager->FindIndex(create_id, is_special); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(ResultSuccess); + rb.Push(index); + } + + void Move(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto create_id{rp.PopRaw<Common::UUID>()}; + const auto new_index{rp.PopRaw<s32>()}; + + LOG_INFO(Service_Mii, "called with create_id={}, new_index={}", create_id.FormattedString(), + new_index); + + Result result = ResultSuccess; + if (!is_system) { + result = ResultPermissionDenied; + } + + if (result.IsSuccess()) { + const u32 count = manager->GetCount(metadata, SourceFlag::Database); + if (new_index < 0 || new_index >= static_cast<s32>(count)) { + result = ResultInvalidArgument; + } + } + + if (result.IsSuccess()) { + result = manager->Move(metadata, new_index, create_id); + } + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(result); + } + + void AddOrReplace(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto store_data{rp.PopRaw<StoreData>()}; + + LOG_INFO(Service_Mii, "called"); + + Result result = ResultSuccess; + + if (!is_system) { + result = ResultPermissionDenied; + } + + if (result.IsSuccess()) { + result = manager->AddOrReplace(metadata, store_data); + } + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(result); + } + + void Delete(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto create_id{rp.PopRaw<Common::UUID>()}; + + LOG_INFO(Service_Mii, "called, create_id={}", create_id.FormattedString()); + + Result result = ResultSuccess; + + if (!is_system) { + result = ResultPermissionDenied; + } + + if (result.IsSuccess()) { + result = manager->Delete(metadata, create_id); + } + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(result); + } + + void DestroyFile(HLERequestContext& ctx) { + // This calls nn::settings::fwdbg::GetSettingsItemValue("is_db_test_mode_enabled"); + const bool is_db_test_mode_enabled = false; + + LOG_INFO(Service_Mii, "called is_db_test_mode_enabled={}", is_db_test_mode_enabled); + + Result result = ResultSuccess; + + if (!is_db_test_mode_enabled) { + result = ResultTestModeOnly; + } + + if (result.IsSuccess()) { + result = manager->DestroyFile(metadata); + } + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(result); + } + + void DeleteFile(HLERequestContext& ctx) { + // This calls nn::settings::fwdbg::GetSettingsItemValue("is_db_test_mode_enabled"); + const bool is_db_test_mode_enabled = false; + + LOG_INFO(Service_Mii, "called is_db_test_mode_enabled={}", is_db_test_mode_enabled); + + Result result = ResultSuccess; + + if (!is_db_test_mode_enabled) { + result = ResultTestModeOnly; + } + + if (result.IsSuccess()) { + result = manager->DeleteFile(); + } + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(result); + } + + void Format(HLERequestContext& ctx) { + // This calls nn::settings::fwdbg::GetSettingsItemValue("is_db_test_mode_enabled"); + const bool is_db_test_mode_enabled = false; + + LOG_INFO(Service_Mii, "called is_db_test_mode_enabled={}", is_db_test_mode_enabled); + + Result result = ResultSuccess; + + if (!is_db_test_mode_enabled) { + result = ResultTestModeOnly; + } + + if (result.IsSuccess()) { + result = manager->Format(metadata); + } + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(result); + } + + void IsBrokenDatabaseWithClearFlag(HLERequestContext& ctx) { + LOG_DEBUG(Service_Mii, "called"); + + bool is_broken_with_clear_flag = false; + Result result = ResultSuccess; + + if (!is_system) { + result = ResultPermissionDenied; + } + + if (result.IsSuccess()) { + is_broken_with_clear_flag = manager->IsBrokenWithClearFlag(metadata); + } + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(result); + rb.Push<u8>(is_broken_with_clear_flag); } void GetIndex(HLERequestContext& ctx) { @@ -211,19 +450,21 @@ private: LOG_DEBUG(Service_Mii, "called"); - u32 index{}; + s32 index{}; + const auto result = manager->GetIndex(metadata, info, index); + IPC::ResponseBuilder rb{ctx, 3}; - rb.Push(manager.GetIndex(info, index)); + rb.Push(result); rb.Push(index); } void SetInterfaceVersion(HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - current_interface_version = rp.PopRaw<u32>(); + const auto interface_version{rp.PopRaw<u32>()}; - LOG_DEBUG(Service_Mii, "called, interface_version={:08X}", current_interface_version); + LOG_INFO(Service_Mii, "called, interface_version={:08X}", interface_version); - UNIMPLEMENTED_IF(current_interface_version != 1); + manager->SetInterfaceVersion(metadata, interface_version); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(ResultSuccess); @@ -231,57 +472,101 @@ private: void Convert(HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - const auto mii_v3{rp.PopRaw<Ver3StoreData>()}; LOG_INFO(Service_Mii, "called"); + CharInfo char_info{}; + const auto result = manager->ConvertV3ToCharInfo(char_info, mii_v3); + IPC::ResponseBuilder rb{ctx, 2 + sizeof(CharInfo) / sizeof(u32)}; - rb.Push(ResultSuccess); - rb.PushRaw<CharInfo>(manager.ConvertV3ToCharInfo(mii_v3)); + rb.Push(result); + rb.PushRaw<CharInfo>(char_info); } - constexpr bool IsInterfaceVersionSupported(u32 interface_version) const { - return current_interface_version >= interface_version; + void ConvertCoreDataToCharInfo(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto core_data{rp.PopRaw<CoreData>()}; + + LOG_INFO(Service_Mii, "called"); + + CharInfo char_info{}; + const auto result = manager->ConvertCoreDataToCharInfo(char_info, core_data); + + IPC::ResponseBuilder rb{ctx, 2 + sizeof(CharInfo) / sizeof(u32)}; + rb.Push(result); + rb.PushRaw<CharInfo>(char_info); } - MiiManager manager; + void ConvertCharInfoToCoreData(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto char_info{rp.PopRaw<CharInfo>()}; - u32 current_interface_version{}; - u64 current_update_counter{}; -}; + LOG_INFO(Service_Mii, "called"); -class MiiDBModule final : public ServiceFramework<MiiDBModule> { -public: - explicit MiiDBModule(Core::System& system_, const char* name_) - : ServiceFramework{system_, name_} { - // clang-format off - static const FunctionInfo functions[] = { - {0, &MiiDBModule::GetDatabaseService, "GetDatabaseService"}, - }; - // clang-format on + CoreData core_data{}; + const auto result = manager->ConvertCharInfoToCoreData(core_data, char_info); - RegisterHandlers(functions); + IPC::ResponseBuilder rb{ctx, 2 + sizeof(CoreData) / sizeof(u32)}; + rb.Push(result); + rb.PushRaw<CoreData>(core_data); } -private: - void GetDatabaseService(HLERequestContext& ctx) { - IPC::ResponseBuilder rb{ctx, 2, 0, 1}; - rb.Push(ResultSuccess); - rb.PushIpcInterface<IDatabaseService>(system); + void Append(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto char_info{rp.PopRaw<CharInfo>()}; - LOG_DEBUG(Service_Mii, "called"); + LOG_INFO(Service_Mii, "called"); + + const auto result = manager->Append(metadata, char_info); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(result); } + + std::shared_ptr<MiiManager> manager = nullptr; + DatabaseSessionMetadata metadata{}; + bool is_system{}; }; +MiiDBModule::MiiDBModule(Core::System& system_, const char* name_, + std::shared_ptr<MiiManager> mii_manager, bool is_system_) + : ServiceFramework{system_, name_}, manager{mii_manager}, is_system{is_system_} { + // clang-format off + static const FunctionInfo functions[] = { + {0, &MiiDBModule::GetDatabaseService, "GetDatabaseService"}, + }; + // clang-format on + + RegisterHandlers(functions); + + if (manager == nullptr) { + manager = std::make_shared<MiiManager>(); + } +} + +MiiDBModule::~MiiDBModule() = default; + +void MiiDBModule::GetDatabaseService(HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(ResultSuccess); + rb.PushIpcInterface<IDatabaseService>(system, manager, is_system); + + LOG_DEBUG(Service_Mii, "called"); +} + +std::shared_ptr<MiiManager> MiiDBModule::GetMiiManager() { + return manager; +} + class MiiImg final : public ServiceFramework<MiiImg> { public: explicit MiiImg(Core::System& system_) : ServiceFramework{system_, "miiimg"} { // clang-format off static const FunctionInfo functions[] = { - {0, nullptr, "Initialize"}, + {0, &MiiImg::Initialize, "Initialize"}, {10, nullptr, "Reload"}, - {11, nullptr, "GetCount"}, + {11, &MiiImg::GetCount, "GetCount"}, {12, nullptr, "IsEmpty"}, {13, nullptr, "IsFull"}, {14, nullptr, "GetAttribute"}, @@ -298,13 +583,32 @@ public: RegisterHandlers(functions); } + +private: + void Initialize(HLERequestContext& ctx) { + LOG_INFO(Service_Mii, "called"); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSuccess); + } + + void GetCount(HLERequestContext& ctx) { + LOG_DEBUG(Service_Mii, "called"); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(ResultSuccess); + rb.Push(0); + } }; void LoopProcess(Core::System& system) { auto server_manager = std::make_unique<ServerManager>(system); + std::shared_ptr<MiiManager> manager = nullptr; - server_manager->RegisterNamedService("mii:e", std::make_shared<MiiDBModule>(system, "mii:e")); - server_manager->RegisterNamedService("mii:u", std::make_shared<MiiDBModule>(system, "mii:u")); + server_manager->RegisterNamedService( + "mii:e", std::make_shared<MiiDBModule>(system, "mii:e", manager, true)); + server_manager->RegisterNamedService( + "mii:u", std::make_shared<MiiDBModule>(system, "mii:u", manager, false)); server_manager->RegisterNamedService("miiimg", std::make_shared<MiiImg>(system)); ServerManager::RunServer(std::move(server_manager)); } diff --git a/src/core/hle/service/mii/mii.h b/src/core/hle/service/mii/mii.h index ed4e3f62b..9aa4426f6 100644 --- a/src/core/hle/service/mii/mii.h +++ b/src/core/hle/service/mii/mii.h @@ -3,11 +3,29 @@ #pragma once +#include "core/hle/service/service.h" + namespace Core { class System; } namespace Service::Mii { +class MiiManager; + +class MiiDBModule final : public ServiceFramework<MiiDBModule> { +public: + explicit MiiDBModule(Core::System& system_, const char* name_, + std::shared_ptr<MiiManager> mii_manager, bool is_system_); + ~MiiDBModule() override; + + std::shared_ptr<MiiManager> GetMiiManager(); + +private: + void GetDatabaseService(HLERequestContext& ctx); + + std::shared_ptr<MiiManager> manager = nullptr; + bool is_system{}; +}; void LoopProcess(Core::System& system); diff --git a/src/core/hle/service/mii/mii_database.cpp b/src/core/hle/service/mii/mii_database.cpp new file mode 100644 index 000000000..3803e58e2 --- /dev/null +++ b/src/core/hle/service/mii/mii_database.cpp @@ -0,0 +1,142 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "core/hle/service/mii/mii_database.h" +#include "core/hle/service/mii/mii_result.h" +#include "core/hle/service/mii/mii_util.h" + +namespace Service::Mii { + +u8 NintendoFigurineDatabase::GetDatabaseLength() const { + return database_length; +} + +bool NintendoFigurineDatabase::IsFull() const { + return database_length >= MaxDatabaseLength; +} + +StoreData NintendoFigurineDatabase::Get(std::size_t index) const { + StoreData store_data = miis.at(index); + + // This hack is to make external database dumps compatible + store_data.SetDeviceChecksum(); + + return store_data; +} + +u32 NintendoFigurineDatabase::GetCount(const DatabaseSessionMetadata& metadata) const { + if (magic == MiiMagic) { + return GetDatabaseLength(); + } + + u32 mii_count{}; + for (std::size_t index = 0; index < mii_count; ++index) { + const auto& store_data = Get(index); + if (!store_data.IsSpecial()) { + mii_count++; + } + } + + return mii_count; +} + +bool NintendoFigurineDatabase::GetIndexByCreatorId(u32& out_index, + const Common::UUID& create_id) const { + for (std::size_t index = 0; index < database_length; ++index) { + if (miis[index].GetCreateId() == create_id) { + out_index = static_cast<u32>(index); + return true; + } + } + + return false; +} + +Result NintendoFigurineDatabase::Move(u32 current_index, u32 new_index) { + if (current_index == new_index) { + return ResultNotUpdated; + } + + const StoreData store_data = miis[current_index]; + + if (new_index > current_index) { + // Shift left + const u32 index_diff = new_index - current_index; + for (std::size_t i = 0; i < index_diff; i++) { + miis[current_index + i] = miis[current_index + i + 1]; + } + } else { + // Shift right + const u32 index_diff = current_index - new_index; + for (std::size_t i = 0; i < index_diff; i++) { + miis[current_index - i] = miis[current_index - i - 1]; + } + } + + miis[new_index] = store_data; + crc = GenerateDatabaseCrc(); + return ResultSuccess; +} + +void NintendoFigurineDatabase::Replace(u32 index, const StoreData& store_data) { + miis[index] = store_data; + crc = GenerateDatabaseCrc(); +} + +void NintendoFigurineDatabase::Add(const StoreData& store_data) { + miis[database_length] = store_data; + database_length++; + crc = GenerateDatabaseCrc(); +} + +void NintendoFigurineDatabase::Delete(u32 index) { + // Shift left + const s32 new_database_size = database_length - 1; + if (static_cast<s32>(index) < new_database_size) { + for (std::size_t i = index; i < static_cast<std::size_t>(new_database_size); i++) { + miis[i] = miis[i + 1]; + } + } + + database_length = static_cast<u8>(new_database_size); + crc = GenerateDatabaseCrc(); +} + +void NintendoFigurineDatabase::CleanDatabase() { + miis = {}; + version = 1; + magic = DatabaseMagic; + database_length = 0; + crc = GenerateDatabaseCrc(); +} + +void NintendoFigurineDatabase::CorruptCrc() { + crc = GenerateDatabaseCrc(); + crc = ~crc; +} + +Result NintendoFigurineDatabase::CheckIntegrity() { + if (magic != DatabaseMagic) { + return ResultInvalidDatabaseSignature; + } + + if (version != 1) { + return ResultInvalidDatabaseVersion; + } + + if (crc != GenerateDatabaseCrc()) { + return ResultInvalidDatabaseChecksum; + } + + if (database_length >= MaxDatabaseLength) { + return ResultInvalidDatabaseLength; + } + + return ResultSuccess; +} + +u16 NintendoFigurineDatabase::GenerateDatabaseCrc() { + return MiiUtil::CalculateCrc16(&magic, sizeof(NintendoFigurineDatabase) - sizeof(crc)); +} + +} // namespace Service::Mii diff --git a/src/core/hle/service/mii/mii_database.h b/src/core/hle/service/mii/mii_database.h new file mode 100644 index 000000000..3bd240f93 --- /dev/null +++ b/src/core/hle/service/mii/mii_database.h @@ -0,0 +1,66 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "core/hle/result.h" +#include "core/hle/service/mii/types/store_data.h" + +namespace Service::Mii { + +constexpr std::size_t MaxDatabaseLength{100}; +constexpr u32 MiiMagic{0xa523b78f}; +constexpr u32 DatabaseMagic{0x4244464e}; // NFDB + +class NintendoFigurineDatabase { +public: + /// Returns the total mii count. + u8 GetDatabaseLength() const; + + /// Returns true if database is full. + bool IsFull() const; + + /// Returns the mii of the specified index. + StoreData Get(std::size_t index) const; + + /// Returns the total mii count. Ignoring special mii. + u32 GetCount(const DatabaseSessionMetadata& metadata) const; + + /// Returns the index of a mii. If the mii isn't found returns false. + bool GetIndexByCreatorId(u32& out_index, const Common::UUID& create_id) const; + + /// Moves the location of a specific mii. + Result Move(u32 current_index, u32 new_index); + + /// Replaces mii with new data. + void Replace(u32 index, const StoreData& store_data); + + /// Adds a new mii to the end of the database. + void Add(const StoreData& store_data); + + /// Removes mii from database and shifts left the remainding data. + void Delete(u32 index); + + /// Deletes all contents with a fresh database + void CleanDatabase(); + + /// Intentionally sets a bad checksum + void CorruptCrc(); + + /// Returns success if database is valid otherwise returns the corresponding error code. + Result CheckIntegrity(); + +private: + /// Returns the checksum of the database + u16 GenerateDatabaseCrc(); + + u32 magic{}; // 'NFDB' + std::array<StoreData, MaxDatabaseLength> miis{}; + u8 version{}; + u8 database_length{}; + u16 crc{}; +}; +static_assert(sizeof(NintendoFigurineDatabase) == 0x1A98, + "NintendoFigurineDatabase has incorrect size."); + +}; // namespace Service::Mii diff --git a/src/core/hle/service/mii/mii_database_manager.cpp b/src/core/hle/service/mii/mii_database_manager.cpp new file mode 100644 index 000000000..0080b6705 --- /dev/null +++ b/src/core/hle/service/mii/mii_database_manager.cpp @@ -0,0 +1,420 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "common/assert.h" +#include "common/fs/file.h" +#include "common/fs/fs.h" +#include "common/fs/path_util.h" +#include "common/logging/log.h" +#include "common/string_util.h" + +#include "core/hle/service/mii/mii_database_manager.h" +#include "core/hle/service/mii/mii_result.h" +#include "core/hle/service/mii/mii_util.h" +#include "core/hle/service/mii/types/char_info.h" +#include "core/hle/service/mii/types/store_data.h" + +namespace Service::Mii { +const char* DbFileName = "MiiDatabase.dat"; + +DatabaseManager::DatabaseManager() {} + +Result DatabaseManager::MountSaveData() { + if (!is_save_data_mounted) { + system_save_dir = + Common::FS::GetYuzuPath(Common::FS::YuzuPath::NANDDir) / "system/save/8000000000000030"; + if (is_test_db) { + system_save_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::NANDDir) / + "system/save/8000000000000031"; + } + + // mount point should be "mii:" + + if (!Common::FS::CreateDirs(system_save_dir)) { + return ResultUnknown; + } + } + + is_save_data_mounted = true; + return ResultSuccess; +} + +Result DatabaseManager::Initialize(DatabaseSessionMetadata& metadata, bool& is_database_broken) { + is_database_broken = false; + if (!is_save_data_mounted) { + return ResultInvalidArgument; + } + + database.CleanDatabase(); + update_counter++; + metadata.update_counter = update_counter; + + const Common::FS::IOFile db_file{system_save_dir / DbFileName, Common::FS::FileAccessMode::Read, + Common::FS::FileType::BinaryFile}; + + if (!db_file.IsOpen()) { + return SaveDatabase(); + } + + if (Common::FS::GetSize(system_save_dir / DbFileName) != sizeof(NintendoFigurineDatabase)) { + is_database_broken = true; + } + + if (db_file.Read(database) != 1) { + is_database_broken = true; + } + + if (is_database_broken) { + // Dragons happen here for simplicity just clean the database + LOG_ERROR(Service_Mii, "Mii database is corrupted"); + database.CleanDatabase(); + return ResultUnknown; + } + + const auto result = database.CheckIntegrity(); + + if (result.IsError()) { + LOG_ERROR(Service_Mii, "Mii database is corrupted 0x{:0x}", result.raw); + database.CleanDatabase(); + return ResultSuccess; + } + + LOG_INFO(Service_Mii, "Successfully loaded mii database. size={}", + database.GetDatabaseLength()); + return ResultSuccess; +} + +bool DatabaseManager::IsFullDatabase() const { + return database.GetDatabaseLength() == MaxDatabaseLength; +} + +bool DatabaseManager::IsModified() const { + return is_moddified; +} + +u64 DatabaseManager::GetUpdateCounter() const { + return update_counter; +} + +u32 DatabaseManager::GetCount(const DatabaseSessionMetadata& metadata) const { + const u32 database_size = database.GetDatabaseLength(); + if (metadata.magic == MiiMagic) { + return database_size; + } + + // Special mii can't be used. Skip those. + + u32 mii_count{}; + for (std::size_t index = 0; index < database_size; ++index) { + const auto& store_data = database.Get(index); + if (store_data.IsSpecial()) { + continue; + } + mii_count++; + } + + return mii_count; +} + +void DatabaseManager::Get(StoreData& out_store_data, std::size_t index, + const DatabaseSessionMetadata& metadata) const { + if (metadata.magic == MiiMagic) { + out_store_data = database.Get(index); + return; + } + + // The index refeers to the mii index without special mii. + // Search on the database until we find it + + u32 virtual_index = 0; + const u32 database_size = database.GetDatabaseLength(); + for (std::size_t i = 0; i < database_size; ++i) { + const auto& store_data = database.Get(i); + if (store_data.IsSpecial()) { + continue; + } + if (virtual_index == index) { + out_store_data = store_data; + return; + } + virtual_index++; + } + + // This function doesn't fail. It returns the first mii instead + out_store_data = database.Get(0); +} + +Result DatabaseManager::FindIndex(s32& out_index, const Common::UUID& create_id, + bool is_special) const { + u32 index{}; + const bool is_found = database.GetIndexByCreatorId(index, create_id); + + if (!is_found) { + return ResultNotFound; + } + + if (is_special) { + out_index = index; + return ResultSuccess; + } + + if (database.Get(index).IsSpecial()) { + return ResultNotFound; + } + + out_index = 0; + + if (index < 1) { + return ResultSuccess; + } + + for (std::size_t i = 0; i < index; ++i) { + if (database.Get(i).IsSpecial()) { + continue; + } + out_index++; + } + return ResultSuccess; +} + +Result DatabaseManager::FindIndex(const DatabaseSessionMetadata& metadata, u32& out_index, + const Common::UUID& create_id) const { + u32 index{}; + const bool is_found = database.GetIndexByCreatorId(index, create_id); + + if (!is_found) { + return ResultNotFound; + } + + if (metadata.magic == MiiMagic) { + out_index = index; + return ResultSuccess; + } + + if (database.Get(index).IsSpecial()) { + return ResultNotFound; + } + + out_index = 0; + + if (index < 1) { + return ResultSuccess; + } + + // The index refeers to the mii index without special mii. + // Search on the database until we find it + + for (std::size_t i = 0; i <= index; ++i) { + const auto& store_data = database.Get(i); + if (store_data.IsSpecial()) { + continue; + } + out_index++; + } + return ResultSuccess; +} + +Result DatabaseManager::FindMoveIndex(u32& out_index, u32 new_index, + const Common::UUID& create_id) const { + const auto database_size = database.GetDatabaseLength(); + + if (database_size >= 1) { + u32 virtual_index{}; + for (std::size_t i = 0; i < database_size; ++i) { + const StoreData& store_data = database.Get(i); + if (store_data.IsSpecial()) { + continue; + } + if (virtual_index == new_index) { + const bool is_found = database.GetIndexByCreatorId(out_index, create_id); + if (!is_found) { + return ResultNotFound; + } + if (store_data.IsSpecial()) { + return ResultInvalidOperation; + } + return ResultSuccess; + } + virtual_index++; + } + } + + const bool is_found = database.GetIndexByCreatorId(out_index, create_id); + if (!is_found) { + return ResultNotFound; + } + const StoreData& store_data = database.Get(out_index); + if (store_data.IsSpecial()) { + return ResultInvalidOperation; + } + return ResultSuccess; +} + +Result DatabaseManager::Move(DatabaseSessionMetadata& metadata, u32 new_index, + const Common::UUID& create_id) { + u32 current_index{}; + if (metadata.magic == MiiMagic) { + const bool is_found = database.GetIndexByCreatorId(current_index, create_id); + if (!is_found) { + return ResultNotFound; + } + } else { + const auto result = FindMoveIndex(current_index, new_index, create_id); + if (result.IsError()) { + return result; + } + } + + const auto result = database.Move(current_index, new_index); + if (result.IsFailure()) { + return result; + } + + is_moddified = true; + update_counter++; + metadata.update_counter = update_counter; + return ResultSuccess; +} + +Result DatabaseManager::AddOrReplace(DatabaseSessionMetadata& metadata, + const StoreData& store_data) { + if (store_data.IsValid() != ValidationResult::NoErrors) { + return ResultInvalidStoreData; + } + if (metadata.magic != MiiMagic && store_data.IsSpecial()) { + return ResultInvalidOperation; + } + + u32 index{}; + const bool is_found = database.GetIndexByCreatorId(index, store_data.GetCreateId()); + if (is_found) { + const StoreData& old_store_data = database.Get(index); + + if (store_data.IsSpecial() != old_store_data.IsSpecial()) { + return ResultInvalidOperation; + } + + database.Replace(index, store_data); + } else { + if (database.IsFull()) { + return ResultDatabaseFull; + } + + database.Add(store_data); + } + + is_moddified = true; + update_counter++; + metadata.update_counter = update_counter; + return ResultSuccess; +} + +Result DatabaseManager::Delete(DatabaseSessionMetadata& metadata, const Common::UUID& create_id) { + u32 index{}; + const bool is_found = database.GetIndexByCreatorId(index, create_id); + if (!is_found) { + return ResultNotFound; + } + + if (metadata.magic != MiiMagic) { + const auto& store_data = database.Get(index); + if (store_data.IsSpecial()) { + return ResultInvalidOperation; + } + } + + database.Delete(index); + + is_moddified = true; + update_counter++; + metadata.update_counter = update_counter; + return ResultSuccess; +} + +Result DatabaseManager::Append(DatabaseSessionMetadata& metadata, const CharInfo& char_info) { + if (char_info.Verify() != ValidationResult::NoErrors) { + return ResultInvalidCharInfo2; + } + if (char_info.GetType() == 1) { + return ResultInvalidCharInfoType; + } + + u32 index{}; + StoreData store_data{}; + + // Loop until the mii we created is not on the database + do { + store_data.BuildWithCharInfo(char_info); + } while (database.GetIndexByCreatorId(index, store_data.GetCreateId())); + + const Result result = store_data.Restore(); + + if (result.IsSuccess() || result == ResultNotUpdated) { + return AddOrReplace(metadata, store_data); + } + + return result; +} + +Result DatabaseManager::DestroyFile(DatabaseSessionMetadata& metadata) { + database.CorruptCrc(); + + is_moddified = true; + update_counter++; + metadata.update_counter = update_counter; + + const auto result = SaveDatabase(); + database.CleanDatabase(); + + return result; +} + +Result DatabaseManager::DeleteFile() { + const bool result = Common::FS::RemoveFile(system_save_dir / DbFileName); + // TODO: Return proper FS error here + return result ? ResultSuccess : ResultUnknown; +} + +void DatabaseManager::Format(DatabaseSessionMetadata& metadata) { + database.CleanDatabase(); + is_moddified = true; + update_counter++; + metadata.update_counter = update_counter; +} + +Result DatabaseManager::SaveDatabase() { + // TODO: Replace unknown error codes with proper FS error codes when available + + if (!Common::FS::Exists(system_save_dir / DbFileName)) { + if (!Common::FS::NewFile(system_save_dir / DbFileName)) { + LOG_ERROR(Service_Mii, "Failed to create mii database"); + return ResultUnknown; + } + } + + const auto file_size = Common::FS::GetSize(system_save_dir / DbFileName); + if (file_size != 0 && file_size != sizeof(NintendoFigurineDatabase)) { + if (!Common::FS::RemoveFile(system_save_dir / DbFileName)) { + LOG_ERROR(Service_Mii, "Failed to delete mii database"); + return ResultUnknown; + } + if (!Common::FS::NewFile(system_save_dir / DbFileName)) { + LOG_ERROR(Service_Mii, "Failed to create mii database"); + return ResultUnknown; + } + } + + const Common::FS::IOFile db_file{system_save_dir / DbFileName, + Common::FS::FileAccessMode::ReadWrite, + Common::FS::FileType::BinaryFile}; + + if (db_file.Write(database) != 1) { + LOG_ERROR(Service_Mii, "Failed to save mii database"); + return ResultUnknown; + } + + is_moddified = false; + return ResultSuccess; +} + +} // namespace Service::Mii diff --git a/src/core/hle/service/mii/mii_database_manager.h b/src/core/hle/service/mii/mii_database_manager.h new file mode 100644 index 000000000..52c32be82 --- /dev/null +++ b/src/core/hle/service/mii/mii_database_manager.h @@ -0,0 +1,58 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "common/fs/fs.h" +#include "core/hle/result.h" +#include "core/hle/service/mii/mii_database.h" + +namespace Service::Mii { +class CharInfo; +class StoreData; + +class DatabaseManager { +public: + DatabaseManager(); + Result MountSaveData(); + Result Initialize(DatabaseSessionMetadata& metadata, bool& is_database_broken); + + bool IsFullDatabase() const; + bool IsModified() const; + u64 GetUpdateCounter() const; + + void Get(StoreData& out_store_data, std::size_t index, + const DatabaseSessionMetadata& metadata) const; + u32 GetCount(const DatabaseSessionMetadata& metadata) const; + + Result FindIndex(s32& out_index, const Common::UUID& create_id, bool is_special) const; + Result FindIndex(const DatabaseSessionMetadata& metadata, u32& out_index, + const Common::UUID& create_id) const; + Result FindMoveIndex(u32& out_index, u32 new_index, const Common::UUID& create_id) const; + + Result Move(DatabaseSessionMetadata& metadata, u32 current_index, + const Common::UUID& create_id); + Result AddOrReplace(DatabaseSessionMetadata& metadata, const StoreData& out_store_data); + Result Delete(DatabaseSessionMetadata& metadata, const Common::UUID& create_id); + Result Append(DatabaseSessionMetadata& metadata, const CharInfo& char_info); + + Result DestroyFile(DatabaseSessionMetadata& metadata); + Result DeleteFile(); + void Format(DatabaseSessionMetadata& metadata); + + Result SaveDatabase(); + +private: + // This is the global value of + // nn::settings::fwdbg::GetSettingsItemValue("is_db_test_mode_enabled"); + bool is_test_db{}; + + bool is_moddified{}; + bool is_save_data_mounted{}; + u64 update_counter{}; + NintendoFigurineDatabase database{}; + + std::filesystem::path system_save_dir{}; +}; + +}; // namespace Service::Mii diff --git a/src/core/hle/service/mii/mii_manager.cpp b/src/core/hle/service/mii/mii_manager.cpp index 46125d473..dcfd6b2e2 100644 --- a/src/core/hle/service/mii/mii_manager.cpp +++ b/src/core/hle/service/mii/mii_manager.cpp @@ -1,697 +1,486 @@ // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later -#include <cstring> -#include <random> - -#include "common/assert.h" #include "common/logging/log.h" -#include "common/string_util.h" - -#include "core/hle/service/acc/profile_manager.h" +#include "core/hle/service/mii/mii_database_manager.h" #include "core/hle/service/mii/mii_manager.h" -#include "core/hle/service/mii/raw_data.h" +#include "core/hle/service/mii/mii_result.h" +#include "core/hle/service/mii/mii_util.h" +#include "core/hle/service/mii/types/char_info.h" +#include "core/hle/service/mii/types/core_data.h" +#include "core/hle/service/mii/types/raw_data.h" +#include "core/hle/service/mii/types/store_data.h" +#include "core/hle/service/mii/types/ver3_store_data.h" namespace Service::Mii { +constexpr std::size_t DefaultMiiCount{RawData::DefaultMii.size()}; -namespace { +MiiManager::MiiManager() {} -constexpr Result ERROR_CANNOT_FIND_ENTRY{ErrorModule::Mii, 4}; +Result MiiManager::Initialize(DatabaseSessionMetadata& metadata) { + database_manager.MountSaveData(); + database_manager.Initialize(metadata, is_broken_with_clear_flag); + return ResultSuccess; +} -constexpr std::size_t BaseMiiCount{2}; -constexpr std::size_t DefaultMiiCount{RawData::DefaultMii.size()}; +void MiiManager::BuildDefault(CharInfo& out_char_info, u32 index) const { + StoreData store_data{}; + store_data.BuildDefault(index); + out_char_info.SetFromStoreData(store_data); +} -constexpr MiiStoreData::Name DefaultMiiName{u'y', u'u', u'z', u'u'}; -constexpr std::array<u8, 8> HairColorLookup{8, 1, 2, 3, 4, 5, 6, 7}; -constexpr std::array<u8, 6> EyeColorLookup{8, 9, 10, 11, 12, 13}; -constexpr std::array<u8, 5> MouthColorLookup{19, 20, 21, 22, 23}; -constexpr std::array<u8, 7> GlassesColorLookup{8, 14, 15, 16, 17, 18, 0}; -constexpr std::array<u8, 62> EyeRotateLookup{ - {0x03, 0x04, 0x04, 0x04, 0x03, 0x04, 0x04, 0x04, 0x03, 0x04, 0x04, 0x04, 0x04, 0x03, 0x03, 0x04, - 0x04, 0x04, 0x03, 0x03, 0x04, 0x03, 0x04, 0x03, 0x03, 0x04, 0x03, 0x04, 0x04, 0x03, 0x04, 0x04, - 0x04, 0x03, 0x03, 0x03, 0x04, 0x04, 0x03, 0x03, 0x03, 0x04, 0x04, 0x03, 0x03, 0x03, 0x03, 0x03, - 0x03, 0x03, 0x03, 0x03, 0x04, 0x04, 0x04, 0x04, 0x03, 0x04, 0x04, 0x03, 0x04, 0x04}}; -constexpr std::array<u8, 24> EyebrowRotateLookup{{0x06, 0x06, 0x05, 0x07, 0x06, 0x07, 0x06, 0x07, - 0x04, 0x07, 0x06, 0x08, 0x05, 0x05, 0x06, 0x06, - 0x07, 0x07, 0x06, 0x06, 0x05, 0x06, 0x07, 0x05}}; - -template <typename T, std::size_t SourceArraySize, std::size_t DestArraySize> -std::array<T, DestArraySize> ResizeArray(const std::array<T, SourceArraySize>& in) { - std::array<T, DestArraySize> out{}; - std::memcpy(out.data(), in.data(), sizeof(T) * std::min(SourceArraySize, DestArraySize)); - return out; +void MiiManager::BuildBase(CharInfo& out_char_info, Gender gender) const { + StoreData store_data{}; + store_data.BuildBase(gender); + out_char_info.SetFromStoreData(store_data); } -CharInfo ConvertStoreDataToInfo(const MiiStoreData& data) { - MiiStoreBitFields bf; - std::memcpy(&bf, data.data.data.data(), sizeof(MiiStoreBitFields)); - - return { - .uuid = data.data.uuid, - .name = ResizeArray<char16_t, 10, 11>(data.data.name), - .font_region = static_cast<u8>(bf.font_region.Value()), - .favorite_color = static_cast<u8>(bf.favorite_color.Value()), - .gender = static_cast<u8>(bf.gender.Value()), - .height = static_cast<u8>(bf.height.Value()), - .build = static_cast<u8>(bf.build.Value()), - .type = static_cast<u8>(bf.type.Value()), - .region_move = static_cast<u8>(bf.region_move.Value()), - .faceline_type = static_cast<u8>(bf.faceline_type.Value()), - .faceline_color = static_cast<u8>(bf.faceline_color.Value()), - .faceline_wrinkle = static_cast<u8>(bf.faceline_wrinkle.Value()), - .faceline_make = static_cast<u8>(bf.faceline_makeup.Value()), - .hair_type = static_cast<u8>(bf.hair_type.Value()), - .hair_color = static_cast<u8>(bf.hair_color.Value()), - .hair_flip = static_cast<u8>(bf.hair_flip.Value()), - .eye_type = static_cast<u8>(bf.eye_type.Value()), - .eye_color = static_cast<u8>(bf.eye_color.Value()), - .eye_scale = static_cast<u8>(bf.eye_scale.Value()), - .eye_aspect = static_cast<u8>(bf.eye_aspect.Value()), - .eye_rotate = static_cast<u8>(bf.eye_rotate.Value()), - .eye_x = static_cast<u8>(bf.eye_x.Value()), - .eye_y = static_cast<u8>(bf.eye_y.Value()), - .eyebrow_type = static_cast<u8>(bf.eyebrow_type.Value()), - .eyebrow_color = static_cast<u8>(bf.eyebrow_color.Value()), - .eyebrow_scale = static_cast<u8>(bf.eyebrow_scale.Value()), - .eyebrow_aspect = static_cast<u8>(bf.eyebrow_aspect.Value()), - .eyebrow_rotate = static_cast<u8>(bf.eyebrow_rotate.Value()), - .eyebrow_x = static_cast<u8>(bf.eyebrow_x.Value()), - .eyebrow_y = static_cast<u8>(bf.eyebrow_y.Value() + 3), - .nose_type = static_cast<u8>(bf.nose_type.Value()), - .nose_scale = static_cast<u8>(bf.nose_scale.Value()), - .nose_y = static_cast<u8>(bf.nose_y.Value()), - .mouth_type = static_cast<u8>(bf.mouth_type.Value()), - .mouth_color = static_cast<u8>(bf.mouth_color.Value()), - .mouth_scale = static_cast<u8>(bf.mouth_scale.Value()), - .mouth_aspect = static_cast<u8>(bf.mouth_aspect.Value()), - .mouth_y = static_cast<u8>(bf.mouth_y.Value()), - .beard_color = static_cast<u8>(bf.beard_color.Value()), - .beard_type = static_cast<u8>(bf.beard_type.Value()), - .mustache_type = static_cast<u8>(bf.mustache_type.Value()), - .mustache_scale = static_cast<u8>(bf.mustache_scale.Value()), - .mustache_y = static_cast<u8>(bf.mustache_y.Value()), - .glasses_type = static_cast<u8>(bf.glasses_type.Value()), - .glasses_color = static_cast<u8>(bf.glasses_color.Value()), - .glasses_scale = static_cast<u8>(bf.glasses_scale.Value()), - .glasses_y = static_cast<u8>(bf.glasses_y.Value()), - .mole_type = static_cast<u8>(bf.mole_type.Value()), - .mole_scale = static_cast<u8>(bf.mole_scale.Value()), - .mole_x = static_cast<u8>(bf.mole_x.Value()), - .mole_y = static_cast<u8>(bf.mole_y.Value()), - .padding = 0, - }; +void MiiManager::BuildRandom(CharInfo& out_char_info, Age age, Gender gender, Race race) const { + StoreData store_data{}; + store_data.BuildRandom(age, gender, race); + out_char_info.SetFromStoreData(store_data); } -u16 GenerateCrc16(const void* data, std::size_t size) { - s32 crc{}; - for (std::size_t i = 0; i < size; i++) { - crc ^= static_cast<const u8*>(data)[i] << 8; - for (std::size_t j = 0; j < 8; j++) { - crc <<= 1; - if ((crc & 0x10000) != 0) { - crc = (crc ^ 0x1021) & 0xFFFF; - } - } - } - return Common::swap16(static_cast<u16>(crc)); +bool MiiManager::IsFullDatabase() const { + return database_manager.IsFullDatabase(); } -template <typename T> -T GetRandomValue(T min, T max) { - std::random_device device; - std::mt19937 gen(device()); - std::uniform_int_distribution<u64> distribution(static_cast<u64>(min), static_cast<u64>(max)); - return static_cast<T>(distribution(gen)); +void MiiManager::SetInterfaceVersion(DatabaseSessionMetadata& metadata, u32 version) const { + metadata.interface_version = version; } -template <typename T> -T GetRandomValue(T max) { - return GetRandomValue<T>({}, max); +bool MiiManager::IsUpdated(DatabaseSessionMetadata& metadata, SourceFlag source_flag) const { + if ((source_flag & SourceFlag::Database) == SourceFlag::None) { + return false; + } + + const u64 metadata_update_counter = metadata.update_counter; + const u64 database_update_counter = database_manager.GetUpdateCounter(); + metadata.update_counter = database_update_counter; + return metadata_update_counter != database_update_counter; } -MiiStoreData BuildRandomStoreData(Age age, Gender gender, Race race, const Common::UUID& user_id) { - MiiStoreBitFields bf{}; - - if (gender == Gender::All) { - gender = GetRandomValue<Gender>(Gender::Maximum); - } - - bf.gender.Assign(gender); - bf.favorite_color.Assign(GetRandomValue<u8>(11)); - bf.region_move.Assign(0); - bf.font_region.Assign(FontRegion::Standard); - bf.type.Assign(0); - bf.height.Assign(64); - bf.build.Assign(64); - - if (age == Age::All) { - const auto temp{GetRandomValue<int>(10)}; - if (temp >= 8) { - age = Age::Old; - } else if (temp >= 4) { - age = Age::Normal; - } else { - age = Age::Young; - } +u32 MiiManager::GetCount(const DatabaseSessionMetadata& metadata, SourceFlag source_flag) const { + u32 mii_count{}; + if ((source_flag & SourceFlag::Default) != SourceFlag::None) { + mii_count += DefaultMiiCount; + } + if ((source_flag & SourceFlag::Database) != SourceFlag::None) { + mii_count += database_manager.GetCount(metadata); } + return mii_count; +} - if (race == Race::All) { - const auto temp{GetRandomValue<int>(10)}; - if (temp >= 8) { - race = Race::Black; - } else if (temp >= 4) { - race = Race::White; - } else { - race = Race::Asian; - } +Result MiiManager::Move(DatabaseSessionMetadata& metadata, u32 index, + const Common::UUID& create_id) { + const auto result = database_manager.Move(metadata, index, create_id); + + if (result.IsFailure()) { + return result; } - u32 axis_y{}; - if (gender == Gender::Female && age == Age::Young) { - axis_y = GetRandomValue<u32>(3); - } - - const std::size_t index{3 * static_cast<std::size_t>(age) + - 9 * static_cast<std::size_t>(gender) + static_cast<std::size_t>(race)}; - - const auto faceline_type_info{RawData::RandomMiiFaceline.at(index)}; - const auto faceline_color_info{RawData::RandomMiiFacelineColor.at( - 3 * static_cast<std::size_t>(gender) + static_cast<std::size_t>(race))}; - const auto faceline_wrinkle_info{RawData::RandomMiiFacelineWrinkle.at(index)}; - const auto faceline_makeup_info{RawData::RandomMiiFacelineMakeup.at(index)}; - const auto hair_type_info{RawData::RandomMiiHairType.at(index)}; - const auto hair_color_info{RawData::RandomMiiHairColor.at(3 * static_cast<std::size_t>(race) + - static_cast<std::size_t>(age))}; - const auto eye_type_info{RawData::RandomMiiEyeType.at(index)}; - const auto eye_color_info{RawData::RandomMiiEyeColor.at(static_cast<std::size_t>(race))}; - const auto eyebrow_type_info{RawData::RandomMiiEyebrowType.at(index)}; - const auto nose_type_info{RawData::RandomMiiNoseType.at(index)}; - const auto mouth_type_info{RawData::RandomMiiMouthType.at(index)}; - const auto glasses_type_info{RawData::RandomMiiGlassType.at(static_cast<std::size_t>(age))}; - - bf.faceline_type.Assign( - faceline_type_info.values[GetRandomValue<std::size_t>(faceline_type_info.values_count)]); - bf.faceline_color.Assign( - faceline_color_info.values[GetRandomValue<std::size_t>(faceline_color_info.values_count)]); - bf.faceline_wrinkle.Assign( - faceline_wrinkle_info - .values[GetRandomValue<std::size_t>(faceline_wrinkle_info.values_count)]); - bf.faceline_makeup.Assign( - faceline_makeup_info - .values[GetRandomValue<std::size_t>(faceline_makeup_info.values_count)]); - - bf.hair_type.Assign( - hair_type_info.values[GetRandomValue<std::size_t>(hair_type_info.values_count)]); - bf.hair_color.Assign( - HairColorLookup[hair_color_info - .values[GetRandomValue<std::size_t>(hair_color_info.values_count)]]); - bf.hair_flip.Assign(GetRandomValue<HairFlip>(HairFlip::Maximum)); - - bf.eye_type.Assign( - eye_type_info.values[GetRandomValue<std::size_t>(eye_type_info.values_count)]); - - const auto eye_rotate_1{gender != Gender::Male ? 4 : 2}; - const auto eye_rotate_2{gender != Gender::Male ? 3 : 4}; - const auto eye_rotate_offset{32 - EyeRotateLookup[eye_rotate_1] + eye_rotate_2}; - const auto eye_rotate{32 - EyeRotateLookup[bf.eye_type]}; - - bf.eye_color.Assign( - EyeColorLookup[eye_color_info - .values[GetRandomValue<std::size_t>(eye_color_info.values_count)]]); - bf.eye_scale.Assign(4); - bf.eye_aspect.Assign(3); - bf.eye_rotate.Assign(eye_rotate_offset - eye_rotate); - bf.eye_x.Assign(2); - bf.eye_y.Assign(axis_y + 12); - - bf.eyebrow_type.Assign( - eyebrow_type_info.values[GetRandomValue<std::size_t>(eyebrow_type_info.values_count)]); - - const auto eyebrow_rotate_1{race == Race::Asian ? 6 : 0}; - const auto eyebrow_y{race == Race::Asian ? 9 : 10}; - const auto eyebrow_rotate_offset{32 - EyebrowRotateLookup[eyebrow_rotate_1] + 6}; - const auto eyebrow_rotate{ - 32 - EyebrowRotateLookup[static_cast<std::size_t>(bf.eyebrow_type.Value())]}; - - bf.eyebrow_color.Assign(bf.hair_color); - bf.eyebrow_scale.Assign(4); - bf.eyebrow_aspect.Assign(3); - bf.eyebrow_rotate.Assign(eyebrow_rotate_offset - eyebrow_rotate); - bf.eyebrow_x.Assign(2); - bf.eyebrow_y.Assign(axis_y + eyebrow_y); - - const auto nose_scale{gender == Gender::Female ? 3 : 4}; - - bf.nose_type.Assign( - nose_type_info.values[GetRandomValue<std::size_t>(nose_type_info.values_count)]); - bf.nose_scale.Assign(nose_scale); - bf.nose_y.Assign(axis_y + 9); - - const auto mouth_color{gender == Gender::Female ? GetRandomValue<int>(4) : 0}; - - bf.mouth_type.Assign( - mouth_type_info.values[GetRandomValue<std::size_t>(mouth_type_info.values_count)]); - bf.mouth_color.Assign(MouthColorLookup[mouth_color]); - bf.mouth_scale.Assign(4); - bf.mouth_aspect.Assign(3); - bf.mouth_y.Assign(axis_y + 13); - - bf.beard_color.Assign(bf.hair_color); - bf.mustache_scale.Assign(4); - - if (gender == Gender::Male && age != Age::Young && GetRandomValue<int>(10) < 2) { - const auto mustache_and_beard_flag{ - GetRandomValue<BeardAndMustacheFlag>(BeardAndMustacheFlag::All)}; - - auto beard_type{BeardType::None}; - auto mustache_type{MustacheType::None}; - - if ((mustache_and_beard_flag & BeardAndMustacheFlag::Beard) == - BeardAndMustacheFlag::Beard) { - beard_type = GetRandomValue<BeardType>(BeardType::Beard1, BeardType::Beard5); - } + if (!database_manager.IsModified()) { + return ResultNotUpdated; + } - if ((mustache_and_beard_flag & BeardAndMustacheFlag::Mustache) == - BeardAndMustacheFlag::Mustache) { - mustache_type = - GetRandomValue<MustacheType>(MustacheType::Mustache1, MustacheType::Mustache5); - } + return database_manager.SaveDatabase(); +} - bf.mustache_type.Assign(mustache_type); - bf.beard_type.Assign(beard_type); - bf.mustache_y.Assign(10); - } else { - bf.mustache_type.Assign(MustacheType::None); - bf.beard_type.Assign(BeardType::None); - bf.mustache_y.Assign(axis_y + 10); - } - - const auto glasses_type_start{GetRandomValue<std::size_t>(100)}; - u8 glasses_type{}; - while (glasses_type_start < glasses_type_info.values[glasses_type]) { - if (++glasses_type >= glasses_type_info.values_count) { - ASSERT(false); - break; - } +Result MiiManager::AddOrReplace(DatabaseSessionMetadata& metadata, const StoreData& store_data) { + const auto result = database_manager.AddOrReplace(metadata, store_data); + + if (result.IsFailure()) { + return result; + } + + if (!database_manager.IsModified()) { + return ResultNotUpdated; } - bf.glasses_type.Assign(glasses_type); - bf.glasses_color.Assign(GlassesColorLookup[0]); - bf.glasses_scale.Assign(4); - bf.glasses_y.Assign(axis_y + 10); + return database_manager.SaveDatabase(); +} - bf.mole_type.Assign(0); - bf.mole_scale.Assign(4); - bf.mole_x.Assign(2); - bf.mole_y.Assign(20); +Result MiiManager::Delete(DatabaseSessionMetadata& metadata, const Common::UUID& create_id) { + const auto result = database_manager.Delete(metadata, create_id); - return {DefaultMiiName, bf, user_id}; + if (result.IsFailure()) { + return result; + } + + if (!database_manager.IsModified()) { + return ResultNotUpdated; + } + + return database_manager.SaveDatabase(); } -MiiStoreData BuildDefaultStoreData(const DefaultMii& info, const Common::UUID& user_id) { - MiiStoreBitFields bf{}; - - bf.font_region.Assign(info.font_region); - bf.favorite_color.Assign(info.favorite_color); - bf.gender.Assign(info.gender); - bf.height.Assign(info.height); - bf.build.Assign(info.weight); - bf.type.Assign(info.type); - bf.region_move.Assign(info.region); - bf.faceline_type.Assign(info.face_type); - bf.faceline_color.Assign(info.face_color); - bf.faceline_wrinkle.Assign(info.face_wrinkle); - bf.faceline_makeup.Assign(info.face_makeup); - bf.hair_type.Assign(info.hair_type); - bf.hair_color.Assign(HairColorLookup[info.hair_color]); - bf.hair_flip.Assign(static_cast<HairFlip>(info.hair_flip)); - bf.eye_type.Assign(info.eye_type); - bf.eye_color.Assign(EyeColorLookup[info.eye_color]); - bf.eye_scale.Assign(info.eye_scale); - bf.eye_aspect.Assign(info.eye_aspect); - bf.eye_rotate.Assign(info.eye_rotate); - bf.eye_x.Assign(info.eye_x); - bf.eye_y.Assign(info.eye_y); - bf.eyebrow_type.Assign(info.eyebrow_type); - bf.eyebrow_color.Assign(HairColorLookup[info.eyebrow_color]); - bf.eyebrow_scale.Assign(info.eyebrow_scale); - bf.eyebrow_aspect.Assign(info.eyebrow_aspect); - bf.eyebrow_rotate.Assign(info.eyebrow_rotate); - bf.eyebrow_x.Assign(info.eyebrow_x); - bf.eyebrow_y.Assign(info.eyebrow_y - 3); - bf.nose_type.Assign(info.nose_type); - bf.nose_scale.Assign(info.nose_scale); - bf.nose_y.Assign(info.nose_y); - bf.mouth_type.Assign(info.mouth_type); - bf.mouth_color.Assign(MouthColorLookup[info.mouth_color]); - bf.mouth_scale.Assign(info.mouth_scale); - bf.mouth_aspect.Assign(info.mouth_aspect); - bf.mouth_y.Assign(info.mouth_y); - bf.beard_color.Assign(HairColorLookup[info.beard_color]); - bf.beard_type.Assign(static_cast<BeardType>(info.beard_type)); - bf.mustache_type.Assign(static_cast<MustacheType>(info.mustache_type)); - bf.mustache_scale.Assign(info.mustache_scale); - bf.mustache_y.Assign(info.mustache_y); - bf.glasses_type.Assign(info.glasses_type); - bf.glasses_color.Assign(GlassesColorLookup[info.glasses_color]); - bf.glasses_scale.Assign(info.glasses_scale); - bf.glasses_y.Assign(info.glasses_y); - bf.mole_type.Assign(info.mole_type); - bf.mole_scale.Assign(info.mole_scale); - bf.mole_x.Assign(info.mole_x); - bf.mole_y.Assign(info.mole_y); - - return {DefaultMiiName, bf, user_id}; +s32 MiiManager::FindIndex(const Common::UUID& create_id, bool is_special) const { + s32 index{}; + const auto result = database_manager.FindIndex(index, create_id, is_special); + if (result.IsError()) { + index = -1; + } + return index; } -} // namespace +Result MiiManager::GetIndex(const DatabaseSessionMetadata& metadata, const CharInfo& char_info, + s32& out_index) const { + if (char_info.Verify() != ValidationResult::NoErrors) { + return ResultInvalidCharInfo; + } + + s32 index{}; + const bool is_special = metadata.magic == MiiMagic; + const auto result = database_manager.FindIndex(index, char_info.GetCreateId(), is_special); -MiiStoreData::MiiStoreData() = default; + if (result.IsError()) { + index = -1; + } -MiiStoreData::MiiStoreData(const MiiStoreData::Name& name, const MiiStoreBitFields& bit_fields, - const Common::UUID& user_id) { - data.name = name; - data.uuid = Common::UUID::MakeRandomRFC4122V4(); + if (index == -1) { + return ResultNotFound; + } - std::memcpy(data.data.data(), &bit_fields, sizeof(MiiStoreBitFields)); - data_crc = GenerateCrc16(data.data.data(), sizeof(data)); - device_crc = GenerateCrc16(&user_id, sizeof(Common::UUID)); + out_index = index; + return ResultSuccess; } -MiiManager::MiiManager() : user_id{Service::Account::ProfileManager().GetLastOpenedUser()} {} +Result MiiManager::Append(DatabaseSessionMetadata& metadata, const CharInfo& char_info) { + const auto result = database_manager.Append(metadata, char_info); -bool MiiManager::CheckAndResetUpdateCounter(SourceFlag source_flag, u64& current_update_counter) { - if ((source_flag & SourceFlag::Database) == SourceFlag::None) { - return false; + if (result.IsError()) { + return ResultNotFound; } - const bool result{current_update_counter != update_counter}; + if (!database_manager.IsModified()) { + return ResultNotUpdated; + } - current_update_counter = update_counter; + return database_manager.SaveDatabase(); +} - return result; +bool MiiManager::IsBrokenWithClearFlag(DatabaseSessionMetadata& metadata) { + const bool is_broken = is_broken_with_clear_flag; + if (is_broken_with_clear_flag) { + is_broken_with_clear_flag = false; + database_manager.Format(metadata); + database_manager.SaveDatabase(); + } + return is_broken; } -bool MiiManager::IsFullDatabase() const { - // TODO(bunnei): We don't implement the Mii database, so it cannot be full - return false; +Result MiiManager::DestroyFile(DatabaseSessionMetadata& metadata) { + is_broken_with_clear_flag = true; + return database_manager.DestroyFile(metadata); } -u32 MiiManager::GetCount(SourceFlag source_flag) const { - std::size_t count{}; - if ((source_flag & SourceFlag::Database) != SourceFlag::None) { - // TODO(bunnei): We don't implement the Mii database, but when we do, update this - count += 0; - } - if ((source_flag & SourceFlag::Default) != SourceFlag::None) { - count += (DefaultMiiCount - BaseMiiCount); +Result MiiManager::DeleteFile() { + return database_manager.DeleteFile(); +} + +Result MiiManager::Format(DatabaseSessionMetadata& metadata) { + database_manager.Format(metadata); + + if (!database_manager.IsModified()) { + return ResultNotUpdated; } - return static_cast<u32>(count); + return database_manager.SaveDatabase(); } -Result MiiManager::UpdateLatest(CharInfo* out_info, const CharInfo& info, SourceFlag source_flag) { - if ((source_flag & SourceFlag::Database) == SourceFlag::None) { - return ERROR_CANNOT_FIND_ENTRY; +Result MiiManager::ConvertV3ToCharInfo(CharInfo& out_char_info, const Ver3StoreData& mii_v3) const { + if (!mii_v3.IsValid()) { + return ResultInvalidCharInfo; } - // TODO(bunnei): We don't implement the Mii database, so we can't have an entry - return ERROR_CANNOT_FIND_ENTRY; -} + StoreData store_data{}; + mii_v3.BuildToStoreData(store_data); + const auto name = store_data.GetNickname(); + if (!MiiUtil::IsFontRegionValid(store_data.GetFontRegion(), name.data)) { + store_data.SetInvalidName(); + } -CharInfo MiiManager::BuildRandom(Age age, Gender gender, Race race) { - return ConvertStoreDataToInfo(BuildRandomStoreData(age, gender, race, user_id)); + out_char_info.SetFromStoreData(store_data); + return ResultSuccess; } -CharInfo MiiManager::BuildDefault(std::size_t index) { - return ConvertStoreDataToInfo(BuildDefaultStoreData(RawData::DefaultMii.at(index), user_id)); +Result MiiManager::ConvertCoreDataToCharInfo(CharInfo& out_char_info, + const CoreData& core_data) const { + if (core_data.IsValid() != ValidationResult::NoErrors) { + return ResultInvalidCharInfo; + } + + StoreData store_data{}; + store_data.BuildWithCoreData(core_data); + const auto name = store_data.GetNickname(); + if (!MiiUtil::IsFontRegionValid(store_data.GetFontRegion(), name.data)) { + store_data.SetInvalidName(); + } + + out_char_info.SetFromStoreData(store_data); + return ResultSuccess; } -CharInfo MiiManager::ConvertV3ToCharInfo(const Ver3StoreData& mii_v3) const { - Service::Mii::MiiManager manager; - auto mii = manager.BuildDefault(0); +Result MiiManager::ConvertCharInfoToCoreData(CoreData& out_core_data, + const CharInfo& char_info) const { + if (char_info.Verify() != ValidationResult::NoErrors) { + return ResultInvalidCharInfo; + } - if (!ValidateV3Info(mii_v3)) { - return mii; + out_core_data.BuildFromCharInfo(char_info); + const auto name = out_core_data.GetNickname(); + if (!MiiUtil::IsFontRegionValid(out_core_data.GetFontRegion(), name.data)) { + out_core_data.SetNickname(out_core_data.GetInvalidNickname()); } - // TODO: We are ignoring a bunch of data from the mii_v3 + return ResultSuccess; +} - mii.gender = static_cast<u8>(mii_v3.mii_information.gender); - mii.favorite_color = static_cast<u8>(mii_v3.mii_information.favorite_color); - mii.height = mii_v3.height; - mii.build = mii_v3.build; +Result MiiManager::UpdateLatest(const DatabaseSessionMetadata& metadata, CharInfo& out_char_info, + const CharInfo& char_info, SourceFlag source_flag) const { + if ((source_flag & SourceFlag::Database) == SourceFlag::None) { + return ResultNotFound; + } - // Copy name until string terminator - mii.name = {}; - for (std::size_t index = 0; index < mii.name.size() - 1; index++) { - mii.name[index] = mii_v3.mii_name[index]; - if (mii.name[index] == 0) { - break; + if (metadata.IsInterfaceVersionSupported(1)) { + if (char_info.Verify() != ValidationResult::NoErrors) { + return ResultInvalidCharInfo; } } - mii.font_region = mii_v3.region_information.character_set; + u32 index{}; + Result result = database_manager.FindIndex(metadata, index, char_info.GetCreateId()); - mii.faceline_type = mii_v3.appearance_bits1.face_shape; - mii.faceline_color = mii_v3.appearance_bits1.skin_color; - mii.faceline_wrinkle = mii_v3.appearance_bits2.wrinkles; - mii.faceline_make = mii_v3.appearance_bits2.makeup; + if (result.IsError()) { + return result; + } + + StoreData store_data{}; + database_manager.Get(store_data, index, metadata); + + if (store_data.GetType() != char_info.GetType()) { + return ResultNotFound; + } - mii.hair_type = mii_v3.hair_style; - mii.hair_color = mii_v3.appearance_bits3.hair_color; - mii.hair_flip = mii_v3.appearance_bits3.flip_hair; + out_char_info.SetFromStoreData(store_data); - mii.eye_type = static_cast<u8>(mii_v3.appearance_bits4.eye_type); - mii.eye_color = static_cast<u8>(mii_v3.appearance_bits4.eye_color); - mii.eye_scale = static_cast<u8>(mii_v3.appearance_bits4.eye_scale); - mii.eye_aspect = static_cast<u8>(mii_v3.appearance_bits4.eye_vertical_stretch); - mii.eye_rotate = static_cast<u8>(mii_v3.appearance_bits4.eye_rotation); - mii.eye_x = static_cast<u8>(mii_v3.appearance_bits4.eye_spacing); - mii.eye_y = static_cast<u8>(mii_v3.appearance_bits4.eye_y_position); + if (char_info == out_char_info) { + return ResultNotUpdated; + } - mii.eyebrow_type = static_cast<u8>(mii_v3.appearance_bits5.eyebrow_style); - mii.eyebrow_color = static_cast<u8>(mii_v3.appearance_bits5.eyebrow_color); - mii.eyebrow_scale = static_cast<u8>(mii_v3.appearance_bits5.eyebrow_scale); - mii.eyebrow_aspect = static_cast<u8>(mii_v3.appearance_bits5.eyebrow_yscale); - mii.eyebrow_rotate = static_cast<u8>(mii_v3.appearance_bits5.eyebrow_rotation); - mii.eyebrow_x = static_cast<u8>(mii_v3.appearance_bits5.eyebrow_spacing); - mii.eyebrow_y = static_cast<u8>(mii_v3.appearance_bits5.eyebrow_y_position); + return ResultSuccess; +} - mii.nose_type = static_cast<u8>(mii_v3.appearance_bits6.nose_type); - mii.nose_scale = static_cast<u8>(mii_v3.appearance_bits6.nose_scale); - mii.nose_y = static_cast<u8>(mii_v3.appearance_bits6.nose_y_position); +Result MiiManager::UpdateLatest(const DatabaseSessionMetadata& metadata, StoreData& out_store_data, + const StoreData& store_data, SourceFlag source_flag) const { + if ((source_flag & SourceFlag::Database) == SourceFlag::None) { + return ResultNotFound; + } - mii.mouth_type = static_cast<u8>(mii_v3.appearance_bits7.mouth_type); - mii.mouth_color = static_cast<u8>(mii_v3.appearance_bits7.mouth_color); - mii.mouth_scale = static_cast<u8>(mii_v3.appearance_bits7.mouth_scale); - mii.mouth_aspect = static_cast<u8>(mii_v3.appearance_bits7.mouth_horizontal_stretch); - mii.mouth_y = static_cast<u8>(mii_v3.appearance_bits8.mouth_y_position); + if (metadata.IsInterfaceVersionSupported(1)) { + if (store_data.IsValid() != ValidationResult::NoErrors) { + return ResultInvalidCharInfo; + } + } - mii.mustache_type = static_cast<u8>(mii_v3.appearance_bits8.mustache_type); - mii.mustache_scale = static_cast<u8>(mii_v3.appearance_bits9.mustache_scale); - mii.mustache_y = static_cast<u8>(mii_v3.appearance_bits9.mustache_y_position); + u32 index{}; + Result result = database_manager.FindIndex(metadata, index, store_data.GetCreateId()); - mii.beard_type = static_cast<u8>(mii_v3.appearance_bits9.bear_type); - mii.beard_color = static_cast<u8>(mii_v3.appearance_bits9.facial_hair_color); + if (result.IsError()) { + return result; + } - mii.glasses_type = static_cast<u8>(mii_v3.appearance_bits10.glasses_type); - mii.glasses_color = static_cast<u8>(mii_v3.appearance_bits10.glasses_color); - mii.glasses_scale = static_cast<u8>(mii_v3.appearance_bits10.glasses_scale); - mii.glasses_y = static_cast<u8>(mii_v3.appearance_bits10.glasses_y_position); + database_manager.Get(out_store_data, index, metadata); - mii.mole_type = static_cast<u8>(mii_v3.appearance_bits11.mole_enabled); - mii.mole_scale = static_cast<u8>(mii_v3.appearance_bits11.mole_scale); - mii.mole_x = static_cast<u8>(mii_v3.appearance_bits11.mole_x_position); - mii.mole_y = static_cast<u8>(mii_v3.appearance_bits11.mole_y_position); + if (out_store_data.GetType() != store_data.GetType()) { + return ResultNotFound; + } - // TODO: Validate mii data + if (store_data == out_store_data) { + return ResultNotUpdated; + } - return mii; + return ResultSuccess; } -Ver3StoreData MiiManager::BuildFromStoreData(const CharInfo& mii) const { - Service::Mii::MiiManager manager; - Ver3StoreData mii_v3{}; - - // TODO: We are ignoring a bunch of data from the mii_v3 +Result MiiManager::Get(const DatabaseSessionMetadata& metadata, + std::span<CharInfoElement> out_elements, u32& out_count, + SourceFlag source_flag) const { + if ((source_flag & SourceFlag::Database) == SourceFlag::None) { + return BuildDefault(out_elements, out_count, source_flag); + } - mii_v3.version = 1; - mii_v3.mii_information.gender.Assign(mii.gender); - mii_v3.mii_information.favorite_color.Assign(mii.favorite_color); - mii_v3.height = mii.height; - mii_v3.build = mii.build; + const auto mii_count = database_manager.GetCount(metadata); - // Copy name until string terminator - mii_v3.mii_name = {}; - for (std::size_t index = 0; index < mii.name.size() - 1; index++) { - mii_v3.mii_name[index] = mii.name[index]; - if (mii_v3.mii_name[index] == 0) { - break; + for (std::size_t index = 0; index < mii_count; ++index) { + if (out_elements.size() <= static_cast<std::size_t>(out_count)) { + return ResultInvalidArgumentSize; } - } - mii_v3.region_information.character_set.Assign(mii.font_region); + StoreData store_data{}; + database_manager.Get(store_data, index, metadata); - mii_v3.appearance_bits1.face_shape.Assign(mii.faceline_type); - mii_v3.appearance_bits2.wrinkles.Assign(mii.faceline_wrinkle); - mii_v3.appearance_bits2.makeup.Assign(mii.faceline_make); + out_elements[out_count].source = Source::Database; + out_elements[out_count].char_info.SetFromStoreData(store_data); + out_count++; + } - mii_v3.hair_style = mii.hair_type; - mii_v3.appearance_bits3.flip_hair.Assign(mii.hair_flip); + // Include default Mii at the end of the list + return BuildDefault(out_elements, out_count, source_flag); +} - mii_v3.appearance_bits4.eye_type.Assign(mii.eye_type); - mii_v3.appearance_bits4.eye_scale.Assign(mii.eye_scale); - mii_v3.appearance_bits4.eye_vertical_stretch.Assign(mii.eye_aspect); - mii_v3.appearance_bits4.eye_rotation.Assign(mii.eye_rotate); - mii_v3.appearance_bits4.eye_spacing.Assign(mii.eye_x); - mii_v3.appearance_bits4.eye_y_position.Assign(mii.eye_y); +Result MiiManager::Get(const DatabaseSessionMetadata& metadata, std::span<CharInfo> out_char_info, + u32& out_count, SourceFlag source_flag) const { + if ((source_flag & SourceFlag::Database) == SourceFlag::None) { + return BuildDefault(out_char_info, out_count, source_flag); + } - mii_v3.appearance_bits5.eyebrow_style.Assign(mii.eyebrow_type); - mii_v3.appearance_bits5.eyebrow_scale.Assign(mii.eyebrow_scale); - mii_v3.appearance_bits5.eyebrow_yscale.Assign(mii.eyebrow_aspect); - mii_v3.appearance_bits5.eyebrow_rotation.Assign(mii.eyebrow_rotate); - mii_v3.appearance_bits5.eyebrow_spacing.Assign(mii.eyebrow_x); - mii_v3.appearance_bits5.eyebrow_y_position.Assign(mii.eyebrow_y); + const auto mii_count = database_manager.GetCount(metadata); - mii_v3.appearance_bits6.nose_type.Assign(mii.nose_type); - mii_v3.appearance_bits6.nose_scale.Assign(mii.nose_scale); - mii_v3.appearance_bits6.nose_y_position.Assign(mii.nose_y); + for (std::size_t index = 0; index < mii_count; ++index) { + if (out_char_info.size() <= static_cast<std::size_t>(out_count)) { + return ResultInvalidArgumentSize; + } - mii_v3.appearance_bits7.mouth_type.Assign(mii.mouth_type); - mii_v3.appearance_bits7.mouth_scale.Assign(mii.mouth_scale); - mii_v3.appearance_bits7.mouth_horizontal_stretch.Assign(mii.mouth_aspect); - mii_v3.appearance_bits8.mouth_y_position.Assign(mii.mouth_y); + StoreData store_data{}; + database_manager.Get(store_data, index, metadata); - mii_v3.appearance_bits8.mustache_type.Assign(mii.mustache_type); - mii_v3.appearance_bits9.mustache_scale.Assign(mii.mustache_scale); - mii_v3.appearance_bits9.mustache_y_position.Assign(mii.mustache_y); + out_char_info[out_count].SetFromStoreData(store_data); + out_count++; + } - mii_v3.appearance_bits9.bear_type.Assign(mii.beard_type); + // Include default Mii at the end of the list + return BuildDefault(out_char_info, out_count, source_flag); +} - mii_v3.appearance_bits10.glasses_scale.Assign(mii.glasses_scale); - mii_v3.appearance_bits10.glasses_y_position.Assign(mii.glasses_y); +Result MiiManager::Get(const DatabaseSessionMetadata& metadata, + std::span<StoreDataElement> out_elements, u32& out_count, + SourceFlag source_flag) const { + if ((source_flag & SourceFlag::Database) == SourceFlag::None) { + return BuildDefault(out_elements, out_count, source_flag); + } - mii_v3.appearance_bits11.mole_enabled.Assign(mii.mole_type); - mii_v3.appearance_bits11.mole_scale.Assign(mii.mole_scale); - mii_v3.appearance_bits11.mole_x_position.Assign(mii.mole_x); - mii_v3.appearance_bits11.mole_y_position.Assign(mii.mole_y); + const auto mii_count = database_manager.GetCount(metadata); - // These types are converted to V3 from a table - mii_v3.appearance_bits1.skin_color.Assign(Ver3FacelineColorTable[mii.faceline_color]); - mii_v3.appearance_bits3.hair_color.Assign(Ver3HairColorTable[mii.hair_color]); - mii_v3.appearance_bits4.eye_color.Assign(Ver3EyeColorTable[mii.eye_color]); - mii_v3.appearance_bits5.eyebrow_color.Assign(Ver3HairColorTable[mii.eyebrow_color]); - mii_v3.appearance_bits7.mouth_color.Assign(Ver3MouthlineColorTable[mii.mouth_color]); - mii_v3.appearance_bits9.facial_hair_color.Assign(Ver3HairColorTable[mii.beard_color]); - mii_v3.appearance_bits10.glasses_color.Assign(Ver3GlassColorTable[mii.glasses_color]); - mii_v3.appearance_bits10.glasses_type.Assign(Ver3GlassTypeTable[mii.glasses_type]); + for (std::size_t index = 0; index < mii_count; ++index) { + if (out_elements.size() <= static_cast<std::size_t>(out_count)) { + return ResultInvalidArgumentSize; + } - mii_v3.crc = GenerateCrc16(&mii_v3, sizeof(Ver3StoreData) - sizeof(u16)); + StoreData store_data{}; + database_manager.Get(store_data, index, metadata); - // TODO: Validate mii_v3 data + out_elements[out_count].store_data = store_data; + out_elements[out_count].source = Source::Database; + out_count++; + } - return mii_v3; + // Include default Mii at the end of the list + return BuildDefault(out_elements, out_count, source_flag); } -NfpStoreDataExtension MiiManager::SetFromStoreData(const CharInfo& mii) const { - return { - .faceline_color = static_cast<u8>(mii.faceline_color & 0xf), - .hair_color = static_cast<u8>(mii.hair_color & 0x7f), - .eye_color = static_cast<u8>(mii.eyebrow_color & 0x7f), - .eyebrow_color = static_cast<u8>(mii.eyebrow_color & 0x7f), - .mouth_color = static_cast<u8>(mii.mouth_color & 0x7f), - .beard_color = static_cast<u8>(mii.beard_color & 0x7f), - .glass_color = static_cast<u8>(mii.glasses_color & 0x7f), - .glass_type = static_cast<u8>(mii.glasses_type & 0x1f), - }; +Result MiiManager::Get(const DatabaseSessionMetadata& metadata, std::span<StoreData> out_store_data, + u32& out_count, SourceFlag source_flag) const { + if ((source_flag & SourceFlag::Database) == SourceFlag::None) { + return BuildDefault(out_store_data, out_count, source_flag); + } + + const auto mii_count = database_manager.GetCount(metadata); + + for (std::size_t index = 0; index < mii_count; ++index) { + if (out_store_data.size() <= static_cast<std::size_t>(out_count)) { + return ResultInvalidArgumentSize; + } + + StoreData store_data{}; + database_manager.Get(store_data, index, metadata); + + out_store_data[out_count] = store_data; + out_count++; + } + + // Include default Mii at the end of the list + return BuildDefault(out_store_data, out_count, source_flag); } +Result MiiManager::BuildDefault(std::span<CharInfoElement> out_elements, u32& out_count, + SourceFlag source_flag) const { + if ((source_flag & SourceFlag::Default) == SourceFlag::None) { + return ResultSuccess; + } -bool MiiManager::ValidateV3Info(const Ver3StoreData& mii_v3) const { - bool is_valid = mii_v3.version == 0 || mii_v3.version == 3; - - is_valid = is_valid && (mii_v3.mii_name[0] != 0); - - is_valid = is_valid && (mii_v3.mii_information.birth_month < 13); - is_valid = is_valid && (mii_v3.mii_information.birth_day < 32); - is_valid = is_valid && (mii_v3.mii_information.favorite_color < 12); - is_valid = is_valid && (mii_v3.height < 128); - is_valid = is_valid && (mii_v3.build < 128); - - is_valid = is_valid && (mii_v3.appearance_bits1.face_shape < 12); - is_valid = is_valid && (mii_v3.appearance_bits1.skin_color < 7); - is_valid = is_valid && (mii_v3.appearance_bits2.wrinkles < 12); - is_valid = is_valid && (mii_v3.appearance_bits2.makeup < 12); - - is_valid = is_valid && (mii_v3.hair_style < 132); - is_valid = is_valid && (mii_v3.appearance_bits3.hair_color < 8); - - is_valid = is_valid && (mii_v3.appearance_bits4.eye_type < 60); - is_valid = is_valid && (mii_v3.appearance_bits4.eye_color < 6); - is_valid = is_valid && (mii_v3.appearance_bits4.eye_scale < 8); - is_valid = is_valid && (mii_v3.appearance_bits4.eye_vertical_stretch < 7); - is_valid = is_valid && (mii_v3.appearance_bits4.eye_rotation < 8); - is_valid = is_valid && (mii_v3.appearance_bits4.eye_spacing < 13); - is_valid = is_valid && (mii_v3.appearance_bits4.eye_y_position < 19); - - is_valid = is_valid && (mii_v3.appearance_bits5.eyebrow_style < 25); - is_valid = is_valid && (mii_v3.appearance_bits5.eyebrow_color < 8); - is_valid = is_valid && (mii_v3.appearance_bits5.eyebrow_scale < 9); - is_valid = is_valid && (mii_v3.appearance_bits5.eyebrow_yscale < 7); - is_valid = is_valid && (mii_v3.appearance_bits5.eyebrow_rotation < 12); - is_valid = is_valid && (mii_v3.appearance_bits5.eyebrow_spacing < 12); - is_valid = is_valid && (mii_v3.appearance_bits5.eyebrow_y_position < 19); - - is_valid = is_valid && (mii_v3.appearance_bits6.nose_type < 18); - is_valid = is_valid && (mii_v3.appearance_bits6.nose_scale < 9); - is_valid = is_valid && (mii_v3.appearance_bits6.nose_y_position < 19); - - is_valid = is_valid && (mii_v3.appearance_bits7.mouth_type < 36); - is_valid = is_valid && (mii_v3.appearance_bits7.mouth_color < 5); - is_valid = is_valid && (mii_v3.appearance_bits7.mouth_scale < 9); - is_valid = is_valid && (mii_v3.appearance_bits7.mouth_horizontal_stretch < 7); - is_valid = is_valid && (mii_v3.appearance_bits8.mouth_y_position < 19); - - is_valid = is_valid && (mii_v3.appearance_bits8.mustache_type < 6); - is_valid = is_valid && (mii_v3.appearance_bits9.mustache_scale < 7); - is_valid = is_valid && (mii_v3.appearance_bits9.mustache_y_position < 17); - - is_valid = is_valid && (mii_v3.appearance_bits9.bear_type < 6); - is_valid = is_valid && (mii_v3.appearance_bits9.facial_hair_color < 8); - - is_valid = is_valid && (mii_v3.appearance_bits10.glasses_type < 9); - is_valid = is_valid && (mii_v3.appearance_bits10.glasses_color < 6); - is_valid = is_valid && (mii_v3.appearance_bits10.glasses_scale < 8); - is_valid = is_valid && (mii_v3.appearance_bits10.glasses_y_position < 21); - - is_valid = is_valid && (mii_v3.appearance_bits11.mole_enabled < 2); - is_valid = is_valid && (mii_v3.appearance_bits11.mole_scale < 9); - is_valid = is_valid && (mii_v3.appearance_bits11.mole_x_position < 17); - is_valid = is_valid && (mii_v3.appearance_bits11.mole_y_position < 31); - - return is_valid; + StoreData store_data{}; + + for (std::size_t index = 0; index < DefaultMiiCount; ++index) { + if (out_elements.size() <= static_cast<std::size_t>(out_count)) { + return ResultInvalidArgumentSize; + } + + store_data.BuildDefault(static_cast<u32>(index)); + + out_elements[out_count].source = Source::Default; + out_elements[out_count].char_info.SetFromStoreData(store_data); + out_count++; + } + + return ResultSuccess; } -std::vector<MiiInfoElement> MiiManager::GetDefault(SourceFlag source_flag) { - std::vector<MiiInfoElement> result; +Result MiiManager::BuildDefault(std::span<CharInfo> out_char_info, u32& out_count, + SourceFlag source_flag) const { + if ((source_flag & SourceFlag::Default) == SourceFlag::None) { + return ResultSuccess; + } + + StoreData store_data{}; + + for (std::size_t index = 0; index < DefaultMiiCount; ++index) { + if (out_char_info.size() <= static_cast<std::size_t>(out_count)) { + return ResultInvalidArgumentSize; + } + + store_data.BuildDefault(static_cast<u32>(index)); + + out_char_info[out_count].SetFromStoreData(store_data); + out_count++; + } + + return ResultSuccess; +} +Result MiiManager::BuildDefault(std::span<StoreDataElement> out_elements, u32& out_count, + SourceFlag source_flag) const { if ((source_flag & SourceFlag::Default) == SourceFlag::None) { - return result; + return ResultSuccess; } - for (std::size_t index = BaseMiiCount; index < DefaultMiiCount; index++) { - result.emplace_back(BuildDefault(index), Source::Default); + for (std::size_t index = 0; index < DefaultMiiCount; ++index) { + if (out_elements.size() <= static_cast<std::size_t>(out_count)) { + return ResultInvalidArgumentSize; + } + + out_elements[out_count].store_data.BuildDefault(static_cast<u32>(index)); + out_elements[out_count].source = Source::Default; + out_count++; } - return result; + return ResultSuccess; } -Result MiiManager::GetIndex([[maybe_unused]] const CharInfo& info, u32& index) { - constexpr u32 INVALID_INDEX{0xFFFFFFFF}; +Result MiiManager::BuildDefault(std::span<StoreData> out_char_info, u32& out_count, + SourceFlag source_flag) const { + if ((source_flag & SourceFlag::Default) == SourceFlag::None) { + return ResultSuccess; + } + + for (std::size_t index = 0; index < DefaultMiiCount; ++index) { + if (out_char_info.size() <= static_cast<std::size_t>(out_count)) { + return ResultInvalidArgumentSize; + } - index = INVALID_INDEX; + out_char_info[out_count].BuildDefault(static_cast<u32>(index)); + out_count++; + } - // TODO(bunnei): We don't implement the Mii database, so we can't have an index - return ERROR_CANNOT_FIND_ENTRY; + return ResultSuccess; } } // namespace Service::Mii diff --git a/src/core/hle/service/mii/mii_manager.h b/src/core/hle/service/mii/mii_manager.h index 45c2be3c8..48d8e8bb7 100644 --- a/src/core/hle/service/mii/mii_manager.h +++ b/src/core/hle/service/mii/mii_manager.h @@ -3,39 +3,85 @@ #pragma once -#include <vector> +#include <span> #include "core/hle/result.h" -#include "core/hle/service/mii/types.h" +#include "core/hle/service/mii/mii_database_manager.h" +#include "core/hle/service/mii/mii_types.h" namespace Service::Mii { +class CharInfo; +class CoreData; +class StoreData; +class Ver3StoreData; -// The Mii manager is responsible for loading and storing the Miis to the database in NAND along -// with providing an easy interface for HLE emulation of the mii service. +struct CharInfoElement; +struct StoreDataElement; + +// The Mii manager is responsible for handling mii operations along with providing an easy interface +// for HLE emulation of the mii service. class MiiManager { public: MiiManager(); + Result Initialize(DatabaseSessionMetadata& metadata); + + // Auto generated mii + void BuildDefault(CharInfo& out_char_info, u32 index) const; + void BuildBase(CharInfo& out_char_info, Gender gender) const; + void BuildRandom(CharInfo& out_char_info, Age age, Gender gender, Race race) const; - bool CheckAndResetUpdateCounter(SourceFlag source_flag, u64& current_update_counter); + // Database operations bool IsFullDatabase() const; - u32 GetCount(SourceFlag source_flag) const; - Result UpdateLatest(CharInfo* out_info, const CharInfo& info, SourceFlag source_flag); - CharInfo BuildRandom(Age age, Gender gender, Race race); - CharInfo BuildDefault(std::size_t index); - CharInfo ConvertV3ToCharInfo(const Ver3StoreData& mii_v3) const; - bool ValidateV3Info(const Ver3StoreData& mii_v3) const; - std::vector<MiiInfoElement> GetDefault(SourceFlag source_flag); - Result GetIndex(const CharInfo& info, u32& index); + void SetInterfaceVersion(DatabaseSessionMetadata& metadata, u32 version) const; + bool IsUpdated(DatabaseSessionMetadata& metadata, SourceFlag source_flag) const; + u32 GetCount(const DatabaseSessionMetadata& metadata, SourceFlag source_flag) const; + Result Move(DatabaseSessionMetadata& metadata, u32 index, const Common::UUID& create_id); + Result AddOrReplace(DatabaseSessionMetadata& metadata, const StoreData& store_data); + Result Delete(DatabaseSessionMetadata& metadata, const Common::UUID& create_id); + s32 FindIndex(const Common::UUID& create_id, bool is_special) const; + Result GetIndex(const DatabaseSessionMetadata& metadata, const CharInfo& char_info, + s32& out_index) const; + Result Append(DatabaseSessionMetadata& metadata, const CharInfo& char_info); + + // Test database operations + bool IsBrokenWithClearFlag(DatabaseSessionMetadata& metadata); + Result DestroyFile(DatabaseSessionMetadata& metadata); + Result DeleteFile(); + Result Format(DatabaseSessionMetadata& metadata); - // This is nn::mii::detail::Ver::StoreDataRaw::BuildFromStoreData - Ver3StoreData BuildFromStoreData(const CharInfo& mii) const; + // Mii conversions + Result ConvertV3ToCharInfo(CharInfo& out_char_info, const Ver3StoreData& mii_v3) const; + Result ConvertCoreDataToCharInfo(CharInfo& out_char_info, const CoreData& core_data) const; + Result ConvertCharInfoToCoreData(CoreData& out_core_data, const CharInfo& char_info) const; + Result UpdateLatest(const DatabaseSessionMetadata& metadata, CharInfo& out_char_info, + const CharInfo& char_info, SourceFlag source_flag) const; + Result UpdateLatest(const DatabaseSessionMetadata& metadata, StoreData& out_store_data, + const StoreData& store_data, SourceFlag source_flag) const; - // This is nn::mii::detail::NfpStoreDataExtentionRaw::SetFromStoreData - NfpStoreDataExtension SetFromStoreData(const CharInfo& mii) const; + // Overloaded getters + Result Get(const DatabaseSessionMetadata& metadata, std::span<CharInfoElement> out_elements, + u32& out_count, SourceFlag source_flag) const; + Result Get(const DatabaseSessionMetadata& metadata, std::span<CharInfo> out_char_info, + u32& out_count, SourceFlag source_flag) const; + Result Get(const DatabaseSessionMetadata& metadata, std::span<StoreDataElement> out_elements, + u32& out_count, SourceFlag source_flag) const; + Result Get(const DatabaseSessionMetadata& metadata, std::span<StoreData> out_store_data, + u32& out_count, SourceFlag source_flag) const; private: - const Common::UUID user_id{}; - u64 update_counter{}; + Result BuildDefault(std::span<CharInfoElement> out_elements, u32& out_count, + SourceFlag source_flag) const; + Result BuildDefault(std::span<CharInfo> out_char_info, u32& out_count, + SourceFlag source_flag) const; + Result BuildDefault(std::span<StoreDataElement> out_char_info, u32& out_count, + SourceFlag source_flag) const; + Result BuildDefault(std::span<StoreData> out_char_info, u32& out_count, + SourceFlag source_flag) const; + + DatabaseManager database_manager{}; + + // This should be a global value + bool is_broken_with_clear_flag{}; }; }; // namespace Service::Mii diff --git a/src/core/hle/service/mii/mii_result.h b/src/core/hle/service/mii/mii_result.h new file mode 100644 index 000000000..e2c36e556 --- /dev/null +++ b/src/core/hle/service/mii/mii_result.h @@ -0,0 +1,27 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "core/hle/result.h" + +namespace Service::Mii { + +constexpr Result ResultInvalidArgument{ErrorModule::Mii, 1}; +constexpr Result ResultInvalidArgumentSize{ErrorModule::Mii, 2}; +constexpr Result ResultNotUpdated{ErrorModule::Mii, 3}; +constexpr Result ResultNotFound{ErrorModule::Mii, 4}; +constexpr Result ResultDatabaseFull{ErrorModule::Mii, 5}; +constexpr Result ResultInvalidCharInfo{ErrorModule::Mii, 100}; +constexpr Result ResultInvalidDatabaseChecksum{ErrorModule::Mii, 101}; +constexpr Result ResultInvalidDatabaseSignature{ErrorModule::Mii, 103}; +constexpr Result ResultInvalidDatabaseVersion{ErrorModule::Mii, 104}; +constexpr Result ResultInvalidDatabaseLength{ErrorModule::Mii, 105}; +constexpr Result ResultInvalidCharInfo2{ErrorModule::Mii, 107}; +constexpr Result ResultInvalidStoreData{ErrorModule::Mii, 109}; +constexpr Result ResultInvalidOperation{ErrorModule::Mii, 202}; +constexpr Result ResultPermissionDenied{ErrorModule::Mii, 203}; +constexpr Result ResultTestModeOnly{ErrorModule::Mii, 204}; +constexpr Result ResultInvalidCharInfoType{ErrorModule::Mii, 205}; + +}; // namespace Service::Mii diff --git a/src/core/hle/service/mii/mii_types.h b/src/core/hle/service/mii/mii_types.h new file mode 100644 index 000000000..08c6029df --- /dev/null +++ b/src/core/hle/service/mii/mii_types.h @@ -0,0 +1,692 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <array> +#include <type_traits> + +#include "common/bit_field.h" +#include "common/common_funcs.h" +#include "common/common_types.h" +#include "common/uuid.h" + +namespace Service::Mii { + +constexpr std::size_t MaxNameSize = 10; +constexpr u8 MaxHeight = 127; +constexpr u8 MaxBuild = 127; +constexpr u8 MaxType = 1; +constexpr u8 MaxRegionMove = 3; +constexpr u8 MaxEyeScale = 7; +constexpr u8 MaxEyeAspect = 6; +constexpr u8 MaxEyeRotate = 7; +constexpr u8 MaxEyeX = 12; +constexpr u8 MaxEyeY = 18; +constexpr u8 MaxEyebrowScale = 8; +constexpr u8 MaxEyebrowAspect = 6; +constexpr u8 MaxEyebrowRotate = 11; +constexpr u8 MaxEyebrowX = 12; +constexpr u8 MaxEyebrowY = 15; +constexpr u8 MaxNoseScale = 8; +constexpr u8 MaxNoseY = 18; +constexpr u8 MaxMouthScale = 8; +constexpr u8 MaxMoutAspect = 6; +constexpr u8 MaxMouthY = 18; +constexpr u8 MaxMustacheScale = 8; +constexpr u8 MaxMustacheY = 16; +constexpr u8 MaxGlassScale = 7; +constexpr u8 MaxGlassY = 20; +constexpr u8 MaxMoleScale = 8; +constexpr u8 MaxMoleX = 16; +constexpr u8 MaxMoleY = 30; +constexpr u8 MaxVer3CommonColor = 7; +constexpr u8 MaxVer3GlassType = 8; + +enum class Age : u8 { + Young, + Normal, + Old, + All, // Default + + Max = All, +}; + +enum class Gender : u8 { + Male, + Female, + All, // Default + + Max = Female, +}; + +enum class Race : u8 { + Black, + White, + Asian, + All, // Default + + Max = All, +}; + +enum class HairType : u8 { + NormalLong, // Default + NormalShort, + NormalMedium, + NormalExtraLong, + NormalLongBottom, + NormalTwoPeaks, + PartingLong, + FrontLock, + PartingShort, + PartingExtraLongCurved, + PartingExtraLong, + PartingMiddleLong, + PartingSquared, + PartingLongBottom, + PeaksTop, + PeaksSquared, + PartingPeaks, + PeaksLongBottom, + Peaks, + PeaksRounded, + PeaksSide, + PeaksMedium, + PeaksLong, + PeaksRoundedLong, + PartingFrontPeaks, + PartingLongFront, + PartingLongRounded, + PartingFrontPeaksLong, + PartingExtraLongRounded, + LongRounded, + NormalUnknown1, + NormalUnknown2, + NormalUnknown3, + NormalUnknown4, + NormalUnknown5, + NormalUnknown6, + DreadLocks, + PlatedMats, + Caps, + Afro, + PlatedMatsLong, + Beanie, + Short, + ShortTopLongSide, + ShortUnknown1, + ShortUnknown2, + MilitaryParting, + Military, + ShortUnknown3, + ShortUnknown4, + ShortUnknown5, + ShortUnknown6, + NoneTop, + None, + LongUnknown1, + LongUnknown2, + LongUnknown3, + LongUnknown4, + LongUnknown5, + LongUnknown6, + LongUnknown7, + LongUnknown8, + LongUnknown9, + LongUnknown10, + LongUnknown11, + LongUnknown12, + LongUnknown13, + LongUnknown14, + LongUnknown15, + LongUnknown16, + LongUnknown17, + LongUnknown18, + LongUnknown19, + LongUnknown20, + LongUnknown21, + LongUnknown22, + LongUnknown23, + LongUnknown24, + LongUnknown25, + LongUnknown26, + LongUnknown27, + LongUnknown28, + LongUnknown29, + LongUnknown30, + LongUnknown31, + LongUnknown32, + LongUnknown33, + LongUnknown34, + LongUnknown35, + LongUnknown36, + LongUnknown37, + LongUnknown38, + LongUnknown39, + LongUnknown40, + LongUnknown41, + LongUnknown42, + LongUnknown43, + LongUnknown44, + LongUnknown45, + LongUnknown46, + LongUnknown47, + LongUnknown48, + LongUnknown49, + LongUnknown50, + LongUnknown51, + LongUnknown52, + LongUnknown53, + LongUnknown54, + LongUnknown55, + LongUnknown56, + LongUnknown57, + LongUnknown58, + LongUnknown59, + LongUnknown60, + LongUnknown61, + LongUnknown62, + LongUnknown63, + LongUnknown64, + LongUnknown65, + LongUnknown66, + TwoMediumFrontStrandsOneLongBackPonyTail, + TwoFrontStrandsLongBackPonyTail, + PartingFrontTwoLongBackPonyTails, + TwoFrontStrandsOneLongBackPonyTail, + LongBackPonyTail, + LongFrontTwoLongBackPonyTails, + StrandsTwoShortSidedPonyTails, + TwoMediumSidedPonyTails, + ShortFrontTwoBackPonyTails, + TwoShortSidedPonyTails, + TwoLongSidedPonyTails, + LongFrontTwoBackPonyTails, + + Max = LongFrontTwoBackPonyTails, +}; + +enum class MoleType : u8 { + None, // Default + OneDot, + + Max = OneDot, +}; + +enum class HairFlip : u8 { + Left, // Default + Right, + + Max = Right, +}; + +enum class CommonColor : u8 { + // For simplicity common colors aren't listed + Max = 99, + Count = 100, +}; + +enum class FavoriteColor : u8 { + Red, // Default + Orange, + Yellow, + LimeGreen, + Green, + Blue, + LightBlue, + Pink, + Purple, + Brown, + White, + Black, + + Max = Black, +}; + +enum class EyeType : u8 { + Normal, // Default + NormalLash, + WhiteLash, + WhiteNoBottom, + OvalAngledWhite, + AngryWhite, + DotLashType1, + Line, + DotLine, + OvalWhite, + RoundedWhite, + NormalShadow, + CircleWhite, + Circle, + CircleWhiteStroke, + NormalOvalNoBottom, + NormalOvalLarge, + NormalRoundedNoBottom, + SmallLash, + Small, + TwoSmall, + NormalLongLash, + WhiteTwoLashes, + WhiteThreeLashes, + DotAngry, + DotAngled, + Oval, + SmallWhite, + WhiteAngledNoBottom, + WhiteAngledNoLeft, + SmallWhiteTwoLashes, + LeafWhiteLash, + WhiteLargeNoBottom, + Dot, + DotLashType2, + DotThreeLashes, + WhiteOvalTop, + WhiteOvalBottom, + WhiteOvalBottomFlat, + WhiteOvalTwoLashes, + WhiteOvalThreeLashes, + WhiteOvalNoBottomTwoLashes, + DotWhite, + WhiteOvalTopFlat, + WhiteThinLeaf, + StarThreeLashes, + LineTwoLashes, + CrowsFeet, + WhiteNoBottomFlat, + WhiteNoBottomRounded, + WhiteSmallBottomLine, + WhiteNoBottomLash, + WhiteNoPartialBottomLash, + WhiteOvalBottomLine, + WhiteNoBottomLashTopLine, + WhiteNoPartialBottomTwoLashes, + NormalTopLine, + WhiteOvalLash, + RoundTired, + WhiteLarge, + + Max = WhiteLarge, +}; + +enum class MouthType : u8 { + Neutral, // Default + NeutralLips, + Smile, + SmileStroke, + SmileTeeth, + LipsSmall, + LipsLarge, + Wave, + WaveAngrySmall, + NeutralStrokeLarge, + TeethSurprised, + LipsExtraLarge, + LipsUp, + NeutralDown, + Surprised, + TeethMiddle, + NeutralStroke, + LipsExtraSmall, + Malicious, + LipsDual, + NeutralComma, + NeutralUp, + TeethLarge, + WaveAngry, + LipsSexy, + SmileInverted, + LipsSexyOutline, + SmileRounded, + LipsTeeth, + NeutralOpen, + TeethRounded, + WaveAngrySmallInverted, + NeutralCommaInverted, + TeethFull, + SmileDownLine, + Kiss, + + Max = Kiss, +}; + +enum class FontRegion : u8 { + Standard, // Default + China, + Korea, + Taiwan, + + Max = Taiwan, +}; + +enum class FacelineType : u8 { + Sharp, // Default + Rounded, + SharpRounded, + SharpRoundedSmall, + Large, + LargeRounded, + SharpSmall, + Flat, + Bump, + Angular, + FlatRounded, + AngularSmall, + + Max = AngularSmall, +}; + +enum class FacelineColor : u8 { + Beige, // Default + WarmBeige, + Natural, + Honey, + Chestnut, + Porcelain, + Ivory, + WarmIvory, + Almond, + Espresso, + + Max = Espresso, + Count = Max + 1, +}; + +enum class FacelineWrinkle : u8 { + None, // Default + TearTroughs, + FacialPain, + Cheeks, + Folds, + UnderTheEyes, + SplitChin, + Chin, + BrowDroop, + MouthFrown, + CrowsFeet, + FoldsCrowsFrown, + + Max = FoldsCrowsFrown, +}; + +enum class FacelineMake : u8 { + None, // Default + CheekPorcelain, + CheekNatural, + EyeShadowBlue, + CheekBlushPorcelain, + CheekBlushNatural, + CheekPorcelainEyeShadowBlue, + CheekPorcelainEyeShadowNatural, + CheekBlushPorcelainEyeShadowEspresso, + Freckles, + LionsManeBeard, + StubbleBeard, + + Max = StubbleBeard, +}; + +enum class EyebrowType : u8 { + FlatAngledLarge, // Default + LowArchRoundedThin, + SoftAngledLarge, + MediumArchRoundedThin, + RoundedMedium, + LowArchMedium, + RoundedThin, + UpThin, + MediumArchRoundedMedium, + RoundedLarge, + UpLarge, + FlatAngledLargeInverted, + MediumArchFlat, + AngledThin, + HorizontalLarge, + HighArchFlat, + Flat, + MediumArchLarge, + LowArchThin, + RoundedThinInverted, + HighArchLarge, + Hairy, + Dotted, + None, + + Max = None, +}; + +enum class NoseType : u8 { + Normal, // Default + Rounded, + Dot, + Arrow, + Roman, + Triangle, + Button, + RoundedInverted, + Potato, + Grecian, + Snub, + Aquiline, + ArrowLeft, + RoundedLarge, + Hooked, + Fat, + Droopy, + ArrowLarge, + + Max = ArrowLarge, +}; + +enum class BeardType : u8 { + None, + Goatee, + GoateeLong, + LionsManeLong, + LionsMane, + Full, + + Min = Goatee, + Max = Full, +}; + +enum class MustacheType : u8 { + None, + Walrus, + Pencil, + Horseshoe, + Normal, + Toothbrush, + + Min = Walrus, + Max = Toothbrush, +}; + +enum class GlassType : u8 { + None, + Oval, + Wayfarer, + Rectangle, + TopRimless, + Rounded, + Oversized, + CatEye, + Square, + BottomRimless, + SemiOpaqueRounded, + SemiOpaqueCatEye, + SemiOpaqueOval, + SemiOpaqueRectangle, + SemiOpaqueAviator, + OpaqueRounded, + OpaqueCatEye, + OpaqueOval, + OpaqueRectangle, + OpaqueAviator, + + Max = OpaqueAviator, + Count = Max + 1, +}; + +enum class BeardAndMustacheFlag : u32 { + Beard = 1, + Mustache, + All = Beard | Mustache, +}; +DECLARE_ENUM_FLAG_OPERATORS(BeardAndMustacheFlag); + +enum class Source : u32 { + Database = 0, + Default = 1, + Account = 2, + Friend = 3, +}; + +enum class SourceFlag : u32 { + None = 0, + Database = 1 << 0, + Default = 1 << 1, +}; +DECLARE_ENUM_FLAG_OPERATORS(SourceFlag); + +enum class ValidationResult : u32 { + NoErrors = 0x0, + InvalidBeardColor = 0x1, + InvalidBeardType = 0x2, + InvalidBuild = 0x3, + InvalidEyeAspect = 0x4, + InvalidEyeColor = 0x5, + InvalidEyeRotate = 0x6, + InvalidEyeScale = 0x7, + InvalidEyeType = 0x8, + InvalidEyeX = 0x9, + InvalidEyeY = 0xa, + InvalidEyebrowAspect = 0xb, + InvalidEyebrowColor = 0xc, + InvalidEyebrowRotate = 0xd, + InvalidEyebrowScale = 0xe, + InvalidEyebrowType = 0xf, + InvalidEyebrowX = 0x10, + InvalidEyebrowY = 0x11, + InvalidFacelineColor = 0x12, + InvalidFacelineMake = 0x13, + InvalidFacelineWrinkle = 0x14, + InvalidFacelineType = 0x15, + InvalidColor = 0x16, + InvalidFont = 0x17, + InvalidGender = 0x18, + InvalidGlassColor = 0x19, + InvalidGlassScale = 0x1a, + InvalidGlassType = 0x1b, + InvalidGlassY = 0x1c, + InvalidHairColor = 0x1d, + InvalidHairFlip = 0x1e, + InvalidHairType = 0x1f, + InvalidHeight = 0x20, + InvalidMoleScale = 0x21, + InvalidMoleType = 0x22, + InvalidMoleX = 0x23, + InvalidMoleY = 0x24, + InvalidMouthAspect = 0x25, + InvalidMouthColor = 0x26, + InvalidMouthScale = 0x27, + InvalidMouthType = 0x28, + InvalidMouthY = 0x29, + InvalidMustacheScale = 0x2a, + InvalidMustacheType = 0x2b, + InvalidMustacheY = 0x2c, + InvalidNoseScale = 0x2e, + InvalidNoseType = 0x2f, + InvalidNoseY = 0x30, + InvalidRegionMove = 0x31, + InvalidCreateId = 0x32, + InvalidName = 0x33, + InvalidChecksum = 0x34, + InvalidType = 0x35, +}; + +struct Nickname { + std::array<char16_t, MaxNameSize> data{}; + + // Checks for null or dirty strings + bool IsValid() const { + if (data[0] == 0) { + return false; + } + + std::size_t index = 1; + while (index < MaxNameSize && data[index] != 0) { + index++; + } + while (index < MaxNameSize && data[index] == 0) { + index++; + } + return index == MaxNameSize; + } +}; +static_assert(sizeof(Nickname) == 0x14, "Nickname is an invalid size"); + +struct DefaultMii { + u32 face_type{}; + u32 face_color{}; + u32 face_wrinkle{}; + u32 face_makeup{}; + u32 hair_type{}; + u32 hair_color{}; + u32 hair_flip{}; + u32 eye_type{}; + u32 eye_color{}; + u32 eye_scale{}; + u32 eye_aspect{}; + u32 eye_rotate{}; + u32 eye_x{}; + u32 eye_y{}; + u32 eyebrow_type{}; + u32 eyebrow_color{}; + u32 eyebrow_scale{}; + u32 eyebrow_aspect{}; + u32 eyebrow_rotate{}; + u32 eyebrow_x{}; + u32 eyebrow_y{}; + u32 nose_type{}; + u32 nose_scale{}; + u32 nose_y{}; + u32 mouth_type{}; + u32 mouth_color{}; + u32 mouth_scale{}; + u32 mouth_aspect{}; + u32 mouth_y{}; + u32 mustache_type{}; + u32 beard_type{}; + u32 beard_color{}; + u32 mustache_scale{}; + u32 mustache_y{}; + u32 glasses_type{}; + u32 glasses_color{}; + u32 glasses_scale{}; + u32 glasses_y{}; + u32 mole_type{}; + u32 mole_scale{}; + u32 mole_x{}; + u32 mole_y{}; + u32 height{}; + u32 weight{}; + u32 gender{}; + u32 favorite_color{}; + u32 region_move{}; + u32 font_region{}; + u32 type{}; + Nickname nickname; +}; +static_assert(sizeof(DefaultMii) == 0xd8, "DefaultMii has incorrect size."); + +struct DatabaseSessionMetadata { + u32 interface_version; + u32 magic; + u64 update_counter; + + bool IsInterfaceVersionSupported(u32 version) const { + return version <= interface_version; + } +}; + +} // namespace Service::Mii diff --git a/src/core/hle/service/mii/mii_util.h b/src/core/hle/service/mii/mii_util.h new file mode 100644 index 000000000..3534fa31d --- /dev/null +++ b/src/core/hle/service/mii/mii_util.h @@ -0,0 +1,85 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <random> +#include <span> + +#include "common/common_types.h" +#include "common/swap.h" +#include "common/uuid.h" +#include "core/hle/service/mii/mii_types.h" + +namespace Service::Mii { +class MiiUtil { +public: + static u16 CalculateCrc16(const void* data, std::size_t size) { + s32 crc{}; + for (std::size_t i = 0; i < size; i++) { + crc ^= static_cast<const u8*>(data)[i] << 8; + for (std::size_t j = 0; j < 8; j++) { + crc <<= 1; + if ((crc & 0x10000) != 0) { + crc = (crc ^ 0x1021) & 0xFFFF; + } + } + } + return Common::swap16(static_cast<u16>(crc)); + } + + static u16 CalculateDeviceCrc16(const Common::UUID& uuid, std::size_t data_size) { + constexpr u16 magic{0x1021}; + s32 crc{}; + + for (std::size_t i = 0; i < uuid.uuid.size(); i++) { + for (std::size_t j = 0; j < 8; j++) { + crc <<= 1; + if ((crc & 0x10000) != 0) { + crc = crc ^ magic; + } + } + crc ^= uuid.uuid[i]; + } + + // As much as this looks wrong this is what N's does + + for (std::size_t i = 0; i < data_size * 8; i++) { + crc <<= 1; + if ((crc & 0x10000) != 0) { + crc = crc ^ magic; + } + } + + return Common::swap16(static_cast<u16>(crc)); + } + + static Common::UUID MakeCreateId() { + return Common::UUID::MakeRandomRFC4122V4(); + } + + static Common::UUID GetDeviceId() { + // This should be nn::settings::detail::GetMiiAuthorId() + return Common::UUID::MakeDefault(); + } + + template <typename T> + static T GetRandomValue(T min, T max) { + std::random_device device; + std::mt19937 gen(device()); + std::uniform_int_distribution<u64> distribution(static_cast<u64>(min), + static_cast<u64>(max)); + return static_cast<T>(distribution(gen)); + } + + template <typename T> + static T GetRandomValue(T max) { + return GetRandomValue<T>({}, max); + } + + static bool IsFontRegionValid(FontRegion font, std::span<const char16_t> text) { + // TODO: This function needs to check against the font tables + return true; + } +}; +} // namespace Service::Mii diff --git a/src/core/hle/service/mii/raw_data.h b/src/core/hle/service/mii/raw_data.h deleted file mode 100644 index c2bec68d4..000000000 --- a/src/core/hle/service/mii/raw_data.h +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include <array> - -#include "core/hle/service/mii/types.h" - -namespace Service::Mii::RawData { - -extern const std::array<Service::Mii::DefaultMii, 8> DefaultMii; -extern const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiFaceline; -extern const std::array<Service::Mii::RandomMiiData3, 6> RandomMiiFacelineColor; -extern const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiFacelineWrinkle; -extern const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiFacelineMakeup; -extern const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiHairType; -extern const std::array<Service::Mii::RandomMiiData3, 9> RandomMiiHairColor; -extern const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiEyeType; -extern const std::array<Service::Mii::RandomMiiData2, 3> RandomMiiEyeColor; -extern const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiEyebrowType; -extern const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiNoseType; -extern const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiMouthType; -extern const std::array<Service::Mii::RandomMiiData2, 3> RandomMiiGlassType; - -} // namespace Service::Mii::RawData diff --git a/src/core/hle/service/mii/types.h b/src/core/hle/service/mii/types.h deleted file mode 100644 index c48d08d79..000000000 --- a/src/core/hle/service/mii/types.h +++ /dev/null @@ -1,553 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include <array> -#include <type_traits> - -#include "common/bit_field.h" -#include "common/common_funcs.h" -#include "common/common_types.h" -#include "common/uuid.h" - -namespace Service::Mii { - -enum class Age : u32 { - Young, - Normal, - Old, - All, -}; - -enum class BeardType : u32 { - None, - Beard1, - Beard2, - Beard3, - Beard4, - Beard5, -}; - -enum class BeardAndMustacheFlag : u32 { - Beard = 1, - Mustache, - All = Beard | Mustache, -}; -DECLARE_ENUM_FLAG_OPERATORS(BeardAndMustacheFlag); - -enum class FontRegion : u32 { - Standard, - China, - Korea, - Taiwan, -}; - -enum class Gender : u32 { - Male, - Female, - All, - Maximum = Female, -}; - -enum class HairFlip : u32 { - Left, - Right, - Maximum = Right, -}; - -enum class MustacheType : u32 { - None, - Mustache1, - Mustache2, - Mustache3, - Mustache4, - Mustache5, -}; - -enum class Race : u32 { - Black, - White, - Asian, - All, -}; - -enum class Source : u32 { - Database = 0, - Default = 1, - Account = 2, - Friend = 3, -}; - -enum class SourceFlag : u32 { - None = 0, - Database = 1 << 0, - Default = 1 << 1, -}; -DECLARE_ENUM_FLAG_OPERATORS(SourceFlag); - -// nn::mii::CharInfo -struct CharInfo { - Common::UUID uuid; - std::array<char16_t, 11> name; - u8 font_region; - u8 favorite_color; - u8 gender; - u8 height; - u8 build; - u8 type; - u8 region_move; - u8 faceline_type; - u8 faceline_color; - u8 faceline_wrinkle; - u8 faceline_make; - u8 hair_type; - u8 hair_color; - u8 hair_flip; - u8 eye_type; - u8 eye_color; - u8 eye_scale; - u8 eye_aspect; - u8 eye_rotate; - u8 eye_x; - u8 eye_y; - u8 eyebrow_type; - u8 eyebrow_color; - u8 eyebrow_scale; - u8 eyebrow_aspect; - u8 eyebrow_rotate; - u8 eyebrow_x; - u8 eyebrow_y; - u8 nose_type; - u8 nose_scale; - u8 nose_y; - u8 mouth_type; - u8 mouth_color; - u8 mouth_scale; - u8 mouth_aspect; - u8 mouth_y; - u8 beard_color; - u8 beard_type; - u8 mustache_type; - u8 mustache_scale; - u8 mustache_y; - u8 glasses_type; - u8 glasses_color; - u8 glasses_scale; - u8 glasses_y; - u8 mole_type; - u8 mole_scale; - u8 mole_x; - u8 mole_y; - u8 padding; -}; -static_assert(sizeof(CharInfo) == 0x58, "CharInfo has incorrect size."); -static_assert(std::has_unique_object_representations_v<CharInfo>, - "All bits of CharInfo must contribute to its value."); - -#pragma pack(push, 4) - -struct MiiInfoElement { - MiiInfoElement(const CharInfo& info_, Source source_) : info{info_}, source{source_} {} - - CharInfo info{}; - Source source{}; -}; -static_assert(sizeof(MiiInfoElement) == 0x5c, "MiiInfoElement has incorrect size."); - -struct MiiStoreBitFields { - union { - u32 word_0{}; - - BitField<0, 8, u32> hair_type; - BitField<8, 7, u32> height; - BitField<15, 1, u32> mole_type; - BitField<16, 7, u32> build; - BitField<23, 1, HairFlip> hair_flip; - BitField<24, 7, u32> hair_color; - BitField<31, 1, u32> type; - }; - - union { - u32 word_1{}; - - BitField<0, 7, u32> eye_color; - BitField<7, 1, Gender> gender; - BitField<8, 7, u32> eyebrow_color; - BitField<16, 7, u32> mouth_color; - BitField<24, 7, u32> beard_color; - }; - - union { - u32 word_2{}; - - BitField<0, 7, u32> glasses_color; - BitField<8, 6, u32> eye_type; - BitField<14, 2, u32> region_move; - BitField<16, 6, u32> mouth_type; - BitField<22, 2, FontRegion> font_region; - BitField<24, 5, u32> eye_y; - BitField<29, 3, u32> glasses_scale; - }; - - union { - u32 word_3{}; - - BitField<0, 5, u32> eyebrow_type; - BitField<5, 3, MustacheType> mustache_type; - BitField<8, 5, u32> nose_type; - BitField<13, 3, BeardType> beard_type; - BitField<16, 5, u32> nose_y; - BitField<21, 3, u32> mouth_aspect; - BitField<24, 5, u32> mouth_y; - BitField<29, 3, u32> eyebrow_aspect; - }; - - union { - u32 word_4{}; - - BitField<0, 5, u32> mustache_y; - BitField<5, 3, u32> eye_rotate; - BitField<8, 5, u32> glasses_y; - BitField<13, 3, u32> eye_aspect; - BitField<16, 5, u32> mole_x; - BitField<21, 3, u32> eye_scale; - BitField<24, 5, u32> mole_y; - }; - - union { - u32 word_5{}; - - BitField<0, 5, u32> glasses_type; - BitField<8, 4, u32> favorite_color; - BitField<12, 4, u32> faceline_type; - BitField<16, 4, u32> faceline_color; - BitField<20, 4, u32> faceline_wrinkle; - BitField<24, 4, u32> faceline_makeup; - BitField<28, 4, u32> eye_x; - }; - - union { - u32 word_6{}; - - BitField<0, 4, u32> eyebrow_scale; - BitField<4, 4, u32> eyebrow_rotate; - BitField<8, 4, u32> eyebrow_x; - BitField<12, 4, u32> eyebrow_y; - BitField<16, 4, u32> nose_scale; - BitField<20, 4, u32> mouth_scale; - BitField<24, 4, u32> mustache_scale; - BitField<28, 4, u32> mole_scale; - }; -}; -static_assert(sizeof(MiiStoreBitFields) == 0x1c, "MiiStoreBitFields has incorrect size."); -static_assert(std::is_trivially_copyable_v<MiiStoreBitFields>, - "MiiStoreBitFields is not trivially copyable."); - -// This is nn::mii::Ver3StoreData -// Based on citra HLE::Applets::MiiData and PretendoNetwork. -// https://github.com/citra-emu/citra/blob/master/src/core/hle/applets/mii_selector.h#L48 -// https://github.com/PretendoNetwork/mii-js/blob/master/mii.js#L299 -struct Ver3StoreData { - u8 version; - union { - u8 raw; - - BitField<0, 1, u8> allow_copying; - BitField<1, 1, u8> profanity_flag; - BitField<2, 2, u8> region_lock; - BitField<4, 2, u8> character_set; - } region_information; - u16_be mii_id; - u64_be system_id; - u32_be specialness_and_creation_date; - std::array<u8, 0x6> creator_mac; - u16_be padding; - union { - u16 raw; - - BitField<0, 1, u16> gender; - BitField<1, 4, u16> birth_month; - BitField<5, 5, u16> birth_day; - BitField<10, 4, u16> favorite_color; - BitField<14, 1, u16> favorite; - } mii_information; - std::array<char16_t, 0xA> mii_name; - u8 height; - u8 build; - union { - u8 raw; - - BitField<0, 1, u8> disable_sharing; - BitField<1, 4, u8> face_shape; - BitField<5, 3, u8> skin_color; - } appearance_bits1; - union { - u8 raw; - - BitField<0, 4, u8> wrinkles; - BitField<4, 4, u8> makeup; - } appearance_bits2; - u8 hair_style; - union { - u8 raw; - - BitField<0, 3, u8> hair_color; - BitField<3, 1, u8> flip_hair; - } appearance_bits3; - union { - u32 raw; - - BitField<0, 6, u32> eye_type; - BitField<6, 3, u32> eye_color; - BitField<9, 4, u32> eye_scale; - BitField<13, 3, u32> eye_vertical_stretch; - BitField<16, 5, u32> eye_rotation; - BitField<21, 4, u32> eye_spacing; - BitField<25, 5, u32> eye_y_position; - } appearance_bits4; - union { - u32 raw; - - BitField<0, 5, u32> eyebrow_style; - BitField<5, 3, u32> eyebrow_color; - BitField<8, 4, u32> eyebrow_scale; - BitField<12, 3, u32> eyebrow_yscale; - BitField<16, 4, u32> eyebrow_rotation; - BitField<21, 4, u32> eyebrow_spacing; - BitField<25, 5, u32> eyebrow_y_position; - } appearance_bits5; - union { - u16 raw; - - BitField<0, 5, u16> nose_type; - BitField<5, 4, u16> nose_scale; - BitField<9, 5, u16> nose_y_position; - } appearance_bits6; - union { - u16 raw; - - BitField<0, 6, u16> mouth_type; - BitField<6, 3, u16> mouth_color; - BitField<9, 4, u16> mouth_scale; - BitField<13, 3, u16> mouth_horizontal_stretch; - } appearance_bits7; - union { - u8 raw; - - BitField<0, 5, u8> mouth_y_position; - BitField<5, 3, u8> mustache_type; - } appearance_bits8; - u8 allow_copying; - union { - u16 raw; - - BitField<0, 3, u16> bear_type; - BitField<3, 3, u16> facial_hair_color; - BitField<6, 4, u16> mustache_scale; - BitField<10, 5, u16> mustache_y_position; - } appearance_bits9; - union { - u16 raw; - - BitField<0, 4, u16> glasses_type; - BitField<4, 3, u16> glasses_color; - BitField<7, 4, u16> glasses_scale; - BitField<11, 5, u16> glasses_y_position; - } appearance_bits10; - union { - u16 raw; - - BitField<0, 1, u16> mole_enabled; - BitField<1, 4, u16> mole_scale; - BitField<5, 5, u16> mole_x_position; - BitField<10, 5, u16> mole_y_position; - } appearance_bits11; - - std::array<u16_le, 0xA> author_name; - INSERT_PADDING_BYTES(0x2); - u16_be crc; -}; -static_assert(sizeof(Ver3StoreData) == 0x60, "Ver3StoreData is an invalid size"); - -struct NfpStoreDataExtension { - u8 faceline_color; - u8 hair_color; - u8 eye_color; - u8 eyebrow_color; - u8 mouth_color; - u8 beard_color; - u8 glass_color; - u8 glass_type; -}; -static_assert(sizeof(NfpStoreDataExtension) == 0x8, "NfpStoreDataExtension is an invalid size"); - -constexpr std::array<u8, 0x10> Ver3FacelineColorTable{ - 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x0, 0x1, 0x5, 0x5, -}; - -constexpr std::array<u8, 100> Ver3HairColorTable{ - 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x0, 0x4, 0x3, 0x5, 0x4, 0x4, 0x6, 0x2, 0x0, - 0x6, 0x4, 0x3, 0x2, 0x2, 0x7, 0x3, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, - 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x0, 0x0, 0x4, - 0x4, 0x4, 0x4, 0x4, 0x4, 0x0, 0x0, 0x0, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x5, 0x5, 0x5, - 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x5, 0x7, 0x5, 0x7, 0x7, 0x7, 0x7, 0x7, 0x6, 0x7, - 0x7, 0x7, 0x7, 0x7, 0x3, 0x7, 0x7, 0x7, 0x7, 0x7, 0x0, 0x4, 0x4, 0x4, 0x4, -}; - -constexpr std::array<u8, 100> Ver3EyeColorTable{ - 0x0, 0x2, 0x2, 0x2, 0x1, 0x3, 0x2, 0x3, 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x2, 0x2, 0x4, - 0x2, 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, - 0x2, 0x2, 0x2, 0x2, 0x0, 0x0, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x1, 0x0, 0x4, 0x4, - 0x4, 0x4, 0x4, 0x4, 0x4, 0x0, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, - 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x2, 0x2, - 0x3, 0x3, 0x3, 0x3, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, -}; - -constexpr std::array<u8, 100> Ver3MouthlineColorTable{ - 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x3, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x1, 0x4, - 0x4, 0x4, 0x0, 0x1, 0x2, 0x3, 0x4, 0x4, 0x2, 0x3, 0x3, 0x4, 0x4, 0x4, 0x4, 0x1, 0x4, - 0x4, 0x2, 0x3, 0x3, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x3, 0x3, 0x3, 0x4, 0x4, 0x4, - 0x3, 0x3, 0x3, 0x3, 0x3, 0x4, 0x4, 0x4, 0x4, 0x4, 0x3, 0x3, 0x3, 0x3, 0x4, 0x4, 0x4, - 0x4, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x4, 0x4, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x4, 0x3, - 0x3, 0x3, 0x3, 0x3, 0x4, 0x0, 0x3, 0x3, 0x3, 0x3, 0x4, 0x3, 0x3, 0x3, 0x3, -}; - -constexpr std::array<u8, 100> Ver3GlassColorTable{ - 0x0, 0x1, 0x1, 0x1, 0x5, 0x1, 0x1, 0x4, 0x0, 0x5, 0x1, 0x1, 0x3, 0x5, 0x1, 0x2, 0x3, - 0x4, 0x5, 0x4, 0x2, 0x2, 0x4, 0x4, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, - 0x2, 0x2, 0x2, 0x2, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, - 0x3, 0x3, 0x3, 0x3, 0x3, 0x0, 0x0, 0x0, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x0, 0x5, 0x5, - 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x1, 0x4, - 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, -}; - -constexpr std::array<u8, 20> Ver3GlassTypeTable{ - 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x1, - 0x2, 0x1, 0x3, 0x7, 0x7, 0x6, 0x7, 0x8, 0x7, 0x7, -}; - -struct MiiStoreData { - using Name = std::array<char16_t, 10>; - - MiiStoreData(); - MiiStoreData(const Name& name, const MiiStoreBitFields& bit_fields, - const Common::UUID& user_id); - - // This corresponds to the above structure MiiStoreBitFields. I did it like this because the - // BitField<> type makes this (and any thing that contains it) not trivially copyable, which is - // not suitable for our uses. - struct { - std::array<u8, 0x1C> data{}; - static_assert(sizeof(MiiStoreBitFields) == sizeof(data), "data field has incorrect size."); - - Name name{}; - Common::UUID uuid{}; - } data; - - u16 data_crc{}; - u16 device_crc{}; -}; -static_assert(sizeof(MiiStoreData) == 0x44, "MiiStoreData has incorrect size."); - -struct MiiStoreDataElement { - MiiStoreData data{}; - Source source{}; -}; -static_assert(sizeof(MiiStoreDataElement) == 0x48, "MiiStoreDataElement has incorrect size."); - -struct MiiDatabase { - u32 magic{}; // 'NFDB' - std::array<MiiStoreData, 0x64> miis{}; - INSERT_PADDING_BYTES(1); - u8 count{}; - u16 crc{}; -}; -static_assert(sizeof(MiiDatabase) == 0x1A98, "MiiDatabase has incorrect size."); - -struct RandomMiiValues { - std::array<u8, 0xbc> values{}; -}; -static_assert(sizeof(RandomMiiValues) == 0xbc, "RandomMiiValues has incorrect size."); - -struct RandomMiiData4 { - Gender gender{}; - Age age{}; - Race race{}; - u32 values_count{}; - std::array<u32, 47> values{}; -}; -static_assert(sizeof(RandomMiiData4) == 0xcc, "RandomMiiData4 has incorrect size."); - -struct RandomMiiData3 { - u32 arg_1; - u32 arg_2; - u32 values_count; - std::array<u32, 47> values{}; -}; -static_assert(sizeof(RandomMiiData3) == 0xc8, "RandomMiiData3 has incorrect size."); - -struct RandomMiiData2 { - u32 arg_1; - u32 values_count; - std::array<u32, 47> values{}; -}; -static_assert(sizeof(RandomMiiData2) == 0xc4, "RandomMiiData2 has incorrect size."); - -struct DefaultMii { - u32 face_type{}; - u32 face_color{}; - u32 face_wrinkle{}; - u32 face_makeup{}; - u32 hair_type{}; - u32 hair_color{}; - u32 hair_flip{}; - u32 eye_type{}; - u32 eye_color{}; - u32 eye_scale{}; - u32 eye_aspect{}; - u32 eye_rotate{}; - u32 eye_x{}; - u32 eye_y{}; - u32 eyebrow_type{}; - u32 eyebrow_color{}; - u32 eyebrow_scale{}; - u32 eyebrow_aspect{}; - u32 eyebrow_rotate{}; - u32 eyebrow_x{}; - u32 eyebrow_y{}; - u32 nose_type{}; - u32 nose_scale{}; - u32 nose_y{}; - u32 mouth_type{}; - u32 mouth_color{}; - u32 mouth_scale{}; - u32 mouth_aspect{}; - u32 mouth_y{}; - u32 mustache_type{}; - u32 beard_type{}; - u32 beard_color{}; - u32 mustache_scale{}; - u32 mustache_y{}; - u32 glasses_type{}; - u32 glasses_color{}; - u32 glasses_scale{}; - u32 glasses_y{}; - u32 mole_type{}; - u32 mole_scale{}; - u32 mole_x{}; - u32 mole_y{}; - u32 height{}; - u32 weight{}; - Gender gender{}; - u32 favorite_color{}; - u32 region{}; - FontRegion font_region{}; - u32 type{}; - INSERT_PADDING_WORDS(5); -}; -static_assert(sizeof(DefaultMii) == 0xd8, "MiiStoreData has incorrect size."); - -#pragma pack(pop) - -} // namespace Service::Mii diff --git a/src/core/hle/service/mii/types/char_info.cpp b/src/core/hle/service/mii/types/char_info.cpp new file mode 100644 index 000000000..e90124af4 --- /dev/null +++ b/src/core/hle/service/mii/types/char_info.cpp @@ -0,0 +1,482 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "core/hle/service/mii/types/char_info.h" +#include "core/hle/service/mii/types/store_data.h" + +namespace Service::Mii { + +void CharInfo::SetFromStoreData(const StoreData& store_data) { + name = store_data.GetNickname(); + null_terminator = '\0'; + create_id = store_data.GetCreateId(); + font_region = store_data.GetFontRegion(); + favorite_color = store_data.GetFavoriteColor(); + gender = store_data.GetGender(); + height = store_data.GetHeight(); + build = store_data.GetBuild(); + type = store_data.GetType(); + region_move = store_data.GetRegionMove(); + faceline_type = store_data.GetFacelineType(); + faceline_color = store_data.GetFacelineColor(); + faceline_wrinkle = store_data.GetFacelineWrinkle(); + faceline_make = store_data.GetFacelineMake(); + hair_type = store_data.GetHairType(); + hair_color = store_data.GetHairColor(); + hair_flip = store_data.GetHairFlip(); + eye_type = store_data.GetEyeType(); + eye_color = store_data.GetEyeColor(); + eye_scale = store_data.GetEyeScale(); + eye_aspect = store_data.GetEyeAspect(); + eye_rotate = store_data.GetEyeRotate(); + eye_x = store_data.GetEyeX(); + eye_y = store_data.GetEyeY(); + eyebrow_type = store_data.GetEyebrowType(); + eyebrow_color = store_data.GetEyebrowColor(); + eyebrow_scale = store_data.GetEyebrowScale(); + eyebrow_aspect = store_data.GetEyebrowAspect(); + eyebrow_rotate = store_data.GetEyebrowRotate(); + eyebrow_x = store_data.GetEyebrowX(); + eyebrow_y = store_data.GetEyebrowY() + 3; + nose_type = store_data.GetNoseType(); + nose_scale = store_data.GetNoseScale(); + nose_y = store_data.GetNoseY(); + mouth_type = store_data.GetMouthType(); + mouth_color = store_data.GetMouthColor(); + mouth_scale = store_data.GetMouthScale(); + mouth_aspect = store_data.GetMouthAspect(); + mouth_y = store_data.GetMouthY(); + beard_color = store_data.GetBeardColor(); + beard_type = store_data.GetBeardType(); + mustache_type = store_data.GetMustacheType(); + mustache_scale = store_data.GetMustacheScale(); + mustache_y = store_data.GetMustacheY(); + glass_type = store_data.GetGlassType(); + glass_color = store_data.GetGlassColor(); + glass_scale = store_data.GetGlassScale(); + glass_y = store_data.GetGlassY(); + mole_type = store_data.GetMoleType(); + mole_scale = store_data.GetMoleScale(); + mole_x = store_data.GetMoleX(); + mole_y = store_data.GetMoleY(); + padding = '\0'; +} + +ValidationResult CharInfo::Verify() const { + if (!create_id.IsValid()) { + return ValidationResult::InvalidCreateId; + } + if (!name.IsValid()) { + return ValidationResult::InvalidName; + } + if (font_region > FontRegion::Max) { + return ValidationResult::InvalidFont; + } + if (favorite_color > FavoriteColor::Max) { + return ValidationResult::InvalidColor; + } + if (gender > Gender::Max) { + return ValidationResult::InvalidGender; + } + if (height > MaxHeight) { + return ValidationResult::InvalidHeight; + } + if (build > MaxBuild) { + return ValidationResult::InvalidBuild; + } + if (type > MaxType) { + return ValidationResult::InvalidType; + } + if (region_move > MaxRegionMove) { + return ValidationResult::InvalidRegionMove; + } + if (faceline_type > FacelineType::Max) { + return ValidationResult::InvalidFacelineType; + } + if (faceline_color > FacelineColor::Max) { + return ValidationResult::InvalidFacelineColor; + } + if (faceline_wrinkle > FacelineWrinkle::Max) { + return ValidationResult::InvalidFacelineWrinkle; + } + if (faceline_make > FacelineMake::Max) { + return ValidationResult::InvalidFacelineMake; + } + if (hair_type > HairType::Max) { + return ValidationResult::InvalidHairType; + } + if (hair_color > CommonColor::Max) { + return ValidationResult::InvalidHairColor; + } + if (hair_flip > HairFlip::Max) { + return ValidationResult::InvalidHairFlip; + } + if (eye_type > EyeType::Max) { + return ValidationResult::InvalidEyeType; + } + if (eye_color > CommonColor::Max) { + return ValidationResult::InvalidEyeColor; + } + if (eye_scale > MaxEyeScale) { + return ValidationResult::InvalidEyeScale; + } + if (eye_aspect > MaxEyeAspect) { + return ValidationResult::InvalidEyeAspect; + } + if (eye_rotate > MaxEyeX) { + return ValidationResult::InvalidEyeRotate; + } + if (eye_x > MaxEyeX) { + return ValidationResult::InvalidEyeX; + } + if (eye_y > MaxEyeY) { + return ValidationResult::InvalidEyeY; + } + if (eyebrow_type > EyebrowType::Max) { + return ValidationResult::InvalidEyebrowType; + } + if (eyebrow_color > CommonColor::Max) { + return ValidationResult::InvalidEyebrowColor; + } + if (eyebrow_scale > MaxEyebrowScale) { + return ValidationResult::InvalidEyebrowScale; + } + if (eyebrow_aspect > MaxEyebrowAspect) { + return ValidationResult::InvalidEyebrowAspect; + } + if (eyebrow_rotate > MaxEyebrowRotate) { + return ValidationResult::InvalidEyebrowRotate; + } + if (eyebrow_x > MaxEyebrowX) { + return ValidationResult::InvalidEyebrowX; + } + if (eyebrow_y - 3 > MaxEyebrowY) { + return ValidationResult::InvalidEyebrowY; + } + if (nose_type > NoseType::Max) { + return ValidationResult::InvalidNoseType; + } + if (nose_scale > MaxNoseScale) { + return ValidationResult::InvalidNoseScale; + } + if (nose_y > MaxNoseY) { + return ValidationResult::InvalidNoseY; + } + if (mouth_type > MouthType::Max) { + return ValidationResult::InvalidMouthType; + } + if (mouth_color > CommonColor::Max) { + return ValidationResult::InvalidMouthColor; + } + if (mouth_scale > MaxMouthScale) { + return ValidationResult::InvalidMouthScale; + } + if (mouth_aspect > MaxMoutAspect) { + return ValidationResult::InvalidMouthAspect; + } + if (mouth_y > MaxMouthY) { + return ValidationResult::InvalidMoleY; + } + if (beard_color > CommonColor::Max) { + return ValidationResult::InvalidBeardColor; + } + if (beard_type > BeardType::Max) { + return ValidationResult::InvalidBeardType; + } + if (mustache_type > MustacheType::Max) { + return ValidationResult::InvalidMustacheType; + } + if (mustache_scale > MaxMustacheScale) { + return ValidationResult::InvalidMustacheScale; + } + if (mustache_y > MaxMustacheY) { + return ValidationResult::InvalidMustacheY; + } + if (glass_type > GlassType::Max) { + return ValidationResult::InvalidGlassType; + } + if (glass_color > CommonColor::Max) { + return ValidationResult::InvalidGlassColor; + } + if (glass_scale > MaxGlassScale) { + return ValidationResult::InvalidGlassScale; + } + if (glass_y > MaxGlassY) { + return ValidationResult::InvalidGlassY; + } + if (mole_type > MoleType::Max) { + return ValidationResult::InvalidMoleType; + } + if (mole_scale > MaxMoleScale) { + return ValidationResult::InvalidMoleScale; + } + if (mole_x > MaxMoleX) { + return ValidationResult::InvalidMoleX; + } + if (mole_y > MaxMoleY) { + return ValidationResult::InvalidMoleY; + } + return ValidationResult::NoErrors; +} + +Common::UUID CharInfo::GetCreateId() const { + return create_id; +} + +Nickname CharInfo::GetNickname() const { + return name; +} + +FontRegion CharInfo::GetFontRegion() const { + return font_region; +} + +FavoriteColor CharInfo::GetFavoriteColor() const { + return favorite_color; +} + +Gender CharInfo::GetGender() const { + return gender; +} + +u8 CharInfo::GetHeight() const { + return height; +} + +u8 CharInfo::GetBuild() const { + return build; +} + +u8 CharInfo::GetType() const { + return type; +} + +u8 CharInfo::GetRegionMove() const { + return region_move; +} + +FacelineType CharInfo::GetFacelineType() const { + return faceline_type; +} + +FacelineColor CharInfo::GetFacelineColor() const { + return faceline_color; +} + +FacelineWrinkle CharInfo::GetFacelineWrinkle() const { + return faceline_wrinkle; +} + +FacelineMake CharInfo::GetFacelineMake() const { + return faceline_make; +} + +HairType CharInfo::GetHairType() const { + return hair_type; +} + +CommonColor CharInfo::GetHairColor() const { + return hair_color; +} + +HairFlip CharInfo::GetHairFlip() const { + return hair_flip; +} + +EyeType CharInfo::GetEyeType() const { + return eye_type; +} + +CommonColor CharInfo::GetEyeColor() const { + return eye_color; +} + +u8 CharInfo::GetEyeScale() const { + return eye_scale; +} + +u8 CharInfo::GetEyeAspect() const { + return eye_aspect; +} + +u8 CharInfo::GetEyeRotate() const { + return eye_rotate; +} + +u8 CharInfo::GetEyeX() const { + return eye_x; +} + +u8 CharInfo::GetEyeY() const { + return eye_y; +} + +EyebrowType CharInfo::GetEyebrowType() const { + return eyebrow_type; +} + +CommonColor CharInfo::GetEyebrowColor() const { + return eyebrow_color; +} + +u8 CharInfo::GetEyebrowScale() const { + return eyebrow_scale; +} + +u8 CharInfo::GetEyebrowAspect() const { + return eyebrow_aspect; +} + +u8 CharInfo::GetEyebrowRotate() const { + return eyebrow_rotate; +} + +u8 CharInfo::GetEyebrowX() const { + return eyebrow_x; +} + +u8 CharInfo::GetEyebrowY() const { + return eyebrow_y; +} + +NoseType CharInfo::GetNoseType() const { + return nose_type; +} + +u8 CharInfo::GetNoseScale() const { + return nose_scale; +} + +u8 CharInfo::GetNoseY() const { + return nose_y; +} + +MouthType CharInfo::GetMouthType() const { + return mouth_type; +} + +CommonColor CharInfo::GetMouthColor() const { + return mouth_color; +} + +u8 CharInfo::GetMouthScale() const { + return mouth_scale; +} + +u8 CharInfo::GetMouthAspect() const { + return mouth_aspect; +} + +u8 CharInfo::GetMouthY() const { + return mouth_y; +} + +CommonColor CharInfo::GetBeardColor() const { + return beard_color; +} + +BeardType CharInfo::GetBeardType() const { + return beard_type; +} + +MustacheType CharInfo::GetMustacheType() const { + return mustache_type; +} + +u8 CharInfo::GetMustacheScale() const { + return mustache_scale; +} + +u8 CharInfo::GetMustacheY() const { + return mustache_y; +} + +GlassType CharInfo::GetGlassType() const { + return glass_type; +} + +CommonColor CharInfo::GetGlassColor() const { + return glass_color; +} + +u8 CharInfo::GetGlassScale() const { + return glass_scale; +} + +u8 CharInfo::GetGlassY() const { + return glass_y; +} + +MoleType CharInfo::GetMoleType() const { + return mole_type; +} + +u8 CharInfo::GetMoleScale() const { + return mole_scale; +} + +u8 CharInfo::GetMoleX() const { + return mole_x; +} + +u8 CharInfo::GetMoleY() const { + return mole_y; +} + +bool CharInfo::operator==(const CharInfo& info) { + bool is_identical = info.Verify() == ValidationResult::NoErrors; + is_identical &= name.data == info.GetNickname().data; + is_identical &= create_id == info.GetCreateId(); + is_identical &= font_region == info.GetFontRegion(); + is_identical &= favorite_color == info.GetFavoriteColor(); + is_identical &= gender == info.GetGender(); + is_identical &= height == info.GetHeight(); + is_identical &= build == info.GetBuild(); + is_identical &= type == info.GetType(); + is_identical &= region_move == info.GetRegionMove(); + is_identical &= faceline_type == info.GetFacelineType(); + is_identical &= faceline_color == info.GetFacelineColor(); + is_identical &= faceline_wrinkle == info.GetFacelineWrinkle(); + is_identical &= faceline_make == info.GetFacelineMake(); + is_identical &= hair_type == info.GetHairType(); + is_identical &= hair_color == info.GetHairColor(); + is_identical &= hair_flip == info.GetHairFlip(); + is_identical &= eye_type == info.GetEyeType(); + is_identical &= eye_color == info.GetEyeColor(); + is_identical &= eye_scale == info.GetEyeScale(); + is_identical &= eye_aspect == info.GetEyeAspect(); + is_identical &= eye_rotate == info.GetEyeRotate(); + is_identical &= eye_x == info.GetEyeX(); + is_identical &= eye_y == info.GetEyeY(); + is_identical &= eyebrow_type == info.GetEyebrowType(); + is_identical &= eyebrow_color == info.GetEyebrowColor(); + is_identical &= eyebrow_scale == info.GetEyebrowScale(); + is_identical &= eyebrow_aspect == info.GetEyebrowAspect(); + is_identical &= eyebrow_rotate == info.GetEyebrowRotate(); + is_identical &= eyebrow_x == info.GetEyebrowX(); + is_identical &= eyebrow_y == info.GetEyebrowY(); + is_identical &= nose_type == info.GetNoseType(); + is_identical &= nose_scale == info.GetNoseScale(); + is_identical &= nose_y == info.GetNoseY(); + is_identical &= mouth_type == info.GetMouthType(); + is_identical &= mouth_color == info.GetMouthColor(); + is_identical &= mouth_scale == info.GetMouthScale(); + is_identical &= mouth_aspect == info.GetMouthAspect(); + is_identical &= mouth_y == info.GetMouthY(); + is_identical &= beard_color == info.GetBeardColor(); + is_identical &= beard_type == info.GetBeardType(); + is_identical &= mustache_type == info.GetMustacheType(); + is_identical &= mustache_scale == info.GetMustacheScale(); + is_identical &= mustache_y == info.GetMustacheY(); + is_identical &= glass_type == info.GetGlassType(); + is_identical &= glass_color == info.GetGlassColor(); + is_identical &= glass_scale == info.GetGlassScale(); + is_identical &= glass_y == info.GetGlassY(); + is_identical &= mole_type == info.GetMoleType(); + is_identical &= mole_scale == info.GetMoleScale(); + is_identical &= mole_x == info.GetMoleX(); + is_identical &= mole_y == info.GetMoleY(); + return is_identical; +} + +} // namespace Service::Mii diff --git a/src/core/hle/service/mii/types/char_info.h b/src/core/hle/service/mii/types/char_info.h new file mode 100644 index 000000000..d0c457fd5 --- /dev/null +++ b/src/core/hle/service/mii/types/char_info.h @@ -0,0 +1,137 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "core/hle/service/mii/mii_types.h" + +namespace Service::Mii { +class StoreData; + +// This is nn::mii::detail::CharInfoRaw +class CharInfo { +public: + void SetFromStoreData(const StoreData& store_data_raw); + + ValidationResult Verify() const; + + Common::UUID GetCreateId() const; + Nickname GetNickname() const; + FontRegion GetFontRegion() const; + FavoriteColor GetFavoriteColor() const; + Gender GetGender() const; + u8 GetHeight() const; + u8 GetBuild() const; + u8 GetType() const; + u8 GetRegionMove() const; + FacelineType GetFacelineType() const; + FacelineColor GetFacelineColor() const; + FacelineWrinkle GetFacelineWrinkle() const; + FacelineMake GetFacelineMake() const; + HairType GetHairType() const; + CommonColor GetHairColor() const; + HairFlip GetHairFlip() const; + EyeType GetEyeType() const; + CommonColor GetEyeColor() const; + u8 GetEyeScale() const; + u8 GetEyeAspect() const; + u8 GetEyeRotate() const; + u8 GetEyeX() const; + u8 GetEyeY() const; + EyebrowType GetEyebrowType() const; + CommonColor GetEyebrowColor() const; + u8 GetEyebrowScale() const; + u8 GetEyebrowAspect() const; + u8 GetEyebrowRotate() const; + u8 GetEyebrowX() const; + u8 GetEyebrowY() const; + NoseType GetNoseType() const; + u8 GetNoseScale() const; + u8 GetNoseY() const; + MouthType GetMouthType() const; + CommonColor GetMouthColor() const; + u8 GetMouthScale() const; + u8 GetMouthAspect() const; + u8 GetMouthY() const; + CommonColor GetBeardColor() const; + BeardType GetBeardType() const; + MustacheType GetMustacheType() const; + u8 GetMustacheScale() const; + u8 GetMustacheY() const; + GlassType GetGlassType() const; + CommonColor GetGlassColor() const; + u8 GetGlassScale() const; + u8 GetGlassY() const; + MoleType GetMoleType() const; + u8 GetMoleScale() const; + u8 GetMoleX() const; + u8 GetMoleY() const; + + bool operator==(const CharInfo& info); + +private: + Common::UUID create_id{}; + Nickname name{}; + u16 null_terminator{}; + FontRegion font_region{}; + FavoriteColor favorite_color{}; + Gender gender{}; + u8 height{}; + u8 build{}; + u8 type{}; + u8 region_move{}; + FacelineType faceline_type{}; + FacelineColor faceline_color{}; + FacelineWrinkle faceline_wrinkle{}; + FacelineMake faceline_make{}; + HairType hair_type{}; + CommonColor hair_color{}; + HairFlip hair_flip{}; + EyeType eye_type{}; + CommonColor eye_color{}; + u8 eye_scale{}; + u8 eye_aspect{}; + u8 eye_rotate{}; + u8 eye_x{}; + u8 eye_y{}; + EyebrowType eyebrow_type{}; + CommonColor eyebrow_color{}; + u8 eyebrow_scale{}; + u8 eyebrow_aspect{}; + u8 eyebrow_rotate{}; + u8 eyebrow_x{}; + u8 eyebrow_y{}; + NoseType nose_type{}; + u8 nose_scale{}; + u8 nose_y{}; + MouthType mouth_type{}; + CommonColor mouth_color{}; + u8 mouth_scale{}; + u8 mouth_aspect{}; + u8 mouth_y{}; + CommonColor beard_color{}; + BeardType beard_type{}; + MustacheType mustache_type{}; + u8 mustache_scale{}; + u8 mustache_y{}; + GlassType glass_type{}; + CommonColor glass_color{}; + u8 glass_scale{}; + u8 glass_y{}; + MoleType mole_type{}; + u8 mole_scale{}; + u8 mole_x{}; + u8 mole_y{}; + u8 padding{}; +}; +static_assert(sizeof(CharInfo) == 0x58, "CharInfo has incorrect size."); +static_assert(std::has_unique_object_representations_v<CharInfo>, + "All bits of CharInfo must contribute to its value."); + +struct CharInfoElement { + CharInfo char_info{}; + Source source{}; +}; +static_assert(sizeof(CharInfoElement) == 0x5c, "CharInfoElement has incorrect size."); + +}; // namespace Service::Mii diff --git a/src/core/hle/service/mii/types/core_data.cpp b/src/core/hle/service/mii/types/core_data.cpp new file mode 100644 index 000000000..970c748ca --- /dev/null +++ b/src/core/hle/service/mii/types/core_data.cpp @@ -0,0 +1,805 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "common/assert.h" +#include "core/hle/service/mii/mii_util.h" +#include "core/hle/service/mii/types/char_info.h" +#include "core/hle/service/mii/types/core_data.h" +#include "core/hle/service/mii/types/raw_data.h" + +namespace Service::Mii { + +void CoreData::SetDefault() { + data = {}; + name = GetDefaultNickname(); +} + +void CoreData::BuildRandom(Age age, Gender gender, Race race) { + if (gender == Gender::All) { + gender = MiiUtil::GetRandomValue(Gender::Max); + } + + if (age == Age::All) { + const auto random{MiiUtil::GetRandomValue<int>(10)}; + if (random >= 8) { + age = Age::Old; + } else if (random >= 4) { + age = Age::Normal; + } else { + age = Age::Young; + } + } + + if (race == Race::All) { + const auto random{MiiUtil::GetRandomValue<int>(10)}; + if (random >= 8) { + race = Race::Black; + } else if (random >= 4) { + race = Race::White; + } else { + race = Race::Asian; + } + } + + SetGender(gender); + SetFavoriteColor(MiiUtil::GetRandomValue(FavoriteColor::Max)); + SetRegionMove(0); + SetFontRegion(FontRegion::Standard); + SetType(0); + SetHeight(64); + SetBuild(64); + + u32 axis_y{}; + if (gender == Gender::Female && age == Age::Young) { + axis_y = MiiUtil::GetRandomValue<u32>(3); + } + + const std::size_t index{3 * static_cast<std::size_t>(age) + + 9 * static_cast<std::size_t>(gender) + static_cast<std::size_t>(race)}; + + const auto& faceline_type_info{RawData::RandomMiiFaceline.at(index)}; + const auto& faceline_color_info{RawData::RandomMiiFacelineColor.at( + 3 * static_cast<std::size_t>(gender) + static_cast<std::size_t>(race))}; + const auto& faceline_wrinkle_info{RawData::RandomMiiFacelineWrinkle.at(index)}; + const auto& faceline_makeup_info{RawData::RandomMiiFacelineMakeup.at(index)}; + const auto& hair_type_info{RawData::RandomMiiHairType.at(index)}; + const auto& hair_color_info{RawData::RandomMiiHairColor.at(3 * static_cast<std::size_t>(race) + + static_cast<std::size_t>(age))}; + const auto& eye_type_info{RawData::RandomMiiEyeType.at(index)}; + const auto& eye_color_info{RawData::RandomMiiEyeColor.at(static_cast<std::size_t>(race))}; + const auto& eyebrow_type_info{RawData::RandomMiiEyebrowType.at(index)}; + const auto& nose_type_info{RawData::RandomMiiNoseType.at(index)}; + const auto& mouth_type_info{RawData::RandomMiiMouthType.at(index)}; + const auto& glasses_type_info{RawData::RandomMiiGlassType.at(static_cast<std::size_t>(age))}; + + data.faceline_type.Assign( + faceline_type_info + .values[MiiUtil::GetRandomValue<std::size_t>(faceline_type_info.values_count)]); + data.faceline_color.Assign( + faceline_color_info + .values[MiiUtil::GetRandomValue<std::size_t>(faceline_color_info.values_count)]); + data.faceline_wrinkle.Assign( + faceline_wrinkle_info + .values[MiiUtil::GetRandomValue<std::size_t>(faceline_wrinkle_info.values_count)]); + data.faceline_makeup.Assign( + faceline_makeup_info + .values[MiiUtil::GetRandomValue<std::size_t>(faceline_makeup_info.values_count)]); + + data.hair_type.Assign( + hair_type_info.values[MiiUtil::GetRandomValue<std::size_t>(hair_type_info.values_count)]); + SetHairColor(RawData::GetHairColorFromVer3( + hair_color_info + .values[MiiUtil::GetRandomValue<std::size_t>(hair_color_info.values_count)])); + SetHairFlip(MiiUtil::GetRandomValue(HairFlip::Max)); + + data.eye_type.Assign( + eye_type_info.values[MiiUtil::GetRandomValue<std::size_t>(eye_type_info.values_count)]); + + const auto eye_rotate_1{gender != Gender::Male ? 4 : 2}; + const auto eye_rotate_2{gender != Gender::Male ? 3 : 4}; + const auto eye_rotate_offset{32 - RawData::EyeRotateLookup[eye_rotate_1] + eye_rotate_2}; + const auto eye_rotate{32 - RawData::EyeRotateLookup[data.eye_type]}; + + SetEyeColor(RawData::GetEyeColorFromVer3( + eye_color_info.values[MiiUtil::GetRandomValue<std::size_t>(eye_color_info.values_count)])); + SetEyeScale(4); + SetEyeAspect(3); + SetEyeRotate(static_cast<u8>(eye_rotate_offset - eye_rotate)); + SetEyeX(2); + SetEyeY(static_cast<u8>(axis_y + 12)); + + data.eyebrow_type.Assign( + eyebrow_type_info + .values[MiiUtil::GetRandomValue<std::size_t>(eyebrow_type_info.values_count)]); + + const auto eyebrow_rotate_1{race == Race::Asian ? 6 : 0}; + const auto eyebrow_y{race == Race::Asian ? 6 : 7}; + const auto eyebrow_rotate_offset{32 - RawData::EyebrowRotateLookup[eyebrow_rotate_1] + 6}; + const auto eyebrow_rotate{ + 32 - RawData::EyebrowRotateLookup[static_cast<std::size_t>(data.eyebrow_type.Value())]}; + + SetEyebrowColor(GetHairColor()); + SetEyebrowScale(4); + SetEyebrowAspect(3); + SetEyebrowRotate(static_cast<u8>(eyebrow_rotate_offset - eyebrow_rotate)); + SetEyebrowX(2); + SetEyebrowY(static_cast<u8>(axis_y + eyebrow_y)); + + data.nose_type.Assign( + nose_type_info.values[MiiUtil::GetRandomValue<std::size_t>(nose_type_info.values_count)]); + SetNoseScale(gender == Gender::Female ? 3 : 4); + SetNoseY(static_cast<u8>(axis_y + 9)); + + const auto mouth_color{gender == Gender::Female ? MiiUtil::GetRandomValue<int>(4) : 0}; + + data.mouth_type.Assign( + mouth_type_info.values[MiiUtil::GetRandomValue<std::size_t>(mouth_type_info.values_count)]); + SetMouthColor(RawData::GetMouthColorFromVer3(mouth_color)); + SetMouthScale(4); + SetMouthAspect(3); + SetMouthY(static_cast<u8>(axis_y + 13)); + + SetBeardColor(GetHairColor()); + SetMustacheScale(4); + + if (gender == Gender::Male && age != Age::Young && MiiUtil::GetRandomValue<int>(10) < 2) { + const auto mustache_and_beard_flag{MiiUtil::GetRandomValue(BeardAndMustacheFlag::All)}; + + auto beard_type{BeardType::None}; + auto mustache_type{MustacheType::None}; + + if ((mustache_and_beard_flag & BeardAndMustacheFlag::Beard) == + BeardAndMustacheFlag::Beard) { + beard_type = MiiUtil::GetRandomValue(BeardType::Min, BeardType::Max); + } + + if ((mustache_and_beard_flag & BeardAndMustacheFlag::Mustache) == + BeardAndMustacheFlag::Mustache) { + mustache_type = MiiUtil::GetRandomValue(MustacheType::Min, MustacheType::Max); + } + + SetMustacheType(mustache_type); + SetBeardType(beard_type); + SetMustacheY(10); + } else { + SetMustacheType(MustacheType::None); + SetBeardType(BeardType::None); + SetMustacheY(static_cast<u8>(axis_y + 10)); + } + + const auto glasses_type_start{MiiUtil::GetRandomValue<std::size_t>(100)}; + u8 glasses_type{}; + while (glasses_type_start < glasses_type_info.values[glasses_type]) { + if (++glasses_type >= glasses_type_info.values_count) { + glasses_type = 0; + break; + } + } + + SetGlassType(static_cast<GlassType>(glasses_type)); + SetGlassColor(RawData::GetGlassColorFromVer3(0)); + SetGlassScale(4); + SetGlassY(static_cast<u8>(axis_y + 10)); + + SetMoleType(MoleType::None); + SetMoleScale(4); + SetMoleX(2); + SetMoleY(20); +} + +void CoreData::BuildFromCharInfo(const CharInfo& char_info) { + name = char_info.GetNickname(); + SetFontRegion(char_info.GetFontRegion()); + SetFavoriteColor(char_info.GetFavoriteColor()); + SetGender(char_info.GetGender()); + SetHeight(char_info.GetHeight()); + SetBuild(char_info.GetBuild()); + SetType(char_info.GetType()); + SetRegionMove(char_info.GetRegionMove()); + SetFacelineType(char_info.GetFacelineType()); + SetFacelineColor(char_info.GetFacelineColor()); + SetFacelineWrinkle(char_info.GetFacelineWrinkle()); + SetFacelineMake(char_info.GetFacelineMake()); + SetHairType(char_info.GetHairType()); + SetHairColor(char_info.GetHairColor()); + SetHairFlip(char_info.GetHairFlip()); + SetEyeType(char_info.GetEyeType()); + SetEyeColor(char_info.GetEyeColor()); + SetEyeScale(char_info.GetEyeScale()); + SetEyeAspect(char_info.GetEyeAspect()); + SetEyeRotate(char_info.GetEyeRotate()); + SetEyeX(char_info.GetEyeX()); + SetEyeY(char_info.GetEyeY()); + SetEyebrowType(char_info.GetEyebrowType()); + SetEyebrowColor(char_info.GetEyebrowColor()); + SetEyebrowScale(char_info.GetEyebrowScale()); + SetEyebrowAspect(char_info.GetEyebrowAspect()); + SetEyebrowRotate(char_info.GetEyebrowRotate()); + SetEyebrowX(char_info.GetEyebrowX()); + SetEyebrowY(char_info.GetEyebrowY() - 3); + SetNoseType(char_info.GetNoseType()); + SetNoseScale(char_info.GetNoseScale()); + SetNoseY(char_info.GetNoseY()); + SetMouthType(char_info.GetMouthType()); + SetMouthColor(char_info.GetMouthColor()); + SetMouthScale(char_info.GetMouthScale()); + SetMouthAspect(char_info.GetMouthAspect()); + SetMouthY(char_info.GetMouthY()); + SetBeardColor(char_info.GetBeardColor()); + SetBeardType(char_info.GetBeardType()); + SetMustacheType(char_info.GetMustacheType()); + SetMustacheScale(char_info.GetMustacheScale()); + SetMustacheY(char_info.GetMustacheY()); + SetGlassType(char_info.GetGlassType()); + SetGlassColor(char_info.GetGlassColor()); + SetGlassScale(char_info.GetGlassScale()); + SetGlassY(char_info.GetGlassY()); + SetMoleType(char_info.GetMoleType()); + SetMoleScale(char_info.GetMoleScale()); + SetMoleX(char_info.GetMoleX()); + SetMoleY(char_info.GetMoleY()); +} + +ValidationResult CoreData::IsValid() const { + if (!name.IsValid()) { + return ValidationResult::InvalidName; + } + if (GetFontRegion() > FontRegion::Max) { + return ValidationResult::InvalidFont; + } + if (GetFavoriteColor() > FavoriteColor::Max) { + return ValidationResult::InvalidColor; + } + if (GetGender() > Gender::Max) { + return ValidationResult::InvalidGender; + } + if (GetHeight() > MaxHeight) { + return ValidationResult::InvalidHeight; + } + if (GetBuild() > MaxBuild) { + return ValidationResult::InvalidBuild; + } + if (GetType() > MaxType) { + return ValidationResult::InvalidType; + } + if (GetRegionMove() > MaxRegionMove) { + return ValidationResult::InvalidRegionMove; + } + if (GetFacelineType() > FacelineType::Max) { + return ValidationResult::InvalidFacelineType; + } + if (GetFacelineColor() > FacelineColor::Max) { + return ValidationResult::InvalidFacelineColor; + } + if (GetFacelineWrinkle() > FacelineWrinkle::Max) { + return ValidationResult::InvalidFacelineWrinkle; + } + if (GetFacelineMake() > FacelineMake::Max) { + return ValidationResult::InvalidFacelineMake; + } + if (GetHairType() > HairType::Max) { + return ValidationResult::InvalidHairType; + } + if (GetHairColor() > CommonColor::Max) { + return ValidationResult::InvalidHairColor; + } + if (GetHairFlip() > HairFlip::Max) { + return ValidationResult::InvalidHairFlip; + } + if (GetEyeType() > EyeType::Max) { + return ValidationResult::InvalidEyeType; + } + if (GetEyeColor() > CommonColor::Max) { + return ValidationResult::InvalidEyeColor; + } + if (GetEyeScale() > MaxEyeScale) { + return ValidationResult::InvalidEyeScale; + } + if (GetEyeAspect() > MaxEyeAspect) { + return ValidationResult::InvalidEyeAspect; + } + if (GetEyeRotate() > MaxEyeRotate) { + return ValidationResult::InvalidEyeRotate; + } + if (GetEyeX() > MaxEyeX) { + return ValidationResult::InvalidEyeX; + } + if (GetEyeY() > MaxEyeY) { + return ValidationResult::InvalidEyeY; + } + if (GetEyebrowType() > EyebrowType::Max) { + return ValidationResult::InvalidEyebrowType; + } + if (GetEyebrowColor() > CommonColor::Max) { + return ValidationResult::InvalidEyebrowColor; + } + if (GetEyebrowScale() > MaxEyebrowScale) { + return ValidationResult::InvalidEyebrowScale; + } + if (GetEyebrowAspect() > MaxEyebrowAspect) { + return ValidationResult::InvalidEyebrowAspect; + } + if (GetEyebrowRotate() > MaxEyebrowRotate) { + return ValidationResult::InvalidEyebrowRotate; + } + if (GetEyebrowX() > MaxEyebrowX) { + return ValidationResult::InvalidEyebrowX; + } + if (GetEyebrowY() > MaxEyebrowY) { + return ValidationResult::InvalidEyebrowY; + } + if (GetNoseType() > NoseType::Max) { + return ValidationResult::InvalidNoseType; + } + if (GetNoseScale() > MaxNoseScale) { + return ValidationResult::InvalidNoseScale; + } + if (GetNoseY() > MaxNoseY) { + return ValidationResult::InvalidNoseY; + } + if (GetMouthType() > MouthType::Max) { + return ValidationResult::InvalidMouthType; + } + if (GetMouthColor() > CommonColor::Max) { + return ValidationResult::InvalidMouthColor; + } + if (GetMouthScale() > MaxMouthScale) { + return ValidationResult::InvalidMouthScale; + } + if (GetMouthAspect() > MaxMoutAspect) { + return ValidationResult::InvalidMouthAspect; + } + if (GetMouthY() > MaxMouthY) { + return ValidationResult::InvalidMouthY; + } + if (GetBeardColor() > CommonColor::Max) { + return ValidationResult::InvalidBeardColor; + } + if (GetBeardType() > BeardType::Max) { + return ValidationResult::InvalidBeardType; + } + if (GetMustacheType() > MustacheType::Max) { + return ValidationResult::InvalidMustacheType; + } + if (GetMustacheScale() > MaxMustacheScale) { + return ValidationResult::InvalidMustacheScale; + } + if (GetMustacheY() > MaxMustacheY) { + return ValidationResult::InvalidMustacheY; + } + if (GetGlassType() > GlassType::Max) { + return ValidationResult::InvalidGlassType; + } + if (GetGlassColor() > CommonColor::Max) { + return ValidationResult::InvalidGlassColor; + } + if (GetGlassScale() > MaxGlassScale) { + return ValidationResult::InvalidGlassScale; + } + if (GetGlassY() > MaxGlassY) { + return ValidationResult::InvalidGlassY; + } + if (GetMoleType() > MoleType::Max) { + return ValidationResult::InvalidMoleType; + } + if (GetMoleScale() > MaxMoleScale) { + return ValidationResult::InvalidMoleScale; + } + if (GetMoleX() > MaxMoleX) { + return ValidationResult::InvalidMoleX; + } + if (GetMoleY() > MaxMoleY) { + return ValidationResult::InvalidMoleY; + } + return ValidationResult::NoErrors; +} + +void CoreData::SetFontRegion(FontRegion value) { + data.font_region.Assign(static_cast<u32>(value)); +} + +void CoreData::SetFavoriteColor(FavoriteColor value) { + data.favorite_color.Assign(static_cast<u32>(value)); +} + +void CoreData::SetGender(Gender value) { + data.gender.Assign(static_cast<u32>(value)); +} + +void CoreData::SetHeight(u8 value) { + data.height.Assign(value); +} + +void CoreData::SetBuild(u8 value) { + data.build.Assign(value); +} + +void CoreData::SetType(u8 value) { + data.type.Assign(value); +} + +void CoreData::SetRegionMove(u8 value) { + data.region_move.Assign(value); +} + +void CoreData::SetFacelineType(FacelineType value) { + data.faceline_type.Assign(static_cast<u32>(value)); +} + +void CoreData::SetFacelineColor(FacelineColor value) { + data.faceline_color.Assign(static_cast<u32>(value)); +} + +void CoreData::SetFacelineWrinkle(FacelineWrinkle value) { + data.faceline_wrinkle.Assign(static_cast<u32>(value)); +} + +void CoreData::SetFacelineMake(FacelineMake value) { + data.faceline_makeup.Assign(static_cast<u32>(value)); +} + +void CoreData::SetHairType(HairType value) { + data.hair_type.Assign(static_cast<u32>(value)); +} + +void CoreData::SetHairColor(CommonColor value) { + data.hair_color.Assign(static_cast<u32>(value)); +} + +void CoreData::SetHairFlip(HairFlip value) { + data.hair_flip.Assign(static_cast<u32>(value)); +} + +void CoreData::SetEyeType(EyeType value) { + data.eye_type.Assign(static_cast<u32>(value)); +} + +void CoreData::SetEyeColor(CommonColor value) { + data.eye_color.Assign(static_cast<u32>(value)); +} + +void CoreData::SetEyeScale(u8 value) { + data.eye_scale.Assign(value); +} + +void CoreData::SetEyeAspect(u8 value) { + data.eye_aspect.Assign(value); +} + +void CoreData::SetEyeRotate(u8 value) { + data.eye_rotate.Assign(value); +} + +void CoreData::SetEyeX(u8 value) { + data.eye_x.Assign(value); +} + +void CoreData::SetEyeY(u8 value) { + data.eye_y.Assign(value); +} + +void CoreData::SetEyebrowType(EyebrowType value) { + data.eyebrow_type.Assign(static_cast<u32>(value)); +} + +void CoreData::SetEyebrowColor(CommonColor value) { + data.eyebrow_color.Assign(static_cast<u32>(value)); +} + +void CoreData::SetEyebrowScale(u8 value) { + data.eyebrow_scale.Assign(value); +} + +void CoreData::SetEyebrowAspect(u8 value) { + data.eyebrow_aspect.Assign(value); +} + +void CoreData::SetEyebrowRotate(u8 value) { + data.eyebrow_rotate.Assign(value); +} + +void CoreData::SetEyebrowX(u8 value) { + data.eyebrow_x.Assign(value); +} + +void CoreData::SetEyebrowY(u8 value) { + data.eyebrow_y.Assign(value); +} + +void CoreData::SetNoseType(NoseType value) { + data.nose_type.Assign(static_cast<u32>(value)); +} + +void CoreData::SetNoseScale(u8 value) { + data.nose_scale.Assign(value); +} + +void CoreData::SetNoseY(u8 value) { + data.nose_y.Assign(value); +} + +void CoreData::SetMouthType(MouthType value) { + data.mouth_type.Assign(static_cast<u32>(value)); +} + +void CoreData::SetMouthColor(CommonColor value) { + data.mouth_color.Assign(static_cast<u32>(value)); +} + +void CoreData::SetMouthScale(u8 value) { + data.mouth_scale.Assign(value); +} + +void CoreData::SetMouthAspect(u8 value) { + data.mouth_aspect.Assign(value); +} + +void CoreData::SetMouthY(u8 value) { + data.mouth_y.Assign(value); +} + +void CoreData::SetBeardColor(CommonColor value) { + data.beard_color.Assign(static_cast<u32>(value)); +} + +void CoreData::SetBeardType(BeardType value) { + data.beard_type.Assign(static_cast<u32>(value)); +} + +void CoreData::SetMustacheType(MustacheType value) { + data.mustache_type.Assign(static_cast<u32>(value)); +} + +void CoreData::SetMustacheScale(u8 value) { + data.mustache_scale.Assign(value); +} + +void CoreData::SetMustacheY(u8 value) { + data.mustache_y.Assign(value); +} + +void CoreData::SetGlassType(GlassType value) { + data.glasses_type.Assign(static_cast<u32>(value)); +} + +void CoreData::SetGlassColor(CommonColor value) { + data.glasses_color.Assign(static_cast<u32>(value)); +} + +void CoreData::SetGlassScale(u8 value) { + data.glasses_scale.Assign(value); +} + +void CoreData::SetGlassY(u8 value) { + data.glasses_y.Assign(value); +} + +void CoreData::SetMoleType(MoleType value) { + data.mole_type.Assign(static_cast<u32>(value)); +} + +void CoreData::SetMoleScale(u8 value) { + data.mole_scale.Assign(value); +} + +void CoreData::SetMoleX(u8 value) { + data.mole_x.Assign(value); +} + +void CoreData::SetMoleY(u8 value) { + data.mole_y.Assign(value); +} + +void CoreData::SetNickname(Nickname nickname) { + name = nickname; +} + +FontRegion CoreData::GetFontRegion() const { + return static_cast<FontRegion>(data.font_region.Value()); +} + +FavoriteColor CoreData::GetFavoriteColor() const { + return static_cast<FavoriteColor>(data.favorite_color.Value()); +} + +Gender CoreData::GetGender() const { + return static_cast<Gender>(data.gender.Value()); +} + +u8 CoreData::GetHeight() const { + return static_cast<u8>(data.height.Value()); +} + +u8 CoreData::GetBuild() const { + return static_cast<u8>(data.build.Value()); +} + +u8 CoreData::GetType() const { + return static_cast<u8>(data.type.Value()); +} + +u8 CoreData::GetRegionMove() const { + return static_cast<u8>(data.region_move.Value()); +} + +FacelineType CoreData::GetFacelineType() const { + return static_cast<FacelineType>(data.faceline_type.Value()); +} + +FacelineColor CoreData::GetFacelineColor() const { + return static_cast<FacelineColor>(data.faceline_color.Value()); +} + +FacelineWrinkle CoreData::GetFacelineWrinkle() const { + return static_cast<FacelineWrinkle>(data.faceline_wrinkle.Value()); +} + +FacelineMake CoreData::GetFacelineMake() const { + return static_cast<FacelineMake>(data.faceline_makeup.Value()); +} + +HairType CoreData::GetHairType() const { + return static_cast<HairType>(data.hair_type.Value()); +} + +CommonColor CoreData::GetHairColor() const { + return static_cast<CommonColor>(data.hair_color.Value()); +} + +HairFlip CoreData::GetHairFlip() const { + return static_cast<HairFlip>(data.hair_flip.Value()); +} + +EyeType CoreData::GetEyeType() const { + return static_cast<EyeType>(data.eye_type.Value()); +} + +CommonColor CoreData::GetEyeColor() const { + return static_cast<CommonColor>(data.eye_color.Value()); +} + +u8 CoreData::GetEyeScale() const { + return static_cast<u8>(data.eye_scale.Value()); +} + +u8 CoreData::GetEyeAspect() const { + return static_cast<u8>(data.eye_aspect.Value()); +} + +u8 CoreData::GetEyeRotate() const { + return static_cast<u8>(data.eye_rotate.Value()); +} + +u8 CoreData::GetEyeX() const { + return static_cast<u8>(data.eye_x.Value()); +} + +u8 CoreData::GetEyeY() const { + return static_cast<u8>(data.eye_y.Value()); +} + +EyebrowType CoreData::GetEyebrowType() const { + return static_cast<EyebrowType>(data.eyebrow_type.Value()); +} + +CommonColor CoreData::GetEyebrowColor() const { + return static_cast<CommonColor>(data.eyebrow_color.Value()); +} + +u8 CoreData::GetEyebrowScale() const { + return static_cast<u8>(data.eyebrow_scale.Value()); +} + +u8 CoreData::GetEyebrowAspect() const { + return static_cast<u8>(data.eyebrow_aspect.Value()); +} + +u8 CoreData::GetEyebrowRotate() const { + return static_cast<u8>(data.eyebrow_rotate.Value()); +} + +u8 CoreData::GetEyebrowX() const { + return static_cast<u8>(data.eyebrow_x.Value()); +} + +u8 CoreData::GetEyebrowY() const { + return static_cast<u8>(data.eyebrow_y.Value()); +} + +NoseType CoreData::GetNoseType() const { + return static_cast<NoseType>(data.nose_type.Value()); +} + +u8 CoreData::GetNoseScale() const { + return static_cast<u8>(data.nose_scale.Value()); +} + +u8 CoreData::GetNoseY() const { + return static_cast<u8>(data.nose_y.Value()); +} + +MouthType CoreData::GetMouthType() const { + return static_cast<MouthType>(data.mouth_type.Value()); +} + +CommonColor CoreData::GetMouthColor() const { + return static_cast<CommonColor>(data.mouth_color.Value()); +} + +u8 CoreData::GetMouthScale() const { + return static_cast<u8>(data.mouth_scale.Value()); +} + +u8 CoreData::GetMouthAspect() const { + return static_cast<u8>(data.mouth_aspect.Value()); +} + +u8 CoreData::GetMouthY() const { + return static_cast<u8>(data.mouth_y.Value()); +} + +CommonColor CoreData::GetBeardColor() const { + return static_cast<CommonColor>(data.beard_color.Value()); +} + +BeardType CoreData::GetBeardType() const { + return static_cast<BeardType>(data.beard_type.Value()); +} + +MustacheType CoreData::GetMustacheType() const { + return static_cast<MustacheType>(data.mustache_type.Value()); +} + +u8 CoreData::GetMustacheScale() const { + return static_cast<u8>(data.mustache_scale.Value()); +} + +u8 CoreData::GetMustacheY() const { + return static_cast<u8>(data.mustache_y.Value()); +} + +GlassType CoreData::GetGlassType() const { + return static_cast<GlassType>(data.glasses_type.Value()); +} + +CommonColor CoreData::GetGlassColor() const { + return static_cast<CommonColor>(data.glasses_color.Value()); +} + +u8 CoreData::GetGlassScale() const { + return static_cast<u8>(data.glasses_scale.Value()); +} + +u8 CoreData::GetGlassY() const { + return static_cast<u8>(data.glasses_y.Value()); +} + +MoleType CoreData::GetMoleType() const { + return static_cast<MoleType>(data.mole_type.Value()); +} + +u8 CoreData::GetMoleScale() const { + return static_cast<u8>(data.mole_scale.Value()); +} + +u8 CoreData::GetMoleX() const { + return static_cast<u8>(data.mole_x.Value()); +} + +u8 CoreData::GetMoleY() const { + return static_cast<u8>(data.mole_y.Value()); +} + +Nickname CoreData::GetNickname() const { + return name; +} + +Nickname CoreData::GetDefaultNickname() const { + return {u'n', u'o', u' ', u'n', u'a', u'm', u'e'}; +} + +Nickname CoreData::GetInvalidNickname() const { + return {u'?', u'?', u'?'}; +} + +} // namespace Service::Mii diff --git a/src/core/hle/service/mii/types/core_data.h b/src/core/hle/service/mii/types/core_data.h new file mode 100644 index 000000000..8897e4f3b --- /dev/null +++ b/src/core/hle/service/mii/types/core_data.h @@ -0,0 +1,219 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "core/hle/service/mii/mii_types.h" + +namespace Service::Mii { +class CharInfo; + +struct StoreDataBitFields { + union { + u32 word_0{}; + + BitField<0, 8, u32> hair_type; + BitField<8, 7, u32> height; + BitField<15, 1, u32> mole_type; + BitField<16, 7, u32> build; + BitField<23, 1, u32> hair_flip; + BitField<24, 7, u32> hair_color; + BitField<31, 1, u32> type; + }; + + union { + u32 word_1{}; + + BitField<0, 7, u32> eye_color; + BitField<7, 1, u32> gender; + BitField<8, 7, u32> eyebrow_color; + BitField<16, 7, u32> mouth_color; + BitField<24, 7, u32> beard_color; + }; + + union { + u32 word_2{}; + + BitField<0, 7, u32> glasses_color; + BitField<8, 6, u32> eye_type; + BitField<14, 2, u32> region_move; + BitField<16, 6, u32> mouth_type; + BitField<22, 2, u32> font_region; + BitField<24, 5, u32> eye_y; + BitField<29, 3, u32> glasses_scale; + }; + + union { + u32 word_3{}; + + BitField<0, 5, u32> eyebrow_type; + BitField<5, 3, u32> mustache_type; + BitField<8, 5, u32> nose_type; + BitField<13, 3, u32> beard_type; + BitField<16, 5, u32> nose_y; + BitField<21, 3, u32> mouth_aspect; + BitField<24, 5, u32> mouth_y; + BitField<29, 3, u32> eyebrow_aspect; + }; + + union { + u32 word_4{}; + + BitField<0, 5, u32> mustache_y; + BitField<5, 3, u32> eye_rotate; + BitField<8, 5, u32> glasses_y; + BitField<13, 3, u32> eye_aspect; + BitField<16, 5, u32> mole_x; + BitField<21, 3, u32> eye_scale; + BitField<24, 5, u32> mole_y; + }; + + union { + u32 word_5{}; + + BitField<0, 5, u32> glasses_type; + BitField<8, 4, u32> favorite_color; + BitField<12, 4, u32> faceline_type; + BitField<16, 4, u32> faceline_color; + BitField<20, 4, u32> faceline_wrinkle; + BitField<24, 4, u32> faceline_makeup; + BitField<28, 4, u32> eye_x; + }; + + union { + u32 word_6{}; + + BitField<0, 4, u32> eyebrow_scale; + BitField<4, 4, u32> eyebrow_rotate; + BitField<8, 4, u32> eyebrow_x; + BitField<12, 4, u32> eyebrow_y; + BitField<16, 4, u32> nose_scale; + BitField<20, 4, u32> mouth_scale; + BitField<24, 4, u32> mustache_scale; + BitField<28, 4, u32> mole_scale; + }; +}; +static_assert(sizeof(StoreDataBitFields) == 0x1c, "StoreDataBitFields has incorrect size."); +static_assert(std::is_trivially_copyable_v<StoreDataBitFields>, + "StoreDataBitFields is not trivially copyable."); + +class CoreData { +public: + void SetDefault(); + void BuildRandom(Age age, Gender gender, Race race); + void BuildFromCharInfo(const CharInfo& char_info); + + ValidationResult IsValid() const; + + void SetFontRegion(FontRegion value); + void SetFavoriteColor(FavoriteColor value); + void SetGender(Gender value); + void SetHeight(u8 value); + void SetBuild(u8 value); + void SetType(u8 value); + void SetRegionMove(u8 value); + void SetFacelineType(FacelineType value); + void SetFacelineColor(FacelineColor value); + void SetFacelineWrinkle(FacelineWrinkle value); + void SetFacelineMake(FacelineMake value); + void SetHairType(HairType value); + void SetHairColor(CommonColor value); + void SetHairFlip(HairFlip value); + void SetEyeType(EyeType value); + void SetEyeColor(CommonColor value); + void SetEyeScale(u8 value); + void SetEyeAspect(u8 value); + void SetEyeRotate(u8 value); + void SetEyeX(u8 value); + void SetEyeY(u8 value); + void SetEyebrowType(EyebrowType value); + void SetEyebrowColor(CommonColor value); + void SetEyebrowScale(u8 value); + void SetEyebrowAspect(u8 value); + void SetEyebrowRotate(u8 value); + void SetEyebrowX(u8 value); + void SetEyebrowY(u8 value); + void SetNoseType(NoseType value); + void SetNoseScale(u8 value); + void SetNoseY(u8 value); + void SetMouthType(MouthType value); + void SetMouthColor(CommonColor value); + void SetMouthScale(u8 value); + void SetMouthAspect(u8 value); + void SetMouthY(u8 value); + void SetBeardColor(CommonColor value); + void SetBeardType(BeardType value); + void SetMustacheType(MustacheType value); + void SetMustacheScale(u8 value); + void SetMustacheY(u8 value); + void SetGlassType(GlassType value); + void SetGlassColor(CommonColor value); + void SetGlassScale(u8 value); + void SetGlassY(u8 value); + void SetMoleType(MoleType value); + void SetMoleScale(u8 value); + void SetMoleX(u8 value); + void SetMoleY(u8 value); + void SetNickname(Nickname nickname); + + FontRegion GetFontRegion() const; + FavoriteColor GetFavoriteColor() const; + Gender GetGender() const; + u8 GetHeight() const; + u8 GetBuild() const; + u8 GetType() const; + u8 GetRegionMove() const; + FacelineType GetFacelineType() const; + FacelineColor GetFacelineColor() const; + FacelineWrinkle GetFacelineWrinkle() const; + FacelineMake GetFacelineMake() const; + HairType GetHairType() const; + CommonColor GetHairColor() const; + HairFlip GetHairFlip() const; + EyeType GetEyeType() const; + CommonColor GetEyeColor() const; + u8 GetEyeScale() const; + u8 GetEyeAspect() const; + u8 GetEyeRotate() const; + u8 GetEyeX() const; + u8 GetEyeY() const; + EyebrowType GetEyebrowType() const; + CommonColor GetEyebrowColor() const; + u8 GetEyebrowScale() const; + u8 GetEyebrowAspect() const; + u8 GetEyebrowRotate() const; + u8 GetEyebrowX() const; + u8 GetEyebrowY() const; + NoseType GetNoseType() const; + u8 GetNoseScale() const; + u8 GetNoseY() const; + MouthType GetMouthType() const; + CommonColor GetMouthColor() const; + u8 GetMouthScale() const; + u8 GetMouthAspect() const; + u8 GetMouthY() const; + CommonColor GetBeardColor() const; + BeardType GetBeardType() const; + MustacheType GetMustacheType() const; + u8 GetMustacheScale() const; + u8 GetMustacheY() const; + GlassType GetGlassType() const; + CommonColor GetGlassColor() const; + u8 GetGlassScale() const; + u8 GetGlassY() const; + MoleType GetMoleType() const; + u8 GetMoleScale() const; + u8 GetMoleX() const; + u8 GetMoleY() const; + Nickname GetNickname() const; + Nickname GetDefaultNickname() const; + Nickname GetInvalidNickname() const; + +private: + StoreDataBitFields data{}; + Nickname name{}; +}; +static_assert(sizeof(CoreData) == 0x30, "CoreData has incorrect size."); +static_assert(std::is_trivially_copyable_v<CoreData>, "CoreData type must be trivially copyable."); + +}; // namespace Service::Mii diff --git a/src/core/hle/service/mii/raw_data.cpp b/src/core/hle/service/mii/types/raw_data.cpp index 1442280c8..0e1a07fd7 100644 --- a/src/core/hle/service/mii/raw_data.cpp +++ b/src/core/hle/service/mii/types/raw_data.cpp @@ -1,11 +1,88 @@ // SPDX-FileCopyrightText: Ryujinx Team and Contributors // SPDX-License-Identifier: MIT -#include "core/hle/service/mii/raw_data.h" +#include "core/hle/service/mii/types/raw_data.h" namespace Service::Mii::RawData { -const std::array<Service::Mii::DefaultMii, 8> DefaultMii{ +constexpr std::array<u8, static_cast<u8>(FacelineColor::Count)> FromVer3FacelineColorTable{ + 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x0, 0x1, 0x5, 0x5, +}; + +constexpr std::array<u8, static_cast<u8>(CommonColor::Count)> FromVer3HairColorTable{ + 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x0, 0x4, 0x3, 0x5, 0x4, 0x4, 0x6, 0x2, 0x0, + 0x6, 0x4, 0x3, 0x2, 0x2, 0x7, 0x3, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, + 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x0, 0x0, 0x4, + 0x4, 0x4, 0x4, 0x4, 0x4, 0x0, 0x0, 0x0, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x5, 0x5, 0x5, + 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x5, 0x7, 0x5, 0x7, 0x7, 0x7, 0x7, 0x7, 0x6, 0x7, + 0x7, 0x7, 0x7, 0x7, 0x3, 0x7, 0x7, 0x7, 0x7, 0x7, 0x0, 0x4, 0x4, 0x4, 0x4, +}; + +constexpr std::array<u8, static_cast<u8>(CommonColor::Count)> FromVer3EyeColorTable{ + 0x0, 0x2, 0x2, 0x2, 0x1, 0x3, 0x2, 0x3, 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x2, 0x2, 0x4, + 0x2, 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, + 0x2, 0x2, 0x2, 0x2, 0x0, 0x0, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x1, 0x0, 0x4, 0x4, + 0x4, 0x4, 0x4, 0x4, 0x4, 0x0, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, + 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x2, 0x2, + 0x3, 0x3, 0x3, 0x3, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, +}; + +constexpr std::array<u8, static_cast<u8>(CommonColor::Count)> FromVer3MouthlineColorTable{ + 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x3, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x1, 0x4, + 0x4, 0x4, 0x0, 0x1, 0x2, 0x3, 0x4, 0x4, 0x2, 0x3, 0x3, 0x4, 0x4, 0x4, 0x4, 0x1, 0x4, + 0x4, 0x2, 0x3, 0x3, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x3, 0x3, 0x3, 0x4, 0x4, 0x4, + 0x3, 0x3, 0x3, 0x3, 0x3, 0x4, 0x4, 0x4, 0x4, 0x4, 0x3, 0x3, 0x3, 0x3, 0x4, 0x4, 0x4, + 0x4, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x4, 0x4, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x4, 0x3, + 0x3, 0x3, 0x3, 0x3, 0x4, 0x0, 0x3, 0x3, 0x3, 0x3, 0x4, 0x3, 0x3, 0x3, 0x3, +}; + +constexpr std::array<u8, static_cast<u8>(CommonColor::Count)> FromVer3GlassColorTable{ + 0x0, 0x1, 0x1, 0x1, 0x5, 0x1, 0x1, 0x4, 0x0, 0x5, 0x1, 0x1, 0x3, 0x5, 0x1, 0x2, 0x3, + 0x4, 0x5, 0x4, 0x2, 0x2, 0x4, 0x4, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, + 0x2, 0x2, 0x2, 0x2, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, + 0x3, 0x3, 0x3, 0x3, 0x3, 0x0, 0x0, 0x0, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x0, 0x5, 0x5, + 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x1, 0x4, + 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, +}; + +constexpr std::array<u8, static_cast<u8>(GlassType::Count)> FromVer3GlassTypeTable{ + 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x1, + 0x2, 0x1, 0x3, 0x7, 0x7, 0x6, 0x7, 0x8, 0x7, 0x7, +}; + +constexpr std::array<u8, 8> Ver3FacelineColorTable{ + 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, +}; + +constexpr std::array<u8, 8> Ver3HairColorTable{ + 0x8, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, +}; + +constexpr std::array<u8, 6> Ver3EyeColorTable{ + 0x8, 0x9, 0xa, 0xb, 0xc, 0xd, +}; + +constexpr std::array<u8, 5> Ver3MouthColorTable{ + 0x13, 0x14, 0x15, 0x16, 0x17, +}; + +constexpr std::array<u8, 7> Ver3GlassColorTable{ + 0x8, 0xe, 0xf, 0x10, 0x11, 0x12, 0x0, +}; + +const std::array<u8, 62> EyeRotateLookup{ + 0x03, 0x04, 0x04, 0x04, 0x03, 0x04, 0x04, 0x04, 0x03, 0x04, 0x04, 0x04, 0x04, 0x03, 0x03, 0x04, + 0x04, 0x04, 0x03, 0x03, 0x04, 0x03, 0x04, 0x03, 0x03, 0x04, 0x03, 0x04, 0x04, 0x03, 0x04, 0x04, + 0x04, 0x03, 0x03, 0x03, 0x04, 0x04, 0x03, 0x03, 0x03, 0x04, 0x04, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x04, 0x04, 0x04, 0x04, 0x03, 0x04, 0x04, 0x03, 0x04, 0x04, +}; + +const std::array<u8, 24> EyebrowRotateLookup{ + 0x06, 0x06, 0x05, 0x07, 0x06, 0x07, 0x06, 0x07, 0x04, 0x07, 0x06, 0x08, + 0x05, 0x05, 0x06, 0x06, 0x07, 0x07, 0x06, 0x06, 0x05, 0x06, 0x07, 0x05, +}; + +const std::array<Service::Mii::DefaultMii, 2> BaseMii{ Service::Mii::DefaultMii{ .face_type = 0, .face_color = 0, @@ -51,11 +128,12 @@ const std::array<Service::Mii::DefaultMii, 8> DefaultMii{ .mole_y = 20, .height = 64, .weight = 64, - .gender = Gender::Male, + .gender = 0, .favorite_color = 0, - .region = 0, - .font_region = FontRegion::Standard, + .region_move = 0, + .font_region = 0, .type = 0, + .nickname = {u'n', u'o', u' ', u'n', u'a', u'm', u'e'}, }, Service::Mii::DefaultMii{ .face_type = 0, @@ -102,12 +180,16 @@ const std::array<Service::Mii::DefaultMii, 8> DefaultMii{ .mole_y = 20, .height = 64, .weight = 64, - .gender = Gender::Female, + .gender = 1, .favorite_color = 0, - .region = 0, - .font_region = FontRegion::Standard, + .region_move = 0, + .font_region = 0, .type = 0, + .nickname = {u'n', u'o', u' ', u'n', u'a', u'm', u'e'}, }, +}; + +const std::array<Service::Mii::DefaultMii, 6> DefaultMii{ Service::Mii::DefaultMii{ .face_type = 0, .face_color = 4, @@ -153,11 +235,12 @@ const std::array<Service::Mii::DefaultMii, 8> DefaultMii{ .mole_y = 20, .height = 64, .weight = 64, - .gender = Gender::Male, + .gender = 0, .favorite_color = 4, - .region = 0, - .font_region = FontRegion::Standard, + .region_move = 0, + .font_region = 0, .type = 0, + .nickname = {u'n', u'o', u' ', u'n', u'a', u'm', u'e'}, }, Service::Mii::DefaultMii{ .face_type = 0, @@ -204,11 +287,12 @@ const std::array<Service::Mii::DefaultMii, 8> DefaultMii{ .mole_y = 20, .height = 64, .weight = 64, - .gender = Gender::Male, + .gender = 0, .favorite_color = 5, - .region = 0, - .font_region = FontRegion::Standard, + .region_move = 0, + .font_region = 0, .type = 0, + .nickname = {u'n', u'o', u' ', u'n', u'a', u'm', u'e'}, }, Service::Mii::DefaultMii{ .face_type = 0, @@ -255,11 +339,12 @@ const std::array<Service::Mii::DefaultMii, 8> DefaultMii{ .mole_y = 20, .height = 64, .weight = 64, - .gender = Gender::Male, + .gender = 0, .favorite_color = 0, - .region = 0, - .font_region = FontRegion::Standard, + .region_move = 0, + .font_region = 0, .type = 0, + .nickname = {u'n', u'o', u' ', u'n', u'a', u'm', u'e'}, }, Service::Mii::DefaultMii{ .face_type = 0, @@ -306,11 +391,12 @@ const std::array<Service::Mii::DefaultMii, 8> DefaultMii{ .mole_y = 20, .height = 64, .weight = 64, - .gender = Gender::Female, + .gender = 1, .favorite_color = 2, - .region = 0, - .font_region = FontRegion::Standard, + .region_move = 0, + .font_region = 0, .type = 0, + .nickname = {u'n', u'o', u' ', u'n', u'a', u'm', u'e'}, }, Service::Mii::DefaultMii{ .face_type = 0, @@ -357,11 +443,12 @@ const std::array<Service::Mii::DefaultMii, 8> DefaultMii{ .mole_y = 20, .height = 64, .weight = 64, - .gender = Gender::Female, + .gender = 1, .favorite_color = 6, - .region = 0, - .font_region = FontRegion::Standard, + .region_move = 0, + .font_region = 0, .type = 0, + .nickname = {u'n', u'o', u' ', u'n', u'a', u'm', u'e'}, }, Service::Mii::DefaultMii{ .face_type = 0, @@ -408,176 +495,177 @@ const std::array<Service::Mii::DefaultMii, 8> DefaultMii{ .mole_y = 20, .height = 64, .weight = 64, - .gender = Gender::Female, + .gender = 1, .favorite_color = 7, - .region = 0, - .font_region = FontRegion::Standard, + .region_move = 0, + .font_region = 0, .type = 0, + .nickname = {u'n', u'o', u' ', u'n', u'a', u'm', u'e'}, }, }; -const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiFaceline{ - Service::Mii::RandomMiiData4{ - .gender = Gender::Male, - .age = Age::Young, - .race = Race::Black, +const std::array<RandomMiiData4, 18> RandomMiiFaceline{ + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Male), + .age = static_cast<u32>(Age::Young), + .race = static_cast<u32>(Race::Black), .values_count = 10, .values = {0, 0, 1, 1, 2, 3, 4, 5, 9, 9}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Male, - .age = Age::Normal, - .race = Race::Black, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Male), + .age = static_cast<u32>(Age::Normal), + .race = static_cast<u32>(Race::Black), .values_count = 10, .values = {0, 0, 1, 1, 2, 3, 4, 5, 9, 9}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Male, - .age = Age::Old, - .race = Race::Black, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Male), + .age = static_cast<u32>(Age::Old), + .race = static_cast<u32>(Race::Black), .values_count = 10, .values = {0, 0, 1, 1, 2, 3, 4, 5, 9, 9}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Male, - .age = Age::Young, - .race = Race::White, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Male), + .age = static_cast<u32>(Age::Young), + .race = static_cast<u32>(Race::White), .values_count = 12, .values = {0, 0, 1, 2, 2, 3, 4, 5, 6, 7, 10, 11}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Male, - .age = Age::Normal, - .race = Race::White, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Male), + .age = static_cast<u32>(Age::Normal), + .race = static_cast<u32>(Race::White), .values_count = 13, .values = {0, 1, 2, 2, 3, 4, 5, 6, 6, 7, 7, 10, 11}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Male, - .age = Age::Old, - .race = Race::White, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Male), + .age = static_cast<u32>(Age::Old), + .race = static_cast<u32>(Race::White), .values_count = 12, .values = {0, 0, 1, 2, 2, 3, 4, 5, 6, 7, 10, 11}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Male, - .age = Age::Young, - .race = Race::Asian, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Male), + .age = static_cast<u32>(Age::Young), + .race = static_cast<u32>(Race::Asian), .values_count = 12, .values = {0, 0, 1, 2, 2, 3, 4, 5, 6, 7, 10, 11}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Male, - .age = Age::Normal, - .race = Race::Asian, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Male), + .age = static_cast<u32>(Age::Normal), + .race = static_cast<u32>(Race::Asian), .values_count = 13, .values = {0, 1, 2, 2, 3, 4, 5, 6, 6, 7, 7, 10, 11}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Male, - .age = Age::Old, - .race = Race::Asian, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Male), + .age = static_cast<u32>(Age::Old), + .race = static_cast<u32>(Race::Asian), .values_count = 12, .values = {0, 0, 1, 2, 2, 3, 4, 5, 6, 7, 10, 11}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Female, - .age = Age::Young, - .race = Race::Black, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Female), + .age = static_cast<u32>(Age::Young), + .race = static_cast<u32>(Race::Black), .values_count = 10, .values = {0, 0, 1, 1, 2, 3, 4, 5, 9, 9}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Female, - .age = Age::Normal, - .race = Race::Black, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Female), + .age = static_cast<u32>(Age::Normal), + .race = static_cast<u32>(Race::Black), .values_count = 10, .values = {0, 0, 1, 1, 2, 3, 4, 5, 9, 9}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Female, - .age = Age::Old, - .race = Race::Black, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Female), + .age = static_cast<u32>(Age::Old), + .race = static_cast<u32>(Race::Black), .values_count = 10, .values = {0, 0, 1, 1, 2, 3, 4, 5, 9, 9}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Female, - .age = Age::Young, - .race = Race::White, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Female), + .age = static_cast<u32>(Age::Young), + .race = static_cast<u32>(Race::White), .values_count = 12, .values = {0, 0, 0, 1, 1, 1, 2, 3, 4, 5, 8, 10}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Female, - .age = Age::Normal, - .race = Race::White, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Female), + .age = static_cast<u32>(Age::Normal), + .race = static_cast<u32>(Race::White), .values_count = 12, .values = {0, 0, 0, 1, 1, 1, 2, 3, 4, 5, 8, 10}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Female, - .age = Age::Old, - .race = Race::White, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Female), + .age = static_cast<u32>(Age::Old), + .race = static_cast<u32>(Race::White), .values_count = 12, .values = {0, 0, 0, 1, 1, 1, 2, 3, 4, 5, 8, 10}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Female, - .age = Age::Young, - .race = Race::Asian, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Female), + .age = static_cast<u32>(Age::Young), + .race = static_cast<u32>(Race::Asian), .values_count = 12, .values = {0, 0, 0, 1, 1, 1, 2, 3, 4, 5, 8, 10}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Female, - .age = Age::Normal, - .race = Race::Asian, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Female), + .age = static_cast<u32>(Age::Normal), + .race = static_cast<u32>(Race::Asian), .values_count = 12, .values = {0, 0, 0, 1, 1, 1, 2, 3, 4, 5, 8, 10}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Female, - .age = Age::Old, - .race = Race::Asian, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Female), + .age = static_cast<u32>(Age::Old), + .race = static_cast<u32>(Race::Asian), .values_count = 12, .values = {0, 0, 0, 1, 1, 1, 2, 3, 4, 5, 8, 10}, }, }; -const std::array<Service::Mii::RandomMiiData3, 6> RandomMiiFacelineColor{ - Service::Mii::RandomMiiData3{ +const std::array<RandomMiiData3, 6> RandomMiiFacelineColor{ + RandomMiiData3{ .arg_1 = 0, .arg_2 = 0, .values_count = 10, .values = {2, 2, 4, 4, 4, 4, 5, 5, 5, 5}, }, - Service::Mii::RandomMiiData3{ + RandomMiiData3{ .arg_1 = 0, .arg_2 = 1, .values_count = 10, .values = {0, 0, 0, 0, 1, 1, 2, 3, 3, 3}, }, - Service::Mii::RandomMiiData3{ + RandomMiiData3{ .arg_1 = 0, .arg_2 = 2, .values_count = 10, .values = {0, 0, 1, 1, 1, 1, 1, 1, 1, 2}, }, - Service::Mii::RandomMiiData3{ + RandomMiiData3{ .arg_1 = 1, .arg_2 = 0, .values_count = 10, .values = {2, 2, 4, 4, 4, 4, 5, 5, 5, 5}, }, - Service::Mii::RandomMiiData3{ + RandomMiiData3{ .arg_1 = 1, .arg_2 = 1, .values_count = 10, .values = {0, 0, 0, 0, 0, 0, 0, 0, 1, 3}, }, - Service::Mii::RandomMiiData3{ + RandomMiiData3{ .arg_1 = 1, .arg_2 = 2, .values_count = 10, @@ -585,407 +673,407 @@ const std::array<Service::Mii::RandomMiiData3, 6> RandomMiiFacelineColor{ }, }; -const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiFacelineWrinkle{ - Service::Mii::RandomMiiData4{ - .gender = Gender::Male, - .age = Age::Young, - .race = Race::Black, +const std::array<RandomMiiData4, 18> RandomMiiFacelineWrinkle{ + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Male), + .age = static_cast<u32>(Age::Young), + .race = static_cast<u32>(Race::Black), .values_count = 20, .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Male, - .age = Age::Normal, - .race = Race::Black, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Male), + .age = static_cast<u32>(Age::Normal), + .race = static_cast<u32>(Race::Black), .values_count = 20, .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Male, - .age = Age::Old, - .race = Race::Black, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Male), + .age = static_cast<u32>(Age::Old), + .race = static_cast<u32>(Race::Black), .values_count = 20, .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 8}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Male, - .age = Age::Young, - .race = Race::White, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Male), + .age = static_cast<u32>(Age::Young), + .race = static_cast<u32>(Race::White), .values_count = 20, .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 4, 5, 6, 7, 8, 8, 9}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Male, - .age = Age::Normal, - .race = Race::White, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Male), + .age = static_cast<u32>(Age::Normal), + .race = static_cast<u32>(Race::White), .values_count = 20, .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 4, 5, 6, 7, 8, 8, 9}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Male, - .age = Age::Old, - .race = Race::White, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Male), + .age = static_cast<u32>(Age::Old), + .race = static_cast<u32>(Race::White), .values_count = 20, .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Male, - .age = Age::Young, - .race = Race::Asian, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Male), + .age = static_cast<u32>(Age::Young), + .race = static_cast<u32>(Race::Asian), .values_count = 20, .values = {9, 9, 10, 10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11, 11, 11, 11, 11}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Male, - .age = Age::Normal, - .race = Race::Asian, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Male), + .age = static_cast<u32>(Age::Normal), + .race = static_cast<u32>(Race::Asian), .values_count = 20, .values = {9, 9, 10, 10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11, 11, 11, 11, 11}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Male, - .age = Age::Old, - .race = Race::Asian, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Male), + .age = static_cast<u32>(Age::Old), + .race = static_cast<u32>(Race::Asian), .values_count = 20, .values = {9, 9, 10, 10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11, 11, 11, 11, 11}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Female, - .age = Age::Young, - .race = Race::Black, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Female), + .age = static_cast<u32>(Age::Young), + .race = static_cast<u32>(Race::Black), .values_count = 20, .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Female, - .age = Age::Normal, - .race = Race::Black, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Female), + .age = static_cast<u32>(Age::Normal), + .race = static_cast<u32>(Race::Black), .values_count = 20, .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Female, - .age = Age::Old, - .race = Race::Black, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Female), + .age = static_cast<u32>(Age::Old), + .race = static_cast<u32>(Race::Black), .values_count = 20, .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Female, - .age = Age::Young, - .race = Race::White, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Female), + .age = static_cast<u32>(Age::Young), + .race = static_cast<u32>(Race::White), .values_count = 20, .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 3, 4, 4, 8, 8}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Female, - .age = Age::Normal, - .race = Race::White, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Female), + .age = static_cast<u32>(Age::Normal), + .race = static_cast<u32>(Race::White), .values_count = 20, .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 3, 4, 4, 8, 8}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Female, - .age = Age::Old, - .race = Race::White, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Female), + .age = static_cast<u32>(Age::Old), + .race = static_cast<u32>(Race::White), .values_count = 20, .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 3, 4, 4}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Female, - .age = Age::Young, - .race = Race::Asian, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Female), + .age = static_cast<u32>(Age::Young), + .race = static_cast<u32>(Race::Asian), .values_count = 20, .values = {9, 9, 9, 9, 9, 9, 10, 10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Female, - .age = Age::Normal, - .race = Race::Asian, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Female), + .age = static_cast<u32>(Age::Normal), + .race = static_cast<u32>(Race::Asian), .values_count = 20, .values = {9, 9, 9, 9, 9, 9, 10, 10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Female, - .age = Age::Old, - .race = Race::Asian, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Female), + .age = static_cast<u32>(Age::Old), + .race = static_cast<u32>(Race::Asian), .values_count = 20, .values = {9, 9, 9, 9, 9, 9, 10, 10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11}, }, }; -const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiFacelineMakeup{ - Service::Mii::RandomMiiData4{ - .gender = Gender::Male, - .age = Age::Young, - .race = Race::Black, +const std::array<RandomMiiData4, 18> RandomMiiFacelineMakeup{ + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Male), + .age = static_cast<u32>(Age::Young), + .race = static_cast<u32>(Race::Black), .values_count = 20, .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Male, - .age = Age::Normal, - .race = Race::Black, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Male), + .age = static_cast<u32>(Age::Normal), + .race = static_cast<u32>(Race::Black), .values_count = 20, .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9, 9, 9}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Male, - .age = Age::Old, - .race = Race::Black, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Male), + .age = static_cast<u32>(Age::Old), + .race = static_cast<u32>(Race::Black), .values_count = 20, .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Male, - .age = Age::Young, - .race = Race::White, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Male), + .age = static_cast<u32>(Age::Young), + .race = static_cast<u32>(Race::White), .values_count = 20, .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9, 9}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Male, - .age = Age::Normal, - .race = Race::White, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Male), + .age = static_cast<u32>(Age::Normal), + .race = static_cast<u32>(Race::White), .values_count = 20, .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9, 9}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Male, - .age = Age::Old, - .race = Race::White, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Male), + .age = static_cast<u32>(Age::Old), + .race = static_cast<u32>(Race::White), .values_count = 20, .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9, 9}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Male, - .age = Age::Young, - .race = Race::Asian, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Male), + .age = static_cast<u32>(Age::Young), + .race = static_cast<u32>(Race::Asian), .values_count = 20, .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9, 9}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Male, - .age = Age::Normal, - .race = Race::Asian, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Male), + .age = static_cast<u32>(Age::Normal), + .race = static_cast<u32>(Race::Asian), .values_count = 20, .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9, 9}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Male, - .age = Age::Old, - .race = Race::Asian, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Male), + .age = static_cast<u32>(Age::Old), + .race = static_cast<u32>(Race::Asian), .values_count = 20, .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9, 9}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Female, - .age = Age::Young, - .race = Race::Black, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Female), + .age = static_cast<u32>(Age::Young), + .race = static_cast<u32>(Race::Black), .values_count = 20, .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 2}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Female, - .age = Age::Normal, - .race = Race::Black, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Female), + .age = static_cast<u32>(Age::Normal), + .race = static_cast<u32>(Race::Black), .values_count = 20, .values = {0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 9, 9}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Female, - .age = Age::Old, - .race = Race::Black, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Female), + .age = static_cast<u32>(Age::Old), + .race = static_cast<u32>(Race::Black), .values_count = 20, .values = {0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 9, 9}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Female, - .age = Age::Young, - .race = Race::White, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Female), + .age = static_cast<u32>(Age::Young), + .race = static_cast<u32>(Race::White), .values_count = 20, .values = {0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 3, 4, 5, 5, 6, 7, 8, 9}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Female, - .age = Age::Normal, - .race = Race::White, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Female), + .age = static_cast<u32>(Age::Normal), + .race = static_cast<u32>(Race::White), .values_count = 20, .values = {0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Female, - .age = Age::Old, - .race = Race::White, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Female), + .age = static_cast<u32>(Age::Old), + .race = static_cast<u32>(Race::White), .values_count = 20, .values = {0, 0, 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 6, 7, 8, 9, 9}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Female, - .age = Age::Young, - .race = Race::Asian, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Female), + .age = static_cast<u32>(Age::Young), + .race = static_cast<u32>(Race::Asian), .values_count = 20, .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Female, - .age = Age::Normal, - .race = Race::Asian, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Female), + .age = static_cast<u32>(Age::Normal), + .race = static_cast<u32>(Race::Asian), .values_count = 20, .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Female, - .age = Age::Old, - .race = Race::Asian, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Female), + .age = static_cast<u32>(Age::Old), + .race = static_cast<u32>(Race::Asian), .values_count = 20, .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, }, }; -const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiHairType{ - Service::Mii::RandomMiiData4{ - .gender = Gender::Male, - .age = Age::Young, - .race = Race::Black, +const std::array<RandomMiiData4, 18> RandomMiiHairType{ + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Male), + .age = static_cast<u32>(Age::Young), + .race = static_cast<u32>(Race::Black), .values_count = 30, .values = {13, 23, 30, 31, 32, 33, 34, 35, 36, 37, 38, 40, 43, 44, 45, 47, 48, 49, 50, 51, 52, 54, 56, 57, 64, 66, 75, 76, 86, 89}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Male, - .age = Age::Normal, - .race = Race::Black, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Male), + .age = static_cast<u32>(Age::Normal), + .race = static_cast<u32>(Race::Black), .values_count = 31, .values = {13, 23, 30, 31, 32, 33, 34, 35, 36, 37, 38, 40, 43, 44, 45, 47, 48, 49, 50, 51, 52, 54, 56, 57, 64, 66, 73, 75, 81, 86, 87}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Male, - .age = Age::Old, - .race = Race::Black, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Male), + .age = static_cast<u32>(Age::Old), + .race = static_cast<u32>(Race::Black), .values_count = 31, .values = {13, 23, 30, 31, 32, 33, 34, 35, 36, 37, 38, 40, 43, 44, 45, 47, 48, 49, 50, 51, 52, 54, 56, 57, 64, 66, 73, 75, 81, 86, 87}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Male, - .age = Age::Young, - .race = Race::White, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Male), + .age = static_cast<u32>(Age::Young), + .race = static_cast<u32>(Race::White), .values_count = 38, .values = {13, 23, 30, 31, 32, 33, 34, 36, 37, 38, 40, 42, 43, 44, 45, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 58, 59, 60, 64, 65, 66, 67, 68, 70, 75, 76, 86, 89}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Male, - .age = Age::Normal, - .race = Race::White, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Male), + .age = static_cast<u32>(Age::Normal), + .race = static_cast<u32>(Race::White), .values_count = 39, .values = {13, 23, 30, 31, 32, 33, 34, 36, 37, 38, 39, 40, 43, 44, 45, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 58, 59, 60, 64, 65, 66, 67, 68, 70, 73, 75, 81, 86, 87}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Male, - .age = Age::Old, - .race = Race::White, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Male), + .age = static_cast<u32>(Age::Old), + .race = static_cast<u32>(Race::White), .values_count = 39, .values = {13, 23, 30, 31, 32, 33, 34, 36, 37, 38, 39, 40, 43, 44, 45, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 58, 59, 60, 64, 65, 66, 67, 68, 70, 73, 75, 81, 86, 87}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Male, - .age = Age::Young, - .race = Race::Asian, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Male), + .age = static_cast<u32>(Age::Young), + .race = static_cast<u32>(Race::Asian), .values_count = 18, .values = {13, 23, 30, 36, 37, 41, 45, 47, 51, 53, 54, 55, 58, 59, 65, 67, 86, 88}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Male, - .age = Age::Normal, - .race = Race::Asian, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Male), + .age = static_cast<u32>(Age::Normal), + .race = static_cast<u32>(Race::Asian), .values_count = 19, .values = {13, 23, 30, 36, 37, 39, 41, 45, 47, 51, 53, 54, 55, 58, 59, 65, 67, 86, 88}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Male, - .age = Age::Old, - .race = Race::Asian, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Male), + .age = static_cast<u32>(Age::Old), + .race = static_cast<u32>(Race::Asian), .values_count = 19, .values = {13, 23, 30, 36, 37, 39, 41, 45, 47, 51, 53, 54, 55, 58, 59, 65, 67, 86, 88}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Female, - .age = Age::Young, - .race = Race::Black, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Female), + .age = static_cast<u32>(Age::Young), + .race = static_cast<u32>(Race::Black), .values_count = 39, .values = {0, 1, 2, 3, 4, 5, 6, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 24, 25, 26, 28, 46, 50, 61, 62, 63, 64, 69, 76, 77, 79, 80, 83, 85}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Female, - .age = Age::Normal, - .race = Race::Black, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Female), + .age = static_cast<u32>(Age::Normal), + .race = static_cast<u32>(Race::Black), .values_count = 42, .values = {0, 1, 2, 3, 4, 5, 6, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 24, 25, 26, 28, 46, 50, 61, 62, 63, 64, 69, 72, 74, 77, 78, 82, 83, 84, 85, 87}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Female, - .age = Age::Old, - .race = Race::Black, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Female), + .age = static_cast<u32>(Age::Old), + .race = static_cast<u32>(Race::Black), .values_count = 42, .values = {0, 1, 2, 3, 4, 5, 6, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 24, 25, 26, 28, 46, 50, 61, 62, 63, 64, 69, 72, 74, 77, 78, 82, 83, 84, 85, 87}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Female, - .age = Age::Young, - .race = Race::White, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Female), + .age = static_cast<u32>(Age::Young), + .race = static_cast<u32>(Race::White), .values_count = 44, .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 24, 25, 26, 27, 29, 42, 50, 58, 60, 62, 63, 64, 69, 71, 76, 79, 80, 81, 82, 83, 86}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Female, - .age = Age::Normal, - .race = Race::White, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Female), + .age = static_cast<u32>(Age::Normal), + .race = static_cast<u32>(Race::White), .values_count = 44, .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 24, 25, 26, 27, 29, 50, 58, 60, 62, 63, 64, 69, 71, 72, 74, 79, 81, 82, 83, 84, 85}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Female, - .age = Age::Old, - .race = Race::White, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Female), + .age = static_cast<u32>(Age::Old), + .race = static_cast<u32>(Race::White), .values_count = 44, .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 24, 25, 26, 27, 29, 50, 58, 60, 62, 63, 64, 69, 71, 72, 74, 79, 81, 82, 83, 84, 85}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Female, - .age = Age::Young, - .race = Race::Asian, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Female), + .age = static_cast<u32>(Age::Young), + .race = static_cast<u32>(Race::Asian), .values_count = 24, .values = {0, 1, 2, 3, 4, 5, 6, 10, 11, 12, 13, 14, 16, 17, 18, 20, 21, 24, 25, 58, 62, 69, 76, 83}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Female, - .age = Age::Normal, - .race = Race::Asian, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Female), + .age = static_cast<u32>(Age::Normal), + .race = static_cast<u32>(Race::Asian), .values_count = 27, .values = {0, 1, 2, 3, 4, 5, 6, 10, 11, 12, 13, 14, 16, 17, 18, 20, 21, 24, 25, 58, 62, 69, 74, 76, 81, 83, 85}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Female, - .age = Age::Old, - .race = Race::Asian, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Female), + .age = static_cast<u32>(Age::Old), + .race = static_cast<u32>(Race::Asian), .values_count = 27, .values = {0, 1, 2, 3, 4, 5, 6, 10, 11, 12, 13, 14, 16, 17, 18, 20, 21, 24, 25, 58, 62, 69, 74, 76, 81, 83, 85}, @@ -993,55 +1081,55 @@ const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiHairType{ }; const std::array<RandomMiiData3, 9> RandomMiiHairColor{ - Service::Mii::RandomMiiData3{ + RandomMiiData3{ .arg_1 = 0, .arg_2 = 0, .values_count = 20, .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, }, - Service::Mii::RandomMiiData3{ + RandomMiiData3{ .arg_1 = 0, .arg_2 = 1, .values_count = 20, .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, }, - Service::Mii::RandomMiiData3{ + RandomMiiData3{ .arg_1 = 0, .arg_2 = 2, .values_count = 20, .values = {0, 0, 0, 0, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4}, }, - Service::Mii::RandomMiiData3{ + RandomMiiData3{ .arg_1 = 1, .arg_2 = 0, .values_count = 20, .values = {2, 3, 3, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7}, }, - Service::Mii::RandomMiiData3{ + RandomMiiData3{ .arg_1 = 1, .arg_2 = 1, .values_count = 20, .values = {2, 3, 3, 3, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7}, }, - Service::Mii::RandomMiiData3{ + RandomMiiData3{ .arg_1 = 1, .arg_2 = 2, .values_count = 20, .values = {2, 3, 3, 4, 4, 4, 4, 4, 4, 5, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7}, }, - Service::Mii::RandomMiiData3{ + RandomMiiData3{ .arg_1 = 2, .arg_2 = 0, .values_count = 20, .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1}, }, - Service::Mii::RandomMiiData3{ + RandomMiiData3{ .arg_1 = 2, .arg_2 = 1, .values_count = 20, .values = {0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 3, 3, 3, 3, 3}, }, - Service::Mii::RandomMiiData3{ + RandomMiiData3{ .arg_1 = 2, .arg_2 = 2, .values_count = 20, @@ -1049,598 +1137,642 @@ const std::array<RandomMiiData3, 9> RandomMiiHairColor{ }, }; -const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiEyeType{ - Service::Mii::RandomMiiData4{ - .gender = Gender::Male, - .age = Age::Young, - .race = Race::Black, +const std::array<RandomMiiData4, 18> RandomMiiEyeType{ + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Male), + .age = static_cast<u32>(Age::Young), + .race = static_cast<u32>(Race::Black), .values_count = 26, .values = {2, 3, 5, 7, 8, 9, 11, 12, 13, 15, 16, 18, 27, 29, 32, 34, 36, 38, 39, 41, 43, 47, 49, 51, 53, 57}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Male, - .age = Age::Normal, - .race = Race::Black, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Male), + .age = static_cast<u32>(Age::Normal), + .race = static_cast<u32>(Race::Black), .values_count = 26, .values = {2, 3, 5, 7, 8, 9, 11, 12, 13, 15, 16, 18, 27, 29, 32, 34, 36, 38, 39, 41, 43, 47, 49, 51, 53, 57}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Male, - .age = Age::Old, - .race = Race::Black, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Male), + .age = static_cast<u32>(Age::Old), + .race = static_cast<u32>(Race::Black), .values_count = 27, .values = {2, 3, 5, 7, 8, 9, 11, 12, 13, 15, 16, 18, 26, 27, 29, 32, 34, 36, 38, 39, 41, 43, 47, 48, 49, 53, 57}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Male, - .age = Age::Young, - .race = Race::White, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Male), + .age = static_cast<u32>(Age::Young), + .race = static_cast<u32>(Race::White), .values_count = 35, .values = {2, 3, 5, 6, 7, 8, 9, 11, 12, 13, 15, 16, 17, 18, 21, 22, 27, 29, 31, 32, 34, 36, 37, 38, 39, 41, 43, 44, 47, 49, 51, 53, 55, 56, 57}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Male, - .age = Age::Normal, - .race = Race::White, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Male), + .age = static_cast<u32>(Age::Normal), + .race = static_cast<u32>(Race::White), .values_count = 35, .values = {2, 3, 5, 6, 7, 8, 9, 11, 12, 13, 15, 16, 17, 18, 21, 22, 27, 29, 31, 32, 34, 36, 37, 38, 39, 41, 43, 44, 47, 49, 51, 53, 55, 56, 57}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Male, - .age = Age::Old, - .race = Race::White, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Male), + .age = static_cast<u32>(Age::Old), + .race = static_cast<u32>(Race::White), .values_count = 35, .values = {2, 3, 5, 6, 7, 8, 9, 11, 12, 13, 15, 16, 18, 21, 22, 26, 27, 29, 31, 32, 34, 36, 37, 38, 39, 41, 43, 44, 47, 48, 49, 50, 53, 56, 57}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Male, - .age = Age::Young, - .race = Race::Asian, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Male), + .age = static_cast<u32>(Age::Young), + .race = static_cast<u32>(Race::Asian), .values_count = 30, .values = {2, 3, 5, 7, 8, 9, 11, 12, 13, 14, 15, 16, 17, 18, 21, 22, 31, 32, 34, 36, 37, 39, 41, 44, 49, 51, 53, 55, 56, 57}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Male, - .age = Age::Normal, - .race = Race::Asian, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Male), + .age = static_cast<u32>(Age::Normal), + .race = static_cast<u32>(Race::Asian), .values_count = 30, .values = {2, 3, 5, 7, 8, 9, 11, 12, 13, 14, 15, 16, 17, 18, 21, 22, 31, 32, 34, 36, 37, 39, 41, 44, 49, 51, 53, 55, 56, 57}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Male, - .age = Age::Old, - .race = Race::Asian, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Male), + .age = static_cast<u32>(Age::Old), + .race = static_cast<u32>(Race::Asian), .values_count = 30, .values = {2, 3, 5, 7, 8, 9, 11, 12, 13, 14, 15, 16, 18, 21, 22, 26, 31, 32, 34, 36, 37, 39, 41, 44, 48, 49, 50, 51, 53, 57}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Female, - .age = Age::Young, - .race = Race::Black, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Female), + .age = static_cast<u32>(Age::Young), + .race = static_cast<u32>(Race::Black), .values_count = 39, .values = {0, 1, 2, 4, 5, 7, 8, 9, 10, 11, 12, 13, 15, 16, 18, 19, 23, 24, 25, 27, 28, 29, 32, 33, 34, 35, 38, 39, 40, 41, 42, 45, 46, 47, 48, 53, 54, 57, 59}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Female, - .age = Age::Normal, - .race = Race::Black, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Female), + .age = static_cast<u32>(Age::Normal), + .race = static_cast<u32>(Race::Black), .values_count = 39, .values = {0, 1, 2, 4, 5, 7, 8, 9, 10, 11, 12, 13, 15, 16, 18, 19, 23, 24, 25, 27, 28, 29, 32, 33, 34, 35, 38, 39, 40, 41, 42, 45, 46, 47, 48, 53, 54, 57, 59}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Female, - .age = Age::Old, - .race = Race::Black, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Female), + .age = static_cast<u32>(Age::Old), + .race = static_cast<u32>(Race::Black), .values_count = 40, .values = {0, 1, 2, 4, 5, 7, 8, 9, 10, 11, 12, 13, 15, 16, 18, 19, 23, 24, 25, 26, 27, 28, 29, 32, 33, 34, 35, 38, 39, 40, 41, 42, 45, 46, 47, 48, 53, 54, 57, 59}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Female, - .age = Age::Young, - .race = Race::White, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Female), + .age = static_cast<u32>(Age::Young), + .race = static_cast<u32>(Race::White), .values_count = 46, .values = {0, 1, 2, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 15, 16, 17, 18, 19, 20, 21, 23, 24, 25, 27, 28, 29, 30, 32, 33, 34, 35, 37, 38, 39, 40, 41, 42, 45, 46, 47, 48, 53, 54, 57, 58, 59}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Female, - .age = Age::Normal, - .race = Race::White, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Female), + .age = static_cast<u32>(Age::Normal), + .race = static_cast<u32>(Race::White), .values_count = 46, .values = {0, 1, 2, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 15, 16, 17, 18, 19, 20, 21, 23, 24, 25, 27, 28, 29, 30, 32, 33, 34, 35, 37, 38, 39, 40, 41, 42, 45, 46, 47, 48, 53, 54, 57, 58, 59}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Female, - .age = Age::Old, - .race = Race::White, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Female), + .age = static_cast<u32>(Age::Old), + .race = static_cast<u32>(Race::White), .values_count = 46, .values = {0, 1, 2, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 15, 16, 18, 19, 20, 21, 23, 24, 25, 26, 27, 28, 29, 30, 32, 33, 34, 35, 37, 38, 39, 40, 41, 42, 45, 46, 47, 48, 53, 54, 57, 58, 59}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Female, - .age = Age::Young, - .race = Race::Asian, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Female), + .age = static_cast<u32>(Age::Young), + .race = static_cast<u32>(Race::Asian), .values_count = 34, .values = {0, 1, 2, 4, 5, 7, 8, 9, 10, 11, 12, 13, 15, 16, 18, 19, 23, 24, 25, 27, 28, 29, 32, 33, 34, 35, 38, 39, 40, 41, 42, 45, 46, 47}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Female, - .age = Age::Normal, - .race = Race::Asian, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Female), + .age = static_cast<u32>(Age::Normal), + .race = static_cast<u32>(Race::Asian), .values_count = 34, .values = {0, 1, 2, 4, 5, 7, 8, 9, 10, 11, 12, 13, 15, 16, 18, 19, 23, 24, 25, 27, 28, 29, 32, 33, 34, 35, 38, 39, 40, 41, 42, 45, 46, 47}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Female, - .age = Age::Old, - .race = Race::Asian, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Female), + .age = static_cast<u32>(Age::Old), + .race = static_cast<u32>(Race::Asian), .values_count = 35, .values = {0, 1, 2, 4, 5, 7, 8, 9, 10, 11, 12, 13, 15, 16, 18, 19, 23, 24, 25, 26, 27, 28, 29, 32, 33, 34, 35, 38, 39, 40, 41, 42, 45, 46, 47}, }, }; -const std::array<Service::Mii::RandomMiiData2, 3> RandomMiiEyeColor{ - Service::Mii::RandomMiiData2{ +const std::array<RandomMiiData2, 3> RandomMiiEyeColor{ + RandomMiiData2{ .arg_1 = 0, .values_count = 10, .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 1}, }, - Service::Mii::RandomMiiData2{ + RandomMiiData2{ .arg_1 = 1, .values_count = 10, .values = {0, 1, 1, 2, 3, 3, 4, 4, 4, 5}, }, - Service::Mii::RandomMiiData2{ + RandomMiiData2{ .arg_1 = 2, .values_count = 10, .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 1}, }, }; -const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiEyebrowType{ - Service::Mii::RandomMiiData4{ - .gender = Gender::Male, - .age = Age::Young, - .race = Race::Black, +const std::array<RandomMiiData4, 18> RandomMiiEyebrowType{ + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Male), + .age = static_cast<u32>(Age::Young), + .race = static_cast<u32>(Race::Black), .values_count = 18, .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 17, 18, 20}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Male, - .age = Age::Normal, - .race = Race::Black, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Male), + .age = static_cast<u32>(Age::Normal), + .race = static_cast<u32>(Race::Black), .values_count = 18, .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 17, 18, 20}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Male, - .age = Age::Old, - .race = Race::Black, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Male), + .age = static_cast<u32>(Age::Old), + .race = static_cast<u32>(Race::Black), .values_count = 18, .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 17, 18, 20}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Male, - .age = Age::Young, - .race = Race::White, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Male), + .age = static_cast<u32>(Age::Young), + .race = static_cast<u32>(Race::White), .values_count = 23, .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Male, - .age = Age::Normal, - .race = Race::White, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Male), + .age = static_cast<u32>(Age::Normal), + .race = static_cast<u32>(Race::White), .values_count = 23, .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Male, - .age = Age::Old, - .race = Race::White, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Male), + .age = static_cast<u32>(Age::Old), + .race = static_cast<u32>(Race::White), .values_count = 23, .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Male, - .age = Age::Young, - .race = Race::Asian, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Male), + .age = static_cast<u32>(Age::Young), + .race = static_cast<u32>(Race::Asian), .values_count = 21, .values = {0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 20, 21, 22}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Male, - .age = Age::Normal, - .race = Race::Asian, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Male), + .age = static_cast<u32>(Age::Normal), + .race = static_cast<u32>(Race::Asian), .values_count = 21, .values = {0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 20, 21, 22}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Male, - .age = Age::Old, - .race = Race::Asian, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Male), + .age = static_cast<u32>(Age::Old), + .race = static_cast<u32>(Race::Asian), .values_count = 21, .values = {0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 20, 21, 22}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Female, - .age = Age::Young, - .race = Race::Black, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Female), + .age = static_cast<u32>(Age::Young), + .race = static_cast<u32>(Race::Black), .values_count = 9, .values = {0, 1, 3, 7, 8, 9, 10, 11, 13}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Female, - .age = Age::Normal, - .race = Race::Black, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Female), + .age = static_cast<u32>(Age::Normal), + .race = static_cast<u32>(Race::Black), .values_count = 9, .values = {0, 1, 3, 7, 8, 9, 10, 11, 13}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Female, - .age = Age::Old, - .race = Race::Black, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Female), + .age = static_cast<u32>(Age::Old), + .race = static_cast<u32>(Race::Black), .values_count = 9, .values = {0, 1, 3, 7, 8, 9, 10, 11, 13}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Female, - .age = Age::Young, - .race = Race::White, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Female), + .age = static_cast<u32>(Age::Young), + .race = static_cast<u32>(Race::White), .values_count = 11, .values = {0, 1, 3, 7, 8, 9, 10, 11, 13, 15, 19}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Female, - .age = Age::Normal, - .race = Race::White, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Female), + .age = static_cast<u32>(Age::Normal), + .race = static_cast<u32>(Race::White), .values_count = 11, .values = {0, 1, 3, 7, 8, 9, 10, 11, 13, 15, 19}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Female, - .age = Age::Old, - .race = Race::White, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Female), + .age = static_cast<u32>(Age::Old), + .race = static_cast<u32>(Race::White), .values_count = 11, .values = {0, 1, 3, 7, 8, 9, 10, 11, 13, 15, 19}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Female, - .age = Age::Young, - .race = Race::Asian, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Female), + .age = static_cast<u32>(Age::Young), + .race = static_cast<u32>(Race::Asian), .values_count = 9, .values = {0, 3, 7, 8, 9, 10, 11, 13, 15}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Female, - .age = Age::Normal, - .race = Race::Asian, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Female), + .age = static_cast<u32>(Age::Normal), + .race = static_cast<u32>(Race::Asian), .values_count = 9, .values = {0, 3, 7, 8, 9, 10, 11, 13, 15}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Female, - .age = Age::Old, - .race = Race::Asian, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Female), + .age = static_cast<u32>(Age::Old), + .race = static_cast<u32>(Race::Asian), .values_count = 9, .values = {0, 3, 7, 8, 9, 10, 11, 13, 15}, }, }; -const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiNoseType{ - Service::Mii::RandomMiiData4{ - .gender = Gender::Male, - .age = Age::Young, - .race = Race::Black, +const std::array<RandomMiiData4, 18> RandomMiiNoseType{ + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Male), + .age = static_cast<u32>(Age::Young), + .race = static_cast<u32>(Race::Black), .values_count = 11, .values = {0, 1, 2, 3, 4, 5, 7, 8, 10, 13, 14}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Male, - .age = Age::Normal, - .race = Race::Black, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Male), + .age = static_cast<u32>(Age::Normal), + .race = static_cast<u32>(Race::Black), .values_count = 11, .values = {0, 1, 2, 3, 4, 5, 7, 8, 10, 13, 14}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Male, - .age = Age::Old, - .race = Race::Black, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Male), + .age = static_cast<u32>(Age::Old), + .race = static_cast<u32>(Race::Black), .values_count = 11, .values = {0, 1, 2, 3, 4, 5, 7, 8, 10, 13, 14}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Male, - .age = Age::Young, - .race = Race::White, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Male), + .age = static_cast<u32>(Age::Young), + .race = static_cast<u32>(Race::White), .values_count = 18, .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Male, - .age = Age::Normal, - .race = Race::White, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Male), + .age = static_cast<u32>(Age::Normal), + .race = static_cast<u32>(Race::White), .values_count = 18, .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Male, - .age = Age::Old, - .race = Race::White, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Male), + .age = static_cast<u32>(Age::Old), + .race = static_cast<u32>(Race::White), .values_count = 15, .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 12, 13, 14, 16}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Male, - .age = Age::Young, - .race = Race::Asian, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Male), + .age = static_cast<u32>(Age::Young), + .race = static_cast<u32>(Race::Asian), .values_count = 18, .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Male, - .age = Age::Normal, - .race = Race::Asian, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Male), + .age = static_cast<u32>(Age::Normal), + .race = static_cast<u32>(Race::Asian), .values_count = 18, .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Male, - .age = Age::Old, - .race = Race::Asian, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Male), + .age = static_cast<u32>(Age::Old), + .race = static_cast<u32>(Race::Asian), .values_count = 15, .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 12, 13, 14, 16}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Female, - .age = Age::Young, - .race = Race::Black, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Female), + .age = static_cast<u32>(Age::Young), + .race = static_cast<u32>(Race::Black), .values_count = 8, .values = {0, 1, 3, 4, 8, 10, 13, 14}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Female, - .age = Age::Normal, - .race = Race::Black, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Female), + .age = static_cast<u32>(Age::Normal), + .race = static_cast<u32>(Race::Black), .values_count = 8, .values = {0, 1, 3, 4, 8, 10, 13, 14}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Female, - .age = Age::Old, - .race = Race::Black, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Female), + .age = static_cast<u32>(Age::Old), + .race = static_cast<u32>(Race::Black), .values_count = 8, .values = {0, 1, 3, 4, 8, 10, 13, 14}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Female, - .age = Age::Young, - .race = Race::White, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Female), + .age = static_cast<u32>(Age::Young), + .race = static_cast<u32>(Race::White), .values_count = 12, .values = {0, 1, 3, 4, 6, 8, 9, 10, 11, 13, 14, 15}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Female, - .age = Age::Normal, - .race = Race::White, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Female), + .age = static_cast<u32>(Age::Normal), + .race = static_cast<u32>(Race::White), .values_count = 11, .values = {0, 1, 3, 4, 6, 8, 9, 10, 11, 13, 15}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Female, - .age = Age::Old, - .race = Race::White, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Female), + .age = static_cast<u32>(Age::Old), + .race = static_cast<u32>(Race::White), .values_count = 10, .values = {0, 1, 3, 4, 6, 8, 10, 11, 13, 14}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Female, - .age = Age::Young, - .race = Race::Asian, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Female), + .age = static_cast<u32>(Age::Young), + .race = static_cast<u32>(Race::Asian), .values_count = 12, .values = {0, 1, 3, 4, 6, 8, 9, 10, 11, 13, 14, 15}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Female, - .age = Age::Normal, - .race = Race::Asian, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Female), + .age = static_cast<u32>(Age::Normal), + .race = static_cast<u32>(Race::Asian), .values_count = 11, .values = {0, 1, 3, 4, 6, 8, 9, 10, 11, 13, 15}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Female, - .age = Age::Old, - .race = Race::Asian, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Female), + .age = static_cast<u32>(Age::Old), + .race = static_cast<u32>(Race::Asian), .values_count = 10, .values = {0, 1, 3, 4, 6, 8, 10, 11, 13, 14}, }, }; -const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiMouthType{ - Service::Mii::RandomMiiData4{ - .gender = Gender::Male, - .age = Age::Young, - .race = Race::Black, +const std::array<RandomMiiData4, 18> RandomMiiMouthType{ + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Male), + .age = static_cast<u32>(Age::Young), + .race = static_cast<u32>(Race::Black), .values_count = 25, .values = {0, 2, 3, 6, 7, 8, 9, 10, 12, 14, 15, 17, 18, 19, 21, 22, 23, 25, 26, 28, 30, 32, 33, 34, 35}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Male, - .age = Age::Normal, - .race = Race::Black, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Male), + .age = static_cast<u32>(Age::Normal), + .race = static_cast<u32>(Race::Black), .values_count = 27, .values = {0, 2, 3, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 17, 18, 19, 21, 22, 23, 25, 26, 28, 30, 32, 33, 34, 35}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Male, - .age = Age::Old, - .race = Race::Black, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Male), + .age = static_cast<u32>(Age::Old), + .race = static_cast<u32>(Race::Black), .values_count = 28, .values = {0, 2, 3, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 17, 18, 19, 21, 22, 23, 25, 26, 28, 30, 31, 32, 33, 34, 35}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Male, - .age = Age::Young, - .race = Race::White, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Male), + .age = static_cast<u32>(Age::Young), + .race = static_cast<u32>(Race::White), .values_count = 24, .values = {0, 2, 3, 6, 7, 8, 9, 10, 12, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 30, 31, 33, 34, 35}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Male, - .age = Age::Normal, - .race = Race::White, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Male), + .age = static_cast<u32>(Age::Normal), + .race = static_cast<u32>(Race::White), .values_count = 26, .values = {0, 2, 3, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 30, 31, 33, 34, 35}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Male, - .age = Age::Old, - .race = Race::White, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Male), + .age = static_cast<u32>(Age::Old), + .race = static_cast<u32>(Race::White), .values_count = 26, .values = {0, 2, 3, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 30, 31, 33, 34, 35}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Male, - .age = Age::Young, - .race = Race::Asian, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Male), + .age = static_cast<u32>(Age::Young), + .race = static_cast<u32>(Race::Asian), .values_count = 24, .values = {0, 2, 3, 6, 7, 8, 9, 10, 12, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 30, 31, 33, 34, 35}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Male, - .age = Age::Normal, - .race = Race::Asian, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Male), + .age = static_cast<u32>(Age::Normal), + .race = static_cast<u32>(Race::Asian), .values_count = 26, .values = {0, 2, 3, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 30, 31, 33, 34, 35}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Male, - .age = Age::Old, - .race = Race::Asian, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Male), + .age = static_cast<u32>(Age::Old), + .race = static_cast<u32>(Race::Asian), .values_count = 26, .values = {0, 2, 3, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 30, 31, 33, 34, 35}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Female, - .age = Age::Young, - .race = Race::Black, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Female), + .age = static_cast<u32>(Age::Young), + .race = static_cast<u32>(Race::Black), .values_count = 25, .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 14, 15, 17, 18, 19, 21, 22, 23, 25, 26, 30, 33, 34, 35}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Female, - .age = Age::Normal, - .race = Race::Black, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Female), + .age = static_cast<u32>(Age::Normal), + .race = static_cast<u32>(Race::Black), .values_count = 26, .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 13, 14, 15, 17, 18, 19, 21, 22, 23, 25, 26, 30, 33, 34, 35}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Female, - .age = Age::Old, - .race = Race::Black, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Female), + .age = static_cast<u32>(Age::Old), + .race = static_cast<u32>(Race::Black), .values_count = 26, .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 13, 14, 15, 17, 18, 19, 21, 22, 23, 25, 26, 30, 33, 34, 35}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Female, - .age = Age::Young, - .race = Race::White, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Female), + .age = static_cast<u32>(Age::Young), + .race = static_cast<u32>(Race::White), .values_count = 25, .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 14, 15, 17, 18, 19, 21, 22, 23, 24, 26, 27, 29, 33, 35}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Female, - .age = Age::Normal, - .race = Race::White, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Female), + .age = static_cast<u32>(Age::Normal), + .race = static_cast<u32>(Race::White), .values_count = 26, .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 13, 14, 15, 17, 18, 19, 21, 22, 23, 24, 26, 27, 29, 33, 35}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Female, - .age = Age::Old, - .race = Race::White, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Female), + .age = static_cast<u32>(Age::Old), + .race = static_cast<u32>(Race::White), .values_count = 25, .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 13, 14, 15, 17, 18, 19, 21, 22, 23, 24, 25, 29, 33, 35}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Female, - .age = Age::Young, - .race = Race::Asian, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Female), + .age = static_cast<u32>(Age::Young), + .race = static_cast<u32>(Race::Asian), .values_count = 24, .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 14, 15, 16, 17, 18, 19, 21, 22, 23, 25, 26, 29, 33}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Female, - .age = Age::Normal, - .race = Race::Asian, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Female), + .age = static_cast<u32>(Age::Normal), + .race = static_cast<u32>(Race::Asian), .values_count = 25, .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 13, 14, 15, 16, 17, 18, 19, 21, 22, 23, 25, 26, 29, 33}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Female, - .age = Age::Old, - .race = Race::Asian, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Female), + .age = static_cast<u32>(Age::Old), + .race = static_cast<u32>(Race::Asian), .values_count = 25, .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 13, 14, 15, 16, 17, 18, 19, 21, 22, 23, 25, 26, 29, 33}, }, }; -const std::array<Service::Mii::RandomMiiData2, 3> RandomMiiGlassType{ - Service::Mii::RandomMiiData2{ +const std::array<RandomMiiData2, 3> RandomMiiGlassType{ + RandomMiiData2{ .arg_1 = 0, - .values_count = 9, - .values = {90, 94, 96, 100, 0, 0, 0, 0, 0}, + .values_count = 4, + .values = {90, 94, 96, 100}, }, - Service::Mii::RandomMiiData2{ + RandomMiiData2{ .arg_1 = 1, - .values_count = 9, - .values = {83, 86, 90, 93, 94, 96, 98, 100, 0}, + .values_count = 8, + .values = {83, 86, 90, 93, 94, 96, 98, 100}, }, - Service::Mii::RandomMiiData2{ + RandomMiiData2{ .arg_1 = 2, - .values_count = 9, - .values = {78, 83, 0, 93, 0, 0, 98, 100, 0}, + .values_count = 8, + .values = {78, 83, 0, 93, 0, 0, 98, 100}, }, }; +u8 FromVer3GetFacelineColor(u8 color) { + return FromVer3FacelineColorTable[color]; +} + +u8 FromVer3GetHairColor(u8 color) { + return FromVer3HairColorTable[color]; +} + +u8 FromVer3GetEyeColor(u8 color) { + return FromVer3EyeColorTable[color]; +} + +u8 FromVer3GetMouthlineColor(u8 color) { + return FromVer3MouthlineColorTable[color]; +} + +u8 FromVer3GetGlassColor(u8 color) { + return FromVer3GlassColorTable[color]; +} + +u8 FromVer3GetGlassType(u8 type) { + return FromVer3GlassTypeTable[type]; +} + +FacelineColor GetFacelineColorFromVer3(u32 color) { + return static_cast<FacelineColor>(Ver3FacelineColorTable[color]); +} + +CommonColor GetHairColorFromVer3(u32 color) { + return static_cast<CommonColor>(Ver3HairColorTable[color]); +} + +CommonColor GetEyeColorFromVer3(u32 color) { + return static_cast<CommonColor>(Ver3EyeColorTable[color]); +} + +CommonColor GetMouthColorFromVer3(u32 color) { + return static_cast<CommonColor>(Ver3MouthColorTable[color]); +} + +CommonColor GetGlassColorFromVer3(u32 color) { + return static_cast<CommonColor>(Ver3GlassColorTable[color]); +} + } // namespace Service::Mii::RawData diff --git a/src/core/hle/service/mii/types/raw_data.h b/src/core/hle/service/mii/types/raw_data.h new file mode 100644 index 000000000..9a4cfa738 --- /dev/null +++ b/src/core/hle/service/mii/types/raw_data.h @@ -0,0 +1,73 @@ +// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <array> + +#include "core/hle/service/mii/mii_types.h" + +namespace Service::Mii::RawData { + +struct RandomMiiValues { + std::array<u8, 188> values{}; +}; +static_assert(sizeof(RandomMiiValues) == 0xbc, "RandomMiiValues has incorrect size."); + +struct RandomMiiData4 { + u32 gender{}; + u32 age{}; + u32 race{}; + u32 values_count{}; + std::array<u32, 47> values{}; +}; +static_assert(sizeof(RandomMiiData4) == 0xcc, "RandomMiiData4 has incorrect size."); + +struct RandomMiiData3 { + u32 arg_1; + u32 arg_2; + u32 values_count; + std::array<u32, 47> values{}; +}; +static_assert(sizeof(RandomMiiData3) == 0xc8, "RandomMiiData3 has incorrect size."); + +struct RandomMiiData2 { + u32 arg_1; + u32 values_count; + std::array<u32, 47> values{}; +}; +static_assert(sizeof(RandomMiiData2) == 0xc4, "RandomMiiData2 has incorrect size."); + +extern const std::array<Service::Mii::DefaultMii, 2> BaseMii; +extern const std::array<Service::Mii::DefaultMii, 6> DefaultMii; + +extern const std::array<u8, 62> EyeRotateLookup; +extern const std::array<u8, 24> EyebrowRotateLookup; + +extern const std::array<RandomMiiData4, 18> RandomMiiFaceline; +extern const std::array<RandomMiiData3, 6> RandomMiiFacelineColor; +extern const std::array<RandomMiiData4, 18> RandomMiiFacelineWrinkle; +extern const std::array<RandomMiiData4, 18> RandomMiiFacelineMakeup; +extern const std::array<RandomMiiData4, 18> RandomMiiHairType; +extern const std::array<RandomMiiData3, 9> RandomMiiHairColor; +extern const std::array<RandomMiiData4, 18> RandomMiiEyeType; +extern const std::array<RandomMiiData2, 3> RandomMiiEyeColor; +extern const std::array<RandomMiiData4, 18> RandomMiiEyebrowType; +extern const std::array<RandomMiiData4, 18> RandomMiiNoseType; +extern const std::array<RandomMiiData4, 18> RandomMiiMouthType; +extern const std::array<RandomMiiData2, 3> RandomMiiGlassType; + +u8 FromVer3GetFacelineColor(u8 color); +u8 FromVer3GetHairColor(u8 color); +u8 FromVer3GetEyeColor(u8 color); +u8 FromVer3GetMouthlineColor(u8 color); +u8 FromVer3GetGlassColor(u8 color); +u8 FromVer3GetGlassType(u8 type); + +FacelineColor GetFacelineColorFromVer3(u32 color); +CommonColor GetHairColorFromVer3(u32 color); +CommonColor GetEyeColorFromVer3(u32 color); +CommonColor GetMouthColorFromVer3(u32 color); +CommonColor GetGlassColorFromVer3(u32 color); + +} // namespace Service::Mii::RawData diff --git a/src/core/hle/service/mii/types/store_data.cpp b/src/core/hle/service/mii/types/store_data.cpp new file mode 100644 index 000000000..127221fdb --- /dev/null +++ b/src/core/hle/service/mii/types/store_data.cpp @@ -0,0 +1,676 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "core/hle/service/mii/mii_result.h" +#include "core/hle/service/mii/mii_util.h" +#include "core/hle/service/mii/types/raw_data.h" +#include "core/hle/service/mii/types/store_data.h" + +namespace Service::Mii { + +void StoreData::BuildDefault(u32 mii_index) { + const auto& default_mii = RawData::DefaultMii[mii_index]; + core_data.SetDefault(); + + core_data.SetFacelineType(static_cast<FacelineType>(default_mii.face_type)); + core_data.SetFacelineColor( + RawData::GetFacelineColorFromVer3(static_cast<u8>(default_mii.face_color))); + core_data.SetFacelineWrinkle(static_cast<FacelineWrinkle>(default_mii.face_wrinkle)); + core_data.SetFacelineMake(static_cast<FacelineMake>(default_mii.face_makeup)); + + core_data.SetHairType(static_cast<HairType>(default_mii.hair_type)); + core_data.SetHairColor(RawData::GetHairColorFromVer3(static_cast<u8>(default_mii.hair_color))); + core_data.SetHairFlip(static_cast<HairFlip>(default_mii.hair_flip)); + core_data.SetEyeType(static_cast<EyeType>(default_mii.eye_type)); + core_data.SetEyeColor(RawData::GetEyeColorFromVer3(static_cast<u8>(default_mii.eye_color))); + core_data.SetEyeScale(static_cast<u8>(default_mii.eye_scale)); + core_data.SetEyeAspect(static_cast<u8>(default_mii.eye_aspect)); + core_data.SetEyeRotate(static_cast<u8>(default_mii.eye_rotate)); + core_data.SetEyeX(static_cast<u8>(default_mii.eye_x)); + core_data.SetEyeY(static_cast<u8>(default_mii.eye_y)); + + core_data.SetEyebrowType(static_cast<EyebrowType>(default_mii.eyebrow_type)); + core_data.SetEyebrowColor( + RawData::GetHairColorFromVer3(static_cast<u8>(default_mii.eyebrow_color))); + core_data.SetEyebrowScale(static_cast<u8>(default_mii.eyebrow_scale)); + core_data.SetEyebrowAspect(static_cast<u8>(default_mii.eyebrow_aspect)); + core_data.SetEyebrowRotate(static_cast<u8>(default_mii.eyebrow_rotate)); + core_data.SetEyebrowX(static_cast<u8>(default_mii.eyebrow_x)); + core_data.SetEyebrowY(static_cast<u8>(default_mii.eyebrow_y - 3)); + + core_data.SetNoseType(static_cast<NoseType>(default_mii.nose_type)); + core_data.SetNoseScale(static_cast<u8>(default_mii.nose_scale)); + core_data.SetNoseY(static_cast<u8>(default_mii.nose_y)); + + core_data.SetMouthType(static_cast<MouthType>(default_mii.mouth_type)); + core_data.SetMouthColor( + RawData::GetMouthColorFromVer3(static_cast<u8>(default_mii.mouth_color))); + core_data.SetMouthScale(static_cast<u8>(default_mii.mouth_scale)); + core_data.SetMouthAspect(static_cast<u8>(default_mii.mouth_aspect)); + core_data.SetMouthY(static_cast<u8>(default_mii.mouth_y)); + + core_data.SetMustacheType(static_cast<MustacheType>(default_mii.mustache_type)); + core_data.SetBeardType(static_cast<BeardType>(default_mii.beard_type)); + core_data.SetBeardColor( + RawData::GetHairColorFromVer3(static_cast<u8>(default_mii.beard_color))); + core_data.SetMustacheScale(static_cast<u8>(default_mii.mustache_scale)); + core_data.SetMustacheY(static_cast<u8>(default_mii.mustache_y)); + + core_data.SetGlassType(static_cast<GlassType>(default_mii.glasses_type)); + core_data.SetGlassColor( + RawData::GetGlassColorFromVer3(static_cast<u8>(default_mii.glasses_color))); + core_data.SetGlassScale(static_cast<u8>(default_mii.glasses_scale)); + core_data.SetGlassY(static_cast<u8>(default_mii.glasses_y)); + + core_data.SetMoleType(static_cast<MoleType>(default_mii.mole_type)); + core_data.SetMoleScale(static_cast<u8>(default_mii.mole_scale)); + core_data.SetMoleX(static_cast<u8>(default_mii.mole_x)); + core_data.SetMoleY(static_cast<u8>(default_mii.mole_y)); + + core_data.SetHeight(static_cast<u8>(default_mii.height)); + core_data.SetBuild(static_cast<u8>(default_mii.weight)); + core_data.SetGender(static_cast<Gender>(default_mii.gender)); + core_data.SetFavoriteColor(static_cast<FavoriteColor>(default_mii.favorite_color)); + core_data.SetRegionMove(static_cast<u8>(default_mii.region_move)); + core_data.SetFontRegion(static_cast<FontRegion>(default_mii.font_region)); + core_data.SetType(static_cast<u8>(default_mii.type)); + core_data.SetNickname(default_mii.nickname); + + create_id = MiiUtil::MakeCreateId(); + SetChecksum(); +} + +void StoreData::BuildBase(Gender gender) { + const auto& default_mii = RawData::BaseMii[gender == Gender::Female ? 1 : 0]; + core_data.SetDefault(); + + core_data.SetFacelineType(static_cast<FacelineType>(default_mii.face_type)); + core_data.SetFacelineColor( + RawData::GetFacelineColorFromVer3(static_cast<u8>(default_mii.face_color))); + core_data.SetFacelineWrinkle(static_cast<FacelineWrinkle>(default_mii.face_wrinkle)); + core_data.SetFacelineMake(static_cast<FacelineMake>(default_mii.face_makeup)); + + core_data.SetHairType(static_cast<HairType>(default_mii.hair_type)); + core_data.SetHairColor(RawData::GetHairColorFromVer3(static_cast<u8>(default_mii.hair_color))); + core_data.SetHairFlip(static_cast<HairFlip>(default_mii.hair_flip)); + core_data.SetEyeType(static_cast<EyeType>(default_mii.eye_type)); + core_data.SetEyeColor(RawData::GetEyeColorFromVer3(static_cast<u8>(default_mii.eye_color))); + core_data.SetEyeScale(static_cast<u8>(default_mii.eye_scale)); + core_data.SetEyeAspect(static_cast<u8>(default_mii.eye_aspect)); + core_data.SetEyeRotate(static_cast<u8>(default_mii.eye_rotate)); + core_data.SetEyeX(static_cast<u8>(default_mii.eye_x)); + core_data.SetEyeY(static_cast<u8>(default_mii.eye_y)); + + core_data.SetEyebrowType(static_cast<EyebrowType>(default_mii.eyebrow_type)); + core_data.SetEyebrowColor( + RawData::GetHairColorFromVer3(static_cast<u8>(default_mii.eyebrow_color))); + core_data.SetEyebrowScale(static_cast<u8>(default_mii.eyebrow_scale)); + core_data.SetEyebrowAspect(static_cast<u8>(default_mii.eyebrow_aspect)); + core_data.SetEyebrowRotate(static_cast<u8>(default_mii.eyebrow_rotate)); + core_data.SetEyebrowX(static_cast<u8>(default_mii.eyebrow_x)); + core_data.SetEyebrowY(static_cast<u8>(default_mii.eyebrow_y - 3)); + + core_data.SetNoseType(static_cast<NoseType>(default_mii.nose_type)); + core_data.SetNoseScale(static_cast<u8>(default_mii.nose_scale)); + core_data.SetNoseY(static_cast<u8>(default_mii.nose_y)); + + core_data.SetMouthType(static_cast<MouthType>(default_mii.mouth_type)); + core_data.SetMouthColor( + RawData::GetMouthColorFromVer3(static_cast<u8>(default_mii.mouth_color))); + core_data.SetMouthScale(static_cast<u8>(default_mii.mouth_scale)); + core_data.SetMouthAspect(static_cast<u8>(default_mii.mouth_aspect)); + core_data.SetMouthY(static_cast<u8>(default_mii.mouth_y)); + + core_data.SetMustacheType(static_cast<MustacheType>(default_mii.mustache_type)); + core_data.SetBeardType(static_cast<BeardType>(default_mii.beard_type)); + core_data.SetBeardColor( + RawData::GetHairColorFromVer3(static_cast<u8>(default_mii.beard_color))); + core_data.SetMustacheScale(static_cast<u8>(default_mii.mustache_scale)); + core_data.SetMustacheY(static_cast<u8>(default_mii.mustache_y)); + + core_data.SetGlassType(static_cast<GlassType>(default_mii.glasses_type)); + core_data.SetGlassColor( + RawData::GetGlassColorFromVer3(static_cast<u8>(default_mii.glasses_color))); + core_data.SetGlassScale(static_cast<u8>(default_mii.glasses_scale)); + core_data.SetGlassY(static_cast<u8>(default_mii.glasses_y)); + + core_data.SetMoleType(static_cast<MoleType>(default_mii.mole_type)); + core_data.SetMoleScale(static_cast<u8>(default_mii.mole_scale)); + core_data.SetMoleX(static_cast<u8>(default_mii.mole_x)); + core_data.SetMoleY(static_cast<u8>(default_mii.mole_y)); + + core_data.SetHeight(static_cast<u8>(default_mii.height)); + core_data.SetBuild(static_cast<u8>(default_mii.weight)); + core_data.SetGender(static_cast<Gender>(default_mii.gender)); + core_data.SetFavoriteColor(static_cast<FavoriteColor>(default_mii.favorite_color)); + core_data.SetRegionMove(static_cast<u8>(default_mii.region_move)); + core_data.SetFontRegion(static_cast<FontRegion>(default_mii.font_region)); + core_data.SetType(static_cast<u8>(default_mii.type)); + core_data.SetNickname(default_mii.nickname); + + create_id = MiiUtil::MakeCreateId(); + SetChecksum(); +} + +void StoreData::BuildRandom(Age age, Gender gender, Race race) { + core_data.BuildRandom(age, gender, race); + create_id = MiiUtil::MakeCreateId(); + SetChecksum(); +} + +void StoreData::BuildWithCharInfo(const CharInfo& char_info) { + core_data.BuildFromCharInfo(char_info); + create_id = MiiUtil::MakeCreateId(); + SetChecksum(); +} + +void StoreData::BuildWithCoreData(const CoreData& in_core_data) { + core_data = in_core_data; + create_id = MiiUtil::MakeCreateId(); + SetChecksum(); +} + +Result StoreData::Restore() { + // TODO: Implement this + return ResultNotUpdated; +} + +ValidationResult StoreData::IsValid() const { + if (core_data.IsValid() != ValidationResult::NoErrors) { + return core_data.IsValid(); + } + if (data_crc != MiiUtil::CalculateCrc16(&core_data, sizeof(CoreData) + sizeof(Common::UUID))) { + return ValidationResult::InvalidChecksum; + } + const auto device_id = MiiUtil::GetDeviceId(); + if (device_crc != MiiUtil::CalculateDeviceCrc16(device_id, sizeof(StoreData))) { + return ValidationResult::InvalidChecksum; + } + return ValidationResult::NoErrors; +} + +bool StoreData::IsSpecial() const { + return GetType() == 1; +} + +void StoreData::SetFontRegion(FontRegion value) { + core_data.SetFontRegion(value); +} + +void StoreData::SetFavoriteColor(FavoriteColor value) { + core_data.SetFavoriteColor(value); +} + +void StoreData::SetGender(Gender value) { + core_data.SetGender(value); +} + +void StoreData::SetHeight(u8 value) { + core_data.SetHeight(value); +} + +void StoreData::SetBuild(u8 value) { + core_data.SetBuild(value); +} + +void StoreData::SetType(u8 value) { + core_data.SetType(value); +} + +void StoreData::SetRegionMove(u8 value) { + core_data.SetRegionMove(value); +} + +void StoreData::SetFacelineType(FacelineType value) { + core_data.SetFacelineType(value); +} + +void StoreData::SetFacelineColor(FacelineColor value) { + core_data.SetFacelineColor(value); +} + +void StoreData::SetFacelineWrinkle(FacelineWrinkle value) { + core_data.SetFacelineWrinkle(value); +} + +void StoreData::SetFacelineMake(FacelineMake value) { + core_data.SetFacelineMake(value); +} + +void StoreData::SetHairType(HairType value) { + core_data.SetHairType(value); +} + +void StoreData::SetHairColor(CommonColor value) { + core_data.SetHairColor(value); +} + +void StoreData::SetHairFlip(HairFlip value) { + core_data.SetHairFlip(value); +} + +void StoreData::SetEyeType(EyeType value) { + core_data.SetEyeType(value); +} + +void StoreData::SetEyeColor(CommonColor value) { + core_data.SetEyeColor(value); +} + +void StoreData::SetEyeScale(u8 value) { + core_data.SetEyeScale(value); +} + +void StoreData::SetEyeAspect(u8 value) { + core_data.SetEyeAspect(value); +} + +void StoreData::SetEyeRotate(u8 value) { + core_data.SetEyeRotate(value); +} + +void StoreData::SetEyeX(u8 value) { + core_data.SetEyeX(value); +} + +void StoreData::SetEyeY(u8 value) { + core_data.SetEyeY(value); +} + +void StoreData::SetEyebrowType(EyebrowType value) { + core_data.SetEyebrowType(value); +} + +void StoreData::SetEyebrowColor(CommonColor value) { + core_data.SetEyebrowColor(value); +} + +void StoreData::SetEyebrowScale(u8 value) { + core_data.SetEyebrowScale(value); +} + +void StoreData::SetEyebrowAspect(u8 value) { + core_data.SetEyebrowAspect(value); +} + +void StoreData::SetEyebrowRotate(u8 value) { + core_data.SetEyebrowRotate(value); +} + +void StoreData::SetEyebrowX(u8 value) { + core_data.SetEyebrowX(value); +} + +void StoreData::SetEyebrowY(u8 value) { + core_data.SetEyebrowY(value); +} + +void StoreData::SetNoseType(NoseType value) { + core_data.SetNoseType(value); +} + +void StoreData::SetNoseScale(u8 value) { + core_data.SetNoseScale(value); +} + +void StoreData::SetNoseY(u8 value) { + core_data.SetNoseY(value); +} + +void StoreData::SetMouthType(MouthType value) { + core_data.SetMouthType(value); +} + +void StoreData::SetMouthColor(CommonColor value) { + core_data.SetMouthColor(value); +} + +void StoreData::SetMouthScale(u8 value) { + core_data.SetMouthScale(value); +} + +void StoreData::SetMouthAspect(u8 value) { + core_data.SetMouthAspect(value); +} + +void StoreData::SetMouthY(u8 value) { + core_data.SetMouthY(value); +} + +void StoreData::SetBeardColor(CommonColor value) { + core_data.SetBeardColor(value); +} + +void StoreData::SetBeardType(BeardType value) { + core_data.SetBeardType(value); +} + +void StoreData::SetMustacheType(MustacheType value) { + core_data.SetMustacheType(value); +} + +void StoreData::SetMustacheScale(u8 value) { + core_data.SetMustacheScale(value); +} + +void StoreData::SetMustacheY(u8 value) { + core_data.SetMustacheY(value); +} + +void StoreData::SetGlassType(GlassType value) { + core_data.SetGlassType(value); +} + +void StoreData::SetGlassColor(CommonColor value) { + core_data.SetGlassColor(value); +} + +void StoreData::SetGlassScale(u8 value) { + core_data.SetGlassScale(value); +} + +void StoreData::SetGlassY(u8 value) { + core_data.SetGlassY(value); +} + +void StoreData::SetMoleType(MoleType value) { + core_data.SetMoleType(value); +} + +void StoreData::SetMoleScale(u8 value) { + core_data.SetMoleScale(value); +} + +void StoreData::SetMoleX(u8 value) { + core_data.SetMoleX(value); +} + +void StoreData::SetMoleY(u8 value) { + core_data.SetMoleY(value); +} + +void StoreData::SetNickname(Nickname value) { + core_data.SetNickname(value); +} + +void StoreData::SetInvalidName() { + const auto& invalid_name = core_data.GetInvalidNickname(); + core_data.SetNickname(invalid_name); + SetChecksum(); +} + +void StoreData::SetChecksum() { + SetDataChecksum(); + SetDeviceChecksum(); +} + +void StoreData::SetDataChecksum() { + data_crc = MiiUtil::CalculateCrc16(&core_data, sizeof(CoreData) + sizeof(Common::UUID)); +} + +void StoreData::SetDeviceChecksum() { + const auto device_id = MiiUtil::GetDeviceId(); + device_crc = MiiUtil::CalculateDeviceCrc16(device_id, sizeof(StoreData)); +} + +Common::UUID StoreData::GetCreateId() const { + return create_id; +} + +FontRegion StoreData::GetFontRegion() const { + return static_cast<FontRegion>(core_data.GetFontRegion()); +} + +FavoriteColor StoreData::GetFavoriteColor() const { + return core_data.GetFavoriteColor(); +} + +Gender StoreData::GetGender() const { + return core_data.GetGender(); +} + +u8 StoreData::GetHeight() const { + return core_data.GetHeight(); +} + +u8 StoreData::GetBuild() const { + return core_data.GetBuild(); +} + +u8 StoreData::GetType() const { + return core_data.GetType(); +} + +u8 StoreData::GetRegionMove() const { + return core_data.GetRegionMove(); +} + +FacelineType StoreData::GetFacelineType() const { + return core_data.GetFacelineType(); +} + +FacelineColor StoreData::GetFacelineColor() const { + return core_data.GetFacelineColor(); +} + +FacelineWrinkle StoreData::GetFacelineWrinkle() const { + return core_data.GetFacelineWrinkle(); +} + +FacelineMake StoreData::GetFacelineMake() const { + return core_data.GetFacelineMake(); +} + +HairType StoreData::GetHairType() const { + return core_data.GetHairType(); +} + +CommonColor StoreData::GetHairColor() const { + return core_data.GetHairColor(); +} + +HairFlip StoreData::GetHairFlip() const { + return core_data.GetHairFlip(); +} + +EyeType StoreData::GetEyeType() const { + return core_data.GetEyeType(); +} + +CommonColor StoreData::GetEyeColor() const { + return core_data.GetEyeColor(); +} + +u8 StoreData::GetEyeScale() const { + return core_data.GetEyeScale(); +} + +u8 StoreData::GetEyeAspect() const { + return core_data.GetEyeAspect(); +} + +u8 StoreData::GetEyeRotate() const { + return core_data.GetEyeRotate(); +} + +u8 StoreData::GetEyeX() const { + return core_data.GetEyeX(); +} + +u8 StoreData::GetEyeY() const { + return core_data.GetEyeY(); +} + +EyebrowType StoreData::GetEyebrowType() const { + return core_data.GetEyebrowType(); +} + +CommonColor StoreData::GetEyebrowColor() const { + return core_data.GetEyebrowColor(); +} + +u8 StoreData::GetEyebrowScale() const { + return core_data.GetEyebrowScale(); +} + +u8 StoreData::GetEyebrowAspect() const { + return core_data.GetEyebrowAspect(); +} + +u8 StoreData::GetEyebrowRotate() const { + return core_data.GetEyebrowRotate(); +} + +u8 StoreData::GetEyebrowX() const { + return core_data.GetEyebrowX(); +} + +u8 StoreData::GetEyebrowY() const { + return core_data.GetEyebrowY(); +} + +NoseType StoreData::GetNoseType() const { + return core_data.GetNoseType(); +} + +u8 StoreData::GetNoseScale() const { + return core_data.GetNoseScale(); +} + +u8 StoreData::GetNoseY() const { + return core_data.GetNoseY(); +} + +MouthType StoreData::GetMouthType() const { + return core_data.GetMouthType(); +} + +CommonColor StoreData::GetMouthColor() const { + return core_data.GetMouthColor(); +} + +u8 StoreData::GetMouthScale() const { + return core_data.GetMouthScale(); +} + +u8 StoreData::GetMouthAspect() const { + return core_data.GetMouthAspect(); +} + +u8 StoreData::GetMouthY() const { + return core_data.GetMouthY(); +} + +CommonColor StoreData::GetBeardColor() const { + return core_data.GetBeardColor(); +} + +BeardType StoreData::GetBeardType() const { + return core_data.GetBeardType(); +} + +MustacheType StoreData::GetMustacheType() const { + return core_data.GetMustacheType(); +} + +u8 StoreData::GetMustacheScale() const { + return core_data.GetMustacheScale(); +} + +u8 StoreData::GetMustacheY() const { + return core_data.GetMustacheY(); +} + +GlassType StoreData::GetGlassType() const { + return core_data.GetGlassType(); +} + +CommonColor StoreData::GetGlassColor() const { + return core_data.GetGlassColor(); +} + +u8 StoreData::GetGlassScale() const { + return core_data.GetGlassScale(); +} + +u8 StoreData::GetGlassY() const { + return core_data.GetGlassY(); +} + +MoleType StoreData::GetMoleType() const { + return core_data.GetMoleType(); +} + +u8 StoreData::GetMoleScale() const { + return core_data.GetMoleScale(); +} + +u8 StoreData::GetMoleX() const { + return core_data.GetMoleX(); +} + +u8 StoreData::GetMoleY() const { + return core_data.GetMoleY(); +} + +Nickname StoreData::GetNickname() const { + return core_data.GetNickname(); +} + +bool StoreData::operator==(const StoreData& data) { + bool is_identical = data.core_data.IsValid() == ValidationResult::NoErrors; + is_identical &= core_data.GetNickname().data == data.core_data.GetNickname().data; + is_identical &= GetCreateId() == data.GetCreateId(); + is_identical &= GetFontRegion() == data.GetFontRegion(); + is_identical &= GetFavoriteColor() == data.GetFavoriteColor(); + is_identical &= GetGender() == data.GetGender(); + is_identical &= GetHeight() == data.GetHeight(); + is_identical &= GetBuild() == data.GetBuild(); + is_identical &= GetType() == data.GetType(); + is_identical &= GetRegionMove() == data.GetRegionMove(); + is_identical &= GetFacelineType() == data.GetFacelineType(); + is_identical &= GetFacelineColor() == data.GetFacelineColor(); + is_identical &= GetFacelineWrinkle() == data.GetFacelineWrinkle(); + is_identical &= GetFacelineMake() == data.GetFacelineMake(); + is_identical &= GetHairType() == data.GetHairType(); + is_identical &= GetHairColor() == data.GetHairColor(); + is_identical &= GetHairFlip() == data.GetHairFlip(); + is_identical &= GetEyeType() == data.GetEyeType(); + is_identical &= GetEyeColor() == data.GetEyeColor(); + is_identical &= GetEyeScale() == data.GetEyeScale(); + is_identical &= GetEyeAspect() == data.GetEyeAspect(); + is_identical &= GetEyeRotate() == data.GetEyeRotate(); + is_identical &= GetEyeX() == data.GetEyeX(); + is_identical &= GetEyeY() == data.GetEyeY(); + is_identical &= GetEyebrowType() == data.GetEyebrowType(); + is_identical &= GetEyebrowColor() == data.GetEyebrowColor(); + is_identical &= GetEyebrowScale() == data.GetEyebrowScale(); + is_identical &= GetEyebrowAspect() == data.GetEyebrowAspect(); + is_identical &= GetEyebrowRotate() == data.GetEyebrowRotate(); + is_identical &= GetEyebrowX() == data.GetEyebrowX(); + is_identical &= GetEyebrowY() == data.GetEyebrowY(); + is_identical &= GetNoseType() == data.GetNoseType(); + is_identical &= GetNoseScale() == data.GetNoseScale(); + is_identical &= GetNoseY() == data.GetNoseY(); + is_identical &= GetMouthType() == data.GetMouthType(); + is_identical &= GetMouthColor() == data.GetMouthColor(); + is_identical &= GetMouthScale() == data.GetMouthScale(); + is_identical &= GetMouthAspect() == data.GetMouthAspect(); + is_identical &= GetMouthY() == data.GetMouthY(); + is_identical &= GetBeardColor() == data.GetBeardColor(); + is_identical &= GetBeardType() == data.GetBeardType(); + is_identical &= GetMustacheType() == data.GetMustacheType(); + is_identical &= GetMustacheScale() == data.GetMustacheScale(); + is_identical &= GetMustacheY() == data.GetMustacheY(); + is_identical &= GetGlassType() == data.GetGlassType(); + is_identical &= GetGlassColor() == data.GetGlassColor(); + is_identical &= GetGlassScale() == data.GetGlassScale(); + is_identical &= GetGlassY() == data.GetGlassY(); + is_identical &= GetMoleType() == data.GetMoleType(); + is_identical &= GetMoleScale() == data.GetMoleScale(); + is_identical &= GetMoleX() == data.GetMoleX(); + is_identical &= data.GetMoleY() == data.GetMoleY(); + return is_identical; +} + +} // namespace Service::Mii diff --git a/src/core/hle/service/mii/types/store_data.h b/src/core/hle/service/mii/types/store_data.h new file mode 100644 index 000000000..ed5dfb949 --- /dev/null +++ b/src/core/hle/service/mii/types/store_data.h @@ -0,0 +1,150 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "core/hle/result.h" +#include "core/hle/service/mii/mii_types.h" +#include "core/hle/service/mii/types/core_data.h" + +namespace Service::Mii { + +class StoreData { +public: + void BuildDefault(u32 mii_index); + void BuildBase(Gender gender); + void BuildRandom(Age age, Gender gender, Race race); + void BuildWithCharInfo(const CharInfo& char_info); + void BuildWithCoreData(const CoreData& in_core_data); + Result Restore(); + + ValidationResult IsValid() const; + + bool IsSpecial() const; + + void SetFontRegion(FontRegion value); + void SetFavoriteColor(FavoriteColor value); + void SetGender(Gender value); + void SetHeight(u8 value); + void SetBuild(u8 value); + void SetType(u8 value); + void SetRegionMove(u8 value); + void SetFacelineType(FacelineType value); + void SetFacelineColor(FacelineColor value); + void SetFacelineWrinkle(FacelineWrinkle value); + void SetFacelineMake(FacelineMake value); + void SetHairType(HairType value); + void SetHairColor(CommonColor value); + void SetHairFlip(HairFlip value); + void SetEyeType(EyeType value); + void SetEyeColor(CommonColor value); + void SetEyeScale(u8 value); + void SetEyeAspect(u8 value); + void SetEyeRotate(u8 value); + void SetEyeX(u8 value); + void SetEyeY(u8 value); + void SetEyebrowType(EyebrowType value); + void SetEyebrowColor(CommonColor value); + void SetEyebrowScale(u8 value); + void SetEyebrowAspect(u8 value); + void SetEyebrowRotate(u8 value); + void SetEyebrowX(u8 value); + void SetEyebrowY(u8 value); + void SetNoseType(NoseType value); + void SetNoseScale(u8 value); + void SetNoseY(u8 value); + void SetMouthType(MouthType value); + void SetMouthColor(CommonColor value); + void SetMouthScale(u8 value); + void SetMouthAspect(u8 value); + void SetMouthY(u8 value); + void SetBeardColor(CommonColor value); + void SetBeardType(BeardType value); + void SetMustacheType(MustacheType value); + void SetMustacheScale(u8 value); + void SetMustacheY(u8 value); + void SetGlassType(GlassType value); + void SetGlassColor(CommonColor value); + void SetGlassScale(u8 value); + void SetGlassY(u8 value); + void SetMoleType(MoleType value); + void SetMoleScale(u8 value); + void SetMoleX(u8 value); + void SetMoleY(u8 value); + void SetNickname(Nickname nickname); + void SetInvalidName(); + void SetChecksum(); + void SetDataChecksum(); + void SetDeviceChecksum(); + + Common::UUID GetCreateId() const; + FontRegion GetFontRegion() const; + FavoriteColor GetFavoriteColor() const; + Gender GetGender() const; + u8 GetHeight() const; + u8 GetBuild() const; + u8 GetType() const; + u8 GetRegionMove() const; + FacelineType GetFacelineType() const; + FacelineColor GetFacelineColor() const; + FacelineWrinkle GetFacelineWrinkle() const; + FacelineMake GetFacelineMake() const; + HairType GetHairType() const; + CommonColor GetHairColor() const; + HairFlip GetHairFlip() const; + EyeType GetEyeType() const; + CommonColor GetEyeColor() const; + u8 GetEyeScale() const; + u8 GetEyeAspect() const; + u8 GetEyeRotate() const; + u8 GetEyeX() const; + u8 GetEyeY() const; + EyebrowType GetEyebrowType() const; + CommonColor GetEyebrowColor() const; + u8 GetEyebrowScale() const; + u8 GetEyebrowAspect() const; + u8 GetEyebrowRotate() const; + u8 GetEyebrowX() const; + u8 GetEyebrowY() const; + NoseType GetNoseType() const; + u8 GetNoseScale() const; + u8 GetNoseY() const; + MouthType GetMouthType() const; + CommonColor GetMouthColor() const; + u8 GetMouthScale() const; + u8 GetMouthAspect() const; + u8 GetMouthY() const; + CommonColor GetBeardColor() const; + BeardType GetBeardType() const; + MustacheType GetMustacheType() const; + u8 GetMustacheScale() const; + u8 GetMustacheY() const; + GlassType GetGlassType() const; + CommonColor GetGlassColor() const; + u8 GetGlassScale() const; + u8 GetGlassY() const; + MoleType GetMoleType() const; + u8 GetMoleScale() const; + u8 GetMoleX() const; + u8 GetMoleY() const; + Nickname GetNickname() const; + + bool operator==(const StoreData& data); + +private: + CoreData core_data{}; + Common::UUID create_id{}; + u16 data_crc{}; + u16 device_crc{}; +}; +static_assert(sizeof(StoreData) == 0x44, "StoreData has incorrect size."); +static_assert(std::is_trivially_copyable_v<StoreData>, + "StoreData type must be trivially copyable."); + +struct StoreDataElement { + StoreData store_data{}; + Source source{}; +}; +static_assert(sizeof(StoreDataElement) == 0x48, "StoreDataElement has incorrect size."); + +}; // namespace Service::Mii diff --git a/src/core/hle/service/mii/types/ver3_store_data.cpp b/src/core/hle/service/mii/types/ver3_store_data.cpp new file mode 100644 index 000000000..a019cc9f7 --- /dev/null +++ b/src/core/hle/service/mii/types/ver3_store_data.cpp @@ -0,0 +1,241 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "core/hle/service/mii/mii_util.h" +#include "core/hle/service/mii/types/raw_data.h" +#include "core/hle/service/mii/types/store_data.h" +#include "core/hle/service/mii/types/ver3_store_data.h" + +namespace Service::Mii { + +void NfpStoreDataExtension::SetFromStoreData(const StoreData& store_data) { + faceline_color = static_cast<u8>(store_data.GetFacelineColor()) & 0xf; + hair_color = static_cast<u8>(store_data.GetHairColor()) & 0x7f; + eye_color = static_cast<u8>(store_data.GetEyeColor()) & 0x7f; + eyebrow_color = static_cast<u8>(store_data.GetEyebrowColor()) & 0x7f; + mouth_color = static_cast<u8>(store_data.GetMouthColor()) & 0x7f; + beard_color = static_cast<u8>(store_data.GetBeardColor()) & 0x7f; + glass_color = static_cast<u8>(store_data.GetGlassColor()) & 0x7f; + glass_type = static_cast<u8>(store_data.GetGlassType()) & 0x1f; +} + +void Ver3StoreData::BuildToStoreData(StoreData& out_store_data) const { + out_store_data.BuildBase(Gender::Male); + + out_store_data.SetGender(static_cast<Gender>(mii_information.gender.Value())); + out_store_data.SetFavoriteColor( + static_cast<FavoriteColor>(mii_information.favorite_color.Value())); + out_store_data.SetHeight(height); + out_store_data.SetBuild(build); + + out_store_data.SetNickname(mii_name); + out_store_data.SetFontRegion( + static_cast<FontRegion>(static_cast<u8>(region_information.font_region.Value()))); + + out_store_data.SetFacelineType( + static_cast<FacelineType>(appearance_bits1.faceline_type.Value())); + out_store_data.SetFacelineColor( + RawData::GetFacelineColorFromVer3(appearance_bits1.faceline_color.Value())); + out_store_data.SetFacelineWrinkle( + static_cast<FacelineWrinkle>(appearance_bits2.faceline_wrinkle.Value())); + out_store_data.SetFacelineMake( + static_cast<FacelineMake>(appearance_bits2.faceline_make.Value())); + + out_store_data.SetHairType(static_cast<HairType>(hair_type)); + out_store_data.SetHairColor(RawData::GetHairColorFromVer3(appearance_bits3.hair_color.Value())); + out_store_data.SetHairFlip(static_cast<HairFlip>(appearance_bits3.hair_flip.Value())); + + out_store_data.SetEyeType(static_cast<EyeType>(appearance_bits4.eye_type.Value())); + out_store_data.SetEyeColor(RawData::GetEyeColorFromVer3(appearance_bits4.eye_color.Value())); + out_store_data.SetEyeScale(static_cast<u8>(appearance_bits4.eye_scale.Value())); + out_store_data.SetEyeAspect(static_cast<u8>(appearance_bits4.eye_aspect.Value())); + out_store_data.SetEyeRotate(static_cast<u8>(appearance_bits4.eye_rotate.Value())); + out_store_data.SetEyeX(static_cast<u8>(appearance_bits4.eye_x.Value())); + out_store_data.SetEyeY(static_cast<u8>(appearance_bits4.eye_y.Value())); + + out_store_data.SetEyebrowType(static_cast<EyebrowType>(appearance_bits5.eyebrow_type.Value())); + out_store_data.SetEyebrowColor( + RawData::GetHairColorFromVer3(appearance_bits5.eyebrow_color.Value())); + out_store_data.SetEyebrowScale(static_cast<u8>(appearance_bits5.eyebrow_scale.Value())); + out_store_data.SetEyebrowAspect(static_cast<u8>(appearance_bits5.eyebrow_aspect.Value())); + out_store_data.SetEyebrowRotate(static_cast<u8>(appearance_bits5.eyebrow_rotate.Value())); + out_store_data.SetEyebrowX(static_cast<u8>(appearance_bits5.eyebrow_x.Value())); + out_store_data.SetEyebrowY(static_cast<u8>(appearance_bits5.eyebrow_y.Value() - 3)); + + out_store_data.SetNoseType(static_cast<NoseType>(appearance_bits6.nose_type.Value())); + out_store_data.SetNoseScale(static_cast<u8>(appearance_bits6.nose_scale.Value())); + out_store_data.SetNoseY(static_cast<u8>(appearance_bits6.nose_y.Value())); + + out_store_data.SetMouthType(static_cast<MouthType>(appearance_bits7.mouth_type.Value())); + out_store_data.SetMouthColor( + RawData::GetMouthColorFromVer3(appearance_bits7.mouth_color.Value())); + out_store_data.SetMouthScale(static_cast<u8>(appearance_bits7.mouth_scale.Value())); + out_store_data.SetMouthAspect(static_cast<u8>(appearance_bits7.mouth_aspect.Value())); + out_store_data.SetMouthY(static_cast<u8>(appearance_bits8.mouth_y.Value())); + + out_store_data.SetMustacheType( + static_cast<MustacheType>(appearance_bits8.mustache_type.Value())); + out_store_data.SetMustacheScale(static_cast<u8>(appearance_bits9.mustache_scale.Value())); + out_store_data.SetMustacheY(static_cast<u8>(appearance_bits9.mustache_y.Value())); + + out_store_data.SetBeardType(static_cast<BeardType>(appearance_bits9.beard_type.Value())); + out_store_data.SetBeardColor( + RawData::GetHairColorFromVer3(appearance_bits9.beard_color.Value())); + + // Glass type is compatible as it is. It doesn't need a table + out_store_data.SetGlassType(static_cast<GlassType>(appearance_bits10.glass_type.Value())); + out_store_data.SetGlassColor( + RawData::GetGlassColorFromVer3(appearance_bits10.glass_color.Value())); + out_store_data.SetGlassScale(static_cast<u8>(appearance_bits10.glass_scale.Value())); + out_store_data.SetGlassY(static_cast<u8>(appearance_bits10.glass_y.Value())); + + out_store_data.SetMoleType(static_cast<MoleType>(appearance_bits11.mole_type.Value())); + out_store_data.SetMoleScale(static_cast<u8>(appearance_bits11.mole_scale.Value())); + out_store_data.SetMoleX(static_cast<u8>(appearance_bits11.mole_x.Value())); + out_store_data.SetMoleY(static_cast<u8>(appearance_bits11.mole_y.Value())); + + out_store_data.SetChecksum(); +} + +void Ver3StoreData::BuildFromStoreData(const StoreData& store_data) { + version = 1; + mii_information.gender.Assign(static_cast<u8>(store_data.GetGender())); + mii_information.favorite_color.Assign(static_cast<u8>(store_data.GetFavoriteColor())); + height = store_data.GetHeight(); + build = store_data.GetBuild(); + + mii_name = store_data.GetNickname(); + region_information.font_region.Assign(static_cast<u8>(store_data.GetFontRegion())); + + appearance_bits1.faceline_type.Assign(static_cast<u8>(store_data.GetFacelineType())); + appearance_bits2.faceline_wrinkle.Assign(static_cast<u8>(store_data.GetFacelineWrinkle())); + appearance_bits2.faceline_make.Assign(static_cast<u8>(store_data.GetFacelineMake())); + + hair_type = static_cast<u8>(store_data.GetHairType()); + appearance_bits3.hair_flip.Assign(static_cast<u8>(store_data.GetHairFlip())); + + appearance_bits4.eye_type.Assign(static_cast<u8>(store_data.GetEyeType())); + appearance_bits4.eye_scale.Assign(store_data.GetEyeScale()); + appearance_bits4.eye_aspect.Assign(store_data.GetEyebrowAspect()); + appearance_bits4.eye_rotate.Assign(store_data.GetEyeRotate()); + appearance_bits4.eye_x.Assign(store_data.GetEyeX()); + appearance_bits4.eye_y.Assign(store_data.GetEyeY()); + + appearance_bits5.eyebrow_type.Assign(static_cast<u8>(store_data.GetEyebrowType())); + appearance_bits5.eyebrow_scale.Assign(store_data.GetEyebrowScale()); + appearance_bits5.eyebrow_aspect.Assign(store_data.GetEyebrowAspect()); + appearance_bits5.eyebrow_rotate.Assign(store_data.GetEyebrowRotate()); + appearance_bits5.eyebrow_x.Assign(store_data.GetEyebrowX()); + appearance_bits5.eyebrow_y.Assign(store_data.GetEyebrowY()); + + appearance_bits6.nose_type.Assign(static_cast<u8>(store_data.GetNoseType())); + appearance_bits6.nose_scale.Assign(store_data.GetNoseScale()); + appearance_bits6.nose_y.Assign(store_data.GetNoseY()); + + appearance_bits7.mouth_type.Assign(static_cast<u8>(store_data.GetMouthType())); + appearance_bits7.mouth_scale.Assign(store_data.GetMouthScale()); + appearance_bits7.mouth_aspect.Assign(store_data.GetMouthAspect()); + appearance_bits8.mouth_y.Assign(store_data.GetMouthY()); + + appearance_bits8.mustache_type.Assign(static_cast<u8>(store_data.GetMustacheType())); + appearance_bits9.mustache_scale.Assign(store_data.GetMustacheScale()); + appearance_bits9.mustache_y.Assign(store_data.GetMustacheY()); + + appearance_bits9.beard_type.Assign(static_cast<u8>(store_data.GetBeardType())); + + appearance_bits10.glass_scale.Assign(store_data.GetGlassScale()); + appearance_bits10.glass_y.Assign(store_data.GetGlassY()); + + appearance_bits11.mole_type.Assign(static_cast<u8>(store_data.GetMoleType())); + appearance_bits11.mole_scale.Assign(store_data.GetMoleScale()); + appearance_bits11.mole_x.Assign(store_data.GetMoleX()); + appearance_bits11.mole_y.Assign(store_data.GetMoleY()); + + // These types are converted to V3 from a table + appearance_bits1.faceline_color.Assign( + RawData::FromVer3GetFacelineColor(static_cast<u8>(store_data.GetFacelineColor()))); + appearance_bits3.hair_color.Assign( + RawData::FromVer3GetHairColor(static_cast<u8>(store_data.GetHairColor()))); + appearance_bits4.eye_color.Assign( + RawData::FromVer3GetEyeColor(static_cast<u8>(store_data.GetEyeColor()))); + appearance_bits5.eyebrow_color.Assign( + RawData::FromVer3GetHairColor(static_cast<u8>(store_data.GetEyebrowColor()))); + appearance_bits7.mouth_color.Assign( + RawData::FromVer3GetMouthlineColor(static_cast<u8>(store_data.GetMouthColor()))); + appearance_bits9.beard_color.Assign( + RawData::FromVer3GetHairColor(static_cast<u8>(store_data.GetBeardColor()))); + appearance_bits10.glass_color.Assign( + RawData::FromVer3GetGlassColor(static_cast<u8>(store_data.GetGlassColor()))); + appearance_bits10.glass_type.Assign( + RawData::FromVer3GetGlassType(static_cast<u8>(store_data.GetGlassType()))); + + crc = MiiUtil::CalculateCrc16(&version, sizeof(Ver3StoreData) - sizeof(u16)); +} + +u32 Ver3StoreData::IsValid() const { + bool is_valid = version == 0 || version == 3; + + is_valid = is_valid && (mii_name.data[0] != '\0'); + + is_valid = is_valid && (mii_information.birth_month < 13); + is_valid = is_valid && (mii_information.birth_day < 32); + is_valid = is_valid && (mii_information.favorite_color <= static_cast<u8>(FavoriteColor::Max)); + is_valid = is_valid && (height <= MaxHeight); + is_valid = is_valid && (build <= MaxBuild); + + is_valid = is_valid && (appearance_bits1.faceline_type <= static_cast<u8>(FacelineType::Max)); + is_valid = is_valid && (appearance_bits1.faceline_color <= MaxVer3CommonColor - 2); + is_valid = + is_valid && (appearance_bits2.faceline_wrinkle <= static_cast<u8>(FacelineWrinkle::Max)); + is_valid = is_valid && (appearance_bits2.faceline_make <= static_cast<u8>(FacelineMake::Max)); + + is_valid = is_valid && (hair_type <= static_cast<u8>(HairType::Max)); + is_valid = is_valid && (appearance_bits3.hair_color <= MaxVer3CommonColor); + + is_valid = is_valid && (appearance_bits4.eye_type <= static_cast<u8>(EyeType::Max)); + is_valid = is_valid && (appearance_bits4.eye_color <= MaxVer3CommonColor - 2); + is_valid = is_valid && (appearance_bits4.eye_scale <= MaxEyeScale); + is_valid = is_valid && (appearance_bits4.eye_aspect <= MaxEyeAspect); + is_valid = is_valid && (appearance_bits4.eye_rotate <= MaxEyeRotate); + is_valid = is_valid && (appearance_bits4.eye_x <= MaxEyeX); + is_valid = is_valid && (appearance_bits4.eye_y <= MaxEyeY); + + is_valid = is_valid && (appearance_bits5.eyebrow_type <= static_cast<u8>(EyebrowType::Max)); + is_valid = is_valid && (appearance_bits5.eyebrow_color <= MaxVer3CommonColor); + is_valid = is_valid && (appearance_bits5.eyebrow_scale <= MaxEyebrowScale); + is_valid = is_valid && (appearance_bits5.eyebrow_aspect <= MaxEyebrowAspect); + is_valid = is_valid && (appearance_bits5.eyebrow_rotate <= MaxEyebrowRotate); + is_valid = is_valid && (appearance_bits5.eyebrow_x <= MaxEyebrowX); + is_valid = is_valid && (appearance_bits5.eyebrow_y <= MaxEyebrowY); + + is_valid = is_valid && (appearance_bits6.nose_type <= static_cast<u8>(NoseType::Max)); + is_valid = is_valid && (appearance_bits6.nose_scale <= MaxNoseScale); + is_valid = is_valid && (appearance_bits6.nose_y <= MaxNoseY); + + is_valid = is_valid && (appearance_bits7.mouth_type <= static_cast<u8>(MouthType::Max)); + is_valid = is_valid && (appearance_bits7.mouth_color <= MaxVer3CommonColor - 3); + is_valid = is_valid && (appearance_bits7.mouth_scale <= MaxMouthScale); + is_valid = is_valid && (appearance_bits7.mouth_aspect <= MaxMoutAspect); + is_valid = is_valid && (appearance_bits8.mouth_y <= MaxMouthY); + + is_valid = is_valid && (appearance_bits8.mustache_type <= static_cast<u8>(MustacheType::Max)); + is_valid = is_valid && (appearance_bits9.mustache_scale < MaxMustacheScale); + is_valid = is_valid && (appearance_bits9.mustache_y <= MaxMustacheY); + + is_valid = is_valid && (appearance_bits9.beard_type <= static_cast<u8>(BeardType::Max)); + is_valid = is_valid && (appearance_bits9.beard_color <= MaxVer3CommonColor); + + is_valid = is_valid && (appearance_bits10.glass_type <= MaxVer3GlassType); + is_valid = is_valid && (appearance_bits10.glass_color <= MaxVer3CommonColor - 2); + is_valid = is_valid && (appearance_bits10.glass_scale <= MaxGlassScale); + is_valid = is_valid && (appearance_bits10.glass_y <= MaxGlassY); + + is_valid = is_valid && (appearance_bits11.mole_type <= static_cast<u8>(MoleType::Max)); + is_valid = is_valid && (appearance_bits11.mole_scale <= MaxMoleScale); + is_valid = is_valid && (appearance_bits11.mole_x <= MaxMoleX); + is_valid = is_valid && (appearance_bits11.mole_y <= MaxMoleY); + + return is_valid; +} + +} // namespace Service::Mii diff --git a/src/core/hle/service/mii/types/ver3_store_data.h b/src/core/hle/service/mii/types/ver3_store_data.h new file mode 100644 index 000000000..47907bf7d --- /dev/null +++ b/src/core/hle/service/mii/types/ver3_store_data.h @@ -0,0 +1,160 @@ +// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "core/hle/service/mii/mii_types.h" + +namespace Service::Mii { +class StoreData; + +// This is nn::mii::Ver3StoreData +// Based on citra HLE::Applets::MiiData and PretendoNetwork. +// https://github.com/citra-emu/citra/blob/master/src/core/hle/applets/mii_selector.h#L48 +// https://github.com/PretendoNetwork/mii-js/blob/master/mii.js#L299 + +struct NfpStoreDataExtension { + void SetFromStoreData(const StoreData& store_data); + + u8 faceline_color; + u8 hair_color; + u8 eye_color; + u8 eyebrow_color; + u8 mouth_color; + u8 beard_color; + u8 glass_color; + u8 glass_type; +}; +static_assert(sizeof(NfpStoreDataExtension) == 0x8, "NfpStoreDataExtension is an invalid size"); + +#pragma pack(push, 4) +class Ver3StoreData { +public: + void BuildToStoreData(StoreData& out_store_data) const; + void BuildFromStoreData(const StoreData& store_data); + + u32 IsValid() const; + + u8 version; + union { + u8 raw; + + BitField<0, 1, u8> allow_copying; + BitField<1, 1, u8> profanity_flag; + BitField<2, 2, u8> region_lock; + BitField<4, 2, u8> font_region; + } region_information; + u16_be mii_id; + u64_be system_id; + u32_be specialness_and_creation_date; + std::array<u8, 6> creator_mac; + u16_be padding; + union { + u16 raw; + + BitField<0, 1, u16> gender; + BitField<1, 4, u16> birth_month; + BitField<5, 5, u16> birth_day; + BitField<10, 4, u16> favorite_color; + BitField<14, 1, u16> favorite; + } mii_information; + Nickname mii_name; + u8 height; + u8 build; + union { + u8 raw; + + BitField<0, 1, u8> disable_sharing; + BitField<1, 4, u8> faceline_type; + BitField<5, 3, u8> faceline_color; + } appearance_bits1; + union { + u8 raw; + + BitField<0, 4, u8> faceline_wrinkle; + BitField<4, 4, u8> faceline_make; + } appearance_bits2; + u8 hair_type; + union { + u8 raw; + + BitField<0, 3, u8> hair_color; + BitField<3, 1, u8> hair_flip; + } appearance_bits3; + union { + u32 raw; + + BitField<0, 6, u32> eye_type; + BitField<6, 3, u32> eye_color; + BitField<9, 4, u32> eye_scale; + BitField<13, 3, u32> eye_aspect; + BitField<16, 5, u32> eye_rotate; + BitField<21, 4, u32> eye_x; + BitField<25, 5, u32> eye_y; + } appearance_bits4; + union { + u32 raw; + + BitField<0, 5, u32> eyebrow_type; + BitField<5, 3, u32> eyebrow_color; + BitField<8, 4, u32> eyebrow_scale; + BitField<12, 3, u32> eyebrow_aspect; + BitField<16, 4, u32> eyebrow_rotate; + BitField<21, 4, u32> eyebrow_x; + BitField<25, 5, u32> eyebrow_y; + } appearance_bits5; + union { + u16 raw; + + BitField<0, 5, u16> nose_type; + BitField<5, 4, u16> nose_scale; + BitField<9, 5, u16> nose_y; + } appearance_bits6; + union { + u16 raw; + + BitField<0, 6, u16> mouth_type; + BitField<6, 3, u16> mouth_color; + BitField<9, 4, u16> mouth_scale; + BitField<13, 3, u16> mouth_aspect; + } appearance_bits7; + union { + u8 raw; + + BitField<0, 5, u8> mouth_y; + BitField<5, 3, u8> mustache_type; + } appearance_bits8; + u8 allow_copying; + union { + u16 raw; + + BitField<0, 3, u16> beard_type; + BitField<3, 3, u16> beard_color; + BitField<6, 4, u16> mustache_scale; + BitField<10, 5, u16> mustache_y; + } appearance_bits9; + union { + u16 raw; + + BitField<0, 4, u16> glass_type; + BitField<4, 3, u16> glass_color; + BitField<7, 4, u16> glass_scale; + BitField<11, 5, u16> glass_y; + } appearance_bits10; + union { + u16 raw; + + BitField<0, 1, u16> mole_type; + BitField<1, 4, u16> mole_scale; + BitField<5, 5, u16> mole_x; + BitField<10, 5, u16> mole_y; + } appearance_bits11; + + Nickname author_name; + INSERT_PADDING_BYTES(0x2); + u16_be crc; +}; +static_assert(sizeof(Ver3StoreData) == 0x60, "Ver3StoreData is an invalid size"); +#pragma pack(pop) + +}; // namespace Service::Mii diff --git a/src/core/hle/service/nfc/common/device.cpp b/src/core/hle/service/nfc/common/device.cpp index 49446bc42..e7a00deb3 100644 --- a/src/core/hle/service/nfc/common/device.cpp +++ b/src/core/hle/service/nfc/common/device.cpp @@ -28,7 +28,6 @@ #include "core/hle/kernel/k_event.h" #include "core/hle/service/ipc_helpers.h" #include "core/hle/service/mii/mii_manager.h" -#include "core/hle/service/mii/types.h" #include "core/hle/service/nfc/common/amiibo_crypto.h" #include "core/hle/service/nfc/common/device.h" #include "core/hle/service/nfc/mifare_result.h" @@ -440,6 +439,7 @@ Result NfcDevice::Mount(NFP::ModelType model_type, NFP::MountTarget mount_target device_state = DeviceState::TagMounted; mount_target = mount_target_; + return ResultSuccess; } @@ -681,12 +681,16 @@ Result NfcDevice::GetRegisterInfo(NFP::RegisterInfo& register_info) const { return ResultRegistrationIsNotInitialized; } - Service::Mii::MiiManager manager; + Mii::CharInfo char_info{}; + Mii::StoreData store_data{}; + tag_data.owner_mii.BuildToStoreData(store_data); + char_info.SetFromStoreData(store_data); + const auto& settings = tag_data.settings; // TODO: Validate this data register_info = { - .mii_char_info = manager.ConvertV3ToCharInfo(tag_data.owner_mii), + .mii_char_info = char_info, .creation_date = settings.init_date.GetWriteDate(), .amiibo_name = GetAmiiboName(settings), .font_region = settings.settings.font_region, @@ -713,12 +717,13 @@ Result NfcDevice::GetRegisterInfoPrivate(NFP::RegisterInfoPrivate& register_info return ResultRegistrationIsNotInitialized; } - Service::Mii::MiiManager manager; + Mii::StoreData store_data{}; const auto& settings = tag_data.settings; + tag_data.owner_mii.BuildToStoreData(store_data); // TODO: Validate and complete this data register_info = { - .mii_store_data = {}, + .mii_store_data = store_data, .creation_date = settings.init_date.GetWriteDate(), .amiibo_name = GetAmiiboName(settings), .font_region = settings.settings.font_region, @@ -825,8 +830,6 @@ Result NfcDevice::SetRegisterInfoPrivate(const NFP::RegisterInfoPrivate& registe return ResultWrongDeviceState; } - Service::Mii::MiiManager manager; - const auto mii = manager.BuildDefault(0); auto& settings = tag_data.settings; if (tag_data.settings.settings.amiibo_initialized == 0) { @@ -835,8 +838,8 @@ Result NfcDevice::SetRegisterInfoPrivate(const NFP::RegisterInfoPrivate& registe } SetAmiiboName(settings, register_info.amiibo_name); - tag_data.owner_mii = manager.BuildFromStoreData(mii); - tag_data.mii_extension = manager.SetFromStoreData(mii); + tag_data.owner_mii.BuildFromStoreData(register_info.mii_store_data); + tag_data.mii_extension.SetFromStoreData(register_info.mii_store_data); tag_data.unknown = 0; tag_data.unknown2 = {}; settings.country_code_id = 0; @@ -868,17 +871,19 @@ Result NfcDevice::RestoreAmiibo() { } Result NfcDevice::Format() { - auto result1 = DeleteApplicationArea(); - auto result2 = DeleteRegisterInfo(); + Result result = ResultSuccess; - if (result1.IsError()) { - return result1; + if (device_state == DeviceState::TagFound) { + result = Mount(NFP::ModelType::Amiibo, NFP::MountTarget::All); } - if (result2.IsError()) { - return result2; + if (result.IsError()) { + return result; } + DeleteApplicationArea(); + DeleteRegisterInfo(); + return Flush(); } @@ -1453,7 +1458,7 @@ void NfcDevice::UpdateRegisterInfoCrc() { void NfcDevice::BuildAmiiboWithoutKeys(NFP::NTAG215File& stubbed_tag_data, const NFP::EncryptedNTAG215File& encrypted_file) const { - Service::Mii::MiiManager manager; + Service::Mii::StoreData store_data{}; auto& settings = stubbed_tag_data.settings; stubbed_tag_data = NFP::AmiiboCrypto::NfcDataToEncodedData(encrypted_file); @@ -1467,7 +1472,8 @@ void NfcDevice::BuildAmiiboWithoutKeys(NFP::NTAG215File& stubbed_tag_data, SetAmiiboName(settings, {'y', 'u', 'z', 'u', 'A', 'm', 'i', 'i', 'b', 'o'}); settings.settings.font_region.Assign(0); settings.init_date = GetAmiiboDate(GetCurrentPosixTime()); - stubbed_tag_data.owner_mii = manager.BuildFromStoreData(manager.BuildDefault(0)); + store_data.BuildBase(Mii::Gender::Male); + stubbed_tag_data.owner_mii.BuildFromStoreData(store_data); // Admin info settings.settings.amiibo_initialized.Assign(1); diff --git a/src/core/hle/service/nfp/nfp_types.h b/src/core/hle/service/nfp/nfp_types.h index aed12a7f8..f96d21220 100644 --- a/src/core/hle/service/nfp/nfp_types.h +++ b/src/core/hle/service/nfp/nfp_types.h @@ -6,7 +6,9 @@ #include <array> #include "common/swap.h" -#include "core/hle/service/mii/types.h" +#include "core/hle/service/mii/types/char_info.h" +#include "core/hle/service/mii/types/store_data.h" +#include "core/hle/service/mii/types/ver3_store_data.h" #include "core/hle/service/nfc/nfc_types.h" namespace Service::NFP { @@ -322,7 +324,7 @@ static_assert(sizeof(RegisterInfo) == 0x100, "RegisterInfo is an invalid size"); // This is nn::nfp::RegisterInfoPrivate struct RegisterInfoPrivate { - Service::Mii::MiiStoreData mii_store_data; + Service::Mii::StoreData mii_store_data; WriteDate creation_date; AmiiboName amiibo_name; u8 font_region; diff --git a/src/core/hle/service/ngc/ngc.cpp b/src/core/hle/service/ngc/ngc.cpp new file mode 100644 index 000000000..c26019ec0 --- /dev/null +++ b/src/core/hle/service/ngc/ngc.cpp @@ -0,0 +1,150 @@ +// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "common/string_util.h" +#include "core/core.h" +#include "core/hle/service/ipc_helpers.h" +#include "core/hle/service/ngc/ngc.h" +#include "core/hle/service/server_manager.h" +#include "core/hle/service/service.h" + +namespace Service::NGC { + +class NgctServiceImpl final : public ServiceFramework<NgctServiceImpl> { +public: + explicit NgctServiceImpl(Core::System& system_) : ServiceFramework{system_, "ngct:u"} { + // clang-format off + static const FunctionInfo functions[] = { + {0, &NgctServiceImpl::Match, "Match"}, + {1, &NgctServiceImpl::Filter, "Filter"}, + }; + // clang-format on + + RegisterHandlers(functions); + } + +private: + void Match(HLERequestContext& ctx) { + const auto buffer = ctx.ReadBuffer(); + const auto text = Common::StringFromFixedZeroTerminatedBuffer( + reinterpret_cast<const char*>(buffer.data()), buffer.size()); + + LOG_WARNING(Service_NGC, "(STUBBED) called, text={}", text); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(ResultSuccess); + // Return false since we don't censor anything + rb.Push(false); + } + + void Filter(HLERequestContext& ctx) { + const auto buffer = ctx.ReadBuffer(); + const auto text = Common::StringFromFixedZeroTerminatedBuffer( + reinterpret_cast<const char*>(buffer.data()), buffer.size()); + + LOG_WARNING(Service_NGC, "(STUBBED) called, text={}", text); + + // Return the same string since we don't censor anything + ctx.WriteBuffer(buffer); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSuccess); + } +}; + +class NgcServiceImpl final : public ServiceFramework<NgcServiceImpl> { +public: + explicit NgcServiceImpl(Core::System& system_) : ServiceFramework(system_, "ngc:u") { + // clang-format off + static const FunctionInfo functions[] = { + {0, &NgcServiceImpl::GetContentVersion, "GetContentVersion"}, + {1, &NgcServiceImpl::Check, "Check"}, + {2, &NgcServiceImpl::Mask, "Mask"}, + {3, &NgcServiceImpl::Reload, "Reload"}, + }; + // clang-format on + + RegisterHandlers(functions); + } + +private: + static constexpr u32 NgcContentVersion = 1; + + // This is nn::ngc::detail::ProfanityFilterOption + struct ProfanityFilterOption { + INSERT_PADDING_BYTES_NOINIT(0x20); + }; + static_assert(sizeof(ProfanityFilterOption) == 0x20, + "ProfanityFilterOption has incorrect size"); + + void GetContentVersion(HLERequestContext& ctx) { + LOG_INFO(Service_NGC, "(STUBBED) called"); + + // This calls nn::ngc::ProfanityFilter::GetContentVersion + const u32 version = NgcContentVersion; + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(ResultSuccess); + rb.Push(version); + } + + void Check(HLERequestContext& ctx) { + LOG_INFO(Service_NGC, "(STUBBED) called"); + + struct InputParameters { + u32 flags; + ProfanityFilterOption option; + }; + + IPC::RequestParser rp{ctx}; + [[maybe_unused]] const auto params = rp.PopRaw<InputParameters>(); + [[maybe_unused]] const auto input = ctx.ReadBuffer(0); + + // This calls nn::ngc::ProfanityFilter::CheckProfanityWords + const u32 out_flags = 0; + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(ResultSuccess); + rb.Push(out_flags); + } + + void Mask(HLERequestContext& ctx) { + LOG_INFO(Service_NGC, "(STUBBED) called"); + + struct InputParameters { + u32 flags; + ProfanityFilterOption option; + }; + + IPC::RequestParser rp{ctx}; + [[maybe_unused]] const auto params = rp.PopRaw<InputParameters>(); + const auto input = ctx.ReadBuffer(0); + + // This calls nn::ngc::ProfanityFilter::MaskProfanityWordsInText + const u32 out_flags = 0; + ctx.WriteBuffer(input); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(ResultSuccess); + rb.Push(out_flags); + } + + void Reload(HLERequestContext& ctx) { + LOG_INFO(Service_NGC, "(STUBBED) called"); + + // This reloads the database. + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSuccess); + } +}; + +void LoopProcess(Core::System& system) { + auto server_manager = std::make_unique<ServerManager>(system); + + server_manager->RegisterNamedService("ngct:u", std::make_shared<NgctServiceImpl>(system)); + server_manager->RegisterNamedService("ngc:u", std::make_shared<NgcServiceImpl>(system)); + ServerManager::RunServer(std::move(server_manager)); +} + +} // namespace Service::NGC diff --git a/src/core/hle/service/ngct/ngct.h b/src/core/hle/service/ngc/ngc.h index 27c34dad4..823b1aa81 100644 --- a/src/core/hle/service/ngct/ngct.h +++ b/src/core/hle/service/ngc/ngc.h @@ -7,8 +7,8 @@ namespace Core { class System; } -namespace Service::NGCT { +namespace Service::NGC { void LoopProcess(Core::System& system); -} // namespace Service::NGCT +} // namespace Service::NGC diff --git a/src/core/hle/service/ngct/ngct.cpp b/src/core/hle/service/ngct/ngct.cpp deleted file mode 100644 index 493c80ed2..000000000 --- a/src/core/hle/service/ngct/ngct.cpp +++ /dev/null @@ -1,62 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include "common/string_util.h" -#include "core/core.h" -#include "core/hle/service/ipc_helpers.h" -#include "core/hle/service/ngct/ngct.h" -#include "core/hle/service/server_manager.h" -#include "core/hle/service/service.h" - -namespace Service::NGCT { - -class IService final : public ServiceFramework<IService> { -public: - explicit IService(Core::System& system_) : ServiceFramework{system_, "ngct:u"} { - // clang-format off - static const FunctionInfo functions[] = { - {0, &IService::Match, "Match"}, - {1, &IService::Filter, "Filter"}, - }; - // clang-format on - - RegisterHandlers(functions); - } - -private: - void Match(HLERequestContext& ctx) { - const auto buffer = ctx.ReadBuffer(); - const auto text = Common::StringFromFixedZeroTerminatedBuffer( - reinterpret_cast<const char*>(buffer.data()), buffer.size()); - - LOG_WARNING(Service_NGCT, "(STUBBED) called, text={}", text); - - IPC::ResponseBuilder rb{ctx, 3}; - rb.Push(ResultSuccess); - // Return false since we don't censor anything - rb.Push(false); - } - - void Filter(HLERequestContext& ctx) { - const auto buffer = ctx.ReadBuffer(); - const auto text = Common::StringFromFixedZeroTerminatedBuffer( - reinterpret_cast<const char*>(buffer.data()), buffer.size()); - - LOG_WARNING(Service_NGCT, "(STUBBED) called, text={}", text); - - // Return the same string since we don't censor anything - ctx.WriteBuffer(buffer); - - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ResultSuccess); - } -}; - -void LoopProcess(Core::System& system) { - auto server_manager = std::make_unique<ServerManager>(system); - - server_manager->RegisterNamedService("ngct:u", std::make_shared<IService>(system)); - ServerManager::RunServer(std::move(server_manager)); -} - -} // namespace Service::NGCT diff --git a/src/core/hle/service/ns/iplatform_service_manager.cpp b/src/core/hle/service/ns/iplatform_service_manager.cpp index 6c2f5e70b..46268be95 100644 --- a/src/core/hle/service/ns/iplatform_service_manager.cpp +++ b/src/core/hle/service/ns/iplatform_service_manager.cpp @@ -144,7 +144,7 @@ IPlatformServiceManager::IPlatformServiceManager(Core::System& system_, const ch {3, &IPlatformServiceManager::GetSharedMemoryAddressOffset, "GetSharedMemoryAddressOffset"}, {4, &IPlatformServiceManager::GetSharedMemoryNativeHandle, "GetSharedMemoryNativeHandle"}, {5, &IPlatformServiceManager::GetSharedFontInOrderOfPriority, "GetSharedFontInOrderOfPriority"}, - {6, nullptr, "GetSharedFontInOrderOfPriorityForSystem"}, + {6, &IPlatformServiceManager::GetSharedFontInOrderOfPriority, "GetSharedFontInOrderOfPriorityForSystem"}, {100, nullptr, "RequestApplicationFunctionAuthorization"}, {101, nullptr, "RequestApplicationFunctionAuthorizationByProcessId"}, {102, nullptr, "RequestApplicationFunctionAuthorizationByApplicationId"}, @@ -262,8 +262,17 @@ void IPlatformServiceManager::GetSharedMemoryNativeHandle(HLERequestContext& ctx } void IPlatformServiceManager::GetSharedFontInOrderOfPriority(HLERequestContext& ctx) { + // The maximum number of elements that can be returned is 6. Regardless of the available fonts + // or buffer size. + constexpr std::size_t MaxElementCount = 6; IPC::RequestParser rp{ctx}; const u64 language_code{rp.Pop<u64>()}; // TODO(ogniK): Find out what this is used for + const std::size_t font_codes_count = + std::min(MaxElementCount, ctx.GetWriteBufferNumElements<u32>(0)); + const std::size_t font_offsets_count = + std::min(MaxElementCount, ctx.GetWriteBufferNumElements<u32>(1)); + const std::size_t font_sizes_count = + std::min(MaxElementCount, ctx.GetWriteBufferNumElements<u32>(2)); LOG_DEBUG(Service_NS, "called, language_code={:X}", language_code); IPC::ResponseBuilder rb{ctx, 4}; @@ -280,9 +289,9 @@ void IPlatformServiceManager::GetSharedFontInOrderOfPriority(HLERequestContext& } // Resize buffers if game requests smaller size output - font_codes.resize(std::min(font_codes.size(), ctx.GetWriteBufferNumElements<u32>(0))); - font_offsets.resize(std::min(font_offsets.size(), ctx.GetWriteBufferNumElements<u32>(1))); - font_sizes.resize(std::min(font_sizes.size(), ctx.GetWriteBufferNumElements<u32>(2))); + font_codes.resize(std::min(font_codes.size(), font_codes_count)); + font_offsets.resize(std::min(font_offsets.size(), font_offsets_count)); + font_sizes.resize(std::min(font_sizes.size(), font_sizes_count)); ctx.WriteBuffer(font_codes, 0); ctx.WriteBuffer(font_offsets, 1); diff --git a/src/core/hle/service/nvdrv/core/nvmap.cpp b/src/core/hle/service/nvdrv/core/nvmap.cpp index a51ca5444..0ca05257e 100644 --- a/src/core/hle/service/nvdrv/core/nvmap.cpp +++ b/src/core/hle/service/nvdrv/core/nvmap.cpp @@ -160,8 +160,8 @@ u32 NvMap::PinHandle(NvMap::Handle::Id handle) { u32 address{}; auto& smmu_allocator = host1x.Allocator(); auto& smmu_memory_manager = host1x.MemoryManager(); - while (!(address = - smmu_allocator.Allocate(static_cast<u32>(handle_description->aligned_size)))) { + while ((address = smmu_allocator.Allocate( + static_cast<u32>(handle_description->aligned_size))) == 0) { // Free handles until the allocation succeeds std::scoped_lock queueLock(unmap_queue_lock); if (auto freeHandleDesc{unmap_queue.front()}) { diff --git a/src/core/hle/service/nvdrv/devices/nvmap.h b/src/core/hle/service/nvdrv/devices/nvmap.h index 40c65b430..4c0cc71cd 100644 --- a/src/core/hle/service/nvdrv/devices/nvmap.h +++ b/src/core/hle/service/nvdrv/devices/nvmap.h @@ -45,13 +45,6 @@ public: IsSharedMemMapped = 6 }; -private: - /// Id to use for the next handle that is created. - u32 next_handle = 0; - - /// Id to use for the next object that is created. - u32 next_id = 0; - struct IocCreateParams { // Input u32_le size{}; @@ -113,6 +106,13 @@ private: NvResult IocParam(std::span<const u8> input, std::span<u8> output); NvResult IocFree(std::span<const u8> input, std::span<u8> output); +private: + /// Id to use for the next handle that is created. + u32 next_handle = 0; + + /// Id to use for the next object that is created. + u32 next_id = 0; + NvCore::Container& container; NvCore::NvMap& file; }; diff --git a/src/core/hle/service/nvnflinger/buffer_item.h b/src/core/hle/service/nvnflinger/buffer_item.h index 7fd808f54..3da8cc3aa 100644 --- a/src/core/hle/service/nvnflinger/buffer_item.h +++ b/src/core/hle/service/nvnflinger/buffer_item.h @@ -15,7 +15,7 @@ namespace Service::android { -class GraphicBuffer; +struct GraphicBuffer; class BufferItem final { public: diff --git a/src/core/hle/service/nvnflinger/buffer_slot.h b/src/core/hle/service/nvnflinger/buffer_slot.h index d25bca049..d8c9dec3b 100644 --- a/src/core/hle/service/nvnflinger/buffer_slot.h +++ b/src/core/hle/service/nvnflinger/buffer_slot.h @@ -13,7 +13,7 @@ namespace Service::android { -class GraphicBuffer; +struct GraphicBuffer; enum class BufferState : u32 { Free = 0, diff --git a/src/core/hle/service/nvnflinger/fb_share_buffer_manager.cpp b/src/core/hle/service/nvnflinger/fb_share_buffer_manager.cpp new file mode 100644 index 000000000..469a53244 --- /dev/null +++ b/src/core/hle/service/nvnflinger/fb_share_buffer_manager.cpp @@ -0,0 +1,351 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include <random> + +#include "core/core.h" +#include "core/hle/kernel/k_process.h" +#include "core/hle/kernel/k_system_resource.h" +#include "core/hle/service/nvdrv/devices/nvmap.h" +#include "core/hle/service/nvdrv/nvdrv.h" +#include "core/hle/service/nvnflinger/buffer_queue_producer.h" +#include "core/hle/service/nvnflinger/fb_share_buffer_manager.h" +#include "core/hle/service/nvnflinger/pixel_format.h" +#include "core/hle/service/nvnflinger/ui/graphic_buffer.h" +#include "core/hle/service/vi/layer/vi_layer.h" +#include "core/hle/service/vi/vi_results.h" + +namespace Service::Nvnflinger { + +namespace { + +Result AllocateIoForProcessAddressSpace(Common::ProcessAddress* out_map_address, + std::unique_ptr<Kernel::KPageGroup>* out_page_group, + Core::System& system, u32 size) { + using Core::Memory::YUZU_PAGESIZE; + + // Allocate memory for the system shared buffer. + // FIXME: Because the gmmu can only point to cpu addresses, we need + // to map this in the application space to allow it to be used. + // FIXME: Add proper smmu emulation. + // FIXME: This memory belongs to vi's .data section. + auto& kernel = system.Kernel(); + auto* process = system.ApplicationProcess(); + auto& page_table = process->GetPageTable(); + + // Hold a temporary page group reference while we try to map it. + auto pg = std::make_unique<Kernel::KPageGroup>( + kernel, std::addressof(kernel.GetSystemSystemResource().GetBlockInfoManager())); + + // Allocate memory from secure pool. + R_TRY(kernel.MemoryManager().AllocateAndOpen( + pg.get(), size / YUZU_PAGESIZE, + Kernel::KMemoryManager::EncodeOption(Kernel::KMemoryManager::Pool::Secure, + Kernel::KMemoryManager::Direction::FromBack))); + + // Get bounds of where mapping is possible. + const VAddr alias_code_begin = GetInteger(page_table.GetAliasCodeRegionStart()); + const VAddr alias_code_size = page_table.GetAliasCodeRegionSize() / YUZU_PAGESIZE; + const auto state = Kernel::KMemoryState::Io; + const auto perm = Kernel::KMemoryPermission::UserReadWrite; + std::mt19937_64 rng{process->GetRandomEntropy(0)}; + + // Retry up to 64 times to map into alias code range. + Result res = ResultSuccess; + int i; + for (i = 0; i < 64; i++) { + *out_map_address = alias_code_begin + ((rng() % alias_code_size) * YUZU_PAGESIZE); + res = page_table.MapPageGroup(*out_map_address, *pg, state, perm); + if (R_SUCCEEDED(res)) { + break; + } + } + + // Return failure, if necessary + R_UNLESS(i < 64, res); + + // Return the mapped page group. + *out_page_group = std::move(pg); + + // We succeeded. + R_SUCCEED(); +} + +template <typename T> +std::span<u8> SerializeIoc(T& params) { + return std::span(reinterpret_cast<u8*>(std::addressof(params)), sizeof(T)); +} + +Result CreateNvMapHandle(u32* out_nv_map_handle, Nvidia::Devices::nvmap& nvmap, u32 size) { + // Create a handle. + Nvidia::Devices::nvmap::IocCreateParams create_in_params{ + .size = size, + .handle = 0, + }; + Nvidia::Devices::nvmap::IocCreateParams create_out_params{}; + R_UNLESS(nvmap.IocCreate(SerializeIoc(create_in_params), SerializeIoc(create_out_params)) == + Nvidia::NvResult::Success, + VI::ResultOperationFailed); + + // Assign the output handle. + *out_nv_map_handle = create_out_params.handle; + + // We succeeded. + R_SUCCEED(); +} + +Result FreeNvMapHandle(Nvidia::Devices::nvmap& nvmap, u32 handle) { + // Free the handle. + Nvidia::Devices::nvmap::IocFreeParams free_in_params{ + .handle = handle, + }; + Nvidia::Devices::nvmap::IocFreeParams free_out_params{}; + R_UNLESS(nvmap.IocFree(SerializeIoc(free_in_params), SerializeIoc(free_out_params)) == + Nvidia::NvResult::Success, + VI::ResultOperationFailed); + + // We succeeded. + R_SUCCEED(); +} + +Result AllocNvMapHandle(Nvidia::Devices::nvmap& nvmap, u32 handle, Common::ProcessAddress buffer, + u32 size) { + // Assign the allocated memory to the handle. + Nvidia::Devices::nvmap::IocAllocParams alloc_in_params{ + .handle = handle, + .heap_mask = 0, + .flags = {}, + .align = 0, + .kind = 0, + .address = GetInteger(buffer), + }; + Nvidia::Devices::nvmap::IocAllocParams alloc_out_params{}; + R_UNLESS(nvmap.IocAlloc(SerializeIoc(alloc_in_params), SerializeIoc(alloc_out_params)) == + Nvidia::NvResult::Success, + VI::ResultOperationFailed); + + // We succeeded. + R_SUCCEED(); +} + +Result AllocateHandleForBuffer(u32* out_handle, Nvidia::Module& nvdrv, + Common::ProcessAddress buffer, u32 size) { + // Get the nvmap device. + auto nvmap_fd = nvdrv.Open("/dev/nvmap"); + auto nvmap = nvdrv.GetDevice<Nvidia::Devices::nvmap>(nvmap_fd); + ASSERT(nvmap != nullptr); + + // Create a handle. + R_TRY(CreateNvMapHandle(out_handle, *nvmap, size)); + + // Ensure we maintain a clean state on failure. + ON_RESULT_FAILURE { + ASSERT(R_SUCCEEDED(FreeNvMapHandle(*nvmap, *out_handle))); + }; + + // Assign the allocated memory to the handle. + R_RETURN(AllocNvMapHandle(*nvmap, *out_handle, buffer, size)); +} + +constexpr auto SharedBufferBlockLinearFormat = android::PixelFormat::Rgba8888; +constexpr u32 SharedBufferBlockLinearBpp = 4; + +constexpr u32 SharedBufferBlockLinearWidth = 1280; +constexpr u32 SharedBufferBlockLinearHeight = 768; +constexpr u32 SharedBufferBlockLinearStride = + SharedBufferBlockLinearWidth * SharedBufferBlockLinearBpp; +constexpr u32 SharedBufferNumSlots = 7; + +constexpr u32 SharedBufferWidth = 1280; +constexpr u32 SharedBufferHeight = 720; +constexpr u32 SharedBufferAsync = false; + +constexpr u32 SharedBufferSlotSize = + SharedBufferBlockLinearWidth * SharedBufferBlockLinearHeight * SharedBufferBlockLinearBpp; +constexpr u32 SharedBufferSize = SharedBufferSlotSize * SharedBufferNumSlots; + +constexpr SharedMemoryPoolLayout SharedBufferPoolLayout = [] { + SharedMemoryPoolLayout layout{}; + layout.num_slots = SharedBufferNumSlots; + + for (u32 i = 0; i < SharedBufferNumSlots; i++) { + layout.slots[i].buffer_offset = i * SharedBufferSlotSize; + layout.slots[i].size = SharedBufferSlotSize; + layout.slots[i].width = SharedBufferWidth; + layout.slots[i].height = SharedBufferHeight; + } + + return layout; +}(); + +void MakeGraphicBuffer(android::BufferQueueProducer& producer, u32 slot, u32 handle) { + auto buffer = std::make_shared<android::GraphicBuffer>(); + buffer->width = SharedBufferWidth; + buffer->height = SharedBufferHeight; + buffer->stride = SharedBufferBlockLinearStride; + buffer->format = SharedBufferBlockLinearFormat; + buffer->buffer_id = handle; + buffer->offset = slot * SharedBufferSlotSize; + ASSERT(producer.SetPreallocatedBuffer(slot, buffer) == android::Status::NoError); +} + +} // namespace + +FbShareBufferManager::FbShareBufferManager(Core::System& system, Nvnflinger& flinger, + std::shared_ptr<Nvidia::Module> nvdrv) + : m_system(system), m_flinger(flinger), m_nvdrv(std::move(nvdrv)) {} + +FbShareBufferManager::~FbShareBufferManager() = default; + +Result FbShareBufferManager::Initialize(u64* out_buffer_id, u64* out_layer_id, u64 display_id) { + std::scoped_lock lk{m_guard}; + + // Ensure we have not already created a buffer. + R_UNLESS(m_buffer_id == 0, VI::ResultOperationFailed); + + // Allocate memory and space for the shared buffer. + Common::ProcessAddress map_address; + R_TRY(AllocateIoForProcessAddressSpace(std::addressof(map_address), + std::addressof(m_buffer_page_group), m_system, + SharedBufferSize)); + + // Create an nvmap handle for the buffer and assign the memory to it. + R_TRY(AllocateHandleForBuffer(std::addressof(m_buffer_nvmap_handle), *m_nvdrv, map_address, + SharedBufferSize)); + + // Record the display id. + m_display_id = display_id; + + // Create a layer for the display. + m_layer_id = m_flinger.CreateLayer(m_display_id).value(); + + // Set up the buffer. + m_buffer_id = m_next_buffer_id++; + + // Get the layer. + VI::Layer* layer = m_flinger.FindLayer(m_display_id, m_layer_id); + ASSERT(layer != nullptr); + + // Get the producer and set preallocated buffers. + auto& producer = layer->GetBufferQueue(); + MakeGraphicBuffer(producer, 0, m_buffer_nvmap_handle); + MakeGraphicBuffer(producer, 1, m_buffer_nvmap_handle); + + // Assign outputs. + *out_buffer_id = m_buffer_id; + *out_layer_id = m_layer_id; + + // We succeeded. + R_SUCCEED(); +} + +Result FbShareBufferManager::GetSharedBufferMemoryHandleId(u64* out_buffer_size, + s32* out_nvmap_handle, + SharedMemoryPoolLayout* out_pool_layout, + u64 buffer_id, + u64 applet_resource_user_id) { + std::scoped_lock lk{m_guard}; + + R_UNLESS(m_buffer_id > 0, VI::ResultNotFound); + R_UNLESS(buffer_id == m_buffer_id, VI::ResultNotFound); + + *out_pool_layout = SharedBufferPoolLayout; + *out_buffer_size = SharedBufferSize; + *out_nvmap_handle = m_buffer_nvmap_handle; + + R_SUCCEED(); +} + +Result FbShareBufferManager::GetLayerFromId(VI::Layer** out_layer, u64 layer_id) { + // Ensure the layer id is valid. + R_UNLESS(m_layer_id > 0 && layer_id == m_layer_id, VI::ResultNotFound); + + // Get the layer. + VI::Layer* layer = m_flinger.FindLayer(m_display_id, layer_id); + R_UNLESS(layer != nullptr, VI::ResultNotFound); + + // We succeeded. + *out_layer = layer; + R_SUCCEED(); +} + +Result FbShareBufferManager::AcquireSharedFrameBuffer(android::Fence* out_fence, + std::array<s32, 4>& out_slot_indexes, + s64* out_target_slot, u64 layer_id) { + std::scoped_lock lk{m_guard}; + + // Get the layer. + VI::Layer* layer; + R_TRY(this->GetLayerFromId(std::addressof(layer), layer_id)); + + // Get the producer. + auto& producer = layer->GetBufferQueue(); + + // Get the next buffer from the producer. + s32 slot; + R_UNLESS(producer.DequeueBuffer(std::addressof(slot), out_fence, SharedBufferAsync != 0, + SharedBufferWidth, SharedBufferHeight, + SharedBufferBlockLinearFormat, 0) == android::Status::NoError, + VI::ResultOperationFailed); + + // Assign remaining outputs. + *out_target_slot = slot; + out_slot_indexes = {0, 1, -1, -1}; + + // We succeeded. + R_SUCCEED(); +} + +Result FbShareBufferManager::PresentSharedFrameBuffer(android::Fence fence, + Common::Rectangle<s32> crop_region, + u32 transform, s32 swap_interval, + u64 layer_id, s64 slot) { + std::scoped_lock lk{m_guard}; + + // Get the layer. + VI::Layer* layer; + R_TRY(this->GetLayerFromId(std::addressof(layer), layer_id)); + + // Get the producer. + auto& producer = layer->GetBufferQueue(); + + // Request to queue the buffer. + std::shared_ptr<android::GraphicBuffer> buffer; + R_UNLESS(producer.RequestBuffer(static_cast<s32>(slot), std::addressof(buffer)) == + android::Status::NoError, + VI::ResultOperationFailed); + + // Queue the buffer to the producer. + android::QueueBufferInput input{}; + android::QueueBufferOutput output{}; + input.crop = crop_region; + input.fence = fence; + input.transform = static_cast<android::NativeWindowTransform>(transform); + input.swap_interval = swap_interval; + R_UNLESS(producer.QueueBuffer(static_cast<s32>(slot), input, std::addressof(output)) == + android::Status::NoError, + VI::ResultOperationFailed); + + // We succeeded. + R_SUCCEED(); +} + +Result FbShareBufferManager::GetSharedFrameBufferAcquirableEvent(Kernel::KReadableEvent** out_event, + u64 layer_id) { + std::scoped_lock lk{m_guard}; + + // Get the layer. + VI::Layer* layer; + R_TRY(this->GetLayerFromId(std::addressof(layer), layer_id)); + + // Get the producer. + auto& producer = layer->GetBufferQueue(); + + // Set the event. + *out_event = std::addressof(producer.GetNativeHandle()); + + // We succeeded. + R_SUCCEED(); +} + +} // namespace Service::Nvnflinger diff --git a/src/core/hle/service/nvnflinger/fb_share_buffer_manager.h b/src/core/hle/service/nvnflinger/fb_share_buffer_manager.h new file mode 100644 index 000000000..c809c01b4 --- /dev/null +++ b/src/core/hle/service/nvnflinger/fb_share_buffer_manager.h @@ -0,0 +1,65 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "common/math_util.h" +#include "core/hle/service/nvnflinger/nvnflinger.h" +#include "core/hle/service/nvnflinger/ui/fence.h" + +namespace Kernel { +class KPageGroup; +} + +namespace Service::Nvnflinger { + +struct SharedMemorySlot { + u64 buffer_offset; + u64 size; + s32 width; + s32 height; +}; +static_assert(sizeof(SharedMemorySlot) == 0x18, "SharedMemorySlot has wrong size"); + +struct SharedMemoryPoolLayout { + s32 num_slots; + std::array<SharedMemorySlot, 0x10> slots; +}; +static_assert(sizeof(SharedMemoryPoolLayout) == 0x188, "SharedMemoryPoolLayout has wrong size"); + +class FbShareBufferManager final { +public: + explicit FbShareBufferManager(Core::System& system, Nvnflinger& flinger, + std::shared_ptr<Nvidia::Module> nvdrv); + ~FbShareBufferManager(); + + Result Initialize(u64* out_buffer_id, u64* out_layer_handle, u64 display_id); + Result GetSharedBufferMemoryHandleId(u64* out_buffer_size, s32* out_nvmap_handle, + SharedMemoryPoolLayout* out_pool_layout, u64 buffer_id, + u64 applet_resource_user_id); + Result AcquireSharedFrameBuffer(android::Fence* out_fence, std::array<s32, 4>& out_slots, + s64* out_target_slot, u64 layer_id); + Result PresentSharedFrameBuffer(android::Fence fence, Common::Rectangle<s32> crop_region, + u32 transform, s32 swap_interval, u64 layer_id, s64 slot); + Result GetSharedFrameBufferAcquirableEvent(Kernel::KReadableEvent** out_event, u64 layer_id); + +private: + Result GetLayerFromId(VI::Layer** out_layer, u64 layer_id); + +private: + u64 m_next_buffer_id = 1; + u64 m_display_id = 0; + u64 m_buffer_id = 0; + u64 m_layer_id = 0; + u32 m_buffer_nvmap_handle = 0; + SharedMemoryPoolLayout m_pool_layout = {}; + + std::unique_ptr<Kernel::KPageGroup> m_buffer_page_group; + + std::mutex m_guard; + Core::System& m_system; + Nvnflinger& m_flinger; + std::shared_ptr<Nvidia::Module> m_nvdrv; +}; + +} // namespace Service::Nvnflinger diff --git a/src/core/hle/service/nvnflinger/graphic_buffer_producer.h b/src/core/hle/service/nvnflinger/graphic_buffer_producer.h index 21d7b31f3..5d7cff7d3 100644 --- a/src/core/hle/service/nvnflinger/graphic_buffer_producer.h +++ b/src/core/hle/service/nvnflinger/graphic_buffer_producer.h @@ -19,6 +19,7 @@ class InputParcel; #pragma pack(push, 1) struct QueueBufferInput final { explicit QueueBufferInput(InputParcel& parcel); + explicit QueueBufferInput() = default; void Deflate(s64* timestamp_, bool* is_auto_timestamp_, Common::Rectangle<s32>* crop_, NativeWindowScalingMode* scaling_mode_, NativeWindowTransform* transform_, @@ -34,7 +35,6 @@ struct QueueBufferInput final { *fence_ = fence; } -private: s64 timestamp{}; s32 is_auto_timestamp{}; Common::Rectangle<s32> crop{}; diff --git a/src/core/hle/service/nvnflinger/nvnflinger.cpp b/src/core/hle/service/nvnflinger/nvnflinger.cpp index 21f31f7a0..a07c621d9 100644 --- a/src/core/hle/service/nvnflinger/nvnflinger.cpp +++ b/src/core/hle/service/nvnflinger/nvnflinger.cpp @@ -17,6 +17,7 @@ #include "core/hle/service/nvdrv/nvdrv.h" #include "core/hle/service/nvnflinger/buffer_item_consumer.h" #include "core/hle/service/nvnflinger/buffer_queue_core.h" +#include "core/hle/service/nvnflinger/fb_share_buffer_manager.h" #include "core/hle/service/nvnflinger/hos_binder_driver_server.h" #include "core/hle/service/nvnflinger/nvnflinger.h" #include "core/hle/service/nvnflinger/ui/graphic_buffer.h" @@ -331,4 +332,14 @@ s64 Nvnflinger::GetNextTicks() const { return static_cast<s64>(speed_scale * (1000000000.f / effective_fps)); } +FbShareBufferManager& Nvnflinger::GetSystemBufferManager() { + const auto lock_guard = Lock(); + + if (!system_buffer_manager) { + system_buffer_manager = std::make_unique<FbShareBufferManager>(system, *this, nvdrv); + } + + return *system_buffer_manager; +} + } // namespace Service::Nvnflinger diff --git a/src/core/hle/service/nvnflinger/nvnflinger.h b/src/core/hle/service/nvnflinger/nvnflinger.h index f478c2bc6..14c783582 100644 --- a/src/core/hle/service/nvnflinger/nvnflinger.h +++ b/src/core/hle/service/nvnflinger/nvnflinger.h @@ -45,6 +45,9 @@ class BufferQueueProducer; namespace Service::Nvnflinger { +class FbShareBufferManager; +class HosBinderDriverServer; + class Nvnflinger final { public: explicit Nvnflinger(Core::System& system_, HosBinderDriverServer& hos_binder_driver_server_); @@ -90,12 +93,16 @@ public: [[nodiscard]] s64 GetNextTicks() const; + FbShareBufferManager& GetSystemBufferManager(); + private: struct Layer { std::unique_ptr<android::BufferQueueCore> core; std::unique_ptr<android::BufferQueueProducer> producer; }; + friend class FbShareBufferManager; + private: [[nodiscard]] std::unique_lock<std::mutex> Lock() const { return std::unique_lock{*guard}; @@ -140,6 +147,8 @@ private: std::shared_ptr<Core::Timing::EventType> multi_composition_event; std::shared_ptr<Core::Timing::EventType> single_composition_event; + std::unique_ptr<FbShareBufferManager> system_buffer_manager; + std::shared_ptr<std::mutex> guard; Core::System& system; diff --git a/src/core/hle/service/nvnflinger/ui/fence.h b/src/core/hle/service/nvnflinger/ui/fence.h index 536e8156d..177aed758 100644 --- a/src/core/hle/service/nvnflinger/ui/fence.h +++ b/src/core/hle/service/nvnflinger/ui/fence.h @@ -20,6 +20,9 @@ public: static constexpr Fence NoFence() { Fence fence; fence.fences[0].id = -1; + fence.fences[1].id = -1; + fence.fences[2].id = -1; + fence.fences[3].id = -1; return fence; } diff --git a/src/core/hle/service/nvnflinger/ui/graphic_buffer.h b/src/core/hle/service/nvnflinger/ui/graphic_buffer.h index 75d1705a8..3eac5cedd 100644 --- a/src/core/hle/service/nvnflinger/ui/graphic_buffer.h +++ b/src/core/hle/service/nvnflinger/ui/graphic_buffer.h @@ -12,8 +12,7 @@ namespace Service::android { -class GraphicBuffer final { -public: +struct GraphicBuffer final { constexpr GraphicBuffer() = default; constexpr GraphicBuffer(u32 width_, u32 height_, PixelFormat format_, u32 usage_) @@ -77,7 +76,6 @@ public: return false; } -private: u32 magic{}; s32 width{}; s32 height{}; diff --git a/src/core/hle/service/service.cpp b/src/core/hle/service/service.cpp index 69cdb5918..0ad607391 100644 --- a/src/core/hle/service/service.cpp +++ b/src/core/hle/service/service.cpp @@ -43,7 +43,7 @@ #include "core/hle/service/ncm/ncm.h" #include "core/hle/service/nfc/nfc.h" #include "core/hle/service/nfp/nfp.h" -#include "core/hle/service/ngct/ngct.h" +#include "core/hle/service/ngc/ngc.h" #include "core/hle/service/nifm/nifm.h" #include "core/hle/service/nim/nim.h" #include "core/hle/service/npns/npns.h" @@ -257,7 +257,7 @@ Services::Services(std::shared_ptr<SM::ServiceManager>& sm, Core::System& system kernel.RunOnGuestCoreProcess("NCM", [&] { NCM::LoopProcess(system); }); kernel.RunOnGuestCoreProcess("nfc", [&] { NFC::LoopProcess(system); }); kernel.RunOnGuestCoreProcess("nfp", [&] { NFP::LoopProcess(system); }); - kernel.RunOnGuestCoreProcess("ngct", [&] { NGCT::LoopProcess(system); }); + kernel.RunOnGuestCoreProcess("ngc", [&] { NGC::LoopProcess(system); }); kernel.RunOnGuestCoreProcess("nifm", [&] { NIFM::LoopProcess(system); }); kernel.RunOnGuestCoreProcess("nim", [&] { NIM::LoopProcess(system); }); kernel.RunOnGuestCoreProcess("npns", [&] { NPNS::LoopProcess(system); }); diff --git a/src/core/hle/service/sockets/bsd.cpp b/src/core/hle/service/sockets/bsd.cpp index d8509c1dd..85849d5f3 100644 --- a/src/core/hle/service/sockets/bsd.cpp +++ b/src/core/hle/service/sockets/bsd.cpp @@ -170,7 +170,7 @@ void BSD::Socket(HLERequestContext& ctx) { } void BSD::Select(HLERequestContext& ctx) { - LOG_WARNING(Service, "(STUBBED) called"); + LOG_DEBUG(Service, "(STUBBED) called"); IPC::ResponseBuilder rb{ctx, 4}; diff --git a/src/core/hle/service/sockets/nsd.cpp b/src/core/hle/service/sockets/nsd.cpp index bac21752a..491b76d48 100644 --- a/src/core/hle/service/sockets/nsd.cpp +++ b/src/core/hle/service/sockets/nsd.cpp @@ -19,6 +19,12 @@ enum class ServerEnvironmentType : u8 { Dp, }; +// This is nn::nsd::EnvironmentIdentifier +struct EnvironmentIdentifier { + std::array<u8, 8> identifier; +}; +static_assert(sizeof(EnvironmentIdentifier) == 0x8); + NSD::NSD(Core::System& system_, const char* name) : ServiceFramework{system_, name} { // clang-format off static const FunctionInfo functions[] = { @@ -101,8 +107,9 @@ void NSD::ResolveEx(HLERequestContext& ctx) { } void NSD::GetEnvironmentIdentifier(HLERequestContext& ctx) { - const std::string environment_identifier = "lp1"; - ctx.WriteBuffer(environment_identifier); + constexpr EnvironmentIdentifier lp1 = { + .identifier = {'l', 'p', '1', '\0', '\0', '\0', '\0', '\0'}}; + ctx.WriteBuffer(lp1); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(ResultSuccess); diff --git a/src/core/hle/service/sockets/sfdnsres.cpp b/src/core/hle/service/sockets/sfdnsres.cpp index 22e4a6f49..c657c4efd 100644 --- a/src/core/hle/service/sockets/sfdnsres.cpp +++ b/src/core/hle/service/sockets/sfdnsres.cpp @@ -150,6 +150,12 @@ static std::pair<u32, GetAddrInfoError> GetHostByNameRequestImpl(HLERequestConte const std::string host = Common::StringFromBuffer(host_buffer); // For now, ignore options, which are in input buffer 1 for GetHostByNameRequestWithOptions. + // Prevent resolution of Nintendo servers + if (host.find("srv.nintendo.net") != std::string::npos) { + LOG_WARNING(Network, "Resolution of hostname {} requested, returning EAI_AGAIN", host); + return {0, GetAddrInfoError::AGAIN}; + } + auto res = Network::GetAddressInfo(host, /*service*/ std::nullopt); if (!res.has_value()) { return {0, Translate(res.error())}; @@ -261,6 +267,12 @@ static std::pair<u32, GetAddrInfoError> GetAddrInfoRequestImpl(HLERequestContext const auto host_buffer = ctx.ReadBuffer(0); const std::string host = Common::StringFromBuffer(host_buffer); + // Prevent resolution of Nintendo servers + if (host.find("srv.nintendo.net") != std::string::npos) { + LOG_WARNING(Network, "Resolution of hostname {} requested, returning EAI_AGAIN", host); + return {0, GetAddrInfoError::AGAIN}; + } + std::optional<std::string> service = std::nullopt; if (ctx.CanReadBuffer(1)) { const std::span<const u8> service_buffer = ctx.ReadBuffer(1); diff --git a/src/core/hle/service/ssl/ssl_backend_schannel.cpp b/src/core/hle/service/ssl/ssl_backend_schannel.cpp index d834a0c1f..212057cfc 100644 --- a/src/core/hle/service/ssl/ssl_backend_schannel.cpp +++ b/src/core/hle/service/ssl/ssl_backend_schannel.cpp @@ -477,7 +477,8 @@ public: return ResultInternalError; } PCCERT_CONTEXT some_cert = nullptr; - while ((some_cert = CertEnumCertificatesInStore(returned_cert->hCertStore, some_cert))) { + while ((some_cert = CertEnumCertificatesInStore(returned_cert->hCertStore, some_cert)) != + nullptr) { out_certs->emplace_back(static_cast<u8*>(some_cert->pbCertEncoded), static_cast<u8*>(some_cert->pbCertEncoded) + some_cert->cbCertEncoded); diff --git a/src/core/hle/service/vi/vi.cpp b/src/core/hle/service/vi/vi.cpp index 2eb978379..b1bfb9898 100644 --- a/src/core/hle/service/vi/vi.cpp +++ b/src/core/hle/service/vi/vi.cpp @@ -20,9 +20,12 @@ #include "core/hle/kernel/k_readable_event.h" #include "core/hle/kernel/k_thread.h" #include "core/hle/service/ipc_helpers.h" +#include "core/hle/service/nvdrv/devices/nvmap.h" #include "core/hle/service/nvdrv/nvdata.h" +#include "core/hle/service/nvdrv/nvdrv.h" #include "core/hle/service/nvnflinger/binder.h" #include "core/hle/service/nvnflinger/buffer_queue_producer.h" +#include "core/hle/service/nvnflinger/fb_share_buffer_manager.h" #include "core/hle/service/nvnflinger/hos_binder_driver_server.h" #include "core/hle/service/nvnflinger/nvnflinger.h" #include "core/hle/service/nvnflinger/parcel.h" @@ -131,8 +134,9 @@ private: class ISystemDisplayService final : public ServiceFramework<ISystemDisplayService> { public: - explicit ISystemDisplayService(Core::System& system_) - : ServiceFramework{system_, "ISystemDisplayService"} { + explicit ISystemDisplayService(Core::System& system_, Nvnflinger::Nvnflinger& nvnflinger_) + : ServiceFramework{system_, "ISystemDisplayService"}, nvnflinger{nvnflinger_} { + // clang-format off static const FunctionInfo functions[] = { {1200, nullptr, "GetZOrderCountMin"}, {1202, nullptr, "GetZOrderCountMax"}, @@ -170,22 +174,126 @@ public: {3217, nullptr, "SetDisplayCmuLuma"}, {3218, nullptr, "SetDisplayCrcMode"}, {6013, nullptr, "GetLayerPresentationSubmissionTimestamps"}, - {8225, nullptr, "GetSharedBufferMemoryHandleId"}, - {8250, nullptr, "OpenSharedLayer"}, + {8225, &ISystemDisplayService::GetSharedBufferMemoryHandleId, "GetSharedBufferMemoryHandleId"}, + {8250, &ISystemDisplayService::OpenSharedLayer, "OpenSharedLayer"}, {8251, nullptr, "CloseSharedLayer"}, - {8252, nullptr, "ConnectSharedLayer"}, + {8252, &ISystemDisplayService::ConnectSharedLayer, "ConnectSharedLayer"}, {8253, nullptr, "DisconnectSharedLayer"}, - {8254, nullptr, "AcquireSharedFrameBuffer"}, - {8255, nullptr, "PresentSharedFrameBuffer"}, - {8256, nullptr, "GetSharedFrameBufferAcquirableEvent"}, + {8254, &ISystemDisplayService::AcquireSharedFrameBuffer, "AcquireSharedFrameBuffer"}, + {8255, &ISystemDisplayService::PresentSharedFrameBuffer, "PresentSharedFrameBuffer"}, + {8256, &ISystemDisplayService::GetSharedFrameBufferAcquirableEvent, "GetSharedFrameBufferAcquirableEvent"}, {8257, nullptr, "FillSharedFrameBufferColor"}, {8258, nullptr, "CancelSharedFrameBuffer"}, {9000, nullptr, "GetDp2hdmiController"}, }; + // clang-format on RegisterHandlers(functions); } private: + void GetSharedBufferMemoryHandleId(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const u64 buffer_id = rp.PopRaw<u64>(); + + LOG_INFO(Service_VI, "called. buffer_id={:#x}", buffer_id); + + struct OutputParameters { + s32 nvmap_handle; + u64 size; + }; + + OutputParameters out{}; + Nvnflinger::SharedMemoryPoolLayout layout{}; + const auto result = nvnflinger.GetSystemBufferManager().GetSharedBufferMemoryHandleId( + &out.size, &out.nvmap_handle, &layout, buffer_id, 0); + + ctx.WriteBuffer(&layout, sizeof(layout)); + + IPC::ResponseBuilder rb{ctx, 6}; + rb.Push(result); + rb.PushRaw(out); + } + + void OpenSharedLayer(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const u64 layer_id = rp.PopRaw<u64>(); + + LOG_INFO(Service_VI, "(STUBBED) called. layer_id={:#x}", layer_id); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSuccess); + } + + void ConnectSharedLayer(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const u64 layer_id = rp.PopRaw<u64>(); + + LOG_INFO(Service_VI, "(STUBBED) called. layer_id={:#x}", layer_id); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSuccess); + } + + void GetSharedFrameBufferAcquirableEvent(HLERequestContext& ctx) { + LOG_DEBUG(Service_VI, "called"); + + IPC::RequestParser rp{ctx}; + const u64 layer_id = rp.PopRaw<u64>(); + + Kernel::KReadableEvent* event{}; + const auto result = nvnflinger.GetSystemBufferManager().GetSharedFrameBufferAcquirableEvent( + &event, layer_id); + + IPC::ResponseBuilder rb{ctx, 2, 1}; + rb.Push(result); + rb.PushCopyObjects(event); + } + + void AcquireSharedFrameBuffer(HLERequestContext& ctx) { + LOG_DEBUG(Service_VI, "called"); + + IPC::RequestParser rp{ctx}; + const u64 layer_id = rp.PopRaw<u64>(); + + struct OutputParameters { + android::Fence fence; + std::array<s32, 4> slots; + s64 target_slot; + }; + static_assert(sizeof(OutputParameters) == 0x40, "OutputParameters has wrong size"); + + OutputParameters out{}; + const auto result = nvnflinger.GetSystemBufferManager().AcquireSharedFrameBuffer( + &out.fence, out.slots, &out.target_slot, layer_id); + + IPC::ResponseBuilder rb{ctx, 18}; + rb.Push(result); + rb.PushRaw(out); + } + + void PresentSharedFrameBuffer(HLERequestContext& ctx) { + LOG_DEBUG(Service_VI, "called"); + + struct InputParameters { + android::Fence fence; + Common::Rectangle<s32> crop_region; + u32 window_transform; + s32 swap_interval; + u64 layer_id; + s64 surface_id; + }; + static_assert(sizeof(InputParameters) == 0x50, "InputParameters has wrong size"); + + IPC::RequestParser rp{ctx}; + auto input = rp.PopRaw<InputParameters>(); + + const auto result = nvnflinger.GetSystemBufferManager().PresentSharedFrameBuffer( + input.fence, input.crop_region, input.window_transform, input.swap_interval, + input.layer_id, input.surface_id); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(result); + } + void SetLayerZ(HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const u64 layer_id = rp.Pop<u64>(); @@ -228,6 +336,9 @@ private: rb.PushRaw<float>(60.0f); // This wouldn't seem to be correct for 30 fps games. rb.Push<u32>(0); } + +private: + Nvnflinger::Nvnflinger& nvnflinger; }; class IManagerDisplayService final : public ServiceFramework<IManagerDisplayService> { @@ -453,7 +564,7 @@ private: IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(ResultSuccess); - rb.PushIpcInterface<ISystemDisplayService>(system); + rb.PushIpcInterface<ISystemDisplayService>(system, nv_flinger); } void GetManagerDisplayService(HLERequestContext& ctx) { |