mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-11-04 13:07:32 +00:00
feat(libSceSaveData): implement sceSaveDataCheckBackupData, sceSaveDataRestoreBackupData and sceSaveDataUmountWithBackup
This commit is contained in:
1 parent
319621ba2e
commit
5fed277cf2
6 files changed
+317
-31
No files matched your search
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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 = ¶m;
|
||||
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);
|
||||
|
||||
@@ -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
|
||||
Reference in new issue
Block a user