4.7 KiB
Save System
Source: src/dusk/save/, platform layers in src/dusk<platform>/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
extern save_t SAVE;
// SAVE.files[SAVE_FILE_COUNT_MAX] -- one per slot
// SAVE.platform -- platform-specific state
API
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.
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.
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
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<platform>/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.