A scripted mod needs two things the plain game does not have: the Lua runtime next to the hook, and a Scripts folder inside the mod with a main.lua in it.

The runtime#

The hook does not link Lua. It loads lua51.dll at start-up from its own folder, the one that holds dinput8.dll and SDL3.dll. The file is LuaJIT 2.1, built for x86 from the v2.1 branch, and is about 640 KB. The JIT is enabled and scripts have access to jit and ffi.

If you need to build the runtime yourself, the following commands produce it:

git clone --depth 1 --branch v2.1 https://github.com/LuaJIT/LuaJIT.git
cd LuaJIT/src && mingw32-make BUILDMODE=dynamic CC=gcc

Without the file nothing happens: the log reports it in one line and the game runs as if no mod had scripts. The DLL is not loaded at all when no mod has a Scripts\main.lua, so a Mods folder of asset-only mods costs nothing.

The Mods folder#

Mods are placed under Mods next to the game, one folder each. The Assets folder is the asset override system (loose files that shadow the same paths inside the game's archives) and is optional. A file under Assets also replaces the file of the same path in the game's Data folder, so Assets\Movies\BootUp\Intro.bik plays in place of the game's intro movie. The Scripts folder is what this documentation covers.

<game>\Mods\
└── MyMod\
    ├── Assets\              loose files shadowing the archives (optional)
    ├── Scripts\
    │   ├── main.lua         runs once at start-up, registers handlers
    │   └── util.lua         require("util") finds it
    └── ModInfo.ini

main.lua runs when the game starts, before the window exists. Any other file in Scripts can be loaded with require by its file name. The mod's name, as ss.mod.name reports it, is the name of the folder.

ModInfo.ini#

The mod's description. The sample mods ship with the following:

Mods\Magnet\ModInfo.ini
[Mod]
Name=Magnet
Version=1.0
Author=sshook samples
Description=The level's props slowly gravitate towards the player's car.

Folders and zips#

A mod can be a folder or a zip with the same layout inside it (Mods\MyMod.zip with Scripts\main.lua at its root). Both load the same way, in the order the start-up scan finds them. The one visible difference is where saved data goes: a folder mod writes Mods\MyMod\save.lua, a zipped mod writes Mods\MyMod.zip.save.lua next to the archive, as the archive itself is never written to.

Checking it works#

Start the game with a main.lua that calls ss.log("hello"). The line sshook: lua: [MyMod] hello appears in the debug output before the window is up; How to: Testing a mod explains where to watch for it. A main.lua that fails to compile or run is reported there as well, with the file and line, and that mod receives nothing for the rest of the run.