Files
SyncGames/docs/protocol.md
T
DawnsorrowandCursor 0d6b0b2f80 Initial SyncGames tree: agent, Android, deploy, docs.
Session-gated MinIO save sync with AppImage GUI, CLI edit/session flow, and Gitea release helper.

Co-authored-by: Cursor <[email protected]>
2026-07-14 22:06:36 -05:00

3.7 KiB

SyncGames protocol (Python ↔ Android)

Schema version: 1

Both clients MUST implement these rules identically. Diverging behavior is a LOPE risk.

Object key layout

Bucket: configured (default syncgames).

Key Purpose
games/<game-id>/meta.json Lease, checksums, retention
games/<game-id>/live/<rel> Current SSOT save file(s)
games/<game-id>/history/<device-id>/<iso-ts>/<rel> Immutable snapshot
retired/<game-id>-<date>/… Soft-deleted games
  • <game-id>: lowercase slug, [a-z0-9-]+
  • <device-id>: stable per install (e.g. pc-desk, phone-pixel)
  • <iso-ts>: UTC YYYYMMDDTHHMMSSZ
  • <rel>: relative path using /, no .. segments

meta.json

{
  "schema": 1,
  "game_id": "elden-ring-seamless",
  "live_hash": "sha256:…",
  "file_checksums": {
    "ER0000.co2": "sha256:…"
  },
  "lease": {
    "holder": "pc-desk",
    "expires_at": "2026-07-14T03:00:00Z",
    "session_id": "uuid"
  },
  "versions_to_keep": 5,
  "updated_at": "2026-07-13T22:00:00Z",
  "updated_by": "pc-desk"
}
  • live_hash: SHA-256 of the sorted concatenation of path\\0hexdigest\\n for every live object (canonical tree hash).
  • lease may be null when idle.
  • Clients MUST treat unknown JSON fields as forward-compatible (ignore).

Lease rules

  1. Acquire before mutating native installs from SSOT (start session).
  2. Acquire succeeds if lease is null/expired OR holder equals this device.
  3. Default TTL: 6 hours (refreshable by same holder via start again).
  4. Another device MUST refuse start while lease is valid for a different holder.
  5. Release on successful end session (set lease to null). Crash: wait for expiry or operator clears via doctor --break-lease (destructive admin).

start_session

  1. Load meta.json (if missing, treat as empty live + null lease).
  2. Acquire lease (conditional overwrite: read-modify-write; if lost race, abort).
  3. Download all games/<id>/live/* objects.
  4. Install into configured native path(s) (create parents; overwrite existing).
  5. Persist local session state: { parent_live_hash, session_id, started_at }.

end_session

  1. Require local session state for game.
  2. Refuse if configured process/AppID still running (Linux); Android warns if user confirms force.
  3. Compute WIP tree hash from native files.
  4. Hash gate: if parent_live_hash != meta.live_hash and not force, abort (stale WIP).
  5. Upload snapshot to history/<device>/<ts>/….
  6. Upload/replace all live/… objects to match WIP (delete remote live keys no longer present).
  7. Update meta.json: new hashes, lease=null, updated_by=device.
  8. Prune: list history/<device>/; keep newest versions_to_keep; delete older prefixes.
  9. Clear local session state.

restore

  1. User selects device/ts history prefix explicitly.
  2. Acquire lease (or require idle + force).
  3. Copy history objects → live/.
  4. Update meta.json hashes; leave lease held by restoring device until they end or release.
  5. Never auto-restore.

Checksum algorithm

  • Per file: SHA-256 of raw bytes, encoded sha256:<hex>.
  • Tree hash: sort relative paths lexicographically (UTF-8), for each path append path + "\\0" + hex_digest + "\\n", then SHA-256 that byte string, encoded sha256:<hex>.

S3 addressing

  • Path-style: https://syncgames-s3.example.com/syncgames/games/...
  • Region can be us-east-1 dummy; path-style + custom endpoint required.
  • TLS required on WAN.

Error codes (CLI / UI mapping)

Condition Message key
Lease held by other lease_held
Hash gate fail stale_wip
Network / S3 store_error
Game still running game_running
Missing config config_error