JJenkx's #13324 gives isMountedDirectory an std::error_code parameter.
The merge passes one in checkBackupData and restoreBackupData and
returns SAVE_DATA_ERROR_INTERNAL on a host error, as main's callers do.
GuestSaveDataMountStatus.cpp: CheckBackups writes param.json where
ResolvePath_nid_no_patch puts /app0/sce_sys/param.json, not under the
working directory. #13324's CheckHostMountFailures now resolves the
first guest path from its mount-host-failures subdirectory, so /app0
maps there, and checkBackupData didn't find the title's param.json.
Checks behaviour 5fed277cf2 already has: a second
sceSaveDataUmountWithBackup replaces the first backup; a backup copy
that fails still unmounts and returns 0, with SAVE_DATA_ERROR_INTERNAL
in its event and the previous backup kept; another title's titleId is
SAVE_DATA_ERROR_NOT_FOUND for the check and the restore; ".." and "a/b"
are SAVE_DATA_ERROR_PARAMETER for the check, the restore and the mount a
backup needs, and as mount points sceSaveDataUmountWithBackup reports
them SAVE_DATA_ERROR_NOT_MOUNTED, as it does for any unknown mount
point. The sizes and field offsets of SaveDataCheckBackupData and
SaveDataRestoreBackupData are checked with static_assert.
Before any copy or delete, the backup and the restore check the roots
they use (_sd, _sd_backup and their staging and old directories) and
every entry under them for the save: each must be a plain directory or
file, not a symbolic link, or on Windows a reparse point such as a
junction, and must stay under its root after weakly_canonical. Anything
else makes the restore return SAVE_DATA_ERROR_INTERNAL, and the backup
report it in its event, with nothing changed. sceSaveDataCheckBackupData
checks the backup the same way. The copies walk the tree themselves and
refuse a link instead of following it.
backupSave moved the previous backup to _sd_backup_old and then renamed
the new copy into place; when that rename failed, the save had no
backup until the next one. A failed rename now moves the previous
backup back into _sd_backup and removes the staged copy. Every staged
file is flushed before the swap (FlushFileBuffers on Windows, fsync on
POSIX, as libc does), and on POSIX the directories on both sides of
each rename are flushed too, all before the previous backup is removed.
sceSaveDataRestoreBackupData used to remove the live save's files and
copy the backup over them, so a copy that failed half way left neither.
It now copies the backup, its .param and its blocks file to
_sd_restore_tmp/<dir>, checks that the copy has every entry at its size,
and flushes it. Then it moves the live save, .param and blocks file to
_sd_restore_old/<dir>, moves the staged copy into place and removes the
old one. A failed move puts back everything already moved, so the live
save stays as it was. The staging roots sit next to _sd, not in it,
where sceSaveDataDirNameSearch would list them.
SaveDataSetFileStepHook_nid_no_patch sets a function that is called
before each copy and rename of the backup and restore paths with a step
name and the path written; when it returns false that step fails. It
does nothing unless a test sets it.
sceSaveDataCheckBackupData and sceSaveDataRestoreBackupData now read a
backup through one check that runs before anything in the live save
changes. A backup directory without a save inside, a missing .param, a
.param that is not exactly one SaveDataParam, or a blocks entry that is
not a file returns SAVE_DATA_ERROR_INTERNAL, whether or not the caller
asked for param. Before, the check threw on a wrong-sized .param, and
the restore removed the live save's files before finding the .param
missing.
MinGW's std::filesystem::create_directory_symlink always fails on Windows, so CheckEquivalentMounts never ran its symbolic link alias there and only checked the case alias. It now creates and removes the link with CreateSymbolicLinkW and RemoveDirectoryW, as SaveDataFileWriteFailure.cpp does.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01GtXavMkgCTCdmRpweviHcQ
An unterminated or empty directory name, "." or "..", or a name
containing "/", "\" or ":" now returns SAVE_DATA_ERROR_PARAMETER and
leaves the mount result unchanged. The SDK 12.000 reference lists that
code for an invalid parameter of sceSaveDataMount3, KytyPS5's
SaveDataMount3 returns it for an invalid name, and sceSaveDataDelete
and sceSaveDataConvert already return it here for the same check.
It threw before, which stopped Minecraft Preview (PPSA19639) on
entering a world.
The Mount3 trace now prints at most the 32 bytes of the directory name
and the 16 bytes of the mount point, since both can be unterminated
when the mount fails.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Animal Well (PPSA22520) checks for its save with a CREATE2 mount it never
unmounts, then saves with a second mount of the same directory. That
mount returns BUSY, and the title keeps using the first mount point for
sceSaveDataPrepare, its save file and sceSaveDataUmount2.
sceSaveDataMount3 cleared the result before its checks, so the save was
written to "/save" in the game folder instead of the save directory, and
the first mount was never released. The result is now written only when
the mount succeeds, as shadPS4 does.
sceSaveDataMount3 with SCE_SAVE_DATA_MOUNT_MODE_CREATE2 opens a save that
already exists, so its mount status is 0 rather than CREATED, as shadPS4's
savedata.cpp reports. A CREATE mount still reports CREATED.