mirror of
https://github.com/RPCS3/rpcs3.git
synced 2025-12-06 07:12:28 +01:00
Multi-Slot Savestates
Some checks failed
Generate Translation Template / Generate Translation Template (push) Has been cancelled
Build RPCS3 / RPCS3 Linux ${{ matrix.os }} ${{ matrix.compiler }} (/rpcs3/.ci/build-linux-aarch64.sh, gcc, rpcs3/rpcs3-ci-jammy-aarch64:1.6, ubuntu-24.04-arm) (push) Has been cancelled
Build RPCS3 / RPCS3 Linux ${{ matrix.os }} ${{ matrix.compiler }} (/rpcs3/.ci/build-linux.sh, gcc, rpcs3/rpcs3-ci-jammy:1.6, ubuntu-24.04) (push) Has been cancelled
Build RPCS3 / RPCS3 Linux ${{ matrix.os }} ${{ matrix.compiler }} (a1d35836e8d45bfc6f63c26f0a3e5d46ef622fe1, rpcs3/rpcs3-binaries-linux-arm64, /rpcs3/.ci/build-linux-aarch64.sh, clang, rpcs3/rpcs3-ci-jammy-aarch64:1.6, ubuntu-24.04-arm) (push) Has been cancelled
Build RPCS3 / RPCS3 Linux ${{ matrix.os }} ${{ matrix.compiler }} (d812f1254a1157c80fd402f94446310560f54e5f, rpcs3/rpcs3-binaries-linux, /rpcs3/.ci/build-linux.sh, clang, rpcs3/rpcs3-ci-jammy:1.6, ubuntu-24.04) (push) Has been cancelled
Build RPCS3 / RPCS3 Mac ${{ matrix.name }} (51ae32f468089a8169aaf1567de355ff4a3e0842, rpcs3/rpcs3-binaries-mac, .ci/build-mac.sh, Intel) (push) Has been cancelled
Build RPCS3 / RPCS3 Mac ${{ matrix.name }} (8e21bdbc40711a3fccd18fbf17b742348b0f4281, rpcs3/rpcs3-binaries-mac-arm64, .ci/build-mac-arm64.sh, Apple Silicon) (push) Has been cancelled
Build RPCS3 / RPCS3 Windows (push) Has been cancelled
Build RPCS3 / RPCS3 Windows Clang (win64, clang, clang64) (push) Has been cancelled
Build RPCS3 / RPCS3 FreeBSD (push) Has been cancelled
Some checks failed
Generate Translation Template / Generate Translation Template (push) Has been cancelled
Build RPCS3 / RPCS3 Linux ${{ matrix.os }} ${{ matrix.compiler }} (/rpcs3/.ci/build-linux-aarch64.sh, gcc, rpcs3/rpcs3-ci-jammy-aarch64:1.6, ubuntu-24.04-arm) (push) Has been cancelled
Build RPCS3 / RPCS3 Linux ${{ matrix.os }} ${{ matrix.compiler }} (/rpcs3/.ci/build-linux.sh, gcc, rpcs3/rpcs3-ci-jammy:1.6, ubuntu-24.04) (push) Has been cancelled
Build RPCS3 / RPCS3 Linux ${{ matrix.os }} ${{ matrix.compiler }} (a1d35836e8d45bfc6f63c26f0a3e5d46ef622fe1, rpcs3/rpcs3-binaries-linux-arm64, /rpcs3/.ci/build-linux-aarch64.sh, clang, rpcs3/rpcs3-ci-jammy-aarch64:1.6, ubuntu-24.04-arm) (push) Has been cancelled
Build RPCS3 / RPCS3 Linux ${{ matrix.os }} ${{ matrix.compiler }} (d812f1254a1157c80fd402f94446310560f54e5f, rpcs3/rpcs3-binaries-linux, /rpcs3/.ci/build-linux.sh, clang, rpcs3/rpcs3-ci-jammy:1.6, ubuntu-24.04) (push) Has been cancelled
Build RPCS3 / RPCS3 Mac ${{ matrix.name }} (51ae32f468089a8169aaf1567de355ff4a3e0842, rpcs3/rpcs3-binaries-mac, .ci/build-mac.sh, Intel) (push) Has been cancelled
Build RPCS3 / RPCS3 Mac ${{ matrix.name }} (8e21bdbc40711a3fccd18fbf17b742348b0f4281, rpcs3/rpcs3-binaries-mac-arm64, .ci/build-mac-arm64.sh, Apple Silicon) (push) Has been cancelled
Build RPCS3 / RPCS3 Windows (push) Has been cancelled
Build RPCS3 / RPCS3 Windows Clang (win64, clang, clang64) (push) Has been cancelled
Build RPCS3 / RPCS3 FreeBSD (push) Has been cancelled
This commit is contained in:
parent
48bf30aa2d
commit
b18a0830f3
|
|
@ -2,8 +2,8 @@
|
||||||
#include "overlay_home_menu_savestate.h"
|
#include "overlay_home_menu_savestate.h"
|
||||||
#include "overlay_home_menu_components.h"
|
#include "overlay_home_menu_components.h"
|
||||||
#include "Emu/system_config.h"
|
#include "Emu/system_config.h"
|
||||||
|
#include "Emu/savestate_utils.hpp"
|
||||||
|
|
||||||
extern bool boot_last_savestate(bool testing);
|
|
||||||
|
|
||||||
namespace rsx
|
namespace rsx
|
||||||
{
|
{
|
||||||
|
|
@ -35,7 +35,7 @@ namespace rsx
|
||||||
return page_navigation::exit;
|
return page_navigation::exit;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!suspend_mode && boot_last_savestate(true)) {
|
if (!suspend_mode && boot_current_game_savestate(true, 1)) {
|
||||||
std::unique_ptr<overlay_element> reload_state = std::make_unique<home_menu_entry>(
|
std::unique_ptr<overlay_element> reload_state = std::make_unique<home_menu_entry>(
|
||||||
get_localized_string(localized_string_id::HOME_MENU_RELOAD_SAVESTATE));
|
get_localized_string(localized_string_id::HOME_MENU_RELOAD_SAVESTATE));
|
||||||
|
|
||||||
|
|
@ -43,7 +43,7 @@ namespace rsx
|
||||||
{
|
{
|
||||||
if (btn != pad_button::cross) return page_navigation::stay;
|
if (btn != pad_button::cross) return page_navigation::stay;
|
||||||
rsx_log.notice("User selected reload savestate in home menu");
|
rsx_log.notice("User selected reload savestate in home menu");
|
||||||
Emu.CallFromMainThread([]() { boot_last_savestate(false); });
|
Emu.CallFromMainThread([]() { boot_current_game_savestate(true, 1); });
|
||||||
return page_navigation::exit;
|
return page_navigation::exit;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2529,7 +2529,7 @@ void Emulator::FixGuestTime()
|
||||||
// Mark a known savestate location and the one we try to boot (in case we boot a moved/copied savestate)
|
// Mark a known savestate location and the one we try to boot (in case we boot a moved/copied savestate)
|
||||||
if (g_cfg.savestate.suspend_emu)
|
if (g_cfg.savestate.suspend_emu)
|
||||||
{
|
{
|
||||||
for (std::string old_path : std::initializer_list<std::string>{m_ar ? m_path_old : "", m_title_id.empty() ? "" : get_savestate_file(m_title_id, m_path_old, 0, 0)})
|
for (std::string old_path : std::initializer_list<std::string>{m_ar ? m_path_old : "", m_title_id.empty() ? "" : get_savestate_file(m_title_id, m_path_old, -1)})
|
||||||
{
|
{
|
||||||
if (old_path.empty())
|
if (old_path.empty())
|
||||||
{
|
{
|
||||||
|
|
@ -3390,7 +3390,7 @@ void Emulator::Kill(bool allow_autoexit, bool savestate, savestate_stage* save_s
|
||||||
{
|
{
|
||||||
set_progress_message("Creating File");
|
set_progress_message("Creating File");
|
||||||
|
|
||||||
path = get_savestate_file(m_title_id, m_path, 0, 0);
|
path = get_savestate_file(m_title_id, m_path, 0, umax);
|
||||||
|
|
||||||
// The function is meant for reading files, so if there is no ZST file it would not return compressed file path
|
// The function is meant for reading files, so if there is no ZST file it would not return compressed file path
|
||||||
// So this is the only place where the result is edited if need to be
|
// So this is the only place where the result is edited if need to be
|
||||||
|
|
@ -3630,13 +3630,20 @@ void Emulator::Kill(bool allow_autoexit, bool savestate, savestate_stage* save_s
|
||||||
sys_log.success("Old savestate has been removed: path='%s'", old_path2);
|
sys_log.success("Old savestate has been removed: path='%s'", old_path2);
|
||||||
}
|
}
|
||||||
|
|
||||||
sys_log.success("Saved savestate! path='%s' (file_size=0x%x, time_to_save=%gs)", path, file_stat.size, (get_system_time() - start_time) / 1000000.);
|
sys_log.success("Saved savestate! path='%s' (file_size=0x%x (%d MiB), time_to_save=%gs)", path, file_stat.size, utils::aligned_div<u64>(file_stat.size, 1u << 20), (get_system_time() - start_time) / 1000000.);
|
||||||
|
|
||||||
if (!g_cfg.savestate.suspend_emu)
|
if (!g_cfg.savestate.suspend_emu)
|
||||||
{
|
{
|
||||||
// Allow to reboot from GUI
|
// Allow to reboot from GUI
|
||||||
m_path = path;
|
m_path = path;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Clean savestates
|
||||||
|
// Cap by number and aggregate file size
|
||||||
|
const u64 max_files = g_cfg.savestate.max_files;
|
||||||
|
const u64 max_files_size_mb = g_cfg.savestate.max_files_size;
|
||||||
|
|
||||||
|
clean_savestates(m_title_id, m_path, max_files, max_files_size_mb << 20);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -270,17 +270,146 @@ bool is_savestate_version_compatible(const std::vector<version_entry>& data, boo
|
||||||
return ok;
|
return ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string get_savestate_file(std::string_view title_id, std::string_view boot_path, s64 abs_id, s64 rel_id)
|
std::string get_savestate_file(std::string_view title_id, std::string_view boot_path, s64 rel_id, u64 aggregate_file_size)
|
||||||
{
|
{
|
||||||
const std::string title = std::string{title_id.empty() ? boot_path.substr(boot_path.find_last_of(fs::delim) + 1) : title_id};
|
const std::string title = std::string{title_id.empty() ? boot_path.substr(boot_path.find_last_of(fs::delim) + 1) : title_id};
|
||||||
|
|
||||||
if (abs_id == -1 && rel_id == -1)
|
// Internal functionality ATM
|
||||||
|
constexpr s64 abs_id = 0;
|
||||||
|
|
||||||
|
if (aggregate_file_size == umax && rel_id == -1)
|
||||||
{
|
{
|
||||||
// Return directory
|
// Return directory
|
||||||
return fs::get_config_dir() + "savestates/" + title + "/";
|
return fs::get_config_dir() + "savestates/" + title + "/";
|
||||||
}
|
}
|
||||||
|
|
||||||
ensure(rel_id < 0 || abs_id >= 0, "Unimplemented!");
|
if (rel_id >= 0)
|
||||||
|
{
|
||||||
|
std::string dir_path = fs::get_config_dir() + "savestates/" + title + "/";
|
||||||
|
|
||||||
|
fs::dir dir_view{dir_path};
|
||||||
|
|
||||||
|
std::map<std::string, usz, std::greater<>> save_files;
|
||||||
|
|
||||||
|
for (auto&& dir_entry : dir_view)
|
||||||
|
{
|
||||||
|
if (dir_entry.is_directory || dir_entry.size <= 1024)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string& entry = dir_entry.name;
|
||||||
|
|
||||||
|
if (!title_id.empty() && !entry.starts_with(title + "_"))
|
||||||
|
{
|
||||||
|
// Check prefix only for certified applications
|
||||||
|
// Because ELF file names can be long and unhelpful
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entry.ends_with(".SAVESTAT.zst") || entry.ends_with(".SAVESTAT.gz") || entry.ends_with(".SAVESTAT"))
|
||||||
|
{
|
||||||
|
if (usz dot_idx = entry.rfind(".SAVESTAT"); dot_idx && dot_idx != umax)
|
||||||
|
{
|
||||||
|
if (usz uc_pos = entry.rfind("_", dot_idx - 1); uc_pos != umax && uc_pos + 1 < dot_idx)
|
||||||
|
{
|
||||||
|
if (std::all_of(entry.begin() + uc_pos + 1, entry.begin() + dot_idx, [](char c) { return c >= '0' && c <= '9'; }))
|
||||||
|
{
|
||||||
|
save_files.emplace(entry, dir_entry.size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string rel_path;
|
||||||
|
std::string size_based_path;
|
||||||
|
|
||||||
|
auto prepare_return_value = [](std::string& dir_path, std::string& filename) -> std::string&&
|
||||||
|
{
|
||||||
|
dir_path.append(filename);
|
||||||
|
filename = std::move(dir_path);
|
||||||
|
return std::move(filename);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (rel_id > 0)
|
||||||
|
{
|
||||||
|
if (static_cast<usz>(rel_id - 1) < save_files.size())
|
||||||
|
{
|
||||||
|
rel_path = std::next(save_files.begin(), rel_id - 1)->first;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (aggregate_file_size != umax)
|
||||||
|
{
|
||||||
|
usz size_sum = 0;
|
||||||
|
|
||||||
|
for (auto&& [path, size] : save_files)
|
||||||
|
{
|
||||||
|
if (size_sum >= aggregate_file_size)
|
||||||
|
{
|
||||||
|
size_based_path = path;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_sum += size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!rel_path.empty() || !size_based_path.empty())
|
||||||
|
{
|
||||||
|
if (rel_path > size_based_path)
|
||||||
|
{
|
||||||
|
return prepare_return_value(dir_path, rel_path);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return prepare_return_value(dir_path, size_based_path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rel_id > 0 || aggregate_file_size != umax)
|
||||||
|
{
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Increment number in string in reverse
|
||||||
|
// Return index of new character if appended a character, umax otherwise
|
||||||
|
auto increment_string = [](std::string& out, usz pos) -> usz
|
||||||
|
{
|
||||||
|
while (pos != umax && out[pos] == '9')
|
||||||
|
{
|
||||||
|
out[pos] = '0';
|
||||||
|
pos--;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pos == umax || (out[pos] < '0' || out[pos] > '9'))
|
||||||
|
{
|
||||||
|
out.insert(out.begin() + (pos + 1), '1');
|
||||||
|
return pos + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
out[pos]++;
|
||||||
|
return umax;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!save_files.empty())
|
||||||
|
{
|
||||||
|
std::string last_entry = save_files.begin()->first;
|
||||||
|
|
||||||
|
// Increment entry ID
|
||||||
|
if (usz inc_pos = increment_string(last_entry, last_entry.rfind(".SAVESTAT") - 1); inc_pos != umax)
|
||||||
|
{
|
||||||
|
// Increment entry suffix - ID has become wider in length (keeps the string in alphbetic ordering)
|
||||||
|
ensure(inc_pos >= 2);
|
||||||
|
ensure(last_entry[inc_pos - 2]++ != 'z');
|
||||||
|
}
|
||||||
|
|
||||||
|
return prepare_return_value(dir_path, last_entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback - create new file
|
||||||
|
}
|
||||||
|
|
||||||
const std::string save_id = fmt::format("%d", abs_id);
|
const std::string save_id = fmt::format("%d", abs_id);
|
||||||
|
|
||||||
|
|
@ -335,48 +464,22 @@ std::vector<version_entry> read_used_savestate_versions()
|
||||||
return used_serial;
|
return used_serial;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool boot_last_savestate(bool testing)
|
bool boot_current_game_savestate(bool testing, u32 index)
|
||||||
{
|
{
|
||||||
|
if (index == 0 || index > g_cfg.savestate.max_files.get())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (!g_cfg.savestate.suspend_emu && !Emu.GetTitleID().empty() && (Emu.IsRunning() || Emu.GetStatus() == system_state::paused))
|
if (!g_cfg.savestate.suspend_emu && !Emu.GetTitleID().empty() && (Emu.IsRunning() || Emu.GetStatus() == system_state::paused))
|
||||||
{
|
{
|
||||||
const std::string save_dir = get_savestate_file(Emu.GetTitleID(), Emu.GetBoot(), -1, -1);
|
const std::string savestate_path = get_savestate_file(Emu.GetTitleID(), Emu.GetBoot(), index);
|
||||||
|
|
||||||
std::string savestate_path;
|
|
||||||
s64 mtime = smin;
|
|
||||||
|
|
||||||
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 + ".zst"; is_savestate_compatible(fs::file(path), path))
|
|
||||||
{
|
|
||||||
savestate_path = std::move(path);
|
|
||||||
mtime = entry.mtime;
|
|
||||||
}
|
|
||||||
else if (std::string path = save_dir + entry.name + ".gz"; is_savestate_compatible(fs::file(path), path))
|
|
||||||
{
|
|
||||||
savestate_path = std::move(path);
|
|
||||||
mtime = entry.mtime;
|
|
||||||
}
|
|
||||||
else if (std::string path = save_dir + entry.name; is_savestate_compatible(fs::file(path), path))
|
|
||||||
{
|
|
||||||
savestate_path = std::move(path);
|
|
||||||
mtime = entry.mtime;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const bool result = fs::is_file(savestate_path);
|
const bool result = fs::is_file(savestate_path);
|
||||||
|
|
||||||
if (testing)
|
if (testing)
|
||||||
{
|
{
|
||||||
sys_log.trace("boot_last_savestate(true) returned %s.", result);
|
sys_log.trace("boot_current_game_savestate(true, %d) returned %s.", index, result);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -398,12 +501,48 @@ bool boot_last_savestate(bool testing)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sys_log.error("No compatible savestate file found in \'%s\''", save_dir);
|
sys_log.error("No compatible savestate file found in \'%s\''", get_savestate_file(Emu.GetTitleID(), Emu.GetBoot(), -1));
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void clean_savestates(std::string_view title_id, std::string_view boot_path, usz max_files, usz max_files_size)
|
||||||
|
{
|
||||||
|
ensure(max_files && max_files != umax);
|
||||||
|
|
||||||
|
bool logged_limits = false;
|
||||||
|
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
const std::string to_remove = get_savestate_file(title_id, boot_path, max_files + 1, max_files_size == 0 ? u64{umax} : max_files_size);
|
||||||
|
|
||||||
|
if (to_remove.empty())
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!fs::remove_file(to_remove))
|
||||||
|
{
|
||||||
|
sys_log.error("Failed to remove savestate file at '%s'! (error: %s)", to_remove, fs::g_tls_error);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (!logged_limits)
|
||||||
|
{
|
||||||
|
sys_log.success("Maximum save state files set: %d.\nMaximum save state disk space set: %d (MiB).\nRemoved old savestate file at '%s'.\n"
|
||||||
|
, max_files, max_files_size, to_remove);
|
||||||
|
logged_limits = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sys_log.success("Removed old savestate file at '%s'.", to_remove);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool load_and_check_reserved(utils::serial& ar, usz size)
|
bool load_and_check_reserved(utils::serial& ar, usz size)
|
||||||
{
|
{
|
||||||
u8 bytes[4096];
|
u8 bytes[4096];
|
||||||
|
|
|
||||||
|
|
@ -44,4 +44,6 @@ std::vector<version_entry> get_savestate_versioning_data(fs::file&& file, std::s
|
||||||
bool is_savestate_compatible(fs::file&& file, std::string_view filepath);
|
bool is_savestate_compatible(fs::file&& file, std::string_view filepath);
|
||||||
bool is_savestate_compatible(const std::string& filepath);
|
bool is_savestate_compatible(const std::string& filepath);
|
||||||
std::vector<version_entry> read_used_savestate_versions();
|
std::vector<version_entry> read_used_savestate_versions();
|
||||||
std::string get_savestate_file(std::string_view title_id, std::string_view boot_path, s64 abs_id, s64 rel_id);
|
std::string get_savestate_file(std::string_view title_id, std::string_view boot_path, s64 rel_id, u64 aggregate_file_size = umax);
|
||||||
|
bool boot_current_game_savestate(bool testing, u32 index);
|
||||||
|
void clean_savestates(std::string_view title_id, std::string_view boot_path, usz max_files, usz max_files_size);
|
||||||
|
|
|
||||||
|
|
@ -338,6 +338,8 @@ struct cfg_root : cfg::node
|
||||||
cfg::_bool compatible_mode{ this, "Compatible Savestate Mode", false }; // SPU emulation optimized for savestate compatibility (off by default for performance reasons)
|
cfg::_bool compatible_mode{ this, "Compatible Savestate Mode", false }; // SPU emulation optimized for savestate compatibility (off by default for performance reasons)
|
||||||
cfg::_bool state_inspection_mode{ this, "Inspection Mode Savestates" }; // Save memory stored in executable files, thus allowing to view state without any files (for debugging)
|
cfg::_bool state_inspection_mode{ this, "Inspection Mode Savestates" }; // Save memory stored in executable files, thus allowing to view state without any files (for debugging)
|
||||||
cfg::_bool save_disc_game_data{ this, "Save Disc Game Data", false };
|
cfg::_bool save_disc_game_data{ this, "Save Disc Game Data", false };
|
||||||
|
cfg::uint<0, 64> max_files{ this, "Maximum SaveState Files", 4 };
|
||||||
|
cfg::uint<0, 1024 * 512> max_files_size{ this, "Maximum SaveState Files Space (MiB)", 4096 };
|
||||||
} savestate{this};
|
} savestate{this};
|
||||||
|
|
||||||
struct node_misc : cfg::node
|
struct node_misc : cfg::node
|
||||||
|
|
|
||||||
|
|
@ -46,7 +46,7 @@ LOG_CHANNEL(sys_log, "SYS");
|
||||||
|
|
||||||
extern atomic_t<bool> g_system_progress_canceled;
|
extern atomic_t<bool> g_system_progress_canceled;
|
||||||
|
|
||||||
std::string get_savestate_file(std::string_view title_id, std::string_view boot_pat, s64 abs_id, s64 rel_id);
|
std::string get_savestate_file(std::string_view title_id, std::string_view boot_pat, s64 rel_id, u64 aggregate_file_size = umax);
|
||||||
|
|
||||||
game_list_frame::game_list_frame(std::shared_ptr<gui_settings> gui_settings, std::shared_ptr<emu_settings> emu_settings, std::shared_ptr<persistent_settings> persistent_settings, QWidget* parent)
|
game_list_frame::game_list_frame(std::shared_ptr<gui_settings> gui_settings, std::shared_ptr<emu_settings> emu_settings, std::shared_ptr<persistent_settings> persistent_settings, QWidget* parent)
|
||||||
: custom_dock_widget(tr("Game List"), parent)
|
: custom_dock_widget(tr("Game List"), parent)
|
||||||
|
|
@ -1221,13 +1221,20 @@ void game_list_frame::ShowContextMenu(const QPoint &pos)
|
||||||
|
|
||||||
extern bool is_savestate_compatible(const std::string& filepath);
|
extern bool is_savestate_compatible(const std::string& filepath);
|
||||||
|
|
||||||
if (const std::string sstate = get_savestate_file(current_game.serial, current_game.path, 0, 0); is_savestate_compatible(sstate))
|
if (const std::string sstate = get_savestate_file(current_game.serial, current_game.path, 1); is_savestate_compatible(sstate))
|
||||||
{
|
{
|
||||||
QAction* boot_state = menu.addAction(is_current_running_game
|
QAction* boot_state = menu.addAction(is_current_running_game
|
||||||
? tr("&Reboot with savestate")
|
? tr("&Reboot with savestate")
|
||||||
: tr("&Boot with savestate"));
|
: tr("&Boot with savestate"));
|
||||||
connect(boot_state, &QAction::triggered, [this, gameinfo, sstate]
|
connect(boot_state, &QAction::triggered, [this, gameinfo, sstate, current_game]
|
||||||
{
|
{
|
||||||
|
if (!get_savestate_file(current_game.serial, current_game.path, 2).empty())
|
||||||
|
{
|
||||||
|
// If there is any ambiguity, launch the savestate manager
|
||||||
|
Q_EMIT RequestSaveStateManager(gameinfo);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
sys_log.notice("Booting savestate from gamelist per context menu...");
|
sys_log.notice("Booting savestate from gamelist per context menu...");
|
||||||
Q_EMIT RequestBoot(gameinfo, cfg_mode::custom, "", sstate);
|
Q_EMIT RequestBoot(gameinfo, cfg_mode::custom, "", sstate);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -95,6 +95,7 @@ Q_SIGNALS:
|
||||||
void NotifyEmuSettingsChange();
|
void NotifyEmuSettingsChange();
|
||||||
void FocusToSearchBar();
|
void FocusToSearchBar();
|
||||||
void Refreshed();
|
void Refreshed();
|
||||||
|
void RequestSaveStateManager(const game_info& game);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
template <typename KeyType>
|
template <typename KeyType>
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@
|
||||||
#include "Emu/System.h"
|
#include "Emu/System.h"
|
||||||
#include "Emu/system_config.h"
|
#include "Emu/system_config.h"
|
||||||
#include "Emu/system_progress.hpp"
|
#include "Emu/system_progress.hpp"
|
||||||
|
#include "Emu/savestate_utils.hpp"
|
||||||
#include "Emu/IdManager.h"
|
#include "Emu/IdManager.h"
|
||||||
#include "Emu/Audio/audio_utils.h"
|
#include "Emu/Audio/audio_utils.h"
|
||||||
#include "Emu/Cell/Modules/cellScreenshot.h"
|
#include "Emu/Cell/Modules/cellScreenshot.h"
|
||||||
|
|
@ -315,6 +316,10 @@ void gs_frame::handle_shortcut(gui::shortcuts::shortcut shortcut_key, const QKey
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case gui::shortcuts::shortcut::gw_restart:
|
case gui::shortcuts::shortcut::gw_restart:
|
||||||
|
case gui::shortcuts::shortcut::gw_savestate_1:
|
||||||
|
case gui::shortcuts::shortcut::gw_savestate_2:
|
||||||
|
case gui::shortcuts::shortcut::gw_savestate_3:
|
||||||
|
case gui::shortcuts::shortcut::gw_savestate_4:
|
||||||
{
|
{
|
||||||
if (Emu.IsStopped())
|
if (Emu.IsStopped())
|
||||||
{
|
{
|
||||||
|
|
@ -322,8 +327,18 @@ void gs_frame::handle_shortcut(gui::shortcuts::shortcut shortcut_key, const QKey
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
extern bool boot_last_savestate(bool testing);
|
u32 index = 1;
|
||||||
boot_last_savestate(false);
|
|
||||||
|
switch (shortcut_key)
|
||||||
|
{
|
||||||
|
case gui::shortcuts::shortcut::gw_restart: index = 1; break;
|
||||||
|
case gui::shortcuts::shortcut::gw_savestate_1: index = 1; break;
|
||||||
|
case gui::shortcuts::shortcut::gw_savestate_2: index = 2; break;
|
||||||
|
case gui::shortcuts::shortcut::gw_savestate_3: index = 3; break;
|
||||||
|
case gui::shortcuts::shortcut::gw_savestate_4: index = 4; break;
|
||||||
|
}
|
||||||
|
|
||||||
|
boot_current_game_savestate(false, index);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case gui::shortcuts::shortcut::gw_savestate:
|
case gui::shortcuts::shortcut::gw_savestate:
|
||||||
|
|
|
||||||
|
|
@ -3320,6 +3320,17 @@ void main_window::CreateConnects()
|
||||||
ResizeIcons(idx);
|
ResizeIcons(idx);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
connect(m_game_list_frame, &game_list_frame::RequestSaveStateManager, this, [this](const game_info& gameinfo)
|
||||||
|
{
|
||||||
|
savestate_manager_dialog* manager = new savestate_manager_dialog(m_gui_settings, std::vector<game_info>{gameinfo});
|
||||||
|
connect(this, &main_window::RequestDialogRepaint, manager, &savestate_manager_dialog::HandleRepaintUiRequest);
|
||||||
|
connect(manager, &savestate_manager_dialog::RequestBoot, this, [this, gameinfo](const std::string& path)
|
||||||
|
{
|
||||||
|
Boot(path, gameinfo->info.serial, false, false, cfg_mode::custom, "");
|
||||||
|
});
|
||||||
|
manager->show();
|
||||||
|
});
|
||||||
|
|
||||||
connect(m_list_mode_act_group, &QActionGroup::triggered, this, [this](QAction* act)
|
connect(m_list_mode_act_group, &QActionGroup::triggered, this, [this](QAction* act)
|
||||||
{
|
{
|
||||||
const bool is_list_act = act == ui->setlistModeListAct;
|
const bool is_list_act = act == ui->setlistModeListAct;
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,10 @@ void fmt_class_string<shortcut>::format(std::string& out, u64 arg)
|
||||||
case shortcut::gw_toggle_recording: return "gw_toggle_recording";
|
case shortcut::gw_toggle_recording: return "gw_toggle_recording";
|
||||||
case shortcut::gw_pause_play: return "gw_pause_play";
|
case shortcut::gw_pause_play: return "gw_pause_play";
|
||||||
case shortcut::gw_savestate: return "gw_savestate";
|
case shortcut::gw_savestate: return "gw_savestate";
|
||||||
|
case shortcut::gw_savestate_1: return "gw_savestate1";
|
||||||
|
case shortcut::gw_savestate_2: return "gw_savestate2";
|
||||||
|
case shortcut::gw_savestate_3: return "gw_savestate3";
|
||||||
|
case shortcut::gw_savestate_4: return "gw_savestate4";
|
||||||
case shortcut::gw_restart: return "gw_restart";
|
case shortcut::gw_restart: return "gw_restart";
|
||||||
case shortcut::gw_rsx_capture: return "gw_rsx_capture";
|
case shortcut::gw_rsx_capture: return "gw_rsx_capture";
|
||||||
case shortcut::gw_frame_limit: return "gw_frame_limit";
|
case shortcut::gw_frame_limit: return "gw_frame_limit";
|
||||||
|
|
@ -71,6 +75,10 @@ shortcut_settings::shortcut_settings()
|
||||||
{ shortcut::gw_screenshot, shortcut_info{ "game_window_screenshot", tr("Screenshot"), "F12", shortcut_handler_id::game_window, false } },
|
{ shortcut::gw_screenshot, shortcut_info{ "game_window_screenshot", tr("Screenshot"), "F12", shortcut_handler_id::game_window, false } },
|
||||||
{ shortcut::gw_pause_play, shortcut_info{ "game_window_pause_play", tr("Pause/Play"), "Ctrl+P", shortcut_handler_id::game_window, false } },
|
{ shortcut::gw_pause_play, shortcut_info{ "game_window_pause_play", tr("Pause/Play"), "Ctrl+P", shortcut_handler_id::game_window, false } },
|
||||||
{ shortcut::gw_savestate, shortcut_info{ "game_window_savestate", tr("Savestate"), "Ctrl+S", shortcut_handler_id::game_window, false } },
|
{ shortcut::gw_savestate, shortcut_info{ "game_window_savestate", tr("Savestate"), "Ctrl+S", shortcut_handler_id::game_window, false } },
|
||||||
|
{ shortcut::gw_savestate_1, shortcut_info{ "game_window_savestate_1", tr("Savestate"), "Alt+Ctrl+1", shortcut_handler_id::game_window, false } },
|
||||||
|
{ shortcut::gw_savestate_2, shortcut_info{ "game_window_savestate_2", tr("Savestate"), "Alt+Ctrl+2", shortcut_handler_id::game_window, false } },
|
||||||
|
{ shortcut::gw_savestate_3, shortcut_info{ "game_window_savestate_3", tr("Savestate"), "Alt+Ctrl+3", shortcut_handler_id::game_window, false } },
|
||||||
|
{ shortcut::gw_savestate_4, shortcut_info{ "game_window_savestate_4", tr("Savestate"), "Alt+Ctrl+4", shortcut_handler_id::game_window, false } },
|
||||||
{ shortcut::gw_restart, shortcut_info{ "game_window_restart", tr("Restart"), "Ctrl+R", shortcut_handler_id::game_window, false } },
|
{ shortcut::gw_restart, shortcut_info{ "game_window_restart", tr("Restart"), "Ctrl+R", shortcut_handler_id::game_window, false } },
|
||||||
{ shortcut::gw_rsx_capture, shortcut_info{ "game_window_rsx_capture", tr("RSX Capture"), "Alt+C", shortcut_handler_id::game_window, false } },
|
{ shortcut::gw_rsx_capture, shortcut_info{ "game_window_rsx_capture", tr("RSX Capture"), "Alt+C", shortcut_handler_id::game_window, false } },
|
||||||
{ shortcut::gw_frame_limit, shortcut_info{ "game_window_frame_limit", tr("Toggle Framelimit"), "Ctrl+F10", shortcut_handler_id::game_window, false } },
|
{ shortcut::gw_frame_limit, shortcut_info{ "game_window_frame_limit", tr("Toggle Framelimit"), "Ctrl+F10", shortcut_handler_id::game_window, false } },
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,10 @@ namespace gui
|
||||||
gw_toggle_recording,
|
gw_toggle_recording,
|
||||||
gw_pause_play,
|
gw_pause_play,
|
||||||
gw_savestate,
|
gw_savestate,
|
||||||
|
gw_savestate_1,
|
||||||
|
gw_savestate_2,
|
||||||
|
gw_savestate_3,
|
||||||
|
gw_savestate_4,
|
||||||
gw_restart,
|
gw_restart,
|
||||||
gw_rsx_capture,
|
gw_rsx_capture,
|
||||||
gw_frame_limit,
|
gw_frame_limit,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue