/** * 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/savefile.h" #include #include #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 - // 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 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 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. */ errorret_t saveDeletePSP(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) 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 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);