#include "common/abi.h" #include "common/assert.h" #include "common/common.h" #include "common/file.h" #include "common/logging/log.h" #include "common/stringUtils.h" #include "common/threads.h" #include "kernel/fileSystem.h" #include "libs/errno.h" #include "libs/libs.h" #include "libs/saveDataMountSlots.h" #include "loader/symbolDatabase.h" #include "loader/systemContent.h" #include #include #include #include #include #include namespace Libs { LIB_VERSION("SaveData", 1, "SaveData", 1, 1); namespace SaveData { // TODO(): specify dir at launcher static constexpr char SAVE_DATA_DIR[] = "_SaveData"; static constexpr uint64_t SAVE_DATA_BLOCKS_MIN = 48; static constexpr uint64_t SAVE_DATA_BLOCKS_MAX = 16384; struct SceSaveDataDirName { char data[32]; }; struct SceSaveDataTitleId { char data[10]; char padding[6]; }; struct SceSaveDataFingerprint { char data[65]; char padding[15]; }; struct SaveDataParam; enum class SaveDataSortKey : uint32_t { DirName = 0, UserParam = 1, Blocks = 2, Mtime = 3, FreeBlocks = 5, }; enum class SaveDataSortOrder : uint32_t { Ascent = 0, Descent = 1, }; struct SaveDataDirNameSearchCond { int32_t user_id; int32_t pad; const SceSaveDataTitleId* title_id; const SceSaveDataDirName* dir_name; SaveDataSortKey key; SaveDataSortOrder order; uint8_t reserved[32]; }; struct SaveDataSearchInfo { uint64_t blocks; uint64_t free_blocks; uint8_t reserved[32]; }; struct SaveDataDirNameSearchResult { uint32_t hit_num; int32_t pad; SceSaveDataDirName* dir_names; uint32_t dir_names_num; uint32_t set_num; SaveDataParam* params; SaveDataSearchInfo* infos; uint8_t reserved[12]; int32_t pad2; }; struct SaveDataMountPoint { char data[16]; }; struct SaveDataMount3 { int user_id; int pad; const SceSaveDataDirName* dir_name; uint64_t blocks; uint64_t system_blocks; uint32_t mount_mode; int pad2; int32_t resource; uint8_t reserved[32]; }; struct SaveDataMountResult { SaveDataMountPoint mount_point; uint64_t required_blocks; uint32_t unused; uint32_t mount_status; uint8_t reserved[28]; int pad; }; struct SaveDataParam { char title[128]; char sub_title[128]; char detail[1024]; uint32_t user_param; int pad; int64_t mtime; uint8_t reserved[32]; }; struct SaveDataMountInfo { uint64_t blocks; uint64_t free_blocks; uint8_t reserved[32]; }; struct SaveDataIcon { void* buf; size_t buf_size; size_t data_size; uint8_t reserved[32]; }; struct SaveDataMemoryData { void* buf; size_t buf_size; int64_t offset; uint8_t reserved[40]; }; struct SaveDataMemoryGet2 { int32_t user_id; uint8_t padding[4]; SaveDataMemoryData* data; SaveDataParam* param; SaveDataIcon* icon; uint32_t slot_id; uint8_t reserved[28]; }; struct SaveDataMemorySetup2 { uint32_t option; int32_t user_id; size_t memory_size; size_t icon_memory_size; const SaveDataParam* init_param; const SaveDataIcon* init_icon; uint32_t slot_id; uint8_t reserved[20]; }; struct SaveDataMemorySetupResult { size_t existed_memory_size; uint8_t reserved[16]; }; struct SaveDataMemorySet2 { int32_t user_id; uint8_t padding[4]; const SaveDataMemoryData* data; const SaveDataParam* param; const SaveDataIcon* icon; uint32_t data_num; uint32_t slot_id; uint8_t reserved[24]; }; struct SaveDataMemorySync { int32_t user_id; uint32_t slot_id; uint32_t option; uint8_t reserved[28]; }; struct SaveDataTransferringMount { int32_t user_id; const SceSaveDataTitleId* title_id; const SceSaveDataDirName* dir_name; const void* fingerprint; uint8_t reserved[32]; }; struct SaveDataPrepareParam { int32_t resource; uint32_t prepare_mode; uint8_t reserved[32]; }; struct SaveDataCommitParam { int32_t resource; uint32_t commit_mode; uint8_t reserved[32]; }; struct SaveDataDelete { int32_t user_id; int32_t pad; const SceSaveDataTitleId* title_id; const SceSaveDataDirName* dir_name; uint32_t unused; uint8_t reserved[32]; int32_t pad2; }; struct SaveDataEvent { uint32_t type; int32_t error_code; int32_t user_id; uint8_t padding[4]; SceSaveDataTitleId title_id; SceSaveDataDirName dir_name; uint8_t reserved[40]; }; struct SaveDataBackup { int32_t user_id; int32_t pad; const SceSaveDataTitleId* title_id; const SceSaveDataDirName* dir_name; const void* fingerprint; uint8_t reserved[32]; }; static constexpr uint32_t SAVE_DATA_UMOUNT_MODE_BACKUP_ASYNC = (1u << 16u); static constexpr uint32_t SAVE_DATA_COMMIT_MODE_BACKUP_ASYNC = 1u; static constexpr uint32_t SAVE_DATA_EVENT_TYPE_UMOUNT_BACKUP_END = 1u; static constexpr uint32_t SAVE_DATA_EVENT_TYPE_BACKUP_END = 2u; static constexpr uint32_t SAVE_DATA_EVENT_TYPE_MEMORY_SYNC_END = 3u; static constexpr uint32_t SAVE_DATA_EVENT_TYPE_COMMIT_BACKUP_END = 4u; static constexpr size_t SAVE_DATA_MEMORY_MAX_SIZE = 32 * 1024 * 1024; static constexpr uint32_t SAVE_DATA_MEMORY_SET_PARAM = 1u; static constexpr uint32_t SAVE_DATA_MEMORY_DOUBLE_BUFFER = 2u; struct SaveDataMemory { std::vector data; SaveDataParam param {}; uint32_t option = 0; }; static std::map g_save_data_memory; static int32_t g_next_transaction_resource = 1; static std::deque g_save_data_events; static SaveDataMountSlots g_mount_slots; static Common::Mutex g_mount_mutex; static bool valid_path_component(std::string_view name) { return !name.empty() && name != "." && name != ".." && name.find_first_not_of( "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-.@") == std::string_view::npos; } template static bool valid_path_component(const char (&name)[N]) { const auto end = std::find(name, name + N, '\0'); return end != name + N && valid_path_component(std::string_view(name, end - name)); } static std::string get_title_id() { std::string title_id; if (!Loader::SystemContentParamSfoGetString("TITLE_ID", &title_id) || !valid_path_component(title_id)) { title_id = "UNKNOWN"; } return title_id; } static std::string memory_dir_name(uint32_t slot_id) { return "sce_sdmemory" + (slot_id == 0 ? std::string() : std::to_string(slot_id)); } static std::filesystem::path save_directory(std::string_view title_id, std::string_view dir_name, int32_t user_id) { const auto directory = std::filesystem::path(SAVE_DATA_DIR) / title_id / dir_name; for (uint32_t slot = 0; slot < 4; slot++) { if (dir_name == memory_dir_name(slot)) { return directory / std::to_string(user_id); } } return directory; } static std::filesystem::path memory_directory(int32_t user_id, uint32_t slot_id) { return save_directory(get_title_id(), memory_dir_name(slot_id), user_id); } static bool valid_memory_range(const SaveDataMemoryData& data, size_t size) { return data.buf != nullptr && data.offset >= 0 && static_cast(data.offset) <= size && data.buf_size <= size - static_cast(data.offset); } // Replacing a flushed temporary file protects the // previous save if writing fails or the emulator exits partway through an update. static int write_save_file(const std::filesystem::path& path, const void* data, uint32_t size) { const auto temporary = std::filesystem::path(path.string() + ".tmp"); Common::File file; if (!file.Create(temporary)) { return SAVE_DATA_ERROR_INTERNAL; } uint32_t written = 0; file.Write(data, size, &written); const bool flushed = file.Flush(); file.Close(); std::error_code error; if (written == size && flushed) { // Common::File::RenameFile deliberately refuses to replace an existing file. std::filesystem::rename(temporary, path, error); if (!error) { return OK; } } std::filesystem::remove(temporary, error); return SAVE_DATA_ERROR_INTERNAL; } static int read_save_blocks(const std::filesystem::path& directory, uint64_t* blocks) { Common::File file(directory / "sce_sys" / "blocks.bin", Common::File::Mode::Read); if (file.IsInvalid() || file.Size() != sizeof(*blocks)) { return SAVE_DATA_ERROR_BROKEN; } uint64_t value = 0; uint32_t read = 0; file.Read(&value, sizeof(value), &read); if (read != sizeof(value) || value < SAVE_DATA_BLOCKS_MIN || value > SAVE_DATA_BLOCKS_MAX) { return SAVE_DATA_ERROR_BROKEN; } *blocks = value; return OK; } static int save_memory(const std::filesystem::path& directory, const SaveDataMemory& memory) { std::error_code error; std::filesystem::create_directories(directory, error); if (error) { return SAVE_DATA_ERROR_INTERNAL; } // Display metadata is a separate host sidecar, leaving memory.dat as raw guest bytes. int result = write_save_file(directory / "param.bin", &memory.param, sizeof(memory.param)); if (result == OK) { result = write_save_file(directory / "memory.dat", memory.data.data(), memory.data.size()); } return result; } static int load_memory(const std::filesystem::path& directory, SaveDataMemory* memory, size_t* existed_size) { std::error_code error; const bool exists = std::filesystem::exists(directory / "memory.dat", error); if (error) { return SAVE_DATA_ERROR_INTERNAL; } if (!exists) { return OK; } Common::File file(directory / "memory.dat", Common::File::Mode::Read); if (file.IsInvalid()) { return SAVE_DATA_ERROR_INTERNAL; } *existed_size = file.Size(); if (*existed_size == 0 || *existed_size > SAVE_DATA_MEMORY_MAX_SIZE) { return SAVE_DATA_ERROR_BROKEN; } const auto size = static_cast(std::min(*existed_size, memory->data.size())); uint32_t read = 0; file.Read(memory->data.data(), size, &read); if (read != size) { 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; } static void queue_save_data_event(uint32_t type, int32_t user_id, const SceSaveDataTitleId* title_id, const SceSaveDataDirName* dir_name, int32_t error_code = OK) { SaveDataEvent event = {}; event.type = type; event.error_code = error_code; event.user_id = user_id; if (title_id != nullptr) { std::memcpy(&event.title_id, title_id, sizeof(event.title_id)); } if (dir_name != nullptr) { std::memcpy(&event.dir_name, dir_name, sizeof(event.dir_name)); } g_save_data_events.push_back(event); if (g_save_data_events.size() > 20) { g_save_data_events.pop_front(); } } static bool dir_name_match(const char* str, const char* pattern) { while (*str != '\0' && *pattern != '\0') { if (*pattern == '%') { for (const char* s = str;; s++) { if (dir_name_match(s, pattern + 1)) { return true; } if (*s == '\0') { break; } } return false; } if (*pattern == '_') { str++; pattern++; continue; } if (*pattern != *str) { return false; } str++; pattern++; } return *str == '\0' && *pattern == '\0'; } static int mount_save_data(int slot, std::string_view dir_name, const std::string& directory, uint32_t status, SaveDataMountResult* result) { const std::string mount_point = SaveDataMountSlots::MountPoint(static_cast(slot)); LibKernel::FileSystem::Mount(directory, mount_point); g_mount_slots.Mount(static_cast(slot), dir_name); std::snprintf(result->mount_point.data, sizeof(result->mount_point.data), "%s", mount_point.c_str()); result->required_blocks = 0; result->mount_status = status; return OK; } int KYTY_SYSV_ABI SaveDataInitialize3(const void* /*init*/) { PRINT_NAME(); // EXIT_NOT_IMPLEMENTED(init != nullptr); return OK; } int KYTY_SYSV_ABI SaveDataTerminate() { PRINT_NAME(); Common::LockGuard lock(g_mount_mutex); if (!g_mount_slots.Empty()) { return SAVE_DATA_ERROR_BUSY; } g_save_data_events.clear(); g_save_data_memory.clear(); return OK; } int KYTY_SYSV_ABI SaveDataCreateTransactionResource(uint32_t size) { PRINT_NAME(); LOGF("\t size = %" PRIu32 "\n", size); return g_next_transaction_resource++; } int KYTY_SYSV_ABI SaveDataDeleteTransactionResource(int32_t resource) { PRINT_NAME(); LOGF("\t resource = %" PRId32 "\n", resource); return OK; } int KYTY_SYSV_ABI SaveDataDirNameSearch(const SaveDataDirNameSearchCond* cond, SaveDataDirNameSearchResult* result) { PRINT_NAME(); if (cond == nullptr || result == nullptr || (cond->title_id != nullptr && !valid_path_component(cond->title_id->data)) || (result->dir_names_num != 0 && result->dir_names == nullptr) || (cond->dir_name != nullptr && std::memchr(cond->dir_name->data, 0, sizeof(cond->dir_name->data)) == nullptr) || static_cast(cond->key) > static_cast(SaveDataSortKey::FreeBlocks) || static_cast(cond->order) > static_cast(SaveDataSortOrder::Descent)) { return SAVE_DATA_ERROR_PARAMETER; } if (cond->user_id < 0) { return SAVE_DATA_ERROR_INVALID_LOGIN_USER; } LOGF("\t user_id = %d\n" "\t title_id = %s\n" "\t dir_name = %s\n" "\t key = %" PRIu32 "\n" "\t order = %" PRIu32 "\n" "\t dir_names_num = %" PRIu32 "\n", cond->user_id, cond->title_id != nullptr ? cond->title_id->data : "", cond->dir_name != nullptr ? cond->dir_name->data : "", static_cast(cond->key), static_cast(cond->order), result->dir_names_num); result->hit_num = 0; result->pad = 0; result->set_num = 0; std::memset(result->reserved, 0, sizeof(result->reserved)); result->pad2 = 0; Common::LockGuard lock(g_mount_mutex); const std::string title_id = cond->title_id != nullptr ? cond->title_id->data : get_title_id(); const auto root = std::filesystem::path(SAVE_DATA_DIR) / title_id; std::vector dir_list; 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)) { 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())) { dir_list.push_back(entry.name); } } } } std::sort(dir_list.begin(), dir_list.end(), [](const std::string& a, const std::string& b) { return std::strcmp(a.c_str(), b.c_str()) < 0; }); if (cond->order == SaveDataSortOrder::Descent) { std::vector reversed; for (size_t i = dir_list.size(); i > 0; i--) { reversed.push_back(dir_list[i - 1]); } dir_list = reversed; } auto max_count = (result->dir_names_num < dir_list.size() ? result->dir_names_num : dir_list.size()); result->hit_num = static_cast(dir_list.size()); result->set_num = static_cast(max_count); for (size_t i = 0; i < max_count; i++) { 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] = {}; } if (result->infos != nullptr) { auto& info = result->infos[i]; info = {}; const int status = read_save_blocks(root / dir_list[i], &info.blocks); if (status != OK) { return status; } // Host-directory saves report their allocation as free space. info.free_blocks = info.blocks; } } return OK; } int KYTY_SYSV_ABI SaveDataMount3(const SaveDataMount3* mount, SaveDataMountResult* mount_result) { PRINT_NAME(); if (mount == nullptr || mount_result == nullptr || mount->dir_name == nullptr || !valid_path_component(mount->dir_name->data)) { return SAVE_DATA_ERROR_PARAMETER; } if (mount->user_id < 0) { return SAVE_DATA_ERROR_INVALID_LOGIN_USER; } LOGF("\t user_id = %d\n" "\t dir_name = %s\n" "\t blocks = %" PRIu64 "\n" "\t system_blocks = %" PRIu64 "\n" "\t mount_mode = %" PRIu32 "\n" "\t resource = %" PRId32 "\n", mount->user_id, mount->dir_name->data, mount->blocks, mount->system_blocks, mount->mount_mode, mount->resource); *mount_result = {}; 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 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); if (slot == SaveDataMountSlots::BUSY) { return SAVE_DATA_ERROR_BUSY; } if (slot == SaveDataMountSlots::FULL) { return SAVE_DATA_ERROR_MOUNT_FULL; } if (!create && !create2 && !open) { EXIT("unknown mount mode: %u", mount->mount_mode); } if (open && !Common::File::IsDirectoryExisting(mount_dir)) { return SAVE_DATA_ERROR_NOT_FOUND; } if (create && Common::File::IsDirectoryExisting(mount_dir)) { return SAVE_DATA_ERROR_EXISTS; } bool created = false; if ((create || create2) && !Common::File::IsDirectoryExisting(mount_dir)) { 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"; std::error_code error; std::filesystem::create_directories(metadata, error); if (error) { return SAVE_DATA_ERROR_INTERNAL; } const int status = write_save_file(metadata / "blocks.bin", &mount->blocks, sizeof(mount->blocks)); if (status != OK) { std::filesystem::remove_all(mount_dir, error); return status; } created = true; } return mount_save_data(slot, dir_name, mount_dir, created ? 1u : 0u, mount_result); } int KYTY_SYSV_ABI SaveDataSetupSaveDataMemory2(const SaveDataMemorySetup2* setup_param, SaveDataMemorySetupResult* result) { PRINT_NAME(); if (setup_param == nullptr || setup_param->slot_id >= 4 || setup_param->memory_size == 0 || setup_param->memory_size > SAVE_DATA_MEMORY_MAX_SIZE || (setup_param->option & ~3u) != 0) { return SAVE_DATA_ERROR_PARAMETER; } if (setup_param->user_id < 0) { return SAVE_DATA_ERROR_INVALID_LOGIN_USER; } LOGF("\t option = 0x%08" PRIx32 "\n" "\t user_id = %" PRId32 "\n" "\t memory_size = %" PRIu64 "\n" "\t icon_memory_size = %" PRIu64 "\n" "\t slot_id = %" PRIu32 "\n", setup_param->option, setup_param->user_id, static_cast(setup_param->memory_size), static_cast(setup_param->icon_memory_size), setup_param->slot_id); 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)) == SaveDataMountSlots::BUSY) { return SAVE_DATA_ERROR_BUSY; } size_t total = setup_param->memory_size * ((setup_param->option & SAVE_DATA_MEMORY_DOUBLE_BUFFER) != 0 ? 2 : 1); for (const auto& [path, memory]: g_save_data_memory) { total += memory.data.size() * ((memory.option & SAVE_DATA_MEMORY_DOUBLE_BUFFER) != 0 ? 2 : 1); } if (g_save_data_memory.size() >= 4 || total > SAVE_DATA_MEMORY_MAX_SIZE) { return SAVE_DATA_ERROR_LIMITATION_OVER; } SaveDataMemory memory; memory.data.resize(setup_param->memory_size); memory.option = setup_param->option; std::snprintf(memory.param.title, sizeof(memory.param.title), "Saved Data"); size_t existed_size = 0; const int status = load_memory(directory, &memory, &existed_size); if (status != OK) { return status; } if (existed_size == 0 && setup_param->init_param != nullptr && (memory.option & SAVE_DATA_MEMORY_SET_PARAM) != 0) { memory.param = *setup_param->init_param; memory.param.mtime = 0; } g_save_data_memory.emplace(directory, std::move(memory)); if (result != nullptr) { *result = {}; result->existed_memory_size = existed_size; } return OK; } int KYTY_SYSV_ABI SaveDataGetSaveDataMemory2(SaveDataMemoryGet2* get_param) { PRINT_NAME(); if (get_param == nullptr || get_param->slot_id >= 4) { return SAVE_DATA_ERROR_PARAMETER; } if (get_param->user_id < 0) { return SAVE_DATA_ERROR_INVALID_LOGIN_USER; } LOGF("\t user_id = %" PRId32 "\n" "\t data = 0x%016" PRIx64 "\n" "\t param = 0x%016" PRIx64 "\n" "\t icon = 0x%016" PRIx64 "\n" "\t slot_id = %" PRIu32 "\n", get_param->user_id, reinterpret_cast(get_param->data), reinterpret_cast(get_param->param), reinterpret_cast(get_param->icon), get_param->slot_id); Common::LockGuard lock(g_mount_mutex); const auto it = g_save_data_memory.find(memory_directory(get_param->user_id, get_param->slot_id)); if (it == g_save_data_memory.end()) { return SAVE_DATA_ERROR_MEMORY_NOT_READY; } const auto& memory = it->second; if ((get_param->param != nullptr && (memory.option & SAVE_DATA_MEMORY_SET_PARAM) == 0) || (get_param->data != nullptr && !valid_memory_range(*get_param->data, memory.data.size()))) { return SAVE_DATA_ERROR_PARAMETER; } if (get_param->data != nullptr) { const auto& data = *get_param->data; std::memcpy(data.buf, memory.data.data() + data.offset, data.buf_size); } if (get_param->param != nullptr) { *get_param->param = memory.param; } if (get_param->icon != nullptr) { get_param->icon->data_size = 0; } return OK; } int KYTY_SYSV_ABI SaveDataSetSaveDataMemory2(const SaveDataMemorySet2* set_param) { PRINT_NAME(); if (set_param == nullptr || set_param->slot_id >= 4 || set_param->data_num > 5 || (set_param->data == nullptr && set_param->data_num != 0)) { return SAVE_DATA_ERROR_PARAMETER; } if (set_param->user_id < 0) { return SAVE_DATA_ERROR_INVALID_LOGIN_USER; } LOGF("\t user_id = %" PRId32 "\n" "\t data = 0x%016" PRIx64 "\n" "\t param = 0x%016" PRIx64 "\n" "\t icon = 0x%016" PRIx64 "\n" "\t data_num = %" PRIu32 "\n" "\t slot_id = %" PRIu32 "\n", set_param->user_id, reinterpret_cast(set_param->data), reinterpret_cast(set_param->param), reinterpret_cast(set_param->icon), set_param->data_num, set_param->slot_id); Common::LockGuard lock(g_mount_mutex); const auto it = g_save_data_memory.find(memory_directory(set_param->user_id, set_param->slot_id)); if (it == g_save_data_memory.end()) { return SAVE_DATA_ERROR_MEMORY_NOT_READY; } if (set_param->param != nullptr && (it->second.option & SAVE_DATA_MEMORY_SET_PARAM) == 0) { return SAVE_DATA_ERROR_PARAMETER; } // Some games supply one descriptor with an implicit zero count. const uint32_t data_num = set_param->data != nullptr && set_param->data_num == 0 ? 1 : set_param->data_num; for (uint32_t i = 0; i < data_num; i++) { if (!valid_memory_range(set_param->data[i], it->second.data.size())) { return SAVE_DATA_ERROR_PARAMETER; } } if (data_num == 0 && set_param->param == nullptr) { return OK; } // Save the complete update before publishing it so progress survives process exit. auto updated = it->second; for (uint32_t i = 0; i < data_num; i++) { const auto& data = set_param->data[i]; std::memcpy(updated.data.data() + data.offset, data.buf, data.buf_size); } if (set_param->param != nullptr) { updated.param = *set_param->param; } updated.param.mtime = static_cast(std::time(nullptr)); const int status = save_memory(it->first, updated); if (status == OK) { it->second = std::move(updated); } return status; } int KYTY_SYSV_ABI SaveDataTransferringMount(const SaveDataTransferringMount* mount, SaveDataMountResult* mount_result) { PRINT_NAME(); if (mount == nullptr || mount_result == nullptr || mount->title_id == nullptr || mount->dir_name == nullptr || !valid_path_component(mount->title_id->data) || !valid_path_component(mount->dir_name->data)) { return SAVE_DATA_ERROR_PARAMETER; } if (mount->user_id < 0) { return SAVE_DATA_ERROR_INVALID_LOGIN_USER; } LOGF("\t user_id = %" PRId32 "\n" "\t title_id = %s\n" "\t dir_name = %s\n", mount->user_id, mount->title_id->data, mount->dir_name->data); *mount_result = {}; 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); if (slot == SaveDataMountSlots::BUSY) { return SAVE_DATA_ERROR_BUSY; } if (slot == SaveDataMountSlots::FULL) { return SAVE_DATA_ERROR_MOUNT_FULL; } if (!Common::File::IsDirectoryExisting(mount_dir)) { return SAVE_DATA_ERROR_NOT_FOUND; } return mount_save_data(slot, dir_name, mount_dir, 0, mount_result); } int KYTY_SYSV_ABI SaveDataUmount2(uint32_t mode, const SaveDataMountPoint* mount_point) { PRINT_NAME(); if (mount_point == nullptr) { return SAVE_DATA_ERROR_PARAMETER; } LOGF("\t mode = %" PRIu32 "\n" "\t mount_point = %s\n", mode, mount_point->data); Common::LockGuard lock(g_mount_mutex); const std::string point = mount_point->data; const int slot = g_mount_slots.Find(point); if (slot == SaveDataMountSlots::FULL) { return SAVE_DATA_ERROR_NOT_FOUND; } if ((mode & SAVE_DATA_UMOUNT_MODE_BACKUP_ASYNC) != 0) { queue_save_data_event(SAVE_DATA_EVENT_TYPE_UMOUNT_BACKUP_END, 0, nullptr, nullptr); } LibKernel::FileSystem::Umount(point); g_mount_slots.Release(static_cast(slot)); return OK; } int KYTY_SYSV_ABI SaveDataPrepare(const SaveDataMountPoint* mount_point, const SaveDataPrepareParam* param) { PRINT_NAME(); if (mount_point == nullptr || param == nullptr) { return SAVE_DATA_ERROR_PARAMETER; } LOGF("\t mount_point = %s\n" "\t resource = %" PRId32 "\n" "\t prepare_mode = %" PRIu32 "\n", mount_point->data, param->resource, param->prepare_mode); return OK; } int KYTY_SYSV_ABI SaveDataCommit(const SaveDataCommitParam* param) { PRINT_NAME(); if (param == nullptr) { return SAVE_DATA_ERROR_PARAMETER; } LOGF("\t resource = %" PRId32 "\n" "\t commit_mode = %" PRIu32 "\n", param->resource, param->commit_mode); if ((param->commit_mode & SAVE_DATA_COMMIT_MODE_BACKUP_ASYNC) != 0) { Common::LockGuard lock(g_mount_mutex); queue_save_data_event(SAVE_DATA_EVENT_TYPE_COMMIT_BACKUP_END, 0, nullptr, nullptr); } return OK; } int KYTY_SYSV_ABI SaveDataDelete(const SaveDataDelete* del) { PRINT_NAME(); if (del == nullptr || del->dir_name == nullptr || !valid_path_component(del->dir_name->data) || (del->title_id != nullptr && !valid_path_component(del->title_id->data))) { return SAVE_DATA_ERROR_PARAMETER; } if (del->user_id < 0) { return SAVE_DATA_ERROR_INVALID_LOGIN_USER; } LOGF("\t user_id = %" PRId32 "\n" "\t title_id = %s\n" "\t dir_name = %s\n", del->user_id, del->title_id != nullptr ? del->title_id->data : "", del->dir_name->data); 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 || g_save_data_memory.contains(dir)) { return SAVE_DATA_ERROR_BUSY; } std::error_code error; std::filesystem::remove_all(dir, error); return error ? SAVE_DATA_ERROR_INTERNAL : OK; } int KYTY_SYSV_ABI SaveDataGetParam(const SaveDataMountPoint* mount_point, uint32_t param_type, void* param_buf, size_t param_buf_size, size_t* got_size) { PRINT_NAME(); if (mount_point == nullptr || param_buf == nullptr) { 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); 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; } std::memset(param_buf, 0, param_buf_size); if (got_size != nullptr) { *got_size = param_buf_size; } return OK; } int KYTY_SYSV_ABI SaveDataLoadIcon(const SaveDataMountPoint* mount_point, SaveDataIcon* icon) { PRINT_NAME(); if (mount_point == nullptr || icon == nullptr) { return SAVE_DATA_ERROR_PARAMETER; } LOGF("\t mount_point = %s\n" "\t buf = %016" PRIx64 "\n" "\t buf_size = %" PRIu64 "\n", mount_point->data, reinterpret_cast(icon->buf), icon->buf_size); icon->data_size = 0; return OK; } int KYTY_SYSV_ABI SaveDataSaveIconByPath(const SaveDataMountPoint* mount_point, const char* path) { PRINT_NAME(); if (mount_point == nullptr || path == nullptr) { return SAVE_DATA_ERROR_PARAMETER; } LOGF("\t mount_point = %s\n" "\t path = %s\n", mount_point->data, path); return OK; } int KYTY_SYSV_ABI SaveDataSyncSaveDataMemory(const SaveDataMemorySync* sync_param) { PRINT_NAME(); if (sync_param == nullptr || sync_param->slot_id >= 4 || sync_param->option > 1) { return SAVE_DATA_ERROR_PARAMETER; } if (sync_param->user_id < 0) { return SAVE_DATA_ERROR_INVALID_LOGIN_USER; } Common::LockGuard lock(g_mount_mutex); const auto it = g_save_data_memory.find(memory_directory(sync_param->user_id, sync_param->slot_id)); if (it == g_save_data_memory.end()) { 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) { return SAVE_DATA_ERROR_BUSY; } const int status = save_memory(it->first, it->second); if (sync_param->option == 1) { return status; } SceSaveDataTitleId title {}; SceSaveDataDirName directory {}; std::snprintf(title.data, sizeof(title.data), "%s", get_title_id().c_str()); std::snprintf(directory.data, sizeof(directory.data), "%s", dir_name.c_str()); queue_save_data_event(SAVE_DATA_EVENT_TYPE_MEMORY_SYNC_END, sync_param->user_id, &title, &directory, status); return OK; } int KYTY_SYSV_ABI SaveDataGetEventResult(const void* event_param, SaveDataEvent* event) { PRINT_NAME(); LOGF("\t event_param = 0x%016" PRIx64 "\n" "\t event = 0x%016" PRIx64 "\n", reinterpret_cast(event_param), reinterpret_cast(event)); if (event == nullptr) { return SAVE_DATA_ERROR_PARAMETER; } Common::LockGuard lock(g_mount_mutex); if (g_save_data_events.empty()) { return SAVE_DATA_ERROR_NOT_FOUND; } *event = g_save_data_events.front(); g_save_data_events.pop_front(); LOGF("\t type = %" PRIu32 "\n" "\t error_code = %" PRId32 "\n" "\t user_id = %" PRId32 "\n" "\t title_id = %.10s\n" "\t dir_name = %.32s\n", event->type, event->error_code, event->user_id, event->title_id.data, event->dir_name.data); return OK; } int KYTY_SYSV_ABI SaveDataBackup(const SaveDataBackup* backup) { PRINT_NAME(); LOGF("\t backup = 0x%016" PRIx64 "\n", reinterpret_cast(backup)); if (backup == nullptr || backup->dir_name == nullptr) { return SAVE_DATA_ERROR_PARAMETER; } LOGF("\t user_id = %" PRId32 "\n" "\t title_id = %.10s\n" "\t dir_name = %.32s\n", backup->user_id, backup->title_id != nullptr ? backup->title_id->data : "", backup->dir_name->data); Common::LockGuard lock(g_mount_mutex); queue_save_data_event(SAVE_DATA_EVENT_TYPE_BACKUP_END, backup->user_id, backup->title_id, backup->dir_name); return OK; } int KYTY_SYSV_ABI SaveDataSetParam(const SaveDataMountPoint* mount_point, uint32_t param_type, const void* param_buf, size_t param_buf_size) { PRINT_NAME(); EXIT_NOT_IMPLEMENTED(mount_point == nullptr); 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); if (param_type == 0) { const auto* p = static_cast(param_buf); LOGF("\t title = %s\n" "\t sub_title = %s\n" "\t detail = %s\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 OK; } int KYTY_SYSV_ABI SaveDataGetMountInfo(const SaveDataMountPoint* mount_point, SaveDataMountInfo* info) { PRINT_NAME(); if (mount_point == nullptr || info == nullptr || std::memchr(mount_point->data, 0, sizeof(mount_point->data)) == nullptr) { return SAVE_DATA_ERROR_PARAMETER; } Common::LockGuard lock(g_mount_mutex); if (g_mount_slots.Find(mount_point->data) == 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); if (status != OK) { return status; } *info = {}; info->blocks = blocks; info->free_blocks = blocks; return OK; } int KYTY_SYSV_ABI SaveDataSaveIcon(const SaveDataMountPoint* mount_point, const SaveDataIcon* icon) { EXIT_NOT_IMPLEMENTED(mount_point == nullptr); EXIT_NOT_IMPLEMENTED(icon == nullptr); LOGF("\t buf = %016" PRIx64 "\n" "\t buf_size = %" PRIu64 "\n" "\t data_size = %" PRIu64 "\n", reinterpret_cast(icon->buf), icon->buf_size, icon->data_size); return OK; } } // namespace SaveData namespace LibSaveDataNative { LIB_VERSION("SaveData_native", 1, "SaveData_native", 1, 1); LIB_DEFINE(InitSaveDataNative_1) { LIB_FUNC("TywrFKCoLGY", ::Libs::SaveData::SaveDataInitialize3); LIB_FUNC("dyIhnXq-0SM", ::Libs::SaveData::SaveDataDirNameSearch); LIB_FUNC("PHnuI4LhuRk", ::Libs::SaveData::SaveDataDirNameSearch); LIB_FUNC("ZP4e7rlzOUk", ::Libs::SaveData::SaveDataMount3); LIB_FUNC("gjRZNnw0JPE", ::Libs::SaveData::SaveDataCreateTransactionResource); LIB_FUNC("lJUQuaKqoKY", ::Libs::SaveData::SaveDataDeleteTransactionResource); LIB_FUNC("sDCBrmc61XU", ::Libs::SaveData::SaveDataPrepare); LIB_FUNC("ie7qhZ4X0Cc", ::Libs::SaveData::SaveDataCommit); LIB_FUNC("oQySEUfgXRA", ::Libs::SaveData::SaveDataSetupSaveDataMemory2); LIB_FUNC("QwOO7vegnV8", ::Libs::SaveData::SaveDataGetSaveDataMemory2); LIB_FUNC("cduy9v4YmT4", ::Libs::SaveData::SaveDataSetSaveDataMemory2); LIB_FUNC("wiT9jeC7xPw", ::Libs::SaveData::SaveDataSyncSaveDataMemory); LIB_FUNC("j8xKtiFj0SY", ::Libs::SaveData::SaveDataGetEventResult); LIB_FUNC("WAzWTZm1H+I", ::Libs::SaveData::SaveDataTransferringMount); LIB_FUNC("RjMlsR8EXrw", ::Libs::SaveData::SaveDataTransferringMount); LIB_FUNC("z1JA8-iJt3k", ::Libs::SaveData::SaveDataBackup); LIB_FUNC("uW4vfTwMQVo", ::Libs::SaveData::SaveDataUmount2); LIB_FUNC("S1GkePI17zQ", ::Libs::SaveData::SaveDataDelete); LIB_FUNC("85zul--eGXs", ::Libs::SaveData::SaveDataSetParam); LIB_FUNC("XgvSuIdnMlw", ::Libs::SaveData::SaveDataGetParam); LIB_FUNC("65VH0Qaaz6s", ::Libs::SaveData::SaveDataGetMountInfo); LIB_FUNC("c88Yy54Mx0w", ::Libs::SaveData::SaveDataSaveIcon); LIB_FUNC("Z7z6HXWORJY", ::Libs::SaveData::SaveDataSaveIconByPath); LIB_FUNC("cGjO3wM3V28", ::Libs::SaveData::SaveDataLoadIcon); LIB_FUNC("X4MYzukPc3g", ::Libs::SaveData::SaveDataDirNameSearch); LIB_FUNC("yKDy8S5yLA0", ::Libs::SaveData::SaveDataTerminate); } } // namespace LibSaveDataNative LIB_DEFINE(InitSaveData_1) { LIB_FUNC("TywrFKCoLGY", SaveData::SaveDataInitialize3); LIB_FUNC("dyIhnXq-0SM", SaveData::SaveDataDirNameSearch); LIB_FUNC("ZP4e7rlzOUk", SaveData::SaveDataMount3); LIB_FUNC("gjRZNnw0JPE", SaveData::SaveDataCreateTransactionResource); LIB_FUNC("lJUQuaKqoKY", SaveData::SaveDataDeleteTransactionResource); LIB_FUNC("sDCBrmc61XU", SaveData::SaveDataPrepare); LIB_FUNC("ie7qhZ4X0Cc", SaveData::SaveDataCommit); LIB_FUNC("oQySEUfgXRA", SaveData::SaveDataSetupSaveDataMemory2); LIB_FUNC("QwOO7vegnV8", SaveData::SaveDataGetSaveDataMemory2); LIB_FUNC("cduy9v4YmT4", SaveData::SaveDataSetSaveDataMemory2); LIB_FUNC("wiT9jeC7xPw", SaveData::SaveDataSyncSaveDataMemory); LIB_FUNC("j8xKtiFj0SY", SaveData::SaveDataGetEventResult); LIB_FUNC("WAzWTZm1H+I", SaveData::SaveDataTransferringMount); LIB_FUNC("RjMlsR8EXrw", SaveData::SaveDataTransferringMount); LIB_FUNC("z1JA8-iJt3k", SaveData::SaveDataBackup); LIB_FUNC("uW4vfTwMQVo", SaveData::SaveDataUmount2); LIB_FUNC("S1GkePI17zQ", SaveData::SaveDataDelete); LIB_FUNC("85zul--eGXs", SaveData::SaveDataSetParam); LIB_FUNC("XgvSuIdnMlw", SaveData::SaveDataGetParam); LIB_FUNC("65VH0Qaaz6s", SaveData::SaveDataGetMountInfo); LIB_FUNC("c88Yy54Mx0w", SaveData::SaveDataSaveIcon); LIB_FUNC("Z7z6HXWORJY", SaveData::SaveDataSaveIconByPath); LIB_FUNC("cGjO3wM3V28", SaveData::SaveDataLoadIcon); LIB_FUNC("X4MYzukPc3g", SaveData::SaveDataDirNameSearch); LIB_FUNC("yKDy8S5yLA0", SaveData::SaveDataTerminate); LibSaveDataNative::InitSaveDataNative_1(s); } } // namespace Libs