Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
4.9 KiB
gMod
A 32-bit Windows DLL that hooks Direct3D 9 to load texture mods for Guild Wars. Continuation of uMod. Integrated with GW Launcher and Daybreak.
Project structure
header/— header files (D3D9Hooks, D3D9State, Defines, Error, Main, Utils)source/— implementation files (D3D9Hooks.cpp, dll_main.cpp, Error.cpp)modules/— C++23 module files (ModfileLoader, ModfileLoader_TpfReader, TextureClient, TextureFunction)TpfConvert/— standalone utility to fix broken .tpf filescmake/— CMake helper scriptsbuild/— CMake build tree (generated, ignored)bin/— compiled output (generated, ignored)
Build
Requires Visual Studio 2022, CMake 3.29+, and vcpkg — all via the VS 2022 Developer PowerShell.
cmake --preset=vcpkg
cmake --open build
# then compile inside Visual Studio, or:
cmake --build build --config Release
- Target: 32-bit only (
-A Win32/CMAKE_GENERATOR_PLATFORM=win32) - Standard: C++23
- Output:
bin/(gMod.dll, TpfConvert.exe) - Version: set in
CMakeLists.txt(VERSION_MAJOR/MINOR/PATCH/TWEAK)
Cannot be built or run in this agent environment (Linux, no MSVC, no 32-bit D3D9, no Guild Wars to inject into). Don't burn turns attempting
cmake/builds here — validate changes by reading and matching existing patterns, and lean on Windows CI (ci.yaml) for the real build. When an actual compile/test is needed, ask the user to do it in Visual Studio rather than trying locally.
Key dependencies (via vcpkg)
minhook— D3D9 function hookinglibzippp/libzip/zlib— reading .tpf/.zip mod filesdirectxtex— texture format conversionMicrosoft.GSL— Guidelines Support Library
How it works
dll_main.cpp— DLL entry point, hooks d3d9.dll via MinHookD3D9Hooks.cpp— interceptsDirect3DCreate9and IDirect3DDevice9 methodsModfileLoader— readsmodlist.txt, loads .tpf/.zip mod archivesTextureClient/TextureFunction— matches and replaces textures by hash at runtime
Hooking model (read before touching D3D9 code)
gMod uses MinHook vtable hooks, not proxy/subclass wrappers (the old uMod model
was replaced — see git log "Replace D3D9 proxy wrappers with vtable hooks"). The
game keeps the real IDirect3D9 / IDirect3DDevice9 / texture objects byte-for-byte
untouched; gMod patches only the individual vtable slots it cares about (CreateDevice,
CreateTexture/Volume/Cube, UpdateTexture, BeginScene, Set/GetTexture, Release).
- Per-texture side-state lives out of band in
header/D3D9State.h(TexState, keyed by the real texture pointer), so reverting the hooks fully detaches gMod. RemoveAllD3D9Hooks()must leave the host process pristine — preserve that invariant.- A vtable is shared by all instances of a class, so each slot is hooked once; texture Release slots are hooked lazily on first instance. Don't double-hook.
Conventions
- Formatting is CI-enforced by clang-format
22.1.5(.clang-format, applied bylint.yamlon push todev). Don't hand-reformat or "tidy" whole files — it just churns the diff. Notable rules:ColumnLimit: 0(never reflow long D3D signatures / vtable typedefs),SortIncludes: Never(include order is load-bearing in Windows headers — never reorder), 4-space indent, braces on their own line after functions. - C++23 named modules (
modules/*.ixx). Editing a module interface triggers wide rebuilds; prefer changing implementation over interface when possible. - Public exported API is a fixed surface — four
extern "C" __declspec(dllexport)functions indll_main.cpp:SetDevice,AddFile,RemoveFile,GetFiles. These are consumed by GW Launcher and Daybreak; do not change their names, signatures, or calling convention (__cdecl) without coordinating downstream. - Commits: short imperative summaries, lowercase, no trailing period (e.g. "Keep loaded_files in load order so GetFiles returns priority order").
- Branching:
devis the integration branch; PRs targetmaster. CI builds on push todevand on PRs tomaster.
Where things live (by size / how often they change)
modules/TextureClient.ixx(~670 lines) — texture match/replace bookkeeping, hotsource/D3D9Hooks.cpp(~580) — the vtable detours and install/removemodules/TextureFunction.ixx(~380) — texture creation/conversion helperssource/dll_main.cpp(~345) — entry point + exported C APImodules/ModfileLoader*.ixx— .tpf/.zip parsingheader/Defines.h— shared structs/constants (TextureFileStruct,HashTuple, etc.)
Notes
- Must be injected before d3d9.dll loads, or renamed to d3d9.dll in the GW folder
- Reshade > 5.0.1 causes glitches; recommend 5.0.1 or 4.9.1
modlist.txtcontains full paths to .tpf/.zip mod files, one per line