← back

Designing a Durable Save Backup on Steam Deck

Abstract

After spending dozens of hours in The Legend of Zelda: Breath of the Wild, I wanted stronger guarantees around save durability than simply trusting a single SSD.

The goal was not merely to back up save files. The goal was to recreate, for an emulator-based game, the confidence that Steam Cloud provides for native Steam titles: progress should remain recoverable even if the underlying device does not. So the design should handle:

  • Wi-Fi disconnects
  • Power loss
  • Multiple save events
  • Concurrent backup execution
  • Partial filesystem updates

This article describes the resulting design, the guarantees it provides, and the trade-offs that were intentionally made.

Motivation

Most Steam games already benefit from Steam Cloud synchronization.

If a Steam Deck is lost, reset, or replaced, Steam can usually restore progress automatically.

However, this project started with a game that exists entirely outside that ecosystem.

My copy of The Legend of Zelda: Breath of the Wild runs through Cemu, a Wii U emulator. The save data lives entirely within Cemu's emulated filesystem and is unknown to Steam Cloud.

From Steam's perspective, the game does not exist.

This creates an interesting reliability gap:

Steam Game
    ↓
Steam Cloud
    ↓
Automatic Recovery

BOTW on Cemu
    ↓
Local Files Only
    ↓
Single Point of Failure

The save data is ultimately stored on a single SSD inside the Steam Deck.

While SSD failures are uncommon, they are not impossible. More realistic failure scenarios include:

  • Accidental deletion
  • Corrupted emulator configuration
  • Failed storage migrations
  • Broken updates
  • Device loss or theft
  • User error during emulator maintenance

After investing dozens of hours into a playthrough, relying on a single copy of the save directory felt unnecessarily risky.

The obvious solution was "just back up the files."

The less obvious challenge was determining what properties a good backup system should have.

I wanted something that was:

  • Fully automatic
  • Resource efficient
  • Reliable while traveling
  • Resilient to intermittent connectivity
  • Easy to understand and debug
  • Independent of proprietary backup software

Rather than treating the problem as file copying, I approached it as a small systems design exercise:

Can a Steam Deck continuously produce durable recovery points for emulator saves while remaining effectively invisible during normal gameplay?

The rest of this article describes the resulting architecture.

Problem Statement

The challenge appears deceptively simple:

When BOTW saves, upload the save directory to Google Drive.

However, several questions emerge immediately:

  • How do we know the save operation is complete?
  • What happens if the Deck loses connectivity during upload?
  • How do we prevent multiple overlapping backup processes?
  • How do we avoid uploading identical save states?
  • What consistency guarantees can realistically be provided?

The system should be:

  • Event driven
  • Battery efficient
  • Failure tolerant
  • Easy to reason about
  • Operable without external infrastructure

Requirements

Functional Requirements

  1. Automatically detect save activity.
  2. Upload save snapshots to Google Drive.
  3. Preserve historical versions.
  4. Survive Steam Deck reboots.

Non-Functional Requirements

  1. Near-zero idle CPU usage.
  2. No polling.
  3. No concurrent uploads.
  4. No duplicate snapshots.
  5. No advancement of backup state on failed uploads.

Design Principles

The implementation was guided by four principles.

Event Driven

The system should wake only when useful work exists.

Polling introduces unnecessary CPU wakeups and battery consumption. Linux already provides filesystem notifications through inotify, making periodic scans unnecessary.

Single Writer

Only one backup operation may execute at a time.

This dramatically simplifies reasoning about state transitions and eliminates an entire class of concurrency bugs.

State Advances Only After Success

A failed upload must never be treated as a completed backup.

The backup state is updated only after a successful upload to Google Drive.

Simplicity Over Cleverness

The objective is long-term maintainability rather than maximum sophistication.

The entire system is intentionally built from a small set of primitives:

  • inotify
  • flock
  • SHA-256
  • systemd
  • rclone

A system that can be fully understood in a few minutes is easier to trust than a more complex alternative.

Architecture

sequenceDiagram
    participant BOTW as BOTW/Cemu
    participant Watcher as inotifywait
    participant Backup as backup-botw.sh
    participant Drive as Google Drive

    BOTW->>Watcher: Save files written
    Watcher->>Watcher: Stabilization delay
    Watcher->>Backup: Launch backup

    Backup->>Backup: Wait for flock lock
    Backup->>Backup: Compute directory hash

    alt Hash unchanged
        Backup-->>Watcher: Exit
    else Hash changed
        Backup->>Drive: Upload snapshot

        alt Upload succeeds
            Backup->>Backup: Update stored hash
        else Upload fails
            Backup->>Backup: Preserve previous state
        end
    end

Consistency Model

The primary design challenge is consistency.

A filesystem notification does not imply that an application has finished updating all related files.

Consider a hypothetical save sequence:

  1. Write save data
  2. Flush data
  3. Update metadata
  4. Update tracking information
  5. Close file descriptors

Capturing a snapshot midway through this sequence may produce a backup state that never existed from the application's perspective.

The solution is intentionally simple:

sleep 20

A 20-second stabilization window is introduced between event detection and backup execution.

This trades immediacy for consistency.

Guarantee

Every uploaded snapshot is taken from a quiescent save directory.

Concurrency Model

Filesystem events frequently arrive in bursts.

A single save action may trigger multiple events:

close_write
close_write
moved_to
close_write

Without coordination, multiple backup processes could execute simultaneously.

The system uses a file lock:

exec 9>"$LOCKFILE"
flock 9

This establishes a single-writer model.

Backup processes are serialized rather than executed concurrently.

If a save event occurs while a backup is already running, the subsequent backup process waits for the lock to become available before proceeding.

Because each backup recomputes the save directory hash after acquiring the lock, every observed save event eventually results in a backup evaluation, while duplicate uploads are eliminated through content hashing.

Consequence

The system guarantees:

  • No concurrent uploads
  • No lost save events due to overlapping backup execution
  • No duplicate uploads for identical save states

Change Detection

A common implementation would compare timestamps.

This system instead computes a deterministic hash across the save directory:

find ...
sha256sum ...

This ensures:

  • Metadata-only changes are ignored.
  • Identical save states are never uploaded twice.
  • Backup state reflects content rather than filesystem metadata.

Failure Analysis

Failure Outcome Recovery
Wi-Fi disconnects Upload fails Next save retries
Google Drive unavailable Upload fails Future saves retry
Steam Deck reboot Service restarts Monitoring resumes
Multiple save events Serialized by flock Consistent state
Timestamp-only change Hash unchanged No upload
Upload interrupted State not advanced Retry later
Save during active backup Backup waits for lock Evaluated after prior backup completes

Guarantees

The System Guarantees

  • Event-driven operation
  • No polling
  • No concurrent uploads
  • No duplicate snapshots
  • No advancement of state after failed uploads
  • Historical recovery points
  • Automatic restart after reboot
  • Every observed save event eventually results in a backup evaluation

Operational Characteristics

CPU

Idle CPU utilization is effectively zero.

The watcher blocks inside the kernel waiting for filesystem notifications.

Memory

The watcher process consumes roughly:

~1 MB

Battery Impact

Because the design is event-driven, battery consumption is dominated by the upload itself rather than monitoring.

For save files measured in megabytes, the impact is negligible compared to Cemu emulation.

Conclusion

The final design intentionally favors simplicity.

The result is an eventually durable backup system that remains easy to understand, easy to debug, and resilient to common failure scenarios while running continuously on a Steam Deck.

More importantly, it restores a property that emulator-based games often lose compared to native Steam titles: confidence that progress is durable.

Steam Cloud provides that confidence for supported games.

For software operating outside that ecosystem, we sometimes have to build it ourselves.