// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include #include #include #include #include "common/assert.h" #include "common/cstring.h" #include "common/elf_info.h" #include "common/enum.h" #include "common/logging/log.h" #include "common/path_util.h" #include "common/string_util.h" #include "core/file_format/psf.h" #include "core/file_sys/fs.h" #include "core/libraries/error_codes.h" #include "core/libraries/libs.h" #include "core/libraries/save_data/savedata.h" #include "core/libraries/system/msgdialog.h" #include "save_backup.h" #include "save_instance.h" #include "save_memory.h" namespace fs = std::filesystem; namespace chrono = std::chrono; using Common::CString; using Common::ElfInfo; namespace Libraries::SaveData { enum class Error : u32 { OK = 0, USER_SERVICE_NOT_INITIALIZED = 0x80960002, PARAMETER = 0x809F0000, NOT_INITIALIZED = 0x809F0001, OUT_OF_MEMORY = 0x809F0002, BUSY = 0x809F0003, NOT_MOUNTED = 0x809F0004, EXISTS = 0x809F0007, NOT_FOUND = 0x809F0008, NO_SPACE_FS = 0x809F000A, INTERNAL = 0x809F000B, MOUNT_FULL = 0x809F000C, BAD_MOUNTED = 0x809F000D, BROKEN = 0x809F000F, INVALID_LOGIN_USER = 0x809F0011, MEMORY_NOT_READY = 0x809F0012, BACKUP_BUSY = 0x809F0013, BUSY_FOR_SAVING = 0x809F0016, }; enum class OrbisSaveDataSaveDataMemoryOption : u32 { NONE = 0, SET_PARAM = 1 << 0, DOUBLE_BUFFER = 1 << 1, UNLOCK_LIMITATIONS = 1 << 2, }; using OrbisUserServiceUserId = s32; using OrbisSaveDataBlocks = u64; constexpr u32 OrbisSaveDataBlockSize = 32768; // 32 KiB constexpr u32 OrbisSaveDataBlocksMin2 = 96; // 3MiB constexpr u32 OrbisSaveDataBlocksMax = 32768; // 1 GiB // Maximum size for a mount point "/savedataN", where N is a number constexpr size_t OrbisSaveDataMountPointDataMaxsize = 16; constexpr size_t OrbisSaveDataFingerprintDataSize = 65; // Maximum fingerprint size enum class OrbisSaveDataMountMode : u32 { RDONLY = 1 << 0, RDWR = 1 << 1, CREATE = 1 << 2, DESTRUCT_OFF = 1 << 3, COPY_ICON = 1 << 4, CREATE2 = 1 << 5, }; DECLARE_ENUM_FLAG_OPERATORS(OrbisSaveDataMountMode); enum class OrbisSaveDataMountStatus : u32 { NOTHING = 0, CREATED = 1, }; enum class OrbisSaveDataParamType : u32 { ALL = 0, TITLE = 1, SUB_TITLE = 2, DETAIL = 3, USER_PARAM = 4, MTIME = 5, }; enum class OrbisSaveDataSortKey : u32 { DIRNAME = 0, USER_PARAM = 1, BLOCKS = 2, MTIME = 3, FREE_BLOCKS = 5, }; enum class OrbisSaveDataSortOrder : u32 { ASCENT = 0, DESCENT = 1, }; struct OrbisSaveDataFingerprint { CString data; std::array _pad; }; struct OrbisSaveDataBackup { OrbisUserServiceUserId userId; s32 : 32; const OrbisSaveDataTitleId* titleId; const OrbisSaveDataDirName* dirName; const OrbisSaveDataFingerprint* param; std::array _reserved; }; struct OrbisSaveDataCheckBackupData { OrbisUserServiceUserId userId; s32 : 32; const OrbisSaveDataTitleId* titleId; const OrbisSaveDataDirName* dirName; OrbisSaveDataParam* param; OrbisSaveDataIcon* icon; std::array _reserved; }; struct OrbisSaveDataDelete { OrbisUserServiceUserId userId; s32 : 32; const OrbisSaveDataTitleId* titleId; const OrbisSaveDataDirName* dirName; u32 _unused; std::array _reserved; s32 : 32; }; struct OrbisSaveDataIcon { void* buf; size_t bufSize; size_t dataSize; std::array _reserved; Error LoadIcon(const std::filesystem::path& icon_path) { try { const Common::FS::IOFile file(icon_path, Common::FS::FileAccessMode::Read); dataSize = file.GetSize(); file.Seek(0); file.ReadRaw(buf, std::min(bufSize, dataSize)); } catch (const fs::filesystem_error& e) { LOG_ERROR(Lib_SaveData, "Failed to load icon: {}", e.what()); return Error::INTERNAL; } return Error::OK; } }; struct OrbisSaveDataMemoryData { void* buf; size_t bufSize; s64 offset; u8 _reserved[40]; }; struct OrbisSaveDataMemoryGet2 { OrbisUserServiceUserId userId; std::array _pad; OrbisSaveDataMemoryData* data; OrbisSaveDataParam* param; OrbisSaveDataIcon* icon; std::array _reserved; }; struct OrbisSaveDataMemorySet2 { OrbisUserServiceUserId userId; std::array _pad; const OrbisSaveDataMemoryData* data; const OrbisSaveDataParam* param; const OrbisSaveDataIcon* icon; std::array _reserved; }; struct OrbisSaveDataMemorySetup2 { OrbisSaveDataSaveDataMemoryOption option; OrbisUserServiceUserId userId; size_t memorySize; size_t iconMemorySize; // +4.5 const OrbisSaveDataParam* initParam; // +4.5 const OrbisSaveDataIcon* initIcon; std::array _reserved; }; struct OrbisSaveDataMemorySetupResult { size_t existedMemorySize; std::array _reserved; }; struct OrbisSaveDataMemorySync { OrbisUserServiceUserId userId; std::array _reserved; }; struct OrbisSaveDataMount2 { OrbisUserServiceUserId userId; s32 : 32; const OrbisSaveDataDirName* dirName; OrbisSaveDataBlocks blocks; OrbisSaveDataMountMode mountMode; std::array _reserved; s32 : 32; }; struct OrbisSaveDataMount { OrbisUserServiceUserId userId; s32 : 32; const OrbisSaveDataTitleId* titleId; const OrbisSaveDataDirName* dirName; const OrbisSaveDataFingerprint* fingerprint; OrbisSaveDataBlocks blocks; OrbisSaveDataMountMode mountMode; std::array _reserved; }; struct OrbisSaveDataMountInfo { OrbisSaveDataBlocks blocks; OrbisSaveDataBlocks freeBlocks; std::array _reserved; }; struct OrbisSaveDataMountPoint { CString data; }; struct OrbisSaveDataMountResult { OrbisSaveDataMountPoint mount_point; OrbisSaveDataBlocks required_blocks; u32 _unused; // +4.5 OrbisSaveDataMountStatus mount_status; std::array _reserved; s32 : 32; }; struct OrbisSaveDataRestoreBackupData { OrbisUserServiceUserId userId; s32 : 32; const OrbisSaveDataTitleId* titleId; const OrbisSaveDataDirName* dirName; const OrbisSaveDataFingerprint* fingerprint; u32 _unused; std::array _reserved; s32 : 32; }; struct OrbisSaveDataTransferringMount { OrbisUserServiceUserId userId; const OrbisSaveDataTitleId* titleId; const OrbisSaveDataDirName* dirName; const OrbisSaveDataFingerprint* fingerprint; std::array _reserved; }; struct OrbisSaveDataDirNameSearchCond { OrbisUserServiceUserId userId; int : 32; const OrbisSaveDataTitleId* titleId; const OrbisSaveDataDirName* dirName; OrbisSaveDataSortKey key; OrbisSaveDataSortOrder order; std::array _reserved; }; struct OrbisSaveDataSearchInfo { u64 blocks; u64 freeBlocks; std::array _reserved; }; struct OrbisSaveDataDirNameSearchResult { u32 hitNum; int : 32; OrbisSaveDataDirName* dirNames; u32 dirNamesNum; // +1.7 u32 setNum; // +1.7 OrbisSaveDataParam* params; // +2.5 OrbisSaveDataSearchInfo* infos; std::array _reserved; int : 32; }; struct OrbisSaveDataEventParam { // dummy structure OrbisSaveDataEventParam() = delete; }; using OrbisSaveDataEventType = Backup::OrbisSaveDataEventType; struct OrbisSaveDataEvent { OrbisSaveDataEventType type; s32 errorCode; OrbisUserServiceUserId userId; std::array _pad; OrbisSaveDataTitleId titleId; OrbisSaveDataDirName dirName; std::array _reserved; }; static bool g_initialized = false; static std::string g_game_serial; static u32 g_fw_ver; static std::array, 16> g_mount_slots; static void initialize() { g_initialized = true; g_game_serial = ElfInfo::Instance().GameSerial(); g_fw_ver = ElfInfo::Instance().FirmwareVer(); } // game_00other | game*other static bool match(std::string_view str, std::string_view pattern) { auto str_it = str.begin(); auto pat_it = pattern.begin(); while (str_it != str.end() && pat_it != pattern.end()) { if (*pat_it == '%') { // 0 or more wildcard for (auto str_wild_it = str_it; str_wild_it <= str.end(); ++str_wild_it) { if (match({str_wild_it, str.end()}, {pat_it + 1, pattern.end()})) { return true; } } return false; } if (*pat_it == '_') { // 1 character wildcard ++str_it; ++pat_it; } else if (*pat_it != *str_it) { return false; } ++str_it; ++pat_it; } return str_it == str.end() && pat_it == pattern.end(); } static Error setNotInitializedError() { if (g_fw_ver < ElfInfo::FW_20) { return Error::INTERNAL; } if (g_fw_ver < ElfInfo::FW_25) { return Error::USER_SERVICE_NOT_INITIALIZED; } return Error::NOT_INITIALIZED; } static Error saveDataMount(const OrbisSaveDataMount2* mount_info, OrbisSaveDataMountResult* mount_result, std::string_view title_id = g_game_serial) { if (mount_info->userId < 0) { return Error::INVALID_LOGIN_USER; } if (mount_info->dirName == nullptr) { LOG_INFO(Lib_SaveData, "called without dirName"); return Error::PARAMETER; } // check backup status { const auto save_path = SaveInstance::MakeDirSavePath(mount_info->userId, title_id, mount_info->dirName->data); if (Backup::IsBackupExecutingFor(save_path) && g_fw_ver) { return Error::BACKUP_BUSY; } } auto mount_mode = mount_info->mountMode; const bool is_ro = True(mount_mode & OrbisSaveDataMountMode::RDONLY); const bool create = True(mount_mode & OrbisSaveDataMountMode::CREATE); const bool create_if_not_exist = True(mount_mode & OrbisSaveDataMountMode::CREATE2) && g_fw_ver >= ElfInfo::FW_45; ASSERT(!create || !create_if_not_exist); // Can't have both const bool copy_icon = True(mount_mode & OrbisSaveDataMountMode::COPY_ICON); const bool ignore_corrupt = True(mount_mode & OrbisSaveDataMountMode::DESTRUCT_OFF) || g_fw_ver < ElfInfo::FW_16; const std::string_view dir_name{mount_info->dirName->data}; // find available mount point int slot_num = -1; for (size_t i = 0; i < g_mount_slots.size(); i++) { const auto& instance = g_mount_slots[i]; if (instance.has_value()) { const auto& slot_name = instance->GetDirName(); if (slot_name == dir_name) { return Error::BUSY; } } else { slot_num = static_cast(i); break; } } if (slot_num == -1) { return Error::MOUNT_FULL; } SaveInstance save_instance{slot_num, mount_info->userId, std::string{title_id}, dir_name, (int)mount_info->blocks}; if (save_instance.Mounted()) { UNREACHABLE_MSG("Save instance should not be mounted"); } if (!create && !create_if_not_exist && !save_instance.Exists()) { return Error::NOT_FOUND; } if (create && save_instance.Exists()) { return Error::EXISTS; } bool to_be_created = !save_instance.Exists(); if (to_be_created) { // Check size if (mount_info->blocks < OrbisSaveDataBlocksMin2 || mount_info->blocks > OrbisSaveDataBlocksMax) { LOG_INFO(Lib_SaveData, "called with invalid block size"); } const auto root_save = Common::FS::GetUserPath(Common::FS::PathType::SaveDataDir); fs::create_directories(root_save); const auto available = fs::space(root_save).available; auto requested_size = save_instance.GetMaxBlocks() * OrbisSaveDataBlockSize; if (requested_size > available) { mount_result->required_blocks = (requested_size - available) / OrbisSaveDataBlockSize; return Error::NO_SPACE_FS; } } try { save_instance.SetupAndMount(is_ro, copy_icon, ignore_corrupt); } catch (const fs::filesystem_error& e) { if (e.code() == std::errc::illegal_byte_sequence) { LOG_ERROR(Lib_SaveData, "Corrupted save data"); return Error::BROKEN; } if (e.code() == std::errc::no_space_on_device) { return Error::NO_SPACE_FS; } LOG_ERROR(Lib_SaveData, "Failed to mount save data: {}", e.what()); return Error::INTERNAL; } mount_result->mount_point.data.FromString(save_instance.GetMountPoint()); if (g_fw_ver >= ElfInfo::FW_45) { mount_result->mount_status = create_if_not_exist && to_be_created ? OrbisSaveDataMountStatus::CREATED : OrbisSaveDataMountStatus::NOTHING; } g_mount_slots[slot_num].emplace(std::move(save_instance)); return Error::OK; } static Error Umount(const OrbisSaveDataMountPoint* mountPoint, bool call_backup = false) { if (!g_initialized) { LOG_INFO(Lib_SaveData, "called without initialize"); return setNotInitializedError(); } if (mountPoint == nullptr) { LOG_INFO(Lib_SaveData, "called with invalid parameter"); return Error::PARAMETER; } LOG_DEBUG(Lib_SaveData, "Umount mountPoint:{}", mountPoint->data.to_view()); const std::string_view mount_point_str{mountPoint->data}; for (auto& instance : g_mount_slots) { if (instance.has_value()) { const auto& slot_name = instance->GetMountPoint(); if (slot_name == mount_point_str) { // TODO: check if is busy instance->Umount(); if (call_backup) { Backup::StartThread(); Backup::NewRequest(instance->GetUserId(), instance->GetTitleId(), instance->GetDirName(), OrbisSaveDataEventType::UMOUNT_BACKUP); } instance.reset(); return Error::OK; } } } return Error::NOT_FOUND; } void OrbisSaveDataParam::FromSFO(const PSF& sfo) { memset(this, 0, sizeof(OrbisSaveDataParam)); title.FromString(sfo.GetString(SaveParams::MAINTITLE).value_or("Unknown")); subTitle.FromString(sfo.GetString(SaveParams::SUBTITLE).value_or("")); detail.FromString(sfo.GetString(SaveParams::DETAIL).value_or("")); userParam = sfo.GetInteger(SaveParams::SAVEDATA_LIST_PARAM).value_or(0); const auto time_since_epoch = sfo.GetLastWrite().time_since_epoch(); mtime = chrono::duration_cast(time_since_epoch).count(); } void OrbisSaveDataParam::ToSFO(PSF& sfo) const { sfo.AddString(std::string{SaveParams::MAINTITLE}, std::string{title}, true); sfo.AddString(std::string{SaveParams::SUBTITLE}, std::string{subTitle}, true); sfo.AddString(std::string{SaveParams::DETAIL}, std::string{detail}, true); sfo.AddInteger(std::string{SaveParams::SAVEDATA_LIST_PARAM}, static_cast(userParam), true); } int PS4_SYSV_ABI sceSaveDataAbort() { LOG_ERROR(Lib_SaveData, "(STUBBED) called"); return ORBIS_OK; } Error PS4_SYSV_ABI sceSaveDataBackup(const OrbisSaveDataBackup* backup) { if (!g_initialized) { LOG_INFO(Lib_SaveData, "called without initialize"); return setNotInitializedError(); } if (backup == nullptr || backup->dirName == nullptr) { LOG_INFO(Lib_SaveData, "called with invalid parameter"); return Error::PARAMETER; } const std::string_view dir_name{backup->dirName->data}; LOG_DEBUG(Lib_SaveData, "called dirName: {}", dir_name); std::string_view title{backup->titleId != nullptr ? std::string_view{backup->titleId->data} : std::string_view{g_game_serial}}; for (const auto& instance : g_mount_slots) { if (instance.has_value() && instance->GetUserId() == backup->userId && instance->GetTitleId() == title && instance->GetDirName() == dir_name) { return Error::BUSY; } } Backup::StartThread(); Backup::NewRequest(backup->userId, title, dir_name, OrbisSaveDataEventType::BACKUP); return Error::OK; } int PS4_SYSV_ABI sceSaveDataBindPsnAccount() { LOG_ERROR(Lib_SaveData, "(STUBBED) called"); return ORBIS_OK; } int PS4_SYSV_ABI sceSaveDataBindPsnAccountForSystemBackup() { LOG_ERROR(Lib_SaveData, "(STUBBED) called"); return ORBIS_OK; } int PS4_SYSV_ABI sceSaveDataChangeDatabase() { LOG_ERROR(Lib_SaveData, "(STUBBED) called"); return ORBIS_OK; } int PS4_SYSV_ABI sceSaveDataChangeInternal() { LOG_ERROR(Lib_SaveData, "(STUBBED) called"); return ORBIS_OK; } Error PS4_SYSV_ABI sceSaveDataCheckBackupData(const OrbisSaveDataCheckBackupData* check) { if (!g_initialized) { LOG_INFO(Lib_SaveData, "called without initialize"); return setNotInitializedError(); } if (check == nullptr || check->dirName == nullptr) { LOG_INFO(Lib_SaveData, "called with invalid parameter"); return Error::PARAMETER; } const std::string_view title{check->titleId != nullptr ? std::string_view{check->titleId->data} : std::string_view{g_game_serial}}; LOG_DEBUG(Lib_SaveData, "called with titleId={}", title); const auto save_path = SaveInstance::MakeDirSavePath(check->userId, title, check->dirName->data); for (const auto& instance : g_mount_slots) { if (instance.has_value() && instance->GetSavePath() == save_path) { return Error::BUSY; } } if (Backup::IsBackupExecutingFor(save_path)) { return Error::BACKUP_BUSY; } const auto backup_path = Backup::MakeBackupPath(save_path); if (!fs::exists(backup_path)) { return Error::NOT_FOUND; } if (check->param != nullptr) { PSF sfo; if (!sfo.Open(backup_path / "sce_sys" / "param.sfo")) { LOG_ERROR(Lib_SaveData, "Failed to read SFO at {}", fmt::UTF(backup_path.u8string())); return Error::INTERNAL; } check->param->FromSFO(sfo); } if (check->icon != nullptr) { const auto icon_path = backup_path / "sce_sys" / "icon0.png"; if (fs::exists(icon_path) && check->icon->LoadIcon(icon_path) != Error::OK) { return Error::INTERNAL; } } return Error::OK; } int PS4_SYSV_ABI sceSaveDataCheckBackupDataForCdlg() { LOG_ERROR(Lib_SaveData, "(STUBBED) called"); return ORBIS_OK; } int PS4_SYSV_ABI sceSaveDataCheckBackupDataInternal() { LOG_ERROR(Lib_SaveData, "(STUBBED) called"); return ORBIS_OK; } int PS4_SYSV_ABI sceSaveDataCheckCloudData() { LOG_ERROR(Lib_SaveData, "(STUBBED) called"); return ORBIS_OK; } int PS4_SYSV_ABI sceSaveDataCheckIpmiIfSize() { LOG_ERROR(Lib_SaveData, "(STUBBED) called"); return ORBIS_OK; } int PS4_SYSV_ABI sceSaveDataCheckSaveDataBroken() { LOG_ERROR(Lib_SaveData, "(STUBBED) called"); return ORBIS_OK; } int PS4_SYSV_ABI sceSaveDataCheckSaveDataVersion() { LOG_ERROR(Lib_SaveData, "(STUBBED) called"); return ORBIS_OK; } int PS4_SYSV_ABI sceSaveDataCheckSaveDataVersionLatest() { LOG_ERROR(Lib_SaveData, "(STUBBED) called"); return ORBIS_OK; } Error PS4_SYSV_ABI sceSaveDataClearProgress() { if (!g_initialized) { LOG_INFO(Lib_SaveData, "called without initialize"); return setNotInitializedError(); } LOG_DEBUG(Lib_SaveData, "called"); Backup::ClearProgress(); return Error::OK; } int PS4_SYSV_ABI sceSaveDataCopy5() { LOG_ERROR(Lib_SaveData, "(STUBBED) called"); return ORBIS_OK; } int PS4_SYSV_ABI sceSaveDataCreateUploadData() { LOG_ERROR(Lib_SaveData, "(STUBBED) called"); return ORBIS_OK; } int PS4_SYSV_ABI sceSaveDataDebug() { LOG_ERROR(Lib_SaveData, "(STUBBED) called"); return ORBIS_OK; } int PS4_SYSV_ABI sceSaveDataDebugCleanMount() { LOG_ERROR(Lib_SaveData, "(STUBBED) called"); return ORBIS_OK; } int PS4_SYSV_ABI sceSaveDataDebugCompiledSdkVersion() { LOG_ERROR(Lib_SaveData, "(STUBBED) called"); return ORBIS_OK; } int PS4_SYSV_ABI sceSaveDataDebugCreateSaveDataRoot() { LOG_ERROR(Lib_SaveData, "(STUBBED) called"); return ORBIS_OK; } int PS4_SYSV_ABI sceSaveDataDebugGetThreadId() { LOG_ERROR(Lib_SaveData, "(STUBBED) called"); return ORBIS_OK; } int PS4_SYSV_ABI sceSaveDataDebugRemoveSaveDataRoot() { LOG_ERROR(Lib_SaveData, "(STUBBED) called"); return ORBIS_OK; } int PS4_SYSV_ABI sceSaveDataDebugTarget() { LOG_ERROR(Lib_SaveData, "(STUBBED) called"); return ORBIS_OK; } Error PS4_SYSV_ABI sceSaveDataDelete(const OrbisSaveDataDelete* del) { if (!g_initialized) { LOG_INFO(Lib_SaveData, "called without initialize"); return setNotInitializedError(); } if (del == nullptr) { LOG_INFO(Lib_SaveData, "called with invalid parameter"); return Error::PARAMETER; } const std::string_view dirName{del->dirName->data}; LOG_DEBUG(Lib_SaveData, "called dirName: {}", dirName); if (dirName.empty()) { return Error::PARAMETER; } for (const auto& instance : g_mount_slots) { if (instance.has_value() && instance->GetDirName() == dirName) { return Error::BUSY; } } const auto save_path = SaveInstance::MakeDirSavePath(del->userId, g_game_serial, dirName); try { if (fs::exists(save_path)) { fs::remove_all(save_path); } } catch (const fs::filesystem_error& e) { LOG_ERROR(Lib_SaveData, "Failed to delete save data: {}", e.what()); return Error::INTERNAL; } return Error::OK; } int PS4_SYSV_ABI sceSaveDataDelete5() { LOG_ERROR(Lib_SaveData, "(STUBBED) called"); return ORBIS_OK; } int PS4_SYSV_ABI sceSaveDataDeleteAllUser() { LOG_ERROR(Lib_SaveData, "(STUBBED) called"); return ORBIS_OK; } int PS4_SYSV_ABI sceSaveDataDeleteCloudData() { LOG_ERROR(Lib_SaveData, "(STUBBED) called"); return ORBIS_OK; } int PS4_SYSV_ABI sceSaveDataDeleteUser() { LOG_ERROR(Lib_SaveData, "(STUBBED) called"); return ORBIS_OK; } Error PS4_SYSV_ABI sceSaveDataDirNameSearch(const OrbisSaveDataDirNameSearchCond* cond, OrbisSaveDataDirNameSearchResult* result) { if (!g_initialized) { LOG_INFO(Lib_SaveData, "called without initialize"); return setNotInitializedError(); } if (cond == nullptr || result == nullptr || cond->key > OrbisSaveDataSortKey::FREE_BLOCKS || cond->order > OrbisSaveDataSortOrder::DESCENT) { LOG_INFO(Lib_SaveData, "called with invalid parameter"); return Error::PARAMETER; } LOG_DEBUG(Lib_SaveData, "called"); const std::string_view title_id{cond->titleId == nullptr ? std::string_view{g_game_serial} : std::string_view{cond->titleId->data}}; const auto save_path = SaveInstance::MakeTitleSavePath(cond->userId, title_id); if (!fs::exists(save_path)) { result->hitNum = 0; if (g_fw_ver >= ElfInfo::FW_17) { result->setNum = 0; } return Error::OK; } std::vector dir_list; for (const auto& path : fs::directory_iterator{save_path}) { auto dir_name = path.path().filename().string(); // skip non-directories, sce_* and directories without param.sfo if (fs::is_directory(path) && !dir_name.starts_with("sce_") && fs::exists(SaveInstance::GetParamSFOPath(path))) { dir_list.push_back(dir_name); } } if (cond->dirName != nullptr) { // Filter names const auto pat = Common::ToLower(std::string_view{cond->dirName->data}); if (!pat.empty()) { std::erase_if(dir_list, [&](const std::string& dir_name) { return !match(Common::ToLower(dir_name), pat); }); } } std::unordered_map map_dir_sfo; std::unordered_map map_max_blocks; std::unordered_map map_free_size; for (const auto& dir_name : dir_list) { const auto dir_path = SaveInstance::MakeDirSavePath(cond->userId, title_id, dir_name); const auto sfo_path = SaveInstance::GetParamSFOPath(dir_path); PSF sfo; if (!sfo.Open(sfo_path)) { LOG_ERROR(Lib_SaveData, "Failed to read SFO: {}", fmt::UTF(sfo_path.u8string())); ASSERT_MSG(false, "Failed to read SFO"); } size_t size = Common::FS::GetDirectorySize(dir_path); size_t total = SaveInstance::GetMaxBlockFromSFO(sfo); map_dir_sfo.emplace(dir_name, std::move(sfo)); map_free_size.emplace(dir_name, total - size / OrbisSaveDataBlockSize); map_max_blocks.emplace(dir_name, total); } #define PROJ(x) [&](const auto& v) { return x; } switch (cond->key) { case OrbisSaveDataSortKey::DIRNAME: std::ranges::stable_sort(dir_list); break; case OrbisSaveDataSortKey::USER_PARAM: std::ranges::stable_sort( dir_list, {}, PROJ(map_dir_sfo.at(v).GetInteger(SaveParams::SAVEDATA_LIST_PARAM).value_or(0))); break; case OrbisSaveDataSortKey::BLOCKS: std::ranges::stable_sort(dir_list, {}, PROJ(map_max_blocks.at(v))); break; case OrbisSaveDataSortKey::FREE_BLOCKS: std::ranges::stable_sort(dir_list, {}, PROJ(map_free_size.at(v))); break; case OrbisSaveDataSortKey::MTIME: std::ranges::stable_sort(dir_list, {}, PROJ(map_dir_sfo.at(v).GetLastWrite())); break; } #undef PROJ if (cond->order == OrbisSaveDataSortOrder::DESCENT) { std::ranges::reverse(dir_list); } size_t max_count = std::min(static_cast(result->dirNamesNum), dir_list.size()); if (g_fw_ver >= ElfInfo::FW_17) { result->hitNum = dir_list.size(); result->setNum = max_count; } else { result->hitNum = max_count; } for (size_t i = 0; i < max_count; i++) { auto& name_data = result->dirNames[i].data; name_data.FromString(dir_list[i]); if (g_fw_ver >= ElfInfo::FW_17 && result->params != nullptr) { auto& sfo = map_dir_sfo.at(dir_list[i]); auto& param_data = result->params[i]; param_data.FromSFO(sfo); } if (g_fw_ver >= ElfInfo::FW_25 && result->infos != nullptr) { auto& info = result->infos[i]; info.blocks = map_max_blocks.at(dir_list[i]); info.freeBlocks = map_free_size.at(dir_list[i]); } } return Error::OK; } int PS4_SYSV_ABI sceSaveDataDirNameSearchInternal() { LOG_ERROR(Lib_SaveData, "(STUBBED) called"); return ORBIS_OK; } int PS4_SYSV_ABI sceSaveDataDownload() { LOG_ERROR(Lib_SaveData, "(STUBBED) called"); return ORBIS_OK; } int PS4_SYSV_ABI sceSaveDataGetAllSize() { LOG_ERROR(Lib_SaveData, "(STUBBED) called"); return ORBIS_OK; } int PS4_SYSV_ABI sceSaveDataGetAppLaunchedUser() { LOG_ERROR(Lib_SaveData, "(STUBBED) called"); return ORBIS_OK; } int PS4_SYSV_ABI sceSaveDataGetAutoUploadConditions() { LOG_ERROR(Lib_SaveData, "(STUBBED) called"); return ORBIS_OK; } int PS4_SYSV_ABI sceSaveDataGetAutoUploadRequestInfo() { LOG_ERROR(Lib_SaveData, "(STUBBED) called"); return ORBIS_OK; } int PS4_SYSV_ABI sceSaveDataGetAutoUploadSetting() { LOG_ERROR(Lib_SaveData, "(STUBBED) called"); return ORBIS_OK; } int PS4_SYSV_ABI sceSaveDataGetBoundPsnAccountCount() { LOG_ERROR(Lib_SaveData, "(STUBBED) called"); return ORBIS_OK; } int PS4_SYSV_ABI sceSaveDataGetClientThreadPriority() { LOG_ERROR(Lib_SaveData, "(STUBBED) called"); return ORBIS_OK; } int PS4_SYSV_ABI sceSaveDataGetCloudQuotaInfo() { LOG_ERROR(Lib_SaveData, "(STUBBED) called"); return ORBIS_OK; } int PS4_SYSV_ABI sceSaveDataGetDataBaseFilePath() { LOG_ERROR(Lib_SaveData, "(STUBBED) called"); return ORBIS_OK; } int PS4_SYSV_ABI sceSaveDataGetEventInfo() { LOG_ERROR(Lib_SaveData, "(STUBBED) called"); return ORBIS_OK; } Error PS4_SYSV_ABI sceSaveDataGetEventResult(const OrbisSaveDataEventParam*, OrbisSaveDataEvent* event) { if (!g_initialized) { LOG_INFO(Lib_SaveData, "called without initialize"); return setNotInitializedError(); } if (event == nullptr) { LOG_INFO(Lib_SaveData, "called with invalid parameter"); return Error::PARAMETER; } LOG_TRACE(Lib_SaveData, "called"); auto last_event = Backup::PopLastEvent(); if (!last_event.has_value()) { return Error::NOT_FOUND; } event->type = last_event->origin; event->errorCode = 0; event->userId = last_event->user_id; event->titleId.data.FromString(last_event->title_id); event->dirName.data.FromString(last_event->dir_name); return Error::OK; } int PS4_SYSV_ABI sceSaveDataGetFormat() { LOG_ERROR(Lib_SaveData, "(STUBBED) called"); return ORBIS_OK; } int PS4_SYSV_ABI sceSaveDataGetMountedSaveDataCount() { LOG_ERROR(Lib_SaveData, "(STUBBED) called"); return ORBIS_OK; } Error PS4_SYSV_ABI sceSaveDataGetMountInfo(const OrbisSaveDataMountPoint* mountPoint, OrbisSaveDataMountInfo* info) { if (!g_initialized) { LOG_INFO(Lib_SaveData, "called without initialize"); return setNotInitializedError(); } if (mountPoint == nullptr || info == nullptr) { LOG_INFO(Lib_SaveData, "called with invalid parameter"); return Error::PARAMETER; } LOG_DEBUG(Lib_SaveData, "called"); const std::string_view mount_point_str{mountPoint->data}; for (const auto& instance : g_mount_slots) { if (instance.has_value() && instance->GetMountPoint() == mount_point_str) { const u32 blocks = instance->GetMaxBlocks(); const u64 size = Common::FS::GetDirectorySize(instance->GetSavePath()); info->blocks = blocks; info->freeBlocks = blocks - size / OrbisSaveDataBlockSize; return Error::OK; } } return Error::NOT_MOUNTED; } Error PS4_SYSV_ABI sceSaveDataGetParam(const OrbisSaveDataMountPoint* mountPoint, OrbisSaveDataParamType paramType, void* paramBuf, size_t paramBufSize, size_t* gotSize) { if (!g_initialized) { LOG_INFO(Lib_SaveData, "called without initialize"); return setNotInitializedError(); } if (paramType > OrbisSaveDataParamType::MTIME || paramBuf == nullptr) { LOG_INFO(Lib_SaveData, "called with invalid parameter"); return Error::PARAMETER; } LOG_DEBUG(Lib_SaveData, "called: paramType = {}", magic_enum::enum_name(paramType)); const PSF* param_sfo = nullptr; const std::string_view mount_point_str{mountPoint->data}; for (const auto& instance : g_mount_slots) { if (instance.has_value() && instance->GetMountPoint() == mount_point_str) { param_sfo = &instance->GetParamSFO(); break; } } if (param_sfo == nullptr) { return Error::NOT_MOUNTED; } switch (paramType) { case OrbisSaveDataParamType::ALL: { const auto param = static_cast(paramBuf); ASSERT(paramBufSize == sizeof(OrbisSaveDataParam)); param->FromSFO(*param_sfo); if (gotSize != nullptr) { *gotSize = sizeof(OrbisSaveDataParam); } break; } case OrbisSaveDataParamType::TITLE: case OrbisSaveDataParamType::SUB_TITLE: case OrbisSaveDataParamType::DETAIL: { const auto param = static_cast(paramBuf); std::string_view key; if (paramType == OrbisSaveDataParamType::TITLE) { key = SaveParams::MAINTITLE; } else if (paramType == OrbisSaveDataParamType::SUB_TITLE) { key = SaveParams::SUBTITLE; } else if (paramType == OrbisSaveDataParamType::DETAIL) { key = SaveParams::DETAIL; } else { UNREACHABLE(); } const size_t s = param_sfo->GetString(key).value_or("").copy(param, paramBufSize - 1); param[s] = '\0'; // null terminate if (gotSize != nullptr) { *gotSize = s + 1; } } break; case OrbisSaveDataParamType::USER_PARAM: { const auto param = static_cast(paramBuf); *param = param_sfo->GetInteger(SaveParams::SAVEDATA_LIST_PARAM).value_or(0); if (gotSize != nullptr) { *gotSize = sizeof(u32); } } break; case OrbisSaveDataParamType::MTIME: { const auto param = static_cast(paramBuf); const auto last_write = param_sfo->GetLastWrite().time_since_epoch(); *param = chrono::duration_cast(last_write).count(); if (gotSize != nullptr) { *gotSize = sizeof(time_t); } } break; default: UNREACHABLE(); } return Error::OK; } Error PS4_SYSV_ABI sceSaveDataGetProgress(float* progress) { if (!g_initialized) { LOG_INFO(Lib_SaveData, "called without initialize"); return setNotInitializedError(); } if (progress == nullptr) { LOG_INFO(Lib_SaveData, "called with invalid parameter"); return Error::PARAMETER; } LOG_DEBUG(Lib_SaveData, "called"); *progress = Backup::GetProgress(); return Error::OK; } int PS4_SYSV_ABI sceSaveDataGetSaveDataCount() { LOG_ERROR(Lib_SaveData, "(STUBBED) called"); return ORBIS_OK; } Error PS4_SYSV_ABI sceSaveDataGetSaveDataMemory(const OrbisUserServiceUserId userId, void* buf, const size_t bufSize, const int64_t offset) { LOG_DEBUG(Lib_SaveData, "Redirecting to sceSaveDataGetSaveDataMemory2"); OrbisSaveDataMemoryData data{}; data.buf = buf; data.bufSize = bufSize; data.offset = offset; OrbisSaveDataMemoryGet2 param{}; param.userId = userId; param.data = &data; param.param = nullptr; param.icon = nullptr; return sceSaveDataGetSaveDataMemory2(¶m); } Error PS4_SYSV_ABI sceSaveDataGetSaveDataMemory2(OrbisSaveDataMemoryGet2* getParam) { if (!g_initialized) { LOG_INFO(Lib_SaveData, "called without initialize"); return setNotInitializedError(); } if (getParam == nullptr) { LOG_INFO(Lib_SaveData, "called with invalid parameter"); return Error::PARAMETER; } if (!SaveMemory::IsSaveMemoryInitialized()) { LOG_INFO(Lib_SaveData, "called without save memory initialized"); return Error::MEMORY_NOT_READY; } if (SaveMemory::IsSaving()) { LOG_TRACE(Lib_SaveData, "called while saving"); return Error::BUSY_FOR_SAVING; } LOG_DEBUG(Lib_SaveData, "called"); auto data = getParam->data; if (data != nullptr) { SaveMemory::ReadMemory(data->buf, data->bufSize, data->offset); } auto param = getParam->param; if (param != nullptr) { param->FromSFO(SaveMemory::GetParamSFO()); } auto icon = getParam->icon; if (icon != nullptr) { auto icon_mem = SaveMemory::GetIcon(); size_t total = std::min(icon->bufSize, icon_mem.size()); std::memcpy(icon->buf, icon_mem.data(), total); icon->dataSize = total; } return Error::OK; } int PS4_SYSV_ABI sceSaveDataGetSaveDataRootDir() { LOG_ERROR(Lib_SaveData, "(STUBBED) called"); return ORBIS_OK; } int PS4_SYSV_ABI sceSaveDataGetSaveDataRootPath() { LOG_ERROR(Lib_SaveData, "(STUBBED) called"); return ORBIS_OK; } int PS4_SYSV_ABI sceSaveDataGetSaveDataRootUsbPath() { LOG_ERROR(Lib_SaveData, "(STUBBED) called"); return ORBIS_OK; } int PS4_SYSV_ABI sceSaveDataGetSavePoint() { LOG_ERROR(Lib_SaveData, "(STUBBED) called"); return ORBIS_OK; } int PS4_SYSV_ABI sceSaveDataGetUpdatedDataCount() { LOG_ERROR(Lib_SaveData, "(STUBBED) called"); return ORBIS_OK; } Error PS4_SYSV_ABI sceSaveDataInitialize(void*) { LOG_DEBUG(Lib_SaveData, "called"); initialize(); return Error::OK; } Error PS4_SYSV_ABI sceSaveDataInitialize2(void*) { LOG_DEBUG(Lib_SaveData, "called"); initialize(); return Error::OK; } Error PS4_SYSV_ABI sceSaveDataInitialize3(void*) { LOG_DEBUG(Lib_SaveData, "called"); initialize(); return Error::OK; } int PS4_SYSV_ABI sceSaveDataInitializeForCdlg() { LOG_ERROR(Lib_SaveData, "(STUBBED) called"); return ORBIS_OK; } int PS4_SYSV_ABI sceSaveDataIsDeletingUsbDb() { LOG_ERROR(Lib_SaveData, "(STUBBED) called"); return ORBIS_OK; } int PS4_SYSV_ABI sceSaveDataIsMounted() { LOG_ERROR(Lib_SaveData, "(STUBBED) called"); return ORBIS_OK; } Error PS4_SYSV_ABI sceSaveDataLoadIcon(const OrbisSaveDataMountPoint* mountPoint, OrbisSaveDataIcon* icon) { if (!g_initialized) { LOG_INFO(Lib_SaveData, "called without initialize"); return setNotInitializedError(); } if (mountPoint == nullptr || icon == nullptr || icon->buf == nullptr) { LOG_INFO(Lib_SaveData, "called with invalid parameter"); return Error::PARAMETER; } LOG_DEBUG(Lib_SaveData, "called"); std::filesystem::path path; const std::string_view mount_point_str{mountPoint->data}; for (const auto& instance : g_mount_slots) { if (instance.has_value() && instance->GetMountPoint() == mount_point_str) { path = instance->GetIconPath(); break; } } if (path.empty()) { return Error::NOT_MOUNTED; } if (!fs::exists(path)) { return Error::NOT_FOUND; } return icon->LoadIcon(path); } Error PS4_SYSV_ABI sceSaveDataMount(const OrbisSaveDataMount* mount, OrbisSaveDataMountResult* mount_result) { if (!g_initialized) { LOG_INFO(Lib_SaveData, "called without initialize"); return setNotInitializedError(); } if (mount == nullptr && mount->dirName != nullptr) { LOG_INFO(Lib_SaveData, "called with invalid parameter"); return Error::PARAMETER; } LOG_DEBUG(Lib_SaveData, "called dirName: {}, mode: {:0b}, blocks: {}", mount->dirName->data.to_view(), (int)mount->mountMode, mount->blocks); OrbisSaveDataMount2 mount_info{}; mount_info.userId = mount->userId; mount_info.dirName = mount->dirName; mount_info.mountMode = mount->mountMode; mount_info.blocks = mount->blocks; return saveDataMount(&mount_info, mount_result); } Error PS4_SYSV_ABI sceSaveDataMount2(const OrbisSaveDataMount2* mount, OrbisSaveDataMountResult* mount_result) { if (!g_initialized) { LOG_INFO(Lib_SaveData, "called without initialize"); return setNotInitializedError(); } if (mount == nullptr && mount->dirName != nullptr) { LOG_INFO(Lib_SaveData, "called with invalid parameter"); return Error::PARAMETER; } LOG_DEBUG(Lib_SaveData, "called dirName: {}, mode: {:0b}, blocks: {}", mount->dirName->data.to_view(), (int)mount->mountMode, mount->blocks); return saveDataMount(mount, mount_result); } int PS4_SYSV_ABI sceSaveDataMount5() { LOG_ERROR(Lib_SaveData, "(STUBBED) called"); return ORBIS_OK; } int PS4_SYSV_ABI sceSaveDataMountInternal() { LOG_ERROR(Lib_SaveData, "(STUBBED) called"); return ORBIS_OK; } int PS4_SYSV_ABI sceSaveDataMountSys() { LOG_ERROR(Lib_SaveData, "(STUBBED) called"); return ORBIS_OK; } int PS4_SYSV_ABI sceSaveDataPromote5() { LOG_ERROR(Lib_SaveData, "(STUBBED) called"); return ORBIS_OK; } int PS4_SYSV_ABI sceSaveDataRebuildDatabase() { LOG_ERROR(Lib_SaveData, "(STUBBED) called"); return ORBIS_OK; } int PS4_SYSV_ABI sceSaveDataRegisterEventCallback() { LOG_ERROR(Lib_SaveData, "(STUBBED) called"); return ORBIS_OK; } Error PS4_SYSV_ABI sceSaveDataRestoreBackupData(const OrbisSaveDataRestoreBackupData* restore) { if (!g_initialized) { LOG_INFO(Lib_SaveData, "called without initialize"); return setNotInitializedError(); } if (restore == nullptr || restore->dirName == nullptr) { LOG_INFO(Lib_SaveData, "called with invalid parameter"); return Error::PARAMETER; } const std::string_view dir_name{restore->dirName->data}; LOG_DEBUG(Lib_SaveData, "called dirName: {}", dir_name); std::string_view title{restore->titleId != nullptr ? std::string_view{restore->titleId->data} : std::string_view{g_game_serial}}; const auto save_path = SaveInstance::MakeDirSavePath(restore->userId, title, dir_name); for (const auto& instance : g_mount_slots) { if (instance.has_value() && instance->GetSavePath() == save_path) { return Error::BUSY; } } if (Backup::IsBackupExecutingFor(save_path)) { return Error::BACKUP_BUSY; } const auto backup_path = Backup::MakeBackupPath(save_path); if (!fs::exists(backup_path)) { return Error::NOT_FOUND; } const bool ok = Backup::Restore(save_path); if (!ok) { return Error::INTERNAL; } return Error::OK; } int PS4_SYSV_ABI sceSaveDataRestoreBackupDataForCdlg() { LOG_ERROR(Lib_SaveData, "(STUBBED) called"); return ORBIS_OK; } int PS4_SYSV_ABI sceSaveDataRestoreLoadSaveDataMemory() { LOG_ERROR(Lib_SaveData, "(STUBBED) called"); return ORBIS_OK; } Error PS4_SYSV_ABI sceSaveDataSaveIcon(const OrbisSaveDataMountPoint* mountPoint, const OrbisSaveDataIcon* icon) { if (!g_initialized) { LOG_INFO(Lib_SaveData, "called without initialize"); return setNotInitializedError(); } if (mountPoint == nullptr || icon == nullptr || icon->buf == nullptr) { LOG_INFO(Lib_SaveData, "called with invalid parameter"); return Error::PARAMETER; } LOG_DEBUG(Lib_SaveData, "called"); std::filesystem::path path; const std::string_view mount_point_str{mountPoint->data}; for (const auto& instance : g_mount_slots) { if (instance.has_value() && instance->GetMountPoint() == mount_point_str) { if (instance->IsReadOnly()) { return Error::BAD_MOUNTED; } path = instance->GetIconPath(); break; } } if (path.empty()) { return Error::NOT_MOUNTED; } try { const Common::FS::IOFile file(path, Common::FS::FileAccessMode::Write); file.WriteRaw(icon->buf, std::min(icon->bufSize, icon->dataSize)); } catch (const fs::filesystem_error& e) { LOG_ERROR(Lib_SaveData, "Failed to load icon: {}", e.what()); return Error::INTERNAL; } return Error::OK; } int PS4_SYSV_ABI sceSaveDataSetAutoUploadSetting() { LOG_ERROR(Lib_SaveData, "(STUBBED) called"); return ORBIS_OK; } int PS4_SYSV_ABI sceSaveDataSetEventInfo() { LOG_ERROR(Lib_SaveData, "(STUBBED) called"); return ORBIS_OK; } Error PS4_SYSV_ABI sceSaveDataSetParam(const OrbisSaveDataMountPoint* mountPoint, OrbisSaveDataParamType paramType, const void* paramBuf, size_t paramBufSize) { if (!g_initialized) { LOG_INFO(Lib_SaveData, "called without initialize"); return setNotInitializedError(); } if (paramType > OrbisSaveDataParamType::USER_PARAM || mountPoint == nullptr || paramBuf == nullptr) { LOG_INFO(Lib_SaveData, "called with invalid parameter"); return Error::PARAMETER; } LOG_DEBUG(Lib_SaveData, "called: paramType = {}", magic_enum::enum_name(paramType)); PSF* param_sfo = nullptr; const std::string_view mount_point_str{mountPoint->data}; for (auto& instance : g_mount_slots) { if (instance.has_value() && instance->GetMountPoint() == mount_point_str) { param_sfo = &instance->GetParamSFO(); break; } } if (param_sfo == nullptr) { return Error::NOT_MOUNTED; } switch (paramType) { case OrbisSaveDataParamType::ALL: { const auto param = static_cast(paramBuf); ASSERT(paramBufSize == sizeof(OrbisSaveDataParam)); param->ToSFO(*param_sfo); return Error::OK; } break; case OrbisSaveDataParamType::TITLE: { const auto value = static_cast(paramBuf); param_sfo->AddString(std::string{SaveParams::MAINTITLE}, {value}, true); } break; case OrbisSaveDataParamType::SUB_TITLE: { const auto value = static_cast(paramBuf); param_sfo->AddString(std::string{SaveParams::SUBTITLE}, {value}, true); } break; case OrbisSaveDataParamType::DETAIL: { const auto value = static_cast(paramBuf); param_sfo->AddString(std::string{SaveParams::DETAIL}, {value}, true); } break; case OrbisSaveDataParamType::USER_PARAM: { const auto value = static_cast(paramBuf); param_sfo->AddInteger(std::string{SaveParams::SAVEDATA_LIST_PARAM}, *value, true); } break; default: UNREACHABLE(); } return Error::OK; } int PS4_SYSV_ABI sceSaveDataSetSaveDataLibraryUser() { LOG_ERROR(Lib_SaveData, "(STUBBED) called"); return ORBIS_OK; } Error PS4_SYSV_ABI sceSaveDataSetSaveDataMemory(OrbisUserServiceUserId userId, void* buf, size_t bufSize, int64_t offset) { LOG_DEBUG(Lib_SaveData, "Redirecting to sceSaveDataSetSaveDataMemory2"); OrbisSaveDataMemoryData data{}; data.buf = buf; data.bufSize = bufSize; data.offset = offset; OrbisSaveDataMemorySet2 setParam{}; setParam.userId = userId; setParam.data = &data; setParam.param = nullptr; setParam.icon = nullptr; return sceSaveDataSetSaveDataMemory2(&setParam); } Error PS4_SYSV_ABI sceSaveDataSetSaveDataMemory2(const OrbisSaveDataMemorySet2* setParam) { if (!g_initialized) { LOG_INFO(Lib_SaveData, "called without initialize"); return setNotInitializedError(); } if (setParam == nullptr) { LOG_INFO(Lib_SaveData, "called with invalid parameter"); return Error::PARAMETER; } if (!SaveMemory::IsSaveMemoryInitialized()) { LOG_INFO(Lib_SaveData, "called without save memory initialized"); return Error::MEMORY_NOT_READY; } if (SaveMemory::IsSaving()) { LOG_TRACE(Lib_SaveData, "called while saving"); return Error::BUSY_FOR_SAVING; } LOG_DEBUG(Lib_SaveData, "called"); auto data = setParam->data; if (data != nullptr) { SaveMemory::WriteMemory(data->buf, data->bufSize, data->offset); } auto param = setParam->param; if (param != nullptr) { param->ToSFO(SaveMemory::GetParamSFO()); SaveMemory::SaveSFO(); } auto icon = setParam->icon; if (icon != nullptr) { SaveMemory::WriteIcon(icon->buf, icon->bufSize); } SaveMemory::TriggerSaveWithoutEvent(); return Error::OK; } Error PS4_SYSV_ABI sceSaveDataSetupSaveDataMemory(OrbisUserServiceUserId userId, size_t memorySize, OrbisSaveDataParam* param) { LOG_DEBUG(Lib_SaveData, "called: userId = {}, memorySize = {}", userId, memorySize); OrbisSaveDataMemorySetup2 setupParam{}; setupParam.userId = userId; setupParam.memorySize = memorySize; setupParam.initParam = nullptr; setupParam.initIcon = nullptr; OrbisSaveDataMemorySetupResult result{}; const auto res = sceSaveDataSetupSaveDataMemory2(&setupParam, &result); if (res != Error::OK) { return res; } if (param != nullptr) { OrbisSaveDataMemorySet2 setParam{}; setParam.userId = userId; setParam.data = nullptr; setParam.param = param; setParam.icon = nullptr; sceSaveDataSetSaveDataMemory2(&setParam); } return Error::OK; } Error PS4_SYSV_ABI sceSaveDataSetupSaveDataMemory2(const OrbisSaveDataMemorySetup2* setupParam, OrbisSaveDataMemorySetupResult* result) { if (!g_initialized) { LOG_INFO(Lib_SaveData, "called without initialize"); return setNotInitializedError(); } if (setupParam == nullptr) { LOG_INFO(Lib_SaveData, "called with invalid parameter"); return Error::PARAMETER; } LOG_DEBUG(Lib_SaveData, "called"); SaveMemory::SetDirectories(setupParam->userId, g_game_serial); const auto& save_path = SaveMemory::GetSavePath(); for (const auto& instance : g_mount_slots) { if (instance.has_value() && instance->GetSavePath() == save_path) { return Error::BUSY; } } try { size_t existed_size = SaveMemory::CreateSaveMemory(setupParam->memorySize); if (existed_size == 0) { // Just created if (g_fw_ver >= ElfInfo::FW_45 && setupParam->initParam != nullptr) { auto& sfo = SaveMemory::GetParamSFO(); setupParam->initParam->ToSFO(sfo); } SaveMemory::SaveSFO(); auto init_icon = setupParam->initIcon; if (g_fw_ver >= ElfInfo::FW_45 && init_icon != nullptr) { SaveMemory::SetIcon(init_icon->buf, init_icon->bufSize); } else { SaveMemory::SetIcon(nullptr, 0); } } SaveMemory::TriggerSaveWithoutEvent(); if (g_fw_ver >= ElfInfo::FW_45 && result != nullptr) { result->existedMemorySize = existed_size; } } catch (const fs::filesystem_error& e) { LOG_ERROR(Lib_SaveData, "Failed to create/load save memory: {}", e.what()); const MsgDialog::MsgDialogState dialog{MsgDialog::MsgDialogState::UserState{ .type = MsgDialog::ButtonType::OK, .msg = "Failed to create or load save memory:\n" + std::string{e.what()}, }}; MsgDialog::ShowMsgDialog(dialog); return Error::INTERNAL; } return Error::OK; } int PS4_SYSV_ABI sceSaveDataShutdownStart() { LOG_ERROR(Lib_SaveData, "(STUBBED) called"); return ORBIS_OK; } int PS4_SYSV_ABI sceSaveDataSupportedFakeBrokenStatus() { LOG_ERROR(Lib_SaveData, "(STUBBED) called"); return ORBIS_OK; } int PS4_SYSV_ABI sceSaveDataSyncCloudList() { LOG_ERROR(Lib_SaveData, "(STUBBED) called"); return ORBIS_OK; } Error PS4_SYSV_ABI sceSaveDataSyncSaveDataMemory(OrbisSaveDataMemorySync* syncParam) { if (!g_initialized) { LOG_INFO(Lib_SaveData, "called without initialize"); return setNotInitializedError(); } if (syncParam == nullptr) { LOG_INFO(Lib_SaveData, "called with invalid parameter"); return Error::PARAMETER; } if (!SaveMemory::IsSaveMemoryInitialized()) { LOG_INFO(Lib_SaveData, "called without save memory initialized"); return Error::MEMORY_NOT_READY; } LOG_DEBUG(Lib_SaveData, "called"); bool ok = SaveMemory::TriggerSave(); if (!ok) { return Error::BUSY_FOR_SAVING; } return Error::OK; } Error PS4_SYSV_ABI sceSaveDataTerminate() { LOG_DEBUG(Lib_SaveData, "called"); if (!g_initialized) { return setNotInitializedError(); } for (auto& instance : g_mount_slots) { if (instance.has_value()) { if (g_fw_ver >= ElfInfo::FW_40) { return Error::BUSY; } instance->Umount(); instance.reset(); } } g_initialized = false; Backup::StopThread(); return Error::OK; } Error PS4_SYSV_ABI sceSaveDataTransferringMount(const OrbisSaveDataTransferringMount* mount, OrbisSaveDataMountResult* mountResult) { LOG_DEBUG(Lib_SaveData, "called"); if (!g_initialized) { LOG_INFO(Lib_SaveData, "called without initialize"); return setNotInitializedError(); } if (mount == nullptr || mount->titleId == nullptr || mount->dirName == nullptr) { LOG_INFO(Lib_SaveData, "called with invalid parameter"); return Error::PARAMETER; } LOG_DEBUG(Lib_SaveData, "called titleId: {}, dirName: {}", mount->titleId->data.to_view(), mount->dirName->data.to_view()); OrbisSaveDataMount2 mount_info{}; mount_info.userId = mount->userId; mount_info.dirName = mount->dirName; mount_info.mountMode = OrbisSaveDataMountMode::RDONLY; return saveDataMount(&mount_info, mountResult, mount->titleId->data.to_string()); } Error PS4_SYSV_ABI sceSaveDataUmount(const OrbisSaveDataMountPoint* mountPoint) { LOG_DEBUG(Lib_SaveData, "called"); return Umount(mountPoint); } int PS4_SYSV_ABI sceSaveDataUmountSys() { LOG_ERROR(Lib_SaveData, "(STUBBED) called"); return ORBIS_OK; } Error PS4_SYSV_ABI sceSaveDataUmountWithBackup(const OrbisSaveDataMountPoint* mountPoint) { LOG_DEBUG(Lib_SaveData, "called"); return Umount(mountPoint, true); } int PS4_SYSV_ABI sceSaveDataUnregisterEventCallback() { LOG_ERROR(Lib_SaveData, "(STUBBED) called"); return ORBIS_OK; } int PS4_SYSV_ABI sceSaveDataUpload() { LOG_ERROR(Lib_SaveData, "(STUBBED) called"); return ORBIS_OK; } int PS4_SYSV_ABI Func_02E4C4D201716422() { LOG_ERROR(Lib_SaveData, "(STUBBED) called"); return ORBIS_OK; } void RegisterlibSceSaveData(Core::Loader::SymbolsResolver* sym) { LIB_FUNCTION("dQ2GohUHXzk", "libSceSaveData", 1, "libSceSaveData", 1, 1, sceSaveDataAbort); LIB_FUNCTION("z1JA8-iJt3k", "libSceSaveData", 1, "libSceSaveData", 1, 1, sceSaveDataBackup); LIB_FUNCTION("kLJQ3XioYiU", "libSceSaveData", 1, "libSceSaveData", 1, 1, sceSaveDataBindPsnAccount); LIB_FUNCTION("hHHCPRqA3+g", "libSceSaveData", 1, "libSceSaveData", 1, 1, sceSaveDataBindPsnAccountForSystemBackup); LIB_FUNCTION("ykwIZfVD08s", "libSceSaveData", 1, "libSceSaveData", 1, 1, sceSaveDataChangeDatabase); LIB_FUNCTION("G0hFeOdRCUs", "libSceSaveData", 1, "libSceSaveData", 1, 1, sceSaveDataChangeInternal); LIB_FUNCTION("RQOqDbk3bSU", "libSceSaveData", 1, "libSceSaveData", 1, 1, sceSaveDataCheckBackupData); LIB_FUNCTION("rYvLW1z2poM", "libSceSaveData", 1, "libSceSaveData", 1, 1, sceSaveDataCheckBackupDataForCdlg); LIB_FUNCTION("v1TrX+3ZB10", "libSceSaveData", 1, "libSceSaveData", 1, 1, sceSaveDataCheckBackupDataInternal); LIB_FUNCTION("-eczr5e4dsI", "libSceSaveData", 1, "libSceSaveData", 1, 1, sceSaveDataCheckCloudData); LIB_FUNCTION("4OPOZxfVkHA", "libSceSaveData", 1, "libSceSaveData", 1, 1, sceSaveDataCheckIpmiIfSize); LIB_FUNCTION("1i0rfc+mfa8", "libSceSaveData", 1, "libSceSaveData", 1, 1, sceSaveDataCheckSaveDataBroken); LIB_FUNCTION("p6A1adyQi3E", "libSceSaveData", 1, "libSceSaveData", 1, 1, sceSaveDataCheckSaveDataVersion); LIB_FUNCTION("S49B+I96kpk", "libSceSaveData", 1, "libSceSaveData", 1, 1, sceSaveDataCheckSaveDataVersionLatest); LIB_FUNCTION("Wz-4JZfeO9g", "libSceSaveData", 1, "libSceSaveData", 1, 1, sceSaveDataClearProgress); LIB_FUNCTION("YbCO38BOOl4", "libSceSaveData", 1, "libSceSaveData", 1, 1, sceSaveDataCopy5); LIB_FUNCTION("kbIIP9aXK9A", "libSceSaveData", 1, "libSceSaveData", 1, 1, sceSaveDataCreateUploadData); LIB_FUNCTION("gW6G4HxBBXA", "libSceSaveData", 1, "libSceSaveData", 1, 1, sceSaveDataDebug); LIB_FUNCTION("bYCnxLexU7M", "libSceSaveData", 1, "libSceSaveData", 1, 1, sceSaveDataDebugCleanMount); LIB_FUNCTION("hVDqYB8+jkk", "libSceSaveData", 1, "libSceSaveData", 1, 1, sceSaveDataDebugCompiledSdkVersion); LIB_FUNCTION("K9gXXlrVLNI", "libSceSaveData", 1, "libSceSaveData", 1, 1, sceSaveDataDebugCreateSaveDataRoot); LIB_FUNCTION("5yHFvMwZX2o", "libSceSaveData", 1, "libSceSaveData", 1, 1, sceSaveDataDebugGetThreadId); LIB_FUNCTION("UGTldPVEdB4", "libSceSaveData", 1, "libSceSaveData", 1, 1, sceSaveDataDebugRemoveSaveDataRoot); LIB_FUNCTION("AYBQmnRplrg", "libSceSaveData", 1, "libSceSaveData", 1, 1, sceSaveDataDebugTarget); LIB_FUNCTION("S1GkePI17zQ", "libSceSaveData", 1, "libSceSaveData", 1, 1, sceSaveDataDelete); LIB_FUNCTION("SQWusLoK8Pw", "libSceSaveData", 1, "libSceSaveData", 1, 1, sceSaveDataDelete5); LIB_FUNCTION("pJrlpCgR8h4", "libSceSaveData", 1, "libSceSaveData", 1, 1, sceSaveDataDeleteAllUser); LIB_FUNCTION("fU43mJUgKcM", "libSceSaveData", 1, "libSceSaveData", 1, 1, sceSaveDataDeleteCloudData); LIB_FUNCTION("uZqc4JpFdeY", "libSceSaveData", 1, "libSceSaveData", 1, 1, sceSaveDataDeleteUser); LIB_FUNCTION("dyIhnXq-0SM", "libSceSaveData", 1, "libSceSaveData", 1, 1, sceSaveDataDirNameSearch); LIB_FUNCTION("xJ5NFWC3m+k", "libSceSaveData", 1, "libSceSaveData", 1, 1, sceSaveDataDirNameSearchInternal); LIB_FUNCTION("h1nP9EYv3uc", "libSceSaveData", 1, "libSceSaveData", 1, 1, sceSaveDataDownload); LIB_FUNCTION("A1ThglSGUwA", "libSceSaveData", 1, "libSceSaveData", 1, 1, sceSaveDataGetAllSize); LIB_FUNCTION("KuXcrMAQIMQ", "libSceSaveData", 1, "libSceSaveData", 1, 1, sceSaveDataGetAppLaunchedUser); LIB_FUNCTION("itZ46iH14Vs", "libSceSaveData", 1, "libSceSaveData", 1, 1, sceSaveDataGetAutoUploadConditions); LIB_FUNCTION("PL20kjAXZZ4", "libSceSaveData", 1, "libSceSaveData", 1, 1, sceSaveDataGetAutoUploadRequestInfo); LIB_FUNCTION("G12foE0S77E", "libSceSaveData", 1, "libSceSaveData", 1, 1, sceSaveDataGetAutoUploadSetting); LIB_FUNCTION("PzDtD6eBXIM", "libSceSaveData", 1, "libSceSaveData", 1, 1, sceSaveDataGetBoundPsnAccountCount); LIB_FUNCTION("tu0SDPl+h88", "libSceSaveData", 1, "libSceSaveData", 1, 1, sceSaveDataGetClientThreadPriority); LIB_FUNCTION("6lZYZqQPfkY", "libSceSaveData", 1, "libSceSaveData", 1, 1, sceSaveDataGetCloudQuotaInfo); LIB_FUNCTION("CWlBd2Ay1M4", "libSceSaveData", 1, "libSceSaveData", 1, 1, sceSaveDataGetDataBaseFilePath); LIB_FUNCTION("eBSSNIG6hMk", "libSceSaveData", 1, "libSceSaveData", 1, 1, sceSaveDataGetEventInfo); LIB_FUNCTION("j8xKtiFj0SY", "libSceSaveData", 1, "libSceSaveData", 1, 1, sceSaveDataGetEventResult); LIB_FUNCTION("UMpxor4AlKQ", "libSceSaveData", 1, "libSceSaveData", 1, 1, sceSaveDataGetFormat); LIB_FUNCTION("pc4guaUPVqA", "libSceSaveData", 1, "libSceSaveData", 1, 1, sceSaveDataGetMountedSaveDataCount); LIB_FUNCTION("65VH0Qaaz6s", "libSceSaveData", 1, "libSceSaveData", 1, 1, sceSaveDataGetMountInfo); LIB_FUNCTION("XgvSuIdnMlw", "libSceSaveData", 1, "libSceSaveData", 1, 1, sceSaveDataGetParam); LIB_FUNCTION("ANmSWUiyyGQ", "libSceSaveData", 1, "libSceSaveData", 1, 1, sceSaveDataGetProgress); LIB_FUNCTION("SN7rTPHS+Cg", "libSceSaveData", 1, "libSceSaveData", 1, 1, sceSaveDataGetSaveDataCount); LIB_FUNCTION("7Bt5pBC-Aco", "libSceSaveData", 1, "libSceSaveData", 1, 1, sceSaveDataGetSaveDataMemory); LIB_FUNCTION("QwOO7vegnV8", "libSceSaveData", 1, "libSceSaveData", 1, 1, sceSaveDataGetSaveDataMemory2); LIB_FUNCTION("+bRDRotfj0Y", "libSceSaveData", 1, "libSceSaveData", 1, 1, sceSaveDataGetSaveDataRootDir); LIB_FUNCTION("3luF0xq0DkQ", "libSceSaveData", 1, "libSceSaveData", 1, 1, sceSaveDataGetSaveDataRootPath); LIB_FUNCTION("DwAvlQGvf1o", "libSceSaveData", 1, "libSceSaveData", 1, 1, sceSaveDataGetSaveDataRootUsbPath); LIB_FUNCTION("kb24-4DLyNo", "libSceSaveData", 1, "libSceSaveData", 1, 1, sceSaveDataGetSavePoint); LIB_FUNCTION("OYmnApJ9q+U", "libSceSaveData", 1, "libSceSaveData", 1, 1, sceSaveDataGetUpdatedDataCount); LIB_FUNCTION("ZkZhskCPXFw", "libSceSaveData", 1, "libSceSaveData", 1, 1, sceSaveDataInitialize); LIB_FUNCTION("l1NmDeDpNGU", "libSceSaveData", 1, "libSceSaveData", 1, 1, sceSaveDataInitialize2); LIB_FUNCTION("TywrFKCoLGY", "libSceSaveData", 1, "libSceSaveData", 1, 1, sceSaveDataInitialize3); LIB_FUNCTION("g9uwUI3BlQ8", "libSceSaveData", 1, "libSceSaveData", 1, 1, sceSaveDataInitializeForCdlg); LIB_FUNCTION("voAQW45oKuo", "libSceSaveData", 1, "libSceSaveData", 1, 1, sceSaveDataIsDeletingUsbDb); LIB_FUNCTION("ieP6jP138Qo", "libSceSaveData", 1, "libSceSaveData", 1, 1, sceSaveDataIsMounted); LIB_FUNCTION("cGjO3wM3V28", "libSceSaveData", 1, "libSceSaveData", 1, 1, sceSaveDataLoadIcon); LIB_FUNCTION("32HQAQdwM2o", "libSceSaveData", 1, "libSceSaveData", 1, 1, sceSaveDataMount); LIB_FUNCTION("0z45PIH+SNI", "libSceSaveData", 1, "libSceSaveData", 1, 1, sceSaveDataMount2); LIB_FUNCTION("xz0YMi6BfNk", "libSceSaveData", 1, "libSceSaveData", 1, 1, sceSaveDataMount5); LIB_FUNCTION("msCER7Iibm8", "libSceSaveData", 1, "libSceSaveData", 1, 1, sceSaveDataMountInternal); LIB_FUNCTION("-XYmdxjOqyA", "libSceSaveData", 1, "libSceSaveData", 1, 1, sceSaveDataMountSys); LIB_FUNCTION("uNu7j3pL2mQ", "libSceSaveData", 1, "libSceSaveData", 1, 1, sceSaveDataPromote5); LIB_FUNCTION("SgIY-XYA2Xg", "libSceSaveData", 1, "libSceSaveData", 1, 1, sceSaveDataRebuildDatabase); LIB_FUNCTION("hsKd5c21sQc", "libSceSaveData", 1, "libSceSaveData", 1, 1, sceSaveDataRegisterEventCallback); LIB_FUNCTION("lU9YRFsgwSU", "libSceSaveData", 1, "libSceSaveData", 1, 1, sceSaveDataRestoreBackupData); LIB_FUNCTION("HuToUt1GQ8w", "libSceSaveData", 1, "libSceSaveData", 1, 1, sceSaveDataRestoreBackupDataForCdlg); LIB_FUNCTION("aoZKKNjlq3Y", "libSceSaveData", 1, "libSceSaveData", 1, 1, sceSaveDataRestoreLoadSaveDataMemory); LIB_FUNCTION("c88Yy54Mx0w", "libSceSaveData", 1, "libSceSaveData", 1, 1, sceSaveDataSaveIcon); LIB_FUNCTION("0VFHv-Fa4w8", "libSceSaveData", 1, "libSceSaveData", 1, 1, sceSaveDataSetAutoUploadSetting); LIB_FUNCTION("52pL2GKkdjA", "libSceSaveData", 1, "libSceSaveData", 1, 1, sceSaveDataSetEventInfo); LIB_FUNCTION("85zul--eGXs", "libSceSaveData", 1, "libSceSaveData", 1, 1, sceSaveDataSetParam); LIB_FUNCTION("v3vg2+cooYw", "libSceSaveData", 1, "libSceSaveData", 1, 1, sceSaveDataSetSaveDataLibraryUser); LIB_FUNCTION("h3YURzXGSVQ", "libSceSaveData", 1, "libSceSaveData", 1, 1, sceSaveDataSetSaveDataMemory); LIB_FUNCTION("cduy9v4YmT4", "libSceSaveData", 1, "libSceSaveData", 1, 1, sceSaveDataSetSaveDataMemory2); LIB_FUNCTION("v7AAAMo0Lz4", "libSceSaveData", 1, "libSceSaveData", 1, 1, sceSaveDataSetupSaveDataMemory); LIB_FUNCTION("oQySEUfgXRA", "libSceSaveData", 1, "libSceSaveData", 1, 1, sceSaveDataSetupSaveDataMemory2); LIB_FUNCTION("zMgXM79jRhw", "libSceSaveData", 1, "libSceSaveData", 1, 1, sceSaveDataShutdownStart); LIB_FUNCTION("+orZm32HB1s", "libSceSaveData", 1, "libSceSaveData", 1, 1, sceSaveDataSupportedFakeBrokenStatus); LIB_FUNCTION("LMSQUTxmGVg", "libSceSaveData", 1, "libSceSaveData", 1, 1, sceSaveDataSyncCloudList); LIB_FUNCTION("wiT9jeC7xPw", "libSceSaveData", 1, "libSceSaveData", 1, 1, sceSaveDataSyncSaveDataMemory); LIB_FUNCTION("yKDy8S5yLA0", "libSceSaveData", 1, "libSceSaveData", 1, 1, sceSaveDataTerminate); LIB_FUNCTION("WAzWTZm1H+I", "libSceSaveData", 1, "libSceSaveData", 1, 1, sceSaveDataTransferringMount); LIB_FUNCTION("BMR4F-Uek3E", "libSceSaveData", 1, "libSceSaveData", 1, 1, sceSaveDataUmount); LIB_FUNCTION("2-8NWLS8QSA", "libSceSaveData", 1, "libSceSaveData", 1, 1, sceSaveDataUmountSys); LIB_FUNCTION("VwadwBBBJ80", "libSceSaveData", 1, "libSceSaveData", 1, 1, sceSaveDataUmountWithBackup); LIB_FUNCTION("v-AK1AxQhS0", "libSceSaveData", 1, "libSceSaveData", 1, 1, sceSaveDataUnregisterEventCallback); LIB_FUNCTION("COwz3WBj+5s", "libSceSaveData", 1, "libSceSaveData", 1, 1, sceSaveDataUpload); LIB_FUNCTION("AuTE0gFxZCI", "libSceSaveData", 1, "libSceSaveData", 1, 1, Func_02E4C4D201716422); }; } // namespace Libraries::SaveData