From 4ccf88829131f3f69f3eb50adbf78e3fa44c26cf Mon Sep 17 00:00:00 2001 From: Yunus Shaikh <75296019+EagleBearerYS@users.noreply.github.com> Date: Mon, 12 Oct 2026 17:47:46 -0700 Subject: [PATCH 1/2] fix(savedata): leave the mount result untouched when sceSaveDataMount3 fails 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. --- .../libs/prx/libSceSaveData.native/Export.cpp | 2 +- core/libs/tests/GuestSaveDataMountStatus.cpp | 23 +++++++++++++++++++ docs/dev/debt/libSceSaveData.native.md | 1 + 3 files changed, 25 insertions(+), 1 deletion(-) diff --git a/core/libs/prx/libSceSaveData.native/Export.cpp b/core/libs/prx/libSceSaveData.native/Export.cpp index 58e898833..719a79dbf 100644 --- a/core/libs/prx/libSceSaveData.native/Export.cpp +++ b/core/libs/prx/libSceSaveData.native/Export.cpp @@ -463,7 +463,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; @@ -498,6 +497,7 @@ static int mount3(const SaveDataMount3* mount, SaveDataMountResult* mount_result if (slot == -1) { return SAVE_DATA_ERROR_MOUNT_FULL; } + std::memset(mount_result, 0, sizeof(*mount_result)); if (create || create2) { std::filesystem::create_directories(real_path); } diff --git a/core/libs/tests/GuestSaveDataMountStatus.cpp b/core/libs/tests/GuestSaveDataMountStatus.cpp index c51a1cb5c..9249ab383 100644 --- a/core/libs/tests/GuestSaveDataMountStatus.cpp +++ b/core/libs/tests/GuestSaveDataMountStatus.cpp @@ -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); diff --git a/docs/dev/debt/libSceSaveData.native.md b/docs/dev/debt/libSceSaveData.native.md index dfec69c2a..6f990d175 100644 --- a/docs/dev/debt/libSceSaveData.native.md +++ b/docs/dev/debt/libSceSaveData.native.md @@ -12,4 +12,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` From 6a46563a9357c3024dc59b79612db6692112e032 Mon Sep 17 00:00:00 2001 From: Yunus Shaikh <75296019+EagleBearerYS@users.noreply.github.com> Date: Tue, 13 Oct 2026 07:09:13 -0700 Subject: [PATCH 2/2] refactor(savedata): clear the mount result once the mount point is made #5173 changes the create condition right after the last error check, and #3950 removes the comment after the create block, so clearing the result in either place made the pull requests conflict. The result is still cleared only after every error return and before anything is written to it. --- core/libs/prx/libSceSaveData.native/Export.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/libs/prx/libSceSaveData.native/Export.cpp b/core/libs/prx/libSceSaveData.native/Export.cpp index 719a79dbf..8c455e7cb 100644 --- a/core/libs/prx/libSceSaveData.native/Export.cpp +++ b/core/libs/prx/libSceSaveData.native/Export.cpp @@ -497,7 +497,6 @@ static int mount3(const SaveDataMount3* mount, SaveDataMountResult* mount_result if (slot == -1) { return SAVE_DATA_ERROR_MOUNT_FULL; } - std::memset(mount_result, 0, sizeof(*mount_result)); if (create || create2) { std::filesystem::create_directories(real_path); } @@ -505,6 +504,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;