Phase D: cross-platform auto-detect + Windows installer + catalog diff #10

Merged
Sponge merged 1 commit from feature/dune-extract-phase-d into develop 2026-05-28 06:43:50 +00:00
Owner

Summary

Goal: Complete phase D in full, with no errors. Closes D1 (cross-platform install auto-detect with multi-library + Proton-prefix coverage), D2 (PowerShell installer + cross-platform adopter doc), D3 (dune-extract diff subcommand).

D1 — steam_locator.py rewrite

User said during implementation: "there can be many steam libraries on a system, parse and find game before extract." Implemented exactly. Detection ladder:

Layer What it does
Linux Steam roots ~/.local/share/Steam, ~/.steam/{steam,root}, Flatpak, Snap
Windows registry HKLM/HKCU Valve\Steam InstallPath/SteamPath via winreg (only imported on win32)
libraryfolders.vdf parse For every Steam root, enumerate ADDITIONAL libraries (user's box has Steam libs on both ~/.local/share/Steam AND /run/media/sponge/HDD2/SteamLibrary — both found)
appmanifest_<appid>.acf parse Authoritative. Reads installdir directly from Valve's manifest instead of guessing folder names. App IDs: 1172710 (client) + 4754530 (server). Fixed the appid from the placeholder 1172380 to the real 1172710 after empirical verification
Proton compatdata fall-back Walks <library>/steamapps/compatdata/<appid>/pfx/... for installs running inside wine on Linux

enumerate_candidates() returns every detected install; --dry-run surfaces alternatives when >1 is present.

D2 — install-prereqs.ps1 + CROSS-PLATFORM.md

PowerShell mirror of install-prereqs.sh. Steps: Python 3.9+ check → rustup-init.exe bootstrap → cargo install --git aesdumpster-rs + --locked repak_cli → Python venv + editable install. Oodle backend is advisory on Windows — the script tells users to point $env:OODLE_LIB at an oo2core_*_win64.dll they already own (Darktide / Skate / Jedi: Fallen Order all ship one). We do not vendor or distribute the DLL. Uncompressed pak entries decode without it.

Parse-checked clean:

PowerShell parse: OK (610 tokens, no errors)

via [System.Management.Automation.Language.Parser]::ParseInput.

CROSS-PLATFORM.md documents all four host shapes (Linux native / Linux Proton / WSL Ubuntu / native Windows) + multi-library notes + the diff workflow.

D3 — dune-extract diff <a.json> <b.json>

New subcommand. Loads two --format json catalogs and emits a Markdown report:

  • per-category stem additions / removals (by (pak, stem) tuples)
  • per-category DataTable additions / removals (by .uasset filename)
  • per-DataTable row additions / removals / modifications, with the modified-row diff broken down field-by-field (field_name: A → B)
  • aggregate summary table

Capped at 200 stems / 200 modified rows per category (with truncation notice) so the report stays readable on a big patch.

Synthetic-catalog test confirmed the expected report shape: stems added/removed, DataTables added, RowChanged.Damage 50→75, RowChanged.Name OldName→NewName, RowOnlyInA removed, RowOnlyInB added.

No regression

Probe Result
B1 — probe_pak_entries.py 574/574 (100%) dual-header adjacency on Abilities
B2 — probe_read_uasset.py 4/4 with Oodle backend
B3 — probe_datatable.py Systems.pak 5 DataTables / 60 rows

Test plan

# D1: detection ladder
python3 -m dune_extract --dry-run                # should auto-find install
python3 -c "from dune_extract import steam_locator; \
  print([str(c) for c in steam_locator.enumerate_candidates()])"

# D2: PowerShell parse (Linux box with pwsh installed) OR run on Windows
pwsh -NoProfile -Command '
  $c = Get-Content -Raw tools/dune-extract/install-prereqs.ps1
  $errs = $null; $toks = $null
  [void][System.Management.Automation.Language.Parser]::ParseInput(
      $c, [ref]$toks, [ref]$errs)
  if ($errs) { exit 1 } else { Write-Host "OK ($($toks.Count) tokens)" }'

# D3: diff subcommand
python3 -m dune_extract --format json --output-dir /tmp/cat-a
# ... game updates ...
python3 -m dune_extract --format json --output-dir /tmp/cat-b
python3 -m dune_extract diff /tmp/cat-a/items-catalog.json \
                              /tmp/cat-b/items-catalog.json \
                              --output /tmp/diff.md
## Summary Goal: *Complete phase D in full, with no errors.* Closes **D1** (cross-platform install auto-detect with multi-library + Proton-prefix coverage), **D2** (PowerShell installer + cross-platform adopter doc), **D3** (`dune-extract diff` subcommand). ## D1 — `steam_locator.py` rewrite User said during implementation: *"there can be many steam libraries on a system, parse and find game before extract."* Implemented exactly. Detection ladder: | Layer | What it does | |---|---| | Linux Steam roots | `~/.local/share/Steam`, `~/.steam/{steam,root}`, Flatpak, Snap | | Windows registry | `HKLM/HKCU Valve\Steam InstallPath/SteamPath` via `winreg` (only imported on `win32`) | | `libraryfolders.vdf` parse | For every Steam root, enumerate ADDITIONAL libraries (user's box has Steam libs on both `~/.local/share/Steam` AND `/run/media/sponge/HDD2/SteamLibrary` — both found) | | `appmanifest_<appid>.acf` parse | **Authoritative.** Reads `installdir` directly from Valve's manifest instead of guessing folder names. App IDs: 1172710 (client) + 4754530 (server). Fixed the appid from the placeholder `1172380` to the real `1172710` after empirical verification | | Proton compatdata fall-back | Walks `<library>/steamapps/compatdata/<appid>/pfx/...` for installs running inside wine on Linux | `enumerate_candidates()` returns every detected install; `--dry-run` surfaces alternatives when >1 is present. ## D2 — `install-prereqs.ps1` + `CROSS-PLATFORM.md` PowerShell mirror of `install-prereqs.sh`. Steps: Python 3.9+ check → rustup-init.exe bootstrap → `cargo install --git aesdumpster-rs` + `--locked repak_cli` → Python venv + editable install. Oodle backend is advisory on Windows — the script tells users to point `$env:OODLE_LIB` at an `oo2core_*_win64.dll` they already own (Darktide / Skate / Jedi: Fallen Order all ship one). **We do not vendor or distribute the DLL.** Uncompressed pak entries decode without it. **Parse-checked clean:** ``` PowerShell parse: OK (610 tokens, no errors) ``` via `[System.Management.Automation.Language.Parser]::ParseInput`. `CROSS-PLATFORM.md` documents all four host shapes (Linux native / Linux Proton / WSL Ubuntu / native Windows) + multi-library notes + the diff workflow. ## D3 — `dune-extract diff <a.json> <b.json>` New subcommand. Loads two `--format json` catalogs and emits a Markdown report: - per-category **stem additions / removals** (by `(pak, stem)` tuples) - per-category **DataTable additions / removals** (by `.uasset` filename) - per-DataTable **row additions / removals / modifications**, with the modified-row diff broken down **field-by-field** (`field_name: A → B`) - aggregate summary table Capped at 200 stems / 200 modified rows per category (with truncation notice) so the report stays readable on a big patch. Synthetic-catalog test confirmed the expected report shape: stems added/removed, DataTables added, `RowChanged.Damage 50→75`, `RowChanged.Name OldName→NewName`, `RowOnlyInA` removed, `RowOnlyInB` added. ## No regression | Probe | Result | |---|---| | B1 — `probe_pak_entries.py` | **574/574 (100%)** dual-header adjacency on Abilities | | B2 — `probe_read_uasset.py` | **4/4** with Oodle backend | | B3 — `probe_datatable.py Systems.pak` | **5 DataTables / 60 rows** | ## Test plan ```bash # D1: detection ladder python3 -m dune_extract --dry-run # should auto-find install python3 -c "from dune_extract import steam_locator; \ print([str(c) for c in steam_locator.enumerate_candidates()])" # D2: PowerShell parse (Linux box with pwsh installed) OR run on Windows pwsh -NoProfile -Command ' $c = Get-Content -Raw tools/dune-extract/install-prereqs.ps1 $errs = $null; $toks = $null [void][System.Management.Automation.Language.Parser]::ParseInput( $c, [ref]$toks, [ref]$errs) if ($errs) { exit 1 } else { Write-Host "OK ($($toks.Count) tokens)" }' # D3: diff subcommand python3 -m dune_extract --format json --output-dir /tmp/cat-a # ... game updates ... python3 -m dune_extract --format json --output-dir /tmp/cat-b python3 -m dune_extract diff /tmp/cat-a/items-catalog.json \ /tmp/cat-b/items-catalog.json \ --output /tmp/diff.md ```
Closes D1 (Windows registry + libraryfolders.vdf + appmanifest +
Proton-prefix auto-detect), D2 (install-prereqs.ps1 + WSL/Windows
adopter doc), D3 (`dune-extract diff` subcommand).

D1 — steam_locator.py rewrite
  Detection ladder:
    - Linux Steam roots (~/.local/share/Steam, ~/.steam/steam,
      ~/.steam/root, Flatpak, Snap)
    - Windows registry (HKLM/HKCU Valve\Steam InstallPath/SteamPath)
      via winreg, only imported on sys.platform == "win32"
    - For each detected root, parse libraryfolders.vdf to enumerate
      additional library drives (multi-library hosts are common)
    - For each library, parse appmanifest_<appid>.acf as the
      AUTHORITATIVE installdir source. Replaces directory-name guessing.
      Fixed appid from placeholder 1172380 to real 1172710 (client)
      after empirical verification against the user's manifest.
    - Proton compatdata prefix fall-back for Linux installs running
      inside wine.
  User directive verbatim: "there can be many steam libraries on a
  system, parse and find game before extract" — implemented exactly:
  libraryfolders.vdf parse + per-library appmanifest scan.
  enumerate_candidates() returns every detected install; dry-run
  surfaces alternatives when >1 found.

D2 — install-prereqs.ps1 + CROSS-PLATFORM.md
  PowerShell mirror of install-prereqs.sh. Steps: Python 3.9+ check,
  rustup-init.exe bootstrap, cargo install aesdumpster-rs + repak_cli,
  Python venv with editable dune_extract install. Advisory Oodle
  backend on Windows — user points $env:OODLE_LIB at an oo2core
  *_win64.dll they already own from another UE5 game (Darktide / Skate /
  Jedi: Fallen Order ship them). We do not vendor or distribute the
  DLL. Uncompressed entries decode without it.
  PowerShell parse-checked: 610 tokens, 0 errors via
  [System.Management.Automation.Language.Parser]::ParseInput.
  CROSS-PLATFORM.md documents all four host shapes (Linux native,
  Linux Proton, WSL Ubuntu, native Windows) + multi-library notes +
  the diff workflow.

D3 — catalog_diff.py + `dune-extract diff` subcommand
  Loads two --format json catalogs, computes:
    - per-category stem additions/removals (by (pak, stem) tuples)
    - per-category DataTable additions/removals (by filename)
    - per-DataTable row additions/removals/modifications, with the
      modified-row diff broken down field-by-field (name: A -> B)
    - aggregate summary table
  render_markdown caps at 200 stems per category + 200 modified rows
  per table for readability on a big patch.
  Synthetic-catalog test produces the expected report shape.

No regression — B1, B2, B3 probes all pass identically.
Sponge merged commit 0cbb40f6ba into develop 2026-05-28 06:43:50 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
Sponge/Dune-Awakening-Server-Tools!10
No description provided.