Back in 2016, I started a side project to reverse engineer the game Duke Nukem II and build an open source reimplementation of its engine from scratch – called Rigel Engine (Check it out on GitHub). Now, more than 2 1/2 years later, my version is complete enough to allow playing the entire shareware episode of the original game, offering a practically identical experience to running the original. Here’s a video showing off the first level:
So what can it do? Rigel Engine works as a drop-in replacement for the original DOS binary (NUKEM2.EXE
). You can place it into the game directory and it will read all the data from there, or you can specify the path to the game data as a command-line argument. It builds and runs on Windows, Mac OS X, and Linux. Based on SDL and OpenGL 3/OpenGL ES 2, written in C++ 17.
It implements the game logic for all the enemies and game mechanics found in the Shareware episode, plus most of the menu system. Saved games and high scores from the original game can also be imported.
On top of that, it already offers some enhancements compared to running the original game:
- No emulator or vintage hardware required, no need to tweak settings
- No loading screens – hit enter in the “new game” menu and you’re immediately in the action
- Multiple sound effects can play at the same time, which is not possible in the original
- No limitations on the number of simultaneous particle effects, explosions etc. going on
- Per-user save files and high score lists
- Much more responsive menus
Now, I don’t consider Rigel Engine fully “done” yet. But this is a nice milestone for sure, and a good opportunity to write about the project again (find some older posts here and here). Let’s start by taking stock of what’s in the code right now, and how I got there.
How much code is it?
At the time of this writing, RigelEngine consists of 270 source files containing over 25k lines of code (without comments/blank lines). Of those, 10 files and 2.5k lines are unit tests. A breakdown including blank lines and comments can be found here.
What’s in all that code? There’s a bit of general infrastructure and utilities, we have fundamentals like rendering, and a lot of smaller pieces of logic here and there. On top of that, some of the bigger chunks are:
- parsers/loaders for 14 different file formats used by the original game – 2k LOC
- behavior/game logic for 24 enemies/hostile objects – 3.8k LOC
- game logic for 14 interactive elements and game mechanics – 2k LOC
- the player control logic – 1.2k LOC
- 154 configuration entries (how much health does this enemy have, how much points does this collectable give etc.) – 1k LOC
- 31 destruction effect specifications (effects triggered when an enemy or other destructible object is destroyed) – 254 LOC
- the camera control code – 159 LOC
- interpreter for the game’s menu/cut scene description language – 643 LOC
- HUD and other UI code 818 LOC
- 5 non-menu screens/modes, e.g. the intro movie, bonus screen etc. – 789 LOC
Of course, all of this code had to be written, which brings us to the next part.
How much work was it?
Although it’s been 2 and a half years since I started the project, I didn’t always work on it during this time. There were a couple of months where I didn’t spend any time on the project, and others where I only put a few hours into it. Then, there have also been times where I worked quite extensively on Rigel Engine. Looking at the commit chart from Github gives us a rough idea of how my efforts were distributed in time:
What we see in that chart are 1081 commits to the master branch. Before creating the repository though, I was working in a private one which features another 247 commits, thus giving us 1328 commits in total. In addition to that, there were various prototype branches I used to explore and experiment, but never merged, and I sometimes squashed larger commit histories down to a more condensed form before merging.
Now, writing code was only one part of the project – reverse-engineering being the other major one. I’ve spent quite a few hours looking at the original executable’s disassembly in Ida Pro (the free version), taking notes, writing down pseudocode, and planning out how to implement things in my version. I also did a lot of testing with the original game, running it in DOSBox and on original hardware (various 386 and 486 machines which I got from eBay). I built test lev