diff --git a/Utilities/Thread.cpp b/Utilities/Thread.cpp index 20c2761e96df..f4d3292ac5b6 100644 --- a/Utilities/Thread.cpp +++ b/Utilities/Thread.cpp @@ -1635,7 +1635,7 @@ bool handle_access_violation(u32 addr, bool is_writing, ucontext_t* context) noe if (!g_tls_access_violation_recovered) { vm_log.notice("\n%s", dump_useful_thread_info()); - vm_log.error("Access violation %s location 0x%x (%s)", is_writing ? "writing" : "reading", addr, (is_writing && vm::check_addr(addr)) ? "read-only memory" : "unmapped memory"); + vm_log.error("[%s] Access violation %s location 0x%x (%s)", is_writing ? "writing" : "reading", cpu->get_name(), addr, (is_writing && vm::check_addr(addr)) ? "read-only memory" : "unmapped memory"); } // TODO: diff --git a/rpcs3/Emu/CPU/CPUThread.cpp b/rpcs3/Emu/CPU/CPUThread.cpp index 53844138471f..74f3f1e0bc09 100644 --- a/rpcs3/Emu/CPU/CPUThread.cpp +++ b/rpcs3/Emu/CPU/CPUThread.cpp @@ -791,6 +791,19 @@ bool cpu_thread::check_state() noexcept return store; } + if (s_tls_thread_slot == umax) + { + if (cpu_flag::wait - state) + { + // Force wait flag (must be set during ownership of s_cpu_lock), this makes the atomic op fail as a side effect + state += cpu_flag::wait; + store = true; + } + + // Restore thread in the suspend list + cpu_counter::add(this); + } + if (flags & cpu_flag::wait) { flags -= cpu_flag::wait; @@ -845,12 +858,6 @@ bool cpu_thread::check_state() noexcept if (escape) { - if (s_tls_thread_slot == umax && !retval) - { - // Restore thread in the suspend list - cpu_counter::add(this); - } - if (cpu_can_stop && state0 & cpu_flag::pending) { // Execute pending work @@ -999,7 +1006,7 @@ cpu_thread& cpu_thread::operator=(thread_state) { if (u32 resv = atomic_storage::load(thread->raddr)) { - vm::reservation_notifier(resv).notify_one(); + vm::reservation_notifier(resv).notify_all(-128); } } } diff --git a/rpcs3/Emu/Cell/Modules/cellGem.cpp b/rpcs3/Emu/Cell/Modules/cellGem.cpp index f789c0c0f66a..7b295e2d0e83 100644 --- a/rpcs3/Emu/Cell/Modules/cellGem.cpp +++ b/rpcs3/Emu/Cell/Modules/cellGem.cpp @@ -335,17 +335,7 @@ struct gem_config_data [[maybe_unused]] const s32 version = GET_OR_USE_SERIALIZATION_VERSION(ar.is_writing(), cellGem); ar(attribute, vc_attribute, status_flags, enable_pitch_correction, inertial_counter, controllers - , connected_controllers, update_started, camera_frame, memory_ptr); - - if (version == 1 && !ar.is_writing()) - { - u32 ts = ar; - start_timestamp_us = ts; - } - else - { - ar(start_timestamp_us); - } + , connected_controllers, update_started, camera_frame, memory_ptr, start_timestamp_us); } gem_config_data(utils::serial& ar) diff --git a/rpcs3/Emu/Cell/PPUAnalyser.cpp b/rpcs3/Emu/Cell/PPUAnalyser.cpp index 4efe2a52913e..ebda9c2580e8 100644 --- a/rpcs3/Emu/Cell/PPUAnalyser.cpp +++ b/rpcs3/Emu/Cell/PPUAnalyser.cpp @@ -78,7 +78,7 @@ void ppu_module::validate(u32 reloc) if (size && size != funcs[index].size) { - if (size + 4 != funcs[index].size || *ensure(get_ptr(addr + size)) != ppu_instructions::NOP()) + if (size + 4 != funcs[index].size || get_ref(addr + size) != ppu_instructions::NOP()) { ppu_validator.error("%s.yml : function size mismatch at 0x%x(size=0x%x) (0x%x, 0x%x)", path, found, funcs[index].size, addr, size); } @@ -733,7 +733,7 @@ bool ppu_module::analyse(u32 lib_toc, u32 entry, const u32 sec_end, const std::b // Register TOC from entry point if (entry && !lib_toc) { - lib_toc = *ensure(get_ptr(entry)) ? *ensure(get_ptr(entry + 4)) : *ensure(get_ptr(entry + 20)); + lib_toc = get_ref(entry) ? get_ref(entry + 4) : get_ref(entry + 20); } // Secondary attempt @@ -1425,7 +1425,7 @@ bool ppu_module::analyse(u32 lib_toc, u32 entry, const u32 sec_end, const std::b for (vm::cptr _ptr = vm::cast(block.first); _ptr.addr() < block.first + block.second;) { const u32 iaddr = _ptr.addr(); - const ppu_opcode_t op{*ensure(get_ptr(_ptr++))}; + const ppu_opcode_t op{get_ref(_ptr++)}; const ppu_itype::type type = s_ppu_itype.decode(op.opcode); if (type == ppu_itype::B || type == ppu_itype::BC) @@ -1499,7 +1499,7 @@ bool ppu_module::analyse(u32 lib_toc, u32 entry, const u32 sec_end, const std::b for (vm::cptr _ptr = vm::cast(start); _ptr.addr() < next;) { const u32 addr = _ptr.addr(); - const ppu_opcode_t op{*ensure(get_ptr(_ptr++))}; + const ppu_opcode_t op{get_ref(_ptr++)}; const ppu_itype::type type = s_ppu_itype.decode(op.opcode); if (type == ppu_itype::UNK) @@ -1641,7 +1641,7 @@ bool ppu_module::analyse(u32 lib_toc, u32 entry, const u32 sec_end, const std::b continue; } - const u32 target = *ensure(get_ptr(rel.addr)); + const u32 target = get_ref(rel.addr); if (target % 4 || target < start || target >= end) { @@ -1718,7 +1718,7 @@ bool ppu_module::analyse(u32 lib_toc, u32 entry, const u32 sec_end, const std::b for (; i_pos < lim; i_pos += 4) { - const u32 opc = *ensure(get_ptr(i_pos)); + const u32 opc = get_ref(i_pos); switch (auto type = s_ppu_itype.decode(opc)) { diff --git a/rpcs3/Emu/Cell/PPUAnalyser.h b/rpcs3/Emu/Cell/PPUAnalyser.h index 8c5330c2b309..cde7fb794967 100644 --- a/rpcs3/Emu/Cell/PPUAnalyser.h +++ b/rpcs3/Emu/Cell/PPUAnalyser.h @@ -6,6 +6,7 @@ #include #include "util/types.hpp" #include "util/endian.hpp" +#include "util/asm.hpp" #include "util/to_endian.hpp" #include "Utilities/bit_set.h" @@ -127,7 +128,7 @@ struct ppu_module const u32 seg_size = seg.size; const u32 seg_addr = seg.addr; - if (seg_size >= std::max(size_bytes, 1) && addr <= seg_addr + seg_size - size_bytes) + if (seg_size >= std::max(size_bytes, 1) && addr <= utils::align(seg_addr + seg_size, 0x10000) - size_bytes) { return reinterpret_cast*>(static_cast(seg.ptr) + (addr - seg_addr)); } @@ -148,6 +149,40 @@ struct ppu_module constexpr usz size_element = std::is_void_v ? 0 : sizeof(std::conditional_t, char, T>); return get_ptr(addr.addr(), u32{size_element}); } + + template + to_be_t& get_ref(u32 addr, + u32 line = __builtin_LINE(), + u32 col = __builtin_COLUMN(), + const char* file = __builtin_FILE(), + const char* func = __builtin_FUNCTION()) const + { + constexpr usz size_element = std::is_void_v ? 0 : sizeof(std::conditional_t, char, T>); + if (auto ptr = get_ptr(addr, u32{size_element})) + { + return *ptr; + } + + fmt::throw_exception("get_ref(): Failure! (addr=0x%x)%s", addr, src_loc{line, col, file, func}); + return *std::add_pointer_t>{}; + } + + template requires requires (const U& obj) { +obj.size() * 0; } + to_be_t& get_ref(U&& addr, + u32 line = __builtin_LINE(), + u32 col = __builtin_COLUMN(), + const char* file = __builtin_FILE(), + const char* func = __builtin_FUNCTION()) const + { + constexpr usz size_element = std::is_void_v ? 0 : sizeof(std::conditional_t, char, T>); + if (auto ptr = get_ptr(addr.addr(), u32{size_element})) + { + return *ptr; + } + + fmt::throw_exception("get_ref(): Failure! (addr=0x%x)%s", addr.addr(), src_loc{line, col, file, func}); + return *std::add_pointer_t>{}; + } }; struct main_ppu_module : public ppu_module diff --git a/rpcs3/Emu/Cell/PPUThread.cpp b/rpcs3/Emu/Cell/PPUThread.cpp index b86cde77b9ef..b7e5fa0ddec5 100644 --- a/rpcs3/Emu/Cell/PPUThread.cpp +++ b/rpcs3/Emu/Cell/PPUThread.cpp @@ -2030,21 +2030,7 @@ void ppu_thread::serialize_common(utils::serial& ar) { [[maybe_unused]] const s32 version = GET_OR_USE_SERIALIZATION_VERSION(ar.is_writing(), ppu); - ar(gpr, fpr, cr, fpscr.bits, lr, ctr, vrsave, cia, xer, sat, nj); - - if (ar.is_writing()) - { - ar(prio.load().all); - } - else if (version < 2) - { - prio.raw().all = 0; - prio.raw().prio = ar.operator s32(); - } - else - { - ar(prio.raw().all); - } + ar(gpr, fpr, cr, fpscr.bits, lr, ctr, vrsave, cia, xer, sat, nj, prio.raw().all); ar(optional_savestate_state, vr); diff --git a/rpcs3/Emu/Cell/lv2/sys_net.cpp b/rpcs3/Emu/Cell/lv2/sys_net.cpp index 9a547a7a0ac8..387cb3f623e9 100644 --- a/rpcs3/Emu/Cell/lv2/sys_net.cpp +++ b/rpcs3/Emu/Cell/lv2/sys_net.cpp @@ -259,10 +259,7 @@ lv2_socket::lv2_socket(utils::serial& ar, lv2_socket_type _type) const s32 version = GET_SERIALIZATION_VERSION(lv2_net); - if (version >= 2) - { - ar(so_rcvtimeo, so_sendtimeo); - } + ar(so_rcvtimeo, so_sendtimeo); lv2_id = idm::last_id(); diff --git a/rpcs3/Emu/Cell/lv2/sys_prx.cpp b/rpcs3/Emu/Cell/lv2/sys_prx.cpp index bfbf8c5fa12e..4cde5d52e92f 100644 --- a/rpcs3/Emu/Cell/lv2/sys_prx.cpp +++ b/rpcs3/Emu/Cell/lv2/sys_prx.cpp @@ -312,11 +312,7 @@ std::shared_ptr lv2_prx::load(utils::serial& ar) { std::basic_string loaded_flags, external_flags; - if (version >= 4) - { - ar(loaded_flags); - ar(external_flags); - } + ar(loaded_flags, external_flags); fs::file file{path.substr(0, path.size() - (offset ? fmt::format("_x%x", offset).size() : 0))}; @@ -328,21 +324,11 @@ std::shared_ptr lv2_prx::load(utils::serial& ar) prx->m_loaded_flags = std::move(loaded_flags); prx->m_external_loaded_flags = std::move(external_flags); - if (version == 2 && (state == PRX_STATE_STARTED || state == PRX_STATE_STARTING)) - { - prx->load_exports(); - } - - if (version >= 4 && state <= PRX_STATE_STARTED) + if (state <= PRX_STATE_STARTED) { prx->restore_exports(); } - if (version == 1) - { - prx->load_exports(); - } - ensure(prx); } else diff --git a/rpcs3/Emu/Cell/lv2/sys_spu.cpp b/rpcs3/Emu/Cell/lv2/sys_spu.cpp index d3cb8222398e..7ccbc6acb143 100644 --- a/rpcs3/Emu/Cell/lv2/sys_spu.cpp +++ b/rpcs3/Emu/Cell/lv2/sys_spu.cpp @@ -215,14 +215,7 @@ lv2_spu_group::lv2_spu_group(utils::serial& ar) noexcept { std::common_type_t prio{}; - if (GET_SERIALIZATION_VERSION(spu) < 3) - { - prio.prio = ar.operator s32(); - } - else - { - ar(prio.all); - } + ar(prio.all); return prio; }()) @@ -387,10 +380,7 @@ struct spu_limits_t spu_limits_t(utils::serial& ar) noexcept { - if (GET_SERIALIZATION_VERSION(spu) >= 2) - { - ar(max_raw, max_spu); - } + ar(max_raw, max_spu); } void save(utils::serial& ar) @@ -1407,7 +1397,7 @@ error_code sys_spu_thread_group_terminate(ppu_thread& ppu, u32 id, s32 value) if (prev_resv && prev_resv != resv) { // Batch reservation notifications if possible - vm::reservation_notifier(prev_resv).notify_all(); + vm::reservation_notifier(prev_resv).notify_all(-128); } prev_resv = resv; @@ -1417,7 +1407,7 @@ error_code sys_spu_thread_group_terminate(ppu_thread& ppu, u32 id, s32 value) if (prev_resv) { - vm::reservation_notifier(prev_resv).notify_all(); + vm::reservation_notifier(prev_resv).notify_all(-128); } group->exit_status = value; diff --git a/rpcs3/Emu/RSX/RSXThread.cpp b/rpcs3/Emu/RSX/RSXThread.cpp index 0b6b8efe9b1b..6f82e38a3949 100644 --- a/rpcs3/Emu/RSX/RSXThread.cpp +++ b/rpcs3/Emu/RSX/RSXThread.cpp @@ -506,13 +506,10 @@ namespace rsx ar(u32{0}); } } - else if (version > 1) + else if (u32 count = ar) { - if (u32 count = ar) - { - restore_fifo_count = count; - ar(restore_fifo_cmd); - } + restore_fifo_count = count; + ar(restore_fifo_cmd); } } diff --git a/rpcs3/Emu/System.cpp b/rpcs3/Emu/System.cpp index 5674623bcc88..c005e67371d1 100644 --- a/rpcs3/Emu/System.cpp +++ b/rpcs3/Emu/System.cpp @@ -82,7 +82,7 @@ extern std::pair, CellError> ppu_load_overlay(const extern bool ppu_load_rel_exec(const ppu_rel_object&); extern bool is_savestate_version_compatible(const std::vector>& data, bool is_boot_check); extern std::vector> read_used_savestate_versions(); -std::string get_savestate_path(std::string_view title_id, std::string_view boot_path); +std::string get_savestate_file(std::string_view title_id, std::string_view boot_path, s64 abs_id, s64 rel_id); extern void send_close_home_menu_cmds(); @@ -492,7 +492,7 @@ void Emulator::Init() make_path_verbose(fs::get_cache_dir() + "shaderlog/", false); make_path_verbose(fs::get_cache_dir() + "spu_progs/", false); - make_path_verbose(fs::get_cache_dir() + "/savestates/", false); + make_path_verbose(fs::get_parent_dir(get_savestate_file("NO_ID", "/NO_FILE", -1, -1)), false); make_path_verbose(fs::get_config_dir() + "captures/", false); make_path_verbose(fs::get_config_dir() + "sounds/", false); make_path_verbose(patch_engine::get_patches_path(), false); @@ -2180,7 +2180,7 @@ void Emulator::FixGuestTime() // 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) { - for (std::string old_path : std::initializer_list{m_ar ? m_path_old : "", m_title_id.empty() ? "" : get_savestate_path(m_title_id, m_path_old)}) + for (std::string old_path : std::initializer_list{m_ar ? m_path_old : "", m_title_id.empty() ? "" : get_savestate_file(m_title_id, m_path_old, 0, 0)}) { if (old_path.empty()) { @@ -2841,7 +2841,12 @@ void Emulator::Kill(bool allow_autoexit, bool savestate) if (savestate) { - const std::string path = get_savestate_path(m_title_id, m_path); + const std::string path = get_savestate_file(m_title_id, m_path, 0, 0); + + if (!fs::create_path(fs::get_parent_dir(path))) + { + sys_log.error("Failed to create savestate directory! (path='%s', %s)", fs::get_parent_dir(path), fs::g_tls_error); + } fs::pending_file file(path); diff --git a/rpcs3/Emu/savestate_utils.cpp b/rpcs3/Emu/savestate_utils.cpp index 54175aba6a6f..e76bff203fbd 100644 --- a/rpcs3/Emu/savestate_utils.cpp +++ b/rpcs3/Emu/savestate_utils.cpp @@ -35,20 +35,20 @@ static std::array s_serial_versions; return ::s_serial_versions[identifier].current_version;\ } -SERIALIZATION_VER(global_version, 0, 12) // For stuff not listed here -SERIALIZATION_VER(ppu, 1, 1, 2 /*thread sleep queue order*/) -SERIALIZATION_VER(spu, 2, 1, 2 /*spu_limits_t ctor*/, 3 /*thread sleep queue order*/) +SERIALIZATION_VER(global_version, 0, 13) // 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, 2/*RECV/SEND timeout*/) +SERIALIZATION_VER(lv2_net, 5, 1) SERIALIZATION_VER(lv2_fs, 6, 1) -SERIALIZATION_VER(lv2_prx_overlay, 7, 1, 2/*PRX dynamic exports*/, 4/*Conditionally Loaded Local Exports*/) +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) + SERIALIZATION_VER(rsx, 10, 1) } namespace np @@ -65,7 +65,7 @@ SERIALIZATION_VER(sceNp, 11) SERIALIZATION_VER(cellVdec, 12, 1) SERIALIZATION_VER(cellAudio, 13, 1) SERIALIZATION_VER(cellCamera, 14, 1) -SERIALIZATION_VER(cellGem, 15, 1, 2/*frame_timestamp u32->u64*/) +SERIALIZATION_VER(cellGem, 15, 1) SERIALIZATION_VER(sceNpTrophy, 16, 1) SERIALIZATION_VER(cellMusic, 17, 1) SERIALIZATION_VER(cellVoice, 18, 1) @@ -146,9 +146,25 @@ bool is_savestate_version_compatible(const std::vector>& dat return ok; } -std::string get_savestate_path(std::string_view title_id, std::string_view boot_path) +std::string get_savestate_file(std::string_view title_id, std::string_view boot_path, s64 abs_id, s64 rel_id) { - return fs::get_cache_dir() + "/savestates/" + std::string{title_id.empty() ? boot_path.substr(boot_path.find_last_of(fs::delim) + 1) : title_id} + ".SAVESTAT"; + 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) + { + // Return directory + return fs::get_cache_dir() + "/savestates/" + title + "/"; + } + + ensure(rel_id < 0 || abs_id >= 0, "Unimplemented!"); + + const std::string save_id = fmt::format("%d", abs_id); + + // Make sure that savestate file with higher IDs are placed at the bottom of "by name" file ordering in directory view by adding a single character prefix which tells the ID length + // While not needing to keep a 59 chars long suffix at all times for this purpose + const char prefix = ::at32("0123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"sv, save_id.size()); + + return fs::get_cache_dir() + "/savestates/" + title + "/" + title + '_' + prefix + '_' + save_id + ".SAVESTAT"; } bool is_savestate_compatible(const fs::file& file) diff --git a/rpcs3/rpcs3qt/game_list_frame.cpp b/rpcs3/rpcs3qt/game_list_frame.cpp index 76ec618526a2..4acf9649daf4 100644 --- a/rpcs3/rpcs3qt/game_list_frame.cpp +++ b/rpcs3/rpcs3qt/game_list_frame.cpp @@ -50,6 +50,8 @@ LOG_CHANNEL(sys_log, "SYS"); extern atomic_t 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); + inline std::string sstr(const QString& _in) { return _in.toStdString(); } game_list_frame::game_list_frame(std::shared_ptr gui_settings, std::shared_ptr emu_settings, std::shared_ptr persistent_settings, QWidget* parent) @@ -1010,7 +1012,12 @@ void game_list_frame::ShowContextMenu(const QPoint &pos) // Make Actions QMenu menu; - const bool is_current_running_game = (Emu.IsRunning() || Emu.IsPaused()) && current_game.serial == Emu.GetTitleID(); + static const auto is_game_running = [](const std::string& serial) + { + return Emu.GetStatus(false) != system_state::stopped && serial == Emu.GetTitleID(); + }; + + const bool is_current_running_game = is_game_running(current_game.serial); QAction* boot = new QAction(gameinfo->hasCustomConfig ? (is_current_running_game @@ -1073,7 +1080,7 @@ void game_list_frame::ShowContextMenu(const QPoint &pos) 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))) + if (const std::string sstate = get_savestate_file(current_game.serial, current_game.path, 0, 0); is_savestate_compatible(fs::file(sstate))) { QAction* boot_state = menu.addAction(is_current_running_game ? tr("&Reboot with savestate") @@ -1119,6 +1126,7 @@ void game_list_frame::ShowContextMenu(const QPoint &pos) menu.addSeparator(); QMenu* remove_menu = menu.addMenu(tr("&Remove")); QAction* remove_game = remove_menu->addAction(tr("&Remove %1").arg(gameinfo->localized_category)); + remove_game->setEnabled(!is_current_running_game); if (gameinfo->hasCustomConfig) { QAction* remove_custom_config = remove_menu->addAction(tr("&Remove Custom Configuration")); @@ -1141,27 +1149,63 @@ void game_list_frame::ShowContextMenu(const QPoint &pos) } }); } - if (fs::is_dir(cache_base_dir)) + + const bool has_cache_dir = fs::is_dir(cache_base_dir); + + if (has_cache_dir) { remove_menu->addSeparator(); QAction* remove_shaders_cache = remove_menu->addAction(tr("&Remove Shaders Cache")); + remove_shaders_cache->setEnabled(!is_current_running_game); connect(remove_shaders_cache, &QAction::triggered, [this, cache_base_dir]() { RemoveShadersCache(cache_base_dir, true); }); QAction* remove_ppu_cache = remove_menu->addAction(tr("&Remove PPU Cache")); + remove_ppu_cache->setEnabled(!is_current_running_game); connect(remove_ppu_cache, &QAction::triggered, [this, cache_base_dir]() { RemovePPUCache(cache_base_dir, true); }); QAction* remove_spu_cache = remove_menu->addAction(tr("&Remove SPU Cache")); + remove_spu_cache->setEnabled(!is_current_running_game); connect(remove_spu_cache, &QAction::triggered, [this, cache_base_dir]() { RemoveSPUCache(cache_base_dir, true); }); + } + + bool has_hdd1_cache = false; + const std::string hdd1 = rpcs3::utils::get_hdd1_dir() + "/caches/"; + + for (const auto& entry : fs::dir(hdd1)) + { + if (entry.is_directory && entry.name.starts_with(current_game.serial)) + { + has_hdd1_cache = true; + break; + } + } + + if (has_hdd1_cache) + { + QAction* remove_hdd1_cache = remove_menu->addAction(tr("&Remove HDD1 Cache")); + remove_hdd1_cache->setEnabled(!is_current_running_game); + connect(remove_hdd1_cache, &QAction::triggered, [this, hdd1, serial = current_game.serial]() + { + RemoveHDD1Cache(hdd1, serial, true); + }); + } + + if (has_cache_dir || has_hdd1_cache) + { QAction* remove_all_caches = remove_menu->addAction(tr("&Remove All Caches")); - connect(remove_all_caches, &QAction::triggered, [this, cache_base_dir]() + remove_all_caches->setEnabled(!is_current_running_game); + connect(remove_all_caches, &QAction::triggered, [this, current_game, cache_base_dir, hdd1]() { + if (is_game_running(current_game.serial)) + return; + if (QMessageBox::question(this, tr("Confirm Removal"), tr("Remove all caches?")) != QMessageBox::Yes) return; @@ -1169,8 +1213,11 @@ void game_list_frame::ShowContextMenu(const QPoint &pos) game_list_log.success("Removed cache directory: '%s'", cache_base_dir); else game_list_log.error("Could not remove cache directory: '%s' (%s)", cache_base_dir, fs::g_tls_error); + + RemoveHDD1Cache(hdd1, current_game.serial); }); } + menu.addSeparator(); QAction* open_game_folder = menu.addAction(tr("&Open Install Folder")); if (gameinfo->hasCustomConfig) @@ -1404,6 +1451,12 @@ void game_list_frame::ShowContextMenu(const QPoint &pos) return; } + if (is_game_running(current_game.serial)) + { + QMessageBox::critical(this, tr("Cannot Remove Game"), tr("The PS3 application is still running, it cannot be removed!")); + return; + } + QString size_information; if (current_game.size_on_disk != umax) @@ -1812,6 +1865,48 @@ bool game_list_frame::RemoveSPUCache(const std::string& base_dir, bool is_intera return success; } +void game_list_frame::RemoveHDD1Cache(const std::string& base_dir, const std::string& title_id, bool is_interactive) +{ + if (!fs::is_dir(base_dir)) + return; + + if (is_interactive && QMessageBox::question(this, tr("Confirm Removal"), tr("Remove HDD1 cache?")) != QMessageBox::Yes) + return; + + u32 dirs_removed = 0; + u32 dirs_total = 0; + + const QString q_base_dir = qstr(base_dir); + + const QStringList filter{ qstr(title_id + "_*") }; + + QDirIterator dir_iter(q_base_dir, filter, QDir::Dirs | QDir::NoDotAndDotDot); + + while (dir_iter.hasNext()) + { + const QString filepath = dir_iter.next(); + + if (fs::remove_all(filepath.toStdString())) + { + ++dirs_removed; + game_list_log.notice("Removed HDD1 cache directory: %s", filepath); + } + else + { + game_list_log.warning("Could not remove HDD1 cache directory: %s", filepath); + } + + ++dirs_total; + } + + const bool success = dirs_removed == dirs_total; + + if (success) + game_list_log.success("Removed HDD1 cache in %s (%s)", base_dir, title_id); + else + game_list_log.fatal("Only %d/%d HDD1 cache directories could be removed in %s (%s)", dirs_removed, dirs_total, base_dir, title_id); +} + void game_list_frame::BatchCreatePPUCaches() { const std::string vsh_path = g_cfg_vfs.get_dev_flash() + "vsh/module/"; @@ -1903,6 +1998,11 @@ void game_list_frame::BatchCreatePPUCaches() void game_list_frame::BatchRemovePPUCaches() { + if (Emu.GetStatus(false) != system_state::stopped) + { + return; + } + std::set serials; for (const auto& game : m_game_data) { @@ -1944,6 +2044,11 @@ void game_list_frame::BatchRemovePPUCaches() void game_list_frame::BatchRemoveSPUCaches() { + if (Emu.GetStatus(false) != system_state::stopped) + { + return; + } + std::set serials; for (const auto& game : m_game_data) { @@ -2075,6 +2180,11 @@ void game_list_frame::BatchRemoveCustomPadConfigurations() void game_list_frame::BatchRemoveShaderCaches() { + if (Emu.GetStatus(false) != system_state::stopped) + { + return; + } + std::set serials; for (const auto& game : m_game_data) { diff --git a/rpcs3/rpcs3qt/game_list_frame.h b/rpcs3/rpcs3qt/game_list_frame.h index 50a18282e544..582d407a5232 100644 --- a/rpcs3/rpcs3qt/game_list_frame.h +++ b/rpcs3/rpcs3qt/game_list_frame.h @@ -107,6 +107,7 @@ private Q_SLOTS: bool RemoveShadersCache(const std::string& base_dir, bool is_interactive = false); bool RemovePPUCache(const std::string& base_dir, bool is_interactive = false); bool RemoveSPUCache(const std::string& base_dir, bool is_interactive = false); + void RemoveHDD1Cache(const std::string& base_dir, const std::string& title_id, bool is_interactive = false); static bool CreatePPUCache(const std::string& path, const std::string& serial = {}); static bool CreatePPUCache(const game_info& game); diff --git a/rpcs3/rpcs3qt/gui_settings.cpp b/rpcs3/rpcs3qt/gui_settings.cpp index adaa2bad8246..d89ef4315727 100644 --- a/rpcs3/rpcs3qt/gui_settings.cpp +++ b/rpcs3/rpcs3qt/gui_settings.cpp @@ -9,6 +9,8 @@ #include #include +#include + LOG_CHANNEL(cfg_log, "CFG"); namespace gui @@ -188,6 +190,12 @@ void gui_settings::ShowInfoBox(const QString& title, const QString& text, const bool gui_settings::GetBootConfirmation(QWidget* parent, const gui_save& gui_save_entry) { + while (Emu.GetStatus(false) == system_state::stopping) + { + QCoreApplication::processEvents(); + std::this_thread::sleep_for(16ms); + } + if (!Emu.IsStopped()) { QString title = tr("Close Running Game?"); diff --git a/rpcs3/rpcs3qt/main_window.cpp b/rpcs3/rpcs3qt/main_window.cpp index a2025de926f2..fa7ea4108e0a 100644 --- a/rpcs3/rpcs3qt/main_window.cpp +++ b/rpcs3/rpcs3qt/main_window.cpp @@ -182,15 +182,19 @@ bool main_window::Init([[maybe_unused]] bool with_cli_boot) connect(m_thumb_stop, &QWinThumbnailToolButton::clicked, this, []() { - gui_log.notice("User clicked stop button on thumbnail toolbar"); + gui_log.notice("User clicked the stop button on thumbnail toolbar"); Emu.GracefulShutdown(false, true); }); connect(m_thumb_restart, &QWinThumbnailToolButton::clicked, this, []() { - gui_log.notice("User clicked restart button on thumbnail toolbar"); + gui_log.notice("User clicked the restart button on thumbnail toolbar"); Emu.Restart(); }); - connect(m_thumb_playPause, &QWinThumbnailToolButton::clicked, this, &main_window::OnPlayOrPause); + connect(m_thumb_playPause, &QWinThumbnailToolButton::clicked, this, [this]() + { + gui_log.notice("User clicked the playPause button on thumbnail toolbar"); + OnPlayOrPause(); + }); #endif // RPCS3 Updater @@ -1909,6 +1913,12 @@ void main_window::OnEmuStop() m_thumb_restart->setEnabled(true); #endif } + + ui->batchRemovePPUCachesAct->setEnabled(true); + ui->batchRemoveSPUCachesAct->setEnabled(true); + ui->batchRemoveShaderCachesAct->setEnabled(true); + ui->removeDiskCacheAct->setEnabled(true); + ui->actionManage_Users->setEnabled(true); ui->confCamerasAct->setEnabled(true); @@ -1951,6 +1961,11 @@ void main_window::OnEmuReady() const ui->actionManage_Users->setEnabled(false); ui->confCamerasAct->setEnabled(false); + + ui->batchRemovePPUCachesAct->setEnabled(false); + ui->batchRemoveSPUCachesAct->setEnabled(false); + ui->batchRemoveShaderCachesAct->setEnabled(false); + ui->removeDiskCacheAct->setEnabled(false); } void main_window::EnableMenus(bool enabled) const