OpenSave syncs your game saves between devices, peer-to-peer — no Steam required, no accounts, no subscriptions. Point it at a folder, pair your devices, and your saves follow you everywhere.
A complete Go rewrite of the original Node.js/Electron app: one small native binary, no runtime to install, and wire-compatible with existing peers.
Install · Quick start · Screenshots · How it works · CLI · Self-host the relay · FAQ · Discord
Steam Cloud only covers games bought on Steam — and only when the developer opts in. Everything else (emulators, GOG, Epic, single-player games with no cloud support) is on you: manually copying save folders between your desktop, laptop, and Steam Deck, and hoping you grabbed the newest one.
OpenSave gives every game the Steam Cloud experience:
- You own it. Saves sync directly between your devices. No account to create, nothing stored on someone else's server.
- It's automatic. Auto-detects hundreds of games, watches for changes, and syncs the moment a save is written.
- It's safe. Every change is snapshotted and reversible — a full version history you can roll back, not just the latest copy. Conflicts are detected and resolved without silently clobbering a playthrough.
- Your devices don't have to be on at the same time. Connect your own Google Drive, Dropbox, OneDrive, WebDAV server or NAS folder, and a save made on one device reaches the next one you open, even with the first switched off. Still no account with us.
- Auto-detection — scans for saves from Steam, emulators (RetroArch, Dolphin, Ryujinx, Yuzu, Citra, PCSX2, RPCS3, PPSSPP, Cemu, Xenia), Steam-emulator repacks (Goldberg/GSE, CODEX, RUNE, Tenoke, EMPRESS, Online-Fix, CPY, SKIDROW, 3DM, …), Epic, GOG, Unity
LocalLow, and Unreal Engine conventions — plus the community-maintained Ludusavi manifest covering save paths for tens of thousands of games, whatever store (or no store) they came from. Heroic, Lutris and Bottles prefixes are found on any drive, SD card included, and a newly installed game is noticed in the background — pointed out, never tracked without asking. - Track anything — any folder or single save file, watched live with block-level change detection (SHA-256, 64 KB–2 MB adaptive blocks). Only the blocks that changed are ever transferred.
- One tile per game, not one per folder — a scan finds the same game in several places as a matter of course, and every result shows its file count, size and last-written date so you can tell the save you play from the one an old install left behind. Folders holding no files are hidden. Where a game's save is genuinely split across sibling folders, they are offered together as one game.
- A save split across folders is one game — some titles keep progress in one place and settings or mods in another. Add each folder as a save location and all of them sync, snapshot and restore together.
- Files that shouldn't sync — device-specific settings living beside the save can be excluded per game, written like a
.gitignore, or picked from a list of what is actually in the folder. Excluded files are still captured in every snapshot, so a rule can never be the thing that loses one. - P2P sync — automatic over LAN (zero-config discovery) or across the internet through a relay room code — no port forwarding. A paired-device model means every connection is explicitly approved.
- Snapshot history — every change creates a versioned snapshot, compressed where that helps. Roll back a whole save or a single file; branches keep parallel playthroughs (and conflict resolutions) safe.
- Smart conflict handling — diverged saves are detected by sync lineage, not wall-clock timestamps. Keep yours, keep theirs, or keep both on a new branch.
- Cloud backup and hand-off — optional mirroring to Google Drive, Dropbox, OneDrive, WebDAV, a webhook, or a local/NAS folder. Each device also reads the others' backups: a newer save that carries on from yours is brought over, and one that would replace progress made here is asked about first. Any OAuth provider can use your own app credentials instead of the built-in ones — required for OneDrive, and the fix for Google Drive's weekly re-login.
- Cross-device game matching — the same title tracked under different names on two machines (a Steam install here, a differently-named folder there) can be matched by Steam App ID or linked by hand. App-ID matching is opt-in, so two separate copies of a game are never merged without asking.
- A full command line —
opensavedoes everything the app does, for a Steam Deck in Game Mode or a headless server. See Command line. - In-app updates — one-click update from GitHub releases, pull a newer build straight from a paired device, or
opensave updatefrom the terminal. - Privacy-first — no accounts, no telemetry. As of v2.4.0, saves sent through the relay are encrypted end to end between your own two devices: the relay routes frames it cannot read and writes nothing to disk. You can self-host it so nobody else is on the path at all.
| Platform | Download | Run |
|---|---|---|
| Windows | OpenSave.Setup.exe (installer) or portable OpenSave.exe |
Double-click |
| Linux | opensave-linux-amd64.tar.gz |
extract, then ./opensave |
| Steam Deck / SteamOS | OpenSave.flatpak |
see Steam Deck install |
| macOS (2.4.0 onward) | OpenSave-macos.dmg, or opensave-macos-arm64.tar.gz / -amd64 for the CLI |
open the .dmg and drag OpenSave to Applications — it isn't signed yet, so macOS asks you to allow it under Privacy & Security |
Grab the latest from the Releases page.
macOS says OpenSave "is damaged and can't be opened"? That is 2.4.0-beta.2, whose Mac app really was broken: its Info.plist was not valid, and nothing in it was signed. Nothing you can do opens it — download a later build, which macOS asks about instead; allow it under Privacy & Security.
Use
OpenSave.flatpak, notopensave-linux-amd64.tar.gz. The tarball's desktop app will not start on a stock Deck: SteamOS ships no WebKitGTK, which it needs to draw its window. This trips people up because the tarball's name reads like the Steam Deck build.
The Flatpak is the build that works on a stock Deck — SteamOS ships no WebKitGTK and wipes manually-installed system packages on OS updates; the Flatpak bundles everything and survives updates.
- Switch to Desktop Mode (Steam button → Power → Switch to Desktop).
- Download
OpenSave.flatpakfrom the Releases page. - Double-click it to install via Discover, or run
flatpak install --user OpenSave.flatpakin Konsole. - Launch OpenSave from the application menu. Optional: add it to Steam (right-click → Add to Steam) to open it from Game Mode.
If you only want background syncing and no window, the command line has no such constraint — it needs no WebKitGTK and runs anywhere:
curl -fsSL https://opensave.org/install.sh | sh
opensave scan && opensave service install
sudo loginctl enable-linger $USERSaves on the SD card are found automatically (/run/media is visible to
the app), and Proton game saves are detected inside their compatdata
prefixes. The plain Linux tarball also works on the Deck if you install
webkit2gtk-4.1 yourself, but SteamOS updates can remove it — the
Flatpak is the supported path. A Decky plugin for Game Mode lives in
opensave-decky-plugin/.
Other handhelds / Arch-based distros (CachyOS, Bazzite-likes): if
your distro is not immutable (CachyOS isn't), the plain Linux tarball
with your distro's webkit2gtk-4.1 package is the best install — it
survives updates and uses your native graphics stack. The Flatpak is
for immutable systems like stock SteamOS.
Troubleshooting:
- "runtime org.gnome.Platform … not found" during install — your
flatpak user installation doesn't have Flathub configured yet:
flatpak remote-add --if-not-exists --user flathub https://dl.flathub.org/repo/flathub.flatpakrepothen install the bundle again. - The app must be launched in Desktop Mode (or added to Steam to run from Game Mode) — running it from a bare terminal session shows no window.
Upgrading from the original (JS) OpenSave? Your data migrates automatically on first launch — tracked games, snapshots, pairings, and cloud settings are imported from
~/.opensave/opensave-db.json(kept as a backup, never deleted). Go and JS devices can pair and sync with each other during the transition.
- Launch OpenSave on your first device. It scans for installed games and shows one cover-art tile per game, with what each folder holds and when it was last written.
- Track a game — click a tile, or add any folder / save file manually. A game found in several places says found in N folders; open that to see which is which.
- Pair a second device. On the same network, the other device appears automatically under Devices — approve the request. Remote? One device creates a room code under Internet Sync; the other joins with it.
- Play. When a save changes, OpenSave snapshots it and syncs it to every paired device. There's nothing else to do.
Need to undo something? Open a game's history and roll back a snapshot — the whole save or a single file.
New to this? The Getting Started guide walks through the whole thing from a fresh install, in plain language, with what to do when something looks wrong.
Device A Device B
┌──────────┐ LAN (auto-discovery) ┌──────────┐
│ watcher │◀───────────────────────▶│ watcher │
│ snapshot │ │ snapshot │
│ delta │ WAN via relay room │ delta │
└────┬─────┘ (TLS to the relay) └────┬─────┘
│ ┌───────────┐ │
└───────────▶│ relay │◀───────────┘
│ (routes, │
│ no data) │
└───────────┘
- Watch — a filesystem watcher notices a save was written (safe-write and file-lock aware, so it never grabs a half-flushed file).
- Delta — the save is chunked into content-defined blocks and SHA-256 hashed. A manifest diff finds exactly which blocks changed.
- Snapshot — the new state is recorded as an immutable, versioned snapshot on a branch.
- Sync — only the changed blocks travel to paired peers, over LAN when possible or through a stateless relay room otherwise. Lineage metadata lets the receiver detect a genuine conflict versus a fast-forward.
opensave is a complete client, not a companion to the app: auto-detect saves,
pair devices, sync, resolve conflicts, manage snapshots and branches, back up
to the cloud, and run as a background service. A headless box — a NAS, a home
server, or a Steam Deck that lives in Game Mode — never needs the desktop app.
No account, no token, no server to sign up to.
Run opensave on its own and it tells you what is happening right now, and what
to do next.
→ Full CLI guide — the daemon model, which machine each command belongs on, and worked sequences for pairing, internet sync, running headless, snapshots, split saves and scripting. The tables below are the summary; that is the walkthrough. There is also one on the website.
Linux & Steam Deck
curl -fsSL https://opensave.org/install.sh | shWindows (PowerShell)
irm https://opensave.org/install.ps1 | iexInstalls to your user folder — no root, no admin — puts it on your PATH, and
shows the status panel when it is done. Downloads are verified against the
SHA256SUMS published with each release; piping a script into a shell is enough
trust on its own.
Three names, one program: opensave, os as a short alias, and
opensave-cli (the name the Steam Deck plugin and the Linux packages use
internally). If something on your system already answers to os, the installer
leaves it alone and says so.
To choose where it lands or pin a version:
OPENSAVE_INSTALL_DIR=/usr/local/bin OPENSAVE_VERSION=v2.2.0 sh install.shTo remove it, the same script reverses exactly what it and opensave service install put down — binaries, aliases, the service, the autostart entry, the
PATH line — and leaves your settings and backups where they are:
curl -fsSL https://opensave.org/install.sh | sh -s -- --uninstallAdd --purge to delete ~/.opensave as well; it says what it is about to
delete, counts your backup files, and asks you to type a confirmation first.
On Windows, opensave install --uninstall does the same for what
install.ps1 put down — the binary, the os and opensave-cli shims, and the
PATH entry — and never touches your data. The desktop app's own uninstaller
offers to run it for you.
Or build it: go build -o opensave ./cmd/opensave-cli
opensave update # replace this binary with the latest release
opensave update --check # just report whether a newer one existsPre-releases are never offered automatically — install those yourself from the releases page.
opensave scan # what is on this machine
opensave add "Elden Ring" ~/.local/share/EldenRing
opensave daemon start & # the sync service
opensave pair 192.168.1.42 # pair another device on the LAN
opensave sync --allDifferent networks instead of a LAN? Run opensave relay join <code> with the
same made-up code on both devices — no port forwarding. As of v2.4.0 the relay
passes data it cannot read: saves are encrypted end to end between your two
devices, and nothing is stored on the way.
opensave service install
systemctl --user enable --now opensave-daemon
sudo loginctl enable-linger $USER # Steam Deck: survive Game Mode switchesThat last line matters on a Deck. Without it SteamOS stops your background services the moment you switch to Game Mode — which is exactly when you want syncing to be happening.
Every command accepts --json for scripting.
Games
| Command | What it does |
|---|---|
scan [--all] |
Auto-detect saves: Steam libraries, Proton and Wine prefixes, emulators, 20k+ titles via the Ludusavi manifest. Grouped one game at a time, with each folder's size and last-written date. Empty folders are hidden; --all shows them |
add <name> <path> |
Track a save folder or file |
add <number> |
Track the nth result from the last scan |
remove <gameId> |
Stop tracking. Save files and snapshots stay on disk |
untrack-all --yes |
Stop tracking everything (snapshots are kept) |
game <gameId> set <key> <value> |
Per-game settings: path, app-id, exe-path, cover-url, auto-sync, max-snapshots |
locations <gameId> [add|remove] |
A game's extra save folders, for a save split across more than one place |
ignore <gameId> [add|remove|clear|test] |
Files that shouldn't sync, written like a .gitignore. test answers "would this sync?" |
launch <gameId> |
Start the game |
status |
Tracked games, branches, peers |
Sync & devices
| Command | What it does |
|---|---|
sync [<gameId>|--all] |
Sync now; everything by default |
peers |
Paired devices, devices found on this network, pending requests |
pair <host[:port]> |
Ask a device on the LAN to pair |
pair requests |
Show incoming requests, and which device sent them |
pair approve|reject <peerId> |
Answer one |
unpair <peerId> |
Drop a paired device |
probe <host[:port]> |
Check whether a device answers — works before pairing |
forget <peerId> |
Remove a stale device record |
relay join <code> |
Sync across networks — same code on each device |
relay status|leave |
Show or leave the relay room |
conflicts |
Saves that diverged and are waiting on a decision |
resolve <gameId> <choice> |
keep-both (safest), keep-local, keep-remote |
History
| Command | What it does |
|---|---|
snapshot <gameId> [comment] |
Snapshot the current save |
snapshots <gameId> |
List snapshots, newest first |
rollback <gameId> <snapId> |
Restore a snapshot |
branch <gameId> <name> |
Create a branch for a parallel playthrough |
checkout <gameId> <name> |
Switch branch |
branch-delete <gameId> <name> |
Delete a branch and its snapshots |
snapshot-delete <gameId> <snapId> |
Delete one snapshot |
prune [--apply-default] |
Apply snapshot retention limits now |
files <gameId> <snapId> [path] |
List a snapshot's contents, or restore a single file from it |
export <gameId> <dir> |
Copy the save out exactly as the game wrote it — no archive, no wrapper |
backup export|import <file.sscb> |
Portable backup archive |
Cloud backup
| Command | What it does |
|---|---|
cloud status |
Provider and connection state |
cloud browse |
Everything stored in the cloud |
cloud list <gameId> |
Cloud snapshots for one game |
cloud push <gameId> |
Upload local snapshots |
cloud restore <gameId> <file> |
Pull one back |
cloud delete <gameId> --yes |
Remove a game's cloud copies |
Google Drive, Dropbox and OneDrive need a browser to grant consent, so those are connected once in the desktop app. WebDAV, webhook and local/NAS providers work entirely from the terminal.
Configuration
| Command | What it does |
|---|---|
config [list] |
Show settings |
config set <key> <value> |
device-name, match-by-app-id, snapshot-limit, relay-url |
scanpath list|add|remove <path> |
Extra folders for auto-scan to check |
exclude list|add|remove <path> |
Folders auto-scan should skip |
link <gameId> <otherId> |
Treat two tracked games as the same game |
unlink <aliasId> / links <gameId> |
Undo a link / show linked ids |
Service
| Command | What it does |
|---|---|
daemon start [--port N] |
Run the daemon in the foreground |
daemon status / daemon stop |
Check on, or stop, a daemon started by the CLI |
service install|uninstall|status |
Manage the systemd --user unit (Linux) |
completion bash|zsh|fish |
Shell completion script |
upnp <port> [--delete] |
Forward a router port via UPnP |
update [--check] |
Update this binary from the latest release |
version |
Print the version |
opensave daemon status --json | jq .gameCount
opensave snapshots elden-ring --json | jq -r '.[0].id'
opensave conflicts --json | jq 'keys'
# Scan results carry what is in each folder and which rows are one game.
# Everything found in more than one place:
opensave scan --json | jq -r 'group_by(.groupId)[] | select(length > 1) | .[0].name'
# Track the freshest folder of every game found, by its listed number:
opensave scan --json | jq -r 'to_entries[] | select(.value.role=="primary") | .key + 1'scan --json lists results in the same order the printed listing numbers them,
so index n is what add n tracks. Each row carries fileCount,
totalBytes, latestMtime and measured, plus groupId and role
(primary, location, alternative, inside, only) saying which rows are
one game and what each folder is. measured: false means the folder could not
be read — not that it is empty.
Failures exit non-zero and, with --json, print {"error": "..."}.
Full details: man opensave (shipped in the Linux tarball), or
packaging/man/opensave.1.
The daemon exposes a local REST + WebSocket API (P2P on port 8383) that the
desktop UI, the CLI and the Steam Deck plugin all drive, so anything the app
can do is scriptable.
# Desktop app (needs Go 1.26+, Node 18+, and the Wails CLI)
go install github.com/wailsapp/wails/v2/cmd/wails@latest
cd cmd/opensave-app && wails build
# Headless daemon + CLI
go build ./cmd/opensave-cli
# Relay server (self-host)
go build ./cmd/opensave-relayRun the test suite:
go test ./... -timeout 2700s # everything
go test ./e2e/... -timeout 2700s # end-to-end pairing & sync tests onlyThe timeout is not optional. e2e drives real daemons over real HTTP with
real file watching, and a large part of its ten minutes is spent deliberately
waiting for background work to settle. That is comfortably past Go's default
of ten minutes per package, and overrunning it prints a goroutine dump that
reads like a crash rather than a clock running out. CI uses the same 2700s.
The relay is stateless — it brokers room codes and proxies OAuth, and writes no save to disk. As of v2.4.0, what it forwards is encrypted end to end: saves are sealed with a key only your two devices hold, so a relay operator cannot read what passes through. What a relay can still see is that two devices are talking, roughly how much data moves, and which games by id. Ours is wss://relay.opensave.org; run your own to keep even that to yourself:
./opensave-relay # listens on :8386
PORT=10000 ./opensave-relay # custom port
docker build -f relay/Dockerfile . # or as a containerPoint Internet Sync → Relay server (self-hostable) at your instance, on each device, then join the same room code on all of them. opensave upnp 8386 forwards the port on UPnP-capable routers.
The relay itself never joins a room — it has no such command, and nothing to configure beyond the port. Rooms come into existence when your devices ask for them. Full walkthrough, including TLS and the reverse-proxy settings WebSockets need: docs/RELAY.md.
cmd/opensave-app Wails desktop app (daemon embedded + Svelte UI)
cmd/opensave-cli Headless daemon & CLI
cmd/opensave-relay Stateless WAN relay (room broker + OAuth proxy)
internal/
store SQLite persistence + legacy JSON import
delta Block hashing, manifest diff, patching
snapshot ZIP snapshots, branches, retention
watcher Save-change detection (safe-write aware, lock guard)
p2p Discovery, pairing, sync engine, LAN/WAN transports
cloud Backup providers + PKCE OAuth
presets Game / emulator / store save-location detection
api Local REST + WebSocket dashboard API
daemon Long-running service orchestration
sysintegration Tray, notifications, autostart
opensave-decky-plugin Steam Deck Game Mode plugin (Decky Loader)
The daemon speaks the same REST/WebSocket API and P2P wire protocol as the original JS app, so old and new versions interoperate during a rollout.
Everything lives under ~/.opensave/:
| Path | What |
|---|---|
opensave.db |
SQLite store — tracked games, snapshots, pairings, settings |
snapshots/ |
Versioned save snapshots |
opensave.log |
Activity log for diagnostics |
opensave-db.json |
Legacy JS database (kept as an import backup) |
No accounts, no telemetry, no analytics. See PRIVACY.md for the full statement.
Do I need a server or an account? No. Devices sync directly. The optional relay only matters for syncing across the internet, and you can self-host it.
Is my data encrypted in transit? Over the relay, yes — end to end as of v2.4.0: save data is sealed with a key derived from the two devices' own keys when they paired, so neither the relay nor anyone else in your room can read it. Both devices need v2.4.0 or later, and an internet pairing made on an earlier version has no key to use — unpair and pair those two again to protect them. The app shows each device's state under Devices.
LAN sync is direct, involves no relay, and is not encrypted: anything on the same network can read a save as it transfers. Treat a network you do not control as one that can see your saves.
What if two devices change the same save while offline? OpenSave detects the divergence by sync lineage and asks you to keep yours, theirs, or both (on a new branch). It never silently overwrites.
Does it work with non-Steam or emulated games? Yes. If it writes a save to disk, OpenSave can track it — Steam, emulators, GOG, Epic, and repacks are auto-detected; anything else you can add by path.
Can old (JS) and new (Go) versions talk to each other? Yes, during the transition. They share the same wire protocol and your data migrates automatically.
Issues and pull requests are welcome. Please run go test ./... -timeout 2700s before opening a PR (see Build from source for why the timeout is needed), and keep changes focused. For larger features, open an issue first so we can align on approach.
- Getting Started — start here if OpenSave is new to you. The whole thing from a fresh install, in plain language
- User Guide — the reference: every feature, what each setting does, troubleshooting
- Command line — the CLI end to end: the daemon model, which machine each command runs on, task-by-task sequences, scripting
- Running your own relay — self-hosting, TLS, and the reverse-proxy settings WebSockets need
- Changelog — release notes
- Privacy — what OpenSave does and doesn't do with your data
MIT — retains the original author's copyright and credits the Go rewrite.




