Authoring mods¶
The quality-of-life mod layer is the mods repo: pure NeverScript sources and apply metadata,
zero game data. This page covers the model and how to add a mod. It describes structure only,
not the derivative script sources themselves.
No game data, no derivative sources here
The mods repo contains .ns that are decompiled-and-modified THUG2 scripts plus some
licensed binary blobs. It is private and not for wholesale publishing. These docs stay at
the level of the format and the pipeline. See Contributing for the
publishing rules.
The ns-inject model¶
A mod is a directory that compiles one or more .ns NeverScript files to .qb and injects
them into a named entry inside one of the base .prx archives. The apply core reads three
kinds of file:
mods/
├── mods.list # ordered list of mods to apply
└── <mod-name>/
├── mod.conf # type=ns-inject, layer=<n>
├── inject.list # <archive> <internal-name> <source.ns>
└── src/<source>.ns # the NeverScript source(s)
mods.listnames the mods to apply, in order. Order matters when two mods touch the same entry; later layers win.mod.confdeclares the mod type (ns-inject) and itslayer.inject.listmaps each build: which base archive, which internal entry name, and which.nssource compiles into it.
How apply works (apply/apply.go)¶
flowchart LR
ML[mods.list] --> loop{for each mod, in order}
loop --> conf[read mod.conf + inject.list]
conf --> comp[compileNS: .ns → .qb in-process]
comp --> inj[injectPrx: write into the base .prx entry]
inj --> loop
- Read
mods.listin order. - For each mod, read its
mod.confandinject.list. compileNScompiles each.nsto.qbin-process using the vendored NeverScript compiler (no external toolchain at build time).injectPrxwrites the compiled.qbinto the user's own base.prx: raw for most entries, LZSS-compressed for the size-cappedqb_scripts(see the boot ceiling in Codecs).
Because injection targets the user's own base archives, the build reproduces from any region's base. See the build pipeline for where apply sits in the whole build.
Adding a mod¶
- Create
mods/<mod-name>/with amod.conf(type=ns-inject, alayer) and aninject.listmapping each<archive> <internal-name> <source.ns>. - Write the
.nsundersrc/. - Add
<mod-name>tomods.listat the right position (mind the layer ordering). - Build and boot-test:
./revert build --fast --only <mod-name>iterates quickly, but a full boot is the only proof of safety. If you touchedqb_scripts, confirm it stays under the boot ceiling after compression. - Keep the compiled output round-tripping; if the compiler cannot round-trip your target script, that is a compiler limitation to fix first, not something to force.
In-game surfaces¶
Mods that expose toggles hook the engine's own flag system, surfacing under the in-game LEVEL MODS (pause) and MOD OPTIONS (Game Options) menus, persisted through GlobalFlags. That is how features like disable-traffic, skip-goal, keep-soundtrack, and the button-glyph switch persist without a save-file of our own.