From 0c06a28ac52f60ea81624e9f5ab5b335fed80b9a Mon Sep 17 00:00:00 2001 From: Muhammad Wasif Ali Wasif <76695420+wachi35676@users.noreply.github.com> Date: Wed, 14 Oct 2026 01:18:41 +0500 Subject: [PATCH] fix(savedata): read every sceSaveDataSetSaveDataMemory2 data entry at its 64-byte stride SaveDataMemoryData was declared as 24 bytes, so libSceSaveData.native read the second and later entries of a guest data array from the reserved bytes of the first one, saw a zero size and skipped them while returning OK. The entry is 64 bytes (buffer, size, offset, 40 reserved), as SaveDataCore.hpp already asserted for its own copy of the struct and shadPS4 declares. With the shared type the right size, SaveDataCore.hpp drops its private MemoryData and the casts to it and uses SaveDataMemoryData. Its range check takes the unsigned offset; a negative guest offset is still rejected because it exceeds any memory size. Co-Authored-By: Claude Opus 5.5 --- core/libs/SceTypes.hpp | 8 ++++++- core/libs/prx/libSceSaveData/SaveDataCore.hpp | 22 +++++-------------- core/libs/tests/SaveDataMemoryMetadata.cpp | 20 +++++++++++++++++ 3 files changed, 33 insertions(+), 17 deletions(-) diff --git a/core/libs/SceTypes.hpp b/core/libs/SceTypes.hpp index 3f73232949..5d4088a540 100644 --- a/core/libs/SceTypes.hpp +++ b/core/libs/SceTypes.hpp @@ -1467,7 +1467,13 @@ struct SaveDataSearchInfo { std::uint8_t reserved[32]; }; static_assert(sizeof(SaveDataSearchInfo) == 48); -struct SaveDataMemoryData { void* buf; std::size_t buf_size; std::size_t offset; }; +struct SaveDataMemoryData { + void* buf; + std::size_t buf_size; + std::size_t offset; + std::uint8_t reserved[40]; +}; +static_assert(sizeof(SaveDataMemoryData) == 64); struct SaveDataMount3 { int user_id; diff --git a/core/libs/prx/libSceSaveData/SaveDataCore.hpp b/core/libs/prx/libSceSaveData/SaveDataCore.hpp index 200b607716..4705d8d62e 100644 --- a/core/libs/prx/libSceSaveData/SaveDataCore.hpp +++ b/core/libs/prx/libSceSaveData/SaveDataCore.hpp @@ -106,14 +106,6 @@ constexpr char SD_MEMORY_PREFIX[] = "sce_sdmemory"; constexpr std::size_t SD_MOUNT_PREFIX_LEN = sizeof(SD_ROOT_GUEST); constexpr std::size_t SD_MOUNT_POINT_CAPACITY = sizeof(SaveDataMountPoint) - SD_MOUNT_PREFIX_LEN - 1; -struct MemoryData { - void* buf; - std::size_t buf_size; - std::int64_t offset; - std::uint8_t reserved[40]; -}; -static_assert(sizeof(MemoryData) == 64, "SceSaveDataMemoryData is 64 bytes"); - struct MemorySync { std::int32_t user_id; std::uint32_t slot_id; @@ -873,10 +865,8 @@ std::filesystem::path memory_directory(std::int32_t user_id, std::uint32_t slot_ return save_root() / name / std::to_string(static_cast(user_id)); } -bool valid_memory_range(const MemoryData& data, std::size_t size) { - return data.buf != nullptr && data.offset >= 0 && - static_cast(data.offset) <= size && - data.buf_size <= size - static_cast(data.offset); +bool valid_memory_range(const SaveDataMemoryData& data, std::size_t size) { + return data.buf != nullptr && data.offset <= size && data.buf_size <= size - data.offset; } int setup_memory_internal(const SaveDataMemorySetup2* setup, SaveDataMemorySetupResult* result, @@ -959,14 +949,14 @@ int get_memory_internal(SaveDataMemoryGet2* get_param) { } const MemorySlot& slot = it->second; if (get_param->data != nullptr && - !valid_memory_range(*reinterpret_cast(get_param->data), slot.data.size())) { + !valid_memory_range(*get_param->data, slot.data.size())) { return SD_ERROR_PARAMETER; } if (get_param->param != nullptr && (slot.option & SD_MEMORY_SET_PARAM) == 0) { return SD_ERROR_PARAMETER; } if (get_param->data != nullptr) { - const MemoryData& data = *reinterpret_cast(get_param->data); + const SaveDataMemoryData& data = *get_param->data; std::memcpy(data.buf, slot.data.data() + data.offset, data.buf_size); } if (get_param->param != nullptr) { @@ -1001,7 +991,7 @@ int set_memory_internal(const SaveDataMemorySet2* set_param) { } const std::uint32_t count = set_param->data != nullptr && set_param->data_num == 0 ? 1u : set_param->data_num; - const MemoryData* descriptors = reinterpret_cast(set_param->data); + const SaveDataMemoryData* descriptors = set_param->data; for (std::uint32_t i = 0; i < count; i++) { if (!valid_memory_range(descriptors[i], it->second.data.size())) { return SD_ERROR_PARAMETER; @@ -1009,7 +999,7 @@ int set_memory_internal(const SaveDataMemorySet2* set_param) { } MemorySlot updated = it->second; for (std::uint32_t i = 0; i < count; i++) { - const MemoryData& data = descriptors[i]; + const SaveDataMemoryData& data = descriptors[i]; std::memcpy(updated.data.data() + data.offset, data.buf, data.buf_size); } if (set_param->param != nullptr) { diff --git a/core/libs/tests/SaveDataMemoryMetadata.cpp b/core/libs/tests/SaveDataMemoryMetadata.cpp index 8f40cccf5c..1c6f0f10e6 100644 --- a/core/libs/tests/SaveDataMemoryMetadata.cpp +++ b/core/libs/tests/SaveDataMemoryMetadata.cpp @@ -2,8 +2,10 @@ #include #include #include +#include #include #include +#include #include #include #include @@ -116,6 +118,24 @@ int main() { Require(sceSaveDataSetSaveDataMemory2(&set) == 0); std::copy(first.begin(), first.end(), memoryExpected.begin() + 4); Require(Read(memoryPath) == memoryExpected); + std::array third{'p', 'q', 'r', 's'}; + std::array fourth{'t', 'u', 'v', 'w'}; + constexpr std::size_t GuestMemoryDataSize = 64; + alignas(SaveDataMemoryData) std::array guestData{}; + const auto putGuestData = [&guestData](std::size_t index, const char* buffer, std::uint64_t size, std::uint64_t offset) { + unsigned char* entry = guestData.data() + index * GuestMemoryDataSize; + std::memcpy(entry, &buffer, sizeof(buffer)); + std::memcpy(entry + 8, &size, sizeof(size)); + std::memcpy(entry + 16, &offset, sizeof(offset)); + }; + putGuestData(0, third.data(), third.size(), 0); + putGuestData(1, fourth.data(), fourth.size(), 16); + set.data = reinterpret_cast(guestData.data()); + set.data_num = 2; + Require(sceSaveDataSetSaveDataMemory2(&set) == 0); + std::copy(third.begin(), third.end(), memoryExpected.begin()); + std::copy(fourth.begin(), fourth.end(), memoryExpected.begin() + 16); + Require(Read(memoryPath) == memoryExpected); const auto setupParamPath = std::filesystem::path("_sd_mem/u42/slot0.param"); const auto setupBinPath = std::filesystem::path("_sd_mem/u42/slot0.bin"); const auto setupParamTemp = setupParamPath.string() + ".tmp";