2025-04-08 18:46:57 +02:00
|
|
|
|
#include "Emu/Cell/PPUModule.h"
|
|
|
|
|
|
#include "Emu/IdManager.h"
|
2019-11-05 16:09:03 +01:00
|
|
|
|
#include "Emu/System.h"
|
2020-02-15 23:36:20 +01:00
|
|
|
|
#include "Emu/VFS.h"
|
2025-04-08 18:46:57 +02:00
|
|
|
|
#include "Emu/system_utils.hpp"
|
|
|
|
|
|
#include "stdafx.h"
|
2019-11-05 16:09:03 +01:00
|
|
|
|
|
2019-11-09 12:35:41 +01:00
|
|
|
|
#include "Emu/Cell/lv2/sys_fs.h"
|
2019-11-05 16:09:03 +01:00
|
|
|
|
#include "cellSysutil.h"
|
2025-04-08 18:46:57 +02:00
|
|
|
|
#include "util/StrUtil.h"
|
2019-11-05 16:09:03 +01:00
|
|
|
|
#include "util/init_mutex.hpp"
|
|
|
|
|
|
|
2020-01-31 10:01:17 +01:00
|
|
|
|
LOG_CHANNEL(cellSysutil);
|
2019-11-05 16:09:03 +01:00
|
|
|
|
|
2025-04-05 21:50:45 +02:00
|
|
|
|
template <>
|
2019-11-05 16:09:03 +01:00
|
|
|
|
void fmt_class_string<CellSysCacheError>::format(std::string& out, u64 arg)
|
|
|
|
|
|
{
|
|
|
|
|
|
format_enum(out, arg, [](auto error)
|
|
|
|
|
|
{
|
2025-04-05 21:50:45 +02:00
|
|
|
|
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);
|
|
|
|
|
|
}
|
2019-11-05 16:09:03 +01:00
|
|
|
|
|
2025-04-05 21:50:45 +02:00
|
|
|
|
return unknown;
|
|
|
|
|
|
});
|
2019-11-05 16:09:03 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2020-04-04 19:06:36 +02:00
|
|
|
|
extern lv2_fs_mount_point g_mp_sys_dev_hdd1;
|
|
|
|
|
|
|
2025-04-08 18:46:57 +02:00
|
|
|
|
extern std::string
|
|
|
|
|
|
get_syscache_state_corruption_indicator_file_path(std::string_view dir_path);
|
2024-01-11 08:46:07 +01:00
|
|
|
|
|
2019-11-05 16:09:03 +01:00
|
|
|
|
struct syscache_info
|
|
|
|
|
|
{
|
2021-04-21 22:12:21 +02:00
|
|
|
|
const std::string cache_root = rpcs3::utils::get_hdd1_dir() + "/caches/";
|
2019-11-05 16:09:03 +01:00
|
|
|
|
|
|
|
|
|
|
stx::init_mutex init;
|
|
|
|
|
|
|
|
|
|
|
|
std::string cache_id;
|
|
|
|
|
|
|
2024-01-11 08:46:07 +01:00
|
|
|
|
bool retain_caches = false;
|
|
|
|
|
|
|
2019-11-05 16:09:03 +01:00
|
|
|
|
syscache_info() noexcept
|
|
|
|
|
|
{
|
2019-11-08 21:24:13 +01:00
|
|
|
|
// Check if dev_hdd1 is mounted by parent process
|
2020-02-26 21:13:54 +01:00
|
|
|
|
if (!Emu.hdd1.empty())
|
2019-11-08 21:24:13 +01:00
|
|
|
|
{
|
|
|
|
|
|
const auto lock = init.init();
|
|
|
|
|
|
|
|
|
|
|
|
// Extract cache id from path
|
2024-01-15 09:54:11 +01:00
|
|
|
|
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};
|
|
|
|
|
|
|
2025-04-08 18:46:57 +02:00
|
|
|
|
if (!Emu.DeserialManager() &&
|
|
|
|
|
|
!fs::write_file<true>(
|
|
|
|
|
|
get_syscache_state_corruption_indicator_file_path(Emu.hdd1),
|
|
|
|
|
|
fs::write_new))
|
2024-01-15 09:54:11 +01:00
|
|
|
|
{
|
2025-04-08 18:46:57 +02:00
|
|
|
|
fmt::throw_exception("Failed to create HDD1 corruption indicator file! "
|
|
|
|
|
|
"(path='%s', reason='%s')",
|
|
|
|
|
|
Emu.hdd1, fs::g_tls_error);
|
2024-01-15 09:54:11 +01:00
|
|
|
|
}
|
2019-11-08 21:24:13 +01:00
|
|
|
|
|
2020-02-01 09:31:27 +01:00
|
|
|
|
cellSysutil.success("Retained cache from parent process: %s", Emu.hdd1);
|
2019-11-08 21:24:13 +01:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2019-11-05 16:09:03 +01:00
|
|
|
|
// Find existing cache at startup
|
|
|
|
|
|
const std::string prefix = Emu.GetTitleID() + '_';
|
|
|
|
|
|
|
|
|
|
|
|
for (auto&& entry : fs::dir(cache_root))
|
|
|
|
|
|
{
|
2020-02-17 22:43:23 +01:00
|
|
|
|
if (entry.is_directory && entry.name.starts_with(prefix))
|
2019-11-05 16:09:03 +01:00
|
|
|
|
{
|
2024-01-15 09:54:11 +01:00
|
|
|
|
cache_id = vfs::unescape(entry.name);
|
|
|
|
|
|
|
2025-04-08 18:46:57 +02:00
|
|
|
|
if (fs::is_file(get_syscache_state_corruption_indicator_file_path(
|
|
|
|
|
|
cache_root + '/' + cache_id)))
|
2024-01-11 08:46:07 +01:00
|
|
|
|
{
|
|
|
|
|
|
// State is not complete
|
2024-01-15 09:54:11 +01:00
|
|
|
|
clear(true);
|
|
|
|
|
|
cache_id.clear();
|
|
|
|
|
|
continue;
|
2024-01-11 08:46:07 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-04-08 18:46:57 +02:00
|
|
|
|
cellSysutil.notice("Retained cache from past data: %s",
|
|
|
|
|
|
cache_root + '/' + cache_id);
|
2019-11-05 16:09:03 +01:00
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2023-10-03 03:43:39 +02:00
|
|
|
|
void clear(bool remove_root, bool lock = false) const noexcept
|
2019-11-05 16:09:03 +01:00
|
|
|
|
{
|
|
|
|
|
|
// Clear cache
|
2025-04-08 18:46:57 +02:00
|
|
|
|
if (!vfs::host::remove_all(cache_root + cache_id, cache_root,
|
|
|
|
|
|
&g_mp_sys_dev_hdd1, remove_root, lock))
|
2019-11-05 16:09:03 +01:00
|
|
|
|
{
|
2025-04-08 18:46:57 +02:00
|
|
|
|
cellSysutil.fatal(
|
|
|
|
|
|
"cellSysCache: failed to clear cache directory '%s%s' (%s)",
|
|
|
|
|
|
cache_root, cache_id, fs::g_tls_error);
|
2019-11-05 16:09:03 +01:00
|
|
|
|
}
|
2019-11-09 12:35:41 +01:00
|
|
|
|
|
|
|
|
|
|
// Poison opened files in /dev_hdd1 to return CELL_EIO on access
|
|
|
|
|
|
if (remove_root)
|
|
|
|
|
|
{
|
2021-03-05 20:05:37 +01:00
|
|
|
|
idm::select<lv2_fs_object, lv2_file>([](u32 /*id*/, lv2_file& file)
|
2019-11-09 12:35:41 +01:00
|
|
|
|
{
|
2025-04-05 21:50:45 +02:00
|
|
|
|
if (file.file && file.mp->flags & lv2_mp_flag::cache)
|
|
|
|
|
|
{
|
|
|
|
|
|
file.lock = 2;
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
2019-11-09 12:35:41 +01:00
|
|
|
|
}
|
2019-11-05 16:09:03 +01:00
|
|
|
|
}
|
2024-01-11 08:46:07 +01:00
|
|
|
|
|
|
|
|
|
|
~syscache_info() noexcept
|
|
|
|
|
|
{
|
|
|
|
|
|
if (cache_id.empty())
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!retain_caches)
|
|
|
|
|
|
{
|
2025-04-08 18:46:57 +02:00
|
|
|
|
vfs::host::remove_all(cache_root + cache_id, cache_root,
|
|
|
|
|
|
&g_mp_sys_dev_hdd1, true, false, true);
|
2024-01-11 08:46:07 +01:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
idm::select<lv2_fs_object, lv2_file>([](u32 /*id*/, lv2_file& file)
|
|
|
|
|
|
{
|
2025-04-08 18:46:57 +02:00
|
|
|
|
if (file.file && file.mp->flags & lv2_mp_flag::cache &&
|
|
|
|
|
|
file.flags & CELL_FS_O_ACCMODE)
|
2025-04-05 21:50:45 +02:00
|
|
|
|
{
|
|
|
|
|
|
file.file.sync();
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
2024-01-11 08:46:07 +01:00
|
|
|
|
|
2025-04-08 18:46:57 +02:00
|
|
|
|
fs::remove_file(get_syscache_state_corruption_indicator_file_path(
|
|
|
|
|
|
cache_root + cache_id));
|
2024-01-11 08:46:07 +01:00
|
|
|
|
}
|
2019-11-05 16:09:03 +01:00
|
|
|
|
};
|
|
|
|
|
|
|
2025-04-08 18:46:57 +02:00
|
|
|
|
extern std::string
|
|
|
|
|
|
get_syscache_state_corruption_indicator_file_path(std::string_view dir_path)
|
2024-01-11 08:46:07 +01:00
|
|
|
|
{
|
|
|
|
|
|
constexpr std::u8string_view append_path = u8"/$hdd0_temp_state_indicator";
|
2025-04-08 18:46:57 +02:00
|
|
|
|
const std::string_view filename =
|
|
|
|
|
|
reinterpret_cast<const char*>(append_path.data());
|
2024-01-11 08:46:07 +01:00
|
|
|
|
|
|
|
|
|
|
if (dir_path.empty())
|
|
|
|
|
|
{
|
2025-04-08 18:46:57 +02:00
|
|
|
|
return rpcs3::utils::get_hdd1_dir() + "/caches/" +
|
|
|
|
|
|
ensure(g_fxo->try_get<syscache_info>())->cache_id + "/" +
|
|
|
|
|
|
filename.data();
|
2024-01-11 08:46:07 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return std::string{dir_path} + filename.data();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
extern void signal_system_cache_can_stay()
|
|
|
|
|
|
{
|
|
|
|
|
|
ensure(g_fxo->try_get<syscache_info>())->retain_caches = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2019-11-05 16:09:03 +01:00
|
|
|
|
error_code cellSysCacheClear()
|
|
|
|
|
|
{
|
|
|
|
|
|
cellSysutil.notice("cellSysCacheClear()");
|
|
|
|
|
|
|
2021-03-02 12:59:19 +01:00
|
|
|
|
auto& cache = g_fxo->get<syscache_info>();
|
2019-11-05 16:09:03 +01:00
|
|
|
|
|
2021-03-02 12:59:19 +01:00
|
|
|
|
const auto lock = cache.init.access();
|
2019-11-05 16:09:03 +01:00
|
|
|
|
|
|
|
|
|
|
if (!lock)
|
|
|
|
|
|
{
|
|
|
|
|
|
return CELL_SYSCACHE_ERROR_NOTMOUNTED;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Clear existing cache
|
2021-03-02 12:59:19 +01:00
|
|
|
|
if (!cache.cache_id.empty())
|
2019-11-05 16:09:03 +01:00
|
|
|
|
{
|
2020-09-15 21:20:51 +02:00
|
|
|
|
std::lock_guard lock0(g_mp_sys_dev_hdd1.mutex);
|
2021-03-02 12:59:19 +01:00
|
|
|
|
cache.clear(false);
|
2019-11-05 16:09:03 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return not_an_error(CELL_SYSCACHE_RET_OK_CLEARED);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
error_code cellSysCacheMount(vm::ptr<CellSysCacheParam> param)
|
|
|
|
|
|
{
|
2025-04-08 18:46:57 +02:00
|
|
|
|
cellSysutil.notice("cellSysCacheMount(param=*0x%x ('%s'))", param,
|
|
|
|
|
|
param.ptr(&CellSysCacheParam::cacheId));
|
2019-11-05 16:09:03 +01:00
|
|
|
|
|
2021-03-02 12:59:19 +01:00
|
|
|
|
auto& cache = g_fxo->get<syscache_info>();
|
2019-11-05 16:09:03 +01:00
|
|
|
|
|
2024-01-15 09:54:11 +01:00
|
|
|
|
if (!param)
|
|
|
|
|
|
{
|
|
|
|
|
|
return CELL_SYSCACHE_ERROR_PARAM;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
std::string cache_name;
|
|
|
|
|
|
|
2025-04-08 18:46:57 +02:00
|
|
|
|
ensure(vm::read_string(param.ptr(&CellSysCacheParam::cacheId).addr(),
|
|
|
|
|
|
sizeof(param->cacheId), cache_name),
|
|
|
|
|
|
"Access violation");
|
2024-01-15 09:54:11 +01:00
|
|
|
|
|
2025-04-08 18:46:57 +02:00
|
|
|
|
if (!cache_name.empty() &&
|
|
|
|
|
|
sysutil_check_name_string(cache_name.data(), 1, CELL_SYSCACHE_ID_SIZE) !=
|
|
|
|
|
|
0)
|
2019-11-05 16:09:03 +01:00
|
|
|
|
{
|
|
|
|
|
|
return CELL_SYSCACHE_ERROR_PARAM;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Full virtualized cache id (with title id included)
|
2024-01-15 09:54:11 +01:00
|
|
|
|
std::string cache_id = vfs::escape(Emu.GetTitleID() + '_' + cache_name);
|
2019-11-05 16:09:03 +01:00
|
|
|
|
|
|
|
|
|
|
// Full path to virtual cache root (/dev_hdd1)
|
2021-03-02 12:59:19 +01:00
|
|
|
|
std::string new_path = cache.cache_root + cache_id + '/';
|
2019-11-05 16:09:03 +01:00
|
|
|
|
|
|
|
|
|
|
// Set fixed VFS path
|
2019-11-09 12:35:05 +01:00
|
|
|
|
strcpy_trunc(param->getCachePath, "/dev_hdd1");
|
2019-11-05 16:09:03 +01:00
|
|
|
|
|
|
|
|
|
|
// Lock pseudo-mutex
|
2025-04-05 21:50:45 +02:00
|
|
|
|
const auto lock = cache.init.init_always([&] {});
|
2019-11-05 16:09:03 +01:00
|
|
|
|
|
2023-05-25 08:31:17 +02:00
|
|
|
|
std::lock_guard lock0(g_mp_sys_dev_hdd1.mutex);
|
|
|
|
|
|
|
2025-04-08 18:46:57 +02:00
|
|
|
|
// Check if can reuse existing cache (won't if cache id is an empty string or
|
|
|
|
|
|
// cache is damaged/incomplete)
|
2024-01-15 09:54:11 +01:00
|
|
|
|
if (!cache_name.empty() && cache_id == cache.cache_id)
|
2019-11-05 16:09:03 +01:00
|
|
|
|
{
|
|
|
|
|
|
// Isn't mounted yet on first call to cellSysCacheMount
|
2023-05-25 08:31:17 +02:00
|
|
|
|
if (vfs::mount("/dev_hdd1", new_path))
|
|
|
|
|
|
g_fxo->get<lv2_fs_mount_info_map>().add("/dev_hdd1", &g_mp_sys_dev_hdd1);
|
2019-11-05 16:09:03 +01:00
|
|
|
|
|
|
|
|
|
|
cellSysutil.success("Mounted existing cache at %s", new_path);
|
|
|
|
|
|
return not_an_error(CELL_SYSCACHE_RET_OK_RELAYED);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-01-15 09:54:11 +01:00
|
|
|
|
const bool can_create = cache.cache_id != cache_id || !cache.cache_id.empty();
|
2024-01-11 08:46:07 +01:00
|
|
|
|
|
2021-03-02 12:59:19 +01:00
|
|
|
|
if (!cache.cache_id.empty())
|
2019-11-05 16:09:03 +01:00
|
|
|
|
{
|
2024-01-11 08:46:07 +01:00
|
|
|
|
// Clear previous cache
|
2021-03-02 12:59:19 +01:00
|
|
|
|
cache.clear(true);
|
2019-11-05 16:09:03 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Set new cache id
|
2021-03-02 12:59:19 +01:00
|
|
|
|
cache.cache_id = std::move(cache_id);
|
2024-01-11 08:46:07 +01:00
|
|
|
|
|
|
|
|
|
|
if (can_create)
|
|
|
|
|
|
{
|
|
|
|
|
|
const bool created = fs::create_dir(new_path);
|
|
|
|
|
|
|
|
|
|
|
|
if (!created)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (fs::g_tls_error != fs::error::exist)
|
|
|
|
|
|
{
|
2025-04-08 18:46:57 +02:00
|
|
|
|
fmt::throw_exception(
|
|
|
|
|
|
"Failed to create HDD1 cache! (path='%s', reason='%s')", new_path,
|
|
|
|
|
|
fs::g_tls_error);
|
2024-01-11 08:46:07 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Clear new cache
|
|
|
|
|
|
cache.clear(false);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-04-08 18:46:57 +02:00
|
|
|
|
if (!fs::write_file<true>(
|
|
|
|
|
|
get_syscache_state_corruption_indicator_file_path(new_path),
|
|
|
|
|
|
fs::write_new))
|
2024-01-15 09:54:11 +01:00
|
|
|
|
{
|
2025-04-08 18:46:57 +02:00
|
|
|
|
fmt::throw_exception("Failed to create HDD1 corruption indicator file! "
|
|
|
|
|
|
"(path='%s', reason='%s')",
|
|
|
|
|
|
new_path, fs::g_tls_error);
|
2024-01-15 09:54:11 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2023-05-25 08:31:17 +02:00
|
|
|
|
if (vfs::mount("/dev_hdd1", new_path))
|
|
|
|
|
|
g_fxo->get<lv2_fs_mount_info_map>().add("/dev_hdd1", &g_mp_sys_dev_hdd1);
|
2019-11-05 16:09:03 +01:00
|
|
|
|
|
|
|
|
|
|
return not_an_error(CELL_SYSCACHE_RET_OK_CLEARED);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
extern void cellSysutil_SysCache_init()
|
|
|
|
|
|
{
|
|
|
|
|
|
REG_FUNC(cellSysutil, cellSysCacheMount);
|
|
|
|
|
|
REG_FUNC(cellSysutil, cellSysCacheClear);
|
|
|
|
|
|
}
|