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.
This commit is contained in:
OverkillLabs 2026-09-28 12:48:09 +03:00
parent cdc95d9a70
commit 77ed5e6d0c
5 changed files with 1658 additions and 120 deletions

View File

@ -24,24 +24,11 @@ static std::string save_root() {
return std::string(SAVE_DIR);
}
// Events the title polls with sceSaveDataGetEventResult (a backup completes at once here), and the
// "save data memory" blobs (small per-slot memories the title reads and writes without mounting),
// persisted as files under the save root so they survive restarts.
// Events the title polls with sceSaveDataGetEventResult; a backup completes at once here.
static std::mutex g_state_mutex;
static std::deque<SaveDataEvent> g_events;
static std::map<std::uint32_t, std::vector<std::uint8_t>> g_memories;
constexpr std::uint32_t SAVE_DATA_EVENT_TYPE_BACKUP = 2;
static std::string memory_path(std::uint32_t slot) {
return save_root() + "/_memory_" + std::to_string(slot) + ".bin";
}
static void store_memory(std::uint32_t slot, const std::vector<std::uint8_t>& memory) {
std::filesystem::create_directories(save_root());
std::ofstream file(memory_path(slot), std::ios::binary | std::ios::trunc);
file.write(reinterpret_cast<const char*>(memory.data()), static_cast<std::streamsize>(memory.size()));
}
static bool dir_name_match(const char* str, const char* pattern) {
if (pattern == nullptr || pattern[0] == '\0') {
return true;
@ -72,6 +59,78 @@ static bool dir_name_match(const char* str, const char* pattern) {
return *str == '\0' && *pattern == '\0';
}
namespace {
// Save-data memory: one blob per user and slot, kept next to the save directory (_sd_mem/u<user>/slot<n>.bin),
// with a small .param sidecar holding the last SaveDataParam the title wrote. Keying by user as well as slot
// keeps two users' in-memory saves from colliding on the same console.
constexpr char MEM_DIR[] = "_sd_mem";
constexpr std::size_t MEM_MAX_SIZE = 0x1000000; // 16 MiB
std::mutex g_mem_mutex;
std::string mem_path(std::int32_t user_id, std::uint32_t slot, const char* ext) {
char buf[96];
std::snprintf(buf, sizeof(buf), "%s/u%d/slot%u.%s", MEM_DIR, static_cast<int>(user_id), static_cast<unsigned>(slot), ext);
return buf;
}
bool file_size_of(const std::string& path, std::size_t* out) {
std::error_code ec;
if (!std::filesystem::is_regular_file(path, ec)) {
return false;
}
const auto sz = std::filesystem::file_size(path, ec);
if (ec) {
return false;
}
*out = static_cast<std::size_t>(sz);
return true;
}
// Atomic-ish write: write to a temp file then rename over the target, so a kill mid-write never
// leaves a torn save behind.
bool write_file_replace(const std::string& path, const std::vector<char>& data) {
const std::string tmp = path + ".tmp";
{
std::ofstream f(tmp, std::ios::binary | std::ios::trunc);
if (!f) {
return false;
}
if (!data.empty()) {
f.write(data.data(), static_cast<std::streamsize>(data.size()));
}
f.flush();
if (!f) {
return false;
}
}
std::error_code ec;
std::filesystem::rename(tmp, path, ec);
if (ec) {
std::filesystem::remove(path, ec);
ec.clear();
std::filesystem::rename(tmp, path, ec);
}
return !ec;
}
bool read_file_all(const std::string& path, std::vector<char>& out) {
std::ifstream f(path, std::ios::binary);
if (!f) {
return false;
}
f.seekg(0, std::ios::end);
const auto n = static_cast<std::size_t>(f.tellg());
f.seekg(0);
out.resize(n);
if (n != 0) {
f.read(out.data(), static_cast<std::streamsize>(n));
}
return static_cast<bool>(f);
}
} // namespace
extern "C" {
int APS5_VABI sceSaveDataBackup(const SaveDataBackup* backup) {
@ -196,29 +255,47 @@ int APS5_VABI sceSaveDataGetParam(const SaveDataMountPoint* mount_point, uint32_
int APS5_VABI sceSaveDataGetSaveDataMemory2(SaveDataMemoryGet2* get_param) {
if (get_param == nullptr) {
throw std::runtime_error("sceSaveDataGetSaveDataMemory2: null argument");
return SAVE_DATA_ERROR_PARAMETER;
}
std::lock_guard lock(g_state_mutex);
const auto it = g_memories.find(get_param->slot_id);
if (it == g_memories.end()) {
return SAVE_DATA_ERROR_NOT_FOUND;
if (!g_initialized) {
return SAVE_DATA_ERROR_NOT_INITIALIZED;
}
if (get_param->data != nullptr && get_param->data->buf != nullptr) {
const auto& memory = it->second;
if (get_param->data->offset > memory.size()) {
std::lock_guard<std::mutex> lk(g_mem_mutex);
const std::string path = mem_path(get_param->user_id, get_param->slot_id, "bin");
std::size_t size = 0;
if (!file_size_of(path, &size)) {
return SAVE_DATA_ERROR_MEMORY_NOT_READY;
}
const SaveDataMemoryData* d = get_param->data;
if (d != nullptr && d->buf_size != 0) {
if (d->buf == nullptr || d->offset > size || d->buf_size > size - d->offset) {
return SAVE_DATA_ERROR_PARAMETER;
}
const auto bytes = std::min(get_param->data->buf_size, memory.size() - get_param->data->offset);
std::memcpy(get_param->data->buf, memory.data() + get_param->data->offset, bytes);
std::ifstream f(path, std::ios::binary);
if (!f) {
return SAVE_DATA_ERROR_INTERNAL;
}
f.seekg(static_cast<std::streamoff>(d->offset));
f.read(static_cast<char*>(d->buf), static_cast<std::streamsize>(d->buf_size));
if (!f) {
return SAVE_DATA_ERROR_INTERNAL;
}
}
if (get_param->param != nullptr) {
std::memset(get_param->param, 0, sizeof(SaveDataParam));
std::vector<char> pd;
if (read_file_all(mem_path(get_param->user_id, get_param->slot_id, "param"), pd)) {
std::memcpy(get_param->param, pd.data(), std::min(pd.size(), sizeof(SaveDataParam)));
}
}
if (get_param->icon != nullptr) {
get_param->icon->data_size = 0;
}
if (get_param->param != nullptr) std::memset(get_param->param, 0, sizeof(SaveDataParam));
if (get_param->icon != nullptr) get_param->icon->data_size = 0;
return SAVE_DATA_OK;
}
int APS5_VABI sceSaveDataInitialize3(const void* init) {
(void)init;
// NotImplemented_nid_no_patch(__func__);
if (g_initialized) {
return SAVE_DATA_ERROR_ALREADY_INITIALIZED;
}
@ -332,59 +409,108 @@ int APS5_VABI sceSaveDataSetParam(const SaveDataMountPoint* mount_point, uint32_
int APS5_VABI sceSaveDataSetSaveDataMemory2(const SaveDataMemorySet2* set_param) {
if (set_param == nullptr) {
throw std::runtime_error("sceSaveDataSetSaveDataMemory2: null argument");
return SAVE_DATA_ERROR_PARAMETER;
}
std::lock_guard lock(g_state_mutex);
const auto it = g_memories.find(set_param->slot_id);
if (it == g_memories.end()) {
return SAVE_DATA_ERROR_NOT_FOUND;
if (!g_initialized) {
return SAVE_DATA_ERROR_NOT_INITIALIZED;
}
auto& memory = it->second;
if (set_param->data != nullptr) {
const auto count = set_param->data_num == 0 ? 1u : set_param->data_num;
for (std::uint32_t i = 0; i < count; ++i) {
const auto& data = set_param->data[i];
if (data.buf == nullptr) continue;
if (data.offset > memory.size() || data.buf_size > memory.size() - data.offset) {
return SAVE_DATA_ERROR_PARAMETER;
}
std::memcpy(memory.data() + data.offset, data.buf, data.buf_size);
std::lock_guard<std::mutex> lk(g_mem_mutex);
const std::string path = mem_path(set_param->user_id, set_param->slot_id, "bin");
std::size_t size = 0;
if (!file_size_of(path, &size)) {
return SAVE_DATA_ERROR_MEMORY_NOT_READY;
}
// Validate every range first so a bad entry never leaves a partial write.
const std::uint32_t n = set_param->data != nullptr ? (set_param->data_num != 0 ? set_param->data_num : 1u) : 0u;
for (std::uint32_t i = 0; i < n; i++) {
const SaveDataMemoryData& d = set_param->data[i];
if (d.buf_size == 0) {
continue;
}
if (d.buf == nullptr || d.offset > size || d.buf_size > size - d.offset) {
return SAVE_DATA_ERROR_PARAMETER;
}
}
store_memory(set_param->slot_id, memory);
if (n != 0) {
std::fstream f(path, std::ios::binary | std::ios::in | std::ios::out);
if (!f) {
return SAVE_DATA_ERROR_INTERNAL;
}
for (std::uint32_t i = 0; i < n; i++) {
const SaveDataMemoryData& d = set_param->data[i];
if (d.buf_size == 0) {
continue;
}
f.seekp(static_cast<std::streamoff>(d.offset));
f.write(static_cast<const char*>(d.buf), static_cast<std::streamsize>(d.buf_size));
}
f.flush();
if (!f) {
return SAVE_DATA_ERROR_INTERNAL;
}
}
if (set_param->param != nullptr) {
std::vector<char> pd(sizeof(SaveDataParam));
std::memcpy(pd.data(), set_param->param, sizeof(SaveDataParam));
write_file_replace(mem_path(set_param->user_id, set_param->slot_id, "param"), pd);
}
return SAVE_DATA_OK;
}
int APS5_VABI sceSaveDataSetupSaveDataMemory2(const SaveDataMemorySetup2* setup_param, SaveDataMemorySetupResult* result) {
if (setup_param == nullptr) {
throw std::runtime_error("sceSaveDataSetupSaveDataMemory2: null argument");
return SAVE_DATA_ERROR_PARAMETER;
}
std::lock_guard lock(g_state_mutex);
auto& memory = g_memories[setup_param->slot_id];
if (!g_initialized) {
return SAVE_DATA_ERROR_NOT_INITIALIZED;
}
if (setup_param->memory_size == 0 || setup_param->memory_size > MEM_MAX_SIZE) {
return SAVE_DATA_ERROR_PARAMETER;
}
std::lock_guard<std::mutex> lk(g_mem_mutex);
const std::string path = mem_path(setup_param->user_id, setup_param->slot_id, "bin");
std::size_t existed = 0;
if (memory.empty()) {
std::ifstream file(memory_path(setup_param->slot_id), std::ios::binary);
if (file) {
memory.assign(std::istreambuf_iterator<char>(file), std::istreambuf_iterator<char>());
existed = memory.size();
const bool have = file_size_of(path, &existed);
if (!have) {
existed = 0;
}
// First run: create a zero-filled blob and report existed size 0 so the title treats it as a new save.
if (!have || existed < setup_param->memory_size) {
std::error_code ec;
std::filesystem::create_directories(std::filesystem::path(path).parent_path(), ec);
std::vector<char> data;
if (have) {
read_file_all(path, data);
}
data.resize(setup_param->memory_size, 0);
if (!write_file_replace(path, data)) {
return SAVE_DATA_ERROR_INTERNAL;
}
if (setup_param->init_param != nullptr && (setup_param->option & 1u) != 0) {
std::vector<char> pd(sizeof(SaveDataParam));
std::memcpy(pd.data(), setup_param->init_param, sizeof(SaveDataParam));
write_file_replace(mem_path(setup_param->user_id, setup_param->slot_id, "param"), pd);
}
} else {
existed = memory.size();
}
if (memory.size() < setup_param->memory_size) {
memory.resize(setup_param->memory_size, 0);
}
if (existed == 0) store_memory(setup_param->slot_id, memory);
if (result != nullptr) {
std::memset(result, 0, sizeof(*result));
result->existed_memory_size = existed;
result->existed_memory_size = have ? existed : 0;
}
return SAVE_DATA_OK;
}
int APS5_VABI sceSaveDataSyncSaveDataMemory(const void* sync_param) {
(void)sync_param;
// Every Set already reached the file; nothing is buffered.
if (sync_param == nullptr) {
return SAVE_DATA_ERROR_PARAMETER;
}
// Every Set already writes straight through to disk; sync only has to confirm the memory exists.
const std::int32_t user_id = *static_cast<const std::int32_t*>(sync_param);
const std::uint32_t slot_id = reinterpret_cast<const std::uint32_t*>(sync_param)[1];
std::lock_guard<std::mutex> lk(g_mem_mutex);
std::size_t size = 0;
if (!file_size_of(mem_path(user_id, slot_id, "bin"), &size)) {
return SAVE_DATA_ERROR_MEMORY_NOT_READY;
}
return SAVE_DATA_OK;
}
@ -401,9 +527,11 @@ int APS5_VABI sceSaveDataTerminate(void) {
int APS5_VABI sceSaveDataTransferringMount(const SaveDataTransferringMount* mount, SaveDataMountResult* mount_result) {
(void)mount;
(void)mount_result;
NotImplemented_nid_no_patch(__func__);
return 0;
if (mount_result != nullptr) {
std::memset(mount_result, 0, sizeof(*mount_result));
}
// No PS4-to-PS5 transfer data exists on this console.
return SAVE_DATA_ERROR_NOT_FOUND;
}
int APS5_VABI sceSaveDataUmount2(uint32_t mode, const SaveDataMountPoint* mount_point) {

View File

@ -6,15 +6,18 @@
#include <string>
constexpr int SAVE_DATA_OK = 0;
// Values follow the SDK error table (0x809F0000 + code): 2 OUT_OF_MEMORY, 3 BUSY, 4 NOT_MOUNTED, 7 EXISTS, 8 NOT_FOUND, 0xB INTERNAL, 0xC MOUNT_FULL.
constexpr int SAVE_DATA_ERROR_PARAMETER = -2137063424;
constexpr int SAVE_DATA_ERROR_NOT_INITIALIZED = -2137063423;
constexpr int SAVE_DATA_ERROR_ALREADY_INITIALIZED = -2137063422;
constexpr int SAVE_DATA_ERROR_OUT_OF_MEMORY = -2137063421;
constexpr int SAVE_DATA_ERROR_BUSY = -2137063420;
constexpr int SAVE_DATA_ERROR_NOT_MOUNTED = -2137063419;
constexpr int SAVE_DATA_ERROR_MOUNT_FULL = -2137063418;
constexpr int SAVE_DATA_ERROR_EXISTS = -2137063414;
constexpr int SAVE_DATA_ERROR_NOT_FOUND = -2137063413;
constexpr int SAVE_DATA_ERROR_ALREADY_INITIALIZED = -2137063419; // 0x809F0005
constexpr int SAVE_DATA_ERROR_OUT_OF_MEMORY = -2137063422;
constexpr int SAVE_DATA_ERROR_BUSY = -2137063421;
constexpr int SAVE_DATA_ERROR_NOT_MOUNTED = -2137063420;
constexpr int SAVE_DATA_ERROR_MOUNT_FULL = -2137063412;
constexpr int SAVE_DATA_ERROR_EXISTS = -2137063417;
constexpr int SAVE_DATA_ERROR_NOT_FOUND = -2137063416;
constexpr int SAVE_DATA_ERROR_INTERNAL = -2137063413; // 0x809F000B
constexpr int SAVE_DATA_ERROR_MEMORY_NOT_READY = -2137063406; // 0x809F0012
constexpr std::uint32_t SAVE_DATA_MOUNT_MODE_RDONLY = 1;
constexpr std::uint32_t SAVE_DATA_MOUNT_MODE_RDWR = 2;

View File

@ -1,53 +1,94 @@
#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) {
(void)backup;
NotImplemented_nid_no_patch(__func__);
return 0;
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) {
(void)event_param;
(void)event;
NotImplemented_nid_no_patch(__func__);
return 0;
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) {
(void)get_param;
NotImplemented_nid_no_patch(__func__);
return 0;
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) {
(void)set_param;
NotImplemented_nid_no_patch(__func__);
return 0;
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) {
(void)setup_param;
(void)result;
NotImplemented_nid_no_patch(__func__);
return 0;
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) {
(void)sync_param;
NotImplemented_nid_no_patch(__func__);
return 0;
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) {
(void)mount;
(void)mount_result;
NotImplemented_nid_no_patch(__func__);
return 0;
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;
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,55 +1,67 @@
#include <cstdint>
#include <cstdio>
#include <cstddef>
#include <cstring>
#include "SceTypes.hpp"
#include "prx/libSceSaveDataDialog.native/SaveDataDialog.hpp"
#include "prx/libc/include/General.hpp"
namespace {
// Throwing from these reaches std::terminate and aborts the game process.
int SoftFailNp(const char* func) {
std::fprintf(stderr, "[SOFT-NP] %s -> 0 (unimplemented SCE API - no-op)\n", func);
std::fflush(stderr);
return 0;
}
} // namespace
extern "C" {
int APS5_VABI sceSaveDataDialogClose(const void* close_param) {
(void)close_param;
NotImplemented_nid_no_patch(__func__);
return 0;
return SoftFailNp(__func__);
}
int APS5_VABI sceSaveDataDialogGetResult(void* result) {
(void)result;
NotImplemented_nid_no_patch(__func__);
return 0;
// Never return 0 with an untouched out-struct: the game would assert on the garbage later.
if (result == nullptr) {
return SAVE_DATA_DIALOG_ERROR_ARG_NULL;
}
auto* r = static_cast<SaveDataDialogResult*>(result);
std::memset(r, 0, sizeof(*r));
r->result = SAVE_DATA_DIALOG_RESULT_OK;
r->button_id = SAVE_DATA_DIALOG_BUTTON_ID_OK;
return SoftFailNp(__func__);
}
int APS5_VABI sceSaveDataDialogInitialize(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
return SoftFailNp(__func__);
}
int APS5_VABI sceSaveDataDialogIsReadyToDisplay(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
return SoftFailNp(__func__);
}
int APS5_VABI sceSaveDataDialogOpen(const void* param) {
(void)param;
NotImplemented_nid_no_patch(__func__);
return 0;
return SoftFailNp(__func__);
}
int APS5_VABI sceSaveDataDialogProgressBarInc(int target, uint32_t delta) {
(void)target;
(void)delta;
NotImplemented_nid_no_patch(__func__);
return 0;
return SoftFailNp(__func__);
}
int APS5_VABI sceSaveDataDialogProgressBarSetValue(int target, uint32_t rate) {
(void)target;
(void)rate;
NotImplemented_nid_no_patch(__func__);
return 0;
return SoftFailNp(__func__);
}
int APS5_VABI sceSaveDataDialogTerminate(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
return SoftFailNp(__func__);
}
}