diff --git a/rpcs3/Emu/CMakeLists.txt b/rpcs3/Emu/CMakeLists.txt index 70770321a9..99b4aa7261 100644 --- a/rpcs3/Emu/CMakeLists.txt +++ b/rpcs3/Emu/CMakeLists.txt @@ -2,6 +2,7 @@ add_library(rpcs3_emu cache_utils.cpp IdManager.cpp localized_string.cpp + savestate_utils.cpp System.cpp system_config.cpp system_config_types.cpp diff --git a/rpcs3/Emu/System.cpp b/rpcs3/Emu/System.cpp index a77ab13ea5..c8f1ba6d80 100644 --- a/rpcs3/Emu/System.cpp +++ b/rpcs3/Emu/System.cpp @@ -65,71 +65,6 @@ bool g_use_rtm = false; u64 g_rtm_tx_limit1 = 0; u64 g_rtm_tx_limit2 = 0; -struct serial_ver_t -{ - bool used = false; - s32 current_version = 0; - std::set compatible_versions; -}; - -static std::array s_serial_versions; - -#define SERIALIZATION_VER(name, identifier, ...) \ -\ - const bool s_##name##_serialization_fill = []() { if (::s_serial_versions[identifier].compatible_versions.empty()) ::s_serial_versions[identifier].compatible_versions = {__VA_ARGS__}; return true; }();\ -\ - extern void using_##name##_serialization()\ - {\ - ensure(Emu.IsStopped());\ - ::s_serial_versions[identifier].used = true;\ - }\ -\ - extern s32 get_##name##_serialization_version()\ - {\ - return ::s_serial_versions[identifier].current_version;\ - } - -SERIALIZATION_VER(global_version, 0, 12) // For stuff not listed here -SERIALIZATION_VER(ppu, 1, 1) -SERIALIZATION_VER(spu, 2, 1) -SERIALIZATION_VER(lv2_sync, 3, 1) -SERIALIZATION_VER(lv2_vm, 4, 1) -SERIALIZATION_VER(lv2_net, 5, 1) -SERIALIZATION_VER(lv2_fs, 6, 1) -SERIALIZATION_VER(lv2_prx_overlay, 7, 1) -SERIALIZATION_VER(lv2_memory, 8, 1) -SERIALIZATION_VER(lv2_config, 9, 1) - -namespace rsx -{ - SERIALIZATION_VER(rsx, 10, 1, 2) -} - -namespace np -{ - SERIALIZATION_VER(sceNp, 11, 1) -} - -#ifdef _MSC_VER -// Compiler bug, lambda function body does seem to inherit used namespace atleast for function decleration -SERIALIZATION_VER(rsx, 10) -SERIALIZATION_VER(sceNp, 11) -#endif - -SERIALIZATION_VER(cellVdec, 12, 1) -SERIALIZATION_VER(cellAudio, 13, 1) -SERIALIZATION_VER(cellCamera, 14, 1) -SERIALIZATION_VER(cellGem, 15, 1) -SERIALIZATION_VER(sceNpTrophy, 16, 1) -SERIALIZATION_VER(cellMusic, 17, 1) -SERIALIZATION_VER(cellVoice, 18, 1) -SERIALIZATION_VER(cellGcm, 19, 1) -SERIALIZATION_VER(sysPrxForUser, 20, 1) -SERIALIZATION_VER(cellSaveData, 21, 1) -SERIALIZATION_VER(cellAudioOut, 22, 1) - -#undef SERIALIZATION_VER - std::string g_cfg_defaults; atomic_t g_watchdog_hold_ctr{0}; @@ -144,6 +79,8 @@ extern void ppu_unload_prx(const lv2_prx&); extern std::shared_ptr ppu_load_prx(const ppu_prx_object&, const std::string&, s64 = 0, utils::serial* = nullptr); extern std::pair, CellError> ppu_load_overlay(const ppu_exec_object&, const std::string& path, s64 = 0, utils::serial* = nullptr); extern bool ppu_load_rel_exec(const ppu_rel_object&); +extern bool is_savestate_version_compatible(const std::vector>& data, bool log); +extern std::vector> read_used_savestate_versions(); fs::file g_tty; atomic_t g_tty_size{0}; @@ -875,7 +812,7 @@ game_boot_result Emulator::Load(const std::string& title_id, bool add_only, bool return game_boot_result::savestate_corrupted; } - if (header.LE_format != (std::endian::native == std::endian::little)) + if (header.LE_format != (std::endian::native == std::endian::little) || header.offset >= m_ar->data.size()) { return game_boot_result::savestate_corrupted; } @@ -884,38 +821,13 @@ game_boot_result Emulator::Load(const std::string& title_id, bool add_only, bool // Emulate seek operation (please avoid using in other places) m_ar->pos = header.offset; - const std::vector> versions_data = *m_ar; - m_ar->pos = sizeof(file_header); // Restore position - - if (versions_data.empty()) - { - return game_boot_result::savestate_corrupted; - } - - bool ok = true; - - for (auto [identifier, version] : versions_data) - { - if (identifier >= s_serial_versions.size()) - { - sys_log.error("Savestate version identider is unknown! (category=%u, version=%u)", identifier, version); - ok = false; // Log all mismatches - } - else if (!s_serial_versions[identifier].compatible_versions.count(version)) - { - sys_log.error("Savestate version is not supported. (category=%u, version=%u)", identifier, version); - ok = false; - } - else - { - s_serial_versions[identifier].current_version = version; - } - } - - if (!ok) + + if (!is_savestate_version_compatible(m_ar->operator std::vector>(), true)) { return game_boot_result::savestate_version_unsupported; } + + m_ar->pos = sizeof(file_header); // Restore position argv.clear(); klic.clear(); @@ -2393,13 +2305,8 @@ void Emulator::Kill(bool allow_autoexit, bool savestate) auto& ar = *m_ar; - for (serial_ver_t& ver : s_serial_versions) - { - ver.used = false; - } - - using_global_version_serialization(); - using_ppu_serialization(); + read_used_savestate_versions(); // Reset version data + USING_SERIALIZATION_VERSION(global_version); // Avoid duplicating TAR object memory because it can be very large auto save_tar = [&](const std::string& path) @@ -2535,16 +2442,7 @@ void Emulator::Kill(bool allow_autoexit, bool savestate) fs::pending_file file(path); // Identifer -> version - std::vector> used_serial; - used_serial.reserve(s_serial_versions.size()); - - for (const serial_ver_t& ver : s_serial_versions) - { - if (ver.used) - { - used_serial.emplace_back(&ver - s_serial_versions.data(), *ver.compatible_versions.rbegin()); - } - } + std::vector> used_serial = read_used_savestate_versions(); auto& ar = *m_ar; const usz pos = ar.seek_end(); diff --git a/rpcs3/Emu/savestate_utils.cpp b/rpcs3/Emu/savestate_utils.cpp new file mode 100644 index 0000000000..5295c0a91c --- /dev/null +++ b/rpcs3/Emu/savestate_utils.cpp @@ -0,0 +1,210 @@ +#include "stdafx.h" +#include "util/types.hpp" +#include "util/serialization.hpp" +#include "util/logs.hpp" +#include "Utilities/File.h" +#include "system_config.h" + +#include "System.h" + +#include + +LOG_CHANNEL(sys_log, "SYS"); + +struct serial_ver_t +{ + bool used = false; + s32 current_version = 0; + std::set compatible_versions; +}; + +static std::array s_serial_versions; + +#define SERIALIZATION_VER(name, identifier, ...) \ +\ + const bool s_##name##_serialization_fill = []() { if (::s_serial_versions[identifier].compatible_versions.empty()) ::s_serial_versions[identifier].compatible_versions = {__VA_ARGS__}; return true; }();\ +\ + extern void using_##name##_serialization()\ + {\ + ensure(Emu.IsStopped());\ + ::s_serial_versions[identifier].used = true;\ + }\ +\ + extern s32 get_##name##_serialization_version()\ + {\ + return ::s_serial_versions[identifier].current_version;\ + } + +SERIALIZATION_VER(global_version, 0, 12) // For stuff not listed here +SERIALIZATION_VER(ppu, 1, 1) +SERIALIZATION_VER(spu, 2, 1) +SERIALIZATION_VER(lv2_sync, 3, 1) +SERIALIZATION_VER(lv2_vm, 4, 1) +SERIALIZATION_VER(lv2_net, 5, 1) +SERIALIZATION_VER(lv2_fs, 6, 1) +SERIALIZATION_VER(lv2_prx_overlay, 7, 1) +SERIALIZATION_VER(lv2_memory, 8, 1) +SERIALIZATION_VER(lv2_config, 9, 1) + +namespace rsx +{ + SERIALIZATION_VER(rsx, 10, 1, 2) +} + +namespace np +{ + SERIALIZATION_VER(sceNp, 11, 1) +} + +#ifdef _MSC_VER +// Compiler bug, lambda function body does seem to inherit used namespace atleast for function decleration +SERIALIZATION_VER(rsx, 10) +SERIALIZATION_VER(sceNp, 11) +#endif + +SERIALIZATION_VER(cellVdec, 12, 1) +SERIALIZATION_VER(cellAudio, 13, 1) +SERIALIZATION_VER(cellCamera, 14, 1) +SERIALIZATION_VER(cellGem, 15, 1) +SERIALIZATION_VER(sceNpTrophy, 16, 1) +SERIALIZATION_VER(cellMusic, 17, 1) +SERIALIZATION_VER(cellVoice, 18, 1) +SERIALIZATION_VER(cellGcm, 19, 1) +SERIALIZATION_VER(sysPrxForUser, 20, 1) +SERIALIZATION_VER(cellSaveData, 21, 1) +SERIALIZATION_VER(cellAudioOut, 22, 1) + +std::vector> get_savestate_versioning_data(const fs::file& file) +{ + if (!file) + { + return {}; + } + + file.seek(0); + + if (u64 r = 0; !file.read(r) || r != "RPCS3SAV"_u64) + { + return {}; + } + + file.seek(10); + + u64 offs = 0; + file.read(offs); + + const usz fsize = file.size(); + + if (!offs || fsize <= offs) + { + return {}; + } + + file.seek(offs); + + utils::serial ar; + ar.set_reading_state(); + file.read(ar.data, fsize - offs); + return ar; +} + +bool is_savestate_version_compatible(const std::vector>& data, bool log) +{ + if (data.empty()) + { + return false; + } + + bool ok = true; + + for (auto [identifier, version] : data) + { + if (identifier >= s_serial_versions.size()) + { + (log ? sys_log.error : sys_log.trace)("Savestate version identider is unknown! (category=%u, version=%u)", identifier, version); + ok = false; // Log all mismatches + } + else if (!s_serial_versions[identifier].compatible_versions.count(version)) + { + (log ? sys_log.error : sys_log.trace)("Savestate version is not supported. (category=%u, version=%u)", identifier, version); + ok = false; + } + else + { + s_serial_versions[identifier].current_version = version; + } + } + + return ok; +} + +bool is_savestate_compatible(const fs::file& file) +{ + return is_savestate_version_compatible(get_savestate_versioning_data(file), false); +} + +std::vector> read_used_savestate_versions() +{ + std::vector> used_serial; + used_serial.reserve(s_serial_versions.size()); + + for (serial_ver_t& ver : s_serial_versions) + { + if (std::exchange(ver.used, false)) + { + used_serial.emplace_back(&ver - s_serial_versions.data(), *ver.compatible_versions.rbegin()); + } + } + + return used_serial; +} + +bool boot_last_savestate() +{ + if (!g_cfg.savestate.suspend_emu && !Emu.GetTitleID().empty() && (Emu.IsRunning() || Emu.GetStatus() == system_state::paused)) + { + extern bool is_savestate_compatible(const fs::file& file); + + const std::string save_dir = fs::get_cache_dir() + "/savestates/"; + + std::string savestate_path; + u64 mtime = umax; + + for (auto&& entry : fs::dir(save_dir)) + { + if (entry.is_directory) + { + continue; + } + + // Find the latest savestate file compatible with the game (TODO: Check app version and anything more) + if (entry.name.find(Emu.GetTitleID()) != umax && mtime < entry.mtime) + { + if (std::string path = save_dir + entry.name; is_savestate_compatible(fs::file(path))) + { + savestate_path = std::move(path); + mtime = entry.mtime; + } + } + } + + if (fs::is_file(savestate_path)) + { + sys_log.success("Booting the most recent savestate \'%s\' using the Reload shortcut.", savestate_path); + Emu.GracefulShutdown(false); + + if (game_boot_result error = Emu.BootGame(savestate_path, "", true); error != game_boot_result::no_errors) + { + sys_log.error("Failed to booting savestate \'%s\' using the Reload shortcut. (error: %s)", savestate_path, error); + } + else + { + return true; + } + } + + sys_log.error("No compatible savestate file found in \'%s\''", save_dir); + } + + return false; +} diff --git a/rpcs3/emucore.vcxproj b/rpcs3/emucore.vcxproj index 1b1e13da83..95ca42c0a5 100644 --- a/rpcs3/emucore.vcxproj +++ b/rpcs3/emucore.vcxproj @@ -92,6 +92,7 @@ + diff --git a/rpcs3/emucore.vcxproj.filters b/rpcs3/emucore.vcxproj.filters index 521a02be39..052ae2be49 100644 --- a/rpcs3/emucore.vcxproj.filters +++ b/rpcs3/emucore.vcxproj.filters @@ -1084,6 +1084,9 @@ Emu\GPU\RSX\Overlays + + Emu + diff --git a/rpcs3/main.cpp b/rpcs3/main.cpp index b98f9920a0..9ea9594661 100644 --- a/rpcs3/main.cpp +++ b/rpcs3/main.cpp @@ -1176,7 +1176,6 @@ int main(int argc, char** argv) return 0; } - // run event loop (maybe only needed for the gui application) return app->exec(); } diff --git a/rpcs3/rpcs3qt/game_list_frame.cpp b/rpcs3/rpcs3qt/game_list_frame.cpp index 41e30ad10e..7112cf93f0 100644 --- a/rpcs3/rpcs3qt/game_list_frame.cpp +++ b/rpcs3/rpcs3qt/game_list_frame.cpp @@ -971,7 +971,9 @@ void game_list_frame::ShowContextMenu(const QPoint &pos) }); } - if (const std::string sstate = fs::get_cache_dir() + "/savestates/" + current_game.serial + ".SAVESTAT"; fs::is_file(sstate)) + extern bool is_savestate_compatible(const fs::file& file); + + if (const std::string sstate = fs::get_cache_dir() + "/savestates/" + current_game.serial + ".SAVESTAT"; is_savestate_compatible(fs::file(sstate))) { QAction* boot_state = menu.addAction(is_current_running_game ? tr("&Reboot with savestate") diff --git a/rpcs3/rpcs3qt/gs_frame.cpp b/rpcs3/rpcs3qt/gs_frame.cpp index 124cc2c056..58fcd31e18 100644 --- a/rpcs3/rpcs3qt/gs_frame.cpp +++ b/rpcs3/rpcs3qt/gs_frame.cpp @@ -247,20 +247,6 @@ void gs_frame::keyPressEvent(QKeyEvent *keyEvent) } break; case Qt::Key_P: - if (keyEvent->modifiers() == Qt::ControlModifier && !m_disable_kb_hotkeys && Emu.IsRunning()) - { - Emu.Pause(); - return; - } - break; - case Qt::Key_S: - if (keyEvent->modifiers() == Qt::ControlModifier && !m_disable_kb_hotkeys) - { - Emu.Restart(true); - return; - } - break; - case Qt::Key_R: if (keyEvent->modifiers() == Qt::ControlModifier && !m_disable_kb_hotkeys) { switch (Emu.GetStatus()) @@ -275,8 +261,26 @@ void gs_frame::keyPressEvent(QKeyEvent *keyEvent) Emu.Resume(); return; } - default: break; + default: + { + Emu.Pause(); + return; } + } + } + break; + case Qt::Key_S: + if (keyEvent->modifiers() == Qt::ControlModifier && !m_disable_kb_hotkeys) + { + Emu.Restart(true); + return; + } + break; + case Qt::Key_R: + if (keyEvent->modifiers() == Qt::ControlModifier && !m_disable_kb_hotkeys) + { + extern bool boot_last_savestate(); + boot_last_savestate(); } break; case Qt::Key_C: