Skip to content

Layer 1 Interface

abduznik edited this page Jul 17, 2026 · 2 revisions

Layer 1 — Interface

Dual entry points providing the same operations through different communication mechanisms.

Linkable C ABI

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.

Standalone Binary with IPC

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.

Protocol

  • 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.

v1 Method Surface

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)

Example Session

→ {"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}}

Design Rationale

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

Clone this wiki locally