Reforge lets a Warhammer 40,000: Darktide mod replace the game's own files,
such as particle effects, materials and shader programs, by shipping edited
copies inside the mod folder. Players install the mod like any other DMF mod
by dropping it into mods/. No installer runs, and no game file is changed on
disk. Uninstalling means deleting the mod folder.
It has three parts:
| Part | What it is |
|---|---|
reforge.dll |
A small native library (Rust). While the game runs, it serves read-only opens of registered bundle/… files from files in mods/…. |
reforge.lua |
The Lua library that mods ship. It loads one shared copy of the DLL, lets several mods coexist, and reports a state for each replaced file. |
reforge CLI |
A tool for mod authors. It scaffolds a mod, records stock hashes, generates the Lua manifest and checks mods after game updates. |
- DMF loads the mod. The mod's script calls
reforge.register_manifest(...). - In
on_all_mods_loaded, the mod callsreforge.commit(). The library loadsbin/reforge.dllthroughMods.lua.ffi(LuaJIT FFI) and installs hooks onCreateFileW,CreateFileAandCreateFile2, in this process only. - For every registration, the DLL checks that the stock file still has the expected SHA-256. When it does, later read-only opens of that file get a handle to the mod's copy. When the game has been patched and the hash no longer matches, the file stays stock and the mod is told why.
- It redirects only read-only opens under
<game>/bundle/. Opens for writing or deleting always go to the real file. - Replacements must live under
<game>/mods/. Paths with.., drive letters or backslashes are refused. - It makes no network calls, writes no files, and patches no memory apart from the three hooks.
- Only one copy hooks a process. If several mods ship
reforge.dll, the first loaded copy is shared by all of them. - Every export catches panics, so an internal bug falls back to stock files instead of crashing the game.
The steps below use Windows, Python 3.9+ and the reforge.cmd wrapper in this
folder.
reforge init path\to\mods\MyShaderMod --author "Me"
reforge add path\to\mods\MyShaderMod 98bb14b1d247a0c8 --copy
rem edit path\to\mods\MyShaderMod\payload\98bb14b1d247a0c8 with your own tools
reforge pack path\to\mods\MyShaderMod --game "C:\...\Warhammer 40,000 DARKTIDE"
reforge build path\to\mods\MyShaderMod --game "C:\...\Warhammer 40,000 DARKTIDE"addrecords the stock file's SHA-256 inreforge.json. With--copy, it also copies the stock file intopayload/as a starting point for editing.add-virtualandadd-virtual-dirserve files at game paths that do not exist yet. Use them for extra resources that a replaced bundle refers to.packOodle-compresses bundle payloads whose 512 KiB chunks are stored uncompressed. Use it after writing a bundle with your own tools. The game's DirectStorage reader crashes on stored chunks with "Failed to decompress ... from package".packuses the Oodle library installed with the game, which is not redistributable, so it needs Windows. It checks every chunk by decompressing it again.buildrefuses a bundle payload that still has stored chunks, then validates everything and writesscripts/mods/<Mod>/reforge_manifest.lua. It also copies the currentreforge.lua,bin/reforge.dllandbin/REFORGE_NOTICES.txtinto the mod.- After a game update,
verifyshows which stock files changed.
See docs/authoring.md for the Lua API, file states and coexistence rules, and docs/ABI.md for the C ABI.
| State | Meaning |
|---|---|
active |
This mod's file is being served. |
shared |
Another mod won, but it serves a byte-identical file. |
compatible |
Another mod won with the same contract string. |
displaced |
Another mod's different file is served. |
refused |
The stock file changed (game update), the payload is missing, or a path is invalid. See reforge.reason(handle). |
restart_required |
The game already opened this file this session with other contents. The change applies after a restart. |
unavailable |
reforge.dll could not be loaded. |
pending |
commit() has not run yet. |
In game, /reforge lists every replaced file. /reforge on|off toggles
redirects for files opened after that point. /reforge trace on|off|dump
writes a file trace to the log.
- Files the game opened before
reforge.dllwas installed cannot be detected after the fact. Most effect and material bundles load with missions, after mods load. A file loaded at boot needs a restart to be picked up. - Early-loaded bundles must keep the stock layout exactly. Darktide
reads some bundles, such as the weapons of the character in the menu,
before any mod runs, and loads them later with the stock chunk table. A
replacement with a larger chunk crashes with "Failed to decompress ... from
package". A smaller one crashes with read error
0x89240007, which means end of file. This was observed in game on 2026-09-26.reforge pack --gamerecompresses each chunk and pads it to the stock chunk size (Oodle ignores trailing bytes), which works for edits that keep the stock record list. New resources cannot be added to such a bundle. Put them in a package that the game never loads on its own, replace that package's bundle (mark it"late_load": true), and load it from the mod afterreforge.commit(). Seedocs/authoring.md. Bundles the game first loads in missions may differ from stock; mark themlate_loadtoo. - Game updates change stock hashes. Affected files fall back to stock until the mod is rebuilt against the new files.
- Polychromatic and some other mods ship Wobin's Asset Redirect, which is a
separate hooking DLL. Two hooks serving the same file would race, so
Reforge leaves any file that Asset Redirect also registers to Asset
Redirect. That file reports
displaced, andreforge.winner(handle)names the other mod. Files only one of them replaces are unaffected.
- Windows (MSVC):
powershell -File scripts\build.ps1 -Test. This builds, runs the harness and refreshesdist/. - Linux cross-build + Wine:
scripts/build-cross.sh. - Tests:
luajit lua/tests/test_reforge.luapython -m unittest discover -s cli/testslua/tests/integration.lua, which runs real LuaJIT FFI against the real DLL.
Release builds come only from GitHub Actions. Each tag produces a draft
release with build provenance you can check with
gh attestation verify reforge.dll --repo Vansinnet/Reforge. The DLL is
unsigned. Antivirus heuristics may flag any unsigned DLL that hooks
CreateFileW. Compare the file against the release's SHA256SUMS and
attestation, and report false positives to your antivirus vendor. Never
disable scanning.
Thanks to Wobin for the original Asset Redirect DLL for Darktide. His work showed that mods could serve replacement game resources from their own folders and inspired Reforge. Reforge is an independent implementation; it does not include Wobin's DLL or source code.
MIT, see LICENSE. The DLL includes MinHook (BSD-2-Clause); see
THIRD_PARTY_NOTICES.md. Mods that ship
reforge.dll must ship REFORGE_NOTICES.txt with it, and reforge build
does that automatically.
Reforge is a community tool. It is not affiliated with Fatshark or Games Workshop.