Skip to content

Launch Wrapper

savevault-cli wrap and its silent sibling savevault-wrap run a game command with SaveVault wrapped around it: figure out which game is launching, optionally pull the latest cloud backup and restore the latest local backup first, launch the game and wait for it to exit, then back it up again and push to the cloud. You wire it into a launcher once per game (or once globally for Heroic and Lutris) and then just play.

savevault-cli wrap [--game <name>] [--restore <auto|ask|off>] [--no-sync] [--no-backup] [--no-notify] [--no-hotkey] [--gui] [--force] -- <command> [args...]
savevault-wrap [--game <name>] [--restore <auto|ask|off>] [--no-sync] [--no-backup] [--no-notify] [--no-hotkey] [--gui] [--force] -- <command> [args...]

Both binaries accept the same flags and run the same code. The difference is the console: savevault-cli wrap prints status and asks questions in the terminal; savevault-wrap never opens a console window, so it doesn’t leave a black window open for the whole play session when used from Steam launch options. On Windows, prefer savevault-wrap for launcher settings, and use savevault-cli wrap when running by hand or from scripts where you want to see output.

wrap never prevents or delays your game from launching because a SaveVault step failed. Game resolution, cloud sync, the post-exit backup, and notifications are all best-effort; if any fail, wrap warns (or logs, for savevault-wrap) and moves on. Even the one-time install scan a brand-new setup needs is never run before a launch — see first run.

There are exactly two deliberate exceptions, both about protecting you:

  1. You explicitly decline to launch an unrecognized game. That’s your “no”, not a SaveVault failure.
  2. The pre-launch restore fails. A half-restored save folder isn’t safe to play on, and the safety backup makes recovery possible, so wrap aborts rather than launching into a broken state.
  1. Resolve the game. If it can’t be resolved, see unrecognized game; every following step except the launch is skipped.
  2. Pre-launch cloud sync (skipped with --no-sync, or if cloud backup isn’t set up). Pulls your latest cloud backups down.
  3. Pre-launch restore (skipped for --restore off, if the game has no backups, if your local saves already match the latest backup, or if the latest backup was made on another OS). Runs after the cloud pull, so “latest backup” includes one that just arrived from another device.
  4. Launch the game and wait for it to exit.
  5. Post-exit backup (skipped with --no-backup). A single-game smart backup: a new archive only if something changed, with retention applied.
  6. Post-backup cloud sync (skipped with --no-sync). Pushes the new backup up.
  7. OS notification with the outcome (skipped with --no-notify, or if notifications are off).
  8. Exit with the game’s own exit code.

How SaveVault figures out which game this is

Section titled “How SaveVault figures out which game this is”

In order, stopping at the first match:

  1. --game <name> — matched exactly, then case-insensitively, then by a normalized title.
  2. Steam — Steam sets SteamAppId and SteamGameId in the environment for anything launched from your library; SaveVault reads both, and matches the id against each detected game’s Steam app id.
  3. Lutris — Lutris exports GAME_NAME and lowercase game_name; SaveVault reads both, and GAME_DIRECTORY breaks ties.
  4. Heroic (GOG titles) — Heroic’s HEROIC_APP_NAME is a GOG product id when the runner is gog, which the game database already carries per title. Heroic’s other stores fall through to the next step.
  5. Executable path — every token of the wrapped command that exists on disk is checked against every detected game’s install directory; the longest match wins. This launcher-agnostic fallback is why Heroic’s wrapper setting, Playnite’s play action, and a plain desktop shortcut all work. A tie between two games counts as unresolved rather than a guess.

Steps 1–4 match against games SaveVault has detected on this machine, which a game you’ve never run hasn’t been. So each step gets a second try directly against the game database: a Steam app id (including alternate SKU ids), a GOG id, or a normalized title. A hit there is treated as fully known — no dialog. After you quit, SaveVault does a targeted check of just that title’s save locations; if the session wrote a first save, you get the normal post-exit backup. A full scan later (or just launching again) picks the game up as fully detected from then on.

If nothing resolves, you get a say, unless you’ve opted out:

Situation What happens
--force Skip the question; launch unprotected.
A terminal is attached, or --gui was passed “Launch without protection?” Default answer is yes. Declining aborts without launching (exit code 3).
Headless, no --gui, no terminal OS notification, then launch anyway. Blocking an invisible launch would just hang it.

savevault-wrap has no console, so it always behaves as if --gui were passed.

Controlled entirely by --restore (default ask). There’s no persisted setting for this: every launcher already keys its wrapper invocation per game, so the value in that game’s launch string is the per-game setting.

  • off — never restore before launching.
  • auto — restore the latest backup, then launch. The pre-restore safety backup is always on here, so an automatic restore is always undoable.
  • ask — ask first. On a terminal or with --gui the question defaults to no, since restoring is the more consequential choice. Headless without --gui: skip the restore and send a notification explaining why, rather than restoring without consent.

If the game has no backups yet, or local saves already match the latest backup, the prompt is skipped entirely — no question, no UI. The comparison is against the archive itself, so a newer backup that cloud sync just pulled in from another device is still offered even when nothing changed locally. A restore failure aborts the launch (exit code 4) and tells you your previous saves are safe in the safety backup.

Three more rules keep a handoff between devices safe:

  • “Latest” never means a pre-restore safety backup. Those are dated at the restore they protected, so restoring one would undo it.
  • A latest backup that misses another device’s newer progress is never restored without asking. SaveVault follows each backup’s lineage, so it can tell when the PC backed up an old save after the Deck’s session. Then auto behaves like ask, the question says whose progress would be missing, and with no terminal or dialog to ask on, wrap launches without restoring and sends a notification. See multiple devices.
  • A backup from another operating system is never restored here, in any mode. Saves don’t always carry over between Windows, Linux, and macOS, and where the files should land is worth checking first. wrap launches without restoring and tells you so; review the plan and restore from the Restore page instead.

--force makes an effective ask behave as off. Force means “don’t ask”, not “take the riskier action”. It never touches an explicit auto or off.

Resolving a game needs SaveVault’s install-scan cache. The first time wrap runs on a machine, or any time the cache is stale (database updated, roots changed, exclusions changed), a cold scan can take minutes. Blocking the launch on that would break fail-open, so instead:

  • The game launches immediately, with no pre-launch sync, no restore prompt, and no “unrecognized game?” question. One non-blocking notification says SaveVault is scanning in the background.
  • The scan runs during your session, and the cache is saved as soon as it finishes.
  • Once you quit, SaveVault resolves the game against the fresh results (--game still takes priority) and runs the normal post-exit steps. If it still can’t identify the game, or the scan fails, you get a notification saying so.

Every later launch, including a second launch of the same game, uses the fast path. If the game session ends before the scan does, the wrapper stays alive until the scan finishes so it can still back the session up; Steam shows the game as “Running” during that window. If you need the very first launch on a fresh machine to also pull and restore a cloud backup, run savevault-cli scan once first (or open the GUI once).

The happy path — a recognized game, nothing to restore — shows nothing before the game launches and at most one OS notification after it exits.

  • savevault-cli wrap in a terminal: plain [Y/n] prompts and status lines.
  • --gui, or savevault-wrap: native OS dialogs — a Win32 message box, an AppKit alert, or an XDG portal / zenity window on Linux. The default button is always the “just play” choice: Play Anyway for an unrecognized game, Play Without Restoring for the restore question. If a dialog can’t be shown at all, SaveVault falls back to that same safe default plus a notification rather than hanging.
  • Headless, no --gui, no terminal: no UI before the game starts; a toast covers anything that needed attention.

Terminal prompts are English-only like the rest of the CLI; the native dialogs and notifications follow SaveVault’s language setting.

In Desktop Mode dialogs work like anywhere else. In Game Mode (gamescope), whether a dialog can render depends on whether a portal service or zenity is reachable in that session; if not, SaveVault falls back to notification plus safe default. For guaranteed zero-dialog behavior in Game Mode, make sure the game resolves (Steam’s own SteamAppId does this for anything in your library) and leave --restore at ask — with nothing new to restore, no question ever appears.

Checkpoint hotkey during a wrapped session

Section titled “Checkpoint hotkey during a wrapped session”

Once wrap resolves the game, it arms the same checkpoint action Live Protection uses: the keyboard combo (live.hotkey, default Ctrl+Shift+S) and the controller chord (live.controller_hotkey, default L3+R3) both take a pinned backup archive of the game you’re playing, labeled “Hotkey checkpoint”. It’s tolerant of files the game still has open, and repeated presses within a few seconds coalesce into one checkpoint.

This works whether or not Live Protection is enabled or the GUI is running:

  • If a running GUI’s Live Protection engine already holds the keyboard combo, wrap writes a small session marker and the engine checkpoints your wrapped game on each press.
  • If nobody holds the combo, wrap registers it itself for the session.
  • On desktop the engine owns the controller chord while it’s running and wrap polls only when it isn’t. In Steam Game Mode this inverts: a backgrounded GUI can’t hear the controller under gamescope, so wrap always polls during its session.
  • --no-hotkey skips wrap’s own keyboard hotkey thread and controller-chord poller for one session. The session marker is still written, so a running GUI Live Protection engine still checkpoints the wrapped game.

If the checkpoint hotkey or chord does nothing during a wrapped session, two small marker files in the data directory show who is covering it. wrap-session.json exists only while a wrap session has a resolved game; it names that game and the wrap process that owns the session, and wrap removes it when the game exits. live-engine.json exists only while the GUI’s Live Protection engine is running, so a GUI window that’s open with Live Protection off has none. A leftover file from a crash is ignored once the process that wrote it is gone, and it’s safe to delete.

A deferred first-session launch (cold cache) can’t arm either trigger, because the game isn’t known until after you quit. On Linux, reading a controller in the background needs your user in the input group (already true on SteamOS); see known issues.

Flag Meaning
--game <name> Skip inference; use this game.
--restore <auto|ask|off> Pre-launch restore behavior (default ask).
--no-sync Skip both cloud sync steps.
--no-backup Skip the post-exit backup.
--no-notify Suppress routine notifications. A restore failure still notifies, since headless that’s the only way you’d learn why the game didn’t start.
--no-hotkey Don’t start wrap’s own checkpoint hotkey thread or controller-chord poller for this session. The session marker is still written, so a running GUI engine can still checkpoint the game.
--gui Native dialogs instead of terminal prompts. Implied for savevault-wrap.
--force Never ask: launch an unrecognized game unprotected, and treat an effective ask as off. Doesn’t skip auto’s safety backup or the restore-failure abort.
-- <command> [args...] The game command. Required; everything after -- is passed through untouched, so Steam’s %command% works directly.

wrap exits with the game’s own code once the game has actually run, whatever happened in any SaveVault step around it. Only when the game never launched:

Code Meaning
1 The command couldn’t be spawned (bad path, not executable).
2 No command given after --.
3 You declined to launch an unrecognized game.
4 The pre-launch restore failed; your previous saves are in the safety backup.

On Unix, a game killed by a signal follows the usual 128 + signal convention.

Windows — right-click the game → Properties → Launch Options:

"C:\Path\To\savevault-wrap.exe" -- %command%

If SaveVault itself is installed through Steam, the wrapper is inside its install folder:

"C:\Program Files (x86)\Steam\steamapps\common\SaveVault\savevault-wrap.exe" -- %command%

Linux / SteamOS — same idea, with savevault-wrap on your PATH or given a full path. A Steam-installed SaveVault on Linux keeps one install tree per architecture, each with the wrapper under usr/bin: x86_64/ for desktop Linux, the Steam Deck, and the Steam Machine, aarch64/ for the Steam Frame:

savevault-wrap -- %command%
"$HOME/.local/share/Steam/steamapps/common/SaveVault/x86_64/usr/bin/savevault-wrap" -- %command%

On a Steam Frame, use the aarch64 tree instead:

"$HOME/.local/share/Steam/steamapps/common/SaveVault/aarch64/usr/bin/savevault-wrap" -- %command%

This works in Game Mode, where the GUI isn’t running: wrap is a standalone process. Add flags before the --, e.g. savevault-wrap --restore auto -- %command%.

Per game, Settings → Advanced → Wrapper command — point it directly at the binary:

C:\Path\To\savevault-wrap.exe
/full/path/to/savevault-wrap

Heroic passes the game’s own launch command as arguments automatically; don’t add -- %command% here. GOG titles resolve through Heroic’s environment variables; other stores resolve by executable path.

Preferences → Global options → Advanced → Game execution → Command prefix applies to every game:

savevault-wrap --

Lutris identifies the game via GAME_NAME, so this one global setting covers your whole Lutris library. Per-game overrides use the same field under that game’s Configure → System options if you want a different --restore mode for one title.

Playnite has no single generic wrapper setting. Two options:

  • If you can edit the game’s Play Action (most manually-added games): set the action’s path to savevault-wrap.exe and put the real executable plus its arguments in the arguments field.
  • Otherwise, per-game scripts (Edit → Scripts): an after-game script running savevault-cli backup --games "Game Name" covers the post-exit backup. Playnite’s before-game hook doesn’t wait for the game to exit, so it can’t drive the interactive restore prompt; restore manually or rely on Live Protection instead.
  • Detached launchers. wrap waits on the command it launched. Some launcher-generated commands (Legendary is a known case) hand off to a detached process and return immediately, so the post-exit backup would fire too early. Where a launcher offers its own after-game hook, that’s more reliable for that launcher.
  • Heroic non-GOG stores resolve by executable path rather than Heroic’s environment variables.
  • First session on a cold cache gets post-exit protection only: no pre-launch restore or cloud pull, and no checkpoint hotkey, for that one launch.