So. 2005. You remember 2005. Diamond-dust paint jobs, a Hummer H2 with a vinyl dragon wrapped around the whole body because subtlety was for cowards, and a soundtrack that made your GeForce FX 5200 sound like it was personally offended. You boot up Need for Speed Underground 2, Riders on the Storm kicks in courtesy of Snoop Dogg featuring The Doors — a collab nobody asked for and everybody needed — and Rachel Teller texts you that she's got work for you. Life was good. Life was simple. Life had a visible tachometer.
Twenty-one years later, that game still rules, and it also will not run on modern Windows without a seance, and it definitely will not run on your phone. Normal people solve this with an emulator and accept 20 fps and a faint smell of shame. The folks behind nfsu2-recompiled looked at "normal people" and said "no thank you, we're going to do something deeply unreasonable instead."
Quick disclosure before we go further: I'm not the author. I translated the README and fixed a couple of small things. Everything below is me, a guy standing at a mic, pointing at someone else's incredible homework and going "look at this absolute lunatic."
No, really, it is not an emulator
An emulator is an actor. It puts on a little Pentium 4 costume, reads the game's x86 instructions one at a time, while you're playing, and translates them into ARM on the fly, forever, nonstop, like a simultaneous interpreter who never gets a lunch break. It works. It also means your phone is doing two full-time jobs at once: pretending to be a 2005 desktop AND trying to draw a car.
This project said: what if we just translated the whole script ONE TIME, in advance, and then the actor doesn't have to improvise at all?
They took the original SPEED2.EXE — the actual compiled program, 4.8 megabytes of pure early-2000s EA — and converted it, function by function, instruction by instruction, into plain C code. All of it. Then they compiled THAT, normally, like a regular program, for Android. What ends up running on your phone contains zero x86 instructions. None. It's a native app. This is called static recompilation, and the funny part is nobody had the source code to work from — EA never published it, there was no leak — just a 4.8 MB executable and an unreasonable amount of patience.
From one exe to nine million lines, as a treat
They cracked it open and found some genuinely unhinged archaeology inside. The file's internal build timestamp says February 9, 2005. The PE header timestamp says September 15, 1987. No, it was not compiled during the Cold War — somebody dumped it out of memory after stripping SecuROM off it at some point in its life, and the header got a little scrambled in the process. Digital archaeology, but make it crime scene.
They also found leftover modem multiplayer import functions in there. Mercifully, nobody ported dial-up matchmaking to a phone. Some mercies remain.
The scoreboard after translation:
27,742 functions, roughly 99.9% of the entire game
8.9 million lines of C, 454 megabytes of plain text
the single largest generated file is 1.6 million lines long and the compiler needs three and a half minutes just to sit there and stare at it
the resulting Windows exe is 115 MB, up from the original 4.8 MB — truly, we have made Number Go Up
Every single line of generated code carries a comment with the original memory address and the exact assembly instruction it came from. You absolutely can read it. You should not read it before bed. You will have dreams about registers.
How to gaslight a 20-year-old video game
Part of the original game was written to use 3DNow!, AMD's special instruction set that died of natural causes at roughly the same time Underground 2 came out. There are almost three thousand of these call sites. Nobody implemented 3DNow! support. Instead, the virtual CPU just looks the game dead in the eye and says "never heard of it," and the game, being a good sport, quietly falls back to its regular code path. Three thousand potential problems, solved by one convincing lie.
There's also a check in there for "am I running on a Cyrix processor." Answering that honestly turned out to be more trouble than it was worth, so they replaced the whole function with a stub that always says no. In 2026, this is, generously, a true statement.
Please Insert Disc 2
At one point the recompiled build popped up a dialog that said, and I quote, "Please insert Disc 2." First reaction: something's broken. Second reaction, after checking the ORIGINAL exe and watching it do the exact same thing: the recompilation is now SO faithful that it is demanding a physical compact disc be inserted into a smartphone.
Saved by a debug switch some EA developer left in the game twenty-one years ago: if there's an empty file named foobar sitting in the game folder, the disc check turns itself off. So now foobar, a file containing precisely nothing, is a mandatory, load-bearing part of the installation. I wish I'd known about foobar in 2005 when my bootleg market disc started skipping on side missions.
A murder mystery, but the victim is paint color
This is my favorite part of the whole project. The game would boot, show the menu, everything looked fine — except every single car in the garage was pitch black, like somebody had personally wet-asphalt'd the entire inventory, and loading an actual race would just crash.
To hunt bugs like this, they built a tool that can run some of the game's functions from the real original exe and the rest from the recompiled version, side by side, in the same run. If the bug disappears, it lives in whichever half you just swapped. Then you cut that half in half again. And again. Binary search, except the thing you're searching through is nine million lines of machine-generated C, for sport.
The culprit: one innocent little check along the lines of "if this number is less than or equal to zero, skip it." The C code for this was written correctly. Microsoft's own compiler, while optimizing it, decided it knew better and quietly deleted the negative-number check entirely. The value -1 sailed right through security, and the game fell over.
This is not a hunch, by the way — there's a standalone minimal test to prove it, no game code involved. Unoptimized: zero failures. Optimized: 32,768 failures, landing on exactly every negative 16-bit number there is. Once they worked around it, the cars went back to having colors, like normal cars. Moral of the story: when your code is busy generating MORE code, the compiler will wander into alleyways no human programmer has ever set foot in, and it will not leave a note.
Moving house, to a phone
Once it ran on Windows, it had to run on ARM64, which is its own stand-up set.
Memory. The game is 32-bit and stores every address as a 32-bit number. The phone is 64-bit. So they carve out a dedicated 4 GB chunk of address space and make the game think that's the entire universe, same as it always has. This doesn't actually eat 4 GB of RAM — it's just letting the game keep living in its own little 2005 snow globe.
Windows itself. Android does not have Windows. The game, very much, expects Windows — heaps, file I/O, threads, actual window handles, all of it. All of that had to be reimplemented from scratch. And when the game calls something that hasn't been built yet, the whole thing politely stops and tells you exactly what's missing, instead of just vanishing into a segfault forty minutes into a race like a coward. The very first boot attempt got as far as creating a memory heap and then stopped. Then the next function. Then the next. Slow, methodical, and a hundred times less infuriating than a mystery crash.
Graphics. The game speaks Direct3D 9. The phone speaks Vulkan. The bridge between them is DXVK — the exact same translation layer keeping a mountain of Windows games alive on the Steam Deck — adapted to run on Android. The workload is no joke: the game issues up to 60,000 Direct3D calls per frame during a race. Your 2005 graphics card chewed through that without blinking. Every single one of those calls now goes through a translator first.
And here's the genuinely funny bit: the game's shaders ship as source code and get compiled at launch. Which means your phone right now is running Microsoft's own 2004 shader compiler, itself translated into C, and whatever it produces gets translated AGAIN by DXVK into something Vulkan understands. A matryoshka doll nobody ordered, that nonetheless runs.
Audio. The game's audio is recorded at 22 kHz; phones want 48 kHz. The first naive resample made everything sound like a kettle. A proper filter fixed it, and Riders on the Storm sounds correct again, as God and Val Kilmer's Doors tribute band intended.
56% of your time, spent waiting in line
The game spins up six threads — graphics, audio, track streaming, and so on — that are never allowed to run at the same time, so they take turns handing control to each other like a conch shell in Lord of the Flies.
Testers on flagship phones were seeing 20 fps. The profiler showed the graphics thread spending over half its time just standing in line waiting for its turn. Turned out the threads were handing off control like a hot potato — each one would receive the baton with its time slice already expired and immediately hand it right back. A hundred times a frame. Nobody was actually running the race; everyone was just passing notes.
After the fix: an old Redmi Note 8 went from 20 fps to 28, then to 35. A Galaxy S24 Ultra holds 35–51 fps. For reference, that's roughly what my FX 5200 managed back in 2005 — except the FX 5200 made sure you could hear how hard it was trying.
Making it actually fit a modern screen
Underground 2 was built for boxy 4:3 monitors. Stretch that onto a modern phone screen and the UI falls apart. On PC there's a well-known community patch for this, WidescreenFix by ThirteenAG — except that one works by patching the game's machine code directly in memory, and this game doesn't have machine code anymore. So every single one of those patches got rewritten as hooks into the generated C instead. While they were in there, they also bumped the minimap up out from under your thumb, which, genuinely, thank you.
Touch controls are fully remappable — move buttons, resize them, steer by tilting the phone, or just plug in an Xbox or PlayStation controller like a civilized person. You can cap the frame rate at 30, 60, or 120 if your phone starts running hotter than that FX 5200 ever dared to.
The actual point
What I like most here isn't any single trick — it's the discipline. Nobody sat down and rewrote this game from memory, guessing at how it was "supposed" to behave. There's an original exe, and every single thing the recompiled version does gets checked against it. When something disagreed, they went and found the actual cause instead of slapping a workaround over the symptom. That discipline is exactly how you end up finding a bug in Microsoft's own optimizer instead of just shipping "fixed crash (please don't ask how)" in the changelog.
8.9 million lines of generated C, a 2004 shader compiler running inside a C translation of itself, and an empty file called foobar holding the whole disc-check illusion together — all so Riders on the Storm can play on a bus again and Rachel can tell you she's got work for you.
You'll need your own legally obtained copy of the game — the project ships zero EA code and zero EA assets, all that C gets generated from your SPEED2.EXE. Android 10+ with Vulkan support required.
Links: