From c3f636b3e771ea857cdc8cb975ff0677447bfdc7 Mon Sep 17 00:00:00 2001 From: xbzk Date: Sat, 1 Aug 2026 12:34:33 -0300 Subject: [PATCH] [loader] Preserve nxlink argv markers --- src/core/CMakeLists.txt | 2 + src/core/hle/kernel/k_process.cpp | 1 + src/core/hle/kernel/k_process.h | 22 ++++++ src/core/loader/homebrew_nxlink.cpp | 109 ++++++++++++++++++++++++++++ src/core/loader/homebrew_nxlink.h | 23 ++++++ src/core/loader/nro.cpp | 21 +++++- 6 files changed, 175 insertions(+), 3 deletions(-) create mode 100644 src/core/loader/homebrew_nxlink.cpp create mode 100644 src/core/loader/homebrew_nxlink.h diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 16806ff0c0..10893679e2 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -1139,6 +1139,8 @@ add_library(core STATIC launch_timestamp_cache.h loader/deconstructed_rom_directory.cpp loader/deconstructed_rom_directory.h + loader/homebrew_nxlink.cpp + loader/homebrew_nxlink.h loader/kip.cpp loader/kip.h loader/loader.cpp diff --git a/src/core/hle/kernel/k_process.cpp b/src/core/hle/kernel/k_process.cpp index e2e4add325..02767afc80 100644 --- a/src/core/hle/kernel/k_process.cpp +++ b/src/core/hle/kernel/k_process.cpp @@ -214,6 +214,7 @@ Result KProcess::Initialize(KernelCore& kernel, const Svc::CreateProcessParamete m_homebrew_next_load_path_addr = 0; m_homebrew_next_load_argv_addr = 0; m_is_homebrew_in_place_next_load = false; + m_has_homebrew_nxlink_argv_marker = false; m_code_size = params.code_num_pages * PageSize; m_is_application = True(params.flags & Svc::CreateProcessFlag::IsApplication); diff --git a/src/core/hle/kernel/k_process.h b/src/core/hle/kernel/k_process.h index 11fc130571..b64ec3c936 100644 --- a/src/core/hle/kernel/k_process.h +++ b/src/core/hle/kernel/k_process.h @@ -6,7 +6,9 @@ #pragma once +#include #include +#include #include "core/arm/arm_interface.h" #include "core/file_sys/program_metadata.h" @@ -90,6 +92,7 @@ private: KProcessAddress m_process_handle_addr{}; KProcessAddress m_homebrew_next_load_path_addr{}; KProcessAddress m_homebrew_next_load_argv_addr{}; + std::array m_homebrew_nxlink_argv_marker{}; KHandleTable m_handle_table; KProcessAddress m_plr_address{}; ThreadList m_thread_list{}; @@ -144,6 +147,7 @@ private: bool m_is_immortal : 1 = false; bool m_is_handle_table_initialized : 1 = false; bool m_is_homebrew_in_place_next_load : 1 = false; + bool m_has_homebrew_nxlink_argv_marker : 1 = false; private: Result StartTermination(KernelCore& kernel); @@ -246,6 +250,24 @@ public: void SetHomebrewInPlaceNextLoad(bool enabled) { m_is_homebrew_in_place_next_load = enabled; } + void SetHomebrewNxlinkArgvMarker(std::string_view marker) { + if (marker.size() != m_homebrew_nxlink_argv_marker.size()) { + m_has_homebrew_nxlink_argv_marker = false; + return; + } + + marker.copy(m_homebrew_nxlink_argv_marker.data(), m_homebrew_nxlink_argv_marker.size()); + m_has_homebrew_nxlink_argv_marker = true; + } + void ClearHomebrewNxlinkArgvMarker() { + m_has_homebrew_nxlink_argv_marker = false; + } + std::string_view GetHomebrewNxlinkArgvMarker() const { + if (!m_has_homebrew_nxlink_argv_marker) { + return {}; + } + return {m_homebrew_nxlink_argv_marker.data(), m_homebrew_nxlink_argv_marker.size()}; + } bool IsHomebrewInPlaceNextLoad() const { return m_is_homebrew_in_place_next_load; } diff --git a/src/core/loader/homebrew_nxlink.cpp b/src/core/loader/homebrew_nxlink.cpp new file mode 100644 index 0000000000..a216b71e09 --- /dev/null +++ b/src/core/loader/homebrew_nxlink.cpp @@ -0,0 +1,109 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "core/loader/homebrew_nxlink.h" + +#include + +namespace Loader::HomebrewNxlink { +namespace { + +std::optional GetLastArgvToken(std::string_view argv_string) { + while (!argv_string.empty() && argv_string.back() == '\0') { + argv_string.remove_suffix(1); + } + + std::optional last_token; + bool in_token = false; + bool quoted = false; + size_t token_begin = 0; + size_t token_size = 0; + + for (size_t i = 0; i <= argv_string.size(); i++) { + const char c = i < argv_string.size() ? argv_string[i] : '\0'; + + if (!in_token) { + if (c == '\0' || std::isspace(static_cast(c))) { + continue; + } + + in_token = true; + token_size = 0; + if (c == '"') { + quoted = true; + token_begin = i + 1; + } else { + quoted = false; + token_begin = i; + token_size = 1; + } + continue; + } + + const bool token_end = + quoted ? c == '"' || c == '\0' + : c == '\0' || std::isspace(static_cast(c)); + if (token_end) { + if (token_size != 0) { + last_token = argv_string.substr(token_begin, token_size); + } + in_token = false; + quoted = false; + token_size = 0; + continue; + } + + token_size++; + } + + if (!last_token) { + return std::nullopt; + } + return std::string{*last_token}; +} + +void AppendArgvToken(std::string& argv_string, std::string_view token) { + while (!argv_string.empty() && argv_string.back() == '\0') { + argv_string.pop_back(); + } + if (!argv_string.empty()) { + argv_string.push_back(' '); + } + argv_string.append(token); +} + +} // namespace + +bool IsArgvMarker(std::string_view token) { + if (token.size() != ArgvMarkerSize || token.substr(8) != ArgvMarkerSuffix) { + return false; + } + + for (size_t i = 0; i < 8; i++) { + if (!std::isxdigit(static_cast(token[i]))) { + return false; + } + } + + return true; +} + +std::optional GetArgvMarker(std::string_view argv_string) { + auto last_token = GetLastArgvToken(argv_string); + if (!last_token || !IsArgvMarker(*last_token)) { + return std::nullopt; + } + return last_token; +} + +std::optional PrepareArgv(std::string& argv_string, + std::string_view inherited_marker) { + auto marker = GetArgvMarker(argv_string); + if (!marker && IsArgvMarker(inherited_marker)) { + AppendArgvToken(argv_string, inherited_marker); + marker = std::string{inherited_marker}; + } + return marker; +} + +} // namespace Loader::HomebrewNxlink diff --git a/src/core/loader/homebrew_nxlink.h b/src/core/loader/homebrew_nxlink.h new file mode 100644 index 0000000000..d5cfb70414 --- /dev/null +++ b/src/core/loader/homebrew_nxlink.h @@ -0,0 +1,23 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include +#include +#include +#include + +namespace Loader::HomebrewNxlink { + +constexpr size_t ArgvMarkerSize = 16; +constexpr std::string_view ArgvMarkerSuffix = "_NXLINK_"; + +// This module handles only the libnx argv marker. Client stream handling should be an +// explicit byte-sink API; the marker alone must not imply logging. +bool IsArgvMarker(std::string_view token); +std::optional GetArgvMarker(std::string_view argv_string); +std::optional PrepareArgv(std::string& argv_string, + std::string_view inherited_marker); + +} // namespace Loader::HomebrewNxlink diff --git a/src/core/loader/nro.cpp b/src/core/loader/nro.cpp index 27bddb2428..993662a011 100644 --- a/src/core/loader/nro.cpp +++ b/src/core/loader/nro.cpp @@ -33,6 +33,7 @@ #include "core/hle/kernel/k_process.h" #include "core/hle/kernel/k_thread.h" #include "core/hle/service/filesystem/filesystem.h" +#include "core/loader/homebrew_nxlink.h" #include "core/loader/nro.h" #include "core/memory.h" @@ -319,6 +320,7 @@ struct HomebrewNroImage { size_t image_size{}; size_t args_offset{}; std::optional exit_process_offset; + std::optional nxlink_argv_marker; std::string argv_string; }; @@ -340,7 +342,8 @@ static void SetHomebrewConfigPointers(Kernel::KProcess& process, u64 config_addr static std::optional BuildHomebrewNroImage(const std::vector& data, std::string nro_path, std::string file_name, - std::string launch_argv) { + std::string launch_argv, + std::string_view inherited_marker) { if (data.size() < sizeof(NroHeader)) { return std::nullopt; } @@ -385,6 +388,7 @@ static std::optional BuildHomebrewNroImage(const std::vectorReadAllBytes(), nro_path, nro_file->GetName(), launch_argv); + auto image = BuildHomebrewNroImage(nro_file->ReadAllBytes(), nro_path, nro_file->GetName(), + launch_argv, process.GetHomebrewNxlinkArgvMarker()); if (!image) { LOG_WARNING(Loader, "NextLoad: in-place handoff failed because '{}' is invalid", nro_path); @@ -538,6 +542,11 @@ bool LoadNroInPlace(Core::System& system, Kernel::KProcess& process, Kernel::KTh process.SetArgReturnAddress(Kernel::KProcessAddress{ image->exit_process_offset ? base + *image->exit_process_offset : 0}); SetHomebrewConfigPointers(process, config_addr, next_load_path_addr, next_load_argv_addr); + if (image->nxlink_argv_marker) { + process.SetHomebrewNxlinkArgvMarker(*image->nxlink_argv_marker); + } else { + process.ClearHomebrewNxlinkArgvMarker(); + } system.GetFileSystemController().RegisterProcess( process.GetProcessId(), program_id, std::make_unique(loader, system.GetContentProvider(), @@ -619,6 +628,7 @@ static bool LoadNroImpl(Core::System& system, Kernel::KProcess& process, argv_string.push_back(' '); argv_string += program_args; } + const auto nxlink_argv_marker = HomebrewNxlink::PrepareArgv(argv_string, {}); if (argv_string.empty() || argv_string.back() != '\0') { argv_string.push_back('\0'); } @@ -712,6 +722,11 @@ static bool LoadNroImpl(Core::System& system, Kernel::KProcess& process, codeset.memory = std::move(program_image); process.LoadModule(system.Kernel(), std::move(codeset), process.GetEntryPoint()); process.SetHomebrewInPlaceNextLoad(false); + if (nxlink_argv_marker) { + process.SetHomebrewNxlinkArgvMarker(*nxlink_argv_marker); + } else { + process.ClearHomebrewNxlinkArgvMarker(); + } { const u64 base = GetInteger(process.GetEntryPoint()); const u64 config_addr = base + args_offset_in_image;