From 22a9392d236d993e756bef23230c76599f88962c Mon Sep 17 00:00:00 2001 From: nmzik Date: Sun, 25 Oct 2026 23:31:47 +0100 Subject: [PATCH] savedata: implement sceSaveDataConvert's synchronous checks Co-authored-by: Mac Co-authored-by: Senaxx --- src/libs/errno.h | 2 + src/libs/libSaveData.cpp | 75 +++++++++++++++++++++++++++++++++-- tests/SaveDataMemoryTests.cpp | 33 +++++++++++++++ 3 files changed, 106 insertions(+), 4 deletions(-) diff --git a/src/libs/errno.h b/src/libs/errno.h index e84e56cee..4fe390538 100644 --- a/src/libs/errno.h +++ b/src/libs/errno.h @@ -658,6 +658,8 @@ constexpr int SAVE_DATA_ERROR_BUSY_FOR_SAVING = -2137063402 constexpr int SAVE_DATA_ERROR_LIMITATION_OVER = -2137063401; /* 0x809F0017 */ constexpr int SAVE_DATA_ERROR_EVENT_BUSY = -2137063400; /* 0x809F0018 */ constexpr int SAVE_DATA_ERROR_PARAMSFO_TRANSFER_TITLE_ID_NOT_FOUND = -2137063399; /* 0x809F0019 */ +constexpr int SAVE_DATA_ERROR_NO_NEED_CONVERT = -2137063394; /* 0x809F001E */ +constexpr int SAVE_DATA_ERROR_BLOCKS_FEWER_THAN_SRC = -2137063392; /* 0x809F0020 */ } // namespace SaveData diff --git a/src/libs/libSaveData.cpp b/src/libs/libSaveData.cpp index 0b24e296e..dc26e0acc 100644 --- a/src/libs/libSaveData.cpp +++ b/src/libs/libSaveData.cpp @@ -231,6 +231,15 @@ struct SaveDataDelete { int32_t pad2; }; +struct SaveDataConvert { + int32_t user_id; + int32_t pad; + const SceSaveDataDirName* src_dir_name; + const SceSaveDataDirName* dst_dir_name; + uint64_t dst_blocks; + uint8_t reserved[24]; +}; + struct SaveDataEvent { uint32_t type; int32_t error_code; @@ -309,14 +318,20 @@ static std::filesystem::path save_user_directory(std::string_view title_id, int3 return directory / title_id; } -static std::filesystem::path save_directory(std::string_view title_id, std::string_view dir_name, - int32_t user_id) { +static bool is_memory_dir_name(std::string_view dir_name) { for (uint32_t slot = 0; slot < 4; slot++) { if (dir_name == memory_dir_name(slot)) { - return std::filesystem::path(SAVE_DATA_DIR) / title_id / dir_name / - std::to_string(user_id); + return true; } } + return false; +} + +static std::filesystem::path save_directory(std::string_view title_id, std::string_view dir_name, + int32_t user_id) { + if (is_memory_dir_name(dir_name)) { + return std::filesystem::path(SAVE_DATA_DIR) / title_id / dir_name / std::to_string(user_id); + } return save_user_directory(title_id, user_id) / dir_name; } @@ -1180,6 +1195,56 @@ int KYTY_SYSV_ABI SaveDataGetEventResult(const void* event_param, SaveDataEvent* return OK; } +int KYTY_SYSV_ABI SaveDataConvert(const SaveDataConvert* convert) { + PRINT_NAME(); + + const auto valid_name = [](const SceSaveDataDirName* name) { + return valid_path_component(name->data) && !is_memory_dir_name(name->data); + }; + if (convert == nullptr || convert->src_dir_name == nullptr || + !valid_name(convert->src_dir_name) || + (convert->dst_dir_name != nullptr && !valid_name(convert->dst_dir_name)) || + convert->dst_blocks < SAVE_DATA_BLOCKS_MIN || convert->dst_blocks > SAVE_DATA_BLOCKS_MAX) { + return SAVE_DATA_ERROR_PARAMETER; + } + if (convert->user_id < 0) { + return SAVE_DATA_ERROR_INVALID_LOGIN_USER; + } + + LOGF("\t user_id = %" PRId32 "\n" + "\t src_dir_name = %s\n" + "\t dst_dir_name = %s\n" + "\t dst_blocks = %" PRIu64 "\n", + convert->user_id, convert->src_dir_name->data, + convert->dst_dir_name != nullptr ? convert->dst_dir_name->data : "", + convert->dst_blocks); + + Common::LockGuard lock(g_mount_mutex); + const auto source = + save_directory(get_title_id(), convert->src_dir_name->data, convert->user_id); + if (!Common::File::IsDirectoryExisting(source)) { + return SAVE_DATA_ERROR_NOT_FOUND; + } + if (g_mount_slots.FindAvailable(source) == SaveDataMountSlots::BUSY) { + return SAVE_DATA_ERROR_BUSY; + } + if (convert->dst_dir_name != nullptr && + Common::File::IsDirectoryExisting( + save_directory(get_title_id(), convert->dst_dir_name->data, convert->user_id))) { + return SAVE_DATA_ERROR_EXISTS; + } + uint64_t blocks = 0; + if (read_save_blocks(source, &blocks) == OK) { + if (convert->dst_blocks == blocks) { + return SAVE_DATA_ERROR_NO_NEED_CONVERT; + } + if (convert->dst_blocks < blocks) { + return SAVE_DATA_ERROR_BLOCKS_FEWER_THAN_SRC; + } + } + EXIT("save data conversion of %s is not implemented\n", convert->src_dir_name->data); +} + int KYTY_SYSV_ABI SaveDataBackup(const SaveDataBackup* backup) { PRINT_NAME(); @@ -1313,6 +1378,7 @@ LIB_DEFINE(InitSaveDataNative_1) { LIB_FUNC("cduy9v4YmT4", ::Libs::SaveData::SaveDataSetSaveDataMemory2); LIB_FUNC("wiT9jeC7xPw", ::Libs::SaveData::SaveDataSyncSaveDataMemory); LIB_FUNC("j8xKtiFj0SY", ::Libs::SaveData::SaveDataGetEventResult); + LIB_FUNC("2mfSRGdshtk", ::Libs::SaveData::SaveDataConvert); LIB_FUNC("WAzWTZm1H+I", ::Libs::SaveData::SaveDataTransferringMount); LIB_FUNC("RjMlsR8EXrw", ::Libs::SaveData::SaveDataTransferringMount); LIB_FUNC("z1JA8-iJt3k", ::Libs::SaveData::SaveDataBackup); @@ -1343,6 +1409,7 @@ LIB_DEFINE(InitSaveData_1) { LIB_FUNC("cduy9v4YmT4", SaveData::SaveDataSetSaveDataMemory2); LIB_FUNC("wiT9jeC7xPw", SaveData::SaveDataSyncSaveDataMemory); LIB_FUNC("j8xKtiFj0SY", SaveData::SaveDataGetEventResult); + LIB_FUNC("2mfSRGdshtk", SaveData::SaveDataConvert); LIB_FUNC("WAzWTZm1H+I", SaveData::SaveDataTransferringMount); LIB_FUNC("RjMlsR8EXrw", SaveData::SaveDataTransferringMount); LIB_FUNC("z1JA8-iJt3k", SaveData::SaveDataBackup); diff --git a/tests/SaveDataMemoryTests.cpp b/tests/SaveDataMemoryTests.cpp index 8f976d2bc..a39dae7fd 100644 --- a/tests/SaveDataMemoryTests.cpp +++ b/tests/SaveDataMemoryTests.cpp @@ -396,6 +396,38 @@ void TestClassicSavePaths() { CHECK(Search(1) == names); } +void TestConvert() { + Reset("CONVERT"); + const auto source = DirName("save"); + const auto memory = DirName("sce_sdmemory"); + struct SaveDataConvert convert {}; + convert.user_id = 1; + convert.src_dir_name = &memory; + convert.dst_blocks = 96; + CHECK(SaveDataConvert(&convert) == SAVE_DATA_ERROR_PARAMETER); + // A missing source fails at once. + convert.src_dir_name = &source; + CHECK(SaveDataConvert(&convert) == SAVE_DATA_ERROR_NOT_FOUND); + struct SaveDataMount3 mount {}; + mount.user_id = 1; + mount.dir_name = &source; + mount.mount_mode = 4; + mount.blocks = 96; + SaveDataMountResult mounted {}; + CHECK(SaveDataMount3(&mount, &mounted) == OK); + CHECK(SaveDataConvert(&convert) == SAVE_DATA_ERROR_BUSY); + CHECK(SaveDataUmount2(0, &mounted.mount_point) == OK); + CHECK(SaveDataConvert(&convert) == SAVE_DATA_ERROR_NO_NEED_CONVERT); + convert.dst_blocks = 95; + CHECK(SaveDataConvert(&convert) == SAVE_DATA_ERROR_BLOCKS_FEWER_THAN_SRC); + convert.dst_dir_name = &source; + CHECK(SaveDataConvert(&convert) == SAVE_DATA_ERROR_EXISTS); + for (const uint64_t blocks: {47u, 16385u}) { + convert.dst_blocks = blocks; + CHECK(SaveDataConvert(&convert) == SAVE_DATA_ERROR_PARAMETER); + } +} + void TestGuestSaveIsolation() { Reset("MULTIUSER"); const auto name = DirName("slot"); @@ -670,6 +702,7 @@ int main(int argc, char** argv) { TestIsolationAndSync(); TestFailedWritePreservesSave(); TestClassicSavePaths(); + TestConvert(); TestGuestSaveIsolation(); TestSaveAllocations(); TestClassicSaveParams();