The decompilation recovers Resident Evil 3’s source code one byte-identical function at a time. That source is only useful if it can become a program again, so alongside it I built a modern port: the same game code, compiled with a current compiler, on a new platform layer, with no original executable, no DirectDraw and no compatibility patch.
The rule throughout is that the port must play exactly like the original. Every improvement is a picture or comfort option that leaves the game’s logic untouched, and that is checked, not assumed.
It was built the same way as the decompilation, by a coordinating agent and its workers working to rules I set. It is a private personal project. It contains no game data: the build reads the player’s own copy of the game, checks it, and generates what it needs into a folder that never leaves their machine.
Why a port at all
Resident Evil 3’s PC release is a game from 2000 talking to Windows through DirectDraw, Direct3D, DirectInput and DirectSound. Most players today run it through Classic REbirth, a community patch that rewrites parts of the executable in memory to keep it working. That works, but it is a patch on a binary nobody can read.
With the source recovered, there was another way: rebuild the game as an ordinary modern program, where every fix is a line of readable code and every enhancement is an option. The decompilation already had a build that ran from source with the original compiler. The question was whether that source would survive a compiler and a platform it was never written for.
A first link
The first probe was humbling: the machine had no modern compiler at all. A check with the clang parser alone found that 96.45% of the game’s 3,520 source files already parsed as 32-bit code. Of the failures, 122 were the project’s own guards that insist on the original compiler, and only four were real old-compiler habits. There is no inline assembly anywhere in the game.
I installed a portable, per-user toolchain (LLVM, CMake, Ninja and SDL3, with Microsoft’s runtime and Windows SDK under their licences), and every function was compiled with clang-cl. The whole game then linked with lld-link with zero undefined symbols and zero duplicates, and no code at its original address. The decompilation’s reviewed relocation data gave every original address one canonical symbol, which replaced 22,067 address aliases with six linker entries.
That image started, and stopped at the game’s own installation check, which Classic REbirth normally bypasses. From there, every change of this kind is made in readable source: the port reimplements the effect of each of the patch’s fixes, and never disassembles or copies the patch itself.
Bugs the old compiler had been hiding
A different compiler lays out memory differently, and that exposes things the original build tolerated:
- A glue bug that zeroed four virtual function tables.
- A function that had only just been matched in the decompilation, missing from the port’s list.
- Two stack-frame bugs. One was at
0x00411000, where a one-byte local receives a four-byte store. The decompilation had already found the same defect by recompiling with different optimisation settings; here a different compiler found it again. - Exception tables that had to be rewritten to the original compiler’s layout, undefined behaviour in 33 functions, and the old compiler’s particular way of converting floats to integers.
After those fixes the game ran past start-up without a single exception.
A new platform layer
The cleanest seam turned out to be the one the original developers left. The game was ported to PC from the PlayStation, and it still talks to the hardware through 193 small wrappers that recreate the console’s SDK on Windows. The port keeps every line of game code and replaces what sits under those wrappers: the window, the graphics, input, sound and the thread pool.
The new layer is built on SDL3. The game’s main thread runs as a fiber, driven by a virtual clock that mirrors the original’s end-of-frame routine, so the game sees time pass exactly as it used to. Rendering goes through Direct3D 12, and its blend modes were checked against a reference. Movies play through the port’s own AVI and DV decoder, with no third-party code.
The first build on it reached the title screen and the attract demo at a steady 60 frames per second. One hang remained: the demo waited forever for a looping sound to finish, because of an uninitialised field in a function that did not yet match. With that fixed and the original keyboard, gamepad and volume settings wired in, a scripted controller walked Jill through the first room.
Proving it plays the same
Looking right is not the same as being right, so the port computes a hash of the game’s logic state on every tick: the random number generator, the enemies, the player and the game’s global state. A recorded route then replays the same inputs, and the hashes are compared tick by tick with a reference.
There are now 21 such routes:
- A long new-game route, 13,989 ticks from the opening.
- A Mercenaries round, 5,192 ticks with five kills.
- Seven coverage routes from a 100% save, which fire every weapon in the game and go through doors in seven areas.
- Twelve routes for the game’s branching choices, the Live Selections, taking both options of five of them, plus two boss fights. The saves for these were made by editing story flags in copies of my own, and breakpoints confirm each choice really fires.
Input recording and replay are built in, so a session I play can be replayed later with checkpoints that report the first tick where anything differs.
The bug no hash could see
The hash compares the port with itself, so it cannot catch the port being consistently wrong. A separate audit ran the original functions against the port’s, and found one of the best bugs of the project.
C does not say which side of an expression is evaluated first. In 28 places the game draws two random numbers in one expression, such as rng() >> (rng() & 3) when it sets an enemy’s health. The original compiler draws the right-hand number first; clang draws the left. Same seed, different numbers. So in the port, zombies had different health, Nemesis chose different moves, and some effects spawned differently.
The fix keeps the original order. It also changed play: in one route a zombie that now had 40 hit points met the player at a ledge and the route no longer reached its door, and in one fight with Nemesis the battle now ends at 125 hit points instead of 105. A later scan reviewed 171 more places where the two compilers could legally differ, and found none that did.
Pointers and plain numbers
Everything outside the code, such as tables, strings and resources, is generated from the player’s copy, and the generator has to decide for every word whether it is an address or a number. In the original build that never matters, because nothing moves. In the port everything moves, so it matters a great deal.
Mistakes went both ways. Five entries in a dispatch table, among them a weapon’s fire handler, still held original addresses and would have jumped into unrelated code. Meanwhile part of the arctangent table had been relocated as if it were addresses, which made enemies turn slightly differently from the original. A full audit demoted 21 false pointers and promoted 48 stale ones, and a test now pins the count.
Original bugs
An audit of every place where the port substitutes its own code for the original found seven observable differences. Six were the port’s bugs and were fixed. The seventh was the original’s: a sound routine that can write up to three bytes past the end of a buffer. That one stays fixed in the port.
Better than the original, only where it is safe
With the logic pinned down, the presentation could change freely. Each option is a setting in one re3port.ini, and the defaults reproduce the PC original.
- High frame rate. The game still ticks at its original rate. After each tick, everything drawable is snapshotted, and a render-only copy of the drawing code fills the frames in between, without ever writing to game memory. Camera cuts, loads, movies and menus show the tick’s own picture. The logic hashes are identical at every display rate from 60 to 240 Hz.
- Resolution and widescreen. The 3D is drawn at up to four times the original resolution with sub-pixel precision. The pre-rendered backgrounds stay 4:3, so the side areas extend the scene and fade out objects that would otherwise be cut off at the edge.
- Looks. One setting picks the PC original, a PlayStation-style picture at 320×240, or the enhanced look.
- Modern controls. An optional camera-relative scheme, where you push the stick towards where you want to go on screen. It works by pressing the game’s own turn and walk buttons for you, so recordings replay exactly either way.
- English text. The release I own is Japanese. A text layer can draw the message window in English from a language pack while the game keeps “typing” the Japanese underneath, so the logic is the same in both languages. The translation itself does not exist yet.
- Saves. Saves from Classic REbirth import directly.
Higher resolution exposed the game’s art in ways the original never did. Playing a build, I found holes in Jill’s model in the police station hall. There were two causes. The original drew each joint at exactly the same screen point for both connected body parts, which sub-pixel precision had broken, so the port now welds those points again. And some parts never quite met even in the original, which only shows at four times the resolution, so an optional half-pixel fill closes those gaps. An earlier fix for joints between ticks had already brought the largest seam down from 188 pixels to 3.
A model viewer
I asked for a way to look at the game’s models, and it became a model viewer on the title menu. It browses all 786 models in the game data through the game’s own loaders and renderer: the players with every weapon in hand, enemies and characters with the animation banks they borrow from their relatives, 78 doors playing their own opening scripts, and 588 objects from 170 rooms. Even with the viewer linked in, the logic hashes are identical, and a session in it leaves every later tick unchanged.
Readable, and 64-bit
The port can be built three ways from one command:
- From the decompilation’s sources, the default.
- From a readable source tree of all 3,071 linked functions in 20 subsystem groups, generated from the decompilation’s knowledge base, with every port fix folded in and safe to edit by hand. Both executables built from it give identical hashes on all 21 routes.
- As a 64-bit program. Game data stays in the lower 2 GB, so the original structure layouts, saves and file formats are unchanged, while everything else uses the full address space. It matches the 32-bit build’s logic on every route. It is still experimental.
The separate Mercenaries executable builds from the same source too.
Where it stands
The port builds from a clean checkout with one command, runs all 21 routes with logic identical to their references, and I play its releases on my own machine. It is not finished. No one has recorded a full playthrough on it yet, the final battle and six Live Selections have no route, the English translation does not exist, and the 64-bit build is experimental.
It is also never going to be distributed with any game data. The whole point of building from the player’s own copy is that the game stays theirs.
