Skip to content

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:

my_config_file 'settings.json'
tags { 'trainer', 'ui' }

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.

client_scripts {
    'lib/config.lua',
    'lib/menu.lua',
    'main.lua',
}

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:

local path = MOD_DIR .. "\\settings.txt"
local f = io.open(path, "r")

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, Stop and Reload any 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.