PSP: save through the real sceUtilitySavedata API, not raw file I/O
Rewrote savepsp.c/savestreampsp.c to use sceUtilitySavedataInitStart/ Update/GetStatus/ShutdownStart instead of sceIoOpen/Read/Write, so PSP saves get a proper OS-generated PARAM.SFO (title/savedataTitle/detail) and show up correctly in the native save browser. This dialog spans multiple frames and, per this project's prior experience with the network config dialog, must be pumped non-blocking one step per real engine frame rather than blocked on synchronously - a raw-sceGu blocking loop already froze the app on real hardware for that dialog, since pspGL owns the GU context. So save.h's saveWrite()/saveLoad() are now callback-based (savecallback_t onComplete) instead of returning a result directly, mirroring networkRequestConnection()'s shape, with a new saveUpdate() (wired into engineUpdate()) pumping the active op each frame. Linux/Dolphin behavior is unchanged - their fallback path in save.c still completes synchronously, just via an immediate callback call instead of a direct return. Two real bugs found via PPSSPP testing (not just code review): SAVE/LOAD modes show a confirm screen even for brand-new data, which blocks forever headlessly - switched to AUTOSAVE/AUTOLOAD, which write/read silently and generate the identical PARAM.SFO. And PPSSPP's dialog status goes straight from QUIT to NONE without a separately observable FINISHED in between, which the first version misread as "disappeared without a result" even on a successful save - fixed by tracking whether QUIT was already seen. Confirmed end-to-end in PPSSPP: write, dialog completes, PARAM.SFO + encrypted save.bin appear on the virtual memory stick, and a subsequent load decrypts/deserializes back to the exact original data. Not tested on real PSP hardware.
This commit is contained in:
+83
-36
@@ -9,29 +9,50 @@
|
||||
#include "error/error.h"
|
||||
#include "save/savefile.h"
|
||||
#include <pspiofilemgr.h>
|
||||
#include <psputility.h>
|
||||
|
||||
#define SAVE_PSP_PATH_MAX 256
|
||||
#define SAVE_PSP_ROOT "ms0:/"
|
||||
#define SAVE_PSP_BASE_DIR "ms0:/PSP"
|
||||
#define SAVE_PSP_SAVEDATA_DIR "ms0:/PSP/SAVEDATA"
|
||||
#define SAVE_PSP_FILE_FORMAT "ms0:/PSP/SAVEDATA/%s%02u/save.dat"
|
||||
#define SAVE_PSP_DIR_FORMAT "ms0:/PSP/SAVEDATA/%s%02u"
|
||||
#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_TITLE_ID
|
||||
#define SAVE_PSP_TITLE_ID "DUSK00001"
|
||||
#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 {
|
||||
uint8_t unused;
|
||||
SceUtilitySavedataParam param;
|
||||
// Raw buffer sceUtilitySavedata reads/writes the whole save into/from -
|
||||
// populated by our own savestream_t serialization (see savestreampsp.h)
|
||||
// before a save starts, and deserialized from after a load finishes.
|
||||
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 raw sceIo calls otherwise only fail once something tries to
|
||||
* touch the filesystem - and ensures the PSP/SAVEDATA directory tree
|
||||
* exists (SAVE_PSP_BASE_DIR then SAVE_PSP_SAVEDATA_DIR, since sceIoMkdir
|
||||
* only creates one level at a time).
|
||||
* 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.
|
||||
*/
|
||||
@@ -45,25 +66,7 @@ errorret_t saveInitPSP(void);
|
||||
errorret_t saveDisposePSP(void);
|
||||
|
||||
/**
|
||||
* Loads a save file from PSP save data for the given slot.
|
||||
*
|
||||
* @param slot The save slot index.
|
||||
* @param file Output save file data.
|
||||
* @return An error code if the load fails.
|
||||
*/
|
||||
errorret_t saveLoadPSP(const uint8_t slot, savefile_t *file);
|
||||
|
||||
/**
|
||||
* Writes a save file to PSP save data for the given slot.
|
||||
*
|
||||
* @param slot The save slot index.
|
||||
* @param file Save file data to write.
|
||||
* @return An error code if the write fails.
|
||||
*/
|
||||
errorret_t saveWritePSP(const uint8_t slot, const savefile_t *file);
|
||||
|
||||
/**
|
||||
* Deletes the save file for the given slot from PSP save data.
|
||||
* Deletes the save data folder for the given slot from the memory stick.
|
||||
*
|
||||
* @param slot The save slot index.
|
||||
* @return An error code if the delete fails.
|
||||
@@ -71,10 +74,54 @@ errorret_t saveWritePSP(const uint8_t slot, const savefile_t *file);
|
||||
errorret_t saveDeletePSP(const uint8_t slot);
|
||||
|
||||
/**
|
||||
* Ensures SAVE_PSP_BASE_DIR and SAVE_PSP_SAVEDATA_DIR both exist, creating
|
||||
* whichever are missing. sceIoMkdir only creates one directory level at a
|
||||
* time, so this must run before creating any per-slot save directory
|
||||
* beneath SAVE_PSP_SAVEDATA_DIR. Safe to call repeatedly - an
|
||||
* already-exists result is not an error.
|
||||
* 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) for the
|
||||
* given slot. Serializes SAVE.files[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 for this slot, 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 savePSPEnsureBaseDirs(void);
|
||||
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) for the given
|
||||
* slot, unless a quick sceIoGetstat check finds no save data for this slot
|
||||
* yet - in which case onComplete is invoked immediately with
|
||||
* SAVE.files[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);
|
||||
|
||||
Reference in New Issue
Block a user