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 <noreply@anthropic.com>
This commit is contained in:
Muhammad Wasif Ali WasifandClaude Opus 5.5 committed 2026-10-14 14:00:34 +05:00
1 parent 2582ec1193
commit 0c06a28ac5
3 files changed
+33 -17

No files matched your search

+7 -1
View File
@@ -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;
+6 -16
View File
@@ -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<int>(user_id));
}
bool valid_memory_range(const MemoryData& data, std::size_t size) {
return data.buf != nullptr && data.offset >= 0 &&
static_cast<std::size_t>(data.offset) <= size &&
data.buf_size <= size - static_cast<std::size_t>(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<const MemoryData*>(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<const MemoryData*>(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<const MemoryData*>(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) {
@@ -2,8 +2,10 @@
#include <algorithm>
#include <array>
#include <chrono>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <filesystem>
#include <fstream>
#include <iterator>
@@ -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<char, 4> third{'p', 'q', 'r', 's'};
std::array<char, 4> fourth{'t', 'u', 'v', 'w'};
constexpr std::size_t GuestMemoryDataSize = 64;
alignas(SaveDataMemoryData) std::array<unsigned char, 2 * GuestMemoryDataSize> 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<const SaveDataMemoryData*>(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";