Files
AnyPS5/docs/dev/debt/libSceSaveData.native.md
T

29 KiB

libSceSaveData.native technical debt

Silent stubs

  • sceSaveDataMount3: host filesystem failures and a failed block-count sidecar replacement return the existing INTERNAL error (0x809F000B), following the host-I/O policy used by other save operations. The corresponding PS5 error mapping for each host failure is not confirmed; directory creation and sidecar replacement must finish before exposing a mount.

  • sceSaveDataAbort, sceSaveDataClearProgress (libSceSaveData.native) return SAVE_DATA_OK after initialization without cancelling operations or clearing any progress. The current host operations run synchronously and no backup progress is tracked; sceSaveDataClearProgress does not reset sceSaveDataGetConvertProgress. Their console signatures, errors and behavior are unverified.

  • sceSaveDataPrepare, sceSaveDataCommit (libSceSaveData.native) return success without looking at their arguments; what preparing and committing a mounted save do on a console is unknown. The same six titles call both; this counts tests of the result in the three instructions after each call. sceSaveDataPrepare has 11 call sites: PPSA01494, PPSA30839 and PPSA31162 test none of them there, PPSA03984 takes an error path when it is negative, PPSA34502 checks it at one of two sites and PPSA36591 at two of four. sceSaveDataCommit has 14: PPSA01494 and PPSA30839 test none, PPSA03984 and PPSA31162 check its sign, PPSA34502 checks it at all five sites and PPSA36591 at two of four. A failure code from either would change the save flow of the titles that check it, while the stubs keep all six on the success path.

Unknown function info

Functional

  • sceSaveDataDirNameSearch, sceSaveDataDirNameSearch2 (libSceSaveData.native) - only the running title's saves are kept, so a search with another title's title_id reports zero hits, as shadPS4 and KytyPS5 do when that title has no save directory; what a console returns for another title's saves is not verified. The running title's ID is the titleId in its param.json

  • sceSaveDataDirNameSearch2 (libSceSaveData.native) runs the same search as sceSaveDataDirNameSearch with the same condition and result structures, as SharpEmu 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.

  • sceSaveDataMount2 (libSceSaveData.native) - answers as a PS4 Pro (firmware 10.71) did in #8285: SAVE_DATA_ERROR_NOT_INITIALIZED before sceSaveDataInitialize3, SAVE_DATA_ERROR_INVALID_LOGIN_USER for user IDs -1 and 0, and SAVE_DATA_ERROR_PARAMETER for mount mode 0, for a CREATE mount of fewer than 96 blocks and for a directory name containing _ (in either case; surveytest and surveyTestAuto0 mount). The 96-block minimum is applied to CREATE only, since CREATE2 was not measured, and the _ rule was measured with CREATE mounts only. Other user IDs are not checked against the signed-in users. None of these rows were measured on a PS5 for sceSaveDataMount3

  • sceSaveDataMount3 (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 (libSceSaveData.native) - an unterminated or empty directory name, ., .., or a name containing /, \ or : returns SAVE_DATA_ERROR_PARAMETER, which the SDK 12.000 reference lists for an invalid parameter, and leaves the result as it was, as KytyPS5 does. The SDK lets an application use only letters, digits, -, . and @ in a name, and KytyPS5 also accepts _ (valid_path_component); which other names the console's mount rejects is not checked on hardware

  • sceSaveDataBackup (libSceSaveData.native) - an unterminated directory name returns SAVE_DATA_ERROR_PARAMETER to prevent unbounded copy; which other directory names the console's backup rejects is unverified on hardware.

  • sceSaveDataCheckBackupData, sceSaveDataRestoreBackupData, sceSaveDataUmountWithBackup (libSceSaveData.native) - not measured on a PS5: the block layouts, error checks and behaviour follow shadPS4's PS4 binding (check, restore, unmount with backup), 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 (libSceSaveData.native) - an unknown mount point returns SAVE_DATA_ERROR_NOT_MOUNTED, as sceSaveDataUmount2 does, where shadPS4 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 does; a failed copy sets its error to SAVE_DATA_ERROR_INTERNAL, as shadPS4's backup thread does.

  • sceSaveDataCheckBackupData, sceSaveDataRestoreBackupData (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 inside the save directory goes with it. On a PS4 Pro (firmware 10.71) another title's titleId also returns SAVE_DATA_ERROR_NOT_FOUND, and sceSaveDataDelete drops the backup too: after it the check and the restore return SAVE_DATA_ERROR_NOT_FOUND (run).

  • sceSaveDataCheckBackupData (libSceSaveData.native) - a param buffer that isn't zeroed is not refused here. On a PS4 Pro (firmware 10.71, the system libSceSaveData.sprx, SDK 0x05050001) a 1,328-byte param filled with 0xAA gave SAVE_DATA_ERROR_PARAMETER on every call, also with a backup present, while a zeroed or null one gave 0 (run). Which bytes the console checks wasn't measured, so the check fills param whatever it held. Not measured on a PS5.

  • sceSaveDataRestoreBackupData (libSceSaveData.native) - a crash during a restore is not recovered at the next start, and what the console leaves is not measured. A failed restore leaves the live save, its .param and its blocks file as they were: the backup is copied to _sd_restore_tmp/<dir name> and checked first, and a failed move puts back what was already moved; only if that fails too does the old save stay in _sd_restore_old/<dir name>. A crash during the copy leaves the live save untouched. One during the moves can leave parts of the live save in _sd_restore_old/<dir name> and the copy in _sd_restore_tmp/<dir name> or _sd; while the save's directory is missing from _sd, its restore returns SAVE_DATA_ERROR_INTERNAL and a creating mount makes an empty save. The next restore of the save first removes both directories.

  • sceSaveDataRegisterEventCallback, sceSaveDataUnregisterEventCallback (libSceSaveData.native) - not measured on a console: the behaviour follows shadPS4 #5226, and PS5's signatures are assumed to be PS4's (void (*)(const SaveDataEvent*, void*) and a userdata pointer). Unknown: which thread delivers, its priority and affinity, when it runs relative to the call, whether unregistering drops completions not yet delivered (here it does), and what a terminate from inside the callback does (here SAVE_DATA_ERROR_BUSY, as in shadPS4). Here the first successful sceSaveDataRegisterEventCallback after sceSaveDataInitialize3 starts the delivering thread with scePthreadCreate, the last sceSaveDataTerminate joins it if one was started, and each backup completion (type 1 or 2) is queued for sceSaveDataGetEventResult before it goes to the callback registered at that moment.

  • sceSaveDataUmountWithBackup (libSceSaveData.native) - a crash during a backup is not recovered at the next start either. A failed backup keeps the previous one in _sd_backup/<dir name>. A crash between its two moves leaves the previous backup in _sd_backup_old/<dir name>, where the check and the restore do not find it (SAVE_DATA_ERROR_NOT_FOUND); the next backup of the save removes it, and any partial copy in _sd_backup_tmp/<dir name>, before it copies.

  • sceSaveDataRestoreBackupData, sceSaveDataUmountWithBackup (libSceSaveData.native) - on Windows nothing shows that a backup's or a restore's renames survive a crash or power loss. Every staged file is flushed before the first rename (FlushFileBuffers on Windows, fsync on POSIX, as libc does), and on POSIX the directories on both sides of each rename are flushed after it; Windows cannot flush directory entries, so there it rests on NTFS journaling.

  • sceSaveDataBackup, sceSaveDataConvert, sceSaveDataDelete, sceSaveDataDirNameSearch, sceSaveDataDirNameSearchPs4, sceSaveDataGetEventResult, sceSaveDataGetMountInfo, sceSaveDataGetParam, sceSaveDataLoadIcon, sceSaveDataMount, sceSaveDataMount3, sceSaveDataSaveIcon, sceSaveDataSetParam, sceSaveDataUmount2 (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

  • sceSaveDataUmount2 (libSceSaveData.native) - a mount point that is not mounted returns SAVE_DATA_ERROR_NOT_MOUNTED (0x809F0004), which no console run has checked. KytyPS5's sceSaveDataUmount2 (PS5, src/libs/libSaveData.cpp, commit 5e837a9beac82c031868eaad9348d6a48245fb99) returns SAVE_DATA_ERROR_NOT_FOUND (0x809F0008) there. For the PS4 sceSaveDataUmount, which none of the local titles import, shadPS4 (PS4, src/core/libraries/save_data/savedata.cpp, commit 0614ce324be092791b758d79f93d9f1ca5b10a81) returns SAVE_DATA_ERROR_NOT_FOUND and fpPS4 (PS4, sys/sys_path.pas, commit 04cefd43e6fddd1ab033e7980cd356d14c964905) SAVE_DATA_ERROR_NOT_MOUNTED. Forty of the 53 local titles import sceSaveDataUmount2.

  • sceSaveDataSyncSaveDataMemory (libSceSaveData.native) - validates library initialization and parameters; since in-memory saves flush synchronously on write, sync confirms existence and readiness of the memory blob

  • sceSaveDataGetMountedSaveDataCount (libSceSaveData.native) - signature and behavior are unverified; the function throws. SharpEmu and RPS5E write the count through a second argument and return a status; their console accuracy is unverified.

  • sceSaveDataMount3 (libSceSaveData.native) - the console's NO_SPACE_FS code, the rounding of required_blocks and whether it checks the volume when a save is created are not measured; with ANYPS5_FREE_SPACE=host a save larger than the room of its volume fails with 0x809F000A and sets required_blocks to the missing blocks rounded up to 64 KiB, as shadPS4 does at savedata.cpp L468-L481, which rounds down and checks the full size

  • sceSaveDataGetMountInfo, sceSaveDataDirNameSearch (libSceSaveData.native) - whether the console bounds a save's free blocks by the room of the disk is not measured; with ANYPS5_FREE_SPACE=host they are the smaller of the save's own free blocks and the 64 KiB blocks its volume still has, and a full volume reports none

  • sceSaveDataMount3, sceSaveDataGetMountInfo (libSceSaveData.native) - the 64 KiB block that the room and free-block figures use is not measured on a console; shadPS4 L50-L52 uses 32 KiB blocks and 96 to 32768 blocks, so the bytes that host mode requires for a save, and its free-block counts, would differ by a factor of two if the console uses 32 KiB