1Four kinds of evidence
data The game files
Most answers. We wrote a Python reader for each format (the archives, models, Havok files, animations, map layouts, params, effects, sound, event and talk scripts), plus a small C# tool for animation events, and used the community's DSLuaDecompiler for the scripts. The community's work (SoulsFormatsNEXT, Smithbox, Paramdex, DSAnimStudio, the fxr library) supplies field names; our tools re-implement the formats.
exe The executable and its shaders
Ghidra on DarkSoulsIII.exe and FMOD's DLLs, plus a shader reader of ours, which disassembles DS3's DXBC shaders with their resource and constant names. That way the renderer is read, not guessed.
capture Frames of the running game
RenderDoc on DS3 under Proton. DXVK turns D3D11 into Vulkan, so a capture shows DS3's passes, targets, draws, uploaded constants and bone palettes. Our own scripts read them.
ER Elden Ring's structs
The same engine lineage. Field names from the community fromsoftware-rs project's Elden Ring structs usually carry over; offsets don't.
2Reading a 90 MB executable
- No auto-analysis. Ghidra's full analysis of the exe is mostly single-threaded and takes hours (a run was stopped after 40 minutes). An analysis-free project and a small script of ours
decompile any function in seconds, taking callees from p-code. Whole regions are decompiled from
.pdata's function starts (the physics module: 414 functions, 15.7k lines). - Anchors. A community cheat table's code-patch addresses for v1.15.2 match this build byte for byte: each one is an entry point into the code that does a thing (fall damage, stamina, the jump's gravity).
- Names the exe keeps. MSVC RTTI (the compiler's class names, which lead to each class's vtable), Havok's embedded reflection (member names and descriptions: that's how the character proxy's
maxSlopewas identified), and debug-menu registrations that name settings by their struct offsets ("Enable OBB PrimCulling" at+0x4e28). - Who reads a param field: a script of ours finds the code that fetches a param and the field offsets it loads.
3Frame captures through DXVK
About a dozen frames of DS3 (an RTX 4080 at maximum settings, an AMD laptop at low and auto-detected settings) answered what no file could:
- Which shader each draw runs: DXVK names its shaders by the first 7 hex digits of the DXBC checksum, which our scripts match against every shader in the game, including YEBIS's 3,534 inside the exe.
- Constants as uploaded: one script searches the frame's buffer uploads for a known row (a camera matrix, a gparam value), and another follows DXVK's descriptor buffers to a draw's own blocks. The tone curve's k0 and k1, the lightmaps' 28°, the SH basis constants and the shadow cascades all came this way.
- Shaders as the GPU ran them: the capture holds DXVK's SPIR-V translation of every shader, and we print it with DS3's register names. The DXBC in the game files says the same and keeps the constant names, so we read that first.
- What DS3 draws: matching each draw's index range to a FLVER face set gave per-model triangle counts. Our culling, LODs and instancing were tuned until they agreed within 5 %.
- Pixel history (RenderDoc's replay, driven from a script): what one draw added to one pixel. It settled the particle shader's premultiply after a constant had been misread.
- Bone palettes: a frame's uploads keep ~25 earlier frames of Gundyr's skinning matrices, so his cloth bones could be compared with ours pose by pose.
The captures were deleted on 2026-10-15 to free 21 GB. Each one's place and settings were noted, so they can be taken again.
4Judging the result
Our engine renders from the same camera as a capture, and the two images are compared by region means: the sky 200 against 199, the courtyard's walls 61 against 60, its flat ground 68 against 68. Screenshots of the real game settle what captures can't (which side of the path the gravestones are on settled the handedness). Every test session runs headless.
5Dead ends worth knowing
- Physics debug strings ("Fall Height : %f") sit next to the physics vtable, but nothing references them: the debug code is stripped.
- Module vtables are tiny, and update functions are called non-virtually, so they can't be found from vtables.
- Scanning the data sections for an END → stamina table finds nothing: the curve is code or another param.
- Elden Ring's defence-curve constants aren't in DS3's exe, so DS3's damage curve differs from ER's.
- Guessing the lightmap direction's space by statistics gave no clean winner. The capture's instance data answered it in one step.
- For a week our notes called the
GXFlver_*shader binder (the water) encrypted, from a flag in the archive listing. Nobody had extracted it. It decrypts like every other file, and the shaders inside are plain DXBC. The long way round, through the captures' SPIR-V, got one constant from the wrong row: the water's absorption rate, 0.9 instead of 6. Before calling a file unreadable, open it.
6Still open
| Question | Where to look |
|---|---|
| DS3's damage formula and END → max stamina | The damage modules from the hit-record builder 0x1409be300 |
| Where the 28° light turn comes from | The light-set builder 0x14014a2d0's matrix |
| Why LODs switch at 2 × LodParam | Perhaps a field-of-view term |
| YEBIS's light shafts, bloom scales and depth-of-field LUT | Their $Globals in a new capture facing the sun |
| What moves an enemy between normal, caution, find and battle | The C++ search system behind the Lua's IsBattleState |
FrpgCollisionFilter's layer matrix; slide speed | vtable 0x14282adb8; the contact collector |
| The event flags and saved position in the save file | Diff two saves that differ by one action (the save file) |
| Where the PC key bindings live | Change one binding and diff two saves |
7The save file
DS30000.sl2 has its own chapter: a BND4 of 12 entries, each AES-128-CBC encrypted with one fixed key and checked with an MD5. Ten character slots,
the account, and a copy of the regulation. Read with a reader of ours: the save file →