Unreal Engine 4.22 – 5.7 · Unity IL2CPP and Mono · Windows x64
Point it at an Unreal game — running, crashed, or just sitting on disk —
and get back every class, struct, enum, property offset, function signature and
Blueprint script. Then render it as a C++ SDK, a .usmap, types for IDA,
Ghidra or Binary Ninja, Frida bindings, Python stubs, or a report on what a patch just
broke.
It does the same for Unity IL2CPP games: every C# type, field
offset, method RVA and enum value, read out of the running game rather than by parsing
global-metadata.dat — which is why encrypted metadata stops
mattering, and why a new Unity release does not break it.
Since 0.10.0 it also hands you the addresses a mod starts from —
GWorld, ProcessEvent and AppendString — read out of the game's
memory and code from outside, no injection. On Escape the Backrooms all three match
Dumper-7 exactly, and zircon fingerprint prints them in about a second.
Since 0.9.0 it does the same for Unity Mono, the other backend, where the
game ships its code as real .NET assemblies. Field offsets come from the runtime because
the CLI does not store them; enum values and IL offsets come from the assemblies because a
const has no storage for the runtime to read. Both halves, merged, with
neither number invented.
Since 0.7.0 it reads that metadata file too, and --mode dual takes both at
once. The runtime knows what is true at execution — offsets, method addresses, the
concrete generics that only exist once something has run. The file knows the exact type
set, does not move between two reads, and answers for targets that will not run here at
all: an APK, a console build, a game with anti-cheat, a game that crashes the moment the
walk touches it. Each record says which side it came from, and every disagreement
between them is recorded rather than quietly resolved.
That file is read without a version table as well. Its layout is worked out from constraints it cannot satisfy by accident — the spans tile the file, a record table's leading fields all land on a name, a start/count pair partitions the table it indexes. Across metadata versions 24 through 39, ten of eleven installed games solve completely, and the eleventh refuses with its reason instead of guessing.
There is no engine-version table anywhere in it. Every offset is worked out at runtime from something structural to the engine, which is why it holds up on licensee forks, renamed executables, and engine versions that did not exist when it was written. Both Unity paths hold to the same rule.
Windows x64 · 4.2 MB · no runtime to install
Capability
Dumper-7 and UEDumper are mature, widely used and good at what they do — if all you want is a C++ SDK from an injected DLL, they are well-trodden paths. This is a comparison of what the tools set out to cover, not a scoreboard.
| Capability | Dumper-7 | UEDumper | Zircon |
|---|---|---|---|
| How it attaches | injected DLL | external | injected, external, minidump, or a PE on diskonly |
| Analyse a crash offline, with no game running | — | — | yes — dump once, analyse foreveronly |
| Primary output | C++ SDK | C++ SDK + live viewer | 11 formats + plugins |
| Blueprint bytecode decompiler | — | — | ~90 Kismet opcodesonly |
| Build-to-build diffing | — | — | 27 change kinds, CI exit codeonly |
| Find objects by what they hold | — | — | --where Health<50only |
| Reverse reference index | — | — | xref -f Actoronly |
| Reads CDO default values | — | — | into the SDK, docs and diffonly |
| Live object browser | — | yes | standalone and in-process |
| Edit values in a running game | — | yes | yes, opt-in, with value freezing |
| Callable function wrappers in the SDK | yes | — | yes, ProcessEvent slot derived — no setup |
| Scriptable / extensible | — | — | C plugin ABI + vendored Luaonly |
| Checks its own output for contradictions | — | — | validate --strictonly |
| Behaviour when it is unsure | — | — | refuses rather than guessing |
The parts worth showing
Each of these came out of needing it, not out of a feature list.
Roughly ninety Kismet opcodes, with names, properties and called functions resolved through the same reflection data as everything else. Every line carries its bytecode offset, so you can cross-reference a raw disassembly.
100% of 2.1 MB decoded on the largest target tested. When an opcode isn't recognised the walk stops and says where — a misaligned decode produces confident nonsense, which is worse than stopping.
Dump before the patch, dump after, and the diff reports every class that changed size, every property that moved and every function that vanished — graded by how badly it breaks code you already wrote.
It exits with code 8 when something breaking changed, so it drops
straight into a build script. Twenty-seven change kinds, including a property
whose default value moved — a patch that shifts nothing and changes
how the game behaves no longer produces an empty report.
Enums resolve to their names rather than integers, object properties to their paths, structs to their members, and unreadable memory says so instead of showing zeroes. Select a class and you get its designer-set defaults; select a live instance and you get what it holds right now.
Tick Allow edits and the value column becomes editable. Seven bools commonly share one byte, so setting one reads the byte, changes that bit and writes it back — the other six survive. Values the game owns can be frozen, re-applied every 30 ms, which beats a 60 Hz tick.
You don't name an engine version and you don't pick your game from a list of supported titles. Every running process is scored on what is actually inside it, and the layout is derived from memory.
Every offset comes with a confidence value and the evidence behind it, and every conclusion lands in the dump header — so a member that looks wrong can be checked against the reasoning that produced it, rather than trusted because the file parsed.
On a running game fingerprint also lists GObjects, FNamePool, GWorld,
ProcessEvent with its vtable slot, and AppendString — everything a mod menu's
offsets file needs, from one command.
One dump, twelve renderings
The reflection data is read once into a versioned JSON intermediate representation.
Every output is a pure function of that file, which is why adding a format never
touches the code that reads memory — and why you can write your own against
dump.json in any language.
.pyi stubs so your editor completes the game's own classeszircon emit all game.json -o out/
zircon emit cpp_sdk,usmap,frida_js game.json -o out/
And out of your machine
Zdex indexes Zircon dumps and makes them
browsable, searchable and diffable in a browser. zircon publish puts one
there without leaving the terminal. Entirely optional — nothing leaves your
machine unless you ask it to.
zircon login paste an API key, once
zircon publish game.json --label "1.4.2 (Steam)" gzip, upload, index, URL
No browser flow, no OAuth, no sign-in window. The key lives in one file and
zircon logout deletes it. It never reaches a dump, a log line, or
anything in your project.
Chunked and resumable: kill it halfway and the next run carries on from what the server already has. A 59 MB dump compresses to 3.4 MB — 17×, with a DEFLATE encoder written for this and checked byte-for-byte against zlib.
Publish… sits next to Dump… in the GUI. It fills in the file and the game for you, shows the same progress, and ends on a link. Same key, same config file.
zircon fetch 42 --usmap pulls a published dump's mappings back down, and
--sdk its generated headers.
How it is checked
A tool agreeing with itself proves very little. These are the checks that involve a second opinion — a different implementation, a different way of reading memory, or a compiler.
Both tools were pointed at the same UE 5.6 game and their SDKs parsed for the offset every member carries. Of the 26,625 members present in both, all 26,625 agree exactly. Two independently written dumpers, no disagreement anywhere — which is evidence for Dumper-7 every bit as much as for this.
The same game read from outside the process, from inside it, and from a 7 GB full-memory minidump gives 10,850 of 10,850 types byte-identical, three ways. Two real bugs were found by that check disagreeing with itself — bugs no amount of “the SDK compiles” would catch.
The generated SDK carries a static_assert on every member.
56,396 of them on one game, compiling clean with
windows.h included first, as any injected DLL would. A wrong offset
upstream is a build error here, not a silent misread at runtime.
zircon validate --strict reads
a finished dump back and checks it agrees with itself — members that overlap,
offsets past the end of their class, two bools claiming the same bit, an enum too
narrow for its own values. Pointed at a shipped game for the first time it found two
real defects that all three ways of reading memory had been reproducing
identically, so no amount of cross-checking them would ever have caught it.
Why it survives a patch
Every offset is derived from a property that is structural to the engine rather than incidental to a build. This is the whole design, and it is why the tool works on games patched yesterday and on engine versions that did not exist when it was written.
| Fact | How it is established |
|---|---|
GObjects | the object in slot i stores i in its own InternalIndex — nothing else in the address space does that across a whole array |
| the name pool | block 0 begins with None, always the first name interned |
ClassPrivate vs OuterPrivate | follow each repeatedly: class converges to a fixed point, outer terminates at null |
Offset_Internal | a struct's own properties must tile it — distinct offsets, ending inside it |
| the class default object | it points at an object whose class is this class, named Default__* |
| what an object is | walk its meta-class chain, never its class name — a Blueprint class reports BlueprintGeneratedClass |
PropertiesSize | the root class reports the size of a UObject, and nothing that derives from it is smaller |
UField::Next | it lands on sizeof(UObject), and its chains have to link — not merely terminate |
UObject and carries a UE5 structure inside a 4.27
UStruct, so sizeof(UObject) is 48 where its members imply 40.
Anything anchored on “OuterPrivate is the last member” is
simply wrong there. The rules above ask nothing about where UObject ends,
and a build that extends it is reported rather than smoothed over.
Get it
zircon.exe does everything from outside a game, zircon-gui.exe
is the live browser, and zircon.dll is the injectable payload. You almost
certainly only need the first — injection buys exactly one capability, the
ability to call the game's own functions.
4.2 MB · the three binaries, the Lua plugin host and the docs · MSVC runtime linked statically, so it runs on a clean Windows install
Installer, if you would rather not keep a folder: same files into
Program Files\Zircon, and it can put zircon on your PATH.
The zip needs no installing at all and is the one to take if you are unsure.
Visual Studio 2022 and CMake. Nothing to install — Dear ImGui and Lua are vendored in the repo.
cmake -S . -B build -G "Visual Studio 17 2022" -A x64
cmake --build build --config Release
Find it, check the layout derives, dump it, render it. Four commands, and the last two collapse into one.
zircon detect
zircon fingerprint --pid 12345
zircon dump --pid 12345 --script --defaults \
-o game.json --emit cpp_sdk,usmap
zircon-gui.exe does all of it from one dialog — attach, browse,
and dump any combination of formats on a background thread.
zircon inject
refuses outright when the target has anti-cheat loaded. Patching game code: it edits
reflected property values, which is data the engine already describes and
changes itself, but it never writes over instructions, installs hooks or patches
functions. The injected browser opens its own window rather than overlaying the game,
because an overlay means writing a trampoline over code the game owns. And non-Unreal
engines.