From ba702509c85f65ef2375f4a77c77ea91abd95216 Mon Sep 17 00:00:00 2001 From: Megamouse Date: Thu, 23 Jan 2025 00:30:53 +0100 Subject: [PATCH] Qt: Add Recent savestates menu --- rpcs3/Emu/System.cpp | 57 +------- rpcs3/Emu/savestate_utils.cpp | 63 +++++++++ rpcs3/Emu/savestate_utils.hpp | 14 +- rpcs3/rpcs3qt/gui_settings.h | 3 + rpcs3/rpcs3qt/main_window.cpp | 214 ++++++++++++++++++++----------- rpcs3/rpcs3qt/main_window.h | 15 ++- rpcs3/rpcs3qt/main_window.ui | 19 +++ rpcs3/util/serialization_ext.hpp | 1 - 8 files changed, 248 insertions(+), 138 deletions(-) diff --git a/rpcs3/Emu/System.cpp b/rpcs3/Emu/System.cpp index 0bb4311b64..1423798366 100644 --- a/rpcs3/Emu/System.cpp +++ b/rpcs3/Emu/System.cpp @@ -1117,47 +1117,7 @@ game_boot_result Emulator::Load(const std::string& title_id, bool is_disc_patch, } else { - fs::file save{m_path, fs::isfile + fs::read}; - - if (m_path.ends_with(".SAVESTAT") && save && save.size() >= 8 && save.read() == "RPCS3SAV"_u64) - { - m_ar = std::make_shared(); - m_ar->set_reading_state(); - - m_ar->m_file_handler = make_uncompressed_serialization_file_handler(std::move(save)); - } - else if (save && m_path.ends_with(".zst")) - { - m_ar = std::make_shared(); - m_ar->set_reading_state(); - - m_ar->m_file_handler = make_compressed_zstd_serialization_file_handler(std::move(save)); - - if (m_ar->try_read().second != "RPCS3SAV"_u64) - { - m_ar.reset(); - } - else - { - m_ar->pos = 0; - } - } - else if (save && m_path.ends_with(".gz")) - { - m_ar = std::make_shared(); - m_ar->set_reading_state(); - - m_ar->m_file_handler = make_compressed_serialization_file_handler(std::move(save)); - - if (m_ar->try_read().second != "RPCS3SAV"_u64) - { - m_ar.reset(); - } - else - { - m_ar->pos = 0; - } - } + m_ar = make_savestate_reader(m_path); m_boot_source_type = CELL_GAME_GAMETYPE_SYS; } @@ -1189,18 +1149,7 @@ game_boot_result Emulator::Load(const std::string& title_id, bool is_disc_patch, if (m_ar) { - struct file_header - { - ENABLE_BITWISE_SERIALIZATION; - - nse_t magic; - bool LE_format; - bool state_inspection_support; - nse_t offset; - b8 flag_versions_is_following_data; - }; - - const auto header = m_ar->try_read().second; + const auto header = m_ar->try_read().second; if (header.magic != "RPCS3SAV"_u64) { @@ -1329,7 +1278,7 @@ game_boot_result Emulator::Load(const std::string& title_id, bool is_disc_patch, if (m_ar->m_max_data != m_ar->pos) { - fmt::throw_exception("TAR desrialization failed: read bytes: 0x%x, expected: 0x%x, path='%s', ar: %s", m_ar->pos - (m_ar->m_max_data - size), size, path, *m_ar); + fmt::throw_exception("TAR deserialization failed: read bytes: 0x%x, expected: 0x%x, path='%s', ar: %s", m_ar->pos - (m_ar->m_max_data - size), size, path, *m_ar); } m_ar->m_max_data = umax; diff --git a/rpcs3/Emu/savestate_utils.cpp b/rpcs3/Emu/savestate_utils.cpp index bb3e440564..c1b5c953fb 100644 --- a/rpcs3/Emu/savestate_utils.cpp +++ b/rpcs3/Emu/savestate_utils.cpp @@ -162,6 +162,60 @@ std::vector get_savestate_versioning_data(fs::file&& file, std::s return ver_data; } +std::shared_ptr make_savestate_reader(const std::string& path) +{ + std::shared_ptr ar; + + fs::file save{path, fs::isfile + fs::read}; + + if (!save) + { + return ar; + } + + if (path.ends_with(".SAVESTAT") && save.size() >= 8 && save.read() == "RPCS3SAV"_u64) + { + ar = std::make_shared(); + ar->set_reading_state(); + + ar->m_file_handler = make_uncompressed_serialization_file_handler(std::move(save)); + } + else if (path.ends_with(".zst")) + { + ar = std::make_shared(); + ar->set_reading_state(); + + ar->m_file_handler = make_compressed_zstd_serialization_file_handler(std::move(save)); + + if (ar->try_read().second != "RPCS3SAV"_u64) + { + ar.reset(); + } + else + { + ar->pos = 0; + } + } + else if (path.ends_with(".gz")) + { + ar = std::make_shared(); + ar->set_reading_state(); + + ar->m_file_handler = make_compressed_serialization_file_handler(std::move(save)); + + if (ar->try_read().second != "RPCS3SAV"_u64) + { + ar.reset(); + } + else + { + ar->pos = 0; + } + } + + return ar; +} + bool is_savestate_version_compatible(const std::vector& data, bool is_boot_check) { if (data.empty()) @@ -256,6 +310,15 @@ bool is_savestate_compatible(fs::file&& file, std::string_view filepath) return is_savestate_version_compatible(get_savestate_versioning_data(std::move(file), filepath), false); } +bool is_savestate_compatible(const std::string& filepath) +{ + if (fs::file file{filepath, fs::isfile + fs::read}) + { + return is_savestate_compatible(std::move(file), filepath); + } + return false; +} + std::vector read_used_savestate_versions() { std::vector used_serial; diff --git a/rpcs3/Emu/savestate_utils.hpp b/rpcs3/Emu/savestate_utils.hpp index 66d445369c..18028ae9ec 100644 --- a/rpcs3/Emu/savestate_utils.hpp +++ b/rpcs3/Emu/savestate_utils.hpp @@ -10,6 +10,16 @@ struct version_entry ENABLE_BITWISE_SERIALIZATION; }; +struct savestate_header +{ + ENABLE_BITWISE_SERIALIZATION; + + nse_t magic; + bool LE_format; + bool state_inspection_support; + nse_t offset; + b8 flag_versions_is_following_data; +}; struct hle_locks_t { @@ -27,9 +37,11 @@ struct hle_locks_t bool try_finalize(std::function test); }; +std::shared_ptr make_savestate_reader(const std::string& path); bool load_and_check_reserved(utils::serial& ar, usz size); bool is_savestate_version_compatible(const std::vector& data, bool is_boot_check); std::vector get_savestate_versioning_data(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); std::vector 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); \ No newline at end of file +std::string get_savestate_file(std::string_view title_id, std::string_view boot_path, s64 abs_id, s64 rel_id); diff --git a/rpcs3/rpcs3qt/gui_settings.h b/rpcs3/rpcs3qt/gui_settings.h index 82c6770d00..81cfe6bcc1 100644 --- a/rpcs3/rpcs3qt/gui_settings.h +++ b/rpcs3/rpcs3qt/gui_settings.h @@ -120,6 +120,9 @@ namespace gui const gui_save rg_freeze = gui_save(main_window, "recentGamesFrozen", false); const gui_save rg_entries = gui_save(main_window, "recentGamesNames", QVariant::fromValue(q_pair_list())); + const gui_save rs_freeze = gui_save(main_window, "recentSavestatesFrozen", false); + const gui_save rs_entries = gui_save(main_window, "recentSavestatesNames", QVariant::fromValue(q_pair_list())); + const gui_save ib_skip_version = gui_save(main_window, "infoBoxSkipVersion", ""); const gui_save ib_pkg_success = gui_save(main_window, "infoBoxEnabledInstallPKG", true); const gui_save ib_pup_success = gui_save(main_window, "infoBoxEnabledInstallPUP", true); diff --git a/rpcs3/rpcs3qt/main_window.cpp b/rpcs3/rpcs3qt/main_window.cpp index e23d9b32bc..e4cccd3307 100644 --- a/rpcs3/rpcs3qt/main_window.cpp +++ b/rpcs3/rpcs3qt/main_window.cpp @@ -63,6 +63,7 @@ #include "Emu/System.h" #include "Emu/system_utils.hpp" #include "Emu/system_config.h" +#include "Emu/savestate_utils.hpp" #include "Crypto/unpkg.h" #include "Crypto/unself.h" @@ -234,9 +235,9 @@ bool main_window::Init([[maybe_unused]] bool with_cli_boot) show(); // needs to be done before creating the thumbnail toolbar // enable play options if a recent game exists - const bool enable_play_last = !m_recent_game_acts.isEmpty() && m_recent_game_acts.first(); + const bool enable_play_last = !m_recent_game.actions.isEmpty() && m_recent_game.actions.first(); - const QString start_tooltip = enable_play_last ? tr("Play %0").arg(m_recent_game_acts.first()->text()) : tr("Play"); + const QString start_tooltip = enable_play_last ? tr("Play %0").arg(m_recent_game.actions.first()->text()) : tr("Play"); if (enable_play_last) { @@ -510,9 +511,9 @@ void main_window::OnPlayOrPause() show_boot_error(error); } } - else if (!m_recent_game_acts.isEmpty()) + else if (!m_recent_game.actions.isEmpty()) { - BootRecentAction(m_recent_game_acts.first()); + BootRecentAction(m_recent_game.actions.first(), false); } return; @@ -607,7 +608,7 @@ void main_window::Boot(const std::string& path, const std::string& title_id, boo { gui_log.success("Boot successful."); - AddRecentAction(gui::Recent_Game(qstr(Emu.GetBoot()), qstr(Emu.GetTitleAndTitleID()))); + AddRecentAction(gui::Recent_Game(qstr(Emu.GetBoot()), qstr(Emu.GetTitleAndTitleID())), is_savestate_compatible(path)); if (refresh_list) { @@ -2190,55 +2191,59 @@ void main_window::OnEnableDiscInsert(bool enabled) const ui->insertDiscAct->setEnabled(enabled); } -void main_window::BootRecentAction(const QAction* act) +void main_window::BootRecentAction(const QAction* act, bool is_savestate) { if (Emu.IsRunning()) { return; } + recent_game_wrapper& rgw = is_savestate ? m_recent_save : m_recent_game; + QMenu* menu = is_savestate ? ui->bootRecentSavestatesMenu : ui->bootRecentMenu; + const QString pth = act->data().toString(); - const std::string path = sstr(pth); + const std::string path = pth.toStdString(); QString name; bool contains_path = false; int idx = -1; - for (int i = 0; i < m_rg_entries.count(); i++) + for (int i = 0; i < rgw.entries.count(); i++) { - if (::at32(m_rg_entries, i).first == pth) + const auto& entry = rgw.entries[i]; + if (entry.first == pth) { idx = i; contains_path = true; - name = ::at32(m_rg_entries, idx).second; + name = entry.second; break; } } // path is invalid: remove action from list return - if ((contains_path && name.isEmpty()) || (!QFileInfo(pth).isDir() && !QFileInfo(pth).isFile())) + if ((contains_path && name.isEmpty()) || !fs::exists(path)) { if (contains_path) { // clear menu of actions - for (QAction* action : m_recent_game_acts) + for (QAction* action : rgw.actions) { - ui->bootRecentMenu->removeAction(action); + menu->removeAction(action); } // remove action from list - m_rg_entries.removeAt(idx); - m_recent_game_acts.removeAt(idx); + rgw.entries.removeAt(idx); + rgw.actions.removeAt(idx); - m_gui_settings->SetValue(gui::rg_entries, gui_settings::List2Var(m_rg_entries)); + m_gui_settings->SetValue(is_savestate ? gui::rs_entries : gui::rg_entries, gui_settings::List2Var(rgw.entries)); gui_log.error("Recent Game not valid, removed from Boot Recent list: %s", path); // refill menu with actions - for (int i = 0; i < m_recent_game_acts.count(); i++) + for (int i = 0; i < rgw.actions.count(); i++) { - m_recent_game_acts[i]->setShortcut(tr("Ctrl+%1").arg(i + 1)); - m_recent_game_acts[i]->setToolTip(::at32(m_rg_entries, i).second); - ui->bootRecentMenu->addAction(m_recent_game_acts[i]); + rgw.actions[i]->setShortcut(tr("Ctrl+%1").arg(i + 1)); + rgw.actions[i]->setToolTip(::at32(rgw.entries, i).second); + menu->addAction(rgw.actions[i]); } gui_log.warning("Boot Recent list refreshed"); @@ -2253,19 +2258,21 @@ void main_window::BootRecentAction(const QAction* act) Boot(path, "", true); } -QAction* main_window::CreateRecentAction(const q_string_pair& entry, const uint& sc_idx) +QAction* main_window::CreateRecentAction(const q_string_pair& entry, u32 sc_idx, bool is_savestate) { + recent_game_wrapper& rgw = is_savestate ? m_recent_save : m_recent_game; + // if path is not valid remove from list if (entry.second.isEmpty() || (!QFileInfo(entry.first).isDir() && !QFileInfo(entry.first).isFile())) { - if (m_rg_entries.contains(entry)) + if (rgw.entries.contains(entry)) { gui_log.warning("Recent Game not valid, removing from Boot Recent list: %s", entry.first); - const int idx = m_rg_entries.indexOf(entry); - m_rg_entries.removeAt(idx); + const int idx = rgw.entries.indexOf(entry); + rgw.entries.removeAt(idx); - m_gui_settings->SetValue(gui::rg_entries, gui_settings::List2Var(m_rg_entries)); + m_gui_settings->SetValue(is_savestate ? gui::rs_entries : gui::rg_entries, gui_settings::List2Var(rgw.entries)); } return nullptr; } @@ -2290,69 +2297,74 @@ QAction* main_window::CreateRecentAction(const q_string_pair& entry, const uint& } // connect boot - connect(act, &QAction::triggered, this, [act, this]() {BootRecentAction(act); }); + connect(act, &QAction::triggered, this, [this, act, is_savestate](){ BootRecentAction(act, is_savestate); }); return act; } -void main_window::AddRecentAction(const q_string_pair& entry) +void main_window::AddRecentAction(const q_string_pair& entry, bool is_savestate) { + QAction* freezeAction = is_savestate ? ui->freezeRecentSavestatesAct : ui->freezeRecentAct; + // don't change list on freeze - if (ui->freezeRecentAct->isChecked()) + if (freezeAction->isChecked()) { return; } // create new action, return if not valid - QAction* act = CreateRecentAction(entry, 1); + QAction* act = CreateRecentAction(entry, 1, is_savestate); if (!act) { return; } + recent_game_wrapper& rgw = is_savestate ? m_recent_save : m_recent_game; + QMenu* menu = is_savestate ? ui->bootRecentSavestatesMenu : ui->bootRecentMenu; + // clear menu of actions - for (QAction* action : m_recent_game_acts) + for (QAction* action : rgw.actions) { - ui->bootRecentMenu->removeAction(action); + menu->removeAction(action); } // If path already exists, remove it in order to get it to beginning. Also remove duplicates. - for (int i = m_rg_entries.count() - 1; i >= 0; --i) + for (int i = rgw.entries.count() - 1; i >= 0; --i) { - if (m_rg_entries[i].first == entry.first) + if (rgw.entries[i].first == entry.first) { - m_rg_entries.removeAt(i); - m_recent_game_acts.removeAt(i); + rgw.entries.removeAt(i); + rgw.actions.removeAt(i); } } // remove oldest action at the end if needed - if (m_rg_entries.count() == 9) + if (rgw.entries.count() == 9) { - m_rg_entries.removeLast(); - m_recent_game_acts.removeLast(); + rgw.entries.removeLast(); + rgw.actions.removeLast(); } - else if (m_rg_entries.count() > 9) + else if (rgw.entries.count() > 9) { gui_log.error("Recent games entrylist too big"); } - if (m_rg_entries.count() < 9) + if (rgw.entries.count() < 9) { // add new action at the beginning - m_rg_entries.prepend(entry); - m_recent_game_acts.prepend(act); + rgw.entries.prepend(entry); + rgw.actions.prepend(act); } // refill menu with actions - for (int i = 0; i < m_recent_game_acts.count(); i++) + for (int i = 0; i < rgw.actions.count(); i++) { - m_recent_game_acts[i]->setShortcut(tr("Ctrl+%1").arg(i + 1)); - m_recent_game_acts[i]->setToolTip(::at32(m_rg_entries, i).second); - ui->bootRecentMenu->addAction(m_recent_game_acts[i]); + rgw.actions[i]->setShortcut(tr("Ctrl+%1").arg(i + 1)); + rgw.actions[i]->setToolTip(::at32(rgw.entries, i).second); + menu->addAction(rgw.actions[i]); } - m_gui_settings->SetValue(gui::rg_entries, gui_settings::List2Var(m_rg_entries)); + m_gui_settings->SetValue(is_savestate ? gui::rs_entries : gui::rg_entries, gui_settings::List2Var(rgw.entries)); } void main_window::UpdateLanguageActions(const QStringList& language_codes, const QString& language_code) @@ -2638,26 +2650,59 @@ void main_window::CreateConnects() } }); + connect(ui->bootRecentSavestatesMenu, &QMenu::aboutToShow, this, [this]() + { + // Enable/Disable Recent Savestates List + const bool stopped = Emu.IsStopped(); + for (QAction* act : ui->bootRecentSavestatesMenu->actions()) + { + if (act != ui->freezeRecentSavestatesAct && act != ui->clearRecentSavestatesAct) + { + act->setEnabled(stopped); + } + } + }); + connect(ui->clearRecentAct, &QAction::triggered, this, [this]() { if (ui->freezeRecentAct->isChecked()) { return; } - m_rg_entries.clear(); - for (QAction* act : m_recent_game_acts) + m_recent_game.entries.clear(); + for (QAction* act : m_recent_game.actions) { ui->bootRecentMenu->removeAction(act); } - m_recent_game_acts.clear(); + m_recent_game.actions.clear(); m_gui_settings->SetValue(gui::rg_entries, gui_settings::List2Var(q_pair_list())); }); + connect(ui->clearRecentSavestatesAct, &QAction::triggered, this, [this]() + { + if (ui->freezeRecentSavestatesAct->isChecked()) + { + return; + } + m_recent_save.entries.clear(); + for (QAction* act : m_recent_save.actions) + { + ui->bootRecentSavestatesMenu->removeAction(act); + } + m_recent_save.actions.clear(); + m_gui_settings->SetValue(gui::rs_entries, gui_settings::List2Var(q_pair_list())); + }); + connect(ui->freezeRecentAct, &QAction::triggered, this, [this](bool checked) { m_gui_settings->SetValue(gui::rg_freeze, checked); }); + connect(ui->freezeRecentSavestatesAct, &QAction::triggered, this, [this](bool checked) + { + m_gui_settings->SetValue(gui::rs_freeze, checked); + }); + connect(ui->bootInstallPkgAct, &QAction::triggered, this, [this] {InstallPackages(); }); connect(ui->bootInstallPupAct, &QAction::triggered, this, [this] {InstallPup(); }); @@ -3530,9 +3575,9 @@ void main_window::CreateDockWindows() ui->toolbar_start->setIcon(m_icon_restart); ui->toolbar_start->setText(tr("Restart")); } - else if (!m_recent_game_acts.isEmpty()) // Get last played game + else if (!m_recent_game.actions.isEmpty()) // Get last played game { - tooltip = tr("Play %0").arg(m_recent_game_acts.first()->text()); + tooltip = tr("Play %0").arg(m_recent_game.actions.first()->text()); } else { @@ -3584,35 +3629,45 @@ void main_window::ConfigureGuiFromSettings() m_mw->restoreState(m_gui_settings->GetValue(gui::mw_mwState).toByteArray()); ui->freezeRecentAct->setChecked(m_gui_settings->GetValue(gui::rg_freeze).toBool()); - m_rg_entries = gui_settings::Var2List(m_gui_settings->GetValue(gui::rg_entries)); + ui->freezeRecentSavestatesAct->setChecked(m_gui_settings->GetValue(gui::rs_freeze).toBool()); + m_recent_game.entries = gui_settings::Var2List(m_gui_settings->GetValue(gui::rg_entries)); + m_recent_save.entries = gui_settings::Var2List(m_gui_settings->GetValue(gui::rs_entries)); - // clear recent games menu of actions - for (QAction* act : m_recent_game_acts) + const auto update_recent_games_menu = [this](bool is_savestate) { - ui->bootRecentMenu->removeAction(act); - } - m_recent_game_acts.clear(); + recent_game_wrapper& rgw = is_savestate ? m_recent_save : m_recent_game; + QMenu* menu = is_savestate ? ui->bootRecentSavestatesMenu : ui->bootRecentMenu; - // Fill the recent games menu - for (int i = 0; i < m_rg_entries.count(); i++) - { - // adjust old unformatted entries (avoid duplication) - m_rg_entries[i] = gui::Recent_Game(m_rg_entries[i].first, m_rg_entries[i].second); - - // create new action - QAction* act = CreateRecentAction(m_rg_entries[i], i + 1); - - // add action to menu - if (act) + // clear recent games menu of actions + for (QAction* act : rgw.actions) { - m_recent_game_acts.append(act); - ui->bootRecentMenu->addAction(act); + menu->removeAction(act); } - else + rgw.actions.clear(); + + // Fill the recent games menu + for (int i = 0; i < rgw.entries.count(); i++) { - i--; // list count is now an entry shorter so we have to repeat the same index in order to load all other entries + // adjust old unformatted entries (avoid duplication) + rgw.entries[i] = gui::Recent_Game(rgw.entries[i].first, rgw.entries[i].second); + + // create new action + QAction* act = CreateRecentAction(rgw.entries[i], i + 1, is_savestate); + + // add action to menu + if (act) + { + rgw.actions.append(act); + menu->addAction(act); + } + else + { + i--; // list count is now an entry shorter so we have to repeat the same index in order to load all other entries + } } - } + }; + update_recent_games_menu(false); + update_recent_games_menu(true); ui->showLogAct->setChecked(m_gui_settings->GetValue(gui::mw_logger).toBool()); ui->showGameListAct->setChecked(m_gui_settings->GetValue(gui::mw_gamelist).toBool()); @@ -4147,14 +4202,19 @@ void main_window::dropEvent(QDropEvent* event) Emu.GracefulShutdown(false); - if (const auto error = Emu.BootGame(sstr(drop_paths.first()), "", true); error != game_boot_result::no_errors) + const std::string path = drop_paths.first().toStdString(); + + if (const auto error = Emu.BootGame(path, "", true); error != game_boot_result::no_errors) { - gui_log.error("Boot failed: reason: %s, path: %s", error, drop_paths.first()); + gui_log.error("Boot failed: reason: %s, path: %s", error, path); show_boot_error(error); } else { - gui_log.success("Elf Boot from drag and drop done: %s", drop_paths.first()); + gui_log.success("Elf Boot from drag and drop done: %s", path); + + AddRecentAction(gui::Recent_Game(QString::fromStdString(path), QString::fromStdString(Emu.GetTitleAndTitleID())), is_savestate_compatible(path)); + m_game_list_frame->Refresh(true); } break; diff --git a/rpcs3/rpcs3qt/main_window.h b/rpcs3/rpcs3qt/main_window.h index a2a8f25678..daaf2848aa 100644 --- a/rpcs3/rpcs3qt/main_window.h +++ b/rpcs3/rpcs3qt/main_window.h @@ -175,17 +175,22 @@ private: drop_type IsValidFile(const QMimeData& md, QStringList* drop_paths = nullptr); void AddGamesFromDirs(QStringList&& paths); - QAction* CreateRecentAction(const q_string_pair& entry, const uint& sc_idx); - void BootRecentAction(const QAction* act); - void AddRecentAction(const q_string_pair& entry); + QAction* CreateRecentAction(const q_string_pair& entry, u32 sc_idx, bool is_savestate); + void BootRecentAction(const QAction* act, bool is_savestate); + void AddRecentAction(const q_string_pair& entry, bool is_savestate); void UpdateLanguageActions(const QStringList& language_codes, const QString& language); void UpdateFilterActions(); static QString GetCurrentTitle(); - q_pair_list m_rg_entries; - QList m_recent_game_acts; + struct recent_game_wrapper + { + q_pair_list entries; + QList actions; + }; + recent_game_wrapper m_recent_game {}; + recent_game_wrapper m_recent_save {}; std::shared_ptr m_selected_game; diff --git a/rpcs3/rpcs3qt/main_window.ui b/rpcs3/rpcs3qt/main_window.ui index adb66d8bcf..3ddf7b8b1a 100644 --- a/rpcs3/rpcs3qt/main_window.ui +++ b/rpcs3/rpcs3qt/main_window.ui @@ -202,10 +202,19 @@ + + + Boot Recent Savestate + + + + + + @@ -1387,6 +1396,16 @@ Operating System + + + List Clear + + + + + List Freeze + + diff --git a/rpcs3/util/serialization_ext.hpp b/rpcs3/util/serialization_ext.hpp index 75b6212890..51071e7188 100644 --- a/rpcs3/util/serialization_ext.hpp +++ b/rpcs3/util/serialization_ext.hpp @@ -187,7 +187,6 @@ private: void initialize(utils::serial& ar); void stream_data_prepare_thread_op(); void file_writer_thread_op(); - void blocked_compressed_write(const std::vector& data); }; template requires (std::is_same_v, fs::file>)