feat(libSceSaveData): implement sceSaveDataCheckBackupData, sceSaveDataRestoreBackupData and sceSaveDataUmountWithBackup

This commit is contained in:
Yunus Shaikh committed 2026-10-28 21:18:18 +00:00
1 parent 319621ba2e
commit 5fed277cf2
6 files changed
+317 -31

No files matched your search

+21
View File
@@ -1819,6 +1819,27 @@ struct SaveDataBackup {
std::uint8_t reserved[32];
};
struct SaveDataCheckBackupData {
std::int32_t user_id;
std::int32_t pad;
const SceSaveDataTitleId* title_id;
const SceSaveDataDirName* dir_name;
SaveDataParam* param;
SaveDataIcon* icon;
std::uint8_t reserved[32];
};
struct SaveDataRestoreBackupData {
std::int32_t user_id;
std::int32_t pad;
const SceSaveDataTitleId* title_id;
const SceSaveDataDirName* dir_name;
const void* fingerprint;
std::uint32_t unused;
std::uint8_t reserved[32];
std::int32_t pad2;
};
struct SaveDataConvert {
std::int32_t user_id;
std::int32_t pad;
+187 -27
View File
@@ -38,6 +38,7 @@ static std::string save_root() {
// Events the title polls with sceSaveDataGetEventResult; a backup completes at once here.
static std::mutex g_state_mutex;
static std::deque<SaveDataEvent> g_events;
constexpr std::uint32_t SAVE_DATA_EVENT_TYPE_UMOUNT_BACKUP = 1;
constexpr std::uint32_t SAVE_DATA_EVENT_TYPE_BACKUP = 2;
constexpr std::uint32_t SAVE_DATA_EVENT_TYPE_CONVERT = 5;
@@ -328,16 +329,81 @@ static bool isMountedDirectory(const std::string& path) {
return false;
}
static constexpr char BACKUP_DIR[] = "_sd_backup";
static constexpr char BACKUP_STAGING_DIR[] = "_sd_backup_tmp";
static constexpr char BACKUP_OLD_DIR[] = "_sd_backup_old";
static std::string backup_entry(const char* root, const std::string& dirName) {
return std::string(root) + "/" + dirName;
}
static std::string entry_save(const std::string& entry) {
return entry + "/save";
}
static bool parse_dir_name(const SceSaveDataDirName* name, std::string* dirName) {
const auto* nameEnd = static_cast<const char*>(std::memchr(name->data, '\0', sizeof(name->data)));
if (nameEnd == nullptr) {
return false;
}
dirName->assign(name->data, static_cast<std::size_t>(nameEnd - name->data));
return !dirName->empty() && *dirName != "." && *dirName != ".." && dirName->find_first_of("/\\:") == std::string::npos;
}
static bool running_title(const SceSaveDataTitleId* titleId) {
return titleId == nullptr || std::strncmp(titleId->data, GetAppTitleId_nid_postfix().value, sizeof(titleId->data)) == 0;
}
static void backupSave(const std::string& dirName) {
const std::string source = save_root() + "/" + dirName;
const std::string entry = backup_entry(BACKUP_DIR, dirName);
const std::string staged = backup_entry(BACKUP_STAGING_DIR, dirName);
const std::string old = backup_entry(BACKUP_OLD_DIR, dirName);
RemoveTree(staged);
RemoveTree(old);
std::filesystem::create_directories(entry_save(staged));
std::filesystem::copy(source, entry_save(staged), std::filesystem::copy_options::recursive);
if (!store_param(entry_save(staged), load_param(source))) {
throw std::runtime_error("SaveData: cannot write the backup's param file for " + source);
}
if (std::filesystem::exists(blocks_path(source))) {
std::filesystem::copy_file(blocks_path(source), blocks_path(entry_save(staged)));
}
std::filesystem::create_directories(BACKUP_DIR);
const bool replace = std::filesystem::exists(entry);
if (replace) {
std::filesystem::create_directories(BACKUP_OLD_DIR);
std::filesystem::rename(entry, old);
}
std::filesystem::rename(staged, entry);
if (replace) {
RemoveTree(old);
}
}
static void restoreSave(const std::string& target, const std::string& backup) {
std::vector<std::filesystem::path> current;
for (const auto& file : std::filesystem::directory_iterator(target)) {
current.push_back(file.path());
}
for (const auto& file : current) {
RemoveTree(file);
}
std::filesystem::copy(backup, target, std::filesystem::copy_options::recursive);
std::filesystem::copy_file(param_path(backup), param_path(target), std::filesystem::copy_options::overwrite_existing);
if (std::filesystem::exists(blocks_path(backup))) {
std::filesystem::copy_file(blocks_path(backup), blocks_path(target), std::filesystem::copy_options::overwrite_existing);
} else {
std::filesystem::remove(blocks_path(target));
}
}
static int deleteSave(const SaveDataDelete* del) {
if (del == nullptr || del->dir_name == nullptr) {
return SAVE_DATA_ERROR_PARAMETER;
}
const auto* nameEnd = static_cast<const char*>(std::memchr(del->dir_name->data, '\0', sizeof(del->dir_name->data)));
if (nameEnd == nullptr) {
return SAVE_DATA_ERROR_PARAMETER;
}
const std::string dirName(del->dir_name->data, static_cast<std::size_t>(nameEnd - del->dir_name->data));
if (dirName.empty() || dirName == "." || dirName == ".." || dirName.find_first_of("/\\:") != std::string::npos) {
std::string dirName;
if (!parse_dir_name(del->dir_name, &dirName)) {
return SAVE_DATA_ERROR_PARAMETER;
}
const std::string path = save_root() + "/" + dirName;
@@ -349,6 +415,7 @@ static int deleteSave(const SaveDataDelete* del) {
std::error_code ec;
std::filesystem::remove(param_path(path), ec);
std::filesystem::remove(blocks_path(path), ec);
RemoveTree(backup_entry(BACKUP_DIR, dirName));
return SAVE_DATA_OK;
}
@@ -618,12 +685,8 @@ static int mount3(const SaveDataMount3* mount, SaveDataMountResult* mount_result
if (!create && !create2 && !open) {
throw std::runtime_error("sceSaveDataMount3: unknown mount_mode");
}
const auto* nameEnd = static_cast<const char*>(std::memchr(mount->dir_name->data, '\0', sizeof(mount->dir_name->data)));
if (nameEnd == nullptr) {
return SAVE_DATA_ERROR_PARAMETER;
}
const std::string dirName(mount->dir_name->data, static_cast<std::size_t>(nameEnd - mount->dir_name->data));
if (dirName.empty() || dirName == "." || dirName == ".." || dirName.find_first_of("/\\:") != std::string::npos) {
std::string dirName;
if (!parse_dir_name(mount->dir_name, &dirName)) {
return SAVE_DATA_ERROR_PARAMETER;
}
const std::string real_path = save_root() + "/" + dirName;
@@ -649,6 +712,7 @@ static int mount3(const SaveDataMount3* mount, SaveDataMountResult* mount_result
AddPathAlias_nid_no_patch(mountPoint.c_str(), std::filesystem::absolute(real_path).string().c_str());
std::memset(mount_result, 0, sizeof(*mount_result));
g_slots[slot].used = true;
g_slots[slot].user_id = mount->user_id;
g_slots[slot].mount_point = mountPoint;
g_slots[slot].real_path = real_path;
std::memcpy(mount_result->mount_point.data, mountPoint.c_str(), mountPoint.size() + 1);
@@ -943,12 +1007,8 @@ static int convert(const SaveDataConvert* param) {
return SAVE_DATA_ERROR_PARAMETER;
}
g_convertProgress = 0.0f;
const auto* nameEnd = static_cast<const char*>(std::memchr(param->dir_name->data, '\0', sizeof(param->dir_name->data)));
if (nameEnd == nullptr) {
return SAVE_DATA_ERROR_PARAMETER;
}
const std::string dirName(param->dir_name->data, static_cast<std::size_t>(nameEnd - param->dir_name->data));
if (dirName.empty() || dirName == "." || dirName == ".." || dirName.find_first_of("/\\:") != std::string::npos) {
std::string dirName;
if (!parse_dir_name(param->dir_name, &dirName)) {
return SAVE_DATA_ERROR_PARAMETER;
}
if (!std::filesystem::is_directory(save_root() + "/" + dirName)) {
@@ -1065,9 +1125,38 @@ int APS5_VABI sceSaveDataChangeInternal(void) {
return 0;
}
int APS5_VABI sceSaveDataCheckBackupData(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
static int checkBackupData(const SaveDataCheckBackupData* check) {
if (g_initializations == 0) {
return SAVE_DATA_ERROR_NOT_INITIALIZED;
}
if (check == nullptr || check->dir_name == nullptr) {
return SAVE_DATA_ERROR_PARAMETER;
}
std::string dirName;
if (!parse_dir_name(check->dir_name, &dirName)) {
return SAVE_DATA_ERROR_PARAMETER;
}
if (!running_title(check->title_id)) {
return SAVE_DATA_ERROR_NOT_FOUND;
}
std::lock_guard lock(g_slots_mutex);
if (isMountedDirectory(save_root() + "/" + dirName)) {
return SAVE_DATA_ERROR_BUSY;
}
const std::string backup = entry_save(backup_entry(BACKUP_DIR, dirName));
if (!std::filesystem::is_directory(backup)) {
return SAVE_DATA_ERROR_NOT_FOUND;
}
if (check->param != nullptr && !read_param_file(param_path(backup), check->param)) {
return SAVE_DATA_ERROR_INTERNAL;
}
return SAVE_DATA_OK;
}
int APS5_VABI sceSaveDataCheckBackupData(const SaveDataCheckBackupData* check) {
const int rc = checkBackupData(check);
SAVEDATA_TRACE("checkBackupData dir=%.*s -> 0x%08x", static_cast<int>(sizeof(SceSaveDataDirName::data)), check != nullptr && check->dir_name != nullptr ? check->dir_name->data : "(null)", static_cast<unsigned>(rc));
return rc;
}
int APS5_VABI sceSaveDataCheckBackupDataForCdlg(void) {
@@ -1305,9 +1394,44 @@ int APS5_VABI sceSaveDataRegisterEventCallback() {
return 0;
}
int APS5_VABI sceSaveDataRestoreBackupData(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
static int restoreBackupData(const SaveDataRestoreBackupData* restore) {
if (g_initializations == 0) {
return SAVE_DATA_ERROR_NOT_INITIALIZED;
}
if (restore == nullptr || restore->dir_name == nullptr) {
return SAVE_DATA_ERROR_PARAMETER;
}
std::string dirName;
if (!parse_dir_name(restore->dir_name, &dirName)) {
return SAVE_DATA_ERROR_PARAMETER;
}
if (!running_title(restore->title_id)) {
return SAVE_DATA_ERROR_NOT_FOUND;
}
const std::string target = save_root() + "/" + dirName;
std::lock_guard lock(g_slots_mutex);
if (isMountedDirectory(target)) {
return SAVE_DATA_ERROR_BUSY;
}
const std::string backup = entry_save(backup_entry(BACKUP_DIR, dirName));
if (!std::filesystem::is_directory(backup)) {
return SAVE_DATA_ERROR_NOT_FOUND;
}
if (!std::filesystem::is_directory(target)) {
return SAVE_DATA_ERROR_INTERNAL;
}
try {
restoreSave(target, backup);
} catch (const std::filesystem::filesystem_error&) {
return SAVE_DATA_ERROR_INTERNAL;
}
return SAVE_DATA_OK;
}
int APS5_VABI sceSaveDataRestoreBackupData(const SaveDataRestoreBackupData* restore) {
const int rc = restoreBackupData(restore);
SAVEDATA_TRACE("restoreBackupData dir=%.*s -> 0x%08x", static_cast<int>(sizeof(SceSaveDataDirName::data)), restore != nullptr && restore->dir_name != nullptr ? restore->dir_name->data : "(null)", static_cast<unsigned>(rc));
return rc;
}
int APS5_VABI sceSaveDataRestoreBackupDataForCdlg(void) {
@@ -1355,9 +1479,45 @@ int APS5_VABI sceSaveDataUmountSys(void) {
return 0;
}
int APS5_VABI sceSaveDataUmountWithBackup(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
static int umountWithBackup(const SaveDataMountPoint* mount_point) {
if (g_initializations == 0) {
return SAVE_DATA_ERROR_NOT_INITIALIZED;
}
if (mount_point == nullptr) {
return SAVE_DATA_ERROR_PARAMETER;
}
SaveDataEvent event{};
event.type = SAVE_DATA_EVENT_TYPE_UMOUNT_BACKUP;
event.error_code = SAVE_DATA_OK;
{
std::lock_guard lock(g_slots_mutex);
const int slot = find_slot_by_mount_point(mount_point->data);
if (slot == -1) {
return SAVE_DATA_ERROR_NOT_MOUNTED;
}
const AppTitleId title = GetAppTitleId_nid_postfix();
const MountSlot mounted = g_slots[slot];
RemovePathAlias_nid_no_patch(mounted.mount_point.c_str());
g_slots[slot] = MountSlot{};
const std::string dirName = std::filesystem::path(mounted.real_path).filename().string();
event.user_id = mounted.user_id;
std::memcpy(event.title_id.data, title.value, strnlen(title.value, sizeof(event.title_id.data) - 1));
std::memcpy(event.dir_name.data, dirName.data(), std::min(dirName.size(), sizeof(event.dir_name.data) - 1));
try {
backupSave(dirName);
} catch (const std::exception&) {
event.error_code = SAVE_DATA_ERROR_INTERNAL;
}
}
std::lock_guard lock(g_state_mutex);
g_events.push_back(event);
return SAVE_DATA_OK;
}
int APS5_VABI sceSaveDataUmountWithBackup(const SaveDataMountPoint* mount_point) {
const int rc = umountWithBackup(mount_point);
SAVEDATA_TRACE("umountWithBackup point=%s -> 0x%08x", mount_point != nullptr ? mount_point->data : "(null)", static_cast<unsigned>(rc));
return rc;
}
int APS5_VABI sceSaveDataUnregisterEventCallback() {
@@ -45,6 +45,7 @@ constexpr std::size_t SAVE_DATA_MOUNT_SLOTS = 16;
struct MountSlot {
bool used = false;
std::int32_t user_id = 0;
std::string mount_point;
std::string real_path;
};
+3 -3
View File
@@ -18,7 +18,7 @@ int APS5_VABI sceSaveDataBindPsnAccount(void);
int APS5_VABI sceSaveDataBindPsnAccountForSystemBackup(void);
int APS5_VABI sceSaveDataChangeDatabase(void);
int APS5_VABI sceSaveDataChangeInternal(void);
int APS5_VABI sceSaveDataCheckBackupData(void);
int APS5_VABI sceSaveDataCheckBackupData(const SaveDataCheckBackupData* check);
int APS5_VABI sceSaveDataCheckBackupDataForCdlg(void);
int APS5_VABI sceSaveDataCheckBackupDataInternal(void);
int APS5_VABI sceSaveDataCheckBackupDataLight(void);
@@ -66,7 +66,7 @@ int APS5_VABI sceSaveDataMountInternal(void);
int APS5_VABI sceSaveDataMountSys(void);
int APS5_VABI sceSaveDataRebuildDatabase(void);
int APS5_VABI sceSaveDataRegisterEventCallback();
int APS5_VABI sceSaveDataRestoreBackupData(void);
int APS5_VABI sceSaveDataRestoreBackupData(const SaveDataRestoreBackupData* restore);
int APS5_VABI sceSaveDataRestoreBackupDataForCdlg(void);
int APS5_VABI sceSaveDataRestoreLoadSaveDataMemory(void);
int APS5_VABI sceSaveDataSetAutoUploadSetting(void);
@@ -76,7 +76,7 @@ int APS5_VABI sceSaveDataShutdownStart(void);
int APS5_VABI sceSaveDataSupportedFakeBrokenStatus(void);
int APS5_VABI sceSaveDataSyncCloudList(void);
int APS5_VABI sceSaveDataUmountSys(void);
int APS5_VABI sceSaveDataUmountWithBackup(void);
int APS5_VABI sceSaveDataUmountWithBackup(const SaveDataMountPoint* mount_point);
int APS5_VABI sceSaveDataUnregisterEventCallback();
int APS5_VABI sceSaveDataUpload(void);
@@ -5,6 +5,8 @@
#include <cstdio>
#include <cstring>
#include <filesystem>
#include <fstream>
#include <iterator>
#include <random>
#include <set>
#include <string>
@@ -19,6 +21,7 @@ constexpr std::uint32_t MountReadWrite = 2;
constexpr std::uint32_t MountCreate = 4;
constexpr std::uint32_t MountCreate2 = 32;
constexpr int SaveDataErrorParameter = -2137063424;
constexpr int SaveDataErrorNotInitialized = -2137063423;
constexpr int SaveDataErrorNotMounted = -2137063420;
constexpr int SaveDataErrorBusy = -2137063421;
constexpr int SaveDataErrorNotFound = -2137063416;
@@ -66,6 +69,15 @@ void CheckUnmountedCalls() {
SaveDataEvent emptyEvent{};
Check(sceSaveDataGetEventResult(nullptr, &emptyEvent) == SaveDataErrorNotFound, "getEventResult on empty queue returns NOT_FOUND");
Check(sceSaveDataGetEventResult(&emptyEvent, nullptr) == SaveDataErrorParameter, "getEventResult null result pointer is PARAMETER");
SceSaveDataDirName dirName{};
std::memcpy(dirName.data, "fresh", sizeof("fresh"));
SaveDataCheckBackupData check{};
check.dir_name = &dirName;
Check(sceSaveDataCheckBackupData(&check) == SaveDataErrorNotInitialized, "checkBackupData before initialize is NOT_INITIALIZED");
SaveDataRestoreBackupData restore{};
restore.dir_name = &dirName;
Check(sceSaveDataRestoreBackupData(&restore) == SaveDataErrorNotInitialized, "restoreBackupData before initialize is NOT_INITIALIZED");
Check(sceSaveDataUmountWithBackup(&point) == SaveDataErrorNotInitialized, "umountWithBackup before initialize is NOT_INITIALIZED");
}
void CheckMountInfoAndParam(const SaveDataMountPoint* point) {
@@ -236,6 +248,94 @@ void CheckInvalidNamesAreRejected() {
MountAndCheck("after", MountReadWrite | MountCreate2, 1u, "CREATE2 after the rejected names");
}
void WriteText(const std::filesystem::path& path, const std::string& text) {
std::ofstream file(path, std::ios::binary | std::ios::trunc);
file << text;
Check(static_cast<bool>(file), "writes " + path.string());
}
std::string ReadText(const std::filesystem::path& path) {
std::ifstream file(path, std::ios::binary);
return std::string(std::istreambuf_iterator<char>(file), std::istreambuf_iterator<char>());
}
bool MountSave(SceSaveDataDirName* dirName, std::uint32_t mode, SaveDataMountResult* result) {
SaveDataMount3 mount{};
mount.user_id = 7;
mount.dir_name = dirName;
mount.mount_mode = mode;
return sceSaveDataMount3(&mount, result) == 0;
}
void CheckBackups() {
std::filesystem::create_directories("app0/sce_sys");
WriteText("app0/sce_sys/param.json", "{\"titleId\":\"PPSA00000\",\"localizedParameters\":{\"en-US\":{\"titleName\":\"Example\"}}}");
SceSaveDataDirName kept{};
std::memcpy(kept.data, "kept", sizeof("kept"));
SceSaveDataDirName fresh{};
std::memcpy(fresh.data, "fresh", sizeof("fresh"));
SaveDataCheckBackupData check{};
SaveDataRestoreBackupData restore{};
Check(sceSaveDataCheckBackupData(nullptr) == SaveDataErrorParameter, "checkBackupData null block is PARAMETER");
Check(sceSaveDataCheckBackupData(&check) == SaveDataErrorParameter, "checkBackupData null dir_name is PARAMETER");
Check(sceSaveDataRestoreBackupData(nullptr) == SaveDataErrorParameter, "restoreBackupData null block is PARAMETER");
Check(sceSaveDataRestoreBackupData(&restore) == SaveDataErrorParameter, "restoreBackupData null dir_name is PARAMETER");
Check(sceSaveDataUmountWithBackup(nullptr) == SaveDataErrorParameter, "umountWithBackup null point is PARAMETER");
check.dir_name = &fresh;
restore.dir_name = &fresh;
Check(sceSaveDataCheckBackupData(&check) == SaveDataErrorNotFound, "checkBackupData without a backup is NOT_FOUND");
Check(sceSaveDataRestoreBackupData(&restore) == SaveDataErrorNotFound, "restoreBackupData without a backup is NOT_FOUND");
SaveDataMountResult mounted{};
Check(MountSave(&kept, MountReadWrite | MountCreate2, &mounted), "the backed-up save mounts");
const auto data = std::filesystem::path("_sd") / "kept" / "data.bin";
WriteText(data, "first");
const char backedUpTitle[] = "Backed Up";
Check(sceSaveDataSetParam(&mounted.mount_point, 1, backedUpTitle, sizeof(backedUpTitle)) == 0, "the backed-up save takes a title");
check.dir_name = &kept;
restore.dir_name = &kept;
Check(sceSaveDataCheckBackupData(&check) == SaveDataErrorBusy, "checkBackupData of a mounted save is BUSY");
Check(sceSaveDataRestoreBackupData(&restore) == SaveDataErrorBusy, "restoreBackupData of a mounted save is BUSY");
Check(sceSaveDataUmountWithBackup(&mounted.mount_point) == 0, "umountWithBackup succeeds");
SaveDataMountInfo info{};
Check(sceSaveDataGetMountInfo(&mounted.mount_point, &info) == SaveDataErrorNotMounted, "umountWithBackup unmounts the save");
SaveDataEvent event{};
Check(sceSaveDataGetEventResult(nullptr, &event) == 0, "umountWithBackup posts an event");
Check(event.type == 1, "the event is UMOUNT_BACKUP, got type " + std::to_string(event.type));
Check(event.error_code == 0, "the backup succeeded, error " + std::to_string(event.error_code));
Check(event.user_id == 7, "the event names the mounting user");
Check(std::strncmp(event.title_id.data, "PPSA00000", sizeof(event.title_id.data)) == 0, "the event names the running title");
Check(std::strncmp(event.dir_name.data, "kept", sizeof(event.dir_name.data)) == 0, "the event names the save");
SaveDataParam param{};
check.param = &param;
Check(sceSaveDataCheckBackupData(&check) == 0, "checkBackupData finds the backup");
Check(std::strcmp(param.title, backedUpTitle) == 0, "checkBackupData fills param from the backup");
SceSaveDataTitleId running{};
std::memcpy(running.data, "PPSA00000", sizeof("PPSA00000"));
check.title_id = &running;
Check(sceSaveDataCheckBackupData(&check) == 0, "checkBackupData with the running title's id finds the backup");
WriteText(data, "second");
WriteText(std::filesystem::path("_sd") / "kept" / "added.bin", "added");
Check(MountSave(&kept, MountReadWrite, &mounted), "the save mounts again after the backup");
const char changedTitle[] = "Changed";
Check(sceSaveDataSetParam(&mounted.mount_point, 1, changedTitle, sizeof(changedTitle)) == 0, "the title changes after the backup");
Check(sceSaveDataUmount2(0, &mounted.mount_point) == 0, "the changed save unmounts");
Check(sceSaveDataRestoreBackupData(&restore) == 0, "restoreBackupData succeeds");
Check(ReadText(data) == "first", "restoreBackupData puts back a file changed after the backup");
Check(!std::filesystem::exists(std::filesystem::path("_sd") / "kept" / "added.bin"), "restoreBackupData removes a file added after the backup");
char title[128]{};
Check(MountSave(&kept, MountReadWrite, &mounted), "the restored save mounts");
Check(sceSaveDataGetParam(&mounted.mount_point, 1, title, sizeof(title), nullptr) == 0 && std::strcmp(title, backedUpTitle) == 0, "restoreBackupData puts back the backed-up title");
Check(sceSaveDataUmount2(0, &mounted.mount_point) == 0, "the restored save unmounts");
SaveDataDelete del{};
del.dir_name = &kept;
Check(sceSaveDataDelete(&del) == 0, "the backed-up save is deleted");
Check(sceSaveDataCheckBackupData(&check) == SaveDataErrorNotFound, "deleting a save deletes its backup");
}
}
int main() {
@@ -253,6 +353,7 @@ int main() {
CheckFailedMountKeepsResult();
CheckEquivalentMounts();
CheckInvalidNamesAreRejected();
CheckBackups();
std::filesystem::current_path(previous);
std::error_code error;
std::filesystem::remove_all(root, error);
+4 -1
View File
@@ -16,7 +16,7 @@
- [sceSaveDataDirNameSearch, sceSaveDataGetMountInfo, sceSaveDataMount3](../../../core/libs/prx/libSceSaveData.native/Export.cpp) (libSceSaveData.native) - the 64 KiB block of [SharpEmu](https://github.com/sharpemu/sharpemu/blob/c6d304cc3328bad62cb671327ace181110cf2ddb/src/SharpEmu.Libs/SaveData/SaveDataExports.cs#L82-L84) and the 48 to 16384-block range of SharpEmu and [KytyPS5](https://github.com/KytyPS5/KytyPS5/blob/569ec04d1a77127c601eed522ad5bba187173f9d/src/libs/libSaveData.cpp#L30-L31) are not measured on a console, and file system overhead is not counted. What the console does with a `blocks` outside that range is unknown: KytyPS5 rejects the creating mount and SharpEmu clamps it; here a larger one is kept as 16384 and a smaller non-zero one as given. A save's size is the `blocks` of the mount that created it, kept in `<dir>.blocks`, else that of its next mount that names one, else 16384; its free blocks are that size less its files, each rounded up to 64 KiB blocks
- [sceSaveDataInitialize3](../../../core/libs/prx/libSceSaveData.native/Export.cpp) (libSceSaveData.native) - a repeated initialize succeeds: PPSA12544's executable and its Unity SaveData plugin both initialize, and the plugin fails on any error. Initializations are counted and `sceSaveDataTerminate` ends the session at the last one; how the console pairs them is unknown. In PPSA12544 the executable calls it once (0x1721d8f) with 0 as its argument, only while a flag byte of its object is 0, and does not import `sceSaveDataTerminate`; the plugin calls it once (0x1fd2) with 0, skips the call while a flag byte of its own is set, and calls `sceSaveDataTerminate` from two functions (0x2059, 0x20d9) that clear that byte.
- [sceSaveDataCheckIpmiIfSize, sceSaveDataDebug, sceSaveDataDebugCheckBackupData, sceSaveDataDebugCleanMount, sceSaveDataDebugCompiledSdkVersion, sceSaveDataDebugCreateSaveDataRoot, sceSaveDataDebugProspero, sceSaveDataDebugRemoveSaveDataRoot and sceSaveDataDebugTarget](../../../core/libs/prx/libSceSaveData.native/Export.cpp) (libSceSaveData.native) - names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceSaveData.native.c). The seven no-argument prototypes follow the PS4 stubs in [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/f68923fe807f657348e6719ce658bcbd0a2b1c72/src/core/libraries/save_data/savedata.cpp#L653-L725), not measured on a console; no credible prototypes were found for the other two. All declarations have no parameters solely to resolve known imports and make no ABI claim.
- The following pending declarations have unverified signatures and behavior; they are parameterless import-resolution declarations and throw through `NotImplemented_nid_no_patch`: [`sceSaveDataAbort`](../../../core/libs/prx/libSceSaveData.native/Export.cpp), [`sceSaveDataBindPsnAccount`](../../../core/libs/prx/libSceSaveData.native/Export.cpp), [`sceSaveDataBindPsnAccountForSystemBackup`](../../../core/libs/prx/libSceSaveData.native/Export.cpp), [`sceSaveDataChangeDatabase`](../../../core/libs/prx/libSceSaveData.native/Export.cpp), [`sceSaveDataChangeInternal`](../../../core/libs/prx/libSceSaveData.native/Export.cpp), [`sceSaveDataCheckBackupData`](../../../core/libs/prx/libSceSaveData.native/Export.cpp), [`sceSaveDataCheckBackupDataForCdlg`](../../../core/libs/prx/libSceSaveData.native/Export.cpp), [`sceSaveDataCheckBackupDataInternal`](../../../core/libs/prx/libSceSaveData.native/Export.cpp), [`sceSaveDataCheckBackupDataLight`](../../../core/libs/prx/libSceSaveData.native/Export.cpp), [`sceSaveDataCheckCloudData`](../../../core/libs/prx/libSceSaveData.native/Export.cpp), [`sceSaveDataCheckSaveDataBroken`](../../../core/libs/prx/libSceSaveData.native/Export.cpp), [`sceSaveDataCheckSaveDataVersion`](../../../core/libs/prx/libSceSaveData.native/Export.cpp), [`sceSaveDataCheckSaveDataVersionLatest`](../../../core/libs/prx/libSceSaveData.native/Export.cpp), [`sceSaveDataClearProgress`](../../../core/libs/prx/libSceSaveData.native/Export.cpp), [`sceSaveDataCreateUploadData`](../../../core/libs/prx/libSceSaveData.native/Export.cpp), [`sceSaveDataDebugGetThreadId`](../../../core/libs/prx/libSceSaveData.native/Export.cpp), [`sceSaveDataDeleteAllUser`](../../../core/libs/prx/libSceSaveData.native/Export.cpp), [`sceSaveDataDeleteBackupData`](../../../core/libs/prx/libSceSaveData.native/Export.cpp), [`sceSaveDataDeleteCloudData`](../../../core/libs/prx/libSceSaveData.native/Export.cpp), [`sceSaveDataDeleteProspero`](../../../core/libs/prx/libSceSaveData.native/Export.cpp), [`sceSaveDataDeleteTitle`](../../../core/libs/prx/libSceSaveData.native/Export.cpp), [`sceSaveDataDeleteUser`](../../../core/libs/prx/libSceSaveData.native/Export.cpp), [`sceSaveDataDirNameSearchInternal`](../../../core/libs/prx/libSceSaveData.native/Export.cpp), [`sceSaveDataDownload`](../../../core/libs/prx/libSceSaveData.native/Export.cpp), [`sceSaveDataGetAllSize`](../../../core/libs/prx/libSceSaveData.native/Export.cpp), [`sceSaveDataGetAppLaunchedUser`](../../../core/libs/prx/libSceSaveData.native/Export.cpp), [`sceSaveDataGetAutoUploadConditions`](../../../core/libs/prx/libSceSaveData.native/Export.cpp), [`sceSaveDataGetAutoUploadRequestInfo`](../../../core/libs/prx/libSceSaveData.native/Export.cpp), [`sceSaveDataGetAutoUploadSetting`](../../../core/libs/prx/libSceSaveData.native/Export.cpp), [`sceSaveDataGetBoundPsnAccountCount`](../../../core/libs/prx/libSceSaveData.native/Export.cpp), [`sceSaveDataGetClientThreadPriority`](../../../core/libs/prx/libSceSaveData.native/Export.cpp), [`sceSaveDataGetCloudQuotaInfo`](../../../core/libs/prx/libSceSaveData.native/Export.cpp), [`sceSaveDataGetEventInfo`](../../../core/libs/prx/libSceSaveData.native/Export.cpp), [`sceSaveDataGetMountedSaveDataCount`](../../../core/libs/prx/libSceSaveData.native/Export.cpp), [`sceSaveDataGetProgress`](../../../core/libs/prx/libSceSaveData.native/Export.cpp), [`sceSaveDataGetSaveDataCount`](../../../core/libs/prx/libSceSaveData.native/Export.cpp), [`sceSaveDataGetSaveDataRootDir`](../../../core/libs/prx/libSceSaveData.native/Export.cpp), [`sceSaveDataGetSaveDataRootPath`](../../../core/libs/prx/libSceSaveData.native/Export.cpp), [`sceSaveDataGetSaveDataRootUsbPath`](../../../core/libs/prx/libSceSaveData.native/Export.cpp), [`sceSaveDataGetSavePoint`](../../../core/libs/prx/libSceSaveData.native/Export.cpp), [`sceSaveDataGetUpdatedDataCount`](../../../core/libs/prx/libSceSaveData.native/Export.cpp), [`sceSaveDataInitialize2`](../../../core/libs/prx/libSceSaveData.native/Export.cpp), [`sceSaveDataInitializeForCdlg`](../../../core/libs/prx/libSceSaveData.native/Export.cpp), [`sceSaveDataIsDeletingUsbDb`](../../../core/libs/prx/libSceSaveData.native/Export.cpp), [`sceSaveDataIsMounted`](../../../core/libs/prx/libSceSaveData.native/Export.cpp), [`sceSaveDataManualDelete`](../../../core/libs/prx/libSceSaveData.native/Export.cpp), [`sceSaveDataManualDownload`](../../../core/libs/prx/libSceSaveData.native/Export.cpp), [`sceSaveDataManualUpload`](../../../core/libs/prx/libSceSaveData.native/Export.cpp), [`sceSaveDataMount5`](../../../core/libs/prx/libSceSaveData.native/Export.cpp), [`sceSaveDataMountInternal`](../../../core/libs/prx/libSceSaveData.native/Export.cpp), [`sceSaveDataMountSys`](../../../core/libs/prx/libSceSaveData.native/Export.cpp), [`sceSaveDataRebuildDatabase`](../../../core/libs/prx/libSceSaveData.native/Export.cpp), [`sceSaveDataRegisterEventCallback`](../../../core/libs/prx/libSceSaveData.native/Export.cpp), [`sceSaveDataRestoreBackupData`](../../../corLine truncated
- The following pending declarations have unverified signatures and behavior; they are parameterless import-resolution declarations and throw through `NotImplemented_nid_no_patch`: [`sceSaveDataAbort`](../../../core/libs/prx/libSceSaveData.native/Export.cpp), [`sceSaveDataBindPsnAccount`](../../../core/libs/prx/libSceSaveData.native/Export.cpp), [`sceSaveDataBindPsnAccountForSystemBackup`](../../../core/libs/prx/libSceSaveData.native/Export.cpp), [`sceSaveDataChangeDatabase`](../../../core/libs/prx/libSceSaveData.native/Export.cpp), [`sceSaveDataChangeInternal`](../../../core/libs/prx/libSceSaveData.native/Export.cpp), [`sceSaveDataCheckBackupDataForCdlg`](../../../core/libs/prx/libSceSaveData.native/Export.cpp), [`sceSaveDataCheckBackupDataInternal`](../../../core/libs/prx/libSceSaveData.native/Export.cpp), [`sceSaveDataCheckBackupDataLight`](../../../core/libs/prx/libSceSaveData.native/Export.cpp), [`sceSaveDataCheckCloudData`](../../../core/libs/prx/libSceSaveData.native/Export.cpp), [`sceSaveDataCheckSaveDataBroken`](../../../core/libs/prx/libSceSaveData.native/Export.cpp), [`sceSaveDataCheckSaveDataVersion`](../../../core/libs/prx/libSceSaveData.native/Export.cpp), [`sceSaveDataCheckSaveDataVersionLatest`](../../../core/libs/prx/libSceSaveData.native/Export.cpp), [`sceSaveDataClearProgress`](../../../core/libs/prx/libSceSaveData.native/Export.cpp), [`sceSaveDataCreateUploadData`](../../../core/libs/prx/libSceSaveData.native/Export.cpp), [`sceSaveDataDebugGetThreadId`](../../../core/libs/prx/libSceSaveData.native/Export.cpp), [`sceSaveDataDeleteAllUser`](../../../core/libs/prx/libSceSaveData.native/Export.cpp), [`sceSaveDataDeleteBackupData`](../../../core/libs/prx/libSceSaveData.native/Export.cpp), [`sceSaveDataDeleteCloudData`](../../../core/libs/prx/libSceSaveData.native/Export.cpp), [`sceSaveDataDeleteProspero`](../../../core/libs/prx/libSceSaveData.native/Export.cpp), [`sceSaveDataDeleteTitle`](../../../core/libs/prx/libSceSaveData.native/Export.cpp), [`sceSaveDataDeleteUser`](../../../core/libs/prx/libSceSaveData.native/Export.cpp), [`sceSaveDataDirNameSearchInternal`](../../../core/libs/prx/libSceSaveData.native/Export.cpp), [`sceSaveDataDownload`](../../../core/libs/prx/libSceSaveData.native/Export.cpp), [`sceSaveDataGetAllSize`](../../../core/libs/prx/libSceSaveData.native/Export.cpp), [`sceSaveDataGetAppLaunchedUser`](../../../core/libs/prx/libSceSaveData.native/Export.cpp), [`sceSaveDataGetAutoUploadConditions`](../../../core/libs/prx/libSceSaveData.native/Export.cpp), [`sceSaveDataGetAutoUploadRequestInfo`](../../../core/libs/prx/libSceSaveData.native/Export.cpp), [`sceSaveDataGetAutoUploadSetting`](../../../core/libs/prx/libSceSaveData.native/Export.cpp), [`sceSaveDataGetBoundPsnAccountCount`](../../../core/libs/prx/libSceSaveData.native/Export.cpp), [`sceSaveDataGetClientThreadPriority`](../../../core/libs/prx/libSceSaveData.native/Export.cpp), [`sceSaveDataGetCloudQuotaInfo`](../../../core/libs/prx/libSceSaveData.native/Export.cpp), [`sceSaveDataGetEventInfo`](../../../core/libs/prx/libSceSaveData.native/Export.cpp), [`sceSaveDataGetMountedSaveDataCount`](../../../core/libs/prx/libSceSaveData.native/Export.cpp), [`sceSaveDataGetProgress`](../../../core/libs/prx/libSceSaveData.native/Export.cpp), [`sceSaveDataGetSaveDataCount`](../../../core/libs/prx/libSceSaveData.native/Export.cpp), [`sceSaveDataGetSaveDataRootDir`](../../../core/libs/prx/libSceSaveData.native/Export.cpp), [`sceSaveDataGetSaveDataRootPath`](../../../core/libs/prx/libSceSaveData.native/Export.cpp), [`sceSaveDataGetSaveDataRootUsbPath`](../../../core/libs/prx/libSceSaveData.native/Export.cpp), [`sceSaveDataGetSavePoint`](../../../core/libs/prx/libSceSaveData.native/Export.cpp), [`sceSaveDataGetUpdatedDataCount`](../../../core/libs/prx/libSceSaveData.native/Export.cpp), [`sceSaveDataInitialize2`](../../../core/libs/prx/libSceSaveData.native/Export.cpp), [`sceSaveDataInitializeForCdlg`](../../../core/libs/prx/libSceSaveData.native/Export.cpp), [`sceSaveDataIsDeletingUsbDb`](../../../core/libs/prx/libSceSaveData.native/Export.cpp), [`sceSaveDataIsMounted`](../../../core/libs/prx/libSceSaveData.native/Export.cpp), [`sceSaveDataManualDelete`](../../../core/libs/prx/libSceSaveData.native/Export.cpp), [`sceSaveDataManualDownload`](../../../core/libs/prx/libSceSaveData.native/Export.cpp), [`sceSaveDataManualUpload`](../../../core/libs/prx/libSceSaveData.native/Export.cpp), [`sceSaveDataMount5`](../../../core/libs/prx/libSceSaveData.native/Export.cpp), [`sceSaveDataMountInternal`](../../../core/libs/prx/libSceSaveData.native/Export.cpp), [`sceSaveDataMountSys`](../../../core/libs/prx/libSceSaveData.native/Export.cpp), [`sceSaveDataRebuildDatabase`](../../../core/libs/prx/libSceSaveData.native/Export.cpp), [`sceSaveDataRegisterEventCallback`](../../../core/libs/prx/libSceSaveData.native/Export.cpp), [`sceSaveDataRestoreBackupDataForCdlg`](../../../core/libs/prx/libSceSaveData.native/Export.cpp), [`sceSaveDataRestoreLoadSaveDataMemoLine truncated
### Functional
@@ -24,6 +24,9 @@
- [sceSaveDataDirNameSearch2](../../../core/libs/prx/libSceSaveData.native/Export.cpp) (libSceSaveData.native) runs the same search as `sceSaveDataDirNameSearch` with the same condition and result structures, as [SharpEmu](https://github.com/sharpemu/sharpemu/blob/main/src/SharpEmu.Libs/SaveData/SaveDataExports.cs) does, its only source; what distinguishes it on the console is unknown. One title (PPSA35669, in a plugin) disassembled for this entry has one call site (0x2eda71); it clears 56 bytes for the result structure first (the size of the structure used here), puts a value returned by a call with the argument 0x8000 at offset 8 and 0x400 at offset 0x10, and afterwards branches on a negative result and reads the 32-bit field at offset 0, skipping its loop when it is 0. That agrees with the result structure of `sceSaveDataDirNameSearch`. The condition structure was not read.
- [sceSaveDataMount3](../../../core/libs/prx/libSceSaveData.native/Export.cpp) (libSceSaveData.native) - a failed mount (BUSY, EXISTS, NOT_FOUND, MOUNT_FULL) leaves the result as it was, as shadPS4 does; not checked on hardware. Animal Well (PPSA22520) depends on it: it checks for its save with a mount it never unmounts, then saves with a second mount of the same directory, ignores its BUSY and keeps using the first mount point for `sceSaveDataPrepare`, its file and `sceSaveDataUmount2`. In PPSA05347 the function that calls it at 0x235fbb5 zeroes its 0x40-byte result before the call and, for a negative result other than 0x809f0008 and 0x809f000f (BUSY included), logs the code and returns it without recording the mount; no abort follows.
- [sceSaveDataMount3](../../../core/libs/prx/libSceSaveData.native/Export.cpp) (libSceSaveData.native) - an unterminated or empty directory name, `.`, `..`, or a name containing `/`, `\` or `:` returns `SAVE_DATA_ERROR_PARAMETER`, which the [SDK 12.000 reference](https://github.com/peterpuppy/bm/blob/3f9d1ba6b896a53080460c903f5c15af8affb80d/features/ps5/document/output/psn_12/System/Save_Data/SaveData-Reference/sce-save-data-mount3.md#L196-L204) lists for an invalid parameter, and leaves the result as it was, as [KytyPS5](https://github.com/KytyPS5/KytyPS5/blob/569ec04d1a77127c601eed522ad5bba187173f9d/src/libs/libSaveData.cpp#L652-L658) does. The [SDK](https://github.com/peterpuppy/bm/blob/3f9d1ba6b896a53080460c903f5c15af8affb80d/features/ps5/document/output/psn_12/System/Save_Data/SaveData-Reference/sce-save-data-dir-name.md#L32-L36) lets an application use only letters, digits, `-`, `.` and `@` in a name, and KytyPS5 also accepts `_` ([`valid_path_component`](https://github.com/KytyPS5/KytyPS5/blob/569ec04d1a77127c601eed522ad5bba187173f9d/src/libs/libSaveData.cpp#L276-L287)); which other names the console's mount rejects is not checked on hardware
- [sceSaveDataCheckBackupData, sceSaveDataRestoreBackupData, sceSaveDataUmountWithBackup](../../../core/libs/prx/libSceSaveData.native/Export.cpp) (libSceSaveData.native) - not measured on a PS5: the block layouts, error checks and behaviour follow shadPS4's PS4 binding ([check](https://github.com/shadps4-emu/shadPS4/blob/ce74386283f559f2279a7c31416bcdc9359be0b3/src/core/libraries/save_data/savedata.cpp#L615-L664), [restore](https://github.com/shadps4-emu/shadPS4/blob/ce74386283f559f2279a7c31416bcdc9359be0b3/src/core/libraries/save_data/savedata.cpp#L1360-L1398), [unmount with backup](https://github.com/shadps4-emu/shadPS4/blob/ce74386283f559f2279a7c31416bcdc9359be0b3/src/core/libraries/save_data/savedata.cpp#L1758-L1761)), and the tests check that binding, not the console. `sceSaveDataBackup` still copies nothing, so the check and the restore see only backups that `sceSaveDataUmountWithBackup` made.
- [sceSaveDataUmountWithBackup](../../../core/libs/prx/libSceSaveData.native/Export.cpp) (libSceSaveData.native) - an unknown mount point returns `SAVE_DATA_ERROR_NOT_MOUNTED`, as `sceSaveDataUmount2` does, where [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/ce74386283f559f2279a7c31416bcdc9359be0b3/src/core/libraries/save_data/savedata.cpp#L512-L543) returns `SAVE_DATA_ERROR_NOT_FOUND`. The backup is copied before the call returns, so `SAVE_DATA_ERROR_BACKUP_BUSY` never occurs, and the `UMOUNT_BACKUP` event is queued at once, as [fpPS4](https://github.com/red-prig/fpPS4/blob/04cefd43e6fddd1ab033e7980cd356d14c964905/src/ps4_libscesavedata.pas#L444-L462) does; a failed copy sets its error to `SAVE_DATA_ERROR_INTERNAL`, as shadPS4's [backup thread](https://github.com/shadps4-emu/shadPS4/blob/ce74386283f559f2279a7c31416bcdc9359be0b3/src/core/libraries/save_data/save_backup.cpp#L167-L173) does.
- [sceSaveDataCheckBackupData, sceSaveDataRestoreBackupData](../../../core/libs/prx/libSceSaveData.native/Export.cpp) (libSceSaveData.native) - saves keep no icons, so the check never fills `icon`, as shadPS4 does without `icon0.png`, and another title's `titleId` returns `SAVE_DATA_ERROR_NOT_FOUND` because only the running title's saves are kept. Backups live in `_sd_backup/<dir name>`, outside the save, and `sceSaveDataDelete` removes one with its save, as shadPS4's [backup](https://github.com/shadps4-emu/shadPS4/blob/ce74386283f559f2279a7c31416bcdc9359be0b3/src/core/libraries/save_data/save_backup.cpp#L93-L134) inside the save directory goes with it; whether the console keeps a deleted save's backup is unknown.
- [sceSaveDataBackup, sceSaveDataConvert, sceSaveDataDelete, sceSaveDataDirNameSearch, sceSaveDataDirNameSearchPs4, sceSaveDataGetEventResult, sceSaveDataGetMountInfo, sceSaveDataGetParam, sceSaveDataLoadIcon, sceSaveDataMount, sceSaveDataMount3, sceSaveDataSaveIcon, sceSaveDataSetParam, sceSaveDataUmount2](../../../core/libs/prx/libSceSaveData.native/Export.cpp) (libSceSaveData.native) - null pointer arguments return `SAVE_DATA_ERROR_PARAMETER` instead of throwing C++ exceptions, an implementation assumption matching guest error expectations not checked on hardware; unmounted targets return `SAVE_DATA_ERROR_NOT_MOUNTED`, and parameter type or buffer size violations return `SAVE_DATA_ERROR_PARAMETER` after slot lookup
- [sceSaveDataSyncSaveDataMemory](../../../core/libs/prx/libSceSaveData.native/Export.cpp) (libSceSaveData.native) - validates library initialization and parameters; since in-memory saves flush synchronously on write, sync confirms existence and readiness of the memory blob