nexus-mod-set
Turn "here is a game + a list of NexusMods links (+ constraints)" into a
reproducible, opt-in Ansible role, following the same MANUAL-staging pattern as
install_re4_hd, metal_gear_master_collection, and install_gt5_master_mod.
Non-negotiables (learned the hard way)
Version first, always. Find the exact game version each mod requires and
VERIFY it against the user's INSTALLED version before designing anything.
Read the game exe's PE FileVersion:
scripts/pe-version.py <game.exe> --expect <version>. Bake a hard version
gate into the role (fail-with-guidance on mismatch). Version traps sink mod
sets — GTA IV needs Complete Edition 1.2.0.59; the RE4 HD Project needs
appid 254700 (the 2005 UHD), not the 2023 remake.
Nexus downloads: API for Premium, else manual. Nexus pages 403 automated
fetchers, and download links only generate with the user's own credentials.
Use scripts/nexus-download.py (env NEXUS_MODS_API_KEY): file-list metadata
works on ANY tier, but generating a CDN download link via the API requires a
Premium account — free/supporter falls back to a browser cookies export
(Netscape or the 13-column tab format), or fully-manual staging. The role
auto-downloads when a key is present, else fails-with-manifest (mod, URL,
target subdir). Gotchas: pick the version-appropriate file id per mod
(variants exist — CE vs IV vs episode; "Fusion Fix build" vs "OIV"/RTX); CDN
paths contain spaces, so percent-encode the path before GET. Genuinely
hotlinkable deps (GitHub releases) auto-fetch pinned by sha256.
Honor environment constraints. EXCLUDE ReShade-required mods (hard under
Proton — see memory feedback_avoid_reshade_mods) and NVIDIA RTX-Remix
variants (heavy/finicky under Proton). Prefer ENB or native. If a candidate
asset is found under NAS Downloads/Hydra/, flag it to the user first
(memory feedback_hydra_assets_flag_first) — those are usually cracked
repacks.
Order is data. Foundation (ASI loader / script hooks / bugfix framework)
→ framework mods → texture/content mods → single-owner data files
(handling.dat, weaponinfo.xml) applied LAST with "last writer wins".
Encode the sequence as an ordered catalog list in group_vars.
Idempotent, reversible, documented. .dg-* markers, backups before
overlay, a revert path, a docs/<game>-mods.md write-up, and — in the
SAME change — rows in docs/external-installers.md and a section in
ansible/host_vars/localhost.yml.example.
Preserve originals; reuse before re-downloading. Every mod archive — the
user's manual Nexus downloads AND auto-fetched GitHub deps — lives under
{{ dg_roms_final_root }}/PC/NexusMods/<game>/ (i.e.
/mnt/terachad/Emulators/ROMS_FINAL/PC/NexusMods/<game>/). This is the
canonical dg_<game>_staging_root. The role checks there and REUSES an
existing archive before fetching again on a retry, and never deletes staged
originals — they are the preserved source of truth on the NAS.
Inspect archives before installing. List each staged zip's contents and
sanity-check it against the mod's declared type: expected file kinds (textures
/ .rpf / .dat / .xml for data & texture mods; .asi / .dll /
.net.dll for scripts), a sane top-level structure, and NO red flags —
unexpected bundled .exe/installers, steam_api.dll / steam_appid.txt /
scene-crack markers, autorun, or paths escaping the intended folder. Flag
anything suspicious to the user and do NOT install a questionable archive.
Map install paths from inspection; don't fake the un-automatable. Mod
archives are heterogeneously rooted — some update/-rooted for a loader's
overloader, some wrapped in a named folder, some loose files, some OpenIV
.oiv packages. Inspect each and encode explicit per-mod copy rules
(src-inside-archive → dest-in-game) rather than one blind extract. Some mods
genuinely need a GUI/manual step — e.g. GTA IV's OpenIV to import into a
.rpf, or a mod's own add-on framework (Liberty's Legacy) — which cannot be
done or verified headlessly. Install the automatable parts, and FLAG the rest
at runtime + in docs. Never pretend an OpenIV/.rpf import happened. And say
plainly that final rendering can only be confirmed by the user in-game.
Workflow
1. Research each link (delegate to a research subagent)
Nexus pages 403 automated fetchers, so a subagent should combine WebFetch with
WebSearch of indexed titles + authoritative guides. For each mod capture: name/
author, type (ENB / ASI / .NET script / OpenIV .rpf / texture / data / trainer
/ tool), game version required, dependencies (Nexus + off-site: ASI
loader, ScriptHook(.NET), xliveless, ENB, OpenIV), install method, and download
file(s). Read each mod's DESCRIPTION and any sticky/pinned notes for stated
limitations, quirks, known incompatibilities, and conflicts with the OTHER mods
in the set — plus load-order notes, required base mods/frameworks, and variant
choices (which file for this game edition). These change install order, variant
selection, or whether to include a mod at all; record them per mod and surface
conflicts to the user. Also capture fundamentals: the version/downgrade landscape, the
foundational load order, Proton specifics (WINEDLLOVERRIDES, protontricks
verbs, DXVK), a recommended install order, and conflicts. Explicitly flag any
ReShade / RTX-Remix dependency to drop.
2. Verify the installed game
Parse steamapps/libraryfolders.vdf + appmanifest_<appid>.acf to find the
install, and confirm the exe version with pe-version.py. Determine the true
game root (mind subfolders — e.g. GTA IV CE puts everything under GTAIV/).
3. Design the role (data-driven)
group_vars/all/<game>_mods.yml: appid, required_version, game_dir
discovery, staging_root, launch_options, an ORDERED dg_<game>_mods catalog
(per mod: id, name, type, staging subdir OR github source, any config-file
edits), and the pinned foundation download.
roles/install_<game>_mods/tasks/main.yml: preflight (game present + version
assert + sources staged under the NexusMods preserve dir) → install the
foundation (auto GitHub, cached in the preserve dir, reused if present) →
inspect + install each mod in catalog order (list archive contents, flag red
flags, extract → place into the mod loader's folder; community.general.ini_file
for config edits) → Steam launch options
(reuse scripts/set-steam-launch-options.py, guard against a running Steam) →
protontricks reminder for runtime deps → completion report.
revert.yml: restore backups, remove added files, clear launch options.
4. Wire it in
site.yml opt-in { role: install_<game>_mods, tags: [<game>_mods, never] };
ansible/install-<game>-mods.yml; docs/<game>-mods.md;
docs/external-installers.md rows (GitHub deps = AUTO, Nexus = MANUAL);
ansible/host_vars/localhost.yml.example section.
5. Be honest about post-staging unknowns
Each Nexus archive's internal structure, the exact mod-loader folder name, and
config keys are only knowable once the user stages the (gated) downloads. Set
documented defaults, expose them as confirm once staged role variables, and
say so in the docs — never fake certainty about a file you haven't seen.
Reference exemplars in this repo
- Steam-game mod overlay + launch options + Steam-running guard:
roles/install_re4_hd, roles/metal_gear_master_collection.
- Pinned GitHub-release fetch (URL + sha256):
roles/metal_gear_master_collection,
roles/refresh_shadps4.
- Extract-once + backup + marker + rollback report:
roles/install_gt5_master_mod.
- PE version gate / 4GB-patch helpers:
scripts/pe-version.py,
scripts/patch-laa.py.
- Nexus downloader (Premium API + cookie fallback):
scripts/nexus-download.py.
- Steam VDF / game-INI editors:
scripts/set-steam-launch-options.py,
scripts/set-ini-options.py.
- Worked example (this skill's first output):
roles/install_gta4_mods +
group_vars/all/gta4_mods.yml (ordered catalog, per-mod copy rules, manual
flags for OpenIV/framework mods).
1---2name: nexus-mod-set3description: Generate a reproducible, opt-in Ansible role that installs a curated set of NexusMods (or similar) game mods for one game in this distrobox-gaming repo. Use when the user gives a game + a list of nexusmods.com links and wants them installed reproducibly, in the correct order, with dependencies and game- version requirements handled. Triggers: "nexus mods", "mod set", "install these mods for <game>", or a pasted list of nexusmods.com links.4---56# nexus-mod-set78Turn "here is a game + a list of NexusMods links (+ constraints)" into a9reproducible, opt-in Ansible role, following the same MANUAL-staging pattern as10`install_re4_hd`, `metal_gear_master_collection`, and `install_gt5_master_mod`.1112## Non-negotiables (learned the hard way)13141. **Version first, always.** Find the exact game version each mod requires and15 VERIFY it against the user's INSTALLED version before designing anything.16 Read the game exe's PE FileVersion:17 `scripts/pe-version.py <game.exe> --expect <version>`. Bake a hard version18 gate into the role (fail-with-guidance on mismatch). Version traps sink mod19 sets — GTA IV needs Complete Edition **1.2.0.59**; the RE4 HD Project needs20 appid **254700** (the 2005 UHD), not the 2023 remake.21222. **Nexus downloads: API for Premium, else manual.** Nexus pages 403 automated23 fetchers, and download links only generate with the user's own credentials.24 Use `scripts/nexus-download.py` (env `NEXUS_MODS_API_KEY`): file-list metadata25 works on ANY tier, but generating a CDN download link via the API requires a26 **Premium** account — free/supporter falls back to a browser cookies export27 (Netscape or the 13-column tab format), or fully-manual staging. The role28 auto-downloads when a key is present, else fails-with-manifest (mod, URL,29 target subdir). Gotchas: pick the version-appropriate `file` id per mod30 (variants exist — CE vs IV vs episode; "Fusion Fix build" vs "OIV"/RTX); CDN31 paths contain spaces, so percent-encode the path before GET. Genuinely32 hotlinkable deps (GitHub releases) auto-fetch pinned by sha256.33343. **Honor environment constraints.** EXCLUDE ReShade-required mods (hard under35 Proton — see memory `feedback_avoid_reshade_mods`) and NVIDIA RTX-Remix36 variants (heavy/finicky under Proton). Prefer ENB or native. If a candidate37 asset is found under NAS `Downloads/Hydra/`, flag it to the user first38 (memory `feedback_hydra_assets_flag_first`) — those are usually cracked39 repacks.40414. **Order is data.** Foundation (ASI loader / script hooks / bugfix framework)42 → framework mods → texture/content mods → single-owner data files43 (`handling.dat`, `weaponinfo.xml`) applied LAST with "last writer wins".44 Encode the sequence as an ordered catalog list in `group_vars`.45465. **Idempotent, reversible, documented.** `.dg-*` markers, backups before47 overlay, a `revert` path, a `docs/<game>-mods.md` write-up, and — in the48 SAME change — rows in `docs/external-installers.md` and a section in49 `ansible/host_vars/localhost.yml.example`.50516. **Preserve originals; reuse before re-downloading.** Every mod archive — the52 user's manual Nexus downloads AND auto-fetched GitHub deps — lives under53 `{{ dg_roms_final_root }}/PC/NexusMods/<game>/` (i.e.54 `/mnt/terachad/Emulators/ROMS_FINAL/PC/NexusMods/<game>/`). This is the55 canonical `dg_<game>_staging_root`. The role checks there and REUSES an56 existing archive before fetching again on a retry, and never deletes staged57 originals — they are the preserved source of truth on the NAS.58597. **Inspect archives before installing.** List each staged zip's contents and60 sanity-check it against the mod's declared type: expected file kinds (textures61 / `.rpf` / `.dat` / `.xml` for data & texture mods; `.asi` / `.dll` /62 `.net.dll` for scripts), a sane top-level structure, and NO red flags —63 unexpected bundled `.exe`/installers, `steam_api.dll` / `steam_appid.txt` /64 scene-crack markers, `autorun`, or paths escaping the intended folder. Flag65 anything suspicious to the user and do NOT install a questionable archive.66678. **Map install paths from inspection; don't fake the un-automatable.** Mod68 archives are heterogeneously rooted — some `update/`-rooted for a loader's69 overloader, some wrapped in a named folder, some loose files, some OpenIV70 `.oiv` packages. Inspect each and encode explicit per-mod copy rules71 (src-inside-archive → dest-in-game) rather than one blind extract. Some mods72 genuinely need a GUI/manual step — e.g. GTA IV's **OpenIV** to import into a73 `.rpf`, or a mod's own add-on framework (Liberty's Legacy) — which cannot be74 done or verified headlessly. Install the automatable parts, and FLAG the rest75 at runtime + in docs. Never pretend an OpenIV/`.rpf` import happened. And say76 plainly that final rendering can only be confirmed by the user in-game.7778## Workflow7980### 1. Research each link (delegate to a research subagent)81Nexus pages 403 automated fetchers, so a subagent should combine WebFetch with82WebSearch of indexed titles + authoritative guides. For each mod capture: name/83author, type (ENB / ASI / .NET script / OpenIV `.rpf` / texture / data / trainer84/ tool), **game version required**, **dependencies** (Nexus + off-site: ASI85loader, ScriptHook(.NET), xliveless, ENB, OpenIV), install method, and download86file(s). **Read each mod's DESCRIPTION and any sticky/pinned notes for stated87limitations, quirks, known incompatibilities, and conflicts with the OTHER mods88in the set** — plus load-order notes, required base mods/frameworks, and variant89choices (which file for this game edition). These change install order, variant90selection, or whether to include a mod at all; record them per mod and surface91conflicts to the user. Also capture fundamentals: the version/downgrade landscape, the92foundational load order, Proton specifics (`WINEDLLOVERRIDES`, `protontricks`93verbs, DXVK), a recommended install order, and conflicts. Explicitly flag any94ReShade / RTX-Remix dependency to drop.9596### 2. Verify the installed game97Parse `steamapps/libraryfolders.vdf` + `appmanifest_<appid>.acf` to find the98install, and confirm the exe version with `pe-version.py`. Determine the true99game root (mind subfolders — e.g. GTA IV CE puts everything under `GTAIV/`).100101### 3. Design the role (data-driven)102- `group_vars/all/<game>_mods.yml`: `appid`, `required_version`, game_dir103 discovery, `staging_root`, launch_options, an ORDERED `dg_<game>_mods` catalog104 (per mod: id, name, type, staging subdir OR github source, any config-file105 edits), and the pinned foundation download.106- `roles/install_<game>_mods/tasks/main.yml`: preflight (game present + version107 assert + sources staged under the NexusMods preserve dir) → install the108 foundation (auto GitHub, cached in the preserve dir, reused if present) →109 inspect + install each mod in catalog order (list archive contents, flag red110 flags, extract → place into the mod loader's folder; `community.general.ini_file`111 for config edits) → Steam launch options112 (reuse `scripts/set-steam-launch-options.py`, guard against a running Steam) →113 `protontricks` reminder for runtime deps → completion report.114- `revert.yml`: restore backups, remove added files, clear launch options.115116### 4. Wire it in117`site.yml` opt-in `{ role: install_<game>_mods, tags: [<game>_mods, never] }`;118`ansible/install-<game>-mods.yml`; `docs/<game>-mods.md`;119`docs/external-installers.md` rows (GitHub deps = AUTO, Nexus = MANUAL);120`ansible/host_vars/localhost.yml.example` section.121122### 5. Be honest about post-staging unknowns123Each Nexus archive's internal structure, the exact mod-loader folder name, and124config keys are only knowable once the user stages the (gated) downloads. Set125documented defaults, expose them as `confirm once staged` role variables, and126say so in the docs — never fake certainty about a file you haven't seen.127128## Reference exemplars in this repo129- Steam-game mod overlay + launch options + Steam-running guard:130 `roles/install_re4_hd`, `roles/metal_gear_master_collection`.131- Pinned GitHub-release fetch (URL + sha256): `roles/metal_gear_master_collection`,132 `roles/refresh_shadps4`.133- Extract-once + backup + marker + rollback report: `roles/install_gt5_master_mod`.134- PE version gate / 4GB-patch helpers: `scripts/pe-version.py`,135 `scripts/patch-laa.py`.136- Nexus downloader (Premium API + cookie fallback): `scripts/nexus-download.py`.137- Steam VDF / game-INI editors: `scripts/set-steam-launch-options.py`,138 `scripts/set-ini-options.py`.139- Worked example (this skill's first output): `roles/install_gta4_mods` +140 `group_vars/all/gta4_mods.yml` (ordered catalog, per-mod copy rules, manual141 flags for OpenIV/framework mods).