mirror of
https://github.com/shadps4-emu/shadPS4.git
synced 2026-03-28 22:49:42 -06:00
* Replace sysmodule enums with table
Dumped this from the 12.52 module, using a script I created.
* Better documentation
* Separate from system libraries
That system folder is going to be getting quite large if I left all the sysmodule stuff in there.
* More arrays from library
* Found another preload list
Ghidra really hates decompiling libSceSysmodule, so I didn't notice this one at first.
Also documented specific versions tied to each preload list.
* Start work on implementation
* Some basic implementations
* Initial stub for module loading
Just enough to see if the logic functions appropriately.
* Clang
* sceSysmoduleLoadModule
Now I need to get sceSysmodulePreloadModuleForLibkernel done so that we don't have bugs from not loading internal LLEs.
* sceSysmoduleLoadModuleInternal
* sceSysmodulePreloadModuleForLibkernel
I have successfully broken quite literally everything.
I shall debug this tomorrow.
* Slight fix
* Maybe fix?
* Change log
Enjoy the log spam 😄
* Increased defined stub count
Now that libc and libSceLibcInternal loads later, all the auto stubs are getting consumed by it.
* sceSysmoduleUnloadModule stub
Also a couple fixes. Sysmodule does pass argc and argv to game modules, but only after loading them once to check binaries.
Shouldn't matter for the most part.
* Clang
* Less stubs
2 thousand is seemingly enough.
* sceSysmoduleLoadModuleInternalWithArg
Doesn't hurt to have, since Apex Legends calls it.
* Oops
* Oops 2
* Rename isModuleLoaded to getModuleHandle
Review comment
* Remove debug game module loads
These cases only trigger when specific sceRegMgr key values are set, and for our purposes, we can treat that case as false.
* Allow preloading to fail
For kalaposfos
* Clang
607 lines
22 KiB
C++
607 lines
22 KiB
C++
// SPDX-FileCopyrightText: Copyright 2025-2026 shadPS4 Emulator Project
|
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
#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 "common/thread.h"
|
|
#include "core/ipc/ipc.h"
|
|
#ifdef ENABLE_DISCORD_RPC
|
|
#include "common/discord_rpc_handler.h"
|
|
#endif
|
|
#include "common/elf_info.h"
|
|
#include "common/memory_patcher.h"
|
|
#include "common/ntapi.h"
|
|
#include "common/path_util.h"
|
|
#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/emulator_state.h"
|
|
#include "core/file_format/psf.h"
|
|
#include "core/file_format/trp.h"
|
|
#include "core/file_sys/fs.h"
|
|
#include "core/libraries/kernel/kernel.h"
|
|
#include "core/libraries/libs.h"
|
|
#include "core/libraries/np/np_trophy.h"
|
|
#include "core/libraries/save_data/save_backup.h"
|
|
#include "core/linker.h"
|
|
#include "core/memory.h"
|
|
#include "emulator.h"
|
|
#include "video_core/cache_storage.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 {
|
|
|
|
Emulator::Emulator() {
|
|
// Initialize NT API functions, set high priority and disable WER
|
|
#ifdef _WIN32
|
|
Common::NtApi::Initialize();
|
|
SetPriorityClass(GetCurrentProcess(), ABOVE_NORMAL_PRIORITY_CLASS);
|
|
SetErrorMode(SetErrorMode(0) | SEM_NOGPFAULTERRORBOX);
|
|
// need to init this in order for winsock2 to work
|
|
WORD versionWanted = MAKEWORD(2, 2);
|
|
WSADATA wsaData;
|
|
WSAStartup(versionWanted, &wsaData);
|
|
#endif
|
|
}
|
|
|
|
Emulator::~Emulator() {}
|
|
|
|
s32 ReadCompiledSdkVersion(const std::filesystem::path& file) {
|
|
Core::Loader::Elf elf;
|
|
elf.Open(file);
|
|
if (!elf.IsElfFile()) {
|
|
return 0;
|
|
}
|
|
const auto elf_pheader = elf.GetProgramHeader();
|
|
auto i_procparam = std::find_if(elf_pheader.begin(), elf_pheader.end(), [](const auto& entry) {
|
|
return entry.p_type == PT_SCE_PROCPARAM;
|
|
});
|
|
|
|
if (i_procparam != elf_pheader.end()) {
|
|
Core::OrbisProcParam param{};
|
|
elf.LoadSegment(u64(¶m), i_procparam->p_offset, i_procparam->p_filesz);
|
|
return param.sdk_version;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void Emulator::Run(std::filesystem::path file, std::vector<std::string> args,
|
|
std::optional<std::filesystem::path> p_game_folder) {
|
|
Common::SetCurrentThreadName("shadPS4:Main");
|
|
if (waitForDebuggerBeforeRun) {
|
|
Debugger::WaitForDebuggerAttach();
|
|
}
|
|
|
|
if (std::filesystem::is_directory(file)) {
|
|
file /= "eboot.bin";
|
|
}
|
|
|
|
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);
|
|
// Certain games may use /hostapp as well such as CUSA001100
|
|
mnt->Mount(game_folder, "/hostapp", true);
|
|
|
|
const auto param_sfo_path = mnt->GetHostPath("/app0/sce_sys/param.sfo");
|
|
const auto param_sfo_exists = std::filesystem::exists(param_sfo_path);
|
|
|
|
// Load param.sfo details if it exists
|
|
std::string id;
|
|
std::string title;
|
|
std::string app_version;
|
|
u32 sdk_version;
|
|
u32 fw_version;
|
|
Common::PSFAttributes psf_attributes{};
|
|
if (param_sfo_exists) {
|
|
auto* param_sfo = Common::Singleton<PSF>::Instance();
|
|
ASSERT_MSG(param_sfo->Open(param_sfo_path), "Failed to open param.sfo");
|
|
|
|
const auto content_id = param_sfo->GetString("CONTENT_ID");
|
|
const auto title_id = param_sfo->GetString("TITLE_ID");
|
|
if (content_id.has_value() && !content_id->empty()) {
|
|
id = std::string(*content_id, 7, 9);
|
|
} else if (title_id.has_value()) {
|
|
id = *title_id;
|
|
}
|
|
title = param_sfo->GetString("TITLE").value_or("Unknown title");
|
|
fw_version = param_sfo->GetInteger("SYSTEM_VER").value_or(0x4700000);
|
|
app_version = param_sfo->GetString("APP_VER").value_or("Unknown version");
|
|
if (const auto raw_attributes = param_sfo->GetInteger("ATTRIBUTE")) {
|
|
psf_attributes.raw = *raw_attributes;
|
|
}
|
|
|
|
// Extract sdk version from pubtool info.
|
|
std::string_view pubtool_info =
|
|
param_sfo->GetString("PUBTOOLINFO").value_or("Unknown value");
|
|
u64 sdk_ver_offset = pubtool_info.find("sdk_ver");
|
|
|
|
if (sdk_ver_offset == pubtool_info.npos) {
|
|
// Default to using firmware version if SDK version is not found.
|
|
sdk_version = fw_version;
|
|
} else {
|
|
// Increment offset to account for sdk_ver= part of string.
|
|
sdk_ver_offset += 8;
|
|
u64 sdk_ver_len = pubtool_info.find(",", sdk_ver_offset);
|
|
if (sdk_ver_len == pubtool_info.npos) {
|
|
// If there's no more commas, this is likely the last entry of pubtool info.
|
|
// Use string length instead.
|
|
sdk_ver_len = pubtool_info.size();
|
|
}
|
|
sdk_ver_len -= sdk_ver_offset;
|
|
std::string sdk_ver_string = pubtool_info.substr(sdk_ver_offset, sdk_ver_len).data();
|
|
// Number is stored in base 16.
|
|
sdk_version = std::stoi(sdk_ver_string, nullptr, 16);
|
|
}
|
|
}
|
|
|
|
auto guest_eboot_path = "/app0/" + eboot_name.generic_string();
|
|
const auto eboot_path = mnt->GetHostPath(guest_eboot_path);
|
|
|
|
auto& game_info = Common::ElfInfo::Instance();
|
|
game_info.initialized = true;
|
|
game_info.game_serial = id;
|
|
game_info.title = title;
|
|
game_info.app_ver = app_version;
|
|
game_info.firmware_ver = fw_version & 0xFFF00000;
|
|
game_info.raw_firmware_ver = fw_version;
|
|
game_info.sdk_ver = ReadCompiledSdkVersion(eboot_path);
|
|
game_info.psf_attributes = psf_attributes;
|
|
|
|
const auto pic1_path = mnt->GetHostPath("/app0/sce_sys/pic1.png");
|
|
if (std::filesystem::exists(pic1_path)) {
|
|
game_info.splash_path = pic1_path;
|
|
}
|
|
|
|
game_info.game_folder = game_folder;
|
|
|
|
Config::load(Common::FS::GetUserPath(Common::FS::PathType::CustomConfigs) / (id + ".toml"),
|
|
true);
|
|
|
|
if (std::filesystem::exists(Common::FS::GetUserPath(Common::FS::PathType::CustomConfigs) /
|
|
(id + ".toml"))) {
|
|
EmulatorState::GetInstance()->SetGameSpecifigConfigUsed(true);
|
|
} else {
|
|
EmulatorState::GetInstance()->SetGameSpecifigConfigUsed(false);
|
|
}
|
|
|
|
// Initialize logging as soon as possible
|
|
if (!id.empty() && Config::getSeparateLogFilesEnabled()) {
|
|
Common::Log::Initialize(id + ".log");
|
|
} else {
|
|
Common::Log::Initialize();
|
|
}
|
|
Common::Log::Start();
|
|
if (!std::filesystem::exists(file)) {
|
|
LOG_CRITICAL(Loader, "eboot.bin does not exist: {}",
|
|
std::filesystem::absolute(file).string());
|
|
std::quick_exit(0);
|
|
}
|
|
|
|
LOG_INFO(Loader, "Starting shadps4 emulator v{} ", Common::g_version);
|
|
LOG_INFO(Loader, "Revision {}", Common::g_scm_rev);
|
|
LOG_INFO(Loader, "Branch {}", Common::g_scm_branch);
|
|
LOG_INFO(Loader, "Description {}", Common::g_scm_desc);
|
|
LOG_INFO(Loader, "Remote {}", Common::g_scm_remote_url);
|
|
|
|
const bool has_game_config = std::filesystem::exists(
|
|
Common::FS::GetUserPath(Common::FS::PathType::CustomConfigs) / (id + ".toml"));
|
|
LOG_INFO(Config, "Game-specific config exists: {}", has_game_config);
|
|
|
|
LOG_INFO(Config, "General LogType: {}", Config::getLogType());
|
|
LOG_INFO(Config, "General isIdenticalLogGrouped: {}", Config::groupIdenticalLogs());
|
|
LOG_INFO(Config, "General isNeo: {}", Config::isNeoModeConsole());
|
|
LOG_INFO(Config, "General isDevKit: {}", Config::isDevKitConsole());
|
|
LOG_INFO(Config, "General isConnectedToNetwork: {}", Config::getIsConnectedToNetwork());
|
|
LOG_INFO(Config, "General isPsnSignedIn: {}", Config::getPSNSignedIn());
|
|
LOG_INFO(Config, "GPU isNullGpu: {}", Config::nullGpu());
|
|
LOG_INFO(Config, "GPU readbacksMode: {}", Config::getReadbacksMode());
|
|
LOG_INFO(Config, "GPU readbackLinearImages: {}", Config::readbackLinearImages());
|
|
LOG_INFO(Config, "GPU directMemoryAccess: {}", Config::directMemoryAccess());
|
|
LOG_INFO(Config, "GPU shouldDumpShaders: {}", Config::dumpShaders());
|
|
LOG_INFO(Config, "GPU vblankFrequency: {}", Config::vblankFreq());
|
|
LOG_INFO(Config, "GPU shouldCopyGPUBuffers: {}", Config::copyGPUCmdBuffers());
|
|
LOG_INFO(Config, "Vulkan gpuId: {}", Config::getGpuId());
|
|
LOG_INFO(Config, "Vulkan vkValidation: {}", Config::vkValidationEnabled());
|
|
LOG_INFO(Config, "Vulkan vkValidationCore: {}", Config::vkValidationCoreEnabled());
|
|
LOG_INFO(Config, "Vulkan vkValidationSync: {}", Config::vkValidationSyncEnabled());
|
|
LOG_INFO(Config, "Vulkan vkValidationGpu: {}", Config::vkValidationGpuEnabled());
|
|
LOG_INFO(Config, "Vulkan crashDiagnostics: {}", Config::getVkCrashDiagnosticEnabled());
|
|
LOG_INFO(Config, "Vulkan hostMarkers: {}", Config::getVkHostMarkersEnabled());
|
|
LOG_INFO(Config, "Vulkan guestMarkers: {}", Config::getVkGuestMarkersEnabled());
|
|
LOG_INFO(Config, "Vulkan rdocEnable: {}", Config::isRdocEnabled());
|
|
|
|
hwinfo::Memory ram;
|
|
hwinfo::OS os;
|
|
const auto cpus = hwinfo::getAllCPUs();
|
|
for (const auto& cpu : cpus) {
|
|
LOG_INFO(Config, "CPU Model: {}", cpu.modelName());
|
|
LOG_INFO(Config, "CPU Physical Cores: {}, Logical Cores: {}", cpu.numPhysicalCores(),
|
|
cpu.numLogicalCores());
|
|
}
|
|
LOG_INFO(Config, "Total RAM: {} GB", std::round(ram.total_Bytes() / pow(1024, 3)));
|
|
LOG_INFO(Config, "Operating System: {}", os.name());
|
|
|
|
if (param_sfo_exists) {
|
|
LOG_INFO(Loader, "Game id: {} Title: {}", id, title);
|
|
LOG_INFO(Loader, "Fw: {:#x} App Version: {}", fw_version, app_version);
|
|
LOG_INFO(Loader, "param.sfo SDK version: {:#x}", sdk_version);
|
|
LOG_INFO(Loader, "eboot SDK version: {:#x}", game_info.sdk_ver);
|
|
LOG_INFO(Loader, "PSVR Supported: {}", (bool)psf_attributes.support_ps_vr.Value());
|
|
LOG_INFO(Loader, "PSVR Required: {}", (bool)psf_attributes.require_ps_vr.Value());
|
|
}
|
|
if (!args.empty()) {
|
|
const auto argc = std::min<size_t>(args.size(), 32);
|
|
for (auto i = 0; i < argc; i++) {
|
|
LOG_INFO(Loader, "Game argument {}: {}", i, args[i]);
|
|
}
|
|
if (args.size() > 32) {
|
|
LOG_ERROR(Loader, "Too many game arguments, only passing the first 32");
|
|
}
|
|
}
|
|
|
|
// Create stdin/stdout/stderr
|
|
Common::Singleton<FileSys::HandleTable>::Instance()->CreateStdHandles();
|
|
|
|
// Initialize components
|
|
memory = Core::Memory::Instance();
|
|
controller = Common::Singleton<Input::GameController>::Instance();
|
|
linker = Common::Singleton<Core::Linker>::Instance();
|
|
|
|
// Load renderdoc module
|
|
VideoCore::LoadRenderDoc();
|
|
|
|
// Initialize patcher and trophies
|
|
if (!id.empty()) {
|
|
MemoryPatcher::g_game_serial = id;
|
|
Libraries::Np::NpTrophy::game_serial = id;
|
|
|
|
const auto trophyDir =
|
|
Common::FS::GetUserPath(Common::FS::PathType::MetaDataDir) / id / "TrophyFiles";
|
|
if (!std::filesystem::exists(trophyDir)) {
|
|
TRP trp;
|
|
if (!trp.Extract(game_folder, id)) {
|
|
LOG_ERROR(Loader, "Couldn't extract trophies");
|
|
}
|
|
}
|
|
}
|
|
|
|
std::string game_title = fmt::format("{} - {} <{}>", id, title, app_version);
|
|
std::string window_title = "";
|
|
std::string remote_url(Common::g_scm_remote_url);
|
|
std::string remote_host = Common::GetRemoteNameFromLink();
|
|
if (Common::g_is_release) {
|
|
if (remote_host == "shadps4-emu" || remote_url.length() == 0) {
|
|
window_title = fmt::format("shadPS4 v{} | {}", Common::g_version, game_title);
|
|
} else {
|
|
window_title =
|
|
fmt::format("shadPS4 {}/v{} | {}", remote_host, Common::g_version, game_title);
|
|
}
|
|
} else {
|
|
if (remote_host == "shadps4-emu" || remote_url.length() == 0) {
|
|
window_title = fmt::format("shadPS4 v{} {} {} | {}", Common::g_version,
|
|
Common::g_scm_branch, Common::g_scm_desc, game_title);
|
|
} else {
|
|
window_title = fmt::format("shadPS4 v{} {}/{} {} | {}", Common::g_version, remote_host,
|
|
Common::g_scm_branch, Common::g_scm_desc, game_title);
|
|
}
|
|
}
|
|
window = std::make_unique<Frontend::WindowSDL>(
|
|
Config::getWindowWidth(), Config::getWindowHeight(), controller, window_title);
|
|
|
|
g_window = window.get();
|
|
|
|
const auto& mount_data_dir = Common::FS::GetUserPath(Common::FS::PathType::GameDataDir);
|
|
mnt->Mount(mount_data_dir, "/data");
|
|
|
|
// Mounting temp folders
|
|
const auto& mount_temp_dir = Common::FS::GetUserPath(Common::FS::PathType::TempDataDir) / id;
|
|
if (std::filesystem::exists(mount_temp_dir)) {
|
|
// Temp folder should be cleared on each boot.
|
|
std::filesystem::remove_all(mount_temp_dir);
|
|
}
|
|
std::filesystem::create_directory(mount_temp_dir);
|
|
mnt->Mount(mount_temp_dir, "/temp0");
|
|
mnt->Mount(mount_temp_dir, "/temp");
|
|
|
|
const auto& mount_download_dir =
|
|
Common::FS::GetUserPath(Common::FS::PathType::DownloadDir) / id;
|
|
if (!std::filesystem::exists(mount_download_dir)) {
|
|
std::filesystem::create_directory(mount_download_dir);
|
|
}
|
|
mnt->Mount(mount_download_dir, "/download0");
|
|
|
|
const auto& mount_captures_dir = Common::FS::GetUserPath(Common::FS::PathType::CapturesDir);
|
|
if (!std::filesystem::exists(mount_captures_dir)) {
|
|
std::filesystem::create_directory(mount_captures_dir);
|
|
}
|
|
VideoCore::SetOutputDir(mount_captures_dir, id);
|
|
|
|
// Mount system fonts
|
|
const auto& fonts_dir = Config::getFontsPath();
|
|
if (!std::filesystem::exists(fonts_dir)) {
|
|
std::filesystem::create_directory(fonts_dir);
|
|
}
|
|
|
|
// Fonts are mounted into the sandboxed system directory, construct the appropriate path.
|
|
const char* sandbox_root = Libraries::Kernel::sceKernelGetFsSandboxRandomWord();
|
|
std::string guest_font_dir = "/";
|
|
guest_font_dir.append(sandbox_root).append("/common/font");
|
|
const auto& host_font_dir = fonts_dir / "font";
|
|
if (!std::filesystem::exists(host_font_dir)) {
|
|
std::filesystem::create_directory(host_font_dir);
|
|
}
|
|
mnt->Mount(host_font_dir, guest_font_dir);
|
|
|
|
// There is a second font directory, mount that too.
|
|
guest_font_dir.append("2");
|
|
const auto& host_font2_dir = fonts_dir / "font2";
|
|
if (!std::filesystem::exists(host_font2_dir)) {
|
|
std::filesystem::create_directory(host_font2_dir);
|
|
}
|
|
mnt->Mount(host_font2_dir, guest_font_dir);
|
|
|
|
if (std::filesystem::is_empty(host_font_dir) || std::filesystem::is_empty(host_font2_dir)) {
|
|
LOG_WARNING(Loader, "No dumped system fonts, expect missing text or instability");
|
|
}
|
|
|
|
// Initialize kernel and library facilities.
|
|
Libraries::InitHLELibs(&linker->GetHLESymbols());
|
|
|
|
// Load the module with the linker
|
|
if (linker->LoadModule(eboot_path) == -1) {
|
|
LOG_CRITICAL(Loader, "Failed to load game's eboot.bin: {}",
|
|
Common::FS::PathToUTF8String(std::filesystem::absolute(eboot_path)));
|
|
std::quick_exit(0);
|
|
}
|
|
|
|
#ifdef ENABLE_DISCORD_RPC
|
|
// Discord RPC
|
|
if (Config::getEnableDiscordRPC()) {
|
|
auto* rpc = Common::Singleton<DiscordRPCHandler::RPC>::Instance();
|
|
if (rpc->getRPCEnabled() == false) {
|
|
rpc->init();
|
|
}
|
|
rpc->setStatusPlaying(game_info.title, id);
|
|
}
|
|
#endif
|
|
|
|
if (!id.empty()) {
|
|
start_time = std::chrono::steady_clock::now();
|
|
|
|
std::thread([this, id]() {
|
|
while (true) {
|
|
std::this_thread::sleep_for(std::chrono::seconds(60));
|
|
UpdatePlayTime(id);
|
|
start_time = std::chrono::steady_clock::now();
|
|
}
|
|
}).detach();
|
|
}
|
|
|
|
args.insert(args.begin(), eboot_name.generic_string());
|
|
linker->Execute(args);
|
|
|
|
window->InitTimers();
|
|
while (window->IsOpen()) {
|
|
window->WaitEvent();
|
|
}
|
|
|
|
UpdatePlayTime(id);
|
|
Storage::DataBase::Instance().Close();
|
|
|
|
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::patch_file.empty()) {
|
|
args.push_back("--patch");
|
|
args.push_back(MemoryPatcher::patch_file);
|
|
}
|
|
|
|
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::UpdatePlayTime(const std::string& serial) {
|
|
const auto user_dir = Common::FS::GetUserPath(Common::FS::PathType::UserDir);
|
|
const auto filePath = (user_dir / "play_time.txt").string();
|
|
|
|
std::ifstream in(filePath);
|
|
if (!in && !std::ofstream(filePath)) {
|
|
LOG_INFO(Loader, "Error opening play_time.txt");
|
|
return;
|
|
}
|
|
|
|
auto end_time = std::chrono::steady_clock::now();
|
|
auto duration = std::chrono::duration_cast<std::chrono::seconds>(end_time - start_time);
|
|
int total_seconds = static_cast<int>(duration.count());
|
|
|
|
std::vector<std::string> lines;
|
|
std::string line;
|
|
while (std::getline(in, line)) {
|
|
lines.push_back(line);
|
|
}
|
|
in.close();
|
|
|
|
int accumulated_seconds = 0;
|
|
bool found = false;
|
|
|
|
for (const auto& l : lines) {
|
|
std::istringstream iss(l);
|
|
std::string s, time_str;
|
|
if (iss >> s >> time_str && s == serial) {
|
|
int h, m, s_;
|
|
char c1, c2;
|
|
std::istringstream ts(time_str);
|
|
if (ts >> h >> c1 >> m >> c2 >> s_ && c1 == ':' && c2 == ':') {
|
|
accumulated_seconds = h * 3600 + m * 60 + s_;
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
accumulated_seconds += total_seconds;
|
|
int hours = accumulated_seconds / 3600;
|
|
int minutes = (accumulated_seconds % 3600) / 60;
|
|
int seconds = accumulated_seconds % 60;
|
|
|
|
std::string playTimeSaved = fmt::format("{:d}:{:02d}:{:02d}", hours, minutes, seconds);
|
|
|
|
std::ofstream outfile(filePath, std::ios::trunc);
|
|
bool lineUpdated = false;
|
|
for (const auto& l : lines) {
|
|
std::istringstream iss(l);
|
|
std::string s;
|
|
if (iss >> s && s == serial) {
|
|
outfile << fmt::format("{} {}\n", serial, playTimeSaved);
|
|
lineUpdated = true;
|
|
} else {
|
|
outfile << l << "\n";
|
|
}
|
|
}
|
|
|
|
if (!lineUpdated) {
|
|
outfile << fmt::format("{} {}\n", serial, playTimeSaved);
|
|
}
|
|
|
|
LOG_INFO(Loader, "Playing time for {}: {}", serial, playTimeSaved);
|
|
}
|
|
|
|
} // namespace Core
|