# 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 convenience** — `quick_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 retained** — `validate_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_/`) 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 implementation** — `tar` + `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-controlled** — `app.ini` is a simple key=value file. Users can inspect or delete it at any time. No binary format, no registry, no opaque storage.