#include "Emu/Cell/PPUModule.h" #include "Emu/IdManager.h" #include "Emu/System.h" #include "Emu/VFS.h" #include "Emu/system_utils.hpp" #include "stdafx.h" #include "Emu/Cell/lv2/sys_fs.h" #include "cellSysutil.h" #include "util/StrUtil.h" #include "util/init_mutex.hpp" LOG_CHANNEL(cellSysutil); template <> void fmt_class_string::format(std::string& out, u64 arg) { format_enum(out, arg, [](auto error) { switch (error) { STR_CASE(CELL_SYSCACHE_ERROR_ACCESS_ERROR); STR_CASE(CELL_SYSCACHE_ERROR_INTERNAL); STR_CASE(CELL_SYSCACHE_ERROR_NOTMOUNTED); STR_CASE(CELL_SYSCACHE_ERROR_PARAM); } return unknown; }); } 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/"; stx::init_mutex init; std::string cache_id; bool retain_caches = false; syscache_info() noexcept { // Check if dev_hdd1 is mounted by parent process if (!Emu.hdd1.empty()) { const auto lock = init.init(); // Extract cache id from path std::string_view id = Emu.hdd1; id = id.substr(0, id.find_last_not_of(fs::delim) + 1); id = id.substr(id.find_last_of(fs::delim) + 1); cache_id = std::string{id}; if (!Emu.DeserialManager() && !fs::write_file( get_syscache_state_corruption_indicator_file_path(Emu.hdd1), fs::write_new)) { fmt::throw_exception("Failed to create HDD1 corruption indicator file! " "(path='%s', reason='%s')", Emu.hdd1, fs::g_tls_error); } cellSysutil.success("Retained cache from parent process: %s", Emu.hdd1); return; } // Find existing cache at startup const std::string prefix = Emu.GetTitleID() + '_'; for (auto&& entry : fs::dir(cache_root)) { if (entry.is_directory && entry.name.starts_with(prefix)) { cache_id = vfs::unescape(entry.name); if (fs::is_file(get_syscache_state_corruption_indicator_file_path( cache_root + '/' + cache_id))) { // State is not complete clear(true); cache_id.clear(); continue; } cellSysutil.notice("Retained cache from past data: %s", cache_root + '/' + cache_id); break; } } } void clear(bool remove_root, bool lock = false) const noexcept { // Clear cache if (!vfs::host::remove_all(cache_root + cache_id, cache_root, &g_mp_sys_dev_hdd1, remove_root, lock)) { cellSysutil.fatal( "cellSysCache: failed to clear cache directory '%s%s' (%s)", cache_root, cache_id, fs::g_tls_error); } // Poison opened files in /dev_hdd1 to return CELL_EIO on access if (remove_root) { idm::select([](u32 /*id*/, lv2_file& file) { if (file.file && file.mp->flags & lv2_mp_flag::cache) { file.lock = 2; } }); } } ~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()"); auto& cache = g_fxo->get(); const auto lock = cache.init.access(); if (!lock) { return CELL_SYSCACHE_ERROR_NOTMOUNTED; } // Clear existing cache if (!cache.cache_id.empty()) { std::lock_guard lock0(g_mp_sys_dev_hdd1.mutex); cache.clear(false); } return not_an_error(CELL_SYSCACHE_RET_OK_CLEARED); } error_code cellSysCacheMount(vm::ptr param) { cellSysutil.notice("cellSysCacheMount(param=*0x%x ('%s'))", param, param.ptr(&CellSysCacheParam::cacheId)); auto& cache = g_fxo->get(); if (!param) { return CELL_SYSCACHE_ERROR_PARAM; } std::string cache_name; ensure(vm::read_string(param.ptr(&CellSysCacheParam::cacheId).addr(), sizeof(param->cacheId), cache_name), "Access violation"); if (!cache_name.empty() && sysutil_check_name_string(cache_name.data(), 1, CELL_SYSCACHE_ID_SIZE) != 0) { return CELL_SYSCACHE_ERROR_PARAM; } // Full virtualized cache id (with title id included) std::string cache_id = vfs::escape(Emu.GetTitleID() + '_' + cache_name); // Full path to virtual cache root (/dev_hdd1) std::string new_path = cache.cache_root + cache_id + '/'; // Set fixed VFS path strcpy_trunc(param->getCachePath, "/dev_hdd1"); // Lock pseudo-mutex const auto lock = cache.init.init_always([&] {}); 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 or // cache is damaged/incomplete) if (!cache_name.empty() && cache_id == cache.cache_id) { // Isn't mounted yet on first call to cellSysCacheMount if (vfs::mount("/dev_hdd1", new_path)) g_fxo->get().add("/dev_hdd1", &g_mp_sys_dev_hdd1); cellSysutil.success("Mounted existing cache at %s", new_path); return not_an_error(CELL_SYSCACHE_RET_OK_RELAYED); } const bool can_create = cache.cache_id != cache_id || !cache.cache_id.empty(); if (!cache.cache_id.empty()) { // Clear previous cache cache.clear(true); } // Set new cache id cache.cache_id = std::move(cache_id); 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 (!fs::write_file( get_syscache_state_corruption_indicator_file_path(new_path), fs::write_new)) { fmt::throw_exception("Failed to create HDD1 corruption indicator file! " "(path='%s', reason='%s')", new_path, fs::g_tls_error); } if (vfs::mount("/dev_hdd1", new_path)) g_fxo->get().add("/dev_hdd1", &g_mp_sys_dev_hdd1); return not_an_error(CELL_SYSCACHE_RET_OK_CLEARED); } extern void cellSysutil_SysCache_init() { REG_FUNC(cellSysutil, cellSysCacheMount); REG_FUNC(cellSysutil, cellSysCacheClear); }