From 71f343d2d603f7b458b914592bc739b00b87fdbd Mon Sep 17 00:00:00 2001 From: Vinicius Rangel Date: Thu, 25 Sep 2025 23:01:52 -0300 Subject: [PATCH] Impl sceSystemServiceLoadExec (#3647) * Add support for restarting the emulator with new configurations - Implement `Restart` function in `Emulator` to enable process relaunch with updated parameters. - Modify `sceSystemServiceLoadExec` to use the restart functionality. * Add logging for emulator restart and system service load execution * Add IPC emulator PID output command Impl `PID` output command to return the emulator process ID - required for launches supporting emulator restart * Add log file append mode support (used after restarting to keep the same log file) * Keep game root between restarts * add --wait-for-debugger option flag * add --wait-for-pid flag used for sync between parent & child process during restart * impl restart via ipc * fix override game root * add qt flags to allow restart --- CMakeLists.txt | 2 + src/common/logging/backend.cpp | 26 +++- src/common/logging/backend.h | 5 + src/core/debugger.cpp | 99 ++++++++++++ src/core/debugger.h | 16 ++ src/core/ipc/ipc.cpp | 15 +- src/core/ipc/ipc.h | 4 + src/core/libraries/system/systemservice.cpp | 17 ++- src/core/libraries/system/systemservice.h | 2 +- src/core/linker.cpp | 10 +- src/core/linker.h | 2 +- src/emulator.cpp | 159 +++++++++++++++++--- src/emulator.h | 13 +- src/main.cpp | 50 +++++- src/qt_gui/main.cpp | 47 +++++- src/qt_gui/main_window.cpp | 8 +- 16 files changed, 432 insertions(+), 43 deletions(-) create mode 100644 src/core/debugger.cpp create mode 100644 src/core/debugger.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 2a0abe7b1..780a20427 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -824,6 +824,8 @@ set(CORE src/core/aerolib/stubs.cpp ${DEV_TOOLS} src/core/debug_state.cpp src/core/debug_state.h + src/core/debugger.cpp + src/core/debugger.h src/core/linker.cpp src/core/linker.h src/core/memory.cpp diff --git a/src/common/logging/backend.cpp b/src/common/logging/backend.cpp index 2bb4c6a4a..ce9386853 100644 --- a/src/common/logging/backend.cpp +++ b/src/common/logging/backend.cpp @@ -61,8 +61,9 @@ private: */ class FileBackend { public: - explicit FileBackend(const std::filesystem::path& filename) - : file{filename, FS::FileAccessMode::Write, FS::FileType::TextFile} {} + explicit FileBackend(const std::filesystem::path& filename, bool should_append = false) + : file{filename, should_append ? FS::FileAccessMode::Append : FS::FileAccessMode::Write, + FS::FileType::TextFile} {} ~FileBackend() = default; @@ -145,6 +146,11 @@ public: initialization_in_progress_suppress_logging = false; } + static void ResetInstance() { + initialization_in_progress_suppress_logging = true; + instance.reset(); + } + static bool IsActive() { return instance != nullptr; } @@ -157,6 +163,10 @@ public: instance->StopBackendThread(); } + static void SetAppend() { + should_append = true; + } + Impl(const Impl&) = delete; Impl& operator=(const Impl&) = delete; @@ -218,7 +228,7 @@ public: private: Impl(const std::filesystem::path& file_backend_filename, const Filter& filter_) - : filter{filter_}, file_backend{file_backend_filename} {} + : filter{filter_}, file_backend{file_backend_filename, should_append} {} ~Impl() = default; @@ -264,6 +274,7 @@ private: } static inline std::unique_ptr instance{nullptr, Deleter}; + static inline bool should_append{false}; Filter filter; DebuggerBackend debugger_backend{}; @@ -292,6 +303,11 @@ void Stop() { Impl::Stop(); } +void Denitializer() { + Impl::Stop(); + Impl::ResetInstance(); +} + void SetGlobalFilter(const Filter& filter) { Impl::Instance().SetGlobalFilter(filter); } @@ -300,6 +316,10 @@ void SetColorConsoleBackendEnabled(bool enabled) { Impl::Instance().SetColorConsoleBackendEnabled(enabled); } +void SetAppend() { + Impl::SetAppend(); +} + void FmtLogMessageImpl(Class log_class, Level log_level, const char* filename, unsigned int line_num, const char* function, const char* format, const fmt::format_args& args) { diff --git a/src/common/logging/backend.h b/src/common/logging/backend.h index a1ad66369..3003d1eae 100644 --- a/src/common/logging/backend.h +++ b/src/common/logging/backend.h @@ -21,9 +21,14 @@ void Start(); /// Explictily stops the logger thread and flushes the buffers void Stop(); +/// Closes log files and stops the logger +void Denitializer(); + /// The global filter will prevent any messages from even being processed if they are filtered. void SetGlobalFilter(const Filter& filter); void SetColorConsoleBackendEnabled(bool enabled); +void SetAppend(); + } // namespace Common::Log diff --git a/src/core/debugger.cpp b/src/core/debugger.cpp new file mode 100644 index 000000000..16071ee69 --- /dev/null +++ b/src/core/debugger.cpp @@ -0,0 +1,99 @@ +// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "debugger.h" + +#include +#include + +#if defined(_WIN32) +#include +#include +#elif defined(__linux__) +#include +#include +#elif defined(__APPLE__) +#include +#include +#include +#include +#include +#endif + +bool Core::Debugger::IsDebuggerAttached() { +#if defined(_WIN32) + return IsDebuggerPresent(); +#elif defined(__linux__) + std::ifstream status_file("/proc/self/status"); + std::string line; + while (std::getline(status_file, line)) { + if (line.starts_with("TracerPid:")) { + std::string tracer_pid = line.substr(10); + tracer_pid.erase(0, tracer_pid.find_first_not_of(" \t")); + return tracer_pid != "0"; + } + } + return false; +#elif defined(__APPLE__) + int mib[4]; + struct kinfo_proc info; + size_t size = sizeof(info); + + mib[0] = CTL_KERN; + mib[1] = KERN_PROC; + mib[2] = KERN_PROC_PID; + mib[3] = getpid(); + + if (sysctl(mib, 4, &info, &size, nullptr, 0) == 0) { + return (info.kp_proc.p_flag & P_TRACED) != 0; + } + return false; +#else +#error "Unsupported platform" +#endif +} + +void Core::Debugger::WaitForDebuggerAttach() { + int count = 0; + while (!IsDebuggerAttached()) { + std::this_thread::sleep_for(std::chrono::milliseconds(200)); + if (--count <= 0) { + count = 10; + std::cerr << "Waiting for debugger to attach..." << std::endl; + } + } +} +int Core::Debugger::GetCurrentPid() { +#if defined(_WIN32) + return GetCurrentProcessId(); +#elif defined(__APPLE__) || defined(__linux__) + return getpid(); +#else +#error "Unsupported platform" +#endif +} + +void Core::Debugger::WaitForPid(int pid) { +#if defined(_WIN32) + HANDLE process_handle = OpenProcess(SYNCHRONIZE, FALSE, pid); + if (process_handle != nullptr) { + std::cerr << "Waiting for process " << pid << " to exit..." << std::endl; + WaitForSingleObject(process_handle, INFINITE); + CloseHandle(process_handle); + } +#elif defined(__linux__) + std::string proc_path = "/proc/" + std::to_string(pid); + + while (std::filesystem::exists(proc_path)) { + std::this_thread::sleep_for(std::chrono::milliseconds(500)); + std::cerr << "Waiting for process " << pid << " to exit..." << std::endl; + } +#elif defined(__APPLE__) + while (kill(pid, 0) == 0) { + std::this_thread::sleep_for(std::chrono::milliseconds(500)); + std::cerr << "Waiting for process " << pid << " to exit..." << std::endl; + } +#else +#error "Unsupported platform" +#endif +} diff --git a/src/core/debugger.h b/src/core/debugger.h new file mode 100644 index 000000000..21be1826a --- /dev/null +++ b/src/core/debugger.h @@ -0,0 +1,16 @@ +// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +namespace Core::Debugger { + +bool IsDebuggerAttached(); + +void WaitForDebuggerAttach(); + +int GetCurrentPid(); + +void WaitForPid(int pid); + +} // namespace Core::Debugger \ No newline at end of file diff --git a/src/core/ipc/ipc.cpp b/src/core/ipc/ipc.cpp index 67df60cf0..5b8376624 100644 --- a/src/core/ipc/ipc.cpp +++ b/src/core/ipc/ipc.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include "ipc.h" @@ -12,6 +12,7 @@ #include "common/thread.h" #include "common/types.h" #include "core/debug_state.h" +#include "core/debugger.h" #include "input/input_handler.h" #include "sdl_window.h" @@ -40,6 +41,7 @@ * Command list: * - CAPABILITIES: * - ENABLE_MEMORY_PATCH: enables PATCH_MEMORY command + * - ENABLE_EMU_CONTROL: enables PAUSE, RESUME, STOP, TOGGLE_FULLSCREEN commands * - INPUT CMD: * - RUN: start the emulator execution * - START: start the game execution @@ -53,7 +55,7 @@ * - STOP: stop and quit the emulator * - TOGGLE_FULLSCREEN: enable / disable fullscreen * - OUTPUT CMD: - * - N/A + * - RESTART(argn: number, argv: ...string): Request restart of the emulator, must call STOP **/ void IPC::Init() { @@ -81,6 +83,15 @@ void IPC::Init() { } } +void IPC::SendRestart(const std::vector& args) { + std::cerr << ";RESTART\n"; + std::cerr << ";" << args.size() << "\n"; + for (const auto& arg : args) { + std::cerr << ";" << arg << "\n"; + } + std::cerr.flush(); +} + void IPC::InputLoop() { auto next_str = [&] -> const std::string& { static std::string line_buffer; diff --git a/src/core/ipc/ipc.h b/src/core/ipc/ipc.h index 28481d524..933afa897 100644 --- a/src/core/ipc/ipc.h +++ b/src/core/ipc/ipc.h @@ -6,7 +6,9 @@ #include "common/singleton.h" #include +#include #include +#include class IPC { bool enabled{false}; @@ -34,6 +36,8 @@ public: start_semaphore.acquire(); } + void SendRestart(const std::vector& args); + private: [[noreturn]] void InputLoop(); }; diff --git a/src/core/libraries/system/systemservice.cpp b/src/core/libraries/system/systemservice.cpp index 927f53f57..c02c4b3c3 100644 --- a/src/core/libraries/system/systemservice.cpp +++ b/src/core/libraries/system/systemservice.cpp @@ -3,9 +3,12 @@ #include "common/config.h" #include "common/logging/log.h" +#include "common/singleton.h" +#include "core/file_sys/fs.h" #include "core/libraries/libs.h" #include "core/libraries/system/systemservice.h" #include "core/libraries/system/systemservice_error.h" +#include "emulator.h" namespace Libraries::SystemService { @@ -1866,8 +1869,18 @@ int PS4_SYSV_ABI sceSystemServiceLaunchWebBrowser() { return ORBIS_OK; } -int PS4_SYSV_ABI sceSystemServiceLoadExec() { - LOG_ERROR(Lib_SystemService, "(STUBBED) called"); +int PS4_SYSV_ABI sceSystemServiceLoadExec(const char* path, const char* argv[]) { + LOG_DEBUG(Lib_SystemService, "called"); + auto emu = Common::Singleton::Instance(); + auto mnt = Common::Singleton::Instance(); + auto hostPath = mnt->GetHostPath(std::string_view(path)); + std::vector args; + if (argv != nullptr) { + for (const char** ptr = argv; *ptr != nullptr; ptr++) { + args.push_back(std::string(*ptr)); + } + } + emu->Restart(hostPath, args); return ORBIS_OK; } diff --git a/src/core/libraries/system/systemservice.h b/src/core/libraries/system/systemservice.h index fec7be399..b8bdf0b5f 100644 --- a/src/core/libraries/system/systemservice.h +++ b/src/core/libraries/system/systemservice.h @@ -501,7 +501,7 @@ int PS4_SYSV_ABI sceSystemServiceLaunchEventDetails(); int PS4_SYSV_ABI sceSystemServiceLaunchTournamentList(); int PS4_SYSV_ABI sceSystemServiceLaunchTournamentsTeamProfile(); int PS4_SYSV_ABI sceSystemServiceLaunchWebBrowser(); -int PS4_SYSV_ABI sceSystemServiceLoadExec(); +int PS4_SYSV_ABI sceSystemServiceLoadExec(const char* path, const char* argv[]); int PS4_SYSV_ABI sceSystemServiceNavigateToAnotherApp(); int PS4_SYSV_ABI sceSystemServiceNavigateToGoBack(); int PS4_SYSV_ABI sceSystemServiceNavigateToGoBackWithValue(); diff --git a/src/core/linker.cpp b/src/core/linker.cpp index 9dcb5c2f2..ac6b37769 100644 --- a/src/core/linker.cpp +++ b/src/core/linker.cpp @@ -55,7 +55,7 @@ Linker::Linker() : memory{Memory::Instance()} {} Linker::~Linker() = default; -void Linker::Execute(const std::vector args) { +void Linker::Execute(const std::vector& args) { if (Config::debugDump()) { DebugDump(); } @@ -115,7 +115,7 @@ void Linker::Execute(const std::vector args) { 0, "SceKernelInternalMemory"); ASSERT_MSG(ret == 0, "Unable to perform sceKernelInternalMemory mapping"); - main_thread.Run([this, module, args](std::stop_token) { + main_thread.Run([this, module, &args](std::stop_token) { Common::SetCurrentThreadName("GAME_MainThread"); if (auto& ipc = IPC::Instance()) { ipc.WaitForStart(); @@ -140,9 +140,9 @@ void Linker::Execute(const std::vector args) { params.argc = 1; params.argv[0] = "eboot.bin"; if (!args.empty()) { - params.argc = args.size() + 1; - for (int i = 0; i < args.size() && i < 32; i++) { - params.argv[i + 1] = args[i].c_str(); + params.argc = args.size(); + for (int i = 0; i < args.size() && i < 33; i++) { + params.argv[i] = args[i].c_str(); } } params.entry_addr = module->GetEntryAddress(); diff --git a/src/core/linker.h b/src/core/linker.h index 028e18ead..fef005605 100644 --- a/src/core/linker.h +++ b/src/core/linker.h @@ -151,7 +151,7 @@ public: void Relocate(Module* module); bool Resolve(const std::string& name, Loader::SymbolType type, Module* module, Loader::SymbolRecord* return_info); - void Execute(const std::vector args = {}); + void Execute(const std::vector& args = {}); void DebugDump(); private: diff --git a/src/emulator.cpp b/src/emulator.cpp index 600f907ae..c6515fe65 100644 --- a/src/emulator.cpp +++ b/src/emulator.cpp @@ -3,15 +3,18 @@ #include #include +#include #include #include #include +#include #include #include "common/config.h" #include "common/debug.h" #include "common/logging/backend.h" #include "common/logging/log.h" +#include "core/ipc/ipc.h" #ifdef ENABLE_QT_GUI #include #endif @@ -19,9 +22,6 @@ #ifdef ENABLE_DISCORD_RPC #include "common/discord_rpc_handler.h" #endif -#ifdef _WIN32 -#include -#endif #include "common/elf_info.h" #include "common/memory_patcher.h" #include "common/ntapi.h" @@ -29,6 +29,7 @@ #include "common/polyfill_thread.h" #include "common/scm_rev.h" #include "common/singleton.h" +#include "core/debugger.h" #include "core/devtools/widget/module_list.h" #include "core/file_format/psf.h" #include "core/file_format/trp.h" @@ -45,6 +46,15 @@ #include "emulator.h" #include "video_core/renderdoc.h" +#ifdef _WIN32 +#include +#endif + +#ifndef _WIN32 +#include +#include +#endif + Frontend::WindowSDL* g_window = nullptr; namespace Core { @@ -63,25 +73,35 @@ Emulator::Emulator() { Emulator::~Emulator() {} -void Emulator::Run(std::filesystem::path file, const std::vector args) { +void Emulator::Run(std::filesystem::path file, std::vector args, + std::optional p_game_folder) { + if (waitForDebuggerBeforeRun) { + Debugger::WaitForDebuggerAttach(); + } + if (std::filesystem::is_directory(file)) { file /= "eboot.bin"; } - const auto eboot_name = file.filename().string(); - - auto game_folder = file.parent_path(); - if (const auto game_folder_name = game_folder.filename().string(); - game_folder_name.ends_with("-UPDATE") || game_folder_name.ends_with("-patch")) { - // If an executable was launched from a separate update directory, - // use the base game directory as the game folder. - const std::string base_name = game_folder_name.substr(0, game_folder_name.rfind('-')); - const auto base_path = game_folder.parent_path() / base_name; - if (std::filesystem::is_directory(base_path)) { - game_folder = base_path; + std::filesystem::path game_folder; + if (p_game_folder.has_value()) { + game_folder = p_game_folder.value(); + } else { + game_folder = file.parent_path(); + if (const auto game_folder_name = game_folder.filename().string(); + game_folder_name.ends_with("-UPDATE") || game_folder_name.ends_with("-patch")) { + // If an executable was launched from a separate update directory, + // use the base game directory as the game folder. + const std::string base_name = game_folder_name.substr(0, game_folder_name.rfind('-')); + const auto base_path = game_folder.parent_path() / base_name; + if (std::filesystem::is_directory(base_path)) { + game_folder = base_path; + } } } + std::filesystem::path eboot_name = std::filesystem::relative(file, game_folder); + // Applications expect to be run from /app0 so mount the file's parent path as app0. auto* mnt = Common::Singleton::Instance(); mnt->Mount(game_folder, "/app0", true); @@ -290,10 +310,11 @@ void Emulator::Run(std::filesystem::path file, const std::vector ar Libraries::InitHLELibs(&linker->GetHLESymbols()); // Load the module with the linker - const auto eboot_path = mnt->GetHostPath("/app0/" + eboot_name); + auto guest_eboot_path = "/app0/" + eboot_name.generic_string(); + const auto eboot_path = mnt->GetHostPath(guest_eboot_path); if (linker->LoadModule(eboot_path) == -1) { LOG_CRITICAL(Loader, "Failed to load game's eboot.bin: {}", - std::filesystem::absolute(eboot_path).string()); + Common::FS::PathToUTF8String(std::filesystem::absolute(eboot_path))); std::quick_exit(0); } @@ -334,6 +355,7 @@ void Emulator::Run(std::filesystem::path file, const std::vector ar } #endif + args.insert(args.begin(), eboot_name.generic_string()); linker->Execute(args); window->InitTimers(); @@ -348,6 +370,109 @@ void Emulator::Run(std::filesystem::path file, const std::vector ar std::quick_exit(0); } +void Emulator::Restart(std::filesystem::path eboot_path, + const std::vector& guest_args) { + std::vector args; + + auto mnt = Common::Singleton::Instance(); + auto game_path = mnt->GetHostPath("/app0"); + + args.push_back("--log-append"); + args.push_back("--game"); + args.push_back(Common::FS::PathToUTF8String(eboot_path)); + + args.push_back("--override-root"); + args.push_back(Common::FS::PathToUTF8String(game_path)); + + if (FileSys::MntPoints::ignore_game_patches) { + args.push_back("--ignore-game-patch"); + } + + if (!MemoryPatcher::patchFile.empty()) { + args.push_back("--patch"); + args.push_back(MemoryPatcher::patchFile); + } + + args.push_back("--wait-for-pid"); + args.push_back(std::to_string(Debugger::GetCurrentPid())); + + if (waitForDebuggerBeforeRun) { + args.push_back("--wait-for-debugger"); + } + + if (guest_args.size() > 0) { + args.push_back("--"); + for (const auto& arg : guest_args) { + args.push_back(arg); + } + } + + LOG_INFO(Common, "Restarting the emulator with args: {}", fmt::join(args, " ")); + Libraries::SaveData::Backup::StopThread(); + Common::Log::Denitializer(); + + auto& ipc = IPC::Instance(); + + if (ipc.IsEnabled()) { + ipc.SendRestart(args); + while (true) { + std::this_thread::sleep_for(std::chrono::minutes(1)); + } + } +#if defined(_WIN32) + std::string cmdline; + // Emulator executable + cmdline += "\""; + cmdline += executableName; + cmdline += "\""; + for (const auto& arg : args) { + cmdline += " \""; + cmdline += arg; + cmdline += "\""; + } + cmdline += "\0"; + + STARTUPINFOA si{}; + si.cb = sizeof(si); + PROCESS_INFORMATION pi{}; + bool success = CreateProcessA(nullptr, cmdline.data(), nullptr, nullptr, TRUE, 0, nullptr, + nullptr, &si, &pi); + + if (!success) { + std::cerr << "Failed to restart game: {}" << GetLastError() << std::endl; + std::quick_exit(1); + } + + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); +#elif defined(__APPLE__) || defined(__linux__) + std::vector argv; + + // Emulator executable + argv.push_back(const_cast(executableName)); + + for (const auto& arg : args) { + argv.push_back(const_cast(arg.c_str())); + } + argv.push_back(nullptr); + + pid_t pid = fork(); + if (pid == 0) { + // Child process - execute the new instance + execvp(executableName, argv.data()); + std::cerr << "Failed to restart game: execvp failed" << std::endl; + std::quick_exit(1); + } else if (pid < 0) { + std::cerr << "Failed to restart game: fork failed" << std::endl; + std::quick_exit(1); + } +#else +#error "Unsupported platform" +#endif + + std::quick_exit(0); +} + void Emulator::LoadSystemModules(const std::string& game_serial) { constexpr auto ModulesToLoad = std::to_array( {{"libSceNgs2.sprx", &Libraries::Ngs2::RegisterLib}, diff --git a/src/emulator.h b/src/emulator.h index 257ccd694..f4dd32c20 100644 --- a/src/emulator.h +++ b/src/emulator.h @@ -4,6 +4,7 @@ #pragma once #include +#include #include #include "common/singleton.h" @@ -25,9 +26,19 @@ public: Emulator(); ~Emulator(); - void Run(std::filesystem::path file, const std::vector args = {}); + void Run(std::filesystem::path file, std::vector args = {}, + std::optional game_folder = {}); void UpdatePlayTime(const std::string& serial); + /** + * This will kill the current process and launch a new process with the same configuration + * (using CLI args) but replacing the eboot image and guest arguments + */ + void Restart(std::filesystem::path eboot_path, const std::vector& guest_args = {}); + + const char* executableName; + bool waitForDebuggerBeforeRun{false}; + private: void LoadSystemModules(const std::string& game_serial); diff --git a/src/main.cpp b/src/main.cpp index c2a3ed221..90c694cc8 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include "functional" @@ -9,8 +9,10 @@ #include #include "common/config.h" +#include "common/logging/backend.h" #include "common/memory_patcher.h" #include "common/path_util.h" +#include "core/debugger.h" #include "core/file_sys/fs.h" #include "core/ipc/ipc.h" #include "emulator.h" @@ -32,6 +34,10 @@ int main(int argc, char* argv[]) { bool has_game_argument = false; std::string game_path; std::vector game_args{}; + std::optional game_folder; + + bool waitForDebugger = false; + std::optional waitPid; // Map of argument strings to lambda functions std::unordered_map> arg_map = { @@ -50,6 +56,12 @@ int main(int argc, char* argv[]) { "state. Does not overwrite the config file.\n" " --add-game-folder Adds a new game folder to the config.\n" " --set-addon-folder Sets the addon folder to the config.\n" + " --log-append Append log output to file instead of " + "overwriting it.\n" + " --override-root Override the game root folder. Default is the " + "parent of game path\n" + " --wait-for-debugger Wait for debugger to attach\n" + " --wait-for-pid Wait for process with specified PID to stop\n" " -h, --help Display this help message\n"; exit(0); }}, @@ -119,7 +131,8 @@ int main(int argc, char* argv[]) { std::cout << "Game folder successfully saved.\n"; exit(0); }}, - {"--set-addon-folder", [&](int& i) { + {"--set-addon-folder", + [&](int& i) { if (++i >= argc) { std::cerr << "Error: Missing argument for --add-addon-folder\n"; exit(1); @@ -136,6 +149,29 @@ int main(int argc, char* argv[]) { Config::save(Common::FS::GetUserPath(Common::FS::PathType::UserDir) / "config.toml"); std::cout << "Addon folder successfully saved.\n"; exit(0); + }}, + {"--log-append", [&](int& i) { Common::Log::SetAppend(); }}, + {"--override-root", + [&](int& i) { + if (++i >= argc) { + std::cerr << "Error: Missing argument for --override-root\n"; + exit(1); + } + std::string folder_str{argv[i]}; + std::filesystem::path folder{folder_str}; + if (!std::filesystem::exists(folder) || !std::filesystem::is_directory(folder)) { + std::cerr << "Error: Folder does not exist: " << folder_str << "\n"; + exit(1); + } + game_folder = folder; + }}, + {"--wait-for-debugger", [&](int& i) { waitForDebugger = true; }}, + {"--wait-for-pid", [&](int& i) { + if (++i >= argc) { + std::cerr << "Error: Missing argument for --wait-for-pid\n"; + exit(1); + } + waitPid = std::stoi(argv[i]); }}}; if (argc == 1) { @@ -206,9 +242,15 @@ int main(int argc, char* argv[]) { } } + if (waitPid.has_value()) { + Core::Debugger::WaitForPid(waitPid.value()); + } + // Run the emulator with the resolved eboot path - Core::Emulator emulator; - emulator.Run(eboot_path, game_args); + Core::Emulator* emulator = Common::Singleton::Instance(); + emulator->executableName = argv[0]; + emulator->waitForDebuggerBeforeRun = waitForDebugger; + emulator->Run(eboot_path, game_args, game_folder); return 0; } diff --git a/src/qt_gui/main.cpp b/src/qt_gui/main.cpp index b7de517e8..c4d1b5129 100644 --- a/src/qt_gui/main.cpp +++ b/src/qt_gui/main.cpp @@ -6,7 +6,9 @@ #include "unordered_map" #include "common/config.h" +#include "common/logging/backend.h" #include "common/memory_patcher.h" +#include "core/debugger.h" #include "core/file_sys/fs.h" #include "emulator.h" #include "game_install_dialog.h" @@ -36,6 +38,10 @@ int main(int argc, char* argv[]) { bool show_gui = false, has_game_argument = false; std::string game_path; std::vector game_args{}; + std::optional game_folder; + + bool waitForDebugger = false; + std::optional waitPid; // Map of argument strings to lambda functions std::unordered_map> arg_map = { @@ -56,6 +62,12 @@ int main(int argc, char* argv[]) { " -f, --fullscreen Specify window initial fullscreen " "state. Does not overwrite the config file.\n" " --add-game-folder Adds a new game folder to the config.\n" + " --log-append Append log output to file instead of " + "overwriting it.\n" + " --override-root Override the game root folder. Default is the " + "parent of game path\n" + " --wait-for-debugger Wait for debugger to attach\n" + " --wait-for-pid Wait for process with specified PID to stop\n" " -h, --help Display this help message\n"; exit(0); }}, @@ -129,7 +141,29 @@ int main(int argc, char* argv[]) { std::cout << "Game folder successfully saved.\n"; exit(0); }}, - }; + {"--log-append", [&](int& i) { Common::Log::SetAppend(); }}, + {"--override-root", + [&](int& i) { + if (++i >= argc) { + std::cerr << "Error: Missing argument for --override-root\n"; + exit(1); + } + std::string folder_str{argv[i]}; + std::filesystem::path folder{folder_str}; + if (!std::filesystem::exists(folder) || !std::filesystem::is_directory(folder)) { + std::cerr << "Error: Folder does not exist: " << folder_str << "\n"; + exit(1); + } + game_folder = folder; + }}, + {"--wait-for-debugger", [&](int& i) { waitForDebugger = true; }}, + {"--wait-for-pid", [&](int& i) { + if (++i >= argc) { + std::cerr << "Error: Missing argument for --wait-for-pid\n"; + exit(1); + } + waitPid = std::stoi(argv[i]); + }}}; // Parse command-line arguments using the map for (int i = 1; i < argc; ++i) { @@ -181,6 +215,14 @@ int main(int argc, char* argv[]) { exit(1); } + if (waitPid.has_value()) { + Core::Debugger::WaitForPid(waitPid.value()); + } + + Core::Emulator* emulator = Common::Singleton::Instance(); + emulator->executableName = argv[0]; + emulator->waitForDebuggerBeforeRun = waitForDebugger; + // Process game path or ID if provided if (has_game_argument) { std::filesystem::path game_file_path(game_path); @@ -204,8 +246,7 @@ int main(int argc, char* argv[]) { } // Run the emulator with the resolved game path - Core::Emulator emulator; - emulator.Run(game_file_path.string(), game_args); + emulator->Run(game_file_path.string(), game_args, game_folder); if (!show_gui) { return 0; // Exit after running the emulator without showing the GUI } diff --git a/src/qt_gui/main_window.cpp b/src/qt_gui/main_window.cpp index 8dc21731c..8d48b6b7d 100644 --- a/src/qt_gui/main_window.cpp +++ b/src/qt_gui/main_window.cpp @@ -1225,12 +1225,12 @@ void MainWindow::StartEmulator(std::filesystem::path path) { isGameRunning = true; #ifdef __APPLE__ // SDL on macOS requires main thread. - Core::Emulator emulator; - emulator.Run(path); + Core::Emulator* emulator = Common::Singleton::Instance(); + emulator->Run(path); #else std::thread emulator_thread([=] { - Core::Emulator emulator; - emulator.Run(path); + Core::Emulator* emulator = Common::Singleton::Instance(); + emulator->Run(path); }); emulator_thread.detach(); #endif