[hle] support passing arguments to homebrew applications (#4013)

Homebrew (.NRO) program args support

Adds a 'program_args' setting and delivers it to NRO homebrews via libnx's homebrew ABI. NROs previously had no way to receive CLI flags (e.g. NZ:P's -noglsl).

Setting: program_args, Category::System, startup-only.
Surfaced in Qt + Android Debug sections.

NRO loader: builds a 4-entry 'ConfigEntry' table + argv at the data segment tail; prepends 'homebrew ' so user args land at argv[1]; scans for 'svc #7' to use as LR. Drops the stale NSO-style argdata append.

KProcess: stores loader-provided guest addresses; 'Run' switches to the homebrew entry (x0=config_ptr, x1=-1, lr=svc7) and patches the real MainThreadHandle into the ConfigEntry slot. Legacy NSO path unchanged.

Tested on NZP — args reach 'argv' correctly.

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4013
Reviewed-by: Lizzie <lizzie@eden-emu.dev>
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
This commit is contained in:
xbzk 2026-06-15 03:02:33 +02:00 committed by crueter
parent 026974211e
commit 78a1cd0533
No known key found for this signature in database
GPG key ID: 425ACD2D4830EBC6
9 changed files with 137 additions and 19 deletions

View file

@ -211,6 +211,9 @@ Result KProcess::Initialize(const Svc::CreateProcessParameter& params, KResource
m_version = params.version;
m_program_id = params.program_id;
m_code_address = params.code_address;
m_arg_pointer = 0;
m_arg_return_address = 0;
m_main_thread_handle_addr = 0;
m_code_size = params.code_num_pages * PageSize;
m_is_application = True(params.flags & Svc::CreateProcessFlag::IsApplication);
@ -995,9 +998,27 @@ Result KProcess::Run(s32 priority, size_t stack_size) {
Handle thread_handle;
R_TRY(m_handle_table.Add(std::addressof(thread_handle), main_thread));
// Set the thread arguments.
main_thread->GetContext().r[0] = 0;
main_thread->GetContext().r[1] = thread_handle;
// Set the thread arguments. Two distinct entry conventions:
// * Kernel/NSO entry (no homebrew ABI): x0 = 0, x1 = thread_handle
// * Homebrew/NRO ABI (loader set arg ptr): x0 = ConfigEntry ptr, x1 = -1ULL
// libnx's switch_crt0.s tests `x0==0 || x1==0xFFFFFFFFFFFFFFFF` to take
// its normal init path; any other combination is interpreted as a user
// exception handler entry.
if (GetInteger(m_arg_pointer) != 0) {
main_thread->GetContext().r[0] = GetInteger(m_arg_pointer);
main_thread->GetContext().r[1] = UINT64_MAX;
main_thread->GetContext().lr = GetInteger(m_arg_return_address);
// Patch the MainThreadHandle entry in the ConfigEntry table now that
// the actual handle exists. libnx stores this verbatim and uses it
// for thread-control SVCs later; a pseudo-handle wouldn't survive
// svcCloseHandle on exit.
if (GetInteger(m_main_thread_handle_addr) != 0) {
this->GetMemory().Write32(m_main_thread_handle_addr, thread_handle);
}
} else {
main_thread->GetContext().r[0] = 0;
main_thread->GetContext().r[1] = thread_handle;
}
// Pass the thread handle to the thread local region.
this->GetMemory().Write32(GetInteger(main_thread->GetTlsAddress()) + 0x110, thread_handle);

View file

@ -84,6 +84,9 @@ private:
Core::Memory::Memory m_memory;
KCapabilities m_capabilities{};
KProcessAddress m_code_address{};
KProcessAddress m_arg_pointer{};
KProcessAddress m_arg_return_address{};
KProcessAddress m_main_thread_handle_addr{};
KHandleTable m_handle_table;
KProcessAddress m_plr_address{};
ThreadList m_thread_list{};
@ -219,6 +222,16 @@ public:
return m_code_address;
}
void SetArgPointer(KProcessAddress addr) {
m_arg_pointer = addr;
}
void SetArgReturnAddress(KProcessAddress addr) {
m_arg_return_address = addr;
}
void SetMainThreadHandleAddr(KProcessAddress addr) {
m_main_thread_handle_addr = addr;
}
size_t GetMainStackSize() const {
return m_main_thread_stack_size;
}