# Save System Source: `src/dusk/save/`, platform layers in `src/dusk/save/` ## Overview The save system provides multi-slot persistent storage. Each slot maps to one `savefile_t`. Platform implementations handle the actual read/write (memory card on GameCube/Wii, EEPROM/flash on PSP, filesystem on Linux). ## Global state ```c extern save_t SAVE; // SAVE.files[SAVE_FILE_COUNT_MAX] -- one per slot // SAVE.platform -- platform-specific state ``` ## API ```c errorret_t saveInit(void); errorret_t saveDispose(void); errorret_t saveLoad(uint8_t slot); // read slot from storage -> SAVE.files[slot] errorret_t saveWrite(uint8_t slot); // write SAVE.files[slot] -> storage errorret_t saveDelete(uint8_t slot); // delete slot from storage bool_t saveExists(uint8_t slot); // true if a save file is present savefile_t *saveGet(uint8_t slot); // pointer to the in-memory slot data ``` Slot indices are 0-based, range `[0, SAVE_FILE_COUNT_MAX - 1]`. ## Save file structure (`savefile.h`) `savefile_t` is a plain struct written verbatim to storage. Keep it small and use fixed-width integer types (`uint8_t`, `int32_t`, etc.) to ensure cross-platform binary compatibility. **Endianness:** storage is always written in little-endian byte order. Use the `endian.h` utilities when reading fields on big-endian targets (GameCube, Wii). See `.claude/util.md`. **Versioning:** include a version field at the start of `savefile_t`. Check it on load and handle mismatches gracefully (reset to defaults rather than crashing on corrupt data). ## Save stream (`savestream.h`) `savestream_t` is a cursor-based reader/writer used to serialize `savefile_t` to/from a raw byte buffer. Platform implementations use it to abstract the I/O layer. ```c typedef struct { bool_t found; uint32_t checksum; uint32_t expectedChecksum; saveplatformstream_t platform; } savestream_t; ``` ### Typed read/write macros Use the `saveFile*` macros inside `saveFileLoad` and `saveFileWrite`. All multi-byte values are stored in little-endian order; endian conversion is handled automatically. ```c saveFileReadHeader(stream, headerBuf) saveFileWriteHeader(stream, headerBuf) saveFileReadVersion(stream, &version) saveFileWriteVersion(stream, &version) saveFileReadBool(stream, &boolField) saveFileWriteBool(stream, &boolField) saveFileReadInt8(stream, &i8) saveFileWriteInt8(stream, &i8) saveFileReadUInt8(stream, &u8) saveFileWriteUInt8(stream, &u8) saveFileReadInt16(stream, &i16) saveFileWriteInt16(stream, &i16) saveFileReadUInt16(stream, &u16) saveFileWriteUInt16(stream, &u16) saveFileReadInt32(stream, &i32) saveFileWriteInt32(stream, &i32) saveFileReadUInt32(stream, &u32) saveFileWriteUInt32(stream, &u32) saveFileReadInt64(stream, &i64) saveFileWriteInt64(stream, &i64) saveFileReadUInt64(stream, &u64) saveFileWriteUInt64(stream, &u64) saveFileReadFloat(stream, &f) saveFileWriteFloat(stream, &f) saveFileReadString(stream, buf, maxLen) saveFileWriteString(stream, str, maxLen) saveFileReadDate(stream, &epoch) saveFileWriteDate(stream, &epoch) ``` Each macro expands to `errorChain(saveStreamRead/WriteXxxImpl(...))`. A failing read/write propagates the error up from `saveFileLoad` / `saveFileWrite`. ### Typical saveFileLoad / saveFileWrite pattern ```c errorret_t saveFileLoad(savestream_t *stream, savefile_t *file) { char_t header[SAVE_FILE_HEADER_SIZE]; saveFileReadHeader(stream, header); saveFileReadVersion(stream, &file->version); saveFileReadInt32(stream, &file->score); // ... remaining fields ... errorOk(); } errorret_t saveFileWrite(savestream_t *stream, savefile_t *file) { char_t header[SAVE_FILE_HEADER_SIZE] = SAVE_FILE_HEADER; saveFileWriteHeader(stream, header); saveFileWriteVersion(stream, &file->version); saveFileWriteInt32(stream, &file->score); // ... remaining fields ... errorOk(); } ``` After `saveFileWrite` completes, the platform layer calls `saveStreamFinalizeWriteImpl` which seeks back and writes the CRC32. After `saveFileLoad`, the platform calls `saveStreamVerifyChecksumImpl` to confirm the CRC matches. ## Platform notes | Platform | Storage mechanism | |----------|------------------| | Linux | File in user home / working directory | | Knulli | File on filesystem | | PSP | EEPROM / memory stick via `sceIo` | | GameCube | Memory Card via libogc `CARD_*` API | | Wii | NAND filesystem via libogc or SD card | Platform-specific save implementations go in `src/dusk/save/` and are wired in via `save/saveplatform.h` macros. ## PSP note PSP save dialogs are OS-level UI shown via `sceUtility`. When a dialog is open, `systemGetActiveDialogType()` returns a blocking type so the engine pauses the main loop. Never call save functions directly from game code without going through the engine's dialog guard.