Weymouth, UKSoftware, security, and things built to understand themUpdated 11 November 2026

Tom Taylor

I build software people rely on.

Front page

Personal project · 2026

Reverse engineering Resident Evil 3

Rebuilding the original C source of Resident Evil 3 from the PC game's executable, with each function accepted only when it compiles back to the same bytes.

Diagram of the pipeline, where workers propose C for a function and a coordinator accepts it only if the recompiled bytes match the original

A matching decompilation recovers C source code that, when compiled with the original compiler, produces the same machine code as the shipped game. It is slow, exacting work, which makes it a good test of whether automated workers can do precise work that is checked rather than trusted. To find out, I designed a system of agents to do it: one coordinating agent that plans the work and hands it out to many others, including agents from a different vendor, while I set the rules and make the calls that matter.

The target is Resident Evil 3: Nemesis, Capcom’s 1999 survival horror game, in its Japanese PC release from SourceNext (version 1.1.0). Its main executable is about a megabyte of x86 code, built by a Microsoft compiler that was already old when the game came out. This is the story of the project so far, told from its own records: 345 commits, 37 decision records, a recipe library of more than 2,600 lines, and an evidence note for every function. It includes the parts that went wrong, because the records do.

How it works

  1. Worker agents each lease a function from the original binary and propose C source for it.
  2. A single coordinator compiles each proposal with the original toolchain, Microsoft Visual C++ 6, and compares the result with the shipped code.
  3. A function is credited only after a full replay matches the original executable. Most matches are exact; the rest match once addresses that are only fixed at link time are normalised. Anything else is rejected.

Nothing is accepted on trust: the compiler is the judge.

In practice a single function’s record reads like this. The address and numbers below are made up for this page:

function  0x4A10C0
attempt 1  compiles; 61 of 64 bytes match    rejected (register order differs)
attempt 2  compiles; 64 of 64 bytes match    staged
replay     whole executable rebuilt; matches  credited

A near miss counts for nothing. Closing one is usually a matter of writing the code the way the original programmers probably did, because the old compiler turns small differences in style into different bytes.

Work runs in waves. Workers stage attempts, the coordinator admits, replays and credits them, and each accepted wave is a checkpoint with a reproducible report. There have been 174 of them so far.

How the work was organised

The “coordinator” in that pipeline is itself an agent, a long-running Claude Code session that I set up to orchestrate everything else. It reads the project’s state, decides what the next wave should attempt, writes a brief for each piece of work and starts worker agents to do it. When they report back, it checks what they found against the compiler and decides what is admitted. My own job is to set the rules, review the decision records, and answer the questions that are mine to answer.

Every worker starts with the same preamble. It may read anything, but it works only in its own scratch folder. It may not edit a tracked file, touch the original game files or run git. Only the coordinator changes the repository, admits a match or credits a function. That one rule is what makes it safe to run many workers at once: a worker can be wrong, or even make something up, and the worst outcome is a rejected attempt in a folder nobody else reads. Over the project there have been more than 800 of these worker runs.

More than one kind of agent

The workers were not all the same. Most were Claude subagents started by the coordinator. When the fleet hit the limit of 20 agents running at once, the coordinator began handing work to Codex, OpenAI’s coding agent, by running it from the command line with the same preamble and the same scratch-folder rules. Pools of Codex workers reviewed 175 near misses in one stretch and staged six new matches worth 3,367 bytes, all of which went through the same compiler check as everyone else’s. The compiler does not care who wrote the code.

Mixing vendors had its own lessons. One emulation job was stopped by Codex’s content filter partway through, and a Claude worker picked it up and finished it. Alongside the agents ran plain programs: an unattended orchestrator that drove “permuters”, tools that try thousands of small rewrites of a near miss and keep any that score better. Between them they ran more than 9.2 million compiles. Windows Defender had been rescanning every one of them, which an exclusion fixed.

When the system slipped

The coordinator made mistakes, and the records keep them.

  • A launch before its driver existed. The permuter orchestrator once started a batch while a worker was still building the program meant to drive it. It launched 181 processes with that file missing. All of them exited before compiling anything, nothing they touched was trusted, and the incident was written up like any other.
  • A race in admission. Workers kept staging new matches while an earlier batch was under review, so a late arrival could slip into an admission nobody had reviewed. The fix, admit --only, makes each admission name exactly the addresses that were reviewed and leaves anything later for the next batch.
  • Review slips. Twice a review described a function’s behaviour wrongly, once for 0x00402830, where it left out nine indirect calls. The bytes and the match were never affected, but the claims were wrong, so both were corrected in later commits and kept in the record.

Who gets to say yes

Some decisions belong to me, and the system was built to keep it that way. When the port first started, it stopped at the game’s own installation check. The coordinator’s next step was to bypass that check, and Claude Code’s permission system blocked it as a change that weakens security. The coordinator brought the question to me instead of finding a way round. Along the way it relayed an approval to a worker on my behalf, and the worker declined to act on it, because a message from another agent is not the owner’s consent. I approved the change in chat myself, and only then was it made.

Budget was a decision too. The agents run on a weekly usage allowance, so the coordinator planned waves around what was left and wound workers down cleanly instead of leaving work half done. Late one night I decided that usage mattered more than CPU time, and the permuters and their orchestrator were paused.

Ground rules, before any code

The first decisions were about what the project would never do. The installed game is kept as a preserved master that nothing runs, patches or reformats; every experiment works on a copy. The repository admits only reviewed source, tools, tests and short metadata, through explicit allowlists. Game files, assets, saves, compiler installations and bulk disassembly stay out of it. No game code or data is published, then or now.

Decompiled pseudocode is treated as a hypothesis, not as source. Every recovered function needs an address, a prototype with evidence for its calling convention, a confidence level, a verification status and a list of open questions. A field seen at some offset can justify a small view of that memory; it does not justify inventing a whole structure. Names stay provisional until something in the binary backs them up.

Then the compiler. The executable’s linker header says version 6.0, which is a lead, not proof. Its Rich header, a block of build metadata the Microsoft linker leaves in every executable, lists 15 records: mostly objects from Visual C++ 6 Service Pack 5 (build 8966), some from an older build (8447), and a few from later tools that probably came in with libraries. I installed the command-line compiler from a German Visual C++ 6 disc, build 8168, and pinned it. That gap between the compiler I had and the compilers the metadata names comes back several times in this story.

First matches, and a pass that produced nothing

The first recorded progress was eleven functions: 123 bytes out of a 1,096,778-byte code section, 0.01%. Six matched exactly; five matched once their address operands were checked and normalised.

The first attempt to speed things up was an instructive failure. Six workers prepared hypotheses covering 1,876 bytes, but none could be compiled into a valid comparison until some object-file details were settled. The one that did compile, the startup function at 0x00401000, came out at 661 bytes instead of 672, with differences all through it. No amount of editing metadata could turn that into a match. The pass ended with zero new verified bytes, and the handoff notes said so plainly instead of counting prepared sources as progress.

Small functions are easier, so the function count rose far faster than the byte count. By wave 25 the project had 1,567 functions but only 139,680 bytes, 12.7% of the code section.

When the process was the bottleneck

The early pipeline was careful to the point of paralysis. Separate approvals for the source, the compiler run, the leased inputs and the handoff meant that, at one point, roughly 44 bespoke scripts were being written for every function admitted. Two agents sharing one checkout made coordination its own engineering problem: decision 0010 gave a single coordinator every tracked edit and git write, with the other lane working on a disjoint range and a handshake to pass control. When work was interrupted mid-wave, the record says exactly what was and was not accepted, and an unaccepted report with 1,608 rows was never quoted as coverage.

A process review found the tools healthy and the ceremony expensive. Decision 0011 replaced it with one driver, wave.py. Workers run next → native → try → stage; the coordinator runs acquire → admit → replay → credit → publish. Each function’s registry entry became one JSON file. Leases expire after two hours, attempts are logged append-only, and each function gets three distinct source attempts before a retry has to give a concrete new reason. A reviewer can block a batch only on a concrete defect: copied code, a wrong boundary, a gap in provenance, or a misleading claim.

The effect was immediate. Waves 35 to 46 took the total from 157,736 to 397,706 bytes, from 14% of the code section to 36%. Speed also exposed small defects in the pipeline itself: released leases that silently vanished from selection, an interrupted empty attempt that blocked the next one, 72 evidence notes missing their standard fields. Each was fixed and recorded.

Opening the gates

By wave 46 the easy candidates were gone, and much of what remained was code the comparator could not yet judge. Phase two (decision 0012) widened the checks one evidenced case at a time, never by relaxing them in general:

  • Field offsets. About 25 near misses referred to a field inside a known global. The rule now allows exactly that compiled offset, checked against the original operand, and nothing else.
  • Switch statements. Jump tables, index tables and their padding are reviewed as one envelope and replayed exactly. Only the code is credited, never the tables. A later survey fix found 39 switch functions, 46,560 bytes, that had been hiding behind a scanning bug.
  • Exception handling and C++ classes. Unwind tables, compiler-generated vtables, deleting destructors and adjustor thunks are compared as complete graphs and reserved as zero-credit attachments.
  • A second compiler. With the original’s metadata pointing at Service Pack 5, I obtained it from Microsoft’s signed installer and pinned it separately. Decision 0022 allows it for a function only after the same source has failed under the base compiler, and a match under it is evidence, not proof of how the original was built.
  • A broader search. The candidate survey learned to find switch roots, functions reached only through pointers, tail calls and tiny stubs. Each new rule has a switch that reproduces the old output exactly.

The results show in the accepted waves: past half of the code section at wave 51, nearly two thirds by wave 58.

Going backwards on purpose

At wave 66 the percentage went down, deliberately. A library matcher identified 488 regions, 102,451 bytes, as code from Microsoft’s C runtime, DirectInput and the Independent JPEG Group’s libjpeg 6a, by rebuilding those libraries and matching them byte for byte. That is real, verified code, but it is not recovered game source, so it now counts in its own category with zero credit. Twenty-eight functions that had already been credited turned out to be library code and were retired. The wave added a function and still reported a net loss of 1,200 bytes.

I then asked for the coverage numbers to be as accurate as possible. Decision 0025 keeps the original measure, so that old and new figures stay comparable, and adds a conservative second one that removes only what is proved not to be code: 28,425 bytes of padding, 8,928 of jump tables, 1,757 of index tables and 4,800 of library data. Anything not proved non-code still counts as code.

That is why this page gives more than one number, and they must not be added together:

Measure What it counts At wave 174
Code section Credited bytes of the whole 1,096,778-byte code section, including the runtime libraries the game links 86.3%
Game address range Credited bytes of the game’s own address range, padding and tables included 95.5%
Game code Credited bytes of the game’s own code, with proven non-code removed 99.2%
Library code Verified library code, with no recovery credit 92.3% of the runtime’s code

Does the recovered code actually run?

A byte match proves code generation. It does not prove that the recovered functions work together, so integration is tracked as a separate set of milestones.

The first step placed every credited body back at its original address in a copy of the executable and required the result to be identical to the original. That checks the harness, not the code, because an identical image runs identically. A placement linker then built the image from a blank template, resolving all 26,679 relocations by symbol name from the reviewed registry. It reproduced the original even from a deliberately corrupted template.

The first build that really differed moved 2,997 recovered functions into a new code section and left forwarding stubs at the old addresses. Most PC players run the game through Classic REbirth, a community compatibility patch that rewrites parts of the executable in memory, which complicated things: some functions had to stay where the patch expects them. The relocated build reached the main menu and intro movie, and stack samples showed the moved copies executing. The next one moved 3,239 functions and played through the opening scenes, the first gameplay room, a death and the return to the title, with screenshots matching the original.

Recompiling the same sources with different optimisation settings found a real bug. One function kept a one-byte local that a callee wrote four bytes into. The original’s stack frame happened to tolerate it; a smaller frame crashed. The credited source stayed as it was, because it is byte-exact, and three similar hazards were recorded. Running original and recompiled functions side by side in an x86 emulator, 500 random states each, found two more: one function with its first two pointer arguments reversed, and one that processed four slots where the original processed three.

Later, every function without a match yet was given its best near-miss source, and the whole game was built from source. I played that build from a save in the final area through to the ending, and from a new game up to the warehouse, without problems. That is one player’s observation of provisional code, not credit, but it changed the question from “can it be done” to “how complete and how clean”. A later build linked by the original Visual C++ linker took only 14,086 bytes of code from the original, most of them kept only for Classic REbirth. Everything outside the code, from data tables to resources, can be generated by tools that read the player’s own copy of the game, check its hash and write into an untracked build folder (decision 0028). Nothing extracted ever enters git.

A top-down camera view of Jill surrounded by zombies in a Raccoon City street
Resident Evil 3 running from recovered source, here in the modern port built on it.

The game also ships a second executable for its Mercenaries minigame. Comparing the two showed it is the same build with a single #define: one #ifdef block, one extra 80-byte function, three functions the linker folded differently, and otherwise only shifted addresses. Every shared function replays against it too, reported separately and never added to the main credit.

The craft: persuading a 1999 compiler

Once a function does the right thing, the remaining difference is usually register allocation, instruction order or stack layout. The compiler makes those choices across the whole function, so a change to one line can fix the first mismatch and create ten new ones elsewhere. Workers compare the complete function after every change, and everything they learn goes into a shared recipe library, including the things that did not work.

Some entries read like folklore until you see the evidence:

  • In one function, the order of a three-term dot product was decided by the initialisation order of pointer locals, not by the order of the arithmetic.
  • Which floating-point temporaries stay on the x87 stack depended on the order the vertices were stored.
  • The compiler decides whether to keep a constant in a register by counting its uses as written, before it removes duplicate code, so a use far away could fix a register choice here.
  • Globals held in registers are reloaded after a call in the order they first appear in the source, so a few statements that do nothing at the top of a function set that order.
  • The compiler switches strategy at a function-size threshold that counts dead code, so a few dead expressions can flip an evaluation order.

The most useful single discovery was the constant-false operand:

if (state == 3 || (cursor && 0)) {
    /* ... */
}

cursor && 0 is always false, so it emits no code. But the compiler counts it as a use of cursor before it optimises it away, and that is enough to change which register cursor gets. Within hours of finding it on one function, workers had used variations of it to fix spill choices, keep a constant in a callee-saved register, stop two loop pointers being merged and block a 16-bit narrowing. The library records its limits as carefully as its uses: x & 0, x * 0 and 0 && x are folded too early to have any effect, and in some positions it emits real code.

A recipe is a description of how this compiler behaves, not a claim about what the original programmers wrote. Where a match relies on such a device, the source says so in a comment.

When guessing stopped working

As the remaining functions got harder, several approaches were tried and measured, and most of what they produced was negative results. They are kept, because they stop the next attempt repeating them.

  • Allocator experiments. 3,109 synthetic test functions produced rules that predicted held-out cases very well, 120 out of 120 in some families, and then completed zero of the thirteen real near misses they were aimed at. Later rounds found nine repairs, worth 2,982 bytes.
  • Translation-unit context. Another decompilation of a game from the same era had found that surrounding code can steer an older Microsoft compiler. Across 1,782 comparisons on this one, unused declarations, names, file paths, pragmas and neighbouring functions changed nothing, apart from one effect already known.
  • Brute force. Source permuters ran more than nine million compilations of random and guided variants. They found nine winners, 4,358 bytes, each then minimised by hand until the surviving edit could be explained. One needed only a change in the order two fields were read, found after 57 probes.
  • Proving equivalence instead. Static equivalence checkers and differential emulation tried to show that a near miss behaves the same as the original even if its bytes differ. The checkers were conservative enough to be of limited use, and a reviewer built a false positive that exposed a real flaw in one of them, which was fixed. The emulator was better at finding bugs than at proving their absence.

What finally worked was measuring the compiler’s back end instead of guessing at it. Each model below was built against the pinned compiler and checked on functions that had already matched before it was used on new ones:

Model What it explains Check
Register allocator How named values are coloured, and how expression temporaries rotate through EAX, ECX and EDX 99.86% of held-out cases
Spill slots How stack slots are sorted by size and reference count, and packed into dead parameter homes About 470 matched frames, no mismatches
Scheduler How each block is reordered for the Pentium’s two pipes 5,765 of 5,765 held-out blocks
Alias rules Which loads and stores the scheduler may swap, including a 31-location limit per base pointer Five new matches in three waves
Block copies Why writing a shared block once per case shifts the registers after it Two switch functions stuck since early on

The recipes stopped being devices that happened to work and became explanations of why they work. The models also sorted the remaining functions by what was actually wrong: of 83 examined at one point, 9 differed only in register colour, 6 only in scheduling, 68 in structure, and none only in stack slots.

Making it readable

For most of the project, credited sources were frozen, which left them full of names like sub_0043BFE0. Decision 0027 allows a credited source to take a readability change (names, types, struct members, comments) only if recompiling every affected function gives exactly the credited bytes. A change that alters a single byte is rejected whole, not tuned.

The names come from a knowledge base in which every entry carries its evidence and a confidence level. Fourteen research passes went through the engine one subsystem at a time: the script interpreter, with 144 opcodes named; actor structures, with 179 fields; rooms and file formats; rendering; audio; menus and saves; the platform layer, where 193 small wrappers recreate the console SDK the game was ported from; collision, effects, text and maths. Then a stricter review downgraded 267 of the 281 entries marked “confirmed”, keeping only the 14 backed by a string, an import or SDK identity. A spot check of 400 names judged 85% right, 1% wrong and 13% weak, and the wrong and weak ones were fixed.

Writing up the architecture exposed contradictions between researchers, which were settled against the binary. A function one researcher had called a script restart turned out to run on every tick, so it is now the per-tick script step.

Applying the names hit one wall. Adding any declaration above a function shifts the compiler’s internal label numbers, and the reviewed switch envelopes pinned those labels, so hundreds of renames were being refused even though every byte was identical. A tool now updates only those label names, and only after proving every byte, relocation, table and label offset unchanged. Since then, more than a thousand credited sources read with proper function, field and parameter names.

The final hundred

From wave 148 the matching sped up again, with the models in hand and the recipe library sorted into a playbook by kind of mismatch. Waves 148 to 164 took the game-code measure from 93.1% to 98.6%. Along the way:

  • Floating-point constants. When source says x * 8.0f, the compiler stores 8.0 as a tiny named constant that the linker pools. Every attempt to write it another way changed the instruction order, so a literal was the only faithful source. Decision 0033 admits exactly those constants, checked bit for bit against the original’s read-only data, with no credit for their bytes.
  • A function that was not a function. A six-byte “function” at 0x004700B0 turned out to be the tail of the one before it, which has no return of its own and runs straight into it. Its only evidence had been a number in a data table that happened to look like an address. It was retired and its predecessor registered at its real length.
  • One instruction. A sound decoder at 0x00411270 was parked for days one instruction away: the compiler emitted xor eax,7 where the original has cmp eax,7. It matched on its twentieth attempt, after a worker found that reading a value twice in a condition stops the compiler substituting it away.
  • The patched function. The last function Classic REbirth rewrites internally, and so the last one the source build still had to borrow, matched in wave 153.

By wave 165 a census of the game’s code left exactly 14 functions, 13,239 bytes, every one of them attempted many times before. There was no unexplained remainder and no untried root.

One old theory was settled here, and it corrects an earlier version of this page. The near misses had looked like the work of a different compiler build, since the metadata names builds I had not tested. I sourced the Service Pack 3 compiler too and compiled all 134 logged attempts for the twelve functions then remaining under it. Every one was byte-identical under all three builds. The compiler does not explain the gaps; they are in the source, which means they can be closed by writing better source.

Six of the fourteen have closed since then. The last, at wave 174, was a 1,534-byte switch function. A trace of the compiler’s internal sort keys showed that a value only merged into a shared s->z0 / 18 the way the original’s did if that division was evaluated first, so the fix was to name the earlier quotient before the test that uses it, and five older devices could be deleted.

Where it stands

As of wave 174, on 22 October 2026, the pipeline has recovered 3,687 functions, verified against the original compiler’s output. Of the game’s own code, 99.2% is recovered source; eight functions, 7,798 bytes, remain. The closest has the original’s size and stack frame and differs only by two registers swapped in its last few instructions. Another has the right size and four differing lines.

Verified bytes of original code, by waveA line rising slowly from 140 KB at wave 25, jumping to 398 KB by wave 46 and 727 KB by wave 58, dipping slightly at wave 66 when library code was taken out of the count, then climbing steadily to 947 KB at wave 174.0 KB250 KB500 KB750 KB1 MB255075100125150174WaveWave 25: 1,567 functions, 139.7 KBWave 26: 1,571 functions, 140.8 KBWave 29: 1,591 functions, 145.4 KBWave 30: 1,593 functions, 146.2 KBWave 31: 1,595 functions, 146.4 KBWave 32: 1,597 functions, 147.0 KBWave 34: 1,599 functions, 147.2 KBWave 35: 1,628 functions, 157.7 KBWave 36: 1,684 functions, 181.1 KBWave 37: 1,752 functions, 211.4 KBWave 38: 1,822 functions, 242.4 KBWave 39: 1,893 functions, 276.0 KBWave 40: 1,960 functions, 301.6 KBWave 41: 2,008 functions, 318.2 KBWave 42: 2,073 functions, 338.6 KBWave 43: 2,133 functions, 353.9 KBWave 44: 2,201 functions, 375.2 KBWave 45: 2,266 functions, 393.3 KBWave 46: 2,285 functions, 397.7 KBWave 47: 2,368 functions, 419.1 KBWave 48: 2,468 functions, 461.2 KBWave 49: 2,642 functions, 503.7 KBWave 50: 2,725 functions, 547.3 KBWave 51: 2,813 functions, 577.2 KBWave 52: 2,886 functions, 619.2 KBWave 53: 2,962 functions, 630.3 KBWave 54: 3,021 functions, 655.7 KBWave 55: 3,127 functions, 676.2 KBWave 56: 3,223 functions, 703.0 KBWave 57: 3,289 functions, 717.7 KBWave 58: 3,312 functions, 727.0 KBWave 59: 3,314 functions, 727.8 KBWave 60: 3,365 functions, 733.3 KBWave 61: 3,385 functions, 736.6 KBWave 62: 3,391 functions, 738.1 KBWave 63: 3,398 functions, 738.9 KBWave 64: 3,400 functions, 741.6 KBWave 65: 3,410 functions, 747.2 KBWave 66: 3,383 functions, 746.0 KBWave 67: 3,385 functions, 746.7 KBWave 68: 3,387 functions, 747.3 KBWave 69: 3,390 functions, 748.4 KBWave 70: 3,396 functions, 750.4 KBWave 71: 3,399 functions, 752.4 KBWave 72: 3,405 functions, 754.9 KBWave 73: 3,411 functions, 757.3 KBWave 74: 3,413 functions, 758.0 KBWave 75: 3,421 functions, 761.3 KBWave 76: 3,426 functions, 763.4 KBWave 77: 3,432 functions, 766.0 KBWave 78: 3,436 functions, 767.2 KBWave 79: 3,442 functions, 768.3 KBWave 80: 3,446 functions, 772.0 KBWave 81: 3,451 functions, 773.4 KBWave 82: 3,456 functions, 776.6 KBWave 83: 3,460 functions, 778.6 KBWave 84: 3,464 functions, 780.9 KBWave 85: 3,468 functions, 784.5 KBWave 86: 3,470 functions, 784.8 KBWave 87: 3,474 functions, 787.8 KBWave 88: 3,475 functions, 789.5 KBWave 89: 3,476 functions, 791.8 KBWave 90: 3,482 functions, 792.3 KBWave 91: 3,483 functions, 792.8 KBWave 92: 3,485 functions, 796.6 KBWave 93: 3,487 functions, 800.4 KBWave 94: 3,488 functions, 803.4 KBWave 95: 3,490 functions, 805.0 KBWave 96: 3,491 functions, 805.5 KBWave 97: 3,493 functions, 806.5 KBWave 98: 3,495 functions, 807.6 KBWave 99: 3,496 functions, 808.9 KBWave 100: 3,501 functions, 811.0 KBWave 101: 3,502 functions, 811.2 KBWave 102: 3,504 functions, 811.6 KBWave 103: 3,507 functions, 813.1 KBWave 104: 3,509 functions, 813.5 KBWave 105: 3,511 functions, 814.2 KBWave 106: 3,513 functions, 815.6 KBWave 107: 3,515 functions, 816.2 KBWave 108: 3,516 functions, 820.0 KBWave 109: 3,517 functions, 820.3 KBWave 110: 3,528 functions, 832.0 KBWave 111: 3,537 functions, 840.6 KBWave 112: 3,545 functions, 846.5 KBWave 113: 3,545 functions, 846.5 KBWave 114: 3,563 functions, 853.1 KBWave 115: 3,568 functions, 855.0 KBWave 116: 3,568 functions, 855.0 KBWave 117: 3,568 functions, 855.0 KBWave 118: 3,568 functions, 855.0 KBWave 119: 3,568 functions, 855.0 KBWave 120: 3,569 functions, 855.7 KBWave 121: 3,569 functions, 855.7 KBWave 122: 3,569 functions, 855.7 KBWave 123: 3,569 functions, 855.7 KBWave 124: 3,572 functions, 857.0 KBWave 125: 3,575 functions, 858.4 KBWave 126: 3,575 functions, 858.4 KBWave 127: 3,577 functions, 859.5 KBWave 128: 3,577 functions, 859.5 KBWave 129: 3,578 functions, 860.1 KBWave 130: 3,578 functions, 860.1 KBWave 131: 3,581 functions, 860.5 KBWave 132: 3,582 functions, 861.0 KBWave 133: 3,585 functions, 862.4 KBWave 134: 3,588 functions, 865.2 KBWave 135: 3,594 functions, 869.1 KBWave 136: 3,596 functions, 869.4 KBWave 137: 3,597 functions, 869.5 KBWave 138: 3,599 functions, 869.9 KBWave 139: 3,601 functions, 870.5 KBWave 140: 3,603 functions, 873.1 KBWave 141: 3,606 functions, 875.8 KBWave 142: 3,608 functions, 879.6 KBWave 143: 3,611 functions, 883.6 KBWave 144: 3,616 functions, 885.4 KBWave 145: 3,616 functions, 885.4 KBWave 146: 3,618 functions, 886.2 KBWave 147: 3,618 functions, 886.2 KBWave 148: 3,619 functions, 888.0 KBWave 149: 3,619 functions, 888.0 KBWave 150: 3,625 functions, 890.3 KBWave 151: 3,631 functions, 892.5 KBWave 152: 3,632 functions, 892.9 KBWave 153: 3,639 functions, 898.2 KBWave 154: 3,645 functions, 907.7 KBWave 155: 3,651 functions, 912.0 KBWave 156: 3,655 functions, 917.5 KBWave 157: 3,660 functions, 921.8 KBWave 158: 3,662 functions, 924.5 KBWave 159: 3,665 functions, 928.5 KBWave 160: 3,671 functions, 933.7 KBWave 161: 3,674 functions, 936.4 KBWave 162: 3,676 functions, 937.8 KBWave 163: 3,677 functions, 938.4 KBWave 164: 3,679 functions, 940.2 KBWave 165: 3,679 functions, 940.2 KBWave 166: 3,679 functions, 940.2 KBWave 167: 3,680 functions, 940.6 KBWave 168: 3,681 functions, 941.2 KBWave 169: 3,682 functions, 941.7 KBWave 170: 3,683 functions, 943.4 KBWave 171: 3,684 functions, 943.7 KBWave 172: 3,685 functions, 944.2 KBWave 173: 3,686 functions, 945.1 KBWave 174: 3,687 functions, 946.7 KB947 KBnew wave process← switch and EH gates open↑ library code taken outthe final hundred
Verified bytes of original code, by wave, from the project's accepted-wave records. Each point is a checkpoint that passed a full replay. The dip at wave 66 is library code being taken out of the count. Hover a point for its function count.

None of this means the decompilation is finished. Eight functions are left, and some have resisted dozens of attempts. Matching every function is not the end either: the final milestone is a clean build whose whole file is identical to the original executable, with every byte accounted for.

What the project does already show is narrower and, to me, more interesting. Precise work can be split across many workers without anyone trusting anyone, as long as there is a judge that cannot be argued with, every claim is checked against it, and the records keep the failures as carefully as the wins.

The recovered source also runs as a modern Windows port, with high frame rates and widescreen and the same game logic, tick for tick. That is its own story: Porting Resident Evil 3 to modern PCs.

The work uses my own copy of the game. No game assets or code are distributed.