Savestate: Add backup slot loading ability

This commit is contained in:
KamFretoZ 2025-04-26 19:19:34 +07:00 committed by lightningterror
parent 7bdf349b9d
commit ad6f5fd6af
10 changed files with 64 additions and 41 deletions

View File

@ -2819,7 +2819,7 @@ QString MainWindow::getDiscDevicePath(const QString& title)
return ret;
}
void MainWindow::startGameListEntry(const GameList::Entry* entry, std::optional<s32> save_slot, std::optional<bool> fast_boot)
void MainWindow::startGameListEntry(const GameList::Entry* entry, std::optional<s32> save_slot, std::optional<bool> fast_boot, bool load_backup)
{
std::shared_ptr<VMBootParameters> params = std::make_shared<VMBootParameters>();
params->fast_boot = fast_boot;
@ -2828,7 +2828,7 @@ void MainWindow::startGameListEntry(const GameList::Entry* entry, std::optional<
if (save_slot.has_value() && !entry->serial.empty())
{
std::string state_filename = VMManager::GetSaveStateFileName(entry->serial.c_str(), entry->crc, save_slot.value());
std::string state_filename = VMManager::GetSaveStateFileName(entry->serial.c_str(), entry->crc, save_slot.value(), load_backup);
if (!FileSystem::FileExists(state_filename.c_str()))
{
QMessageBox::critical(this, tr("Error"), tr("This save state does not exist."));
@ -2953,12 +2953,12 @@ std::optional<bool> MainWindow::promptForResumeState(const QString& save_state_p
return std::nullopt;
}
void MainWindow::loadSaveStateSlot(s32 slot)
void MainWindow::loadSaveStateSlot(s32 slot, bool load_backup)
{
if (s_vm_valid)
{
// easy when we're running
g_emu_thread->loadStateFromSlot(slot);
g_emu_thread->loadStateFromSlot(slot, load_backup);
return;
}
else
@ -2968,7 +2968,7 @@ void MainWindow::loadSaveStateSlot(s32 slot)
if (!entry)
return;
startGameListEntry(entry, slot, std::nullopt);
startGameListEntry(entry, slot, std::nullopt, load_backup);
}
}
@ -3015,15 +3015,6 @@ void MainWindow::populateLoadStateMenu(QMenu* menu, const QString& filename, con
QAction* delete_save_states_action = menu->addAction(tr("Delete Save States..."));
// don't include undo in the right click menu
if (!is_right_click_menu)
{
QAction* load_undo_state = menu->addAction(tr("Undo Load State"));
load_undo_state->setEnabled(false); // CanUndoLoadState()
// connect(load_undo_state, &QAction::triggered, this, &QtHostInterface::undoLoadState);
menu->addSeparator();
}
const QByteArray game_serial_utf8(serial.toUtf8());
std::string state_filename;
FILESYSTEM_STAT_DATA sd;
@ -3053,6 +3044,18 @@ void MainWindow::populateLoadStateMenu(QMenu* menu, const QString& filename, con
has_any_states = true;
}
for (s32 i = 1; i <= VMManager::NUM_SAVE_STATE_SLOTS; i++)
{
FILESYSTEM_STAT_DATA sd;
state_filename = VMManager::GetSaveStateFileName(game_serial_utf8.constData(), crc, i, true);
if (!FileSystem::StatFile(state_filename.c_str(), &sd))
continue;
action = menu->addAction(tr("Load Backup Slot %1 (%2)").arg(i).arg(formatTimestampForSaveStateMenu(sd.ModificationTime)));
connect(action, &QAction::triggered, [this, i]() { loadSaveStateSlot(i, true); });
has_any_states = true;
}
delete_save_states_action->setEnabled(has_any_states);
if (has_any_states)
{

View File

@ -267,13 +267,13 @@ private:
QString getDiscDevicePath(const QString& title);
void startGameListEntry(
const GameList::Entry* entry, std::optional<s32> save_slot = std::nullopt, std::optional<bool> fast_boot = std::nullopt);
const GameList::Entry* entry, std::optional<s32> save_slot = std::nullopt, std::optional<bool> fast_boot = std::nullopt, bool load_backup = false);
void setGameListEntryCoverImage(const GameList::Entry* entry);
void clearGameListEntryPlayTime(const GameList::Entry* entry);
void goToWikiPage(const GameList::Entry* entry);
std::optional<bool> promptForResumeState(const QString& save_state_path);
void loadSaveStateSlot(s32 slot);
void loadSaveStateSlot(s32 slot, bool load_backup = false);
void loadSaveStateFile(const QString& filename, const QString& state_filename);
void populateLoadStateMenu(QMenu* menu, const QString& filename, const QString& serial, quint32 crc);
void populateSaveStateMenu(QMenu* menu, const QString& serial, quint32 crc);

View File

@ -320,18 +320,18 @@ void EmuThread::loadState(const QString& filename)
VMManager::LoadState(filename.toUtf8().constData());
}
void EmuThread::loadStateFromSlot(qint32 slot)
void EmuThread::loadStateFromSlot(qint32 slot, bool load_backup)
{
if (!isOnEmuThread())
{
QMetaObject::invokeMethod(this, "loadStateFromSlot", Qt::QueuedConnection, Q_ARG(qint32, slot));
QMetaObject::invokeMethod(this, "loadStateFromSlot", Qt::QueuedConnection, Q_ARG(qint32, slot), Q_ARG(bool, load_backup));
return;
}
if (!VMManager::HasValidVM())
return;
VMManager::LoadStateFromSlot(slot);
VMManager::LoadStateFromSlot(slot, load_backup);
}
void EmuThread::saveState(const QString& filename)

View File

@ -87,7 +87,7 @@ public Q_SLOTS:
void setVMPaused(bool paused);
void shutdownVM(bool save_state = true);
void loadState(const QString& filename);
void loadStateFromSlot(qint32 slot);
void loadStateFromSlot(qint32 slot, bool load_backup = false);
void saveState(const QString& filename);
void saveStateToSlot(qint32 slot);
void toggleFullscreen();

View File

@ -244,6 +244,11 @@ DEFINE_HOTKEY("LoadStateFromSlot", TRANSLATE_NOOP("Hotkeys", "Save States"),
if (!pressed && VMManager::HasValidVM())
SaveStateSelectorUI::LoadCurrentSlot();
})
DEFINE_HOTKEY("LoadBackupStateFromSlot", TRANSLATE_NOOP("Hotkeys", "Save States"),
TRANSLATE_NOOP("Hotkeys", "Load Backup State From Selected Slot"), [](s32 pressed) {
if (!pressed && VMManager::HasValidVM())
SaveStateSelectorUI::LoadCurrentBackupSlot();
})
DEFINE_HOTKEY("SaveStateAndSelectNextSlot", TRANSLATE_NOOP("Hotkeys", "Save States"),
TRANSLATE_NOOP("Hotkeys", "Save State and Select Next Slot"), [](s32 pressed) {
if (!pressed && VMManager::HasValidVM())

View File

@ -446,7 +446,7 @@ namespace FullscreenUI
static void InitializePlaceholderSaveStateListEntry(SaveStateListEntry* li, s32 slot);
static bool InitializeSaveStateListEntry(
SaveStateListEntry* li, const std::string& title, const std::string& serial, u32 crc, s32 slot);
SaveStateListEntry* li, const std::string& title, const std::string& serial, u32 crc, s32 slot, bool backup = false);
static void ClearSaveStateEntryList();
static u32 PopulateSaveStateListEntries(const std::string& title, const std::string& serial, u32 crc);
static bool OpenLoadStateSelectorForGame(const std::string& game_path);
@ -5671,9 +5671,9 @@ void FullscreenUI::InitializePlaceholderSaveStateListEntry(SaveStateListEntry* l
}
bool FullscreenUI::InitializeSaveStateListEntry(
SaveStateListEntry* li, const std::string& title, const std::string& serial, u32 crc, s32 slot)
SaveStateListEntry* li, const std::string& title, const std::string& serial, u32 crc, s32 slot, bool backup)
{
std::string filename(VMManager::GetSaveStateFileName(serial.c_str(), crc, slot));
std::string filename(VMManager::GetSaveStateFileName(serial.c_str(), crc, slot, backup));
FILESYSTEM_STAT_DATA sd;
if (filename.empty() || !FileSystem::StatFile(filename.c_str(), &sd))
{
@ -5681,7 +5681,7 @@ bool FullscreenUI::InitializeSaveStateListEntry(
return false;
}
li->title = fmt::format("{}##game_slot_{}", TinyString::from_format(FSUI_FSTR("Save Slot {0}"), slot), slot);
li->title = fmt::format("{}##game_slot_{}", TinyString::from_format(FSUI_FSTR("{0} Slot {1}"), backup ? "Backup Save" : "Save", slot), slot);
li->summary = fmt::format(FSUI_FSTR("Saved {}"), TimeToPrintableString(sd.ModificationTime));
li->slot = slot;
li->timestamp = sd.ModificationTime;
@ -5726,6 +5726,10 @@ u32 FullscreenUI::PopulateSaveStateListEntries(const std::string& title, const s
SaveStateListEntry li;
if (InitializeSaveStateListEntry(&li, title, serial, crc, i) || !s_save_state_selector_loading)
s_save_state_selector_slots.push_back(std::move(li));
SaveStateListEntry bli;
if (InitializeSaveStateListEntry(&bli, title, serial, crc, i, true) || !s_save_state_selector_loading)
s_save_state_selector_slots.push_back(std::move(bli));
}
return static_cast<u32>(s_save_state_selector_slots.size());

View File

@ -1107,6 +1107,14 @@ void SaveStateSelectorUI::LoadCurrentSlot()
Close();
}
void SaveStateSelectorUI::LoadCurrentBackupSlot()
{
Host::RunOnCPUThread([slot = GetCurrentSlot()]() {
VMManager::LoadStateFromSlot(slot, true);
});
Close();
}
void SaveStateSelectorUI::SaveCurrentSlot()
{
Host::RunOnCPUThread([slot = GetCurrentSlot()]() {

View File

@ -26,6 +26,7 @@ namespace SaveStateSelectorUI
s32 GetCurrentSlot();
void LoadCurrentSlot();
void LoadCurrentBackupSlot();
void SaveCurrentSlot();
} // namespace SaveStateSelectorUI

View File

@ -111,7 +111,7 @@ namespace VMManager
static bool HasValidOrInitializingVM();
static void PrecacheCDVDFile();
static std::string GetCurrentSaveStateFileName(s32 slot);
static std::string GetCurrentSaveStateFileName(s32 slot, bool backup = false);
static bool DoLoadState(const char* filename);
static bool DoSaveState(const char* filename, s32 slot_for_message, bool zip_on_thread, bool backup_old_state);
static void ZipSaveState(std::unique_ptr<ArchiveEntryList> elist,
@ -1762,13 +1762,15 @@ bool SaveStateBase::vmFreeze()
return IsOkay();
}
std::string VMManager::GetSaveStateFileName(const char* game_serial, u32 game_crc, s32 slot)
std::string VMManager::GetSaveStateFileName(const char* game_serial, u32 game_crc, s32 slot, bool backup)
{
std::string filename;
if (std::strlen(game_serial) > 0)
{
if (slot < 0)
filename = fmt::format("{} ({:08X}).resume.p2s", game_serial, game_crc);
else if (backup)
filename = fmt::format("{} ({:08X}).{:02d}.p2s.backup", game_serial, game_crc, slot);
else
filename = fmt::format("{} ({:08X}).{:02d}.p2s", game_serial, game_crc, slot);
@ -1778,7 +1780,7 @@ std::string VMManager::GetSaveStateFileName(const char* game_serial, u32 game_cr
return filename;
}
std::string VMManager::GetSaveStateFileName(const char* filename, s32 slot)
std::string VMManager::GetSaveStateFileName(const char* filename, s32 slot, bool backup)
{
pxAssertRel(!HasValidVM(), "Should not have a VM when calling the non-gamelist GetSaveStateFileName()");
@ -1786,7 +1788,7 @@ std::string VMManager::GetSaveStateFileName(const char* filename, s32 slot)
std::string serial;
u32 crc;
if (GameList::GetSerialAndCRCForFilename(filename, &serial, &crc))
ret = GetSaveStateFileName(serial.c_str(), crc, slot);
ret = GetSaveStateFileName(serial.c_str(), crc, slot, backup);
return ret;
}
@ -1797,10 +1799,10 @@ bool VMManager::HasSaveStateInSlot(const char* game_serial, u32 game_crc, s32 sl
return (!filename.empty() && FileSystem::FileExists(filename.c_str()));
}
std::string VMManager::GetCurrentSaveStateFileName(s32 slot)
std::string VMManager::GetCurrentSaveStateFileName(s32 slot, bool backup)
{
std::unique_lock lock(s_info_mutex);
return GetSaveStateFileName(s_disc_serial.c_str(), s_disc_crc, slot);
return GetSaveStateFileName(s_disc_serial.c_str(), s_disc_crc, slot, backup);
}
bool VMManager::DoLoadState(const char* filename)
@ -1840,7 +1842,7 @@ bool VMManager::DoSaveState(const char* filename, s32 slot_for_message, bool zip
if (!elist)
{
Host::AddIconOSDMessage(std::move(osd_key), ICON_FA_EXCLAMATION_TRIANGLE,
fmt::format(TRANSLATE_FS("VMManager", "Failed to save save state: {}."), error.GetDescription()),
fmt::format(TRANSLATE_FS("VMManager", "Failed to save state: {}."), error.GetDescription()),
Host::OSD_ERROR_DURATION);
return false;
}
@ -1895,7 +1897,7 @@ void VMManager::ZipSaveState(std::unique_ptr<ArchiveEntryList> elist,
else
{
Host::AddIconOSDMessage(std::move(osd_key), ICON_FA_EXCLAMATION_TRIANGLE,
fmt::format(TRANSLATE_FS("VMManager", "Failed to save save state to slot {}."), slot_for_message,
fmt::format(TRANSLATE_FS("VMManager", "Failed to save state to slot {}."), slot_for_message,
Host::OSD_ERROR_DURATION));
}
@ -1987,13 +1989,13 @@ bool VMManager::LoadState(const char* filename)
return false;
}
bool VMManager::LoadStateFromSlot(s32 slot)
bool VMManager::LoadStateFromSlot(s32 slot, bool backup)
{
const std::string filename = GetCurrentSaveStateFileName(slot);
const std::string filename = GetCurrentSaveStateFileName(slot, backup);
if (filename.empty() || !FileSystem::FileExists(filename.c_str()))
{
Host::AddIconOSDMessage("LoadStateFromSlot", ICON_FA_EXCLAMATION_TRIANGLE,
fmt::format(TRANSLATE_FS("VMManager", "There is no save state in slot {}."), slot),
fmt::format(TRANSLATE_FS("VMManager", "There is no saved {} in slot {}."), backup ? TRANSLATE("VMManager", "backup state") : "state", slot),
Host::OSD_QUICK_DURATION);
return false;
}
@ -2011,13 +2013,13 @@ bool VMManager::LoadStateFromSlot(s32 slot)
if (MemcardBusy::IsBusy())
{
Host::AddIconOSDMessage("LoadStateFromSlot", ICON_FA_EXCLAMATION_TRIANGLE,
fmt::format(TRANSLATE_FS("VMManager", "Failed to load state from slot {} (Memory card is busy)"), slot),
fmt::format(TRANSLATE_FS("VMManager", "Failed to load {} from slot {} (Memory card is busy)"), backup ? TRANSLATE("VMManager", "backup state") : TRANSLATE("VMManager", "state"), slot),
Host::OSD_QUICK_DURATION);
return false;
}
Host::AddIconOSDMessage("LoadStateFromSlot", ICON_FA_FOLDER_OPEN,
fmt::format(TRANSLATE_FS("VMManager", "Loading state from slot {}..."), slot), Host::OSD_QUICK_DURATION);
fmt::format(TRANSLATE_FS("VMManager", "Loading {} from slot {}..."), backup ? TRANSLATE("VMManager", "backup state") : TRANSLATE("VMManager", "state"), slot), Host::OSD_QUICK_DURATION);
return DoLoadState(filename.c_str());
}
@ -2594,7 +2596,7 @@ void VMManager::ShutdownCPUProviders()
#else
// See the comment in the InitializeCPUProviders for an explaination why we
// still need to manage the MTVU thread.
if(vu1Thread.IsOpen())
if (vu1Thread.IsOpen())
vu1Thread.WaitVU();
#endif
}

View File

@ -118,10 +118,10 @@ namespace VMManager
void ReloadInputBindings(bool force = false);
/// Returns the save state filename for the given game serial/crc.
std::string GetSaveStateFileName(const char* game_serial, u32 game_crc, s32 slot);
std::string GetSaveStateFileName(const char* game_serial, u32 game_crc, s32 slot, bool backup = false);
/// Returns the path to save state for the specified disc/elf.
std::string GetSaveStateFileName(const char* filename, s32 slot);
std::string GetSaveStateFileName(const char* filename, s32 slot, bool backup = false);
/// Returns true if there is a save state in the specified slot.
bool HasSaveStateInSlot(const char* game_serial, u32 game_crc, s32 slot);
@ -130,7 +130,7 @@ namespace VMManager
bool LoadState(const char* filename);
/// Loads state from the specified slot.
bool LoadStateFromSlot(s32 slot);
bool LoadStateFromSlot(s32 slot, bool backup = false);
/// Saves state to the specified filename.
bool SaveState(const char* filename, bool zip_on_thread = true, bool backup_old_state = false);