mirror of
https://github.com/azahar-emu/azahar.git
synced 2026-06-06 02:33:44 -04:00
APT/kernel: fix HOME button crash, region detection, GetSdmcCtrRootPath
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/<ID0>/<ID1> as a UTF-16LE wide string. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
ba9ebb58fe
commit
b2819a7bbc
9 changed files with 177 additions and 61 deletions
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ SERIALIZE_IMPL(AddressMapping)
|
|||
|
||||
template <class Archive>
|
||||
void Process::serialize(Archive& ar, const unsigned int) {
|
||||
ar& boost::serialization::base_object<Object>(*this);
|
||||
ar& boost::serialization::base_object<WaitObject>(*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> 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() {
|
||||
|
|
|
|||
|
|
@ -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> codeset;
|
||||
|
|
|
|||
|
|
@ -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<WaitObject>(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<NullWaitObject>(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<Event> evt = kernel.GetCurrentProcess()->handle_table.Get<Event>(handle);
|
||||
auto evt = kernel.GetCurrentProcess()->handle_table.Get<Event>(handle);
|
||||
R_UNLESS(evt, ResultInvalidHandle);
|
||||
|
||||
evt->Signal();
|
||||
|
|
|
|||
|
|
@ -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<u32>(applet_id) & static_cast<u32>(AppletId::AnySystemApplet)) != 0;
|
||||
}
|
||||
|
||||
static bool IsApplicationAppletId(AppletId applet_id) {
|
||||
return (static_cast<u32>(applet_id) & static_cast<u32>(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::InitializeResult> 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<Kernel::Object> object,
|
|||
const std::vector<u8>& 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,
|
||||
|
|
|
|||
|
|
@ -357,9 +357,9 @@ void Module::APTInterface::NotifyToWait(Kernel::HLERequestContext& ctx) {
|
|||
const auto app_id = rp.Pop<u32>();
|
||||
|
||||
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<AppletId>();
|
||||
const auto buffer_size = rp.Pop<u32>();
|
||||
|
||||
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<u32>(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<u32>(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<AppletId>();
|
||||
const u32 buffer_size = rp.Pop<u32>();
|
||||
|
||||
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<u32>(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<u32>(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));
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -1339,6 +1339,31 @@ void FS_USER::GetFormatInfo(Kernel::HLERequestContext& ctx) {
|
|||
rb.Push<bool>(format_info->duplicate_data != 0);
|
||||
}
|
||||
|
||||
void FS_USER::GetSdmcCtrRootPath(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp(ctx);
|
||||
const u32 buffer_size = rp.Pop<u32>(); // in wide characters, including null terminator
|
||||
auto& output_buffer = rp.PopMappedBuffer();
|
||||
|
||||
// Path is /Nintendo 3DS/<ID0>/<ID1>; 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<u32>(path.size()), buffer_size - 1);
|
||||
for (u32 i = 0; i < chars_to_write; ++i) {
|
||||
const u16 wc = static_cast<u16>(static_cast<unsigned char>(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"},
|
||||
|
|
|
|||
|
|
@ -621,6 +621,20 @@ private:
|
|||
*/
|
||||
void GetFormatInfo(Kernel::HLERequestContext& ctx);
|
||||
|
||||
/**
|
||||
* FS_User::GetSdmcCtrRootPath service function (0x08480042).
|
||||
* Retrieves the /Nintendo 3DS/<ID0>/<ID1> 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:
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue