Files

248 lines
9.2 KiB
Markdown

# 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_<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 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.
---
## 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.