Browse Source

[loader] Preserve nxlink argv markers

xbzk 14 hours ago
parent
commit
c3f636b3e7
  1. 2
      src/core/CMakeLists.txt
  2. 1
      src/core/hle/kernel/k_process.cpp
  3. 22
      src/core/hle/kernel/k_process.h
  4. 109
      src/core/loader/homebrew_nxlink.cpp
  5. 23
      src/core/loader/homebrew_nxlink.h
  6. 21
      src/core/loader/nro.cpp

2
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

1
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);

22
src/core/hle/kernel/k_process.h

@ -6,7 +6,9 @@
#pragma once
#include <array>
#include <map>
#include <string_view>
#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<char, 16> 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;
}

109
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 <cctype>
namespace Loader::HomebrewNxlink {
namespace {
std::optional<std::string> GetLastArgvToken(std::string_view argv_string) {
while (!argv_string.empty() && argv_string.back() == '\0') {
argv_string.remove_suffix(1);
}
std::optional<std::string_view> 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<unsigned char>(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<unsigned char>(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<unsigned char>(token[i]))) {
return false;
}
}
return true;
}
std::optional<std::string> 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<std::string> 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

23
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 <cstddef>
#include <optional>
#include <string>
#include <string_view>
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<std::string> GetArgvMarker(std::string_view argv_string);
std::optional<std::string> PrepareArgv(std::string& argv_string,
std::string_view inherited_marker);
} // namespace Loader::HomebrewNxlink

21
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<size_t> exit_process_offset;
std::optional<std::string> nxlink_argv_marker;
std::string argv_string;
};
@ -340,7 +342,8 @@ static void SetHomebrewConfigPointers(Kernel::KProcess& process, u64 config_addr
static std::optional<HomebrewNroImage> BuildHomebrewNroImage(const std::vector<u8>& 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<HomebrewNroImage> BuildHomebrewNroImage(const std::vector<u
} else {
image.argv_string = QuoteHomebrewArgvComponent(argv0);
}
image.nxlink_argv_marker = HomebrewNxlink::PrepareArgv(image.argv_string, inherited_marker);
if (image.argv_string.empty() || image.argv_string.back() != '\0') {
image.argv_string.push_back('\0');
}
@ -447,8 +451,8 @@ bool LoadNroInPlace(Core::System& system, Kernel::KProcess& process, Kernel::KTh
return false;
}
auto image =
BuildHomebrewNroImage(nro_file->ReadAllBytes(), 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<FileSys::RomFSFactory>(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;

Loading…
Cancel
Save