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:
2026-08-04 09:54:45 -05:00
parent 24badd06a5
commit 9abf8101da
11 changed files with 499 additions and 223 deletions
+16 -38
View File
@@ -7,71 +7,49 @@
#pragma once
#include "error/error.h"
#include <pspiofilemgr.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 {
SceUID fd;
uint8_t *buffer;
size_t bufferSize;
size_t position;
size_t length;
} savestreampsp_t;
/**
* Opens a PSP save data file for reading.
*
* @param p Stream to initialize.
* @param found Set to true if the file exists, false if it does not.
* @param slot Save slot index.
* @return An error if the open fails for a reason other than missing file.
*/
errorret_t saveStreamOpenReadPSP(
savestreampsp_t *p, bool_t *found, const uint8_t slot
);
/**
* Opens a PSP save data file for writing, creating or truncating it.
* Creates the save data directory if it does not already exist.
*
* @param p Stream to initialize.
* @param slot Save slot index.
* @return An error if the file cannot be opened for writing.
*/
errorret_t saveStreamOpenWritePSP(savestreampsp_t *p, const uint8_t slot);
/**
* Closes the file descriptor held by the stream.
*
* @param p Stream to close.
*/
void saveStreamClosePSP(savestreampsp_t *p);
/**
* Reads len bytes from the stream into buf.
* 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 fewer than len bytes are available.
* @return An error if the read would exceed the populated data length.
*/
errorret_t saveStreamReadBytesPSP(
savestreampsp_t *p, void *buf, const size_t len
);
/**
* Writes len bytes from buf into the stream.
* 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 fails.
* @return An error if the write would exceed bufferSize.
*/
errorret_t saveStreamWriteBytesPSP(
savestreampsp_t *p, const void *buf, const size_t len
);
/**
* Seeks to an absolute byte position within the stream.
* Sets the current read/write position within the buffer.
*
* @param p Active stream.
* @param pos Target byte offset from the start of the file.
* @return An error if the seek fails.
* @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);