mirror of
https://github.com/RPCS3/rpcs3.git
synced 2025-04-20 11:36:13 +00:00
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:
parent
1f5cf776b2
commit
0bfdfd8433
8 changed files with 247 additions and 129 deletions
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
|
|
210
rpcs3/Emu/savestate_utils.cpp
Normal file
210
rpcs3/Emu/savestate_utils.cpp
Normal 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;
|
||||
}
|
|
@ -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" />
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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:
|
||||
|
|
Loading…
Add table
Reference in a new issue