Skip to content

Archive Format

Every SaveVault backup is a standard compressed archive you can open with any tool, and every one carries enough information to restore it without SaveVault installed. This page is the user-facing summary of the format; the normative spec and the code that writes it live in the app repository.

  • Single-game. One archive holds exactly one game’s files.
  • Self-describing. Every archive embeds savevault-manifest.json (machine-readable index) and README.txt (human restore instructions). Neither SaveVault’s database nor the filename is needed to restore.
  • Atomic. Archives are written to <name>.part and renamed on success, so a file with an archive extension is never half-written.
  • Portable. File paths are stored under placeholder tokens that a restore re-resolves against the destination machine.
<SanitizedGameTitle>_<YYYY-MM-DD_HH.MM.SS>_<device-tag>.<ext>

The timestamp is local time on the source machine; the manifest’s created_at (UTC) is the authoritative creation time. The device tag is the writing device’s name, lowercased with punctuation turned into -, plus the first four characters of its id — for example steam-deck-oled-3f9a — so devices sharing one folder never write the same filename in the same second. Archives from older versions or other tools omit the tag. A second backup of one game on one device within the same second gets a -2, -3… suffix; an existing archive is never replaced.

Title sanitization is lossy, so the filename is not an identity — always trust the manifest. Pre-restore safety backups use <Title>_pre-restore_<timestamp>[_<device-tag>][-N].<ext> (-N is the same collision suffix, -2, -3…); the _pre-restore_ marker is how SaveVault keeps them from ever counting as “the latest backup”. Live snapshots live under <backup_dir>/snapshots/<Game>/.

Written Also accepted on read
.zip (default), .tar.gz, .tar.zst .tgz, .tar.zstd

Zip entries opt into Zip64, so single files over 4 GiB are supported. Entry paths always use forward slashes. Symlinks are never written and are skipped on extraction.

<Slug>_<timestamp>_<device-tag>.<ext>
├── README.txt # human restore instructions
├── savevault-manifest.json # machine-readable index
├── data/
│ ├── saves/<placeholder>/<original-tail>/...
│ └── configs/<placeholder>/<original-tail>/...
└── registry/
├── windows/<saves|configs|shared>/<sanitized-key>.reg
└── proton/<appid>/<saves|configs>/<hive-file> # e.g. user.reg

Saves and configs are kept apart so a restore can bring back one without the other (--include saves|configs|both). Windows registry exports are plain .reg files; Proton hives keep their original names.

The first segment after data/saves/ or data/configs/ names the well-known directory the files came from. Restore resolves it on the destination machine, which is why a backup taken under one user profile restores cleanly under another.

Token Windows Linux / macOS
appData %APPDATA% $XDG_CONFIG_HOME (~/.config)
localAppData %LOCALAPPDATA% $XDG_DATA_HOME (~/.local/share)
localAppDataLow %USERPROFILE%\AppData\LocalLow — (except a Unity <Company>/<Product> folder, which maps into the game’s Steam Android container on a Steam Frame)
documents %USERPROFILE%\Documents ~/Documents
savedGames %USERPROFILE%\Saved Games —
home %USERPROFILE% ~
publicDir %PUBLIC% —
programData %ProgramData% —
winDir %SystemRoot% —
winDrive winDrive/c/Games/X → C:\Games\X —
xdgConfig %APPDATA% $XDG_CONFIG_HOME
xdgData %LOCALAPPDATA% $XDG_DATA_HOME
gameInstall the game’s install directory same
absolute no standard directory matched; the literal source path is in the manifest same

A token marked — is unresolvable on that OS. Restore then skips that root with an explanatory note rather than guessing; you can --remap-root it, opt in to --cross-platform, or use --redirect-wine to send it into a Proton prefix.

savevault-manifest.json records the producer version, creation time, source platform, the game’s identity (name, Steam id, cloud providers, install hint), one entry per root (id, kind, placeholder, original absolute and portable paths), and one entry per file (root, in-archive path, relative path, size, BLAKE3 hash, modification time), plus any registry rows, pin state, and label.

Three optional fields support multiple devices sharing a folder. source_device holds the writing device’s id (random, generated once per install) and display name. archive_id is a random id for this archive, and based_on is the archive_id the saves descended from — the archive the device last restored, or its own previous backup. Following based_on links is how SaveVault tells whether the newest backup includes another device’s progress. Other tools producing archives should omit source_device and based_on rather than invent values.

Restore verifies each file’s BLAKE3 hash before writing it and restores modification times. savevault-cli verify checks the whole archive against these hashes at any time. Root ids are what restore --remap-root <id>=<path> refers to; explain prints them.

Pre-restore safety archives also carry an undo_delete list: files the guarded restore created, each with its hash. Restoring the safety archive deletes those files — but only while their bytes still match, so a save you progressed since is never deleted.

Extract the archive and follow README.txt inside it. It maps each placeholder folder to the real directory on each OS and includes copy-paste PowerShell and bash commands plus registry import steps. That file is generated by the same code that writes the archive, so it’s always accurate for the archive it ships in.

SaveVault treats the backup folder as the source of truth. Any file at the top level of backup_dir (subfolders aren’t scanned) is adopted into the library automatically when its extension is one of the accepted formats and savevault-manifest.json parses from the archive root. Adopted archives take their game name, creation time, pin state, label, and source device from the manifest. Retention is counted per device: each device prunes only the archives it wrote plus unattributed ones, never another device’s.

This is how backups copied from another machine, pulled down by cloud sync, or produced by third-party converters show up in SaveVault with no import step. If you produce archives yourself, preserve the original backup time in created_at (retention orders by it), write atomically under a non-archive name and rename, and include README.txt (explain requires it).

Name Meaning
.savevault-check-access rclone bisync access marker — never delete.
.savevault-scheduled-running A scheduled pass is mid-flight.
*.part An archive being written; ignored by the library.
.sv-manifest-*.zip Pin/label metadata repack in progress.
.savevault-manifest-*.json, .savevault-readme-*.txt Per-backup staging temps (the manifest and README being built); not part of the pin/label repack.
snapshots/ Live Protection snapshots, with their own index. Device-local: excluded from cloud sync.
*.enc Reserved extension. SaveVault never produces it, and any library rows for it are dropped; don’t create files with it.