Merge pull request #4876 from EagleBearerYS/fix/savedata-failed-mount-keeps-result

fix(savedata): leave the mount result untouched when sceSaveDataMount3 fails
This commit is contained in:
Dean 2026-10-14 14:32:59 +13:00 committed by GitHub
commit 8a437941bd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 25 additions and 1 deletions

View File

@ -464,7 +464,6 @@ static int mount3(const SaveDataMount3* mount, SaveDataMountResult* mount_result
if (mount == nullptr || mount_result == nullptr || mount->dir_name == nullptr) {
throw std::runtime_error("sceSaveDataMount3: null argument");
}
std::memset(mount_result, 0, sizeof(*mount_result));
const bool create = (mount->mount_mode & SAVE_DATA_MOUNT_MODE_CREATE) != 0;
const bool create2 = (mount->mount_mode & SAVE_DATA_MOUNT_MODE_CREATE2) != 0;
const bool rdonly = (mount->mount_mode & SAVE_DATA_MOUNT_MODE_RDONLY) != 0;
@ -506,6 +505,7 @@ static int mount3(const SaveDataMount3* mount, SaveDataMountResult* mount_result
// path resolver maps it to the save directory, whose name may be far longer.
const std::string mountPoint = "/savedata" + std::to_string(slot);
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].mount_point = mountPoint;
g_slots[slot].real_path = real_path;

View File

@ -18,6 +18,8 @@ namespace {
constexpr std::uint32_t MountReadWrite = 2;
constexpr std::uint32_t MountCreate = 4;
constexpr std::uint32_t MountCreate2 = 32;
constexpr int SaveDataErrorBusy = -2137063421;
constexpr int SaveDataErrorNotFound = -2137063416;
int failures = 0;
@ -41,6 +43,26 @@ void MountAndCheck(const char* name, std::uint32_t mode, std::uint32_t expectedS
Check(sceSaveDataUmount2(0, &result.mount_point) == 0, what + ": unmounts");
}
void CheckFailedMountKeepsResult() {
SceSaveDataDirName held{};
std::memcpy(held.data, "held", sizeof("held"));
SaveDataMount3 mount{};
mount.dir_name = &held;
mount.mount_mode = MountReadWrite | MountCreate2;
SaveDataMountResult result{};
Check(sceSaveDataMount3(&mount, &result) == 0, "first mount of a save");
const SaveDataMountResult mounted = result;
Check(sceSaveDataMount3(&mount, &result) == SaveDataErrorBusy, "second mount of a mounted save is BUSY");
Check(std::memcmp(&result, &mounted, sizeof(result)) == 0, "a BUSY mount leaves the result as it was");
SceSaveDataDirName absent{};
std::memcpy(absent.data, "absent", sizeof("absent"));
mount.dir_name = &absent;
mount.mount_mode = MountReadWrite;
Check(sceSaveDataMount3(&mount, &result) == SaveDataErrorNotFound, "open of a missing save is NOT_FOUND");
Check(std::memcmp(&result, &mounted, sizeof(result)) == 0, "a NOT_FOUND mount leaves the result as it was");
Check(sceSaveDataUmount2(0, &result.mount_point) == 0, "the kept mount point unmounts the save");
}
}
int main() {
@ -53,6 +75,7 @@ int main() {
MountAndCheck("fresh", MountReadWrite | MountCreate2, 0u, "CREATE2 of an existing save");
MountAndCheck("made", MountReadWrite | MountCreate, 1u, "CREATE of a missing save");
MountAndCheck("made", MountReadWrite, 0u, "open of an existing save");
CheckFailedMountKeepsResult();
std::filesystem::current_path(previous);
std::error_code error;
std::filesystem::remove_all(root, error);

View File

@ -13,4 +13,5 @@
### Functional
- [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.
- [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`