Files
AnyPS5/core/libs/prx/libSceSaveData/Export.cpp
T
OverkillLabs 77ed5e6d0c feat(savedata): mount aliasing, wildcard search, memory slots, async events
Rework the local SaveData implementation into a general-purpose, title-agnostic
backend used by both the native library and the standard libSceSaveData path:

- Directory aliasing: long save directory names are hashed into short mount
  point aliases (persisted to disk) so they fit the SDK's fixed-size mount
  point buffer, with the aliases reversed on directory search.
- Wildcard directory-name search (%/_ patterns) with sorting and optional
  reverse order, matching the DirNameSearch contract.
- Save-data "memory" slots: small per-user, per-slot blobs the title can
  set up, read and write without mounting a directory, backed by files under
  the save root with a companion parameter blob, size and range validation
  on every access, and a dirty flag consulted on commit.
- Async-flavored operations (backup, commit, memory sync) that queue
  SaveDataEvent records consumed through GetEventResult and can be bound to
  kernel event flags, plus transaction-resource and cancel/abort bookkeeping.
- Atomic file writes (temp file + rename) so a mid-write crash never leaves a
  torn save on disk, and corrected/duplicated SDK error code constants.
- Local storage app-status get/set/delete and transferring-mount support for
  PS4-to-PS5 style save transfers.

The native library keeps its own lighter-weight mount/memory implementation
(short "/_sm/<slot>" mount point aliases via the path-alias table) while
gaining the same per-user memory-slot separation, atomic writes and bounds
checking; the two implementations were merged by hand where upstream had
independently touched the native Export.cpp in the same area (backup/event
queue, short mount points) to keep both sets of improvements.

None of this is game-specific: it is a general local save-data backend that
works for any title and tolerates any user id.
2026-09-28 12:48:09 +03:00

95 lines
4.3 KiB
C++

#include <atomic>
#include <cstdint>
#include <cstdio>
#include <cstddef>
#include "SceTypes.hpp"
#include "prx/libSceSaveData/SaveDataCore.hpp"
#include "prx/libc/include/General.hpp"
// Savedata imports bind by NID either to libSceSaveData.native or (via the relinker alias table,
// by name) to this library. Every entry point used to return SoftFailNp (0x800242e2), which is the
// NP-family "online service not available" code - wrong dialect for a purely local save call and
// fatal-looking for callers. All entry points now delegate to SaveDataCore.hpp: a local,
// file-backed implementation that needs no PSN account and tolerates any userId
// (0 / 65535 / 268435456), mirroring the native library.
namespace {
// Logs the first three calls of each entry point, then stays silent, keeping guest logs readable.
#define SAVEDATA_TRACE(...) \
do { \
static std::atomic<int> traceCount{0}; \
if (traceCount.fetch_add(1, std::memory_order_relaxed) < 3) { \
std::fprintf(stderr, "[SAVEDATA:core] " __VA_ARGS__); \
std::fputc('\n', stderr); \
std::fflush(stderr); \
} \
} while (0)
} // namespace
extern "C" {
int APS5_VABI sceSaveDataInitialize3(const void* init) {
const int rc = savedata::initialize_internal(init);
SAVEDATA_TRACE("sceSaveDataInitialize3 -> 0x%08x", static_cast<unsigned>(rc));
return rc;
}
int APS5_VABI sceSaveDataTerminate(void) {
const int rc = savedata::terminate_internal();
SAVEDATA_TRACE("sceSaveDataTerminate -> 0x%08x", static_cast<unsigned>(rc));
return rc;
}
int APS5_VABI sceSaveDataBackup(const SaveDataBackup* backup) {
const int rc = savedata::backup_internal(backup, nullptr);
SAVEDATA_TRACE("sceSaveDataBackup -> 0x%08x", static_cast<unsigned>(rc));
return rc;
}
int APS5_VABI sceSaveDataGetEventResult(const void* event_param, SaveDataEvent* event) {
const int rc = savedata::get_event_result_internal(event_param, event);
SAVEDATA_TRACE("sceSaveDataGetEventResult -> 0x%08x", static_cast<unsigned>(rc));
return rc;
}
int APS5_VABI sceSaveDataGetSaveDataMemory2(SaveDataMemoryGet2* get_param) {
if (get_param == nullptr) {
return savedata::SD_ERROR_PARAMETER;
}
const int rc = savedata::get_memory_internal(get_param);
SAVEDATA_TRACE("get2 user=%d slot=%u -> 0x%08x", static_cast<int>(get_param->user_id),
static_cast<unsigned>(get_param->slot_id), static_cast<unsigned>(rc));
return rc;
}
int APS5_VABI sceSaveDataSetSaveDataMemory2(const SaveDataMemorySet2* set_param) {
if (set_param == nullptr) {
return savedata::SD_ERROR_PARAMETER;
}
const int rc = savedata::set_memory_internal(set_param);
SAVEDATA_TRACE("set2 user=%d slot=%u num=%u -> 0x%08x",
static_cast<int>(set_param->user_id), static_cast<unsigned>(set_param->slot_id),
static_cast<unsigned>(set_param->data_num), static_cast<unsigned>(rc));
return rc;
}
int APS5_VABI sceSaveDataSetupSaveDataMemory2(const SaveDataMemorySetup2* setup_param,
SaveDataMemorySetupResult* result) {
const int rc = savedata::setup_memory_internal(setup_param, result, nullptr);
SAVEDATA_TRACE("setup2 -> 0x%08x existed=0x%zx", static_cast<unsigned>(rc),
result != nullptr ? result->existed_memory_size : static_cast<std::size_t>(0));
return rc;
}
int APS5_VABI sceSaveDataSyncSaveDataMemory(const void* sync_param) {
if (sync_param == nullptr) {
return savedata::SD_ERROR_PARAMETER;
}
const auto* sync = static_cast<const savedata::MemorySync*>(sync_param);
const int rc = savedata::sync_memory_internal(sync->user_id, sync->slot_id, sync->option, nullptr, true);
SAVEDATA_TRACE("sync user=%d slot=%u option=%u -> 0x%08x", static_cast<int>(sync->user_id),
static_cast<unsigned>(sync->slot_id), static_cast<unsigned>(sync->option),
static_cast<unsigned>(rc));
return rc;
}
int APS5_VABI sceSaveDataTransferringMount(const SaveDataTransferringMount* mount,
SaveDataMountResult* mount_result) {
const int rc = savedata::transferring_mount_internal(mount, mount_result);
SAVEDATA_TRACE("sceSaveDataTransferringMount -> 0x%08x", static_cast<unsigned>(rc));
return rc;
}
}