rpcsx/rpcs3/Emu/System.cpp
Nekotekina 86fc842c89 TSX: new fallback method (time-based)
Basically, using timestamp counter.
Rewritten vm::reservation_op with the same principle.
Rewritten another transaction helper.
Add two new settings for configuring fallbacks.
Two limits are specified in nanoseconds (first and second).
Fix PUTLLC reload logic (prevent reusing garbage).
2020-10-31 15:34:14 +03:00

2054 lines
51 KiB
C++

#include "stdafx.h"
#include "VFS.h"
#include "Utilities/bin_patch.h"
#include "Emu/Memory/vm.h"
#include "Emu/System.h"
#include "Emu/Cell/PPUThread.h"
#include "Emu/Cell/PPUCallback.h"
#include "Emu/Cell/PPUOpcodes.h"
#include "Emu/Cell/PPUDisAsm.h"
#include "Emu/Cell/PPUAnalyser.h"
#include "Emu/Cell/SPUThread.h"
#include "Emu/Cell/RawSPUThread.h"
#include "Emu/Cell/lv2/sys_process.h"
#include "Emu/Cell/lv2/sys_memory.h"
#include "Emu/Cell/lv2/sys_sync.h"
#include "Emu/Cell/lv2/sys_prx.h"
#include "Emu/Cell/lv2/sys_rsx.h"
#include "Emu/Cell/Modules/cellMsgDialog.h"
#include "Emu/title.h"
#include "Emu/IdManager.h"
#include "Emu/RSX/Capture/rsx_replay.h"
#include "Loader/PSF.h"
#include "Loader/ELF.h"
#include "Utilities/StrUtil.h"
#include "Utilities/sysinfo.h"
#include "../Crypto/unself.h"
#include "../Crypto/unpkg.h"
#include "util/yaml.hpp"
#include "util/logs.hpp"
#include "cereal/archives/binary.hpp"
#include <thread>
#include <typeinfo>
#include <queue>
#include <fstream>
#include <memory>
#include <regex>
#include <charconv>
#include "Utilities/JIT.h"
#include "display_sleep_control.h"
#if defined(_WIN32) || defined(HAVE_VULKAN)
#include "Emu/RSX/VK/VulkanAPI.h"
#endif
LOG_CHANNEL(sys_log, "SYS");
stx::manual_fixed_typemap<void> g_fixed_typemap;
bool g_use_rtm = false;
u64 g_rtm_tx_limit1 = 0;
u64 g_rtm_tx_limit2 = 0;
std::string g_cfg_defaults;
atomic_t<u64> g_watchdog_hold_ctr{0};
extern void ppu_load_exec(const ppu_exec_object&);
extern void spu_load_exec(const spu_exec_object&);
extern void ppu_initialize(const ppu_module&);
extern void ppu_unload_prx(const lv2_prx&);
extern std::shared_ptr<lv2_prx> ppu_load_prx(const ppu_prx_object&, const std::string&);
fs::file g_tty;
atomic_t<s64> g_tty_size{0};
std::array<std::deque<std::string>, 16> g_tty_input;
std::mutex g_tty_mutex;
// Progress display server synchronization variables
atomic_t<const char*> g_progr{nullptr};
atomic_t<u32> g_progr_ftotal{0};
atomic_t<u32> g_progr_fdone{0};
atomic_t<u32> g_progr_ptotal{0};
atomic_t<u32> g_progr_pdone{0};
namespace
{
struct progress_dialog_server;
}
template<>
void fmt_class_string<game_boot_result>::format(std::string& out, u64 arg)
{
format_enum(out, arg, [](game_boot_result value)
{
switch (value)
{
case game_boot_result::no_errors: return "No errors";
case game_boot_result::generic_error: return "Generic error";
case game_boot_result::nothing_to_boot: return "Nothing to boot";
case game_boot_result::wrong_disc_location: return "Wrong disc location";
case game_boot_result::invalid_file_or_folder: return "Invalid file or folder";
case game_boot_result::install_failed: return "Game install failed";
case game_boot_result::decryption_error: return "Failed to decrypt content";
case game_boot_result::file_creation_error: return "Could not create important files";
case game_boot_result::firmware_missing: return "Firmware is missing";
}
return unknown;
});
}
void Emulator::Init()
{
jit_runtime::initialize();
if (!g_tty)
{
const auto tty_path = fs::get_cache_dir() + "TTY.log";
g_tty.open(tty_path, fs::rewrite + fs::append);
if (!g_tty)
{
sys_log.fatal("Failed to create TTY log: %s (%s)", tty_path, fs::g_tls_error);
}
}
idm::init();
g_fxo->reset();
g_fxo->init<named_thread<progress_dialog_server>>();
// Reset defaults, cache them
g_cfg.from_default();
// Not all renderers are known at compile time, so set a provided default if possible
if (m_default_renderer == video_renderer::vulkan && !m_default_graphics_adapter.empty())
{
g_cfg.video.renderer.set(m_default_renderer);
g_cfg.video.vk.adapter.from_string(m_default_graphics_adapter);
}
g_cfg_defaults = g_cfg.to_string();
// Reload override configuration set via command line
if (!m_config_override_path.empty())
{
if (const fs::file cfg_file{m_config_override_path, fs::read + fs::create})
{
if (!g_cfg.from_string(cfg_file.to_string()))
{
sys_log.fatal("Failed to apply config override: %s. Proceeding with regular configuration.", m_config_override_path);
m_config_override_path.clear();
}
else
{
sys_log.success("Applied config override: %s", m_config_override_path);
g_cfg.name = m_config_override_path;
}
}
else
{
sys_log.fatal("Failed to access config override: %s (%s). Proceeding with regular configuration.", m_config_override_path, fs::g_tls_error);
m_config_override_path.clear();
}
}
// Reload global configuration
if (m_config_override_path.empty())
{
const auto cfg_path = fs::get_config_dir() + "/config.yml";
if (const fs::file cfg_file{cfg_path, fs::read + fs::create})
{
if (!g_cfg.from_string(cfg_file.to_string()))
{
sys_log.fatal("Failed to apply global config: %s", cfg_path);
}
g_cfg.name = cfg_path;
}
else
{
sys_log.fatal("Failed to access global config: %s (%s)", cfg_path, fs::g_tls_error);
}
}
// Create directories (can be disabled if necessary)
const std::string emu_dir = GetEmuDir();
const std::string dev_hdd0 = GetHddDir();
const std::string dev_hdd1 = fmt::replace_all(g_cfg.vfs.dev_hdd1, "$(EmulatorDir)", emu_dir);
const std::string dev_usb = fmt::replace_all(g_cfg.vfs.dev_usb000, "$(EmulatorDir)", emu_dir);
const std::string dev_flsh = g_cfg.vfs.get_dev_flash();
auto make_path_verbose = [](const std::string& path)
{
if (!fs::create_path(path))
{
sys_log.fatal("Failed to create path: %s (%s)", path, fs::g_tls_error);
}
};
const std::string save_path = dev_hdd0 + "home/" + m_usr + "/savedata/";
const std::string user_path = dev_hdd0 + "home/" + m_usr + "/localusername";
if (g_cfg.vfs.init_dirs)
{
make_path_verbose(dev_hdd0);
make_path_verbose(dev_hdd1);
make_path_verbose(dev_flsh);
make_path_verbose(dev_usb);
make_path_verbose(dev_hdd0 + "game/");
make_path_verbose(dev_hdd0 + "game/TEST12345/");
make_path_verbose(dev_hdd0 + "game/TEST12345/USRDIR/");
make_path_verbose(dev_hdd0 + "game/.locks/");
make_path_verbose(dev_hdd0 + "home/");
make_path_verbose(dev_hdd0 + "home/" + m_usr + "/");
make_path_verbose(dev_hdd0 + "home/" + m_usr + "/exdata/");
make_path_verbose(save_path);
make_path_verbose(dev_hdd0 + "home/" + m_usr + "/trophy/");
if (!fs::write_file(user_path, fs::create + fs::excl + fs::write, "User"s))
{
if (fs::g_tls_error != fs::error::exist)
{
sys_log.fatal("Failed to create file: %s (%s)", user_path, fs::g_tls_error);
}
}
make_path_verbose(dev_hdd0 + "disc/");
make_path_verbose(dev_hdd0 + "savedata/");
make_path_verbose(dev_hdd0 + "savedata/vmc/");
make_path_verbose(dev_hdd1 + "caches/");
}
// Log user
if (m_usr.empty())
{
sys_log.fatal("No user configured");
}
else
{
std::string username;
if (const fs::file file = fs::file(user_path))
{
if (const std::string localusername = file.to_string(); !localusername.empty())
{
username = localusername;
}
else
{
sys_log.warning("Empty username in file: '%s'. Consider setting a username for user '%s' in the user manager.", user_path, m_usr);
}
}
else
{
sys_log.error("Could not read file: '%s'", user_path);
}
if (username.empty())
{
sys_log.notice("Logged in as user '%s'", m_usr);
}
else
{
sys_log.notice("Logged in as user '%s' with the username '%s'", m_usr, username);
}
}
// Fixup savedata
for (const auto& entry : fs::dir(save_path))
{
if (entry.is_directory && entry.name.starts_with(".backup_"))
{
const std::string desired = entry.name.substr(8);
const std::string pending = save_path + ".working_" + desired;
if (fs::is_dir(pending))
{
// Finalize interrupted saving
if (!fs::rename(pending, save_path + desired, false))
{
sys_log.fatal("Failed to fix save data: %s (%s)", pending, fs::g_tls_error);
continue;
}
else
{
sys_log.success("Fixed save data: %s", desired);
}
}
// Remove pending backup data
if (!fs::remove_all(save_path + entry.name))
{
sys_log.fatal("Failed to remove save data backup: %s%s (%s)", save_path, entry.name, fs::g_tls_error);
}
else
{
sys_log.success("Removed save data backup: %s%s", save_path, entry.name);
}
}
}
make_path_verbose(fs::get_cache_dir() + "shaderlog/");
make_path_verbose(fs::get_config_dir() + "captures/");
make_path_verbose(fs::get_cache_dir() + "spu_progs/");
// Initialize patch engine
g_fxo->init<patch_engine>()->append_global_patches();
}
namespace
{
struct progress_dialog_server
{
void operator()()
{
while (thread_ctrl::state() != thread_state::aborting)
{
// Wait for the start condition
while (!g_progr_ftotal && !g_progr_ptotal)
{
if (thread_ctrl::state() == thread_state::aborting)
{
break;
}
std::this_thread::sleep_for(5ms);
}
if (thread_ctrl::state() == thread_state::aborting)
{
break;
}
// Initialize message dialog
std::shared_ptr<MsgDialogBase> dlg = Emu.GetCallbacks().get_msg_dialog();
if (dlg)
{
dlg->type.se_normal = true;
dlg->type.bg_invisible = true;
dlg->type.progress_bar_count = 1;
dlg->on_close = [](s32 status)
{
Emu.CallAfter([]()
{
// Abort everything
Emu.Stop();
});
};
Emu.CallAfter([dlg]()
{
dlg->Create(+g_progr, +g_progr);
});
}
u32 ftotal = 0;
u32 fdone = 0;
u32 ptotal = 0;
u32 pdone = 0;
u32 value = 0;
// Update progress
while (thread_ctrl::state() != thread_state::aborting)
{
if (ftotal != g_progr_ftotal || fdone != g_progr_fdone || ptotal != g_progr_ptotal || pdone != g_progr_pdone)
{
ftotal = g_progr_ftotal;
fdone = g_progr_fdone;
ptotal = g_progr_ptotal;
pdone = g_progr_pdone;
// Compute new progress in percents
const u32 total = ftotal + ptotal;
const u32 done = fdone + pdone;
const u32 new_value = static_cast<u32>(double(done) * 100. / double(total ? total : 1));
// Compute the difference
const u32 delta = new_value > value ? new_value - value : 0;
value += delta;
// Changes detected, send update
Emu.CallAfter([=]()
{
std::string progr = "Progress:";
if (ftotal)
fmt::append(progr, " file %u of %u%s", fdone, ftotal, ptotal ? "," : "");
if (ptotal)
fmt::append(progr, " module %u of %u", pdone, ptotal);
if (dlg)
{
dlg->SetMsg(+g_progr);
dlg->ProgressBarSetMsg(0, progr);
dlg->ProgressBarInc(0, delta);
}
});
}
if (fdone >= ftotal && pdone >= ptotal)
{
// Close dialog
break;
}
std::this_thread::sleep_for(10ms);
}
if (thread_ctrl::state() == thread_state::aborting)
{
break;
}
// Cleanup
g_progr_ftotal -= fdone;
g_progr_fdone -= fdone;
g_progr_ptotal -= pdone;
g_progr_pdone -= pdone;
if (dlg)
{
Emu.CallAfter([=]()
{
dlg->Close(true);
});
}
}
}
static auto constexpr thread_name = "Progress Dialog Server"sv;
};
}
const bool Emulator::SetUsr(const std::string& user)
{
if (user.empty())
{
return false;
}
u32 id = 0;
std::from_chars(&user.front(), &user.back() + 1, id);
if (id == 0)
{
return false;
}
m_usrid = id;
m_usr = user;
return true;
}
const std::string Emulator::GetBackgroundPicturePath() const
{
// Try to find a custom icon first
std::string path = fs::get_config_dir() + "/Icons/game_icons/" + Emu.GetTitleID() + "/PIC1.PNG";
if (fs::is_file(path))
{
return path;
}
path = m_sfo_dir + "/PIC1.PNG";
if (!fs::is_file(path))
{
const std::string disc_dir = vfs::get("/dev_bdvd/PS3_GAME");
if (disc_dir.empty())
{
// Fallback to ICON0.PNG
path = m_sfo_dir + "/ICON0.PNG";
}
else
{
// Fallback to PIC1.PNG in disc dir
path = disc_dir + "/PIC1.PNG";
if (!fs::is_file(path))
{
// Fallback to ICON0.PNG in update dir
path = m_sfo_dir + "/ICON0.PNG";
if (!fs::is_file(path))
{
// Fallback to ICON0.PNG in disc dir
path = disc_dir + "/ICON0.PNG";
}
}
}
}
return path;
}
std::string Emulator::PPUCache() const
{
const auto _main = g_fxo->get<ppu_module>();
if (!_main || _main->cache.empty())
{
ppu_log.warning("PPU Cache location not initialized.");
return {};
}
return _main->cache;
}
bool Emulator::BootRsxCapture(const std::string& path)
{
if (!fs::is_file(path))
return false;
std::fstream f(path, std::ios::in | std::ios::binary);
cereal::BinaryInputArchive archive(f);
std::unique_ptr<rsx::frame_capture_data> frame = std::make_unique<rsx::frame_capture_data>();
archive(*frame);
if (frame->magic != rsx::FRAME_CAPTURE_MAGIC)
{
sys_log.error("Invalid rsx capture file!");
return false;
}
if (frame->version != rsx::FRAME_CAPTURE_VERSION)
{
sys_log.error("Rsx capture file version not supported! Expected %d, found %d", rsx::FRAME_CAPTURE_VERSION, frame->version);
return false;
}
Init();
g_cfg.video.disable_on_disk_shader_cache.set(true);
vm::init();
g_fxo->init();
// PS3 'executable'
m_state = system_state::ready;
GetCallbacks().on_ready();
Emu.GetCallbacks().init_gs_render();
Emu.GetCallbacks().init_pad_handler("");
GetCallbacks().on_run(false);
m_state = system_state::running;
g_fxo->init<named_thread<rsx::rsx_replay_thread>>("RSX Replay"sv, std::move(frame));
return true;
}
void Emulator::LimitCacheSize()
{
const std::string cache_location = Emulator::GetHdd1Dir() + "/caches";
if (!fs::is_dir(cache_location))
{
sys_log.warning("Cache does not exist (%s)", cache_location);
return;
}
const u64 size = fs::get_dir_size(cache_location);
if (size == umax)
{
sys_log.error("Could not calculate cache directory '%s' size (%s)", cache_location, fs::g_tls_error);
return;
}
const u64 max_size = static_cast<u64>(g_cfg.vfs.cache_max_size) * 1024 * 1024;
if (max_size == 0) // Everything must go, so no need to do checks
{
fs::remove_all(cache_location, false);
sys_log.success("Cleared disk cache");
return;
}
if (size <= max_size)
{
sys_log.trace("Cache size below limit: %llu/%llu", size, max_size);
return;
}
sys_log.success("Cleaning disk cache...");
std::vector<fs::dir_entry> file_list{};
fs::dir cache_dir(cache_location);
if (!cache_dir)
{
sys_log.error("Could not open cache directory '%s' (%s)", cache_location, fs::g_tls_error);
return;
}
// retrieve items to delete
for (const auto &item : cache_dir)
{
if (item.name != "." && item.name != "..")
file_list.push_back(item);
}
cache_dir.close();
// sort oldest first
std::sort(file_list.begin(), file_list.end(), [](auto left, auto right)
{
return left.mtime < right.mtime;
});
// keep removing until cache is empty or enough bytes have been cleared
// cache is cleared down to 80% of limit to increase interval between clears
const u64 to_remove = static_cast<u64>(size - max_size * 0.8);
u64 removed = 0;
for (const auto &item : file_list)
{
const std::string &name = cache_location + "/" + item.name;
const bool is_dir = fs::is_dir(name);
const u64 item_size = is_dir ? fs::get_dir_size(name) : item.size;
if (is_dir && item_size == umax)
{
sys_log.error("Failed to calculate '%s' item '%s' size (%s)", cache_location, item.name, fs::g_tls_error);
break;
}
if (is_dir ? !fs::remove_all(name, true) : !fs::remove_file(name))
{
sys_log.error("Could not remove cache directory '%s' item '%s' (%s)", cache_location, item.name, fs::g_tls_error);
break;
}
removed += item_size;
if (removed >= to_remove)
break;
}
sys_log.success("Cleaned disk cache, removed %.2f MB", size / 1024.0 / 1024.0);
}
game_boot_result Emulator::BootGame(const std::string& path, const std::string& title_id, bool direct, bool add_only, bool force_global_config)
{
if (g_cfg.vfs.limit_cache_size)
LimitCacheSize();
static const char* boot_list[] =
{
"/eboot.bin",
"/EBOOT.BIN",
"/USRDIR/EBOOT.BIN",
"/USRDIR/ISO.BIN.EDAT",
"/PS3_GAME/USRDIR/EBOOT.BIN",
};
m_path_old = m_path;
if (direct && fs::exists(path))
{
m_path = path;
return Load(title_id, add_only, force_global_config);
}
game_boot_result result = game_boot_result::nothing_to_boot;
for (std::string elf : boot_list)
{
elf = path + elf;
if (fs::is_file(elf))
{
m_path = elf;
result = Load(title_id, add_only, force_global_config);
break;
}
}
if (add_only)
{
for (auto&& entry : fs::dir{ path })
{
if (entry.name == "." || entry.name == "..")
{
continue;
}
if (entry.is_directory && std::regex_match(entry.name, std::regex("^PS3_GM[[:digit:]]{2}$")))
{
const std::string elf = path + "/" + entry.name + "/USRDIR/EBOOT.BIN";
if (fs::is_file(elf))
{
m_path = elf;
if (const auto err = Load(title_id, add_only, force_global_config); err != game_boot_result::no_errors)
{
result = err;
}
}
}
}
}
return result;
}
bool Emulator::InstallPkg(const std::string& path)
{
sys_log.success("Installing package: %s", path);
atomic_t<double> progress(0.);
int int_progress = 0;
// Run PKG unpacking asynchronously
named_thread worker("PKG Installer", [&]
{
package_reader reader(path);
return reader.extract_data(progress);
});
{
// Wait for the completion
while (std::this_thread::sleep_for(5ms), worker <= thread_state::aborting)
{
// TODO: update unified progress dialog
double pval = progress;
pval < 0 ? pval += 1. : pval;
pval *= 100.;
if (static_cast<int>(pval) > int_progress)
{
int_progress = static_cast<int>(pval);
sys_log.success("... %u%%", int_progress);
}
}
}
return worker();
}
std::string Emulator::GetEmuDir()
{
const std::string& emu_dir_ = g_cfg.vfs.emulator_dir;
return emu_dir_.empty() ? fs::get_config_dir() : emu_dir_;
}
std::string Emulator::GetHddDir()
{
return fmt::replace_all(g_cfg.vfs.dev_hdd0, "$(EmulatorDir)", GetEmuDir());
}
std::string Emulator::GetHdd1Dir()
{
return fmt::replace_all(g_cfg.vfs.dev_hdd1, "$(EmulatorDir)", GetEmuDir());
}
std::string Emulator::GetCacheDir()
{
return fs::get_cache_dir() + "cache/";
}
#ifdef _WIN32
std::string Emulator::GetExeDir()
{
wchar_t buffer[32767];
GetModuleFileNameW(nullptr, buffer, sizeof(buffer)/2);
std::string path_to_exe = wchar_to_utf8(buffer);
size_t last = path_to_exe.find_last_of("\\");
return last == std::string::npos ? std::string("") : path_to_exe.substr(0, last+1);
}
#endif
std::string Emulator::GetSfoDirFromGamePath(const std::string& game_path, const std::string& user, const std::string& title_id)
{
if (fs::is_file(game_path + "/PS3_DISC.SFB"))
{
// This is a disc game.
if (!title_id.empty())
{
for (auto&& entry : fs::dir{game_path})
{
if (entry.name == "." || entry.name == "..")
{
continue;
}
const std::string sfo_path = game_path + "/" + entry.name + "/PARAM.SFO";
if (entry.is_directory && fs::is_file(sfo_path))
{
const auto psf = psf::load_object(fs::file(sfo_path));
const auto serial = psf::get_string(psf, "TITLE_ID");
if (serial == title_id)
{
return game_path + "/" + entry.name;
}
}
}
}
return game_path + "/PS3_GAME";
}
const auto psf = psf::load_object(fs::file(game_path + "/PARAM.SFO"));
const auto category = psf::get_string(psf, "CATEGORY");
const auto content_id = std::string(psf::get_string(psf, "CONTENT_ID"));
if (category == "HG" && !content_id.empty())
{
// This is a trial game. Check if the user has a RAP file to unlock it.
const std::string rap_path = GetHddDir() + "home/" + user + "/exdata/" + content_id + ".rap";
if (fs::is_file(rap_path) && fs::is_file(game_path + "/C00/PARAM.SFO"))
{
// Load full game data.
return game_path + "/C00";
}
}
return game_path;
}
std::string Emulator::GetCustomConfigDir()
{
#ifdef _WIN32
return fs::get_config_dir() + "config/custom_configs/";
#else
return fs::get_config_dir() + "custom_configs/";
#endif
}
std::string Emulator::GetCustomConfigPath(const std::string& title_id, bool get_deprecated_path)
{
std::string path;
if (get_deprecated_path)
path = fs::get_config_dir() + "data/" + title_id + "/config.yml";
else
path = GetCustomConfigDir() + "config_" + title_id + ".yml";
return path;
}
std::string Emulator::GetCustomInputConfigDir(const std::string& title_id)
{
// Notice: the extra folder for each title id may be removed at a later stage
// Warning: make sure to change any function that removes this directory as well
#ifdef _WIN32
return fs::get_config_dir() + "config/custom_input_configs/" + title_id + "/";
#else
return fs::get_config_dir() + "custom_input_configs/" + title_id + "/";
#endif
}
std::string Emulator::GetCustomInputConfigPath(const std::string& title_id)
{
return GetCustomInputConfigDir(title_id) + "/config_input_" + title_id + ".yml";
}
void Emulator::SetForceBoot(bool force_boot)
{
m_force_boot = force_boot;
}
game_boot_result Emulator::Load(const std::string& title_id, bool add_only, bool force_global_config, bool is_disc_patch)
{
m_force_global_config = force_global_config;
if (!IsStopped())
{
Stop();
}
if (!title_id.empty())
{
m_title_id = title_id;
}
{
Init();
// Load game list (maps ABCD12345 IDs to /dev_bdvd/ locations)
YAML::Node games;
if (fs::file f{fs::get_config_dir() + "/games.yml", fs::read + fs::create})
{
auto [result, error] = yaml_load(f.to_string());
if (!error.empty())
{
sys_log.error("Failed to load games.yml: %s", error);
}
games = result;
}
if (!games.IsMap())
{
games.reset();
}
sys_log.notice("Path: %s", m_path);
const std::string elf_dir = fs::get_parent_dir(m_path);
// Load PARAM.SFO (TODO)
psf::registry _psf;
if (fs::file sfov{elf_dir + "/sce_sys/param.sfo"})
{
m_sfo_dir = elf_dir;
_psf = psf::load_object(sfov);
}
else
{
if (fs::is_dir(m_path))
{
// Special case (directory scan)
m_sfo_dir = GetSfoDirFromGamePath(m_path, GetUsr(), m_title_id);
}
else if (!disc.empty())
{
// Check previously used category before it's overwritten
if (m_cat == "DG")
{
m_sfo_dir = disc + "/" + m_game_dir;
}
else if (m_cat == "GD")
{
m_sfo_dir = GetHddDir() + "game/" + m_title_id;
}
else
{
m_sfo_dir = GetSfoDirFromGamePath(disc, GetUsr(), m_title_id);
}
}
else
{
m_sfo_dir = GetSfoDirFromGamePath(elf_dir + "/../", GetUsr(), m_title_id);
}
_psf = psf::load_object(fs::file(m_sfo_dir + "/PARAM.SFO"));
}
m_title = std::string(psf::get_string(_psf, "TITLE", std::string_view(m_path).substr(m_path.find_last_of('/') + 1)));
m_title_id = std::string(psf::get_string(_psf, "TITLE_ID"));
m_cat = std::string(psf::get_string(_psf, "CATEGORY"));
const auto version_app = psf::get_string(_psf, "APP_VER", "Unknown");
const auto version_disc = psf::get_string(_psf, "VERSION", "Unknown");
m_app_version = version_app == "Unknown" ? version_disc : version_app;
if (!_psf.empty() && m_cat.empty())
{
sys_log.fatal("Corrupted PARAM.SFO found! Try reinstalling the game.");
return game_boot_result::invalid_file_or_folder;
}
sys_log.notice("Title: %s", GetTitle());
sys_log.notice("Serial: %s", GetTitleID());
sys_log.notice("Category: %s", GetCat());
sys_log.notice("Version: APP_VER=%s VERSION=%s", version_app, version_disc);
if (!add_only && !force_global_config && m_config_override_path.empty())
{
const std::string config_path_new = GetCustomConfigPath(m_title_id);
const std::string config_path_old = GetCustomConfigPath(m_title_id, true);
// Load custom config-1
if (fs::file cfg_file{ config_path_old })
{
sys_log.notice("Applying custom config: %s", config_path_old);
if (!g_cfg.from_string(cfg_file.to_string()))
{
sys_log.fatal("Failed to apply custom config: %s", config_path_old);
}
}
// Load custom config-2
if (fs::file cfg_file{ config_path_new })
{
sys_log.notice("Applying custom config: %s", config_path_new);
if (g_cfg.from_string(cfg_file.to_string()))
{
g_cfg.name = config_path_new;
}
else
{
sys_log.fatal("Failed to apply custom config: %s", config_path_new);
}
}
// Load custom config-3
if (fs::file cfg_file{ m_path + ".yml" })
{
sys_log.notice("Applying custom config: %s.yml", m_path);
if (!g_cfg.from_string(cfg_file.to_string()))
{
sys_log.fatal("Failed to apply custom config: %s.yml", m_path);
}
}
}
// Set RTM usage
g_use_rtm = utils::has_rtm() && ((utils::has_mpx() && g_cfg.core.enable_TSX == tsx_usage::enabled) || g_cfg.core.enable_TSX == tsx_usage::forced);
// Log some extra info in case of boot
if (!add_only)
{
#if defined(_WIN32) || defined(HAVE_VULKAN)
if (g_cfg.video.renderer == video_renderer::vulkan)
{
sys_log.notice("Vulkan SDK Revision: %d", VK_HEADER_VERSION);
}
#endif
sys_log.notice("Used configuration:\n%s\n", g_cfg.to_string());
if (g_use_rtm && !utils::has_mpx())
{
sys_log.warning("TSX forced by User");
}
}
if (g_use_rtm)
{
// Update supplementary settings
const f64 _1ns = utils::get_tsc_freq() / 1000'000'000.;
g_rtm_tx_limit1 = g_cfg.core.tx_limit1_ns * _1ns;
g_rtm_tx_limit2 = g_cfg.core.tx_limit2_ns * _1ns;
}
// Load patches from different locations
g_fxo->get<patch_engine>()->append_title_patches(m_title_id);
// Mount all devices
const std::string emu_dir = GetEmuDir();
const std::string home_dir = g_cfg.vfs.app_home;
std::string bdvd_dir = g_cfg.vfs.dev_bdvd;
// Mount default relative path to non-existent directory
vfs::mount("/dev_hdd0", fmt::replace_all(g_cfg.vfs.dev_hdd0, "$(EmulatorDir)", emu_dir));
vfs::mount("/dev_flash", g_cfg.vfs.get_dev_flash());
vfs::mount("/dev_usb", fmt::replace_all(g_cfg.vfs.dev_usb000, "$(EmulatorDir)", emu_dir));
vfs::mount("/dev_usb000", fmt::replace_all(g_cfg.vfs.dev_usb000, "$(EmulatorDir)", emu_dir));
vfs::mount("/app_home", home_dir.empty() ? elf_dir + '/' : fmt::replace_all(home_dir, "$(EmulatorDir)", emu_dir));
if (!hdd1.empty())
{
vfs::mount("/dev_hdd1", hdd1);
sys_log.notice("Hdd1: %s", vfs::get("/dev_hdd1"));
}
if (!fs::is_file(g_cfg.vfs.get_dev_flash() + "sys/external/liblv2.sprx"))
{
return game_boot_result::firmware_missing;
}
// Special boot mode (directory scan)
if (!add_only && fs::is_dir(m_path))
{
m_state = system_state::ready;
GetCallbacks().on_ready();
vm::init();
g_fxo->init();
Run(false);
m_force_boot = false;
// Force LLVM recompiler
g_cfg.core.ppu_decoder.from_default();
// Force lib loading mode
g_cfg.core.lib_loading.from_string("Manually load selected libraries");
verify(HERE), g_cfg.core.lib_loading == lib_loading_type::manual;
g_cfg.core.load_libraries.from_default();
// Fake arg (workaround)
argv.resize(1);
argv[0] = "/dev_bdvd/PS3_GAME/USRDIR/EBOOT.BIN";
m_dir = "/dev_bdvd/PS3_GAME";
g_fxo->init<named_thread>("SPRX Loader"sv, [this]
{
std::vector<std::string> dir_queue;
dir_queue.emplace_back(m_path + '/');
// Find game update to use EBOOT.BIN from it, also add its directory to scan
if (m_cat == "DG")
{
const std::string hdd0_path = vfs::get("/dev_hdd0/game/") + m_title_id;
if (fs::is_file(hdd0_path + "/USRDIR/EBOOT.BIN"))
{
m_path = hdd0_path;
dir_queue.emplace_back(m_path + '/');
}
}
std::vector<std::pair<std::string, u64>> file_queue;
file_queue.reserve(2000);
// Initialize progress dialog
g_progr = "Scanning directories for SPRX libraries...";
// Find all .sprx files recursively (TODO: process .mself files)
for (std::size_t i = 0; i < dir_queue.size(); i++)
{
if (Emu.IsStopped())
{
break;
}
sys_log.notice("Scanning directory: %s", dir_queue[i]);
for (auto&& entry : fs::dir(dir_queue[i]))
{
if (Emu.IsStopped())
{
break;
}
if (entry.is_directory)
{
if (entry.name != "." && entry.name != "..")
{
dir_queue.emplace_back(dir_queue[i] + entry.name + '/');
}
continue;
}
// Check .sprx filename
if (fmt::to_upper(entry.name).ends_with(".SPRX"))
{
if (entry.name == "libfs_155.sprx")
{
continue;
}
// Get full path
file_queue.emplace_back(dir_queue[i] + entry.name, 0);
g_progr_ftotal++;
}
}
}
g_progr = "Compiling PPU modules";
if (std::string path = m_path + "/USRDIR/EBOOT.BIN"; fs::is_file(path))
{
// Compile EBOOT.BIN first
sys_log.notice("Trying to load EBOOT.BIN: %s", path);
fs::file src{path};
src = decrypt_self(std::move(src));
const ppu_exec_object obj = src;
if (obj == elf_error::ok)
{
const auto _main = g_fxo->get<ppu_module>();
ppu_load_exec(obj);
_main->path = path;
ConfigurePPUCache();
ppu_initialize(*_main);
}
else
{
sys_log.error("Failed to load EBOOT.BIN '%s' (%s)", path, obj.get_error());
}
}
else
{
// Workaround for analyser glitches
verify(HERE), vm::falloc(0x10000, 0xf0000, vm::main);
}
atomic_t<std::size_t> fnext = 0;
shared_mutex sprx_mtx;
named_thread_group workers("SPRX Worker ", GetMaxThreads(), [&]
{
for (std::size_t func_i = fnext++; func_i < file_queue.size(); func_i = fnext++)
{
const auto& path = std::as_const(file_queue)[func_i].first;
sys_log.notice("Trying to load SPRX: %s", path);
// Load MSELF or SPRX
fs::file src{path};
if (file_queue[func_i].second == 0)
{
// Some files may fail to decrypt due to the lack of klic
src = decrypt_self(std::move(src));
}
const ppu_prx_object obj = src;
if (obj == elf_error::ok)
{
std::unique_lock lock(sprx_mtx);
if (auto prx = ppu_load_prx(obj, path))
{
lock.unlock();
ppu_initialize(*prx);
lock.lock();
ppu_unload_prx(*prx);
g_progr_fdone++;
continue;
}
}
sys_log.error("Failed to load SPRX '%s' (%s)", path, obj.get_error());
g_progr_fdone++;
continue;
}
});
// Join every thread
workers.join();
// Exit "process"
Emu.CallAfter([]
{
Emu.Stop();
});
});
return game_boot_result::no_errors;
}
// Detect boot location
const std::string hdd0_game = vfs::get("/dev_hdd0/game/");
const std::string hdd0_disc = vfs::get("/dev_hdd0/disc/");
const std::size_t game_dir_size = 8; // size of PS3_GAME and PS3_GMXX
const std::size_t bdvd_pos = m_cat == "DG" && bdvd_dir.empty() && disc.empty() ? elf_dir.rfind("/USRDIR") - game_dir_size : 0;
const bool from_hdd0_game = m_path.find(hdd0_game) != umax;
if (bdvd_pos && from_hdd0_game)
{
// Booting disc game from wrong location
sys_log.error("Disc game %s found at invalid location /dev_hdd0/game/", m_title_id);
// Move and retry from correct location
if (fs::rename(elf_dir + "/../../", hdd0_disc + elf_dir.substr(hdd0_game.size()) + "/../../", false))
{
sys_log.success("Disc game %s moved to special location /dev_hdd0/disc/", m_title_id);
return m_path = hdd0_disc + m_path.substr(hdd0_game.size()), Load(m_title_id, add_only, force_global_config);
}
else
{
sys_log.error("Failed to move disc game %s to /dev_hdd0/disc/ (%s)", m_title_id, fs::g_tls_error);
return game_boot_result::wrong_disc_location;
}
}
// Booting disc game
if (bdvd_pos)
{
// Mount /dev_bdvd/ if necessary
bdvd_dir = elf_dir.substr(0, bdvd_pos);
m_game_dir = elf_dir.substr(bdvd_pos, game_dir_size);
}
else if (!is_disc_patch)
{
// Reset original disc game dir if this is neither disc nor disc patch
m_game_dir = "PS3_GAME";
}
// Booting patch data
if (m_cat == "GD" && bdvd_dir.empty() && disc.empty())
{
// Load /dev_bdvd/ from game list if available
if (auto node = games[m_title_id])
{
bdvd_dir = node.Scalar();
}
else
{
sys_log.fatal("Disc directory not found. Try to run the game from the actual game disc directory.");
return game_boot_result::invalid_file_or_folder;
}
}
// Check /dev_bdvd/
if (disc.empty() && !bdvd_dir.empty() && fs::is_dir(bdvd_dir))
{
fs::file sfb_file;
vfs::mount("/dev_bdvd", bdvd_dir);
sys_log.notice("Disc: %s", vfs::get("/dev_bdvd"));
vfs::mount("/dev_bdvd/PS3_GAME", bdvd_dir + m_game_dir + "/");
sys_log.notice("Game: %s", vfs::get("/dev_bdvd/PS3_GAME"));
if (!sfb_file.open(vfs::get("/dev_bdvd/PS3_DISC.SFB")) || sfb_file.size() < 4 || sfb_file.read<u32>() != ".SFB"_u32)
{
sys_log.error("Invalid disc directory for the disc game %s", m_title_id);
return game_boot_result::invalid_file_or_folder;
}
const auto game_psf = psf::load_object(fs::file{vfs::get("/dev_bdvd/PS3_GAME/PARAM.SFO")});
const auto bdvd_title_id = psf::get_string(game_psf, "TITLE_ID");
if (bdvd_title_id != m_title_id)
{
sys_log.error("Unexpected disc directory for the disc game %s (found %s)", m_title_id, bdvd_title_id);
return game_boot_result::invalid_file_or_folder;
}
// Store /dev_bdvd/ location
games[m_title_id] = bdvd_dir;
YAML::Emitter out;
out << games;
fs::file(fs::get_config_dir() + "/games.yml.tmp", fs::rewrite).write(out.c_str(), out.size());
fs::rename(fs::get_config_dir() + "/games.yml.tmp", fs::get_config_dir() + "/games.yml", true);
}
else if (m_cat == "1P" && from_hdd0_game)
{
// PS1 Classic located in dev_hdd0/game
sys_log.notice("PS1 Game: %s, %s", m_title_id, m_title);
const std::string game_path = "/dev_hdd0/game/" + m_path.substr(hdd0_game.size(), 9);
sys_log.notice("Forcing manual lib loading mode");
g_cfg.core.lib_loading.from_string(fmt::format("%s", lib_loading_type::manual));
g_cfg.core.load_libraries.from_list({});
argv.resize(9);
argv[0] = "/dev_flash/ps1emu/ps1_newemu.self";
argv[1] = m_title_id + "_mc1.VM1"; // virtual mc 1 /dev_hdd0/savedata/vmc/%argv[1]%
argv[2] = m_title_id + "_mc2.VM1"; // virtual mc 2 /dev_hdd0/savedata/vmc/%argv[2]%
argv[3] = "0082"; // region target
argv[4] = "1600"; // ??? arg4 600 / 1200 / 1600, resolution scale? (purely a guess, the numbers seem to match closely to resolutions tho)
argv[5] = game_path; // ps1 game folder path (not the game serial)
argv[6] = "1"; // ??? arg6 1 ?
argv[7] = "2"; // ??? arg7 2 -- full screen on/off 2/1 ?
argv[8] = "1"; // ??? arg8 2 -- smoothing on/off = 1/0 ?
//TODO, this seems like it would normally be done by sysutil etc
//Basically make 2 128KB memory cards 0 filled and let the games handle formatting.
fs::file card_1_file(vfs::get("/dev_hdd0/savedata/vmc/" + argv[1]), fs::write + fs::create);
card_1_file.trunc(128 * 1024);
fs::file card_2_file(vfs::get("/dev_hdd0/savedata/vmc/" + argv[2]), fs::write + fs::create);
card_2_file.trunc(128 * 1024);
}
else if (m_cat == "PE" && from_hdd0_game)
{
// PSP Remaster located in dev_hdd0/game
sys_log.notice("PSP Remaster Game: %s, %s", m_title_id, m_title);
const std::string game_path = "/dev_hdd0/game/" + m_path.substr(hdd0_game.size(), 9);
argv.resize(2);
argv[0] = "/dev_flash/pspemu/psp_emulator.self";
argv[1] = game_path;
}
else if (m_cat != "DG" && m_cat != "GD")
{
// Don't need /dev_bdvd
}
else if (m_cat == "DG" && from_hdd0_game)
{
// Disc game located in dev_hdd0/game
vfs::mount("/dev_bdvd/PS3_GAME", hdd0_game + m_path.substr(hdd0_game.size(), 10));
sys_log.notice("Game: %s", vfs::get("/dev_bdvd/PS3_GAME"));
}
else if (disc.empty())
{
sys_log.error("Failed to mount disc directory for the disc game %s", m_title_id);
return game_boot_result::invalid_file_or_folder;
}
else
{
// Disc game
bdvd_dir = disc;
vfs::mount("/dev_bdvd", bdvd_dir);
sys_log.notice("Disk: %s", vfs::get("/dev_bdvd"));
}
if (add_only)
{
sys_log.notice("Finished to add data to games.yml by boot for: %s", m_path);
m_path = m_path_old; // Reset m_path to fix boot from gui
return game_boot_result::no_errors;
}
// Install PKGDIR, INSDIR, PS3_EXTRA
if (!bdvd_dir.empty())
{
const std::string ins_dir = vfs::get("/dev_bdvd/PS3_GAME/INSDIR/");
const std::string pkg_dir = vfs::get("/dev_bdvd/PS3_GAME/PKGDIR/");
const std::string extra_dir = vfs::get("/dev_bdvd/PS3_GAME/PS3_EXTRA/");
fs::file lock_file;
if (fs::is_dir(ins_dir) || fs::is_dir(pkg_dir) || fs::is_dir(extra_dir))
{
// Create lock file to prevent double installation
lock_file.open(hdd0_game + ".locks/" + m_title_id, fs::read + fs::create + fs::excl);
}
if (lock_file && fs::is_dir(ins_dir))
{
sys_log.notice("Found INSDIR: %s", ins_dir);
for (auto&& entry : fs::dir{ins_dir})
{
const std::string pkg = ins_dir + entry.name;
if (!entry.is_directory && entry.name.ends_with(".PKG") && !InstallPkg(pkg))
{
sys_log.error("Failed to install %s", pkg);
return game_boot_result::install_failed;
}
}
}
if (lock_file && fs::is_dir(pkg_dir))
{
sys_log.notice("Found PKGDIR: %s", pkg_dir);
for (auto&& entry : fs::dir{pkg_dir})
{
if (entry.is_directory && entry.name.starts_with("PKG"))
{
const std::string pkg_file = pkg_dir + entry.name + "/INSTALL.PKG";
if (fs::is_file(pkg_file) && !InstallPkg(pkg_file))
{
sys_log.error("Failed to install %s", pkg_file);
return game_boot_result::install_failed;
}
}
}
}
if (lock_file && fs::is_dir(extra_dir))
{
sys_log.notice("Found PS3_EXTRA: %s", extra_dir);
for (auto&& entry : fs::dir{extra_dir})
{
if (entry.is_directory && entry.name[0] == 'D')
{
const std::string pkg_file = extra_dir + entry.name + "/DATA000.PKG";
if (fs::is_file(pkg_file) && !InstallPkg(pkg_file))
{
sys_log.error("Failed to install %s", pkg_file);
return game_boot_result::install_failed;
}
}
}
}
}
// Check game updates
const std::string hdd0_boot = hdd0_game + m_title_id + "/USRDIR/EBOOT.BIN";
if (disc.empty() && m_cat == "DG" && fs::is_file(hdd0_boot))
{
// Booting game update
sys_log.success("Updates found at /dev_hdd0/game/%s/!", m_title_id);
return m_path = hdd0_boot, Load(m_title_id, false, force_global_config, true);
}
// Set title to actual disc title if necessary
const std::string disc_sfo_dir = vfs::get("/dev_bdvd/PS3_GAME/PARAM.SFO");
if (!disc_sfo_dir.empty() && fs::is_file(disc_sfo_dir))
{
const auto psf_obj = psf::load_object(fs::file{ disc_sfo_dir });
const auto bdvd_title = psf::get_string(psf_obj, "TITLE");
if (!bdvd_title.empty() && bdvd_title != m_title)
{
sys_log.notice("Title was set from %s to %s", m_title, bdvd_title);
m_title = bdvd_title;
}
}
for (auto& c : m_title)
{
// Replace newlines with spaces
if (c == '\n')
c = ' ';
}
// Mount /host_root/ if necessary (special value)
if (g_cfg.vfs.host_root)
{
vfs::mount("/host_root", "/");
}
// Open SELF or ELF
std::string elf_path = m_path;
if (m_cat == "1P" || m_cat == "PE")
{
// Use emulator path
elf_path = vfs::get(argv[0]);
}
fs::file elf_file(elf_path);
if (!elf_file)
{
sys_log.error("Failed to open executable: %s", elf_path);
return game_boot_result::invalid_file_or_folder;
}
// Check SELF header
if (elf_file.size() >= 4 && elf_file.read<u32>() == "SCE\0"_u32)
{
const std::string decrypted_path = "boot.elf";
fs::stat_t encrypted_stat = elf_file.stat();
fs::stat_t decrypted_stat;
// Check modification time and try to load decrypted ELF
if (false && fs::stat(decrypted_path, decrypted_stat) && decrypted_stat.mtime == encrypted_stat.mtime)
{
elf_file.open(decrypted_path);
}
// Decrypt SELF
else if ((elf_file = decrypt_self(std::move(elf_file), klic.empty() ? nullptr : klic.data(), &g_ps3_process_info.self_info)))
{
if (true)
{
}
else if (fs::file elf_out{decrypted_path, fs::rewrite})
{
elf_out.write(elf_file.to_vector<u8>());
elf_out.close();
fs::utime(decrypted_path, encrypted_stat.atime, encrypted_stat.mtime);
}
else
{
sys_log.error("Failed to create boot.elf");
}
}
}
else
{
g_ps3_process_info.self_info.valid = false;
}
if (!elf_file)
{
sys_log.error("Failed to decrypt SELF: %s", elf_path);
return game_boot_result::decryption_error;
}
ppu_exec_object ppu_exec;
ppu_prx_object ppu_prx;
spu_exec_object spu_exec;
if (ppu_exec.open(elf_file) == elf_error::ok)
{
// PS3 executable
m_state = system_state::ready;
GetCallbacks().on_ready();
vm::init();
if (argv.empty())
{
argv.resize(1);
}
if (argv[0].empty())
{
if (from_hdd0_game && m_cat == "DG")
{
argv[0] = "/dev_bdvd/PS3_GAME/" + m_path.substr(hdd0_game.size() + 10);
m_dir = "/dev_hdd0/game/" + m_path.substr(hdd0_game.size(), 10);
sys_log.notice("Disc path: %s", m_dir);
}
else if (from_hdd0_game)
{
argv[0] = "/dev_hdd0/game/" + m_path.substr(hdd0_game.size());
m_dir = "/dev_hdd0/game/" + m_path.substr(hdd0_game.size(), 10);
sys_log.notice("Boot path: %s", m_dir);
}
else if (!bdvd_dir.empty() && fs::is_dir(bdvd_dir))
{
// Disc games are on /dev_bdvd/
const std::size_t pos = m_path.rfind(m_game_dir);
argv[0] = "/dev_bdvd/PS3_GAME/" + m_path.substr(pos + game_dir_size + 1);
m_dir = "/dev_bdvd/PS3_GAME/";
}
else
{
// For homebrew
argv[0] = "/host_root/" + m_path;
m_dir = "/host_root/" + elf_dir + '/';
}
sys_log.notice("Elf path: %s", argv[0]);
}
const auto _main = g_fxo->init<ppu_module>();
ppu_load_exec(ppu_exec);
ConfigurePPUCache();
g_fxo->init();
Emu.GetCallbacks().init_gs_render();
Emu.GetCallbacks().init_pad_handler(m_title_id);
Emu.GetCallbacks().init_kb_handler();
Emu.GetCallbacks().init_mouse_handler();
}
else if (ppu_prx.open(elf_file) == elf_error::ok)
{
// PPU PRX (experimental)
m_state = system_state::ready;
GetCallbacks().on_ready();
vm::init();
g_fxo->init();
ppu_load_prx(ppu_prx, m_path);
}
else if (spu_exec.open(elf_file) == elf_error::ok)
{
// SPU executable (experimental)
m_state = system_state::ready;
GetCallbacks().on_ready();
vm::init();
g_fxo->init();
spu_load_exec(spu_exec);
}
else
{
sys_log.error("Invalid or unsupported file format: %s", elf_path);
sys_log.warning("** ppu_exec -> %s", ppu_exec.get_error());
sys_log.warning("** ppu_prx -> %s", ppu_prx.get_error());
sys_log.warning("** spu_exec -> %s", spu_exec.get_error());
return game_boot_result::invalid_file_or_folder;
}
if ((m_force_boot || g_cfg.misc.autostart) && IsReady())
{
if (ppu_exec == elf_error::ok)
{
Run(true);
}
m_force_boot = false;
}
else if (IsPaused())
{
m_state = system_state::ready;
GetCallbacks().on_ready();
}
return game_boot_result::no_errors;
}
}
void Emulator::Run(bool start_playtime)
{
if (!IsReady())
{
// Reload with prior configuration.
Load(m_title_id, false, m_force_global_config);
if (!IsReady())
{
return;
}
}
if (IsRunning())
{
Stop();
}
if (IsPaused())
{
Resume();
return;
}
GetCallbacks().on_run(start_playtime);
m_pause_start_time = 0;
m_pause_amend_time = 0;
m_state = system_state::running;
ConfigureLogs();
// Run main thread
idm::check<named_thread<ppu_thread>>(ppu_thread::id_base, [](cpu_thread& cpu)
{
cpu.state -= cpu_flag::stop;
cpu.notify();
});
if (auto thr = g_fxo->get<named_thread<rsx::rsx_replay_thread>>())
{
thr->state -= cpu_flag::stop;
thread_ctrl::notify(*thr);
}
if (g_cfg.misc.prevent_display_sleep)
{
disable_display_sleep();
}
}
bool Emulator::Pause()
{
const u64 start = get_system_time();
// Try to pause
if (!m_state.compare_and_swap_test(system_state::running, system_state::paused))
{
return m_state.compare_and_swap_test(system_state::ready, system_state::paused);
}
// Signal profilers to print results (if enabled)
cpu_thread::flush_profilers();
GetCallbacks().on_pause();
// Update pause start time
if (m_pause_start_time.exchange(start))
{
sys_log.error("Emulator::Pause() error: concurrent access");
}
auto on_select = [](u32, cpu_thread& cpu)
{
cpu.state += cpu_flag::dbg_global_pause;
};
idm::select<named_thread<ppu_thread>>(on_select);
idm::select<named_thread<spu_thread>>(on_select);
// Always Enable display sleep, not only if it was prevented.
enable_display_sleep();
return true;
}
void Emulator::Resume()
{
// Get pause start time
const u64 time = m_pause_start_time.exchange(0);
// Try to increment summary pause time
if (time)
{
m_pause_amend_time += get_system_time() - time;
}
// Print and reset debug data collected
if (m_state == system_state::paused && g_cfg.core.ppu_debug)
{
PPUDisAsm dis_asm(CPUDisAsm_InterpreterMode);
dis_asm.offset = vm::g_base_addr;
std::string dump;
for (u32 i = 0x10000; i < 0x20000000;)
{
if (vm::check_addr(i))
{
if (auto& data = *reinterpret_cast<be_t<u32>*>(vm::g_stat_addr + i))
{
dis_asm.dump_pc = i;
dis_asm.disasm(i);
fmt::append(dump, "\n\t'%08X' %s", data, dis_asm.last_opcode);
data = 0;
}
i += sizeof(u32);
}
else
{
i += 4096;
}
}
ppu_log.notice("[RESUME] Dumping instruction stats:%s", dump);
}
// Try to resume
if (!m_state.compare_and_swap_test(system_state::paused, system_state::running))
{
return;
}
if (!time)
{
sys_log.error("Emulator::Resume() error: concurrent access");
}
auto on_select = [](u32, cpu_thread& cpu)
{
cpu.state -= cpu_flag::dbg_global_pause;
cpu.notify();
};
idm::select<named_thread<ppu_thread>>(on_select);
idm::select<named_thread<spu_thread>>(on_select);
GetCallbacks().on_resume();
if (g_cfg.misc.prevent_display_sleep)
{
disable_display_sleep();
}
}
void Emulator::Stop(bool restart)
{
if (m_state.exchange(system_state::stopped) == system_state::stopped)
{
if (restart)
{
// Reload with prior configs.
if (const auto error = Load(m_title_id, false, m_force_global_config); error != game_boot_result::no_errors)
sys_log.error("Restart failed: %s", error);
return;
}
m_force_boot = false;
return;
}
named_thread stop_watchdog("Stop Watchdog", [&]()
{
for (uint i = 0; thread_ctrl::state() != thread_state::aborting;)
{
// We don't need accurate timekeeping, using clocks may interfere with debugging
if (i >= 1000)
{
// Total amount of waiting: about 5s
report_fatal_error("Stopping emulator took too long."
"\nSome thread has probably deadlocked. Aborting.");
}
thread_ctrl::wait_for(5'000);
if (!g_watchdog_hold_ctr)
{
// Don't count if there are still uninterruptable threads like PPU LLVM workers
i++;
}
}
});
sys_log.notice("Stopping emulator...");
GetCallbacks().on_stop();
cpu_thread::stop_all();
g_fxo->reset();
sys_log.notice("All threads have been stopped.");
lv2_obj::cleanup();
idm::clear();
sys_log.notice("Objects cleared...");
vm::close();
jit_runtime::finalize();
if (restart)
{
// Reload with prior configs.
if (const auto error = Load(m_title_id, false, m_force_global_config); error != game_boot_result::no_errors)
sys_log.error("Restart failed: %s", error);
return;
}
// Boot arg cleanup (preserved in the case restarting)
argv.clear();
envp.clear();
data.clear();
disc.clear();
klic.clear();
hdd1.clear();
// Always Enable display sleep, not only if it was prevented.
enable_display_sleep();
if (Quit(g_cfg.misc.autoexit.get()))
{
return;
}
m_force_boot = false;
Init();
}
bool Emulator::Quit(bool force_quit)
{
m_force_boot = false;
Emu.Stop();
return GetCallbacks().exit(force_quit);
}
std::string Emulator::GetFormattedTitle(double fps) const
{
rpcs3::title_format_data title_data;
title_data.format = g_cfg.misc.title_format.to_string();
title_data.title = GetTitle();
title_data.title_id = GetTitleID();
title_data.renderer = g_cfg.video.renderer.to_string();
title_data.vulkan_adapter = g_cfg.video.vk.adapter.to_string();
title_data.fps = fps;
return rpcs3::get_formatted_title(title_data);
}
u32 Emulator::GetMaxThreads() const
{
const u32 max_threads = static_cast<u32>(g_cfg.core.llvm_threads);
const u32 thread_count = max_threads > 0 ? std::min(max_threads, std::thread::hardware_concurrency()) : std::thread::hardware_concurrency();
return thread_count;
}
s32 error_code::error_report(const fmt_type_info* sup, u64 arg, const fmt_type_info* sup2, u64 arg2)
{
static thread_local std::unordered_map<std::string, std::size_t> g_tls_error_stats;
static thread_local std::string g_tls_error_str;
if (!sup)
{
if (!sup2)
{
// Report and clean error state
for (auto&& pair : g_tls_error_stats)
{
if (pair.second > 3)
{
sys_log.error("Stat: %s [x%u]", pair.first, pair.second);
}
}
g_tls_error_stats.clear();
return 0;
}
}
logs::channel* channel = &sys_log;
const char* func = "Unknown function";
if (auto thread = get_current_cpu_thread())
{
if (thread->id_type() == 1)
{
auto& ppu = static_cast<ppu_thread&>(*thread);
if (ppu.current_function)
{
func = ppu.current_function;
}
}
}
// Format log message (use preallocated buffer)
g_tls_error_str.clear();
fmt::append(g_tls_error_str, "'%s' failed with 0x%08x%s%s%s%s", func, arg, sup ? " : " : "", std::make_pair(sup, arg), sup2 ? ", " : "", std::make_pair(sup2, arg2));
// Update stats and check log threshold
const auto stat = ++g_tls_error_stats[g_tls_error_str];
if (stat <= 3)
{
channel->error("%s [%u]", g_tls_error_str, stat);
}
return static_cast<s32>(arg);
}
void Emulator::ConfigureLogs()
{
static bool was_silenced = false;
const bool silenced = g_cfg.misc.silence_all_logs.get();
if (silenced)
{
if (!was_silenced)
{
sys_log.notice("Disabling logging...");
}
logs::silence();
}
else
{
logs::reset();
logs::set_channel_levels(g_cfg.log.get_map());
if (was_silenced)
{
sys_log.notice("Logging enabled");
}
}
was_silenced = silenced;
}
void Emulator::ConfigurePPUCache()
{
const auto _main = g_fxo->get<ppu_module>();
_main->cache = GetCacheDir();
if (!m_title_id.empty() && m_cat != "1P")
{
_main->cache += Emu.GetTitleID();
_main->cache += '/';
}
fmt::append(_main->cache, "ppu-%s-%s/", fmt::base57(_main->sha1), _main->path.substr(_main->path.find_last_of('/') + 1));
if (!fs::create_path(_main->cache))
{
sys_log.error("Failed to create cache directory: %s (%s)", _main->cache, fs::g_tls_error);
}
else
{
sys_log.notice("Cache: %s", _main->cache);
}
}
template <>
void stx::manual_fixed_typemap<void>::init_reporter(const char* name, unsigned long long created) const noexcept
{
sys_log.notice("[ord:%u] Object '%s' was created", created, name);
}
template <>
void stx::manual_fixed_typemap<void>::destroy_reporter(const char* name, unsigned long long created) const noexcept
{
sys_log.notice("[ord:%u] Object '%s' is destroying", created, name);
}
Emulator Emu;