mirror of
https://github.com/shadps4-emu/shadPS4.git
synced 2025-12-16 12:09:07 +00:00
729 lines
28 KiB
C++
729 lines
28 KiB
C++
// SPDX-FileCopyrightText: Copyright 2025 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 "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/file_format/psf.h"
|
|
#include "core/file_format/trp.h"
|
|
#include "core/file_sys/fs.h"
|
|
#include "core/libraries/disc_map/disc_map.h"
|
|
#include "core/libraries/font/font.h"
|
|
#include "core/libraries/font/fontft.h"
|
|
#include "core/libraries/libc_internal/libc_internal.h"
|
|
#include "core/libraries/libs.h"
|
|
#include "core/libraries/ngs2/ngs2.h"
|
|
#include "core/libraries/np/np_trophy.h"
|
|
#include "core/libraries/rtc/rtc.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"
|
|
|
|
#include "core/file_sys/quasifs/quasi_sys_fcntl.h"
|
|
#include "core/file_sys/quasifs/quasifs.h"
|
|
#include "core/file_sys/quasifs/quasifs_inode_quasi_device.h"
|
|
#include "core/file_sys/quasifs/quasifs_inode_quasi_directory_pfs.h"
|
|
#include "core/file_sys/quasifs/quasifs_partition.h"
|
|
|
|
#include "core/file_sys/devices/console_device.h"
|
|
#include "core/file_sys/devices/deci_tty6_device.h"
|
|
#include "core/file_sys/devices/logger.h"
|
|
#include "core/file_sys/devices/nop_device.h"
|
|
#include "core/file_sys/devices/null_device.h"
|
|
#include "core/file_sys/devices/random_device.h"
|
|
#include "core/file_sys/devices/rng_device.h"
|
|
#include "core/file_sys/devices/srandom_device.h"
|
|
#include "core/file_sys/devices/zero_device.h"
|
|
|
|
#ifdef _WIN32
|
|
#include <WinSock2.h>
|
|
#endif
|
|
|
|
#ifndef _WIN32
|
|
#include <sys/wait.h>
|
|
#include <unistd.h>
|
|
#endif
|
|
|
|
Frontend::WindowSDL* g_window = nullptr;
|
|
|
|
namespace qfs = QuasiFS;
|
|
|
|
namespace Core {
|
|
|
|
bool Emulator::ignore_game_patches = false;
|
|
|
|
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() {}
|
|
|
|
void Emulator::LoadFilesystem(const std::filesystem::path& game_folder) {
|
|
auto* qfs = Common::Singleton<qfs::QFS>::Instance();
|
|
|
|
qfs::partition_ptr partition_app0 =
|
|
qfs::Partition::Create(qfs::DirectoryPFS::Create(), game_folder, 0555, 65536);
|
|
qfs::partition_ptr partition_av_contents = qfs::Partition::Create("", 0775, 16384);
|
|
qfs::partition_ptr partition_av_contents_photo = qfs::Partition::Create("", 0755, 32768);
|
|
qfs::partition_ptr partition_av_contents_thumbs = qfs::Partition::Create("", 0755, 32768);
|
|
qfs::partition_ptr partition_av_contents_video = qfs::Partition::Create("", 0755, 32768);
|
|
qfs::partition_ptr partition_dev = qfs::Partition::Create("", 0755, 16384);
|
|
|
|
qfs->Operation.MKDir("/app0", 0555);
|
|
qfs->Operation.MKDir("/av_contents", 0775);
|
|
qfs->Operation.MKDir("/dev", 0555);
|
|
qfs->Operation.MKDir("/host", 0777);
|
|
qfs->Operation.MKDir("/hostapp", 0777);
|
|
|
|
qfs->Mount("/app0", partition_app0, qfs::MountOptions::MOUNT_NOOPT);
|
|
qfs->Mount("/av_contents", partition_av_contents, qfs::MountOptions::MOUNT_RW);
|
|
qfs->Mount("/dev", partition_dev, qfs::MountOptions::MOUNT_RW);
|
|
|
|
// mounting av_contents would shadow these
|
|
qfs->Operation.MKDir("/av_contents/photo", 0755);
|
|
qfs->Operation.MKDir("/av_contents/thumbnails", 0755);
|
|
qfs->Operation.MKDir("/av_contents/video", 0755);
|
|
qfs->Mount("/av_contents/photo", partition_av_contents_photo, qfs::MountOptions::MOUNT_RW);
|
|
qfs->Mount("/av_contents/thumbnails", partition_av_contents_thumbs,
|
|
qfs::MountOptions::MOUNT_RW);
|
|
qfs->Mount("/av_contents/video", partition_av_contents_video, qfs::MountOptions::MOUNT_RW);
|
|
|
|
//
|
|
// Setup /dev
|
|
//
|
|
|
|
qfs->Operation.MKDir("/dev/fd");
|
|
qfs->ForceInsert("/dev/fd", "0", Devices::ZeroDevice::Create());
|
|
qfs->ForceInsert("/dev/fd", "1", Devices::Logger::Create("stdout", false));
|
|
qfs->ForceInsert("/dev/fd", "2", Devices::Logger::Create("stderr", true));
|
|
// std* is unavailable from within the app???
|
|
qfs->Operation.LinkSymbolic("/dev/fd/0", "/dev/stdin");
|
|
qfs->Operation.LinkSymbolic("/dev/fd/1", "/dev/stdout");
|
|
qfs->Operation.LinkSymbolic("/dev/fd/2", "/dev/stderr");
|
|
qfs->Operation.LinkSymbolic("/dev/fd/0", "/dev/deci_stdin");
|
|
qfs->Operation.LinkSymbolic("/dev/fd/1", "/dev/deci_stdout");
|
|
qfs->Operation.LinkSymbolic("/dev/fd/2", "/dev/deci_stderr");
|
|
|
|
qfs->ForceInsert("/dev", "console", Devices::ConsoleDevice::Create());
|
|
qfs->ForceInsert("/dev", "deci_tty6", Devices::DeciTty6Device::Create());
|
|
qfs->ForceInsert("/dev", "random", Devices::RandomDevice::Create());
|
|
qfs->ForceInsert("/dev", "urandom", Devices::RandomDevice::Create());
|
|
qfs->ForceInsert("/dev", "srandom", Devices::SRandomDevice::Create());
|
|
qfs->ForceInsert("/dev", "zero", Devices::ZeroDevice::Create());
|
|
qfs->ForceInsert("/dev", "null", Devices::NullDevice::Create());
|
|
qfs->ForceInsert("/dev", "rng", Devices::RngDevice::Create());
|
|
|
|
qfs->Operation.Chmod("/dev/deci_stderr", 0666);
|
|
qfs->Operation.Chmod("/dev/deci_stdout", 0666);
|
|
qfs->Operation.Chmod("/dev/random", 0666);
|
|
qfs->Operation.Chmod("/dev/urandom", 0666);
|
|
qfs->Operation.Chmod("/dev/srandom", 0666);
|
|
|
|
if (int fd_dev = qfs->Operation.Open("/dev/stdin", QUASI_O_RDONLY); fd_dev != 0)
|
|
LOG_CRITICAL(Kernel_Fs, "file descriptor of stdin is not 0 (it's {})", fd_dev);
|
|
if (int fd_dev = qfs->Operation.Open("/dev/stdout", QUASI_O_WRONLY); fd_dev != 1)
|
|
LOG_CRITICAL(Kernel_Fs, "file descriptor of stdout is not 1 (it's {})", fd_dev);
|
|
if (int fd_dev = qfs->Operation.Open("/dev/stderr", QUASI_O_WRONLY); fd_dev != 2)
|
|
LOG_CRITICAL(Kernel_Fs, "file descriptor of stderr is not 2 (it's {})", fd_dev);
|
|
|
|
qfs->SyncHost();
|
|
|
|
qfs::printTree(qfs->GetRoot(), "/");
|
|
|
|
return;
|
|
}
|
|
|
|
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";
|
|
}
|
|
|
|
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);
|
|
|
|
this->LoadFilesystem(game_folder);
|
|
|
|
// can't sync here, otherwise all mountpoints would need to be updated one by one
|
|
auto* qfs = Common::Singleton<qfs::QFS>::Instance();
|
|
std::filesystem::path param_sfo_path{};
|
|
{
|
|
qfs::Resolved res{};
|
|
int status = qfs->Resolve("/app0", res);
|
|
// DON'T do this normally. This is init, anything goes
|
|
res.mountpoint->GetHostPath(param_sfo_path, "/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{};
|
|
|
|
// There is no valid dump without this file ~shrug~
|
|
if (!param_sfo_exists)
|
|
UNREACHABLE_MSG("Failed to access param.sfo");
|
|
|
|
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);
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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);
|
|
|
|
const auto& mount_data_dir = Common::FS::GetUserPath(Common::FS::PathType::GameDataDir) / id;
|
|
if (!std::filesystem::exists(mount_data_dir)) {
|
|
std::filesystem::create_directory(mount_data_dir);
|
|
}
|
|
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);
|
|
}
|
|
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);
|
|
|
|
qfs->Operation.MKDir("/data", 0777);
|
|
qfs->Operation.MKDir("/download0", 0777); // not sure about perms here
|
|
qfs->Operation.MKDir("/temp", 0777);
|
|
qfs->Operation.MKDir("/temp0", 0777);
|
|
qfs::partition_ptr partition_data = qfs::Partition::Create(mount_data_dir, 0777, 32768);
|
|
qfs::partition_ptr partition_download = qfs::Partition::Create(mount_download_dir, 0777, 65536);
|
|
qfs::partition_ptr partition_temp = qfs::Partition::Create(mount_temp_dir, 0777, 16384);
|
|
qfs->Mount("/data", partition_data, qfs::MountOptions::MOUNT_RW);
|
|
qfs->Mount("/download0", partition_download, qfs::MountOptions::MOUNT_RW);
|
|
qfs->Mount("/temp", partition_temp, qfs::MountOptions::MOUNT_RW);
|
|
qfs->Mount("/temp0", partition_temp,
|
|
qfs::MountOptions::MOUNT_RW | qfs::MountOptions::MOUNT_BIND);
|
|
qfs->SyncHost("/data");
|
|
qfs->SyncHost("/download0");
|
|
qfs->SyncHost("/temp");
|
|
qfs->SyncHost("/temp0");
|
|
|
|
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 = sdk_version;
|
|
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);
|
|
|
|
// 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 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 readbacks: {}", Config::readbacks());
|
|
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, "Compiled SDK version: {:#x}", sdk_version);
|
|
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");
|
|
}
|
|
}
|
|
|
|
// 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();
|
|
|
|
// Initialize kernel and library facilities.
|
|
Libraries::InitHLELibs(&linker->GetHLESymbols());
|
|
|
|
// Load the module with the linker
|
|
auto guest_eboot_path = "/app0/" + eboot_name.generic_string();
|
|
std::filesystem::path eboot_path{};
|
|
qfs->GetHostPath(eboot_path, guest_eboot_path);
|
|
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);
|
|
}
|
|
|
|
// check if we have system modules to load
|
|
LoadSystemModules(game_info.game_serial);
|
|
|
|
// Load all prx from game's sce_module folder
|
|
mnt->IterateDirectory("/app0/sce_module", [this](const auto& path, const auto is_file) {
|
|
if (is_file) {
|
|
LOG_INFO(Loader, "Loading {}", fmt::UTF(path.u8string()));
|
|
linker->LoadModule(path);
|
|
}
|
|
});
|
|
|
|
#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 (Core::Emulator::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::LoadSystemModules(const std::string& game_serial) {
|
|
constexpr auto ModulesToLoad = std::to_array<SysModules>(
|
|
{{"libSceNgs2.sprx", &Libraries::Ngs2::RegisterLib},
|
|
{"libSceUlt.sprx", nullptr},
|
|
{"libSceJson.sprx", nullptr},
|
|
{"libSceJson2.sprx", nullptr},
|
|
{"libSceLibcInternal.sprx", &Libraries::LibcInternal::RegisterLib},
|
|
{"libSceCesCs.sprx", nullptr},
|
|
{"libSceFont.sprx", &Libraries::Font::RegisterlibSceFont},
|
|
{"libSceFontFt.sprx", &Libraries::FontFt::RegisterlibSceFontFt},
|
|
{"libSceFreeTypeOt.sprx", nullptr}});
|
|
|
|
std::vector<std::filesystem::path> found_modules;
|
|
const auto& sys_module_path = Config::getSysModulesPath();
|
|
for (const auto& entry : std::filesystem::directory_iterator(sys_module_path)) {
|
|
found_modules.push_back(entry.path());
|
|
}
|
|
for (const auto& [module_name, init_func] : ModulesToLoad) {
|
|
const auto it = std::ranges::find_if(
|
|
found_modules, [&](const auto& path) { return path.filename() == module_name; });
|
|
if (it != found_modules.end()) {
|
|
LOG_INFO(Loader, "Loading {}", it->string());
|
|
if (linker->LoadModule(*it) != -1) {
|
|
continue;
|
|
}
|
|
}
|
|
if (init_func) {
|
|
LOG_INFO(Loader, "Can't Load {} switching to HLE", module_name);
|
|
init_func(&linker->GetHLESymbols());
|
|
} else {
|
|
LOG_INFO(Loader, "No HLE available for {} module", module_name);
|
|
}
|
|
}
|
|
if (!game_serial.empty() && std::filesystem::exists(sys_module_path / game_serial)) {
|
|
for (const auto& entry :
|
|
std::filesystem::directory_iterator(sys_module_path / game_serial)) {
|
|
LOG_INFO(Loader, "Loading {} from game serial file {}", entry.path().string(),
|
|
game_serial);
|
|
linker->LoadModule(entry.path());
|
|
}
|
|
}
|
|
}
|
|
|
|
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
|