From c4cfb8e23d92d8fcdfb721d87093999288a0db48 Mon Sep 17 00:00:00 2001 From: Sanjaysrinivas Date: Wed, 7 Oct 2026 14:43:23 +0200 Subject: [PATCH] fix(savedata): preserve memory on write failure --- .../libs/prx/libSceSaveData.native/Export.cpp | 10 ++- core/libs/tests/SaveDataMemoryMetadata.cpp | 66 ++++++++++++++++++- 2 files changed, 67 insertions(+), 9 deletions(-) diff --git a/core/libs/prx/libSceSaveData.native/Export.cpp b/core/libs/prx/libSceSaveData.native/Export.cpp index 785ec69ca..709e723fd 100644 --- a/core/libs/prx/libSceSaveData.native/Export.cpp +++ b/core/libs/prx/libSceSaveData.native/Export.cpp @@ -560,8 +560,8 @@ static int setSaveDataMemory2(const SaveDataMemorySet2* set_param) { } } if (n != 0) { - std::fstream f(path, std::ios::binary | std::ios::in | std::ios::out); - if (!f) { + std::vector memory; + if (!read_file_all(path, memory) || memory.size() != size) { return SAVE_DATA_ERROR_INTERNAL; } for (std::uint32_t i = 0; i < n; i++) { @@ -569,11 +569,9 @@ static int setSaveDataMemory2(const SaveDataMemorySet2* set_param) { if (d.buf_size == 0) { continue; } - f.seekp(static_cast(d.offset)); - f.write(static_cast(d.buf), static_cast(d.buf_size)); + std::memcpy(memory.data() + d.offset, d.buf, d.buf_size); } - f.flush(); - if (!f) { + if (!write_file_replace(path, memory)) { return SAVE_DATA_ERROR_INTERNAL; } } diff --git a/core/libs/tests/SaveDataMemoryMetadata.cpp b/core/libs/tests/SaveDataMemoryMetadata.cpp index 5c75de021..70724bd18 100644 --- a/core/libs/tests/SaveDataMemoryMetadata.cpp +++ b/core/libs/tests/SaveDataMemoryMetadata.cpp @@ -1,12 +1,19 @@ #include "SceTypes.hpp" +#include +#include #include #include #include #include #include #include +#include #include #include +#ifndef _WIN32 +#include +#include +#endif extern "C" { int APS5_VABI sceSaveDataInitialize3(const void*); @@ -14,9 +21,13 @@ int APS5_VABI sceSaveDataTerminate(); int APS5_VABI sceSaveDataSetSaveDataMemory2(const SaveDataMemorySet2*); } -static void Require(bool value) { - if (!value) std::abort(); +static void Check(bool value, int line) { + if (!value) { + std::fprintf(stderr, "Save-data memory check failed at line %d\n", line); + std::abort(); + } } +#define Require(value) Check((value), __LINE__) static std::vector Read(const std::filesystem::path& path) { std::ifstream file(path, std::ios::binary); @@ -31,8 +42,14 @@ int main() { Require(std::filesystem::create_directory(root)); std::filesystem::current_path(root); const auto path = std::filesystem::path("_sd_mem/u7531/slot0.param"); + const auto memoryPath = std::filesystem::path("_sd_mem/u7531/slot0.bin"); std::filesystem::create_directories(path.parent_path()); - { std::ofstream file("_sd_mem/u7531/slot0.bin"); file << "save"; Require(static_cast(file)); } + const std::vector memoryOriginal(32, 's'); + { + std::ofstream file(memoryPath, std::ios::binary); + file.write(memoryOriginal.data(), static_cast(memoryOriginal.size())); + Require(static_cast(file)); + } Require(sceSaveDataInitialize3(nullptr) == 0); Require(sceSaveDataInitialize3(nullptr) == 0); Require(sceSaveDataTerminate() == 0); @@ -55,6 +72,49 @@ int main() { Require(Read(path) == std::vector(bytes, bytes + sizeof(param))); set.param = nullptr; Require(sceSaveDataSetSaveDataMemory2(&set) == 0); + std::array first{'a', '\0', 'b', 'c', 'd', 'e', 'f', 'g'}; + std::array second{'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o'}; + std::array data{{ + {first.data(), first.size(), 4}, + {second.data(), second.size(), 24}, + {nullptr, 0, std::numeric_limits::max()} + }}; + set.data = data.data(); + set.data_num = static_cast(data.size()); + data[1].offset = memoryOriginal.size(); + Require(sceSaveDataSetSaveDataMemory2(&set) == static_cast(0x809F0000u)); + Require(Read(memoryPath) == memoryOriginal); + data[1].offset = 24; + const auto memoryTemporary = memoryPath.string() + ".tmp"; + Require(std::filesystem::create_directory(memoryTemporary)); + Require(sceSaveDataSetSaveDataMemory2(&set) == static_cast(0x809F000Bu)); + Require(Read(memoryPath) == memoryOriginal); + Require(!std::filesystem::exists(memoryTemporary)); +#ifndef _WIN32 + rlimit previousLimit{}; + Require(getrlimit(RLIMIT_FSIZE, &previousLimit) == 0); + auto writeLimit = previousLimit; + writeLimit.rlim_cur = 16; + const auto previousHandler = std::signal(SIGXFSZ, SIG_IGN); + Require(previousHandler != SIG_ERR); + Require(setrlimit(RLIMIT_FSIZE, &writeLimit) == 0); + const int writeStatus = sceSaveDataSetSaveDataMemory2(&set); + Require(setrlimit(RLIMIT_FSIZE, &previousLimit) == 0); + Require(std::signal(SIGXFSZ, previousHandler) != SIG_ERR); + Require(writeStatus == static_cast(0x809F000Bu)); + Require(Read(memoryPath) == memoryOriginal); + Require(!std::filesystem::exists(memoryTemporary)); +#endif + Require(sceSaveDataSetSaveDataMemory2(&set) == 0); + auto memoryExpected = memoryOriginal; + std::copy(first.begin(), first.end(), memoryExpected.begin() + 4); + std::copy(second.begin(), second.end(), memoryExpected.begin() + 24); + Require(Read(memoryPath) == memoryExpected); + first.fill('z'); + set.data_num = 0; + Require(sceSaveDataSetSaveDataMemory2(&set) == 0); + std::copy(first.begin(), first.end(), memoryExpected.begin() + 4); + Require(Read(memoryPath) == memoryExpected); Require(sceSaveDataTerminate() == 0); std::filesystem::current_path(previous); std::filesystem::remove_all(root);