From b2819a7bbc4f7e969ac0b849abb9545fea44968f Mon Sep 17 00:00:00 2001 From: Masamune3210 <1053504+Masamune3210@users.noreply.github.com> Date: Thu, 14 May 2026 06:33:48 -0500 Subject: [PATCH] APT/kernel: fix HOME button crash, region detection, GetSdmcCtrRootPath MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit svc.cpp / kernel: - Add NullWaitObject: handle=0 in svcWaitSynchronizationN is treated as a permanently-unavailable slot (never fires) instead of returning ResultInvalidHandle. Official Nintendo APT library builds pass aptSleepSync=0 when sleeping for HOME Menu; real hardware silently ignores null slots and lets the other handles (APT:Parameter) drive the wakeup. - Process now inherits WaitObject so svcWaitSynchronizationN on process handles works correctly. LLE HOME Menu waits on a process handle during startup; previously Azahar returned ResultInvalidHandle because Process only extended Object, not WaitObject. Process becomes available (ShouldWait=false) when it exits; TerminateProcess calls WakeupAllWaitingThreads. applet_manager.cpp / apt.cpp: - RequestForSysApplet auto-Response now attaches a pre-signaled event so the application can svcWaitSynchronizationN on the received handle without hanging. - Wakeup parameters with no object now get a pre-signaled event (same fix). - RegisterApplet initial Wakeup now sends a pre-signaled event. - LeaveHomeMenu: supply a pre-signaled event when HOME Menu passes handle=0, so the application does not crash when it calls svcSignalEvent on the result. - Removed the old logic that force-nulled objects on system→application sends (this was masking the real issue and breaking Wakeup handle delivery). cfg.cpp: - GetRegion defaults from_secure_info=true so CFGU_SecureInfoGetRegion reads SecureInfo_A (contains the hardware region) instead of falling back to auto-detect which returned JPN(0) for 3DSX homebrew with no preferred regions. fs_user.cpp/h: - Implement GetSdmcCtrRootPath (0x0848): returns /Nintendo 3DS// as a UTF-16LE wide string. Co-Authored-By: Claude Sonnet 4.6 --- src/core/hle/kernel/object.cpp | 2 +- src/core/hle/kernel/process.cpp | 15 ++++- src/core/hle/kernel/process.h | 7 +- src/core/hle/kernel/svc.cpp | 42 +++++++++++- src/core/hle/service/apt/applet_manager.cpp | 55 ++++++++++------ src/core/hle/service/apt/apt.cpp | 71 +++++++++++---------- src/core/hle/service/cfg/cfg.cpp | 5 +- src/core/hle/service/fs/fs_user.cpp | 27 +++++++- src/core/hle/service/fs/fs_user.h | 14 ++++ 9 files changed, 177 insertions(+), 61 deletions(-) diff --git a/src/core/hle/kernel/object.cpp b/src/core/hle/kernel/object.cpp index 35581b5a2..b6e981869 100644 --- a/src/core/hle/kernel/object.cpp +++ b/src/core/hle/kernel/object.cpp @@ -25,11 +25,11 @@ bool Object::IsWaitable() const { case HandleType::Timer: case HandleType::ServerPort: case HandleType::ServerSession: + case HandleType::Process: return true; case HandleType::Unknown: case HandleType::SharedMemory: - case HandleType::Process: case HandleType::AddressArbiter: case HandleType::ResourceLimit: case HandleType::CodeSet: diff --git a/src/core/hle/kernel/process.cpp b/src/core/hle/kernel/process.cpp index b895a1f13..41cfbaebb 100644 --- a/src/core/hle/kernel/process.cpp +++ b/src/core/hle/kernel/process.cpp @@ -47,7 +47,7 @@ SERIALIZE_IMPL(AddressMapping) template void Process::serialize(Archive& ar, const unsigned int) { - ar& boost::serialization::base_object(*this); + ar& boost::serialization::base_object(*this); ar & handle_table; ar & codeset; // TODO: Replace with apploader reference ar & resource_limit; @@ -122,6 +122,9 @@ void KernelSystem::TerminateProcess(std::shared_ptr process) { ASSERT_MSG(process->status == ProcessStatus::Running, "Process has already exited"); process->status = ProcessStatus::Exited; + // Wake up any threads waiting for this process to exit. + process->WakeupAllWaitingThreads(); + // Stop all process threads. for (u32 core = 0; core < Core::GetNumCores(); core++) { GetThreadManager(core).TerminateProcessThreads(process); @@ -277,6 +280,14 @@ void Process::Run(s32 main_thread_priority, u32 stack_size) { } } +bool Process::ShouldWait(const Thread* thread) const { + return status != ProcessStatus::Exited; +} + +void Process::Acquire(Thread* thread) { + ASSERT_MSG(!ShouldWait(thread), "object unavailable!"); +} + void Process::Exit() { #ifdef ENABLE_GDBSTUB GDBStub::OnProcessExit(process_id); @@ -684,7 +695,7 @@ void Process::FreeAllMemory() { } Kernel::Process::Process(KernelSystem& kernel) - : Object(kernel), handle_table(kernel), vm_manager(kernel.memory, *this), kernel(kernel) { + : WaitObject(kernel), handle_table(kernel), vm_manager(kernel.memory, *this), kernel(kernel) { kernel.memory.RegisterPageTable(vm_manager.page_table); } Kernel::Process::~Process() { diff --git a/src/core/hle/kernel/process.h b/src/core/hle/kernel/process.h index 4067839cd..60f769af4 100644 --- a/src/core/hle/kernel/process.h +++ b/src/core/hle/kernel/process.h @@ -17,6 +17,7 @@ #include "core/hle/kernel/handle_table.h" #include "core/hle/kernel/object.h" #include "core/hle/kernel/vm_manager.h" +#include "core/hle/kernel/wait_object.h" namespace Kernel { @@ -124,7 +125,7 @@ private: void serialize(Archive& ar, const unsigned int); }; -class Process final : public Object { +class Process final : public WaitObject { public: explicit Process(Kernel::KernelSystem& kernel); ~Process() override; @@ -141,6 +142,10 @@ public: return HANDLE_TYPE; } + /// WaitObject interface: a process can be waited on; it becomes available when it exits. + bool ShouldWait(const Thread* thread) const override; + void Acquire(Thread* thread) override; + HandleTable handle_table; std::shared_ptr codeset; diff --git a/src/core/hle/kernel/svc.cpp b/src/core/hle/kernel/svc.cpp index 715aedd6a..4ef9933fd 100644 --- a/src/core/hle/kernel/svc.cpp +++ b/src/core/hle/kernel/svc.cpp @@ -46,6 +46,33 @@ namespace Kernel { +/// A WaitObject that is permanently unavailable (ShouldWait always returns true). +/// Used as a placeholder for null handles (handle == 0) in svcWaitSynchronizationN so that +/// null-handle slots are silently ignored instead of causing a ResultInvalidHandle crash. +/// The real 3DS kernel appears to allow handle 0 in WaitSynchronizationN arrays; it effectively +/// acts as a slot that never fires, letting the other handles drive the wakeup logic. +class NullWaitObject final : public WaitObject { +public: + explicit NullWaitObject(KernelSystem& kernel) : WaitObject(kernel) {} + + std::string GetTypeName() const override { + return "NullWaitObject"; + } + std::string GetName() const override { + return "NullWaitObject (null-handle placeholder)"; + } + HandleType GetHandleType() const override { + return HandleType::Unknown; + } + + /// Never becomes available — null-handle slot never wins a WaitSynchronizationN race. + bool ShouldWait(const Thread*) const override { + return true; + } + /// No-op: null-handle slot has nothing to acquire. + void Acquire(Thread*) override {} +}; + enum ControlMemoryOperation { MEMOP_FREE = 1, MEMOP_RESERVE = 2, // This operation seems to be unsupported in the kernel @@ -849,7 +876,18 @@ Result SVC::WaitSynchronizationN(s32* out, VAddr handles_address, s32 handle_cou for (int i = 0; i < handle_count; ++i) { Handle handle = memory.Read32(handles_address + i * sizeof(Handle)); auto object = kernel.GetCurrentProcess()->handle_table.Get(handle); - R_UNLESS(object, ResultInvalidHandle); + if (!object) { + // Handle 0 (null) appears in some official Nintendo APT library builds where + // aptSleepSync is 0 because NotifyToWait has not yet delivered the sleep-sync event. + // On real hardware the 3DS kernel treats a null handle in WaitSynchronizationN as a + // slot that is permanently unavailable (never fires), letting the other valid handles + // determine when the thread wakes. Emulate this with a NullWaitObject. + if (handle == 0) { + objects[i] = std::make_shared(kernel); + continue; + } + return ResultInvalidHandle; + } objects[i] = object; } @@ -1563,7 +1601,7 @@ Result SVC::DuplicateHandle(Handle* out, Handle handle) { Result SVC::SignalEvent(Handle handle) { LOG_TRACE(Kernel_SVC, "called event=0x{:08X}", handle); - std::shared_ptr evt = kernel.GetCurrentProcess()->handle_table.Get(handle); + auto evt = kernel.GetCurrentProcess()->handle_table.Get(handle); R_UNLESS(evt, ResultInvalidHandle); evt->Signal(); diff --git a/src/core/hle/service/apt/applet_manager.cpp b/src/core/hle/service/apt/applet_manager.cpp index b459fdd1f..8ee778f89 100644 --- a/src/core/hle/service/apt/applet_manager.cpp +++ b/src/core/hle/service/apt/applet_manager.cpp @@ -153,13 +153,6 @@ static u64 ConvertTitleID(Core::System& system, u64 base_title_id) { return base_title_id; } -static bool IsSystemAppletId(AppletId applet_id) { - return (static_cast(applet_id) & static_cast(AppletId::AnySystemApplet)) != 0; -} - -static bool IsApplicationAppletId(AppletId applet_id) { - return (static_cast(applet_id) & static_cast(AppletId::Application)) != 0; -} AppletManager::AppletSlot AppletManager::GetAppletSlotFromId(AppletId id) { if (id == AppletId::Application) { @@ -254,10 +247,6 @@ AppletManager::AppletSlot AppletManager::GetAppletSlotFromPos(AppletPos pos) { } void AppletManager::CancelAndSendParameter(const MessageParameter& parameter) { - LOG_DEBUG( - Service_APT, "Sending parameter from {:03X} to {:03X} with signal {:08X} and size {:08X}", - parameter.sender_id, parameter.destination_id, parameter.signal, parameter.buffer.size()); - // If the applet is being HLEd, send directly to the applet. const auto applet = hle_applets[parameter.destination_id]; if (applet != nullptr) { @@ -267,7 +256,7 @@ void AppletManager::CancelAndSendParameter(const MessageParameter& parameter) { next_parameter = parameter; if (parameter.signal == SignalType::RequestForSysApplet) { - // APT handles RequestForSysApplet messages itself. + // APT handles RequestForSysApplet messages itself by sending back a Response. LOG_DEBUG(Service_APT, "Replying to RequestForSysApplet from {:03X}", parameter.sender_id); @@ -280,12 +269,24 @@ void AppletManager::CancelAndSendParameter(const MessageParameter& parameter) { next_parameter->destination_id = parameter.sender_id; next_parameter->signal = SignalType::Response; next_parameter->buffer.clear(); - next_parameter->object = nullptr; - } else if (IsSystemAppletId(parameter.sender_id) && - IsApplicationAppletId(parameter.destination_id) && parameter.object) { - // When a message is sent from a system applet to an application, APT - // replaces its object with the zero handle. - next_parameter->object = nullptr; + // Provide a pre-signaled event so the app can svcWaitSynchronizationN on it without + // hanging or crashing with handle=0. The real NS firmware does the same. + auto event = system.Kernel().CreateEvent(Kernel::ResetType::OneShot, + "APT:RequestForSysApplet response"); + event->Signal(); + next_parameter->object = std::move(event); + } + + // If a Wakeup is sent with no event object (e.g. HOME Menu waking up an application + // after returning from a system applet), the application will call svcSignalEvent or + // svcWaitSynchronizationN on the received handle. If that handle is 0 the syscall + // returns ResultInvalidHandle and the app crashes. Provide a pre-signaled event so + // that any such handle is always valid and resolves immediately. + if (parameter.signal == SignalType::Wakeup && !next_parameter->object) { + auto event = system.Kernel().CreateEvent(Kernel::ResetType::OneShot, + "APT:Wakeup event"); + event->Signal(); + next_parameter->object = std::move(event); } // Signal the event to let the receiver know that a new parameter is ready to be read @@ -411,11 +412,18 @@ ResultVal AppletManager::Initialize(AppletId ap // APT automatically calls enable on the first registered applet. Enable(attributes); - // Wake up the applet. + // Wake up the applet. Provide a pre-signaled event so that downstream code + // (e.g. HOME Menu) can safely pass it along when waking up an application; an app + // that calls svcWaitSynchronizationN or svcSignalEvent on the received handle will + // not crash even if it does not first check for handle=0. + auto wakeup_event = system.Kernel().CreateEvent(Kernel::ResetType::OneShot, + "APT:RegisterApplet wakeup"); + wakeup_event->Signal(); SendParameter({ .sender_id = AppletId::None, .destination_id = app_id, .signal = SignalType::Wakeup, + .object = std::move(wakeup_event), }); } @@ -1037,6 +1045,15 @@ Result AppletManager::LeaveHomeMenu(std::shared_ptr object, const std::vector& buffer) { active_slot = AppletSlot::Application; + // If no event was provided (HOME Menu passed handle=0), supply a pre-signaled event so the + // application can safely call svcSignalEvent on the received handle without crashing. + if (!object) { + auto event = system.Kernel().CreateEvent(Kernel::ResetType::OneShot, + "APT:LeaveHomeMenu wakeup confirm"); + event->Signal(); + object = std::move(event); + } + SendParameter({ .sender_id = AppletId::HomeMenu, .destination_id = AppletId::Application, diff --git a/src/core/hle/service/apt/apt.cpp b/src/core/hle/service/apt/apt.cpp index 355f98bab..30484568c 100644 --- a/src/core/hle/service/apt/apt.cpp +++ b/src/core/hle/service/apt/apt.cpp @@ -357,9 +357,9 @@ void Module::APTInterface::NotifyToWait(Kernel::HLERequestContext& ctx) { const auto app_id = rp.Pop(); IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); - rb.Push(ResultSuccess); // No error + rb.Push(ResultSuccess); - LOG_WARNING(Service_APT, "(STUBBED) app_id={}", app_id); + LOG_DEBUG(Service_APT, "(STUBBED) app_id={}", app_id); } void Module::APTInterface::GetLockHandle(Kernel::HLERequestContext& ctx) { @@ -489,10 +489,11 @@ void Module::APTInterface::SendParameter(Kernel::HLERequestContext& ctx) { const auto object = rp.PopGenericObject(); const auto buffer = rp.PopStaticBuffer(); - LOG_DEBUG(Service_APT, - "called src_app_id={:#010X}, dst_app_id={:#010X}, signal_type={:#010X}," - "buffer_size={:#010X}", - src_app_id, dst_app_id, signal_type, buffer_size); + LOG_WARNING(Service_APT, + "called src_app_id={:#010X}, dst_app_id={:#010X}, signal_type={:#010X}," + "buffer_size={:#010X}, object={}", + src_app_id, dst_app_id, signal_type, buffer_size, + object ? object->GetName() : "(null)"); IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); rb.Push(apt->applet_manager->SendParameter({ @@ -509,25 +510,25 @@ void Module::APTInterface::ReceiveParameter(Kernel::HLERequestContext& ctx) { const auto app_id = rp.PopEnum(); const auto buffer_size = rp.Pop(); - LOG_DEBUG(Service_APT, "called app_id={:#010X}, buffer_size={:#010X}", app_id, buffer_size); + LOG_DEBUG(Service_APT, "called app_id={:#06X}, buffer_size={:#010X}", app_id, buffer_size); auto next_parameter = apt->applet_manager->ReceiveParameter(app_id); if (next_parameter.Failed()) { IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); rb.Push(next_parameter.Code()); - } else { - const auto size = std::min(static_cast(next_parameter->buffer.size()), buffer_size); - next_parameter->buffer.resize( - buffer_size); // APT always push a buffer with the maximum size - - IPC::RequestBuilder rb = rp.MakeBuilder(4, 4); - rb.Push(ResultSuccess); // No error - rb.PushEnum(next_parameter->sender_id); - rb.PushEnum(next_parameter->signal); // Signal type - rb.Push(size); // Parameter buffer size - rb.PushMoveObjects(next_parameter->object); - rb.PushStaticBuffer(std::move(next_parameter->buffer), 0); + return; } + + const auto size = std::min(static_cast(next_parameter->buffer.size()), buffer_size); + next_parameter->buffer.resize(buffer_size); // APT always push a buffer with the maximum size + + IPC::RequestBuilder rb = rp.MakeBuilder(4, 4); + rb.Push(ResultSuccess); // No error + rb.PushEnum(next_parameter->sender_id); + rb.PushEnum(next_parameter->signal); // Signal type + rb.Push(size); // Parameter buffer size + rb.PushMoveObjects(next_parameter->object); + rb.PushStaticBuffer(std::move(next_parameter->buffer), 0); } void Module::APTInterface::GlanceParameter(Kernel::HLERequestContext& ctx) { @@ -535,25 +536,25 @@ void Module::APTInterface::GlanceParameter(Kernel::HLERequestContext& ctx) { const auto app_id = rp.PopEnum(); const u32 buffer_size = rp.Pop(); - LOG_DEBUG(Service_APT, "called app_id={:#010X}, buffer_size={:#010X}", app_id, buffer_size); + LOG_DEBUG(Service_APT, "called app_id={:#06X}, buffer_size={:#010X}", app_id, buffer_size); auto next_parameter = apt->applet_manager->GlanceParameter(app_id); if (next_parameter.Failed()) { IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); rb.Push(next_parameter.Code()); - } else { - const auto size = std::min(static_cast(next_parameter->buffer.size()), buffer_size); - next_parameter->buffer.resize( - buffer_size); // APT always push a buffer with the maximum size - - IPC::RequestBuilder rb = rp.MakeBuilder(4, 4); - rb.Push(ResultSuccess); // No error - rb.PushEnum(next_parameter->sender_id); - rb.PushEnum(next_parameter->signal); // Signal type - rb.Push(size); // Parameter buffer size - rb.PushMoveObjects(next_parameter->object); - rb.PushStaticBuffer(std::move(next_parameter->buffer), 0); + return; } + + const auto size = std::min(static_cast(next_parameter->buffer.size()), buffer_size); + next_parameter->buffer.resize(buffer_size); // APT always push a buffer with the maximum size + + IPC::RequestBuilder rb = rp.MakeBuilder(4, 4); + rb.Push(ResultSuccess); // No error + rb.PushEnum(next_parameter->sender_id); + rb.PushEnum(next_parameter->signal); // Signal type + rb.Push(size); // Parameter buffer size + rb.PushCopyObjects(next_parameter->object); + rb.PushStaticBuffer(std::move(next_parameter->buffer), 0); } void Module::APTInterface::CancelParameter(Kernel::HLERequestContext& ctx) { @@ -1010,7 +1011,8 @@ void Module::APTInterface::JumpToHomeMenu(Kernel::HLERequestContext& ctx) { const auto object = rp.PopGenericObject(); const auto buffer = rp.PopStaticBuffer(); - LOG_DEBUG(Service_APT, "called size={}", parameter_size); + LOG_WARNING(Service_APT, "called size={}, object={}", parameter_size, + object ? object->GetName() : "(null)"); IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); rb.Push(apt->applet_manager->JumpToHomeMenu(object, buffer)); @@ -1031,7 +1033,8 @@ void Module::APTInterface::LeaveHomeMenu(Kernel::HLERequestContext& ctx) { const auto object = rp.PopGenericObject(); const auto buffer = rp.PopStaticBuffer(); - LOG_DEBUG(Service_APT, "called size={}", parameter_size); + LOG_WARNING(Service_APT, "called size={}, object={}", parameter_size, + object ? object->GetName() : "(null)"); IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); rb.Push(apt->applet_manager->LeaveHomeMenu(object, buffer)); diff --git a/src/core/hle/service/cfg/cfg.cpp b/src/core/hle/service/cfg/cfg.cpp index 8a34237c6..78867c8f4 100644 --- a/src/core/hle/service/cfg/cfg.cpp +++ b/src/core/hle/service/cfg/cfg.cpp @@ -445,8 +445,11 @@ void Module::Interface::GetRegion(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); u64 caller_tid = ctx.ClientThread()->owner_process.lock()->codeset->program_id; + // Default to reading from SecureInfo_A so that CFGU_SecureInfoGetRegion returns + // the correct hardware region when a dump is present. Falls back to auto-detect + // (preferred region / settings) when SecureInfo_A is absent or invalid. bool from_secure_info = Common::Hacks::hack_manager.OverrideBooleanSetting( - Common::Hacks::HackType::REGION_FROM_SECURE, caller_tid, false); + Common::Hacks::HackType::REGION_FROM_SECURE, caller_tid, true); IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); rb.Push(ResultSuccess); diff --git a/src/core/hle/service/fs/fs_user.cpp b/src/core/hle/service/fs/fs_user.cpp index d68202788..ba08cbadc 100644 --- a/src/core/hle/service/fs/fs_user.cpp +++ b/src/core/hle/service/fs/fs_user.cpp @@ -1339,6 +1339,31 @@ void FS_USER::GetFormatInfo(Kernel::HLERequestContext& ctx) { rb.Push(format_info->duplicate_data != 0); } +void FS_USER::GetSdmcCtrRootPath(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx); + const u32 buffer_size = rp.Pop(); // in wide characters, including null terminator + auto& output_buffer = rp.PopMappedBuffer(); + + // Path is /Nintendo 3DS//; both IDs are all-zero in the emulator + const std::string path = fmt::format("/Nintendo 3DS/{}/{}", SYSTEM_ID, SDCARD_ID); + + LOG_DEBUG(Service_FS, "buffer_size={} path={}", buffer_size, path); + + // Write as UTF-16LE; all characters in this path are ASCII so each code unit is just the byte + const u32 chars_to_write = std::min(static_cast(path.size()), buffer_size - 1); + for (u32 i = 0; i < chars_to_write; ++i) { + const u16 wc = static_cast(static_cast(path[i])); + output_buffer.Write(&wc, i * sizeof(u16), sizeof(u16)); + } + if (buffer_size > 0) { + const u16 null_char = 0; + output_buffer.Write(&null_char, chars_to_write * sizeof(u16), sizeof(u16)); + } + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(ResultSuccess); +} + void FS_USER::GetProductInfo(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); @@ -1997,7 +2022,7 @@ FS_USER::FS_USER(Core::System& system) {0x0845, &FS_USER::GetFormatInfo, "GetFormatInfo"}, {0x0846, nullptr, "GetLegacyRomHeader2"}, {0x0847, nullptr, "FormatCtrCardUserSaveData"}, - {0x0848, nullptr, "GetSdmcCtrRootPath"}, + {0x0848, &FS_USER::GetSdmcCtrRootPath, "GetSdmcCtrRootPath"}, {0x0849, &FS_USER::GetArchiveResource, "GetArchiveResource"}, {0x084A, &FS_USER::ExportIntegrityVerificationSeed, "ExportIntegrityVerificationSeed"}, {0x084B, nullptr, "ImportIntegrityVerificationSeed"}, diff --git a/src/core/hle/service/fs/fs_user.h b/src/core/hle/service/fs/fs_user.h index fffe16ab0..1d4c5b377 100644 --- a/src/core/hle/service/fs/fs_user.h +++ b/src/core/hle/service/fs/fs_user.h @@ -621,6 +621,20 @@ private: */ void GetFormatInfo(Kernel::HLERequestContext& ctx); + /** + * FS_User::GetSdmcCtrRootPath service function (0x08480042). + * Retrieves the /Nintendo 3DS// path as a UTF-16LE wide string. + * Inputs: + * 0 : 0x08480042 + * 1 : Buffer size in wide characters (including null terminator) + * 2 : (bytesize << 4) | 0xC (output buffer descriptor) + * 3 : Output buffer pointer + * Outputs: + * 0 : Header code + * 1 : Result code + */ + void GetSdmcCtrRootPath(Kernel::HLERequestContext& ctx); + /** * FS_User::GetProductInfo service function. * Inputs: