diff --git a/rpcs3/Emu/Cell/Modules/cellSysCache.cpp b/rpcs3/Emu/Cell/Modules/cellSysCache.cpp index e0b8920811..382c305e32 100644 --- a/rpcs3/Emu/Cell/Modules/cellSysCache.cpp +++ b/rpcs3/Emu/Cell/Modules/cellSysCache.cpp @@ -31,6 +31,8 @@ void fmt_class_string::format(std::string& out, u64 arg) extern lv2_fs_mount_point g_mp_sys_dev_hdd1; +extern std::string get_syscache_state_corruption_indicator_file_path(std::string_view dir_path); + struct syscache_info { const std::string cache_root = rpcs3::utils::get_hdd1_dir() + "/caches/"; @@ -39,6 +41,8 @@ struct syscache_info std::string cache_id; + bool retain_caches = false; + syscache_info() noexcept { // Check if dev_hdd1 is mounted by parent process @@ -63,6 +67,12 @@ struct syscache_info { if (entry.is_directory && entry.name.starts_with(prefix)) { + if (fs::is_file(get_syscache_state_corruption_indicator_file_path(cache_root + '/' + entry.name))) + { + // State is not complete + break; + } + cache_id = std::move(entry.name); break; } @@ -89,8 +99,50 @@ struct syscache_info }); } } + + ~syscache_info() noexcept + { + if (cache_id.empty()) + { + return; + } + + if (!retain_caches) + { + vfs::host::remove_all(cache_root + cache_id, cache_root, &g_mp_sys_dev_hdd1, true, false, true); + return; + } + + idm::select([](u32 /*id*/, lv2_file& file) + { + if (file.file && file.mp->flags & lv2_mp_flag::cache && file.flags & CELL_FS_O_ACCMODE) + { + file.file.sync(); + } + }); + + fs::remove_file(get_syscache_state_corruption_indicator_file_path(cache_root + cache_id)); + } }; +extern std::string get_syscache_state_corruption_indicator_file_path(std::string_view dir_path) +{ + constexpr std::u8string_view append_path = u8"/$hdd0_temp_state_indicator"; + const std::string_view filename = reinterpret_cast(append_path.data()); + + if (dir_path.empty()) + { + return rpcs3::utils::get_hdd1_dir() + "/caches/" + ensure(g_fxo->try_get())->cache_id + "/" + filename.data(); + } + + return std::string{dir_path} + filename.data(); +} + +extern void signal_system_cache_can_stay() +{ + ensure(g_fxo->try_get())->retain_caches = true; +} + error_code cellSysCacheClear() { cellSysutil.notice("cellSysCacheClear()"); @@ -116,7 +168,7 @@ error_code cellSysCacheClear() error_code cellSysCacheMount(vm::ptr param) { - cellSysutil.notice("cellSysCacheMount(param=*0x%x)", param); + cellSysutil.notice("cellSysCacheMount(param=*0x%x ('%s'))", param, param.ptr(&CellSysCacheParam::cacheId)); auto& cache = g_fxo->get(); @@ -141,8 +193,8 @@ error_code cellSysCacheMount(vm::ptr param) std::lock_guard lock0(g_mp_sys_dev_hdd1.mutex); - // Check if can reuse existing cache (won't if cache id is an empty string) - if (param->cacheId[0] && cache_id == cache.cache_id) + // Check if can reuse existing cache (won't if cache id is an empty string or cache is damaged/incomplete) + if (param->cacheId[0] && cache_id == cache.cache_id && !fs::is_file(get_syscache_state_corruption_indicator_file_path(cache.cache_root + cache_id))) { // Isn't mounted yet on first call to cellSysCacheMount if (vfs::mount("/dev_hdd1", new_path)) @@ -152,15 +204,33 @@ error_code cellSysCacheMount(vm::ptr param) return not_an_error(CELL_SYSCACHE_RET_OK_RELAYED); } - // Clear existing cache + const bool can_create = cache.cache_id != cache_id; + if (!cache.cache_id.empty()) { + // Clear previous cache cache.clear(true); } // Set new cache id cache.cache_id = std::move(cache_id); - fs::create_dir(new_path); + + if (can_create) + { + const bool created = fs::create_dir(new_path); + + if (!created) + { + if (fs::g_tls_error != fs::error::exist) + { + fmt::throw_exception("Failed to create HDD1 cache! (path='%s', reason='%s')", new_path, fs::g_tls_error); + } + + // Clear new cache + cache.clear(false); + } + } + if (vfs::mount("/dev_hdd1", new_path)) g_fxo->get().add("/dev_hdd1", &g_mp_sys_dev_hdd1); diff --git a/rpcs3/Emu/Cell/lv2/sys_process.cpp b/rpcs3/Emu/Cell/lv2/sys_process.cpp index 496e3406c0..57b0241b70 100644 --- a/rpcs3/Emu/Cell/lv2/sys_process.cpp +++ b/rpcs3/Emu/Cell/lv2/sys_process.cpp @@ -340,6 +340,8 @@ error_code sys_process_detach_child(u64 unk) return CELL_OK; } +extern void signal_system_cache_can_stay(); + void _sys_process_exit(ppu_thread& ppu, s32 status, u32 arg2, u32 arg3) { ppu.state += cpu_flag::wait; @@ -349,6 +351,7 @@ void _sys_process_exit(ppu_thread& ppu, s32 status, u32 arg2, u32 arg3) Emu.CallFromMainThread([]() { sys_process.success("Process finished"); + signal_system_cache_can_stay(); Emu.Kill(); }); @@ -481,6 +484,7 @@ void lv2_exitspawn(ppu_thread& ppu, std::vector& argv, std::vector< } }; + signal_system_cache_can_stay(); Emu.Kill(false); }); diff --git a/rpcs3/Emu/Memory/vm_ptr.h b/rpcs3/Emu/Memory/vm_ptr.h index eed53220ed..35b5a9f9c0 100644 --- a/rpcs3/Emu/Memory/vm_ptr.h +++ b/rpcs3/Emu/Memory/vm_ptr.h @@ -452,3 +452,15 @@ struct fmt_class_string, void> : fmt_class_string +struct fmt_class_string, void> : fmt_class_string> +{ + // Classify const char[] as const char* +}; + +template +struct fmt_class_string, void> : fmt_class_string> +{ + // Classify char[] as const char* +}; diff --git a/rpcs3/Emu/System.cpp b/rpcs3/Emu/System.cpp index 90c8e71eff..3efc1a097d 100644 --- a/rpcs3/Emu/System.cpp +++ b/rpcs3/Emu/System.cpp @@ -87,6 +87,8 @@ extern bool ppu_load_rel_exec(const ppu_rel_object&); extern void send_close_home_menu_cmds(); +extern void signal_system_cache_can_stay(); + fs::file make_file_view(const fs::file& file, u64 offset, u64 size); fs::file g_tty; @@ -3033,6 +3035,8 @@ void Emulator::Kill(bool allow_autoexit, bool savestate, savestate_stage* save_s to_ar = std::make_unique(); to_ar->m_file_handler = make_compressed_serialization_file_handler(file.file); + + signal_system_cache_can_stay(); break; } diff --git a/rpcs3/Emu/VFS.cpp b/rpcs3/Emu/VFS.cpp index d5952eeac8..1c2df8b685 100644 --- a/rpcs3/Emu/VFS.cpp +++ b/rpcs3/Emu/VFS.cpp @@ -922,9 +922,9 @@ std::string vfs::unescape(std::string_view name) return result; } -std::string vfs::host::hash_path(const std::string& path, const std::string& dev_root) +std::string vfs::host::hash_path(const std::string& path, const std::string& dev_root, std::string_view prefix) { - return fmt::format(u8"%s/$%s%s", dev_root, fmt::base57(std::hash()(path)), fmt::base57(utils::get_unique_tsc())); + return fmt::format(u8"%s/$%s%s%s", dev_root, fmt::base57(std::hash()(path)), prefix, fmt::base57(utils::get_unique_tsc())); } bool vfs::host::rename(const std::string& from, const std::string& to, const lv2_fs_mount_point* mp, bool overwrite, bool lock) @@ -1032,11 +1032,21 @@ bool vfs::host::unlink(const std::string& path, [[maybe_unused]] const std::stri else { // Rename to special dummy name which will be ignored by VFS (but opened file handles can still read or write it) - const std::string dummy = hash_path(path, dev_root); + std::string dummy = hash_path(path, dev_root, "file"); - if (!fs::rename(path, dummy, true)) + while (true) { - return false; + if (fs::rename(path, dummy, false)) + { + break; + } + + if (fs::g_tls_error != fs::error::exist) + { + return false; + } + + dummy = hash_path(path, dev_root, "file"); } if (fs::file f{dummy, fs::read + fs::write}) @@ -1056,17 +1066,33 @@ bool vfs::host::unlink(const std::string& path, [[maybe_unused]] const std::stri #endif } -bool vfs::host::remove_all(const std::string& path, [[maybe_unused]] const std::string& dev_root, [[maybe_unused]] const lv2_fs_mount_point* mp, bool remove_root, [[maybe_unused]] bool lock) +bool vfs::host::remove_all(const std::string& path, [[maybe_unused]] const std::string& dev_root, [[maybe_unused]] const lv2_fs_mount_point* mp, [[maybe_unused]] bool remove_root, [[maybe_unused]] bool lock, [[maybe_unused]] bool force_atomic) { -#ifdef _WIN32 +#ifndef _WIN32 + if (!force_atomic) + { + return fs::remove_all(path, remove_root); + } +#endif + if (remove_root) { // Rename to special dummy folder which will be ignored by VFS (but opened file handles can still read or write it) - const std::string dummy = hash_path(path, dev_root); + std::string dummy = hash_path(path, dev_root, "dir"); - if (!vfs::host::rename(path, dummy, mp, false, lock)) + while (true) { - return false; + if (vfs::host::rename(path, dummy, mp, false, lock)) + { + break; + } + + if (fs::g_tls_error != fs::error::exist) + { + return false; + } + + dummy = hash_path(path, dev_root, "dir"); } if (!vfs::host::remove_all(dummy, dev_root, mp, false, lock)) @@ -1113,7 +1139,4 @@ bool vfs::host::remove_all(const std::string& path, [[maybe_unused]] const std:: } return true; -#else - return fs::remove_all(path, remove_root); -#endif } diff --git a/rpcs3/Emu/VFS.h b/rpcs3/Emu/VFS.h index 7b71e6d83b..2872d9a5e2 100644 --- a/rpcs3/Emu/VFS.h +++ b/rpcs3/Emu/VFS.h @@ -31,7 +31,7 @@ namespace vfs namespace host { // For internal use (don't use) - std::string hash_path(const std::string& path, const std::string& dev_root); + std::string hash_path(const std::string& path, const std::string& dev_root, std::string_view prefix = {}); // Call fs::rename with retry on access error bool rename(const std::string& from, const std::string& to, const lv2_fs_mount_point* mp, bool overwrite, bool lock = true); @@ -40,6 +40,6 @@ namespace vfs bool unlink(const std::string& path, const std::string& dev_root); // Delete folder contents using rename, done atomically if remove_root is true - bool remove_all(const std::string& path, const std::string& dev_root, const lv2_fs_mount_point* mp, bool remove_root = true, bool lock = true); + bool remove_all(const std::string& path, const std::string& dev_root, const lv2_fs_mount_point* mp, bool remove_root = true, bool lock = true, bool force_atomic = false); } }