mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-20 10:56:58 +02:00
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:
+171
-55
@@ -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)), ¶m);
|
||||
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, ¶m, 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*>(¶m.user_param)
|
||||
: static_cast<const void*>(¶m.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, ¶m);
|
||||
if (status != OK) {
|
||||
return status;
|
||||
}
|
||||
if (param_type == 4) {
|
||||
std::memcpy(¶m.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, ¶m, 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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -330,11 +330,16 @@ std::vector<std::string> Search(int32_t user, const SceSaveDataTitleId* title =
|
||||
void TestClassicSavePaths() {
|
||||
Reset("CLASSIC");
|
||||
const std::vector<std::string> names {"save.1", "save1", "slot@A", "slotA"};
|
||||
fs::create_directories("_SaveData/CLASSIC/save.1");
|
||||
{
|
||||
std::ofstream saved("_SaveData/CLASSIC/save.1/progress");
|
||||
saved << "save.1";
|
||||
}
|
||||
const auto existing = DirName("save.1");
|
||||
struct SaveDataMount3 initial {};
|
||||
initial.user_id = 1;
|
||||
initial.dir_name = &existing;
|
||||
initial.mount_mode = 4;
|
||||
initial.blocks = 48;
|
||||
SaveDataMountResult first {};
|
||||
CHECK(SaveDataMount3(&initial, &first) == OK);
|
||||
std::ofstream(g_mounted_directory / "progress") << "save.1";
|
||||
CHECK(SaveDataUmount2(0, &first.mount_point) == OK);
|
||||
CHECK(Search(1) == std::vector<std::string> {"save.1"});
|
||||
for (const auto& text: names) {
|
||||
auto name = DirName(text.c_str());
|
||||
@@ -455,6 +460,123 @@ void TestSaveAllocations() {
|
||||
CHECK(!fs::exists(metadata));
|
||||
}
|
||||
|
||||
void TestClassicSaveParams() {
|
||||
Reset("PARAMS");
|
||||
const auto name = DirName("slot");
|
||||
struct SaveDataMount3 mount {};
|
||||
mount.user_id = 1;
|
||||
mount.dir_name = &name;
|
||||
mount.mount_mode = 4;
|
||||
mount.blocks = 48;
|
||||
SaveDataMountResult mounted {};
|
||||
CHECK(SaveDataMount3(&mount, &mounted) == OK);
|
||||
SaveDataParam param {};
|
||||
std::strcpy(param.title, "First save");
|
||||
std::strcpy(param.sub_title, "Chapter 2");
|
||||
std::strcpy(param.detail, "Progress");
|
||||
param.user_param = 42;
|
||||
CHECK(SaveDataSetParam(&mounted.mount_point, 0, nullptr, sizeof(param)) ==
|
||||
SAVE_DATA_ERROR_PARAMETER);
|
||||
CHECK(SaveDataSetParam(&mounted.mount_point, 0, ¶m, sizeof(param) - 1) ==
|
||||
SAVE_DATA_ERROR_PARAMETER);
|
||||
CHECK(SaveDataSetParam(&mounted.mount_point, 0, ¶m, sizeof(param)) == OK);
|
||||
const fs::path saved = "_SaveData/PARAMS/slot";
|
||||
CHECK(fs::exists(saved / "sce_sys/param.bin"));
|
||||
fs::create_directory(saved / "nested");
|
||||
std::ofstream(saved / "nested/progress") << "payload";
|
||||
SaveDataParam loaded {};
|
||||
size_t got = 0;
|
||||
CHECK(SaveDataGetParam(&mounted.mount_point, 0, &loaded, sizeof(loaded), &got) == OK);
|
||||
CHECK(got == sizeof(loaded) && std::string(loaded.title) == "First save");
|
||||
CHECK(std::string(loaded.sub_title) == "Chapter 2" && loaded.user_param == 42);
|
||||
CHECK(loaded.mtime > 0);
|
||||
fs::last_write_time(saved / "nested/progress",
|
||||
fs::last_write_time(saved / "sce_sys/param.bin") + std::chrono::hours(2));
|
||||
CHECK(SaveDataGetParam(&mounted.mount_point, 0, &loaded, sizeof(loaded), nullptr) == OK);
|
||||
const int64_t nested_mtime = loaded.mtime;
|
||||
CHECK(nested_mtime > static_cast<int64_t>(std::time(nullptr)) + 3600);
|
||||
fs::create_directory(saved / "sce_sys/param.bin.tmp");
|
||||
std::strcpy(param.title, "Failed update");
|
||||
CHECK(SaveDataSetParam(&mounted.mount_point, 0, ¶m, sizeof(param)) ==
|
||||
SAVE_DATA_ERROR_INTERNAL);
|
||||
CHECK(fs::remove(saved / "sce_sys/param.bin.tmp"));
|
||||
CHECK(SaveDataGetParam(&mounted.mount_point, 0, &loaded, sizeof(loaded), nullptr) == OK);
|
||||
CHECK(std::string(loaded.title) == "First save");
|
||||
std::strcpy(param.title, "Updated save");
|
||||
CHECK(SaveDataSetParam(&mounted.mount_point, 0, ¶m, sizeof(param)) == OK);
|
||||
CHECK(SaveDataGetParam(&mounted.mount_point, 0, &loaded, sizeof(loaded), nullptr) == OK);
|
||||
CHECK(std::string(loaded.title) == "Updated save");
|
||||
CHECK(SaveDataUmount2(0, &mounted.mount_point) == OK);
|
||||
CHECK(SaveDataGetParam(&mounted.mount_point, 0, &loaded, sizeof(loaded), nullptr) ==
|
||||
SAVE_DATA_ERROR_NOT_MOUNTED);
|
||||
|
||||
Reset("OTHER");
|
||||
SceSaveDataTitleId source_title {};
|
||||
std::strcpy(source_title.data, "PARAMS");
|
||||
struct SaveDataTransferringMount transfer {};
|
||||
transfer.user_id = 1;
|
||||
transfer.title_id = &source_title;
|
||||
transfer.dir_name = &name;
|
||||
SaveDataMountResult transferred {};
|
||||
CHECK(SaveDataTransferringMount(&transfer, &transferred) == OK);
|
||||
CHECK(SaveDataGetParam(&transferred.mount_point, 0, &loaded, sizeof(loaded), nullptr) == OK);
|
||||
CHECK(std::string(loaded.title) == "Updated save");
|
||||
std::error_code alias_error;
|
||||
fs::create_directory_symlink("PARAMS", "_SaveData/P_ALIAS", alias_error);
|
||||
if (!alias_error) {
|
||||
SceSaveDataTitleId alias_title {};
|
||||
std::strcpy(alias_title.data, "P_ALIAS");
|
||||
transfer.title_id = &alias_title;
|
||||
SaveDataMountResult duplicate {};
|
||||
CHECK(SaveDataTransferringMount(&transfer, &duplicate) == SAVE_DATA_ERROR_BUSY);
|
||||
transfer.title_id = &source_title;
|
||||
CHECK(fs::remove("_SaveData/P_ALIAS"));
|
||||
}
|
||||
#ifdef _WIN32
|
||||
SceSaveDataTitleId lowercase_title {};
|
||||
std::strcpy(lowercase_title.data, "params");
|
||||
transfer.title_id = &lowercase_title;
|
||||
SaveDataMountResult duplicate {};
|
||||
CHECK(SaveDataTransferringMount(&transfer, &duplicate) == SAVE_DATA_ERROR_BUSY);
|
||||
transfer.title_id = &source_title;
|
||||
#endif
|
||||
|
||||
SaveDataMountResult other {};
|
||||
CHECK(SaveDataMount3(&mount, &other) == OK);
|
||||
std::strcpy(param.title, "Other title");
|
||||
CHECK(SaveDataSetParam(&other.mount_point, 0, ¶m, sizeof(param)) == OK);
|
||||
constexpr char typed_title[] = "Typed title";
|
||||
CHECK(SaveDataSetParam(&other.mount_point, 1, typed_title, sizeof(typed_title)) == OK);
|
||||
char title[32] {};
|
||||
CHECK(SaveDataGetParam(&other.mount_point, 1, title, sizeof(title), &got) == OK);
|
||||
CHECK(std::string(title) == typed_title && got == sizeof(typed_title));
|
||||
const uint32_t user_param = 77;
|
||||
CHECK(SaveDataSetParam(&other.mount_point, 4, &user_param, sizeof(user_param)) == OK);
|
||||
CHECK(SaveDataGetParam(&other.mount_point, 0, &loaded, sizeof(loaded), nullptr) == OK);
|
||||
CHECK(std::string(loaded.title) == typed_title && loaded.user_param == user_param);
|
||||
CHECK(SaveDataGetParam(&other.mount_point, 5, &loaded.mtime, sizeof(loaded.mtime), nullptr) == OK);
|
||||
CHECK(loaded.mtime > 0);
|
||||
CHECK(SaveDataSetParam(&other.mount_point, 5, &loaded.mtime, sizeof(loaded.mtime)) ==
|
||||
SAVE_DATA_ERROR_PARAMETER);
|
||||
CHECK(SaveDataGetParam(&transferred.mount_point, 0, &loaded, sizeof(loaded), nullptr) == OK);
|
||||
CHECK(std::string(loaded.title) == "Updated save");
|
||||
CHECK(SaveDataUmount2(0, &other.mount_point) == OK);
|
||||
CHECK(SaveDataUmount2(0, &transferred.mount_point) == OK);
|
||||
|
||||
std::array<SceSaveDataDirName, 1> names {};
|
||||
std::array<SaveDataParam, 1> params {};
|
||||
SaveDataDirNameSearchCond cond {};
|
||||
cond.user_id = 1;
|
||||
cond.title_id = &source_title;
|
||||
SaveDataDirNameSearchResult result {};
|
||||
result.dir_names = names.data();
|
||||
result.dir_names_num = names.size();
|
||||
result.params = params.data();
|
||||
CHECK(SaveDataDirNameSearch(&cond, &result) == OK && result.set_num == 1);
|
||||
CHECK(std::string(names[0].data) == "slot" && std::string(params[0].title) == "Updated save");
|
||||
CHECK(params[0].mtime == nested_mtime);
|
||||
}
|
||||
|
||||
void RunChild(const fs::path& executable, const char* mode) {
|
||||
#ifdef _WIN32
|
||||
CHECK(_spawnl(_P_WAIT, executable.string().c_str(), executable.string().c_str(), mode,
|
||||
@@ -496,6 +618,7 @@ int main(int argc, char** argv) {
|
||||
TestFailedWritePreservesSave();
|
||||
TestClassicSavePaths();
|
||||
TestSaveAllocations();
|
||||
TestClassicSaveParams();
|
||||
CHECK(SaveDataTerminate() == OK);
|
||||
fs::current_path(previous);
|
||||
fs::remove_all(temp);
|
||||
|
||||
Reference in New Issue
Block a user