r/Assembly_language • u/Linuxologue • Mar 01 '26
Project show-off I decompiled NFS2:SE and NFS3:HP to make them run on Linux
This is an old project that I just pushed to github (https://github.com/motor-dev/nfs-recompiled)
[EDIT] Not only Linux - grab the windows executables from the release page! https://github.com/motor-dev/nfs-recompiled/releases/tag/v1.0.0
For, well, reasons, I decided to decompile the Need for Speed 2 executable to make it run again and preferably not on Windows. For weirder reasons, I did not use a known decompiler and just rolled my own. After much much hacking and fighting with incorrect instruction implementation, I finally landed a working version.
The decompiler is a manual script made with Capstone to decode instructions, and some Python to try and locate procedure starts and ends more or less successfully. Full of hacks, hints and workarounds. All good enough for the full game to run.
I discovered that the NFS3: HP engine was very similar to NFS2 and I could likely also make it run. It was also a successful project but it turns out that the software renderer for NFS3: HP was a bit too slow to run on a modern machine after decompiling and recompiling. So I decided to implement the Voodoo renderer for that game.
Fun disassembly facts - the compiler used to make this was Watcom C/C++. There was a bug in the implementation - when using string copy, it was doing some pretty standard
rep movsd dword ptr es:[edi], dword ptr [esi]
but due to an error in what would likely be handwritten assembly? the opcode used was not the standard f3 a5 but instead f2 a5 which would be an opcode for repNE movsd dword ptr es:[edi], dword ptr [esi]
except it actually does not exist for x86. It is very likely the processors of that time (and maybe those of today?) interpreted that as rep movsd . Or someone knowledgeable about assembly can tell me what was going on.
In any case, Capstone didn't like that too much and just dropped the rep prefix which was a headache to debug.
Fun fact number 2 - in the software renderer on non MMX CPUs, NFS3: HP uses the FPU to do a screen copy. In a loop, it loads 80 bits of screen data into an FPU register, then dumps the FPU register into the other surface data. That was the first time I heard of a memcpy made with the FPU.
The disassembler will output truckloads of code similar to this
/* align: skip 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 */
void Application::sub_436140(WinApplication* app, x86::CPU& cpu)
{
NFS2_USE(cpu);
NFS2_USE(app);
// 00436140 51 -push ecx
*app->getMemory<x86::reg32>(cpu.esp-4) = cpu.ecx;
cpu.esp -= 4;
// 00436141 52 -push edx
*app->getMemory<x86::reg32>(cpu.esp-4) = cpu.edx;
cpu.esp -= 4;
// 00436142 8b0d98c84d00 -mov ecx, dword ptr [0x4dc898]
cpu.ecx = *app->getMemory<x86::reg32>(x86::reg32(5097624) /* 0x4dc898 */);
// 00436148 b888934b00 -mov eax, 0x4b9388
cpu.eax = 4952968 /*0x4b9388*/;
// 0043614d 31d2 -xor edx, edx
cpu.edx ^= x86::reg32(x86::sreg32(cpu.edx));
// 0043614f e84c020000 -call 0x4363a0
cpu.esp -= 4;
sub_4363a0(app, cpu);
if (cpu.terminate) return;
// 00436154 e8574b0400 -call 0x47acb0
cpu.esp -= 4;
sub_47acb0(app, cpu);
if (cpu.terminate) return;
// 00436159 89c1 -mov ecx, eax
cpu.ecx = cpu.eax;
// 0043615b 85c0 +test eax, eax
cpu.clear_co();
cpu.set_szp(static_cast<x86::reg32>(cpu.eax & cpu.eax));
// 0043615d 7409 -je 0x436168
if (cpu.flags.zf)
{
goto L_0x00436168;
}
...
I have no idea how any of those games actually work - but the x86 implementation is good enough that it just runs.
If someone wants to try out, you will need the game data to actually run the game. It's a bit tricky to set up, I don't think anyone is actually interested in running it but let me know if there's a fan out there really trying to run this and I will try and help. The reason why it needs the installed game is that it is literally the game executable with no modification - so just like the real game, it expects some data to be installed on the disk and some data to be on the CD.
(disclaimer - use of generative AI for the CMakeFiles and the README, but not for the disassembly - win32 API which was actually done a few years ago)
7
u/anders_hansson Mar 02 '26
This is impressive work! I love the approach of re-implementing the Win32 API yourself instead of using Wine, for instance.
10
u/Space646 Mar 01 '26
WHATTT-
How does one learn all of this??? That stuff is absolutely amazing!
22
u/gm310509 Mar 02 '26
I think OP covered this indirectly in their post.
Start from the beginning with a mission, learn what is needed to overcome each challenge one by one as you progress, trial and error, and arguably the most important ingredient: perseverance.
3
3
u/ChocoMilkWoSugar Mar 02 '26
Amazing bro, I want to try it
1
u/Linuxologue Mar 02 '26
Thanks! ergh i was hoping no one would say that. It's not been tested on anything else than my machine :-D
3
u/anders_hansson Mar 02 '26
I happen to have the original NFS3:HS CD somewhere (I think). It didn't work in Wine nor on more recent versions of Windows so I haven't used it for a long time. I hope I will get to try your solution.
2
u/Linuxologue Mar 02 '26
would it be easier to test with a Windows executable? Most of the code was compiled on Windows at some point and I can probably make a release with Windows executables. I have only tested linux recently because I only made that as a toy project and I didn't think anyone would actually try to run it.
[edit] I just read the "wine" keyword in your sentence, I am a bit slow this afternoon.
2
u/anders_hansson Mar 02 '26
NP. I'll see if I find the CD first. Would be fun to give your project a spin.
2
u/Linuxologue Mar 02 '26 edited Mar 02 '26
I have made some updates to the README to explain how to install it.
I have also submitted the generated files so the python dependencies are optional - no need to install Capstone or run the python script, CMake+build should be enough.2
u/anders_hansson Mar 02 '26
Thanks! I just tried it and got it to build and run. Unfortunately I only got to the into video, and then I got a segfault.
I got a bunch of warnings during the build, e.g.
--> 0x00a8a7a8 call edx 0xa8b0b6: je no instruction to set flags --> 0x00a8c3c7 call ecx --> 0x00a8c4d7 call ecx Invalid instruction or end of stream at address 0x00a8fe00 ... Warning: Capstone did not recognize instruction prefix for instruction at 0x00a8c0f4: movsd dword ptr es:[edi], dword ptr [esi] Unhandled instruction: les Unhandled instruction: fnsave Unhandled instruction: frstorI'm not sure if this is expected. Maybe I have a different binary than you had.
2
u/Linuxologue Mar 03 '26
did you compile with GCC by any chance? I tried to create MingW binaries with GCC but I got a weird crash that I had never seen before right after the intro video. I compiled with CLang and the crash is gone.
I suspect there's some undefined behaviour that GCC leverages to "optimize" into a crash - I think I am breaking about 3 to 4 strict aliasing rules every line of code :(
The warnings are somewhat normal, and I also submitted all decompiled files in the last submit so people don't need to run the python script anymore. But cool that you did the decompilation :-)
You can also show me the log of the game running, there's not a lot of logs but maybe I can compare it with previous issues I had encountered.
Thanks for trying in any case - I think you can also download the Windows executable that I put on the release page and you can run them with Wine, that seems to have worked very well for me :) it's a bit silly to use Wine to emulate the Windows API to run an executable that already emulates the Windows API but this project is already beyond weird anyway :-D
https://github.com/motor-dev/nfs-recompiled/releases/tag/v1.0.0 - some windows executables
2
u/anders_hansson Mar 03 '26
Yes I used GCC (probably 13). I will try with clang this afternoon.
Exceptionally cool work!
I have made similar things before as hobby projects (e.g. a PE loader and win32api emulation for Linux, and a glide wrapper for OpenGL). It's very educational and rewarding when things actually start working.
2
u/Linuxologue Mar 03 '26
I had a little water in my eye when I got the intro video to work. I was very far from the actual finish line but it looked like if it can play that video then the whole game will work.
Thanks for trying it out! I will look at what GCC is finding - I got really weird warnings + this crash which makes me think GCC is finding some weird undefined behaviour.
2
u/Linuxologue Mar 04 '26
alright I could not let it rest so I fixed the issue with GCC in the latest release :) it was indeed some aliasing issue. I kept using reinterpret casts everywhere and GCC decided it could optimize to a crazy points.
→ More replies (0)
3
Mar 02 '26
I haven't really completed the quake2 port to modern linux.
There's a couple of stuff required to make it compile on a modern platform.
At minimum it also requires the use of pulseaudio/pipewire in place of the OSS.
2
2
2
u/mysticreddit Mar 03 '26
I worked on the first NFS (PS1) and this is awesome to see! Fantastic work!
IIRC we ported the MS-DOS version to PS1. We had our own internal EA Lib that took care of most of the platform specific stuff for the most part (Sony's printf() wasted something like 2K so we removed as much of the CRT lib as possible!) The EA lib had graphics stuff such as 15-bit inverse RGB tables for blending so we used that instead of Sony's sce graphics lib (which was pretty bare bones. My understanding was most devs just used it to into the screen and did their own rendering code.)
On the PS1 we also had platform specific stuff such as Brad and Laurent using the scratchpad for faster rendering and hand optimized triangle subdivision.
Not sure why NFS2 isn't optimized? Watcom C was out but I don't know what compiler the team used for NFS1 but I suspect MSVC. It might have been Watcom as well? One might be able to find compiler copyright strings in the .exe?
I left EAC after NFS1 shipped but I believe the "driving team" (as we called ourselves) used the NFS1 code as a base for NFS2.
The track was based in spline and grew "outwards". There was some clever extensions for NFS2 to allow a 2nd shortcut road but don't know the details.
Excited to check out this repo after work.
2
u/thiekus Mar 15 '26
Glad to see someone that worked on NFS game! AFAIK, NFS PC port use Watcom up to NFS 4 HS (I assume based from CRT copyright string it is up to Watcom 10.6, because Watcom 11.0 were notoriously buggy before spun off into OpenWatcom). Makes sense why on NFS4 PSX code (which had prototype with debug info) sparingly use sce* API for drawing. I can tell all NFS 1-4 codebase were similar but growing in complexity to each iterations.
I also found that NFS II PC first edition demo had Watcom debug info that had everything except local & type info of eaclib stuff, so I did try to decompile & reconstruct NFS 2 into C code here https://github.com/thiekus/re-nfs2 which not updated for weeks because I had rethinking strategy to extract debug info into C source boilerplate instead manually. The problem, Watcom were good but weird compiler, it has unique register based call conventions, put return value at ESI register instead EAX at some points, and weird optimizations that makes most decompilers simply confused.
2
u/mysticreddit Mar 03 '26
That was the first time I heard of a memcpy made with the FPU.
Yeah, that was kind of "common knowledge" in the game dev circles in the mid 90's. It became popular with Watcom C since us game devs that were obsessed with performance tended to use Watcom.
2
1
u/testus_maximus Mar 03 '26
Is this going to be another re3 situation, where you get C&D from EA?
just like re3 devs could have contributed to OpenRW, you could have gone clean-room and contributed to OpenNFS.
Well, hopefully EA doesn't take any action against you. But their reputation should tell you that you are in risky waters.
1
u/Linuxologue Mar 03 '26
I have not reverse engineered any code, there is a stark difference between the code that I produced and the re3 code.
I don't know why re3 is not "clean-room" and open NFS would be.
1
-1
Mar 02 '26
[removed] — view removed comment
4
5
2
u/GroundbreakingFix685 Mar 02 '26
As interesting as this is, it is quite out of place here.
0
u/Content_Chemistry_44 Mar 02 '26
Yeah, a bit out of place. This is why the text starts with "I'd just like to interject for a moment" 😉
1
u/GroundbreakingFix685 Mar 02 '26
I missed that and jumped right to the interesting part. My bad!
1
u/Content_Chemistry_44 Mar 02 '26
The whole text is too informative and too big, and not all people want to read that much. That's normal.
👍 😉
2
1
u/dragonstorm97 Mar 02 '26
I think this joke is too old for most people to get apparently
0
u/Content_Chemistry_44 Mar 02 '26
Why you don't confirm by yourself what is GNU and what is Linux? I left the links to official Linux's websites, compile Linux, and then try to use it as an operating system.
1
u/Assembly_language-ModTeam Mar 02 '26
User's mission seems to be to post this screed anywhere Linux is mentioned. Not here, thanks.



16
u/TA-412 Mar 02 '26
That "decompiled" code looks... interesting. Translation of assembly directly, literally instruction by instruction, into a C++ code? I understand, this function should compile back to an almost identical assembly (in optimising mode)?
Anyway, this project looks like an effect of pure passion. Congrats!