Fix save metadata and timestamps

Co-authored-by: Ali Almohaya <AliAlmohaya@gmail.com>
Co-authored-by: CheesyPoofs346 <jjs777406@gmail.com>
This commit is contained in:
nmzik
2026-09-23 22:47:34 +04:00
parent 7d7267cc56
commit 569ec04d1a
3 changed files with 315 additions and 65 deletions
+171 -55
View File
@@ -122,6 +122,20 @@ struct SaveDataParam {
uint8_t reserved[32];
};
struct SaveParamTextField {
char* data;
size_t size;
};
static SaveParamTextField param_text_field(SaveDataParam& param, uint32_t type) {
switch (type) {
case 1: return {param.title, sizeof(param.title)};
case 2: return {param.sub_title, sizeof(param.sub_title)};
case 3: return {param.detail, sizeof(param.detail)};
default: return {nullptr, 0};
}
}
struct SaveDataMountInfo {
uint64_t blocks;
uint64_t free_blocks;
@@ -345,6 +359,62 @@ static int read_save_blocks(const std::filesystem::path& directory, uint64_t* bl
return OK;
}
static int read_save_param(const std::filesystem::path& path, SaveDataParam* param) {
std::error_code error;
const bool exists = std::filesystem::exists(path, error);
if (error) {
return SAVE_DATA_ERROR_INTERNAL;
}
if (!exists) {
return SAVE_DATA_ERROR_NOT_FOUND;
}
Common::File file(path, Common::File::Mode::Read);
if (file.IsInvalid()) {
return SAVE_DATA_ERROR_INTERNAL;
}
if (file.Size() != sizeof(*param)) {
return SAVE_DATA_ERROR_BROKEN;
}
uint32_t read = 0;
file.Read(param, sizeof(*param), &read);
return read == sizeof(*param) ? OK : SAVE_DATA_ERROR_BROKEN;
}
static std::filesystem::path save_param_path(const std::filesystem::path& directory) {
return directory / "sce_sys" / "param.bin";
}
static int64_t newest_save_time(const std::filesystem::path& directory) {
int64_t mtime = 0;
std::error_code error;
for (std::filesystem::recursive_directory_iterator it(
directory, std::filesystem::directory_options::skip_permission_denied, error),
end;
it != end; it.increment(error)) {
if (error) {
error.clear();
continue;
}
if (it->is_regular_file(error) && !error) {
const auto time = Common::File::GetLastWriteTimeUTC(it->path());
if (!time.IsInvalid()) {
mtime = std::max(mtime, static_cast<int64_t>(time.ToUnix()));
}
}
error.clear();
}
return mtime;
}
static int load_save_param(const std::filesystem::path& directory, SaveDataParam* param) {
*param = {};
const int status = read_save_param(save_param_path(directory), param);
if (status == OK) {
param->mtime = newest_save_time(directory);
}
return status;
}
static int save_memory(const std::filesystem::path& directory, const SaveDataMemory& memory) {
std::error_code error;
std::filesystem::create_directories(directory, error);
@@ -385,23 +455,7 @@ static int load_memory(const std::filesystem::path& directory, SaveDataMemory* m
return SAVE_DATA_ERROR_BROKEN;
}
file.Close();
const bool has_param = std::filesystem::exists(directory / "param.bin", error);
if (error) {
return SAVE_DATA_ERROR_INTERNAL;
}
if (has_param) {
if (!file.Open(directory / "param.bin", Common::File::Mode::Read)) {
return SAVE_DATA_ERROR_INTERNAL;
}
if (file.Size() != sizeof(memory->param)) {
return SAVE_DATA_ERROR_BROKEN;
}
file.Read(&memory->param, sizeof(memory->param), &read);
if (read != sizeof(memory->param)) {
return SAVE_DATA_ERROR_BROKEN;
}
}
return OK;
return read_save_param(directory / "param.bin", &memory->param);
}
static void queue_save_data_event(uint32_t type, int32_t user_id,
@@ -451,11 +505,11 @@ static bool dir_name_match(const char* str, const char* pattern) {
return *str == '\0' && *pattern == '\0';
}
static int mount_save_data(int slot, std::string_view dir_name, const std::string& directory,
static int mount_save_data(int slot, const std::filesystem::path& directory,
uint32_t status, SaveDataMountResult* result) {
const std::string mount_point = SaveDataMountSlots::MountPoint(static_cast<size_t>(slot));
LibKernel::FileSystem::Mount(directory, mount_point);
g_mount_slots.Mount(static_cast<size_t>(slot), dir_name);
g_mount_slots.Mount(static_cast<size_t>(slot), directory);
std::snprintf(result->mount_point.data, sizeof(result->mount_point.data), "%s",
mount_point.c_str());
result->required_blocks = 0;
@@ -542,7 +596,8 @@ int KYTY_SYSV_ABI SaveDataDirNameSearch(const SaveDataDirNameSearchCond* cond,
if (Common::File::IsDirectoryExisting(root)) {
for (const auto& entry: Common::File::GetDirEntries(root)) {
if (!entry.is_file && entry.name != "." && entry.name != ".." &&
!entry.name.starts_with("sce_") && valid_path_component(entry.name)) {
!entry.name.starts_with("sce_") && valid_path_component(entry.name) &&
Common::File::IsFileExisting(save_param_path(root / entry.name))) {
if (cond->dir_name == nullptr || cond->dir_name->data[0] == '\0' ||
dir_name_match(Common::ToLower(entry.name).c_str(),
Common::ToLower(std::string(cond->dir_name->data)).c_str())) {
@@ -574,7 +629,10 @@ int KYTY_SYSV_ABI SaveDataDirNameSearch(const SaveDataDirNameSearchCond* cond,
std::snprintf(result->dir_names[i].data, sizeof(result->dir_names[i].data), "%s",
dir_list[i].c_str());
if (result->params != nullptr) {
result->params[i] = {};
const int status = load_save_param(root / dir_list[i], &result->params[i]);
if (status != OK) {
return status;
}
}
if (result->infos != nullptr) {
auto& info = result->infos[i];
@@ -615,12 +673,12 @@ int KYTY_SYSV_ABI SaveDataMount3(const SaveDataMount3* mount, SaveDataMountResul
Common::LockGuard lock(g_mount_mutex);
const std::string dir_name = mount->dir_name->data;
const auto mount_dir = save_directory(get_title_id(), dir_name, mount->user_id).string();
const auto mount_dir = save_directory(get_title_id(), dir_name, mount->user_id);
const bool create = ((mount->mount_mode & 4u) != 0);
const bool create2 = ((mount->mount_mode & 32u) != 0);
const bool open = (!create && !create2 && ((mount->mount_mode & 3u) != 0));
const int slot = g_mount_slots.FindAvailable(dir_name);
const int slot = g_mount_slots.FindAvailable(mount_dir);
if (slot == SaveDataMountSlots::BUSY) {
return SAVE_DATA_ERROR_BUSY;
}
@@ -645,7 +703,7 @@ int KYTY_SYSV_ABI SaveDataMount3(const SaveDataMount3* mount, SaveDataMountResul
if (mount->blocks < SAVE_DATA_BLOCKS_MIN || mount->blocks > SAVE_DATA_BLOCKS_MAX) {
return SAVE_DATA_ERROR_PARAMETER;
}
const auto metadata = std::filesystem::path(mount_dir) / "sce_sys";
const auto metadata = mount_dir / "sce_sys";
std::error_code error;
std::filesystem::create_directories(metadata, error);
if (error) {
@@ -657,10 +715,17 @@ int KYTY_SYSV_ABI SaveDataMount3(const SaveDataMount3* mount, SaveDataMountResul
std::filesystem::remove_all(mount_dir, error);
return status;
}
const SaveDataParam initial {};
const int param_status = write_save_file(save_param_path(mount_dir), &initial,
sizeof(initial));
if (param_status != OK) {
std::filesystem::remove_all(mount_dir, error);
return param_status;
}
created = true;
}
return mount_save_data(slot, dir_name, mount_dir, created ? 1u : 0u, mount_result);
return mount_save_data(slot, mount_dir, created ? 1u : 0u, mount_result);
}
int KYTY_SYSV_ABI SaveDataSetupSaveDataMemory2(const SaveDataMemorySetup2* setup_param,
@@ -686,7 +751,7 @@ int KYTY_SYSV_ABI SaveDataSetupSaveDataMemory2(const SaveDataMemorySetup2* setup
Common::LockGuard lock(g_mount_mutex);
const auto directory = memory_directory(setup_param->user_id, setup_param->slot_id);
if (g_save_data_memory.contains(directory) ||
g_mount_slots.FindAvailable(memory_dir_name(setup_param->slot_id)) ==
g_mount_slots.FindAvailable(directory) ==
SaveDataMountSlots::BUSY) {
return SAVE_DATA_ERROR_BUSY;
}
@@ -848,8 +913,8 @@ int KYTY_SYSV_ABI SaveDataTransferringMount(const SaveDataTransferringMount* mou
Common::LockGuard lock(g_mount_mutex);
const std::string dir_name = mount->dir_name->data;
const auto mount_dir = save_directory(mount->title_id->data, dir_name, mount->user_id).string();
const int slot = g_mount_slots.FindAvailable(dir_name);
const auto mount_dir = save_directory(mount->title_id->data, dir_name, mount->user_id);
const int slot = g_mount_slots.FindAvailable(mount_dir);
if (slot == SaveDataMountSlots::BUSY) {
return SAVE_DATA_ERROR_BUSY;
}
@@ -861,7 +926,7 @@ int KYTY_SYSV_ABI SaveDataTransferringMount(const SaveDataTransferringMount* mou
return SAVE_DATA_ERROR_NOT_FOUND;
}
return mount_save_data(slot, dir_name, mount_dir, 0, mount_result);
return mount_save_data(slot, mount_dir, 0, mount_result);
}
int KYTY_SYSV_ABI SaveDataUmount2(uint32_t mode, const SaveDataMountPoint* mount_point) {
@@ -946,7 +1011,7 @@ int KYTY_SYSV_ABI SaveDataDelete(const SaveDataDelete* del) {
Common::LockGuard lock(g_mount_mutex);
const auto dir = save_directory(del->title_id != nullptr ? del->title_id->data : get_title_id(),
del->dir_name->data, del->user_id);
if (g_mount_slots.FindAvailable(del->dir_name->data) == SaveDataMountSlots::BUSY ||
if (g_mount_slots.FindAvailable(dir) == SaveDataMountSlots::BUSY ||
g_save_data_memory.contains(dir)) {
return SAVE_DATA_ERROR_BUSY;
}
@@ -960,7 +1025,8 @@ int KYTY_SYSV_ABI SaveDataGetParam(const SaveDataMountPoint* mount_point, uint32
void* param_buf, size_t param_buf_size, size_t* got_size) {
PRINT_NAME();
if (mount_point == nullptr || param_buf == nullptr) {
if (mount_point == nullptr || param_buf == nullptr || param_type > 5 ||
std::memchr(mount_point->data, 0, sizeof(mount_point->data)) == nullptr) {
return SAVE_DATA_ERROR_PARAMETER;
}
@@ -968,21 +1034,44 @@ int KYTY_SYSV_ABI SaveDataGetParam(const SaveDataMountPoint* mount_point, uint32
"\t param_type = %u\n"
"\t param_buf_size = %" PRIu64 "\n",
mount_point->data, param_type, param_buf_size);
if (param_type == 0) {
if (param_buf_size < sizeof(SaveDataParam)) {
return SAVE_DATA_ERROR_PARAMETER;
}
std::memset(param_buf, 0, sizeof(SaveDataParam));
if (got_size != nullptr) {
*got_size = sizeof(SaveDataParam);
}
return OK;
Common::LockGuard lock(g_mount_mutex);
const int slot = g_mount_slots.Find(mount_point->data);
if (slot == SaveDataMountSlots::FULL) {
return SAVE_DATA_ERROR_NOT_MOUNTED;
}
std::memset(param_buf, 0, param_buf_size);
SaveDataParam param {};
const int status = load_save_param(g_mount_slots.Directory(static_cast<size_t>(slot)), &param);
if (status != OK) {
return status;
}
size_t size = 0;
if (param_type == 0) {
size = sizeof(param);
if (param_buf_size < size) {
return SAVE_DATA_ERROR_PARAMETER;
}
std::memcpy(param_buf, &param, size);
} else if (param_type == 4 || param_type == 5) {
size = param_type == 4 ? sizeof(param.user_param) : sizeof(param.mtime);
if (param_buf_size < size) {
return SAVE_DATA_ERROR_PARAMETER;
}
std::memcpy(param_buf, param_type == 4 ? static_cast<const void*>(&param.user_param)
: static_cast<const void*>(&param.mtime), size);
} else {
if (param_buf_size == 0) {
return SAVE_DATA_ERROR_PARAMETER;
}
const auto field = param_text_field(param, param_type);
size = std::min(static_cast<size_t>(
std::find(field.data, field.data + field.size, '\0') - field.data),
param_buf_size - 1);
std::memcpy(param_buf, field.data, size);
static_cast<char*>(param_buf)[size++] = '\0';
}
if (got_size != nullptr) {
*got_size = param_buf_size;
*got_size = size;
}
return OK;
@@ -1035,7 +1124,7 @@ int KYTY_SYSV_ABI SaveDataSyncSaveDataMemory(const SaveDataMemorySync* sync_para
return SAVE_DATA_ERROR_MEMORY_NOT_READY;
}
const auto dir_name = memory_dir_name(sync_param->slot_id);
if (g_mount_slots.FindAvailable(dir_name) == SaveDataMountSlots::BUSY) {
if (g_mount_slots.FindAvailable(it->first) == SaveDataMountSlots::BUSY) {
return SAVE_DATA_ERROR_BUSY;
}
const int status = save_memory(it->first, it->second);
@@ -1108,26 +1197,53 @@ int KYTY_SYSV_ABI SaveDataSetParam(const SaveDataMountPoint* mount_point, uint32
const void* param_buf, size_t param_buf_size) {
PRINT_NAME();
EXIT_NOT_IMPLEMENTED(mount_point == nullptr);
if (mount_point == nullptr || param_buf == nullptr || param_type > 4 ||
std::memchr(mount_point->data, 0, sizeof(mount_point->data)) == nullptr ||
(param_type == 0 && param_buf_size < sizeof(SaveDataParam)) ||
(param_type == 4 && param_buf_size < sizeof(uint32_t)) ||
(param_type >= 1 && param_type <= 3 && param_buf_size == 0)) {
return SAVE_DATA_ERROR_PARAMETER;
}
LOGF("\t mount_point = %s\n"
"\t param_type = %u\n"
"\t param_buf_size = %" PRIu64 "\n",
mount_point->data, param_type, param_buf_size);
Common::LockGuard lock(g_mount_mutex);
const int slot = g_mount_slots.Find(mount_point->data);
if (slot == SaveDataMountSlots::FULL) {
return SAVE_DATA_ERROR_NOT_MOUNTED;
}
if (param_type == 0) {
const auto* p = static_cast<const SaveDataParam*>(param_buf);
LOGF("\t title = %s\n"
"\t sub_title = %s\n"
"\t detail = %s\n"
LOGF("\t title = %.128s\n"
"\t sub_title = %.128s\n"
"\t detail = %.1024s\n"
"\t user_param = %u\n",
p->title, p->sub_title, p->detail, p->user_param);
} else {
LOGF("\t unsupported param_type, accepting as no-op\n");
return write_save_file(save_param_path(g_mount_slots.Directory(static_cast<size_t>(slot))),
p, sizeof(*p));
}
return OK;
SaveDataParam param {};
const auto path = save_param_path(g_mount_slots.Directory(static_cast<size_t>(slot)));
const int status = read_save_param(path, &param);
if (status != OK) {
return status;
}
if (param_type == 4) {
std::memcpy(&param.user_param, param_buf, sizeof(param.user_param));
} else {
const auto field = param_text_field(param, param_type);
const auto* text = static_cast<const char*>(param_buf);
const size_t length = std::min(param_buf_size, field.size - 1);
const auto* end = static_cast<const char*>(std::memchr(text, '\0', length));
const size_t count = end != nullptr ? static_cast<size_t>(end - text) : length;
std::memset(field.data, 0, field.size);
std::memcpy(field.data, text, count);
}
return write_save_file(path, &param, sizeof(param));
}
int KYTY_SYSV_ABI SaveDataGetMountInfo(const SaveDataMountPoint* mount_point,
@@ -1139,12 +1255,12 @@ int KYTY_SYSV_ABI SaveDataGetMountInfo(const SaveDataMountPoint* mount_point,
return SAVE_DATA_ERROR_PARAMETER;
}
Common::LockGuard lock(g_mount_mutex);
if (g_mount_slots.Find(mount_point->data) == SaveDataMountSlots::FULL) {
const int slot = g_mount_slots.Find(mount_point->data);
if (slot == SaveDataMountSlots::FULL) {
return SAVE_DATA_ERROR_NOT_MOUNTED;
}
uint64_t blocks = 0;
const int status = read_save_blocks(
LibKernel::FileSystem::GetRealFilename(mount_point->data), &blocks);
const int status = read_save_blocks(g_mount_slots.Directory(static_cast<size_t>(slot)), &blocks);
if (status != OK) {
return status;
}
+16 -5
View File
@@ -2,9 +2,11 @@
#define EMULATOR_INCLUDE_EMULATOR_LIBS_SAVEDATAMOUNTSLOTS_H_
#include <array>
#include <filesystem>
#include <optional>
#include <string>
#include <string_view>
#include <system_error>
namespace Libs::SaveData {
@@ -14,12 +16,15 @@ public:
static constexpr int FULL = -1;
static constexpr size_t COUNT = 16;
[[nodiscard]] int FindAvailable(std::string_view directory) const {
[[nodiscard]] int FindAvailable(const std::filesystem::path& directory) const {
int available = FULL;
for (size_t index = 0; index < m_directories.size(); index++) {
const auto& mounted = m_directories[index];
if (mounted == directory) {
return BUSY;
if (mounted.has_value()) {
std::error_code error;
if (*mounted == directory || std::filesystem::equivalent(*mounted, directory, error)) {
return BUSY;
}
}
if (!mounted.has_value() && available == FULL) {
available = static_cast<int>(index);
@@ -28,7 +33,13 @@ public:
return available;
}
void Mount(size_t slot, std::string_view directory) { m_directories[slot] = directory; }
void Mount(size_t slot, const std::filesystem::path& directory) {
m_directories[slot] = directory;
}
[[nodiscard]] const std::filesystem::path& Directory(size_t slot) const {
return *m_directories[slot];
}
void Release(size_t slot) {
if (slot < m_directories.size()) {
@@ -59,7 +70,7 @@ public:
}
private:
std::array<std::optional<std::string>, COUNT> m_directories;
std::array<std::optional<std::filesystem::path>, COUNT> m_directories;
};
} // namespace Libs::SaveData