Savestates: Implement Ctrl+R to reload the most recent savestate

Ctrl+R no longer means Resume emulation, this functionality has been transferred to Ctrl+P which is also capable of pausing the emulation. (so it's now a toggle)
This commit is contained in:
Eladash 2022-07-11 17:08:34 +03:00 committed by Ivan
parent 1f5cf776b2
commit 0bfdfd8433
8 changed files with 247 additions and 129 deletions

View file

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

View file

@ -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<s32> compatible_versions;
};
static std::array<serial_ver_t, 23> 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<u64> g_watchdog_hold_ctr{0};
@ -144,6 +79,8 @@ extern void ppu_unload_prx(const lv2_prx&);
extern std::shared_ptr<lv2_prx> ppu_load_prx(const ppu_prx_object&, const std::string&, s64 = 0, utils::serial* = nullptr);
extern std::pair<std::shared_ptr<lv2_overlay>, 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<std::pair<u16, u16>>& data, bool log);
extern std::vector<std::pair<u16, u16>> read_used_savestate_versions();
fs::file g_tty;
atomic_t<s64> 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<std::pair<u16, u16>> 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<std::pair<u16, u16>>(), 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<std::pair<u16, u16>> 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<std::pair<u16, u16>> used_serial = read_used_savestate_versions();
auto& ar = *m_ar;
const usz pos = ar.seek_end();

View file

@ -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 <set>
LOG_CHANNEL(sys_log, "SYS");
struct serial_ver_t
{
bool used = false;
s32 current_version = 0;
std::set<s32> compatible_versions;
};
static std::array<serial_ver_t, 23> 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<std::pair<u16, u16>> 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<std::pair<u16, u16>>& 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<std::pair<u16, u16>> read_used_savestate_versions()
{
std::vector<std::pair<u16, u16>> 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;
}

View file

@ -92,6 +92,7 @@
<ClCompile Include="Emu\RSX\RSXDisAsm.cpp" />
<ClCompile Include="Emu\RSX\RSXZCULL.cpp" />
<ClCompile Include="Emu\RSX\rsx_vertex_data.cpp" />
<ClCompile Include="Emu\savestate_utils.cpp" />
<ClCompile Include="Emu\system_config_types.cpp" />
<ClCompile Include="Emu\perf_meter.cpp" />
<ClCompile Include="Emu\system_progress.cpp" />

View file

@ -1084,6 +1084,9 @@
<ClCompile Include="Emu\RSX\Overlays\overlay_cursor.cpp">
<Filter>Emu\GPU\RSX\Overlays</Filter>
</ClCompile>
<ClCompile Include="Emu\savestate_utils.cpp">
<Filter>Emu</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="Crypto\aes.h">

View file

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

View file

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

View file

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