Add claude docs
This commit is contained in:
+148
@@ -0,0 +1,148 @@
|
||||
# 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
|
||||
|
||||
```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<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.
|
||||
Reference in New Issue
Block a user