Mod structure and manifest.lua¶
A mod is a folder inside mods/ with a manifest.lua in it. That file is what
makes the folder a mod: it gives it a name in the mod manager, it lets it start
on its own, and it lists the files to load. A folder without one is skipped,
even if it is full of Lua. The two older formats are still read, see
Legacy formats.
π mods/
βββ π MyFirstMod/
βββ π manifest.lua
βββ π main.lua
βββ π lib/
βββ π menu.lua
The mods/ folder sits next to GTAIV.exe, alongside the loader itself. See
Installation.
The manifest¶
-- manifest.lua
name 'My First Mod'
author 'You'
description 'Heals the player on F4.'
version '1.0.0'
episode 'IV'
autostart = true
client_scripts {
'lib/menu.lua',
'main.lua',
}
| Key | Default | Meaning |
|---|---|---|
name |
the folder name | Display name in the mod manager. |
author |
empty | Free text. |
description |
empty | Free text, shown in the manager. |
version |
empty (1.0.0 when written back) |
Free text. |
episode |
empty | Which episode the mod targets, such as IV, TLAD or TBoGT. Declarative only: the loader never hides a mod over it. |
autostart |
false |
true starts the mod by itself once the game reaches gameplay. |
client_script / client_scripts |
main.lua |
The files to load, in order, into the same Lua state. Both spellings feed the same list. |
main |
first client script | Entry file. Rarely needed. |
Every key is optional, so a manifest can be as short as one line, but the file itself has to exist.
How the syntax works¶
The manifest is real Lua, executed in a throwaway state that has no io and no
os. Every line is a function call, and Lua lets you drop the parentheses when
the single argument is a string or a table. That is why name 'My First Mod'
and client_scripts { ... } read the way they do.
autostart true is a syntax error
The parentheses can only be dropped for a string or a table, never for a
boolean. Write autostart = true or autostart(true). The same holds for
any other key you want to set to true, false or a number.
A key the loader does not know is not an error. It is stored as the mod's own metadata, so you can declare whatever your mod needs:
Letting the editor write it¶
You do not have to type any of this. In the mod manager, right-click and pick
New mod: the loader creates the folder, a manifest.lua with the keys above
and a comment on each one, and a main.lua already holding the thread loop and
an onUnload. It then opens main.lua for you. autostart starts at false,
so the new mod waits for a Start until you change that line.
Your comments survive. When a mod calls Script.DeclareModFile, the loader
slips the new path into the client_scripts block instead of rewriting the
file, so nothing you wrote in there is lost.
Multiple files¶
client_scripts is how you split a mod across several Lua files. They are all
loaded into the same lua_State, in the order they appear, so a global
defined in the first is visible to the next.
Two things follow from this:
- Order matters. Put libraries before the code that uses them.
- Paths use
/and are relative to the mod folder.
A mod can also add a file to its own manifest at runtime with
Script.DeclareModFile,
which is how a mod that generates data files makes them load on the next start.
Streaming: models and game data¶
A mod is not limited to Lua. Two more folders let it ship a vehicle, a weapon
model, a texture, or a few lines of handling.dat, and the player still
installs it by copying one folder into mods/. Nothing of the game is edited,
and nothing is written into the game folder.
π mods/
βββ π MyCar/
βββ π manifest.lua
βββ π stream/
β βββ π f620.wft
β βββ π f620.wtd
βββ π gamedata/
βββ π vehicles.ide
βββ π handling.dat
βββ π carcols.dat
Both folders are served by dinput8.dll, which sits between the game and its
files. They are read once, at launch: a file added to stream/ or gamedata/
while the game runs is picked up at the next launch, not on a mod reload.
stream/¶
Every file under stream/, subfolders included, is packed at launch into one
IMG archive that the engine mounts like its own. Model, texture, collision and
animation files (.wdr, .wft, .wtd, .wbn, .wad...) all go there, by
their plain file name: the engine finds a resource by name, not by path.
The archive is written to the loader's cache, the game folder when it is
writable and %LOCALAPPDATA%\LuaModLoader\cache\ otherwise, and it is only
rebuilt when a file's name, size or date changed. asi_loader.log says how
many entries went in and whether the cache was reused.
If the player has put their own update/update.img in the game folder, its
entries are absorbed into the same archive rather than bypassed, and a mod file
wins over an absorbed entry of the same name.
stream/ adds files; it does not replace the game's own. To replace a stock
file, use the layout FusionFix uses: update/common/... and update/pc/... in
the game folder are served by the loader in place of common/... and
pc/....
gamedata/¶
A file under gamedata/ is a fragment of the game file with the same name:
vehicles.ide, handling.dat, carcols.dat, default.ide, images.txt...
It holds only your lines, under the section header of that format:
# gamedata/vehicles.ide
cars
f620, f620, car, SUPERGT, F620, VEH@LOW, 100, 999, 0.2740, 0.2740, 0, 1.0, 1.0, 0
end
Lines outside any section go to the end of the merged file, which is what
handling.dat needs since it has no sections. Your lines are appended to what
the game, FusionFix or another mod already serves for that file, so two mods
that each add a car add two lines to the same vehicles.ide instead of one
overwriting the other. The merged file lives in the cache and is served to the
engine through the file redirection; the original is never touched.
The engine has a table of 160 handling entries; the loader raises it to 512, so adding vehicles does not overflow it.
Line-based files only
The merger understands files made of records under section headers, or of
plain records with no section. WeaponInfo.xml is XML and is not
merged from gamedata/; a fragment placed there would be appended as
text. Ship a complete update/common/data/WeaponInfo.xml instead.
Per-mod globals¶
Each mod runs in its own sandboxed Lua state, and the loader injects two globals into it:
| Global | Value |
|---|---|
MOD_DIR |
Absolute path of the mod's folder, with no trailing separator. nil for a buffer run from the editor. |
SCRIPT_NAME |
Identifier of the running instance. |
MOD_DIR is what you build paths from. The process working directory belongs to
the game, not to your mod:
The sandbox
A mod can read and write its own files (io.open), but the shell is out of
reach: os.execute, os.exit and io.popen are removed from the standard
library. Nothing a mod does should be able to spawn a command window.
Legacy formats¶
Two older layouts are still read, in this order: manifest.lua first, then
manifest.xml, then meta.xml. A mod that already works keeps working, and
nothing has to be converted.
manifest.xml was the loader's own format before manifest.lua. Same
fields, in XML:
<mod>
<name>My First Mod</name>
<description>Heals the player on F4.</description>
<version>1.0.0</version>
<main>main.lua</main>
<autostart>true</autostart>
<files>
<file>main.lua</file>
</files>
</mod>
Its <autostart> is compared literally against true, so TRUE, 1 and yes
all read as false. Script.DeclareModFile keeps writing XML for a mod that
still has one, so nothing gets converted behind your back.
meta.xml comes from multiplayer resource layouts. Every <script> tag
with type="client" or type="shared" contributes its src to the file list,
and the first one becomes the entry file. autostart is implicitly true
there.
New mods should use manifest.lua.
Starting, stopping, reloading¶
- Autostart: mods with
<autostart>true</autostart>are queued at launch and start on the first tick that is genuinely in-game, never during the loading screen, where the engine has no script context yet. - By hand: from the in-game F6 editor, whose mod list can
Start,StopandReloadany mod without leaving the session. - Reload restarts the instance from scratch: the Lua state is dropped and the files are loaded again. You do not need to restart the game to iterate.
There is no onLoad()
Initialisation is simply the code written at file scope: it runs once when
the file is loaded. On the way out, onUnload() is called: the loader
invokes it just before closing the Lua state, on a stop and on the first
half of a reload. An error raised inside it is logged and the state is
closed anyway.