From 78a1cd0533fd4b6d5be77f9f6f2a87ca6b509d03 Mon Sep 17 00:00:00 2001 From: xbzk Date: Mon, 15 Jun 2026 03:02:33 +0200 Subject: [hle] support passing arguments to homebrew applications (#4013) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 Reviewed-by: MaranBr --- src/core/loader/nro.cpp | 92 +++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 78 insertions(+), 14 deletions(-) (limited to 'src/core/loader/nro.cpp') diff --git a/src/core/loader/nro.cpp b/src/core/loader/nro.cpp index 866059286a..21640b7330 100644 --- a/src/core/loader/nro.cpp +++ b/src/core/loader/nro.cpp @@ -4,9 +4,15 @@ // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include +#include +#include +#include +#include #include #include +#include "common/alignment.h" #include "common/common_funcs.h" #include "common/common_types.h" #include "common/logging.h" @@ -23,7 +29,6 @@ #include "core/hle/kernel/k_thread.h" #include "core/hle/service/filesystem/filesystem.h" #include "core/loader/nro.h" -#include "core/loader/nso.h" #include "core/memory.h" #ifdef HAS_NCE @@ -174,19 +179,6 @@ static bool LoadNroImpl(Core::System& system, Kernel::KProcess& process, codeset.segments[i].size = PageAlignSize(nro_header.segments[i].size); } - if (!Settings::values.program_args.GetValue().empty()) { - const auto arg_data = Settings::values.program_args.GetValue(); - codeset.DataSegment().size += NSO_ARGUMENT_DATA_ALLOCATION_SIZE; - NSOArgumentHeader args_header{ - NSO_ARGUMENT_DATA_ALLOCATION_SIZE, static_cast(arg_data.size()), {}}; - const auto end_offset = program_image.size(); - program_image.resize(static_cast(program_image.size()) + - NSO_ARGUMENT_DATA_ALLOCATION_SIZE); - std::memcpy(program_image.data() + end_offset, &args_header, sizeof(NSOArgumentHeader)); - std::memcpy(program_image.data() + end_offset + sizeof(NSOArgumentHeader), arg_data.data(), - arg_data.size()); - } - // Default .bss to NRO header bss size if MOD0 section doesn't exist u32 bss_size{PageAlignSize(nro_header.bss_size)}; @@ -203,6 +195,47 @@ static bool LoadNroImpl(Core::System& system, Kernel::KProcess& process, codeset.DataSegment().size += bss_size; program_image.resize(static_cast(program_image.size()) + bss_size); + struct ConfigEntry { + u32_le key; + u32_le flags; + u64_le value[2]; + }; + static_assert(sizeof(ConfigEntry) == 0x18); + // AArch64 encoding for svc #0x7 (ExitProcess). + constexpr u32 kSvcExitProcessInstruction = 0xD40000E1; + constexpr size_t kNumEntries = 4; // MainThreadHandle, AppletType, Argv, EndOfList + constexpr size_t kConfigTableSize = kNumEntries * sizeof(ConfigEntry); + std::string argv_string; + size_t args_offset_in_image = 0; + std::optional exit_process_offset_in_image; + const auto& program_args = Settings::values.program_args.GetValue(); + if (!program_args.empty()) { + argv_string = "homebrew "; + argv_string += program_args; + argv_string.push_back('\0'); + + const auto& code = codeset.CodeSegment(); + const size_t code_end = (std::min)(program_image.size(), code.offset + code.size); + for (size_t offset = code.offset; offset + sizeof(u32) <= code_end; offset += sizeof(u32)) { + u32 instruction{}; + std::memcpy(&instruction, program_image.data() + offset, sizeof(instruction)); + if (instruction == kSvcExitProcessInstruction) { + exit_process_offset_in_image = offset; + break; + } + } + if (!exit_process_offset_in_image) { + LOG_WARNING(Loader, + "Unable to find svcExitProcess in NRO; returning from main may fault"); + } + + const size_t entries_and_argv = + Common::AlignUp(kConfigTableSize + argv_string.size(), Core::Memory::YUZU_PAGESIZE); + + args_offset_in_image = program_image.size(); + codeset.DataSegment().size += static_cast(entries_and_argv); + program_image.resize(args_offset_in_image + entries_and_argv); + } size_t image_size = program_image.size(); #ifdef HAS_NCE @@ -264,6 +297,37 @@ static bool LoadNroImpl(Core::System& system, Kernel::KProcess& process, // Load codeset for current process codeset.memory = std::move(program_image); process.LoadModule(std::move(codeset), process.GetEntryPoint()); + if (!argv_string.empty()) { + constexpr u32 kEntryEndOfList = 0; + constexpr u32 kEntryMainThreadHandle = 1; + constexpr u32 kEntryArgv = 5; + constexpr u32 kEntryAppletType = 7; + constexpr u32 kAppletTypeApplication = 0; + + const u64 base = GetInteger(process.GetEntryPoint()); + const u64 config_addr = base + args_offset_in_image; + const u64 argv_addr = config_addr + kConfigTableSize; + + const ConfigEntry entries[kNumEntries] = { + {kEntryMainThreadHandle, 0, {0, 0}}, // Value[0] patched in Run() + {kEntryAppletType, 0, {kAppletTypeApplication, 0}}, + {kEntryArgv, 0, {0, argv_addr}}, + {kEntryEndOfList, 0, {0, 0}}, + }; + process.GetMemory().WriteBlock(Common::ProcessAddress{config_addr}, entries, + sizeof(entries)); + process.GetMemory().WriteBlock(Common::ProcessAddress{argv_addr}, + argv_string.data(), argv_string.size()); + + constexpr size_t kMainThreadHandleValueOffset = offsetof(ConfigEntry, value); + process.SetArgPointer(Kernel::KProcessAddress{config_addr}); + if (exit_process_offset_in_image) { + process.SetArgReturnAddress( + Kernel::KProcessAddress{base + *exit_process_offset_in_image}); + } + process.SetMainThreadHandleAddr( + Kernel::KProcessAddress{config_addr + kMainThreadHandleValueOffset}); + } return true; } -- cgit v1.2.3