Files

9.2 KiB

Design Decisions

This file captures the rationale behind significant architecture and format choices, so the reasoning is preserved for future maintainers (including yourself six months from now).


Sentinel File vs config.ini (v0.3.2)

Problem

config.ini persisted the save folder path to disk, including the user's filesystem-username. This is a privacy concern — paths are visible next to the binary.

Decision

Remove config.ini entirely. The save folder is session-only — set it each time via Set save folder. The disclaimer acceptance is tracked via a 0-byte sentinel file (NotAlterra_LICENSE_ACCEPTED) alongside the binary.

Rationale

Privacy — no paths written to disk. The save folder exists only in memory while the tool runs.

Simplicity — no config parsing, no INI format to maintain, no migration code for renamed keys.

Sentinel, not config — a 0-byte file communicates exactly one boolean (disclaimer accepted). It cannot grow into a configuration file over time. The format intentionally prevents scope creep.

What was removed:

  • AppConfig struct (save_path, ini_path, save_scan, disclaimer_accepted)
  • load_config() / save_config() with INI parsing
  • Cached ini_path — now derived from save folder at runtime
  • Four integration tests for config round-trips

Manual Path Entry vs Auto-Discovery (v0.3.0)

Problem

Auto-discovery scanned user profiles and system directories for Subnautica 2 save folders. This is a privacy concern — it traverses /home/* (Linux) and C:\Users\* (Windows).

Decision

Replace full auto-discovery with manual path entry via Set save folder. Keep a lightweight quick_discover() that checks only the current user's default install paths at startup.

Rationale

Privacy — no scanning of other users' profiles or system drives.

Current-user conveniencequick_discover() checks 1 path on Windows, 3 paths on Linux, all within the current user's own directories. Returns the first match silently, no UI. If nothing is found, the user enters their path manually.

Discovery module retainedvalidate_custom_path() and derive_ini_path() still live in discovery.rs for the manual entry flow. The aggressive scan functions (discover_save_folders(), scan_other_users(), walk_for_subnautica()) are removed.


tar.gz Backup Format (v0.4.0)

Problem

Directory-tree backups (NotAlterra_Backups/notalterra_copy_<timestamp>/) are messy, uncompressed, and have no integrity guarantees.

Decision

One tar.gz archive per backup event, stored in backups/saves/.

Rationale

No vendor lock-in — standard tar -xzf recovers data without the tool. If NotAlterra stops working, the user's backups are still accessible with standard system utilities.

Single file per event — reduces clutter. One backup = one file, not a directory tree with 15+ loose save files.

Compression — save files compress well (~75MB → ~20MB). Reduces disk usage without user effort.

Pure Rust implementationtar + flate2 crates, 200M+ downloads combined. No system dependencies, no external tools.

Per-entry restore — extracting a single save file from the archive does not require decompressing the entire archive.

Safeguards

  • Atomic write: backup written to .tmp file, then atomically renamed. Power loss during backup discards a temp file, not a real backup.
  • Integrity check after creation: archive is read back and validated before reporting success.
  • SHA256 manifest: a MANIFEST file inside each archive records the hash of every contained save file. On restore, each extracted file is verified against its expected hash — silent bit-rot detected before bad data reaches the save folder.
  • Fuzz target: round-trip fuzzing (create archive from diverse inputs → restore → compare) catches logic bugs.

Migration

Existing NotAlterra_Backups/ directory-tree backups are detected and transparently imported on first run after upgrade. No manual migration required.


GVAS Heuristic Parser vs Structural Walker (v0.4.0)

Problem

The GVAS save-file parser in src/gvas.rs uses byte-scanning to find property names as raw string patterns. It does not walk the full UE5 GVAS schema tree. This means:

  • Properties the scanner isn't written to look for are silently skipped
  • Byte sequences that happen to match a property name can produce false positives (mitigated by validating the preceding length field)
  • Overall GVAS structure (header magic, version, property ordering) is not validated

The GVAS format is defined by the public Unreal Engine 5 source code — the SaveGame system and its binary serialization are part of the engine's open API.

Decision

Keep the heuristic byte-scan parser. Marked as Won't fix.

Rationale

No published schema — Unknown Worlds does not publish the GVAS property layout for Subnautica 2. A structural walker would require reverse- engineering the full property type system without ground truth.

No user-facing benefit — the tool reads six properties (SlotName, DisplayName, bIsMultiplayerSave, playtime, etc.) for display purposes. The byte-scan finds all of them reliably on known save files. A structural walker would produce the same output.

Graceful degradation — when the scanner cannot find a property, the picker falls back to the filename. The tool never crashes or presents incorrect data.

Fuzz coverage — three fuzz targets (parse_gvas, full_metadata, backup_roundtrip) verify the parser does not panic on adversarial input. If a future game update changes the binary layout, fuzzing will catch it.


Dead-Code Cleanup (v0.4.0)

Problem

src/main.rs had #![allow(dead_code)] at the crate level, silencing compiler warnings for four unused functions. This prevented the compiler from flagging real dead code.

Decision

Remove _game_running_windows() and _backup_root(). Retain _game_running_linux() and available_space() for planned future use.

Rationale

Removed:

  • _game_running_windows() — process detection via tasklist was intentionally disabled across the project to avoid Windows Defender false positives (Trojan:Win32/Wacatac.C!ml). The startup reminder modal is the replacement. No plan to re-enable.
  • _backup_root() — returned the legacy NotAlterra_Backups/ path, replaced by backups/saves/ and backups/config/ in v0.4.0.

Retained (each with its own #[allow(dead_code)]):

  • _game_running_linux() — kept for possible opt-in process detection on Linux where AV false positives are not a concern.
  • available_space() — kept for a planned disk-space warning before backup operations.

Persistent app.ini vs Session-Only Paths (v0.4.1)

Problem

Save-folder and backup-root paths were session-only — re-entered each time the tool launched. This was a deliberate privacy choice (v0.3.2), but in practice users expected paths to persist between sessions.

Decision

Persist both paths to app.ini under the platform config directory (data_local_dir/NotAlterra/config/). Backup data stays in ~/NotAlterra/backups/ (user-facing, not in AppData).

Rationale

Usability — re-entering paths every session was friction with no real privacy benefit. The paths already existed on the user's filesystem; persisting them simply saves keystrokes.

Transparency, not silence — instead of claiming "no data stored," the documentation now accurately describes what is stored (save-folder and backup-root paths, which may contain the system username), why (minimal convenience data), and that it never leaves the machine.

Platform standards — config goes to the OS-designated config directory (AppData/Local on Windows, ~/.local/share on Linux). User-facing backup data stays in the home directory where users expect to find it. This separation is standard convention.

Plain text, user-controlledapp.ini is a simple key=value file. Users can inspect or delete it at any time. No binary format, no registry, no opaque storage.


No CLI Flags for Automation (v0.4.3 → v0.5.0)

Problem

The v0.4.0 roadmap listed CLI flags (--backup, --extract, --inspect, --list) as a planned feature. These would allow scripting and cron-driven backups without the TUI.

Decision

Removed from the roadmap. Will not implement.

Rationale

TUI is the feature. The file picker with live metadata preview, split layout, pip navigation, and archive integrity checks IS the interface. Dumping raw metadata to stdout or parsing command-line flags for a non-interactive backup loses all the value the tool provides.

No demand. The tool serves a single user (you). You interact with it through the TUI. There is no automation use case driving this feature.

Overengineered for scope. Adding CLI parsing, non-interactive modes, and a separate output formatter would nearly double the surface area for a feature neither the maintainer nor any hypothetical user would use.

Previous error: The CLI flags were carried over as an assumption from "every CLI tool should have flags" without questioning whether they serve the tool's actual purpose. They don't. This decision documents that realization.