A native x86 runtime that injects a Lua 5.4 scripting engine into gamemd.exe (Yuri's Revenge 1.001), enabling gameplay mechanics to be written in pure Lua instead of raw C++/ASM. The flagship showcase: Tesla Overload — a pulsing EMP + damage mechanic that disables and destroys enemy structures.
Status: v1.0.0-Core — stable MinHook engine with modular ModLoader. Tagged
v1.0.0-core.
injector.exe ──► gamemd.exe (suspended) ──► inject LuaAPI.dll ──► resume
│
MinHook trampoline on Unsorted::MainLoop
@ 0x55D360 (per-frame)
│
Lazy Lua-state init on the main game thread
│
scripts/init.lua (Universal ModLoader)
│
scripts/mods/<mod_name>/main.lua (pure Lua mods)
Key design decisions
| Aspect | Decision |
|---|---|
| Hook target | Unsorted::MainLoop @ 0x55D360 (documented in YRpp), via MinHook trampoline |
| Threading | The entire Lua state lives on the main game thread; lazy-initialized on the first hook tick. Only the rotating logger is cross-thread (mutex-protected). |
| Safety | All bindings validate pointer liveness against engine arrays; script errors are contained by pcall and logged — never crash the game. |
| CRT | Statically linked (/MT) — no VC++ redistributable needed inside the game process. |
| Modding | LuaAPI.dll is a frozen host platform; gameplay is authored as pure Lua modules in scripts/mods/<name>/main.lua. |
Community context and upstream validation notes: see PROJECT/AI_CONTEXT.md.
-
Build (or use the deployed binaries):
cmake -B build -A Win32 cmake --build build --config RelWithDebInfo
Binaries are auto-deployed next to
CMakeLists.txt(your game directory) after every successful build. -
Launch the game:
- Double-click
injector.exe— it spawnsgamemd.exesuspended, injectsLuaAPI.dll, resumes it, and exits. - If
gamemd.exeis already running,injector.exeattaches and injects into it instead.
- Double-click
-
Verify: check
LuaAPI.lognext to the DLL:[..] MainLoop hook fired! (first execution) [..] [script] [LuaAPI] Universal ModLoader Online! [..] [script] [LuaAPI] [+] Mod active: 'tesla_overload'
injector.exe :: 1-click launch or attach
injector.exe D:\path\LuaAPI.dll :: explicit DLL path (attach mode)
injector.exe "gamemd.exe" -SPAWN -LOG -CD :: spawner mode (creates process, injects, waits)Create a folder under scripts/mods/<mod_name>/ containing main.lua, then add the mod name to ACTIVE_MODS in scripts/init.lua:
local ACTIVE_MODS = {
"tesla_overload",
-- "my_second_mod",
}A mod module returns a table with an optional Update(frame) function, called every game frame:
local MyMod = {}
function MyMod.Update(frame)
local player = House.GetPlayer()
if player then
Engine.PrintMessage(string.format("Hello from MyMod at frame %d!", frame))
end
end
return MyMod- Mods load via
require— the engine prepends<DLL dir>/scripts/?.luatopackage.path. - Each mod's
Updateruns insidepcall: one broken mod logs an error and keeps running; it never crashes the game.
| Function | Description |
|---|---|
Engine.PrintMessage(text) |
Shows text in the in-game HUD message list (UTF-8 input). |
| Function | Description |
|---|---|
House.GetPlayer() |
Returns the local player's house handle, or nil. |
House.GetCount() |
Number of houses in the scenario. |
House.GetByIndex(idx) |
House handle at idx (bounds-checked), or nil. |
House handles support:
| Method | Returns |
|---|---|
house:GetCredits() |
Available money (via Available_Money()). |
house:SetCredits(amount) |
Sets credits through the game's own money transaction. |
house:AddCredits(delta) |
Adds/subtracts credits. |
house:GetPowerOutput() / house:GetPowerDrain() |
Power grid values. |
house:GetName() |
Internal house ID string (e.g. "Americans"). |
house:IsHuman() |
Whether a human controls this house. |
house:IsAlliedWith(other) |
Alliance test between two houses. |
| Function | Description |
|---|---|
World.GetBuildings() |
Array of building handles (BuildingClass::Array). |
World.GetUnits() |
Array of unit handles (UnitClass::Array). |
All methods validate that the underlying object is still alive.
| Method | Returns |
|---|---|
obj:GetTypeName() |
Type ID string (e.g. "GAPOWR"). |
obj:GetHealth() / obj:GetMaxHealth() |
Current / maximum health. |
obj:GetOwner() |
Owning house handle. |
obj:GetPosition() |
{x = cellX, y = cellY, z = ...} in map cells. |
obj:IsAlive() |
Liveness (health > 0, not in limbo). |
obj:GetDistanceTo(other) |
Euclidean distance in cells. |
obj:TakeDamage(n) |
Applies damage (clamped at 0); returns remaining HP. |
obj:Disable(frames) |
Timed EMP-style disable — buildings lose power (HasPower + DisableStuff()), units get paralyzed (ParalysisTimer). Auto-restores on expiry. |
| Function | Description |
|---|---|
print(...) |
Redirected to LuaAPI.log (tagged [script]). |
OnTick(frame) |
Define this in your mod; called every game frame with the current frame number. |
LuaAPI.log is written next to LuaAPI.dll using a rotating sink (5 MB × 3 files). Contents include engine lifecycle, hook status, [script] output, [HUD] messages, [Combat] events, and per-line source locations. It initializes off the loader lock and is safe across threads.
├── CMakeLists.txt Win32 build (MSVC, /MT, C++20)
├── injector.cpp Dual-mode launcher/injector (spawn + attach)
├── include/LuaAPI/ Public headers (logger, lua_engine, bindings)
├── src/ dllmain, lua_engine, bindings_house, bindings_techno
├── scripts/
│ ├── init.lua Universal ModLoader
│ └── mods/ Pure-Lua gameplay modules
│ └── tesla_overload/main.lua
├── PROJECT/ Roadmap, changelog, AI project context
└── third_party/ YRpp, lua, sol2, spdlog, minhook (git submodules)
- Visual Studio 2026/2022 with Desktop development with C++ (MSVC v145+, Win32 toolchain)
- CMake 3.16+
- Windows SDK 10.x
- Git (submodules):
git submodule update --init --recursive
PROJECT/ROADMAP.md— milestones and gate trackingPROJECT/CHANGELOG.md— version historyPROJECT/AI_CONTEXT.md— architecture decisions & upstream context