Extra Utilities is the only public script extender for Battlezone 98 Redux 2.2.301. Support open modding!
| Platform | Supported |
|---|---|
| Windows | ✅ Yes |
| Steam | ✅ Yes |
| Mac | ❌ No |
| Linux | ❌ No |
| GOG | ❌ No |
| Battlezone 1.5 | ❌ No |
Forked from VTRider's initial project. Massive kudos to him for figuring out how to implement EXU in the first place!
Download exu.dll from the latest release and place it in your mission's
folder (or anywhere on BZR's DLL search path). Then load it from your mission script:
local exu = require("exu")That's it. After this line, all EXU functions are available under the exu table.
-- HelloEXU.lua — minimal mission demonstrating EXU setup
local exu = require("exu")
-- Print the loaded EXU version to the game console
print("EXU version: " .. exu.VERSION)
function Start()
-- Example: disable global turbo (patch-driven behaviour change)
exu.SetGlobalTurbo(false)
-- Example: read the current camera view enum
local view = exu.GetCameraView()
print("Camera view on start: " .. tostring(view))
end
function Update(dtime)
-- Example: read satellite cursor position if sat is active
if exu.GetSatState() == exu.SATELLITE.ENABLED then
local pos = exu.GetSatCursorPos()
-- pos.x, pos.y, pos.z are the world-space coordinates
end
endDefinitions/ExtraUtils.lua is a type-annotation-only file for editor tooling (e.g. the Lua
Language Server / VS Code extension). It contains @class, @field, and @param/@return
annotations that give your editor autocomplete and type-checking for EXU's API. It is not
required at runtime — it raises an error if require()d directly. Copy it into your project's
definitions path and point your editor at it.
Anti-Vendoring Stance: Mod authors should depend on the EXU Steam Workshop item rather than
shipping a copy of exu.dll with their mod. Vendoring a fixed DLL version prevents your users
from automatically receiving bugfixes, and creates compatibility problems when multiple mods
load different copies (the first one to require wins, potentially breaking others that
needed a newer version). The Workshop item is updated alongside releases; pinning to it
is the only way to ensure compatibility across the ecosystem.
- Visual Studio 2022 (any edition) with the Desktop development with C++ workload
- The v143 toolset, version 14.43 or newer (VS 2022 17.13+). The project builds
with
/std:c++23; older 14.3x toolsets silently ignore that flag and fail with a confusingerror C2429: language feature 'nested-namespace-definition' requires compiler flag '/std:c++17'rather than a clear "unsupported standard" message. If you have multiple toolsets installed, force a recent one withmsbuild ... /p:VCToolsVersion=14.44.35207(substitute the version you have).
Run the included setup script once after cloning:
.\setup-dev.ps1This performs a sparse checkout of the Ogre 1.10.0 headers into
third_party\ogre-1.10.0-bzr\_work. No other dependencies need manual setup —
all libraries (Lua 5.1, OgreMain, OgreOverlay) are already checked in under lib\.
After the script completes, open ExtraUtilities.sln and build the Release|x86
solution configuration. It maps to the project-level Release|Win32 target and
writes the output DLL to Release\exu.dll.
From a Visual Studio developer prompt, the same build is:
msbuild ExtraUtilities.sln /p:Configuration=Release /p:Platform=x86If you are writing a native DLL mission (e.g. Battlezone98Redux_Shim) and want to call
into EXU from C++:
- Add
include/from this repo to your project's include paths. - Link against
Release/exu.lib(the import library produced by the EXU build). - Include
<ExtraUtils.h>. Do not defineEXTRAUTILITIES_EXPORTSin your project.
#include <cstring>
#include <Windows.h>
#include <ExtraUtils.h>
void OnMissionInit()
{
// Check for a compatible EXU version at runtime
if (strcmp(EXU_GetVersion(), EXU_VERSION_EXPECTED) != 0) {
OutputDebugStringA("exu.dll version mismatch — update your EXU installation");
return;
}
// Get the Lua state EXU registered during luaopen_exu
lua_State* L = EXU_GetLuaState();
if (!L) return; // EXU not yet initialized from Lua
// ... use the Lua C API
}BZR ships infrequently, but when it does, the hardcoded addresses in src/bzr.h need to
be re-verified. This section documents the process.
Open exu.json at the repo root. Every address EXU relies on is documented there with
a description and the BZR version it was verified against. Load the new EXE + PDB into
your reverse-engineering tool (Ghidra, IDA, x64dbg, or the Battlezone98Redux_Shim RE
toolchain at ../Battlezone98Redux_Shim) and verify each address in the JSON.
Pattern Scans: Look for the functions and data symbols by their PDB names first — the
PDB is the fastest way to confirm addresses haven't moved. If the PDB is unavailable, use
the pattern field in exu.json to locate each function via pattern scan. Pattern scans
are more robust than hardcoded addresses as they often survive binary relocation and minor
code changes.
For any address that has changed, update the corresponding constant in src/bzr.h and
update the address field and version comment in exu.json to reflect the new BZR version.
At the top of src/bzr.h:
/*
* Structs and memory offsets for BZR 2.2.301 ← bump this
*/Also update "version" in exu.json.
Build the Release configuration and drop exu.dll into a BZR installation. Run
examples/RequireTutorial.lua as a mission script — if it loads and prints the EXU
version without crashing, the basic address set is intact.
Once the build is verified, bump version in src/About.h (e.g. "1.1.1") and push a
v1.1.1 tag. The CI workflow will build and publish a GitHub Release automatically.
EXU is the Steam-side native script extender layer for addon code. Its current work falls into four main buckets:
- Native gameplay/state access: command replacement hooks, selected-weapon mask inspection, AI process/task inspection and limited task writes, pause-menu state probes, native save triggering, multiplayer object-build helpers, life and scoreboard helpers, and custom kill-message support.
- Rendering and UI controls: fog, gravity, ambient and sun parameters, time-of-day and shadow controls, viewport retro-lighting material-scheme switching, Ogre overlay creation and manipulation, and radar state/size and edge-path refresh helpers.
- Patch-driven behavior changes and compatibility bridges: engine flame color overrides, shim-owned global/per-unit turbo and HUD positioning, ordnance velocity inheritance, OpenShim-owned hovercraft/smart-reticle convergence and smart-reticle-range controls, and unit-VO throttling, muting, and bark rotation control.
- Stability and integration hardening: standalone EXU builds, stricter patch and scanner validation, safer Ogre/material/environment calls, and better logging when viewport or render-system state is missing instead of crashing Lua-side callers.
Recent additions:
- Viewport retro-lighting toggles via
exu.GetRetroLightingMode()andexu.SetRetroLightingMode(enabled), which switch the active viewport between the stock modern material schemes and theog-*retro variants. - Hovercraft convergence and local smart-reticle convergence via
exu.GetShotConvergence()/exu.SetShotConvergence(enabled)andexu.GetPlayerReticleShotConvergence()/exu.SetPlayerReticleShotConvergence(enabled). These delegate to OpenShim when its bridge exports are present and retain EXU's native fallback for standalone installs. exu.GetReticleRange()/exu.SetReticleRange(range)likewise delegate to OpenShim when its smart-reticle-range bridge is present, retaining EXU's direct-memory fallback for standalone installs.- Scrap/pilot HUD positioning and global/per-unit turbo APIs delegate to OpenShim when its ownership exports are present. Standalone EXU retains its direct-coordinate and native turbo-hook fallbacks. OpenShim's migrated turbo hook calls back into EXU for optional unit-culling updates.
- Optional OpenShim APIs are resolved through the shared
OpenShimBridgehelper rather than per-moduleGetModuleHandle/GetProcAddresscopies. - Hardened environment/material-scheme access so bad viewport state is logged instead of crashing the caller when Ogre data is unavailable.
- Terrain material convenience helpers via
exu.GetTerrainMaterialName([trnFilename])andexu.SetTerrainTextureSet(textureSet)for live planet-style terrain re-theming without reloading HG2/TRN. - OpenShim music bridge helpers:
exu.SetMusicTrack(index),exu.GetMusicTrack(),exu.StopMusic(),exu.PauseMusic(), andexu.ResumeMusic(). These resolve optional OpenShimwinmm.dllexports at runtime and fail closed when OpenShim or a specific native music control is unavailable. - Co-op mission sync helper example in
examples/openshim_coop_sync.lua, wrapping stock LuaSend/Receivefor host-authoritative objective text, markers, mission state, and win/loss synchronization.
EXU now has a terrain-specific material helper for live terrain re-theming. The intended workflow is:
- Keep the current map's
HG2andTRNstatic. - Resolve the terrain atlas material from the map's TRN
[Atlases] MaterialName. - Swap one or more live terrain texture units on that material.
- Pair that with the existing EXU environment calls such as
SetFog,SetAmbientLight,SetSunDiffuse,SetSunSpecular,SetSunDirection,SetTimeOfDay, andSetSunShadowFarDistance.
exu.SetTerrainTextureSet accepts a table with terrain texture keys:
diffuse, detail, normal, specular, and emissive. Each field is
optional, so you can replace only the slots you need.
local exu = require("exu")
local ok, materialName = exu.SetTerrainTextureSet({
diffuse = "trn_ice_diffuse.dds",
detail = "trn_ice_detail.dds",
normal = "trn_ice_normal.dds",
specular = "trn_ice_specular.dds",
emissive = "black.dds",
})
if ok then
exu.SetFog(0.66, 0.76, 0.88, 120, 1200)
exu.SetAmbientLight(0.55, 0.60, 0.70, 1.0)
exu.SetSunDiffuse(0.80, 0.87, 1.00)
exu.SetSunSpecular(0.60, 0.70, 0.90)
exu.SetSunDirection(-0.30, -0.95, 0.08)
exu.SetTimeOfDay(930, true)
exu.SetSunShadowFarDistance(850)
endNotes:
- By default EXU resolves the current map's terrain material automatically from
GetMapTRNFilename()and the TRN's[Atlases] MaterialNameentry. - You can override the target material by passing
material = "my_material"in the texture-set table. - This preserves the map's existing terrain tile painting. It does not hot
reload the map's
TRNorHG2. - A complete usage example lives in
examples/TerrainPlanetSwap.lua.
VTriderfor the original EXU implementation and a large share of the native script-extender codebase.GrizzlyOne95for ongoing maintenance, loader and integration work, and newer rendering and stability additions.Jannefor the original Lua DLL project that paved the way for EXU.DivisionByZerofor the DLL loader work that later integrations build on.Business Lawyerfor bug hunting and technical collaboration.