This document describes how Oxide persists user configuration, save states, and runtime logs.
flowchart LR
A[User settings] --> B[eframe::Storage]
C[ROM-specific save states] --> D[.state files under savestates/]
E[Runtime logs] --> F[logs/app and logs/emulator]
Oxide currently uses two distinct persistence mechanisms:
- UI/application settings via
eframe::Storage - ROM-specific save states via
.statefiles undersavestates/
These two layers are intentionally separate.
Oxide::save() stores user-facing configuration while stripping runtime-only state.
Persisted examples:
- theme
- language
- VSync
- video scale
- controls
- shortcuts
- CPU speed
- sound enabled / volume
- quirks and quirk preset
- debug terminal enabled state
- last ROM path
Runtime-only fields are explicitly reset before serialization, including:
- live CPU state
- live display state
- keypad state
- ROM bytes/path currently in memory
- terminal logs
- focus flags
- splash runtime data
- transient overlay state
- save-state cache arrays
This is why the app can restore preferences without booting into a stale emulation session.
Current save-state structures:
EmuSnapshotSaveStateMetaPersistedSaveState
PersistedSaveState contains:
- save-state format version
- ROM name
- ROM hash
- ROM bytes
- ROM path
- slot index
- human-readable metadata
- emulation snapshot payload
Oxide exposes 3 save slots per ROM.
Slots are available from:
- top bar menus
- keyboard shortcuts
- debug terminal shortcuts
- explicit
.statefile loading
Manual save on an occupied slot can trigger an overwrite confirmation dialog.
Save-state folders are generated from:
- sanitized ROM name
- stable FNV-1a 64-bit hash of ROM bytes
Typical layout:
savestates/
└── <rom-name>-<rom-hash>/
├── <rom-name>_01_<timestamp>.state
├── <rom-name>_02_<timestamp>.state
└── <rom-name>_03_<timestamp>.state
Only one current file per slot is retained; older slot files are replaced.
flowchart TD
A[User triggers save] --> B{ROM loaded?}
B -- No --> C[Reject save]
B -- Yes --> D{Manual save and slot occupied?}
D -- Yes --> E[Ask overwrite confirmation]
E --> F[Proceed on confirm]
D -- No --> F
F --> G[Clone CPU, Display, memory]
G --> H[Build PersistedSaveState]
H --> I[Write slot file to disk]
I --> J[Update in-memory slot cache and metadata]
J --> K[Show status / overlay]
When saving a slot:
- The app verifies that a ROM is loaded.
- CPU, display, and memory state are cloned into
EmuSnapshot. - Slot metadata is created with timestamp and ROM-derived display name.
- A
PersistedSaveStatevalue is serialized to disk. - In-memory slot cache and UI metadata are updated.
- A status/overlay message is shown.
Shortcuts bypass the overwrite dialog; manual saves can require confirmation.
flowchart TD
A[User triggers load] --> B{Slot exists in memory?}
B -- No --> C[Show empty slot message]
B -- Yes --> D[Restore CPU and Display]
D --> E{Snapshot memory valid?}
E -- Yes --> F[Restore memory]
E -- No --> G[Fallback: reload ROM]
F --> H[Reset accumulators]
G --> H
H --> I[Resume emulation]
When loading a slot:
- The app checks whether a snapshot exists in memory.
- CPU and display state are restored.
- Memory is restored when the snapshot payload is valid.
- If memory payload is not usable, the ROM can be reloaded as fallback.
- Runtime accumulators are reset.
- Emulation resumes from the restored state.
Oxide can also load a .state file directly from disk.
When a ROM is loaded, Oxide scans the corresponding save-state directory and attempts to load the latest file for each slot.
Validation uses:
- ROM hash
- slot index
- matching folder naming conventions
This repopulates in-memory slot metadata for menus and overlays.
Each .state carries a version field.
That gives the project room to evolve the format later. At the moment, compatibility is tied to the current internal representation of the CPU/display snapshot types.
Oxide also persists log sessions separately from save states.
Folders:
logs/app/logs/emulator/
Current session log file:
latest.logs
On startup:
- previous
latest.logsis compressed into a timestamped.zip - a fresh
latest.logsfile is opened
This keeps session logs compact and avoids growing a single unbounded file.
From the debug terminal, logs can also be exported manually to a user-selected .txt or .log file.
This export is independent from the rotating session logs above.
Important distinction:
- save states persist emulation state per ROM
- settings persistence does not keep an active emulation session alive between launches
At startup, runtime state is cleared intentionally even if preferences are restored.