mirror of
https://git.eden-emu.dev/eden-emu/eden.git
synced 2026-11-03 04:18:39 +00:00
[loader] Add nxlink log server mode
This commit is contained in:
@@ -840,6 +840,9 @@ struct Values {
|
|||||||
Setting<bool> gpu_log_driver_debug{linkage, true, "gpu_log_driver_debug", Category::Debugging};
|
Setting<bool> gpu_log_driver_debug{linkage, true, "gpu_log_driver_debug", Category::Debugging};
|
||||||
Setting<s32> gpu_log_ring_buffer_size{linkage, 512, "gpu_log_ring_buffer_size",
|
Setting<s32> gpu_log_ring_buffer_size{linkage, 512, "gpu_log_ring_buffer_size",
|
||||||
Category::Debugging};
|
Category::Debugging};
|
||||||
|
Setting<HomebrewNxlinkServerMode> homebrew_nxlink_server_mode{
|
||||||
|
linkage, HomebrewNxlinkServerMode::Disabled, "homebrew_nxlink_server_mode",
|
||||||
|
Category::Debugging};
|
||||||
|
|
||||||
SwitchableSetting<u16, true> debug_knobs{linkage,
|
SwitchableSetting<u16, true> debug_knobs{linkage,
|
||||||
0,
|
0,
|
||||||
|
|||||||
@@ -159,6 +159,7 @@ ENUM(GpuUnswizzleChunk, VeryLow, Low, Normal, Medium, High)
|
|||||||
ENUM(TemperatureUnits, Celsius, Fahrenheit)
|
ENUM(TemperatureUnits, Celsius, Fahrenheit)
|
||||||
ENUM(ExtendedDynamicState, Disabled, EDS1, EDS2, EDS3);
|
ENUM(ExtendedDynamicState, Disabled, EDS1, EDS2, EDS3);
|
||||||
ENUM(GpuLogLevel, Off, Errors, Standard, Verbose, All)
|
ENUM(GpuLogLevel, Off, Errors, Standard, Verbose, All)
|
||||||
|
ENUM(HomebrewNxlinkServerMode, Disabled, EdenLog, HostStdout, File)
|
||||||
ENUM(GameListMode, TreeView, GridView, CarouselView);
|
ENUM(GameListMode, TreeView, GridView, CarouselView);
|
||||||
ENUM(SpeedMode, Standard, Turbo, Slow);
|
ENUM(SpeedMode, Standard, Turbo, Slow);
|
||||||
|
|
||||||
|
|||||||
@@ -52,6 +52,7 @@
|
|||||||
#include "core/hle/service/set/system_settings_server.h"
|
#include "core/hle/service/set/system_settings_server.h"
|
||||||
#include "core/hle/service/sm/sm.h"
|
#include "core/hle/service/sm/sm.h"
|
||||||
#include "core/internal_network/network.h"
|
#include "core/internal_network/network.h"
|
||||||
|
#include "core/loader/homebrew_nxlink.h"
|
||||||
#include "core/loader/loader.h"
|
#include "core/loader/loader.h"
|
||||||
#include "core/memory.h"
|
#include "core/memory.h"
|
||||||
#include "core/memory/cheat_engine.h"
|
#include "core/memory/cheat_engine.h"
|
||||||
@@ -397,6 +398,7 @@ struct System::Impl {
|
|||||||
|
|
||||||
stop_event.request_stop();
|
stop_event.request_stop();
|
||||||
core_timing.SyncPause(false);
|
core_timing.SyncPause(false);
|
||||||
|
Loader::HomebrewNxlink::StopServer();
|
||||||
Network::CancelPendingSocketOperations();
|
Network::CancelPendingSocketOperations();
|
||||||
kernel.SuspendEmulation(true);
|
kernel.SuspendEmulation(true);
|
||||||
kernel.CloseServices();
|
kernel.CloseServices();
|
||||||
|
|||||||
@@ -3,11 +3,48 @@
|
|||||||
|
|
||||||
#include "core/loader/homebrew_nxlink.h"
|
#include "core/loader/homebrew_nxlink.h"
|
||||||
|
|
||||||
|
#include <array>
|
||||||
#include <cctype>
|
#include <cctype>
|
||||||
|
#include <memory>
|
||||||
|
#include <mutex>
|
||||||
|
#include <optional>
|
||||||
|
#include <stop_token>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
#include <boost/asio.hpp>
|
||||||
|
|
||||||
|
#include "common/logging.h"
|
||||||
|
#include "common/polyfill_thread.h"
|
||||||
|
#include "common/thread.h"
|
||||||
|
|
||||||
namespace Loader::HomebrewNxlink {
|
namespace Loader::HomebrewNxlink {
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
|
using boost::asio::ip::tcp;
|
||||||
|
|
||||||
|
constexpr u16 NxlinkClientPort = 28771;
|
||||||
|
constexpr size_t ReceiveBufferSize = 1024;
|
||||||
|
constexpr size_t MaxLogLineSize = 16 * 1024;
|
||||||
|
|
||||||
|
struct LogConnection {
|
||||||
|
explicit LogConnection(boost::asio::io_context& io_context_) : socket{io_context_} {}
|
||||||
|
|
||||||
|
tcp::socket socket;
|
||||||
|
std::array<char, ReceiveBufferSize> buffer{};
|
||||||
|
std::string line;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct LogServerState {
|
||||||
|
LogServerState() : acceptor{io_context} {}
|
||||||
|
|
||||||
|
boost::asio::io_context io_context;
|
||||||
|
tcp::acceptor acceptor;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::mutex server_mutex;
|
||||||
|
std::shared_ptr<LogServerState> server_state;
|
||||||
|
std::optional<std::jthread> server_thread;
|
||||||
|
|
||||||
std::optional<std::string> GetLastArgvToken(std::string_view argv_string) {
|
std::optional<std::string> GetLastArgvToken(std::string_view argv_string) {
|
||||||
while (!argv_string.empty() && argv_string.back() == '\0') {
|
while (!argv_string.empty() && argv_string.back() == '\0') {
|
||||||
argv_string.remove_suffix(1);
|
argv_string.remove_suffix(1);
|
||||||
@@ -72,6 +109,102 @@ void AppendArgvToken(std::string& argv_string, std::string_view token) {
|
|||||||
argv_string.append(token);
|
argv_string.append(token);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void FlushLogLine(std::string& line) {
|
||||||
|
if (!line.empty() && line.back() == '\r') {
|
||||||
|
line.pop_back();
|
||||||
|
}
|
||||||
|
if (!line.empty()) {
|
||||||
|
LOG_INFO(Loader, "{}", line);
|
||||||
|
line.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ConsumeLogBytes(LogConnection& connection, std::string_view bytes) {
|
||||||
|
for (const char byte : bytes) {
|
||||||
|
if (byte == '\n') {
|
||||||
|
FlushLogLine(connection.line);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
connection.line.push_back(byte);
|
||||||
|
if (connection.line.size() >= MaxLogLineSize) {
|
||||||
|
FlushLogLine(connection.line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void StartRead(std::shared_ptr<LogConnection> connection) {
|
||||||
|
connection->socket.async_read_some(
|
||||||
|
boost::asio::buffer(connection->buffer),
|
||||||
|
[connection](const boost::system::error_code& error, size_t bytes_read) {
|
||||||
|
if (error.failed()) {
|
||||||
|
FlushLogLine(connection->line);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ConsumeLogBytes(*connection, {connection->buffer.data(), bytes_read});
|
||||||
|
StartRead(connection);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void StartAccept(std::shared_ptr<LogServerState> state) {
|
||||||
|
auto connection = std::make_shared<LogConnection>(state->io_context);
|
||||||
|
state->acceptor.async_accept(
|
||||||
|
connection->socket,
|
||||||
|
[state, connection](const boost::system::error_code& error) {
|
||||||
|
if (!error.failed()) {
|
||||||
|
LOG_INFO(Loader, "Homebrew nxlink log client connected");
|
||||||
|
StartRead(connection);
|
||||||
|
}
|
||||||
|
if (state->acceptor.is_open()) {
|
||||||
|
StartAccept(state);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void StartLogServer() {
|
||||||
|
std::scoped_lock lock{server_mutex};
|
||||||
|
if (server_thread) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto state = std::make_shared<LogServerState>();
|
||||||
|
const tcp::endpoint endpoint{boost::asio::ip::address_v4::loopback(), NxlinkClientPort};
|
||||||
|
|
||||||
|
boost::system::error_code error;
|
||||||
|
state->acceptor.open(endpoint.protocol(), error);
|
||||||
|
if (!error.failed()) {
|
||||||
|
state->acceptor.set_option(tcp::acceptor::reuse_address(true), error);
|
||||||
|
}
|
||||||
|
if (!error.failed()) {
|
||||||
|
state->acceptor.bind(endpoint, error);
|
||||||
|
}
|
||||||
|
if (!error.failed()) {
|
||||||
|
state->acceptor.listen(boost::asio::socket_base::max_listen_connections, error);
|
||||||
|
}
|
||||||
|
if (error.failed()) {
|
||||||
|
LOG_WARNING(Loader, "Homebrew nxlink log server could not listen on 127.0.0.1:{}: {}",
|
||||||
|
NxlinkClientPort, error.message());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
StartAccept(state);
|
||||||
|
server_state = state;
|
||||||
|
server_thread.emplace([state](std::stop_token stop_token) {
|
||||||
|
Common::SetCurrentThreadName("HomebrewNxlink");
|
||||||
|
std::stop_callback stop_callback{stop_token, [state] {
|
||||||
|
boost::system::error_code ignored;
|
||||||
|
state->acceptor.close(ignored);
|
||||||
|
state->io_context.stop();
|
||||||
|
}};
|
||||||
|
|
||||||
|
LOG_INFO(Loader, "Homebrew nxlink log server listening on 127.0.0.1:{}",
|
||||||
|
NxlinkClientPort);
|
||||||
|
state->io_context.run();
|
||||||
|
LOG_INFO(Loader, "Homebrew nxlink log server stopped");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
bool IsArgvMarker(std::string_view token) {
|
bool IsArgvMarker(std::string_view token) {
|
||||||
@@ -88,6 +221,20 @@ bool IsArgvMarker(std::string_view token) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool IsLoopbackArgvMarker(std::string_view token) {
|
||||||
|
if (!IsArgvMarker(token)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t i = 0; i < 8; i++) {
|
||||||
|
if (std::tolower(static_cast<unsigned char>(token[i])) !=
|
||||||
|
std::tolower(static_cast<unsigned char>(LoopbackArgvMarker[i]))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
std::optional<std::string> GetArgvMarker(std::string_view argv_string) {
|
std::optional<std::string> GetArgvMarker(std::string_view argv_string) {
|
||||||
auto last_token = GetLastArgvToken(argv_string);
|
auto last_token = GetLastArgvToken(argv_string);
|
||||||
if (!last_token || !IsArgvMarker(*last_token)) {
|
if (!last_token || !IsArgvMarker(*last_token)) {
|
||||||
@@ -97,13 +244,68 @@ std::optional<std::string> GetArgvMarker(std::string_view argv_string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::optional<std::string> PrepareArgv(std::string& argv_string,
|
std::optional<std::string> PrepareArgv(std::string& argv_string,
|
||||||
std::string_view inherited_marker) {
|
std::string_view inherited_marker,
|
||||||
|
bool append_loopback_marker) {
|
||||||
auto marker = GetArgvMarker(argv_string);
|
auto marker = GetArgvMarker(argv_string);
|
||||||
if (!marker && IsArgvMarker(inherited_marker)) {
|
if (!marker && IsArgvMarker(inherited_marker)) {
|
||||||
AppendArgvToken(argv_string, inherited_marker);
|
AppendArgvToken(argv_string, inherited_marker);
|
||||||
marker = std::string{inherited_marker};
|
marker = std::string{inherited_marker};
|
||||||
}
|
}
|
||||||
|
if (!marker && append_loopback_marker) {
|
||||||
|
AppendArgvToken(argv_string, LoopbackArgvMarker);
|
||||||
|
marker = std::string{LoopbackArgvMarker};
|
||||||
|
}
|
||||||
return marker;
|
return marker;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ApplyServerMode(Settings::HomebrewNxlinkServerMode mode,
|
||||||
|
const std::optional<std::string>& active_marker) {
|
||||||
|
switch (mode) {
|
||||||
|
case Settings::HomebrewNxlinkServerMode::Disabled:
|
||||||
|
StopServer();
|
||||||
|
return;
|
||||||
|
case Settings::HomebrewNxlinkServerMode::EdenLog:
|
||||||
|
if (active_marker && IsLoopbackArgvMarker(*active_marker)) {
|
||||||
|
StartLogServer();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
StopServer();
|
||||||
|
if (active_marker) {
|
||||||
|
LOG_INFO(Loader,
|
||||||
|
"Homebrew nxlink server mode overridden by argv marker '{}'; not starting "
|
||||||
|
"local nxlink log server",
|
||||||
|
*active_marker);
|
||||||
|
} else {
|
||||||
|
LOG_WARNING(Loader,
|
||||||
|
"Homebrew nxlink log server requested, but no argv marker is active");
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
case Settings::HomebrewNxlinkServerMode::HostStdout:
|
||||||
|
case Settings::HomebrewNxlinkServerMode::File:
|
||||||
|
StopServer();
|
||||||
|
LOG_WARNING(Loader, "Homebrew nxlink server mode {} is not implemented",
|
||||||
|
static_cast<int>(mode));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void StopServer() {
|
||||||
|
std::shared_ptr<LogServerState> state;
|
||||||
|
std::optional<std::jthread> thread;
|
||||||
|
{
|
||||||
|
std::scoped_lock lock{server_mutex};
|
||||||
|
state = std::move(server_state);
|
||||||
|
thread = std::move(server_thread);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state) {
|
||||||
|
boost::system::error_code ignored;
|
||||||
|
state->acceptor.close(ignored);
|
||||||
|
state->io_context.stop();
|
||||||
|
}
|
||||||
|
if (thread) {
|
||||||
|
thread->request_stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace Loader::HomebrewNxlink
|
} // namespace Loader::HomebrewNxlink
|
||||||
|
|||||||
@@ -8,16 +8,23 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
|
|
||||||
|
#include "common/settings_enums.h"
|
||||||
|
|
||||||
namespace Loader::HomebrewNxlink {
|
namespace Loader::HomebrewNxlink {
|
||||||
|
|
||||||
constexpr size_t ArgvMarkerSize = 16;
|
constexpr size_t ArgvMarkerSize = 16;
|
||||||
constexpr std::string_view ArgvMarkerSuffix = "_NXLINK_";
|
constexpr std::string_view ArgvMarkerSuffix = "_NXLINK_";
|
||||||
|
constexpr std::string_view LoopbackArgvMarker = "0100007F_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);
|
bool IsArgvMarker(std::string_view token);
|
||||||
|
bool IsLoopbackArgvMarker(std::string_view token);
|
||||||
std::optional<std::string> GetArgvMarker(std::string_view argv_string);
|
std::optional<std::string> GetArgvMarker(std::string_view argv_string);
|
||||||
std::optional<std::string> PrepareArgv(std::string& argv_string,
|
std::optional<std::string> PrepareArgv(std::string& argv_string,
|
||||||
std::string_view inherited_marker);
|
std::string_view inherited_marker,
|
||||||
|
bool append_loopback_marker = false);
|
||||||
|
|
||||||
|
void ApplyServerMode(Settings::HomebrewNxlinkServerMode mode,
|
||||||
|
const std::optional<std::string>& active_marker);
|
||||||
|
void StopServer();
|
||||||
|
|
||||||
} // namespace Loader::HomebrewNxlink
|
} // namespace Loader::HomebrewNxlink
|
||||||
|
|||||||
+17
-5
@@ -343,7 +343,8 @@ static std::optional<HomebrewNroImage> BuildHomebrewNroImage(const std::vector<u
|
|||||||
std::string nro_path,
|
std::string nro_path,
|
||||||
std::string file_name,
|
std::string file_name,
|
||||||
std::string launch_argv,
|
std::string launch_argv,
|
||||||
std::string_view inherited_marker) {
|
std::string_view inherited_marker,
|
||||||
|
bool append_loopback_nxlink_marker) {
|
||||||
if (data.size() < sizeof(NroHeader)) {
|
if (data.size() < sizeof(NroHeader)) {
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
@@ -388,7 +389,8 @@ static std::optional<HomebrewNroImage> BuildHomebrewNroImage(const std::vector<u
|
|||||||
} else {
|
} else {
|
||||||
image.argv_string = QuoteHomebrewArgvComponent(argv0);
|
image.argv_string = QuoteHomebrewArgvComponent(argv0);
|
||||||
}
|
}
|
||||||
image.nxlink_argv_marker = HomebrewNxlink::PrepareArgv(image.argv_string, inherited_marker);
|
image.nxlink_argv_marker = HomebrewNxlink::PrepareArgv(
|
||||||
|
image.argv_string, inherited_marker, append_loopback_nxlink_marker);
|
||||||
if (image.argv_string.empty() || image.argv_string.back() != '\0') {
|
if (image.argv_string.empty() || image.argv_string.back() != '\0') {
|
||||||
image.argv_string.push_back('\0');
|
image.argv_string.push_back('\0');
|
||||||
}
|
}
|
||||||
@@ -451,8 +453,12 @@ bool LoadNroInPlace(Core::System& system, Kernel::KProcess& process, Kernel::KTh
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto image = BuildHomebrewNroImage(nro_file->ReadAllBytes(), nro_path, nro_file->GetName(),
|
const auto nxlink_server_mode = Settings::values.homebrew_nxlink_server_mode.GetValue();
|
||||||
launch_argv, process.GetHomebrewNxlinkArgvMarker());
|
const bool append_loopback_nxlink_marker =
|
||||||
|
nxlink_server_mode != Settings::HomebrewNxlinkServerMode::Disabled;
|
||||||
|
auto image = BuildHomebrewNroImage(
|
||||||
|
nro_file->ReadAllBytes(), nro_path, nro_file->GetName(), launch_argv,
|
||||||
|
process.GetHomebrewNxlinkArgvMarker(), append_loopback_nxlink_marker);
|
||||||
if (!image) {
|
if (!image) {
|
||||||
LOG_WARNING(Loader, "NextLoad: in-place handoff failed because '{}' is invalid",
|
LOG_WARNING(Loader, "NextLoad: in-place handoff failed because '{}' is invalid",
|
||||||
nro_path);
|
nro_path);
|
||||||
@@ -547,6 +553,7 @@ bool LoadNroInPlace(Core::System& system, Kernel::KProcess& process, Kernel::KTh
|
|||||||
} else {
|
} else {
|
||||||
process.ClearHomebrewNxlinkArgvMarker();
|
process.ClearHomebrewNxlinkArgvMarker();
|
||||||
}
|
}
|
||||||
|
HomebrewNxlink::ApplyServerMode(nxlink_server_mode, image->nxlink_argv_marker);
|
||||||
system.GetFileSystemController().RegisterProcess(
|
system.GetFileSystemController().RegisterProcess(
|
||||||
process.GetProcessId(), program_id,
|
process.GetProcessId(), program_id,
|
||||||
std::make_unique<FileSys::RomFSFactory>(loader, system.GetContentProvider(),
|
std::make_unique<FileSys::RomFSFactory>(loader, system.GetContentProvider(),
|
||||||
@@ -628,7 +635,11 @@ static bool LoadNroImpl(Core::System& system, Kernel::KProcess& process,
|
|||||||
argv_string.push_back(' ');
|
argv_string.push_back(' ');
|
||||||
argv_string += program_args;
|
argv_string += program_args;
|
||||||
}
|
}
|
||||||
const auto nxlink_argv_marker = HomebrewNxlink::PrepareArgv(argv_string, {});
|
const auto nxlink_server_mode = Settings::values.homebrew_nxlink_server_mode.GetValue();
|
||||||
|
const bool append_loopback_nxlink_marker =
|
||||||
|
nxlink_server_mode != Settings::HomebrewNxlinkServerMode::Disabled;
|
||||||
|
const auto nxlink_argv_marker =
|
||||||
|
HomebrewNxlink::PrepareArgv(argv_string, {}, append_loopback_nxlink_marker);
|
||||||
if (argv_string.empty() || argv_string.back() != '\0') {
|
if (argv_string.empty() || argv_string.back() != '\0') {
|
||||||
argv_string.push_back('\0');
|
argv_string.push_back('\0');
|
||||||
}
|
}
|
||||||
@@ -727,6 +738,7 @@ static bool LoadNroImpl(Core::System& system, Kernel::KProcess& process,
|
|||||||
} else {
|
} else {
|
||||||
process.ClearHomebrewNxlinkArgvMarker();
|
process.ClearHomebrewNxlinkArgvMarker();
|
||||||
}
|
}
|
||||||
|
HomebrewNxlink::ApplyServerMode(nxlink_server_mode, nxlink_argv_marker);
|
||||||
{
|
{
|
||||||
const u64 base = GetInteger(process.GetEntryPoint());
|
const u64 base = GetInteger(process.GetEntryPoint());
|
||||||
const u64 config_addr = base + args_offset_in_image;
|
const u64 config_addr = base + args_offset_in_image;
|
||||||
|
|||||||
Reference in New Issue
Block a user