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
This commit is contained in:
Vinicius Rangel
2025-09-25 23:01:52 -03:00
committed by GitHub
parent a6f5e4c7dc
commit 71f343d2d6
16 changed files with 432 additions and 43 deletions

View File

@@ -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

View File

@@ -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<Impl, decltype(&Deleter)> 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) {

View File

@@ -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

99
src/core/debugger.cpp Normal file
View File

@@ -0,0 +1,99 @@
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "debugger.h"
#include <iostream>
#include <thread>
#if defined(_WIN32)
#include <Windows.h>
#include <debugapi.h>
#elif defined(__linux__)
#include <filesystem>
#include <fstream>
#elif defined(__APPLE__)
#include <errno.h>
#include <signal.h>
#include <sys/sysctl.h>
#include <sys/types.h>
#include <unistd.h>
#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
}

16
src/core/debugger.h Normal file
View File

@@ -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

View File

@@ -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<std::string>& 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;

View File

@@ -6,7 +6,9 @@
#include "common/singleton.h"
#include <semaphore>
#include <string>
#include <thread>
#include <vector>
class IPC {
bool enabled{false};
@@ -34,6 +36,8 @@ public:
start_semaphore.acquire();
}
void SendRestart(const std::vector<std::string>& args);
private:
[[noreturn]] void InputLoop();
};

View File

@@ -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<Core::Emulator>::Instance();
auto mnt = Common::Singleton<Core::FileSys::MntPoints>::Instance();
auto hostPath = mnt->GetHostPath(std::string_view(path));
std::vector<std::string> 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;
}

View File

@@ -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();

View File

@@ -55,7 +55,7 @@ Linker::Linker() : memory{Memory::Instance()} {}
Linker::~Linker() = default;
void Linker::Execute(const std::vector<std::string> args) {
void Linker::Execute(const std::vector<std::string>& args) {
if (Config::debugDump()) {
DebugDump();
}
@@ -115,7 +115,7 @@ void Linker::Execute(const std::vector<std::string> 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<std::string> 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();

View File

@@ -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<std::string> args = {});
void Execute(const std::vector<std::string>& args = {});
void DebugDump();
private:

View File

@@ -3,15 +3,18 @@
#include <filesystem>
#include <fstream>
#include <iostream>
#include <set>
#include <sstream>
#include <fmt/core.h>
#include <fmt/xchar.h>
#include <hwinfo/hwinfo.h>
#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 <QtCore>
#endif
@@ -19,9 +22,6 @@
#ifdef ENABLE_DISCORD_RPC
#include "common/discord_rpc_handler.h"
#endif
#ifdef _WIN32
#include <WinSock2.h>
#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 <WinSock2.h>
#endif
#ifndef _WIN32
#include <sys/wait.h>
#include <unistd.h>
#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<std::string> args) {
void Emulator::Run(std::filesystem::path file, std::vector<std::string> args,
std::optional<std::filesystem::path> 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<Core::FileSys::MntPoints>::Instance();
mnt->Mount(game_folder, "/app0", true);
@@ -290,10 +310,11 @@ void Emulator::Run(std::filesystem::path file, const std::vector<std::string> 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<std::string> 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<std::string> ar
std::quick_exit(0);
}
void Emulator::Restart(std::filesystem::path eboot_path,
const std::vector<std::string>& guest_args) {
std::vector<std::string> args;
auto mnt = Common::Singleton<Core::FileSys::MntPoints>::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<char*> argv;
// Emulator executable
argv.push_back(const_cast<char*>(executableName));
for (const auto& arg : args) {
argv.push_back(const_cast<char*>(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<SysModules>(
{{"libSceNgs2.sprx", &Libraries::Ngs2::RegisterLib},

View File

@@ -4,6 +4,7 @@
#pragma once
#include <filesystem>
#include <optional>
#include <thread>
#include "common/singleton.h"
@@ -25,9 +26,19 @@ public:
Emulator();
~Emulator();
void Run(std::filesystem::path file, const std::vector<std::string> args = {});
void Run(std::filesystem::path file, std::vector<std::string> args = {},
std::optional<std::filesystem::path> 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<std::string>& guest_args = {});
const char* executableName;
bool waitForDebuggerBeforeRun{false};
private:
void LoadSystemModules(const std::string& game_serial);

View File

@@ -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 <fmt/core.h>
#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<std::string> game_args{};
std::optional<std::filesystem::path> game_folder;
bool waitForDebugger = false;
std::optional<int> waitPid;
// Map of argument strings to lambda functions
std::unordered_map<std::string, std::function<void(int&)>> arg_map = {
@@ -50,6 +56,12 @@ int main(int argc, char* argv[]) {
"state. Does not overwrite the config file.\n"
" --add-game-folder <folder> Adds a new game folder to the config.\n"
" --set-addon-folder <folder> Sets the addon folder to the config.\n"
" --log-append Append log output to file instead of "
"overwriting it.\n"
" --override-root <folder> 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 <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<Core::Emulator>::Instance();
emulator->executableName = argv[0];
emulator->waitForDebuggerBeforeRun = waitForDebugger;
emulator->Run(eboot_path, game_args, game_folder);
return 0;
}

View File

@@ -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<std::string> game_args{};
std::optional<std::filesystem::path> game_folder;
bool waitForDebugger = false;
std::optional<int> waitPid;
// Map of argument strings to lambda functions
std::unordered_map<std::string, std::function<void(int&)>> arg_map = {
@@ -56,6 +62,12 @@ int main(int argc, char* argv[]) {
" -f, --fullscreen <true|false> Specify window initial fullscreen "
"state. Does not overwrite the config file.\n"
" --add-game-folder <folder> Adds a new game folder to the config.\n"
" --log-append Append log output to file instead of "
"overwriting it.\n"
" --override-root <folder> 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 <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<Core::Emulator>::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
}

View File

@@ -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<Core::Emulator>::Instance();
emulator->Run(path);
#else
std::thread emulator_thread([=] {
Core::Emulator emulator;
emulator.Run(path);
Core::Emulator* emulator = Common::Singleton<Core::Emulator>::Instance();
emulator->Run(path);
});
emulator_thread.detach();
#endif