From 6abc25ca0d4d09a5ac2ddb39886b148f33a5cf7b Mon Sep 17 00:00:00 2001 From: Cynthia Date: Sat, 20 Jun 2026 14:09:49 -0700 Subject: [PATCH] add in extra command line flags for testing, usage fixes #1958 this adds in a series of extra command line flags that should make it easier to run games directly from the command line. these flags include: - a way to mount extra host directories (for one off runs where you want to write to a temporary directory) - a way to forward OSReport/etc. to stderr. - a way to specify the arguments to a game when launching it directly through the command line. --- src/Cafe/CafeSystem.cpp | 24 +++++++++++++++++ src/Cafe/OS/libs/coreinit/coreinit_Misc.cpp | 13 ++++++--- src/config/CMakeLists.txt | 1 + src/config/LaunchSettings.cpp | 30 ++++++++++++++++++++- src/config/LaunchSettings.h | 17 ++++++++---- 5 files changed, 76 insertions(+), 9 deletions(-) diff --git a/src/Cafe/CafeSystem.cpp b/src/Cafe/CafeSystem.cpp index 79184393..524ea32c 100644 --- a/src/Cafe/CafeSystem.cpp +++ b/src/Cafe/CafeSystem.cpp @@ -685,6 +685,22 @@ namespace CafeSystem fsc_unmount("/cemuBossStorage/", FSC_PRIORITY_BASE); } + void MountExtras() + { + for (const auto& [host_path, emulatedPath] : LaunchSettings::ExtraMounts()) + { + FSCDeviceHostFS_Mount(boost::nowide::narrow(emulatedPath), _pathToUtf8(host_path), FSC_PRIORITY_BASE); + } + } + + void UnmountExtras() + { + for (const auto& [_, emulatedPath] : LaunchSettings::ExtraMounts()) + { + fsc_unmount(boost::nowide::narrow(emulatedPath), FSC_PRIORITY_BASE); + } + } + PREPARE_STATUS_CODE LoadAndMountForegroundTitle(TitleId titleId) { cemuLog_log(LogType::Force, "Mounting title {:016x}", (uint64)titleId); @@ -838,6 +854,7 @@ namespace CafeSystem if (r != PREPARE_STATUS_CODE::SUCCESS) return r; InitVirtualMlcStorage(); + MountExtras(); return PREPARE_STATUS_CODE::SUCCESS; } @@ -881,6 +898,7 @@ namespace CafeSystem // load executable PrepareExecutable(); InitVirtualMlcStorage(); + MountExtras(); return PREPARE_STATUS_CODE::SUCCESS; } @@ -961,7 +979,12 @@ namespace CafeSystem std::string GetForegroundTitleArgStr() { if (sLaunchModeIsStandalone) + { + auto optional_arguments = LaunchSettings::StandaloneTitleArguments(); + if (optional_arguments.has_value()) + return *optional_arguments; return ""; + } auto& update = sGameInfo_ForegroundTitle.GetUpdate(); if (update.IsValid()) return update.GetArgStr(); @@ -1059,6 +1082,7 @@ namespace CafeSystem PPCRecompiler_Shutdown(); GraphicPack2::Reset(); UnmountCurrentTitle(); + UnmountExtras(); MlcStorageUnmountAllTitles(); UnmountBaseDirectories(); DestroyMemorySpace(); diff --git a/src/Cafe/OS/libs/coreinit/coreinit_Misc.cpp b/src/Cafe/OS/libs/coreinit/coreinit_Misc.cpp index 13ef6613..1b34d8bf 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit_Misc.cpp +++ b/src/Cafe/OS/libs/coreinit/coreinit_Misc.cpp @@ -4,8 +4,11 @@ #include "Cafe/OS/libs/coreinit/coreinit_OSScreen.h" #include "Cafe/CafeSystem.h" #include "Cafe/Filesystem/fsc.h" +#include "config/LaunchSettings.h" #include +#include + namespace coreinit { sint32 ppc_vcprintf_pad(char* strOut, sint32 maxLength, sint32 padLength, char padChar) @@ -544,15 +547,18 @@ namespace coreinit } std::mutex sCafeConsoleMutex; + bool s_forwardConsoleLogs; void WriteCafeConsole(CafeLogType cafeLogType, const char* msg, sint32 len) { std::unique_lock _l(sCafeConsoleMutex); CafeLogBuffer& logBuffer = getLogBuffer(cafeLogType); // once a line is full or \n is written it will be posted to log - auto flushLine = [](CafeLogBuffer& cafeLogBuffer, std::string_view cafeLogName) -> void + auto flushLine = [](CafeLogBuffer& cafeLogBuffer, std::string_view cafeLogName, bool forwardToConsole) -> void { cemuLog_log(LogType::CoreinitLogging, "[{0}] {1}", cafeLogName, std::basic_string_view(cafeLogBuffer.lineBuffer.data(), cafeLogBuffer.lineLength)); + if (forwardToConsole) + std::cerr << fmt::format("{0}\n", std::basic_string_view(cafeLogBuffer.lineBuffer.data(), cafeLogBuffer.lineLength)); cafeLogBuffer.lineLength = 0; }; @@ -566,13 +572,13 @@ namespace coreinit if (c == '\n') { // flush line immediately - flushLine(logBuffer, getLogBufferName(cafeLogType)); + flushLine(logBuffer, getLogBufferName(cafeLogType), s_forwardConsoleLogs); continue; } logBuffer.lineBuffer[logBuffer.lineLength] = c; logBuffer.lineLength++; if (logBuffer.lineLength >= logBuffer.lineBuffer.size()) - flushLine(logBuffer, getLogBufferName(cafeLogType)); + flushLine(logBuffer, getLogBufferName(cafeLogType), s_forwardConsoleLogs); } } @@ -846,6 +852,7 @@ namespace coreinit { s_currentTitleId = CafeSystem::GetForegroundTitleId(); s_sdkVersion = CafeSystem::GetForegroundTitleSDKVersion(); + s_forwardConsoleLogs = LaunchSettings::ForwardConsoleLogging(); s_transitionToBackground = false; s_transitionToForeground = false; diff --git a/src/config/CMakeLists.txt b/src/config/CMakeLists.txt index 34720b3f..99f84d46 100644 --- a/src/config/CMakeLists.txt +++ b/src/config/CMakeLists.txt @@ -20,5 +20,6 @@ target_include_directories(CemuConfig PUBLIC "../") target_link_libraries(CemuConfig PRIVATE CemuCommon CemuGui + Boost::nowide Boost::program_options ) diff --git a/src/config/LaunchSettings.cpp b/src/config/LaunchSettings.cpp index 4ac12c53..babd2c65 100644 --- a/src/config/LaunchSettings.cpp +++ b/src/config/LaunchSettings.cpp @@ -63,7 +63,7 @@ bool LaunchSettings::HandleCommandline(const std::vector& args) #endif ("game,g", po::wvalue(), "Path of game to launch") - ("title-id,t", po::value(), "Title ID of the title to be launched (overridden by --game)") + ("title-id,t", po::value(), "Title ID of the title to be launched (overridden by --game)") ("mlc,m", po::wvalue(), "Custom mlc folder location") ("fullscreen,f", po::value()->implicit_value(true), "Launch games in fullscreen mode") @@ -71,6 +71,10 @@ bool LaunchSettings::HandleCommandline(const std::vector& args) ("account,a", po::value(), "Persistent id of account") + ("extra-mounts", po::wvalue>()->composing(), "A series of mounts in the form of: (path on host:path within emulated system, e.g. `/tmp:/vol/temporary/`)") + ("forward-console-logging", "Forward OSReport, OSConsoleWrite, etc. to stderr.") + ("standalone-title-arguments", po::value(), "Custom arguments to pass as arguments to a standalone RPX that is launched.") + ("force-interpreter", po::value()->implicit_value(true), "Force interpreter CPU emulation, disables recompiler. Useful for debugging purposes where you want to get accurate memory accesses and stack traces.") ("force-multicore-interpreter", po::value()->implicit_value(true), "Force multi-core interpreter CPU emulation, disables recompiler. Only useful for getting stack traces, but slightly faster than the single-core interpreter mode.") ("enable-gdbstub", po::value()->implicit_value(true), "Enable GDB stub to debug executables inside Cemu using an external debugger"); @@ -189,6 +193,30 @@ bool LaunchSettings::HandleCommandline(const std::vector& args) if (vm.count("enable-gdbstub")) s_enable_gdbstub = vm["enable-gdbstub"].as(); + if (vm.count("forward-console-logging")) + { + requireConsole(); + s_forward_console_logging = true; + } + + if (vm.count("standalone-title-arguments")) + s_standalone_title_arguments = vm["standalone-title-arguments"].as(); + + if (vm.count("extra-mounts")) + { + for (const auto& argument : vm["extra-mounts"].as>()) + { + size_t colon_location = argument.find(L':'); + if (colon_location == std::wstring::npos) + { + std::cerr << "Argument for a mount expects to be in the format: `path on host:path in emulated system`, was not: `" << boost::nowide::narrow(argument) << "`\n"; + continue; + } + + s_extra_mounts[argument.substr(0, colon_location)] = argument.substr(colon_location + 1); + } + } + std::wstring extract_path, log_path; std::string output_path; if (vm.count("extract")) diff --git a/src/config/LaunchSettings.h b/src/config/LaunchSettings.h index 13665cb7..ffce3895 100644 --- a/src/config/LaunchSettings.h +++ b/src/config/LaunchSettings.h @@ -2,6 +2,7 @@ #include #include +#include class LaunchSettings { @@ -16,7 +17,7 @@ public: static bool HandleCommandline(const std::vector& args); static std::optional GetLoadFile() { return s_load_game_file; } - static std::optional GetLoadTitleID() {return s_load_title_id;} + static std::optional GetLoadTitleID() {return s_load_title_id;} static std::optional GetMLCPath() { return s_mlc_path; } static std::optional RenderUpsideDownEnabled() { return s_render_upside_down; } @@ -24,6 +25,10 @@ public: static bool Verbose() { return s_verbose; } + static bool ForwardConsoleLogging() { return s_forward_console_logging; } + static std::optional StandaloneTitleArguments() { return s_standalone_title_arguments; } + static std::unordered_map& ExtraMounts() { return s_extra_mounts; } + static bool GDBStubEnabled() { return s_enable_gdbstub; } static bool NSightModeEnabled() { return s_nsight_mode; } @@ -37,14 +42,18 @@ public: private: inline static std::optional s_load_game_file{}; - inline static std::optional s_load_title_id{}; + inline static std::optional s_load_title_id{}; inline static std::optional s_mlc_path{}; inline static std::optional s_render_upside_down{}; inline static std::optional s_fullscreen{}; inline static bool s_verbose = false; - + + inline static bool s_forward_console_logging = false; + inline static std::optional s_standalone_title_arguments{}; + inline static std::unordered_map s_extra_mounts{}; + inline static bool s_enable_gdbstub = false; inline static bool s_nsight_mode = false; @@ -59,5 +68,3 @@ private: static bool ExtractorTool(std::wstring_view wud_path, std::string_view output_path, std::wstring_view log_path); }; - -