benilla-re: 2,067 Function Rows Now Name a Test That Proves Them

By Indie Kings | October 21, 2026

Updated October 21, 2026: benilla-re, the reverse engineering of the World of Warcraft 1.12.1 client, has moved from naming the client's functions to reimplementing them and proving the reimplementation. 2,067 of its 37,967 function rows now name a test, and twenty-one commits landed between October 17 and October 21, eighteen of them touching the function table. The new work in that window is concentrated in a handful of client systems: the auto-loot sweep, the game clock, screen scaling and the worldport console command each gained a reimplementation and a test.

This site covered the project on October 17, when the headline was a symbol table of 37,967 named functions with every claim graded by the evidence behind it. That part has not changed. What has changed is what the project does with the names. The crates under crates/ are no longer a naming exercise. Each one reimplements a slice of the client's behaviour, and the harness in crates/difftest runs the client's own code inside an emulator beside the reimplementation and compares the two bit for bit.

benilla-re repository banner

Image: benilla-re repository banner. Image credit: samwhosung/benilla-re, via the repository.

The harness: the real client running beside a copy

The method document describes the arrangement in one line: a bit-exact test runs the exe's own code in the Unicorn emulator beside the reimplementation. The client is not shipped. It is the reader's own copy of WoW.exe from their own 1.12.1.5875 install, and the harness refuses any other build by SHA-256.

The crate is called benilla-re-difftest, and its own comment explains the trick. It runs the executable's own code in the Unicorn x86-32 emulator on chosen inputs so a reimplementation can be compared with the client bit for bit. The executable is located through the BENILLA_RE_WOW_EXE environment variable, or at WoW/WoW.exe inside the repository, and when it is absent the crate returns nothing and writes one skip line. Set BENILLA_RE_REQUIRE_EXE=1 and the same situation panics instead, so that a skip can never pass as a test.

That is the design detail that matters most. A differential test that quietly skips when the target is missing is a test that reports success while proving nothing, and the project made the loud version the opt-in. The install's Data folder is treated the same way, with BENILLA_RE_REQUIRE_DATA=1, and a skip log file lets a runner count the skips rather than infer them.

Emulation has to be bounded, and the harness bounds it by instruction count rather than by time, so the limit is the same under load. The budget is 200 million instructions per emulation, and the crate records that the largest healthy case runs about 10 million.

There is also an inventory tool. A coverage variable names a file to which each test appends every executed basic-block address once, and a separate variable collects the x87 instructions a test runs that the emulator computes in the host's double precision, as a census with the address and the mnemonic. More on that below, because it is the interesting failure mode.

The x87 problem and the census that tracks it

The client runs the x87 floating point stack at 53-bit precision, rounding to nearest, with every exception masked, and the method document pins the control word at 0x023F. It traces how that gets set: WinMain calls __clearfp and then __control87(0x9001F, 0xFFFFFFFF), which turns the exception bits into the mask, adds the denormal bit and the 53-bit precision bit, and loads the result with fldcw. The Direct3D 9 device is then created with flags that include D3DCREATE_FPU_PRESERVE, so the graphics device never lowers the precision either.

Every finding and test in the project assumes that control word unless it says otherwise. The emulator's x87 is not the client's at every instruction, and the project does not pretend otherwise. Ten transcendental and remainder instructions, f2xm1, fyl2x, fyl2xp1, fsin, fcos, fsincos, fptan, fpatan, fprem and fprem1, convert their operands to the host's double, call its C library and convert back.

The consequences are specific and the project writes them down. A result carries 53 significant bits where the real x87 rounds at 64, and the two remainders are computed in double rather than exactly. The edges differ too: fsin, fcos, fsincos and fptan reduce an operand that rounds to plus or minus 2 to the 63rd power as a double, which the emulator reduces as a double, answering with infinity and the C2 flag set, where the x87 reports the operand out of range and returns the indefinite. The two remainder instructions give the host's positive NaN for an infinite or NaN operand, a zero divisor, or exponents far enough apart that their scale overflows a double, and with exponents 53 or more apart their partial remainder is not the x87's at all.

The rule that follows is the one a test follows. A test whose compared values pass through one of those instructions shows the code around the instruction, not the client's result there, and keeps its inputs away from the other differences. The census file records every address of the ten that a test runs, and a test that runs one on an operand where the answers differ beyond rounding writes a second line marked edge. A test with an edge line compares no value that passes through it there.

That is a level of honesty about the limits of a verification method that most projects do not reach. The alternative would be to quietly compare a double against a 53-bit x87 and call a mismatch a bug in the reimplementation.

What landed in four days

Between October 17 and October 21 the repository took 21 commits. Eighteen touched the function table, thirteen touched the crates, and six touched the tables. The work is not random: it reads like a client being walked system by system.

The clearest example is auto-loot. The commit that added it is titled "Auto-loot's sweep and LOOT_OPENED, and the DB cache's callback order", and the new document for it describes what the client's shift-click sweep does at 0x4c1fa0 in LootFrame.cpp. The sweep returns at once while item records are pending, sends CMSG_LOOT_MONEY when the money cell is not zero, plays the coin sound and prints the money line once per source outside a party and a raid, sends each row of loot type 0 as a click does, except that a bind-on-pickup row of quality 2 or more raises LOOT_BIND_CONFIRM on the first such row and leaves the rest alone. It then clears the player's auto-loot byte, and closes the loot with release when nothing is left, or signals LOOT_OPENED again if the flag it was given is zero.

That last part is the kind of detail that makes the approach worth reading. The sweep's sends change no row and not the money cell: a row disappears when the server's SMSG_LOOT_REMOVED arrives, and the coin row when SMSG_LOOT_CLEAR_MONEY does, both after the sweep has already returned. The reimplementation in crates/ui/src/auto_loot.rs models the calls the sweep makes and the two cells it stores, and the test reads the 16 rows and the money cell back after each sweep to find them unchanged.

The game clock got the same treatment. The new document covers the server's game-time messages and what they do to the clock, and the crate in crates/gametime reimplements the time-of-day computation, noting that the client runs the x87 arithmetic at 53-bit precision. The decode of the packed date record is the sort of thing that only matters because something else depends on it: seven int32 fields are read out of a dword, minute at bits 0 to 5 and hour at 6 to 10 and weekday at 11 to 13 and day at 14 to 19 and month at 20 to 23 and year at 24 to 28, with a two-bit field at 29 and 30, and a field whose bits are all ones is stored as minus one. No field is range-checked, so a decoded hour can reach 30 and a minute can reach 62.

Screen scaling was documented and reimplemented as a pair, four dwords set from the display's aspect ratio with 446 other call sites scaling, unscale or reading through them, and the worldport console command modelled as a module, reading what CCommand_WorldTeleport takes from its argument text, including the delimiter set at 0x82ecc8, which is tab, carriage return, line feed, quote and space, and no comma, and the usage line the command prints when the arguments do not parse.

Chat channels, the item record's owner signal, the ammo callback, the chat token expander and PickupContainerItem all landed in the same window, alongside a commit that covers the channel notify message that confirms, renames and drops the client's ten joined channel slots.

The shape of the repository now

The tree is bigger than the symbol table. The project holds 1,648 files, of which 507 are markdown documents under systems/ covering 40 documented systems, 23 are tab-separated tables under tables/, 971 belong to 37 Rust crate directories, and 66 sit in tools/. The symbol table names 45 distinct systems, and seven of them, including the base, the C runtime and the Direct3D libraries, have no document of their own.

The tables have their own rules. Each one records facts a program reads, with a header row, one row per fact, and a generator in tools/tables/. Nothing in them is edited by hand, because per the tables README a wrong row is a wrong generator. The generators are Python 3 with the standard library only, they read the executable and refuse any other build by size and SHA-256, and running one twice gives byte-identical output. The check command compares each table's header against the columns the README gives it, and with the executable present it regenerates every derived table into a scratch directory and fails on any byte that differs from the committed one.

That is the point of the whole arrangement, and the Oct 17 piece made it well. A symbol table that nobody checks drifts into folklore. A symbol table where every row is graded, every table is regenerated, and every claim can be turned into a test against the real binary does not drift quietly, because the build fails.

MeasureValue in the repository on October 21, 2026
Function rows in the symbol table37,967
Rows that name a test2,067
Distinct test names2,115
Rows claiming two independent readings reconciled165
Rows with no owning system yet5,889
Documented systems40
Markdown documents under systems/507
Tab-separated tables23
Rust crate directories37
Commits between October 17 and October 2121
Commits since October 13, 2026121

On the licence, the README is specific and slightly unusual. The code, the symbol table, the docs and the tables are all under MIT or Apache-2.0 at the reader's option. The exception is the test harness, which links the Unicorn emulator, a GPL-2.0 project, as a test-only dependency, and the crate's own comment notes that it is only ever a dev-dependency of the system crates, so nothing that ships links it.

What is still unfinished

The gap between a documented client and a reimplemented one is visible in the numbers. 5,889 of the 37,967 function rows still have no owning system, which means the project has a name for the function and has not yet worked out which part of the client it belongs to. The README's method document describes how a system claims a file's functions, by a listed address span, a source path, an initializer run or a stretch of code, and says a row stays unassigned until its file is proved.

The repository is also young, at 121 commits, and it is not a place where a claim is made lightly. The same method document that pins the x87 control word also lays out checklists for reading the bytes, for proving a finding by absence, for writing a finding and for writing an observation, which is a lot of ceremony for a project that started on October 13.

What it buys is a reference that can be checked by a machine instead of trusted. That is the part worth carrying forward.

FAQ

What is benilla-re?
A public reverse engineering of the World of Warcraft 1.12.1 client, read at its bytes and cited by address. Everything ships as plain files, and only the tests, the table generators and the listing read WoW.exe from your own copy, which they check by SHA-256 and refuse to substitute.

How does it test a reimplementation?
The harness in crates/difftest runs the client's own code in the Unicorn x86-32 emulator on chosen inputs, beside the reimplementation, and compares the results bit for bit. The executable is found through an environment variable or at WoW/WoW.exe, and if it is missing the test skips with one line unless you set the require variable, which makes it panic instead so a skip cannot pass as a test.

What does the project not cover?
The 200 million instruction budget per emulation bounds what a test can show, and the largest healthy case runs about 10 million. Separately, the emulator's x87 differs from the client's at ten transcendental and remainder instructions, so the project keeps a census of every address a test runs through one of them and refuses to compare a value that passes through it there.

Is the client binary included?
No. The README says the symbol table, the docs and the tables are plain files that need no executable, and that nothing here comes from a WoW install. Bring your own legally obtained client.

What licence applies?
MIT or Apache-2.0 at your option, for the code, the symbol table, the docs and the tables. The test harness links Unicorn, which is GPL-2.0, as a test-only dependency, so nothing that ships links it.

How much of the client is understood?
37,967 function rows carry a name and a confidence grade, 2,067 of them name a test, and 165 claim two independent readings reconciled at the bytes. 5,889 rows are still marked unassigned to a system, which is the honest measure of what is left to do.

Our take: The interesting shift is not the volume of the work but its shape. A reference that names functions is useful; a reference whose names come with a running test against the original binary is a different kind of artifact, because the next person can disagree with it and find out immediately. The x87 census is the part to admire, since a project that quietly compared doubles against a 53-bit stack would have shipped confident wrong answers and never known.

Bottom Line

benilla-re is the MIT or Apache-2.0 licensed reverse engineering of the World of Warcraft 1.12.1 client, and as of October 21, 2026 it is no longer only a map of the binary. 2,067 of its 37,967 function rows name a test, 1,541 distinct tests exist, and the project reimplements client behaviour in Rust and runs the client's own code beside the reimplementation in the Unicorn x86-32 emulator, comparing results bit for bit. Twenty-one commits landed between October 17 and October 21, adding reimplementations of auto-loot, the game clock, screen scaling and the worldport command. 5,889 function rows are still unassigned to a system, which is the honest measure of the work left. The client's own binary is never included, and the test harness links the GPL-2.0 Unicorn emulator as a test-only dependency so that nothing shipped carries it.

Sources

samwhosung/benilla-re repository README and its method document, including the harness rules, the x87 control word and the emulator's ten differing instructions, the confidence and proof grades, the licence terms and the citation file, verified October 21, 2026.

The repository's own symbols/functions.tsv, crates/difftest/, the crates for auto-loot, gametime, screencoord and worldport, and the system documents for auto-loot, game time, chat channels and screen coordinates, for the counts, the addresses and the test names, verified October 21, 2026.

GitHub commit history for samwhosung/benilla-re, for the 21 commits between October 17 and October 21 and the 121 commits since October 13, verified October 21, 2026.

Related: benilla-re: 37,967 Function Rows Cover WoW 1.12.1.5875

Share