Strip legacy save/settings systems for a unified savemanager rewrite; split uielement list out of uielement.c
Removes the per-platform save/savestream/autosave implementations, the settings UI frames, and the autosave overlay, replacing them with a savemanager.c/h + savefile.h/savedevice.h scaffold to build the new save system on top of. Also separates the concrete UI_ELEMENTS registration into uielementlist.h/.c so uielement.c only holds the generic per-element lifecycle logic. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -21,6 +21,5 @@ add_subdirectory(log)
|
||||
if(DUSK_NETWORK)
|
||||
add_subdirectory(network)
|
||||
endif()
|
||||
add_subdirectory(save)
|
||||
add_subdirectory(system)
|
||||
add_subdirectory(time)
|
||||
@@ -6,7 +6,6 @@
|
||||
*/
|
||||
|
||||
#include "input/input.h"
|
||||
#include "save/save.h"
|
||||
|
||||
// #define INPUT_PSP_GAMEPAD_BUTTON_ACCEPT INPUT_SDL2_GAMEPAD_BUTTON_CUSTOM
|
||||
// #define INPUT_PSP_GAMEPAD_BUTTON_CANCEL INPUT_SDL2_GAMEPAD_BUTTON_CUSTOM
|
||||
@@ -95,5 +94,5 @@ errorret_t inputInitPSP(void) {
|
||||
}
|
||||
|
||||
float_t inputGetDeadzoneSDL2(const inputbutton_t button) {
|
||||
return saveGetMeta()->deadzone;
|
||||
return 0.25f;
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
# Copyright (c) 2026 Dominic Masters
|
||||
#
|
||||
# This software is released under the MIT License.
|
||||
# https://opensource.org/licenses/MIT
|
||||
|
||||
# Sources
|
||||
target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
||||
PUBLIC
|
||||
savepsp.c
|
||||
savestreampsp.c
|
||||
)
|
||||
|
||||
# PSP only needs one Dusk-side save slot - a future main-menu save picker
|
||||
# will let players manage multiple named saves through the OS's own
|
||||
# sceUtilitySavedata browser instead of Dusk maintaining its own numbered
|
||||
# slots (see save/saveslot.h for the default used by every other platform).
|
||||
target_compile_definitions(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
|
||||
SAVE_SLOT_COUNT_MAX=1
|
||||
)
|
||||
@@ -1,53 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "save/savepsp.h"
|
||||
#include "save/savestreampsp.h"
|
||||
|
||||
typedef savepsp_t saveplatform_t;
|
||||
typedef savestreampsp_t saveplatformstream_t;
|
||||
|
||||
#define saveInitPlatform saveInitPSP
|
||||
#define saveDisposePlatform saveDisposePSP
|
||||
#define saveSlotDeletePlatform saveDeleteSlotPSP
|
||||
|
||||
#define saveStreamReadBytesPlatform(stream, buf, len) \
|
||||
saveStreamReadBytesPSP(&(stream)->platform, buf, len)
|
||||
#define saveStreamWriteBytesPlatform(stream, buf, len) \
|
||||
saveStreamWriteBytesPSP(&(stream)->platform, buf, len)
|
||||
#define saveStreamSeekPlatform(stream, pos) \
|
||||
saveStreamSeekPSP(&(stream)->platform, pos)
|
||||
#define saveStreamTellPlatform(stream, out) \
|
||||
saveStreamTellPSP(&(stream)->platform, out)
|
||||
|
||||
// Save/load go entirely through the native sceUtilitySavedata dialog
|
||||
// (savePSPBeginSave/Load), which spans multiple frames - these bypass
|
||||
// save.c's normal synchronous open/write-fields/close flow above (that's
|
||||
// still used internally, just against an in-memory buffer, from within
|
||||
// savePSPBeginSave/Load themselves) and are what save.c's saveWriteSlot()/
|
||||
// saveLoadSlot()/saveWriteMeta()/saveLoadMeta() actually call on this
|
||||
// platform. Meta and the (one) slot are serialized together into the same
|
||||
// buffer - there is no separate meta-only path on PSP - so the meta
|
||||
// variants just drive the same dialog against SAVE_ACTIVE_SLOT.
|
||||
#define saveSlotAsyncWritePlatform(slot, onComplete, user) \
|
||||
savePSPBeginSave(slot, onComplete, user)
|
||||
#define saveSlotAsyncLoadPlatform(slot, onComplete, user) \
|
||||
savePSPBeginLoad(slot, onComplete, user)
|
||||
#define saveMetaAsyncWritePlatform(onComplete, user) \
|
||||
savePSPBeginSave(SAVE_ACTIVE_SLOT, onComplete, user)
|
||||
#define saveMetaAsyncLoadPlatform(onComplete, user) \
|
||||
savePSPBeginLoad(SAVE_ACTIVE_SLOT, onComplete, user)
|
||||
#define saveIsBusyPlatform() savePSPIsBusy()
|
||||
#define savePlatformUpdate() savePSPUpdate()
|
||||
|
||||
// Meta only reaches memory via the native dialog now (folded into the same
|
||||
// payload as the save slot) - running that multi-frame dialog on every
|
||||
// single boot just to eagerly populate SAVE.meta would reintroduce the
|
||||
// exact UX problem a lightweight settings-only file used to avoid, so
|
||||
// saveInit() skips eager loading entirely on this platform.
|
||||
#define saveSkipEagerLoadPlatform
|
||||
@@ -1,291 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "save/save.h"
|
||||
#include "save/savepsp.h"
|
||||
#include "save/savestream.h"
|
||||
#include "system/systempsp.h"
|
||||
#include "util/memory.h"
|
||||
#include "util/string.h"
|
||||
#include "assert/assert.h"
|
||||
|
||||
static void savePSPParamCommonInit(SceUtilitySavedataParam *param) {
|
||||
memoryZero(param, sizeof(SceUtilitySavedataParam));
|
||||
param->base.size = sizeof(SceUtilitySavedataParam);
|
||||
param->base.language = systemPSPGetLanguage();
|
||||
param->base.buttonSwap = systemPSPGetCrossButtonSetting();
|
||||
param->base.graphicsThread = 17;
|
||||
param->base.accessThread = 19;
|
||||
param->base.fontThread = 18;
|
||||
param->base.soundThread = 16;
|
||||
|
||||
stringCopy(param->gameName, SAVE_PSP_GAME_NAME, sizeof(param->gameName));
|
||||
stringCopy(param->fileName, SAVE_PSP_FILE_NAME, sizeof(param->fileName));
|
||||
}
|
||||
|
||||
static void savePSPSaveNameForSlot(
|
||||
char_t *out, const size_t max, const uint8_t slot
|
||||
) {
|
||||
stringFormat(out, max, "%02u", (uint32_t)slot);
|
||||
}
|
||||
|
||||
errorret_t saveInitPSP(void) {
|
||||
SceIoStat stat;
|
||||
if(sceIoGetstat(SAVE_PSP_ROOT, &stat) < 0) {
|
||||
errorThrow("No memory stick detected");
|
||||
}
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t saveDisposePSP(void) {
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t saveDeleteSlotPSP(const uint8_t slot) {
|
||||
char_t path[SAVE_PSP_PATH_MAX];
|
||||
stringFormat(
|
||||
path, sizeof(path), SAVE_PSP_FILE_FORMAT, SAVE_PSP_GAME_NAME,
|
||||
(uint32_t)slot
|
||||
);
|
||||
|
||||
int32_t result = sceIoRemove(path);
|
||||
if(result < 0 && result != (int32_t)0x80010002) {
|
||||
errorThrow("Failed to delete save data for slot %u", (uint32_t)slot);
|
||||
}
|
||||
|
||||
char_t dir[SAVE_PSP_PATH_MAX];
|
||||
stringFormat(
|
||||
dir, sizeof(dir), "ms0:/PSP/SAVEDATA/%s%02u", SAVE_PSP_GAME_NAME,
|
||||
(uint32_t)slot
|
||||
);
|
||||
char_t sfoPath[SAVE_PSP_PATH_MAX];
|
||||
stringFormat(sfoPath, sizeof(sfoPath), "%s/PARAM.SFO", dir);
|
||||
// Best-effort - PARAM.SFO/the directory itself may not exist (e.g. this
|
||||
// slot was written by the old raw-file format, pre-dating this dialog-
|
||||
// based rewrite) or the directory may still contain other entries.
|
||||
sceIoRemove(sfoPath);
|
||||
sceIoRmdir(dir);
|
||||
|
||||
errorOk();
|
||||
}
|
||||
|
||||
void savePSPBeginSave(
|
||||
const uint8_t slot, savecallback_t onComplete, void *user
|
||||
) {
|
||||
assertNotNull(onComplete, "onComplete cannot be NULL");
|
||||
assertTrue(SAVE.platform.op == SAVE_PSP_OP_NONE, "Save already in progress");
|
||||
|
||||
saveslot_t *slotData = &SAVE.slots[slot];
|
||||
|
||||
// Serialize meta then the slot into the buffer synchronously (plain
|
||||
// memory writes, same header/version/CRC framing as every other
|
||||
// platform) before the dialog ever starts - only the actual commit-to-
|
||||
// storage step needs to wait on the dialog.
|
||||
savestream_t stream;
|
||||
memoryZero(&stream, sizeof(savestream_t));
|
||||
stream.platform.buffer = SAVE.platform.dataBuffer;
|
||||
stream.platform.bufferSize = sizeof(SAVE.platform.dataBuffer);
|
||||
|
||||
errorret_t ret = saveMetaSerializeWrite(&stream, &SAVE.meta);
|
||||
if(errorIsOk(ret)) ret = saveSlotSerializeWrite(&stream, slotData);
|
||||
if(errorIsNotOk(ret)) {
|
||||
onComplete(ret, user);
|
||||
return;
|
||||
}
|
||||
SAVE.platform.dataLength = stream.platform.length;
|
||||
|
||||
SceUtilitySavedataParam *param = &SAVE.platform.param;
|
||||
savePSPParamCommonInit(param);
|
||||
// AUTOSAVE rather than SAVE - SAVE shows a "save to this data?" confirm
|
||||
// screen even for a slot with no existing data, which isn't the UX we
|
||||
// want for a menu-triggered "Save" action (that confirmation already
|
||||
// happened when the player chose to save). AUTOSAVE writes silently
|
||||
// (just a brief "saving" icon flash) while still generating the same
|
||||
// PARAM.SFO/title/description as any other mode.
|
||||
param->mode = PSP_UTILITY_SAVEDATA_AUTOSAVE;
|
||||
param->overwrite = 1;
|
||||
savePSPSaveNameForSlot(param->saveName, sizeof(param->saveName), slot);
|
||||
|
||||
param->dataBuf = SAVE.platform.dataBuffer;
|
||||
param->dataBufSize = sizeof(SAVE.platform.dataBuffer);
|
||||
param->dataSize = SAVE.platform.dataLength;
|
||||
|
||||
// No ICON0/PIC1/SND0 art exists in this project yet, so these are left
|
||||
// zeroed (bufSize 0) - the utility treats that as "no icon/background/
|
||||
// sound" rather than an error. title/savedataTitle/detail are still
|
||||
// fully functional and are what actually populates PARAM.SFO and the
|
||||
// save browser entry.
|
||||
stringCopy(param->sfoParam.title, "Dusk", sizeof(param->sfoParam.title));
|
||||
stringCopy(
|
||||
param->sfoParam.savedataTitle, slotData->playerName,
|
||||
sizeof(param->sfoParam.savedataTitle)
|
||||
);
|
||||
stringCopy(
|
||||
param->sfoParam.detail, "Dusk save file.", sizeof(param->sfoParam.detail)
|
||||
);
|
||||
|
||||
int32_t initRet = sceUtilitySavedataInitStart(param);
|
||||
if(initRet < 0) {
|
||||
onComplete(errorThrowImpl(
|
||||
&SAVE.errorState, ERROR_NOT_OK, __FILE__, __func__, __LINE__,
|
||||
"Failed to start save dialog: 0x%08X", initRet
|
||||
), user);
|
||||
return;
|
||||
}
|
||||
|
||||
SAVE.platform.op = SAVE_PSP_OP_SAVE;
|
||||
SAVE.platform.slot = slot;
|
||||
SAVE.platform.onComplete = onComplete;
|
||||
SAVE.platform.onCompleteUser = user;
|
||||
}
|
||||
|
||||
void savePSPBeginLoad(
|
||||
const uint8_t slot, savecallback_t onComplete, void *user
|
||||
) {
|
||||
assertNotNull(onComplete, "onComplete cannot be NULL");
|
||||
assertTrue(SAVE.platform.op == SAVE_PSP_OP_NONE, "Save already in progress");
|
||||
|
||||
char_t path[SAVE_PSP_PATH_MAX];
|
||||
stringFormat(
|
||||
path, sizeof(path), SAVE_PSP_FILE_FORMAT, SAVE_PSP_GAME_NAME,
|
||||
(uint32_t)slot
|
||||
);
|
||||
|
||||
SceIoStat stat;
|
||||
if(sceIoGetstat(path, &stat) < 0) {
|
||||
// No save data yet - not an error (matches every other platform's
|
||||
// "nothing to load yet" behavior), and deliberately skips showing the
|
||||
// dialog at all rather than surfacing an empty "no data" native
|
||||
// screen for data the player has never saved.
|
||||
onComplete(errorOkImpl(), user);
|
||||
return;
|
||||
}
|
||||
|
||||
SceUtilitySavedataParam *param = &SAVE.platform.param;
|
||||
savePSPParamCommonInit(param);
|
||||
param->mode = PSP_UTILITY_SAVEDATA_AUTOLOAD;// See savePSPBeginSave().
|
||||
savePSPSaveNameForSlot(param->saveName, sizeof(param->saveName), slot);
|
||||
|
||||
param->dataBuf = SAVE.platform.dataBuffer;
|
||||
param->dataBufSize = sizeof(SAVE.platform.dataBuffer);
|
||||
|
||||
int32_t initRet = sceUtilitySavedataInitStart(param);
|
||||
if(initRet < 0) {
|
||||
onComplete(errorThrowImpl(
|
||||
&SAVE.errorState, ERROR_NOT_OK, __FILE__, __func__, __LINE__,
|
||||
"Failed to start load dialog: 0x%08X", initRet
|
||||
), user);
|
||||
return;
|
||||
}
|
||||
|
||||
SAVE.platform.op = SAVE_PSP_OP_LOAD;
|
||||
SAVE.platform.slot = slot;
|
||||
SAVE.platform.onComplete = onComplete;
|
||||
SAVE.platform.onCompleteUser = user;
|
||||
}
|
||||
|
||||
bool_t savePSPIsBusy(void) {
|
||||
return SAVE.platform.op != SAVE_PSP_OP_NONE;
|
||||
}
|
||||
|
||||
errorret_t savePSPUpdate(void) {
|
||||
if(SAVE.platform.op == SAVE_PSP_OP_NONE) errorOk();
|
||||
|
||||
int32_t status = sceUtilitySavedataGetStatus();
|
||||
switch(status) {
|
||||
case PSP_UTILITY_DIALOG_INIT:
|
||||
break;
|
||||
|
||||
// NOTE: unlike the netconf dialog, this does not replicate Dusk's own
|
||||
// GL state (blend/cull/depth + texture/color) before calling Update().
|
||||
// A prior fix for exactly that class of bug was documented for the
|
||||
// network dialog, but no longer exists in the current codebase to
|
||||
// copy from - if the save dialog's own text/icons don't render
|
||||
// correctly on real hardware (PPSSPP won't reproduce this - it doesn't
|
||||
// model pspGL's deferred state application), that state-priming
|
||||
// pattern is the fix to reach for. See the network dialog's git
|
||||
// history / the project's PSP dialog memory notes for the exact
|
||||
// technique (state flags + a forced flush via a degenerate triangle
|
||||
// draw).
|
||||
case PSP_UTILITY_DIALOG_VISIBLE:
|
||||
// sceUtilitySavedataUpdate() is void, unlike sceUtilityNetconfUpdate()
|
||||
// - nothing to check here, GetStatus() next frame reflects any
|
||||
// resulting state change.
|
||||
sceUtilitySavedataUpdate(1);
|
||||
break;
|
||||
|
||||
case PSP_UTILITY_DIALOG_QUIT:
|
||||
// The save/load operation itself has already finished (successfully
|
||||
// or not) - this just starts tearing the dialog down. The actual
|
||||
// result is read once that teardown settles, below - don't call
|
||||
// ShutdownStart more than once while waiting for it to.
|
||||
if(!SAVE.platform.shuttingDown) {
|
||||
SAVE.platform.shuttingDown = true;
|
||||
sceUtilitySavedataShutdownStart();
|
||||
}
|
||||
break;
|
||||
|
||||
// Confirmed under PPSSPP: status settles straight from QUIT to NONE,
|
||||
// without FINISHED ever being separately observed in between - so
|
||||
// both are treated identically here as "torn down, read the result",
|
||||
// and it's shuttingDown (not which of these two codes we saw) that
|
||||
// distinguishes that from a genuine disappearance.
|
||||
case PSP_UTILITY_DIALOG_FINISHED:
|
||||
case PSP_UTILITY_DIALOG_NONE: {
|
||||
savepspop_t op = SAVE.platform.op;
|
||||
uint8_t slot = SAVE.platform.slot;
|
||||
savecallback_t cb = SAVE.platform.onComplete;
|
||||
void *user = SAVE.platform.onCompleteUser;
|
||||
bool_t reachedQuit = SAVE.platform.shuttingDown;
|
||||
SAVE.platform.op = SAVE_PSP_OP_NONE;
|
||||
SAVE.platform.shuttingDown = false;
|
||||
|
||||
if(!reachedQuit) {
|
||||
cb(errorThrowImpl(
|
||||
&SAVE.errorState, ERROR_NOT_OK, __FILE__, __func__, __LINE__,
|
||||
"Save dialog disappeared without a result"
|
||||
), user);
|
||||
break;
|
||||
}
|
||||
|
||||
int32_t result = SAVE.platform.param.base.result;
|
||||
if(result != 0) {
|
||||
SAVE.available = false;
|
||||
cb(errorThrowImpl(
|
||||
&SAVE.errorState, ERROR_NOT_OK, __FILE__, __func__, __LINE__,
|
||||
"Save dialog failed: 0x%08X", result
|
||||
), user);
|
||||
break;
|
||||
}
|
||||
SAVE.available = true;
|
||||
|
||||
if(op == SAVE_PSP_OP_LOAD) {
|
||||
savestream_t stream;
|
||||
memoryZero(&stream, sizeof(savestream_t));
|
||||
stream.platform.buffer = SAVE.platform.dataBuffer;
|
||||
stream.platform.bufferSize = sizeof(SAVE.platform.dataBuffer);
|
||||
stream.platform.length = SAVE.platform.param.dataSize;
|
||||
|
||||
errorret_t ret = saveMetaSerializeRead(&stream, &SAVE.meta);
|
||||
if(errorIsOk(ret)) {
|
||||
ret = saveSlotSerializeRead(&stream, &SAVE.slots[slot]);
|
||||
}
|
||||
cb(ret, user);
|
||||
} else {
|
||||
SAVE.meta.exists = true;
|
||||
SAVE.slots[slot].exists = true;
|
||||
cb(errorOkImpl(), user);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
errorThrow("Unknown savedata dialog status: %d", status);
|
||||
}
|
||||
|
||||
errorOk();
|
||||
}
|
||||
@@ -1,133 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "error/error.h"
|
||||
#include "save/saveslot.h"
|
||||
#include "save/savemeta.h"
|
||||
#include <pspiofilemgr.h>
|
||||
#include <psputility.h>
|
||||
|
||||
#define SAVE_PSP_PATH_MAX 256
|
||||
#define SAVE_PSP_ROOT "ms0:/"
|
||||
#define SAVE_PSP_FILE_NAME "save.bin"
|
||||
#define SAVE_PSP_FILE_FORMAT "ms0:/PSP/SAVEDATA/%s%02u/" SAVE_PSP_FILE_NAME
|
||||
#define SAVE_PSP_DATA_BUFFER_SIZE 4096
|
||||
|
||||
#ifndef SAVE_PSP_GAME_NAME
|
||||
#define SAVE_PSP_GAME_NAME "DUSK00001"
|
||||
#endif
|
||||
|
||||
typedef enum {
|
||||
SAVE_PSP_OP_NONE,
|
||||
SAVE_PSP_OP_SAVE,
|
||||
SAVE_PSP_OP_LOAD
|
||||
} savepspop_t;
|
||||
|
||||
typedef struct {
|
||||
SceUtilitySavedataParam param;
|
||||
// Raw buffer sceUtilitySavedata reads/writes the whole save into/from -
|
||||
// holds BOTH save meta and the (single) save slot back-to-back,
|
||||
// populated by our own savestream_t serialization (see savestreampsp.h)
|
||||
// before a save starts, and deserialized from after a load finishes.
|
||||
// Meta lives in here rather than its own lightweight file specifically
|
||||
// because a device-wide preference change is meant to feel like a real
|
||||
// save on this platform (a brief native icon flash), not need its own
|
||||
// separate storage mechanism.
|
||||
uint8_t dataBuffer[SAVE_PSP_DATA_BUFFER_SIZE] __attribute__((aligned(64)));
|
||||
size_t dataLength;
|
||||
|
||||
savepspop_t op;
|
||||
// True once sceUtilitySavedataShutdownStart() has been requested (dialog
|
||||
// status PSP_UTILITY_DIALOG_QUIT seen) - distinguishes a normal "torn
|
||||
// down after finishing" NONE/FINISHED from a genuinely unexpected one
|
||||
// seen before ever reaching QUIT. Some implementations (confirmed on
|
||||
// PPSSPP) settle straight to NONE after shutdown without a separately
|
||||
// observable FINISHED step in between.
|
||||
bool_t shuttingDown;
|
||||
uint8_t slot;
|
||||
savecallback_t onComplete;
|
||||
void *onCompleteUser;
|
||||
} savepsp_t;
|
||||
|
||||
/**
|
||||
* Initializes the save system on PSP. Confirms the memory stick is
|
||||
* actually reachable (sceIoGetstat on SAVE_PSP_ROOT) rather than assuming
|
||||
* so, since the savedata dialog otherwise only reports failure once a
|
||||
* save/load is actually attempted.
|
||||
*
|
||||
* @return An error code if no memory stick is reachable.
|
||||
*/
|
||||
errorret_t saveInitPSP(void);
|
||||
|
||||
/**
|
||||
* Disposes of the save system on PSP.
|
||||
*
|
||||
* @return An error code if disposal fails.
|
||||
*/
|
||||
errorret_t saveDisposePSP(void);
|
||||
|
||||
/**
|
||||
* Deletes the (one) save data folder from the memory stick.
|
||||
*
|
||||
* @param slot The save slot index (always 0 on PSP - see
|
||||
* SAVE_SLOT_COUNT_MAX's override in this platform's CMakeLists.txt).
|
||||
* @return An error code if the delete fails.
|
||||
*/
|
||||
errorret_t saveDeleteSlotPSP(const uint8_t slot);
|
||||
|
||||
/**
|
||||
* Starts a save via the native sceUtilitySavedata dialog (mode AUTOSAVE -
|
||||
* writes silently with just a brief icon flash, no confirm screen, since
|
||||
* SAVE mode shows one even for a slot with no existing data - but
|
||||
* PARAM.SFO/title/description are generated identically regardless of
|
||||
* mode, and the OS handles the save browser entry either way). Serializes
|
||||
* SAVE.meta then SAVE.slots[slot] into SAVE.platform.dataBuffer first,
|
||||
* synchronously, then kicks off the dialog and returns - completion is
|
||||
* reported later via onComplete, driven by savePSPUpdate() each frame.
|
||||
* If no save data exists yet, sceUtilitySavedataInitStart() creates it.
|
||||
*
|
||||
* @param slot The save slot index.
|
||||
* @param onComplete Callback invoked once the dialog finishes.
|
||||
* @param user User data passed through to onComplete.
|
||||
*/
|
||||
void savePSPBeginSave(
|
||||
const uint8_t slot, savecallback_t onComplete, void *user
|
||||
);
|
||||
|
||||
/**
|
||||
* Starts a load via the native sceUtilitySavedata dialog (mode AUTOLOAD -
|
||||
* see savePSPBeginSave() for why not the plain LOAD mode) unless a quick
|
||||
* sceIoGetstat check finds no save data yet - in which case onComplete is
|
||||
* invoked immediately with SAVE.meta/SAVE.slots[slot].exists left false,
|
||||
* matching the other platforms' "no file yet" semantics, and no dialog is
|
||||
* shown at all.
|
||||
*
|
||||
* @param slot The save slot index.
|
||||
* @param onComplete Callback invoked once the dialog (or immediate
|
||||
* not-found short-circuit) finishes.
|
||||
* @param user User data passed through to onComplete.
|
||||
*/
|
||||
void savePSPBeginLoad(
|
||||
const uint8_t slot, savecallback_t onComplete, void *user
|
||||
);
|
||||
|
||||
/**
|
||||
* Pumps the in-progress save/load dialog one step, if any - must be called
|
||||
* every engine frame (see saveUpdate()). No-op if no dialog is active.
|
||||
*
|
||||
* @return An error code indicating success or failure.
|
||||
*/
|
||||
errorret_t savePSPUpdate(void);
|
||||
|
||||
/**
|
||||
* True while a save/load dialog is in progress (see savePSPBeginSave()/
|
||||
* savePSPBeginLoad()).
|
||||
*
|
||||
* @return True if a save/load dialog is currently open.
|
||||
*/
|
||||
bool_t savePSPIsBusy(void);
|
||||
@@ -1,46 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "save/save.h"
|
||||
#include "save/savestreampsp.h"
|
||||
#include "util/memory.h"
|
||||
|
||||
errorret_t saveStreamReadBytesPSP(
|
||||
savestreampsp_t *p, void *buf, const size_t len
|
||||
) {
|
||||
if(p->position + len > p->length) {
|
||||
errorThrow("Save stream read exceeds buffer length");
|
||||
}
|
||||
memoryCopy(buf, p->buffer + p->position, len);
|
||||
p->position += len;
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t saveStreamWriteBytesPSP(
|
||||
savestreampsp_t *p, const void *buf, const size_t len
|
||||
) {
|
||||
if(p->position + len > p->bufferSize) {
|
||||
errorThrow("Save stream write exceeds buffer size");
|
||||
}
|
||||
memoryCopy(p->buffer + p->position, buf, len);
|
||||
p->position += len;
|
||||
if(p->position > p->length) p->length = p->position;
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t saveStreamSeekPSP(savestreampsp_t *p, const size_t pos) {
|
||||
if(pos > p->bufferSize) {
|
||||
errorThrow("Save stream seek out of range");
|
||||
}
|
||||
p->position = pos;
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t saveStreamTellPSP(savestreampsp_t *p, size_t *out) {
|
||||
*out = p->position;
|
||||
errorOk();
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "error/error.h"
|
||||
#include <stddef.h>
|
||||
|
||||
// Backed by SAVE.platform.dataBuffer (see savepsp.h) rather than owning its
|
||||
// own memory - the buffer has to outlive a single saveFileWrite()/Load()
|
||||
// call, since the actual save/load dialog it's handed to only completes
|
||||
// several frames later.
|
||||
typedef struct {
|
||||
uint8_t *buffer;
|
||||
size_t bufferSize;
|
||||
size_t position;
|
||||
size_t length;
|
||||
} savestreampsp_t;
|
||||
|
||||
/**
|
||||
* Copies len bytes from the buffer at the current position into buf.
|
||||
*
|
||||
* @param p Active stream.
|
||||
* @param buf Destination buffer.
|
||||
* @param len Number of bytes to read.
|
||||
* @return An error if the read would exceed the populated data length.
|
||||
*/
|
||||
errorret_t saveStreamReadBytesPSP(
|
||||
savestreampsp_t *p, void *buf, const size_t len
|
||||
);
|
||||
|
||||
/**
|
||||
* Copies len bytes from buf into the buffer at the current position,
|
||||
* growing p->length if this write extends past it.
|
||||
*
|
||||
* @param p Active stream.
|
||||
* @param buf Source buffer.
|
||||
* @param len Number of bytes to write.
|
||||
* @return An error if the write would exceed bufferSize.
|
||||
*/
|
||||
errorret_t saveStreamWriteBytesPSP(
|
||||
savestreampsp_t *p, const void *buf, const size_t len
|
||||
);
|
||||
|
||||
/**
|
||||
* Sets the current read/write position within the buffer.
|
||||
*
|
||||
* @param p Active stream.
|
||||
* @param pos Target byte offset from the start of the buffer.
|
||||
* @return An error if pos is out of range.
|
||||
*/
|
||||
errorret_t saveStreamSeekPSP(savestreampsp_t *p, const size_t pos);
|
||||
|
||||
/**
|
||||
* Gets the current read/write position within the buffer.
|
||||
*
|
||||
* @param p Active stream.
|
||||
* @param out Receives the current position.
|
||||
* @return An error - always succeeds, matches saveStreamTellImpl's shape.
|
||||
*/
|
||||
errorret_t saveStreamTellPSP(savestreampsp_t *p, size_t *out);
|
||||
@@ -9,4 +9,5 @@
|
||||
#include "system/systempsp.h"
|
||||
|
||||
#define systemInitPlatform systemInitPSP
|
||||
#define systemGetActiveDialogTypePlatform systemGetActiveDialogTypePSP
|
||||
#define systemGetActiveDialogTypePlatform systemGetActiveDialogTypePSP
|
||||
#define systemGetLocalePlatform systemGetLocalePSP
|
||||
@@ -61,4 +61,12 @@ int_t systemPSPGetCrossButtonSetting() {
|
||||
return (
|
||||
ret == 1 ? PSP_UTILITY_ACCEPT_CROSS : PSP_UTILITY_ACCEPT_CIRCLE
|
||||
);
|
||||
}
|
||||
|
||||
const localeinfo_t * systemGetLocalePSP(void) {
|
||||
switch(systemPSPGetLanguage()) {
|
||||
case PSP_SYSTEMPARAM_LANGUAGE_JAPANESE: return &LOCALE_INFO_JP_JP;
|
||||
case PSP_SYSTEMPARAM_LANGUAGE_SPANISH: return &LOCALE_INFO_ES_MX;
|
||||
default: return &LOCALE_DEFAULT;
|
||||
}
|
||||
}
|
||||
@@ -37,4 +37,12 @@ int_t systemPSPGetLanguage();
|
||||
*
|
||||
* @return PSP_UTILITY_ACCEPT_CROSS or PSP_UTILITY_ACCEPT_CIRCLE.
|
||||
*/
|
||||
int_t systemPSPGetCrossButtonSetting();
|
||||
int_t systemPSPGetCrossButtonSetting();
|
||||
|
||||
/**
|
||||
* Returns the locale matching the system's configured language, falling
|
||||
* back to LOCALE_DEFAULT if the system language has no matching locale.
|
||||
*
|
||||
* @return The current locale.
|
||||
*/
|
||||
const localeinfo_t * systemGetLocalePSP(void);
|
||||
Reference in New Issue
Block a user