-
Notifications
You must be signed in to change notification settings - Fork 0
Layer 1 Interface
Dual entry points providing the same operations through different communication mechanisms.
A static/shared library with a stable C ABI. Callers link against it and call functions directly. This is the primary interface for languages with FFI support (C, C++, Rust, Zig, etc.).
ABI stability rules apply from the very first shipped version:
- Opaque structs at the boundary — callers never allocate library structs
- Append-only fields — new fields can be added without breaking old callers
- No struct reordering or field removal once shipped
- No struct passed by value across the API boundary
Build: make static produces build/libsavesync.a. Include include/savesync.h.
Status: Implemented. See IPC Protocol v1 for the full specification.
A standalone binary (savesync-ipc) exposing the same operations over newline-delimited JSON (NDJSON) on stdin/stdout. Intended for callers that can't easily FFI (e.g. some JS runtimes, scripting languages without native bindings).
Build: make ipc produces build/savesync-ipc.
- Transport: NDJSON over stdin/stdout — one JSON object per line in, one per line out
-
Request shape:
{"id": "<client-chosen>", "method": "<method>", "params": {...}} -
Response shape:
{"id": "<echoed>", "result": {...}}or{"id": "<echoed>", "error": {"code": <int>, "message": "<string>"}} - Errors never crash the process. Malformed input, unknown methods, and internal errors all produce a JSON error response.
| Method | Description |
|---|---|
init |
Initialize the library with a base path |
manifest_load |
Load an emulator manifest from a file |
register_with_manifest |
Register a save using a manifest (auto-detects game ID) |
register |
Register a save without a manifest |
save |
Snapshot the current live save into the versioned magazine |
pull |
Restore the latest saved version to the live path |
pull_select |
Restore a specific entry by ID |
list_entries |
List all save snapshots for a registration |
list_registrations |
List all active registrations |
unregister |
Remove a registration |
shutdown |
Shut down cleanly (process exits after response) |
→ {"id":"1","method":"init","params":{"base_path":"/tmp/saves"}}
← {"id":"1","result":{"ok":true}}
→ {"id":"2","method":"manifest_load","params":{"path":"manifests/dolphin_gc.cfg"}}
← {"id":"2","result":{"manifest_id":"m0"}}
→ {"id":"3","method":"register_with_manifest","params":{"manifest_id":"m0","live_path":"/path/to/save","shape":"file"}}
← {"id":"3","result":{"registration_id":"r0"}}
→ {"id":"4","method":"save","params":{"registration_id":"r0"}}
← {"id":"4","result":{"entry_id":"e0","entry_created":true,"dedup_skipped":false}}
→ {"id":"5","method":"shutdown"}
← {"id":"5","result":{"ok":true}}
The dual-interface approach means:
- High-performance languages get zero-overhead direct calls
- Low-performance or restricted environments still get full functionality
- The internal logic is implemented exactly once
See also: Architecture-Overview | API-Reference | Philosophy | Examples
libsavesync Wiki