In 2016, I started working on a hobby project for reverse engineering of the game Duke Nukem II and recreating its engine from scratch. The project has the name Rigel Engine and is laid out in open source ( its page on GitHub ). Today, more than two and a half years later, on my engine, you can already go through the entire shareware-episode of the original game with the gameplay almost identical to the original. Here is a video with the passage of the first level:
What can he do? Rigel Engine works as a complete replacement for the original binary DOS file ( NUKEM2.EXE ). You can copy it to the game directory and it counts all the data from it, or you can specify the path to the game data as a command line argument. The engine is built and executed under Windows, Mac OS X and Linux. It is based on SDL and OpenGL 3 / OpenGL ES 2, and is written in C ++ 17.
It implements the game logic of all enemies and game mechanics from the Shareware episode, plus most of the menu system. In addition, you can import saved games and a high score table from the original game. Moreover, the engine has advantages over the original: ')
No emulator or old hardware required, no need to set parameters
No download screens - select “new game” in the menu, press Enter, and immediately start playing
Several sound effects can be played simultaneously, which was not possible in the original.
There are no restrictions on the number of simultaneous effects of particles, explosions, and so on.
Saving files and highscores for each player
Much more responsive menus
While I do not think Rigel Engine is completely "ready." But this is a great stage in development and a good opportunity to write again about the engine (old posts are published here and here ). Let's start by looking at the current state of the code, and find out how I got to it.
How much is in the code engine?
At the time of writing, RigelEngine consists of 270 source files containing more than 25 thousand lines of code (no comments / blank lines). Of these, 10 files and 2.5 thousand lines - unit tests. Detailed breakdown taking into account empty lines and comments posted here .
What is in all this code? Some common infrastructure and support functions, such fundamental things as rendering, and a bunch of small pieces of logic. Besides all this, the biggest parts are:
parsers / loaders for 14 different file formats used in the original game - 2 thousand lines of code (LOC)
behavior logic / game logic for 24 enemies / hostile objects - 3.8k LOC
game logic for 14 interactive elements and game mechanics - 2k LOC
Player Control Logic - 1.2k LOC
154 configuration records (the amount of health of each enemy, the number of points received for the collected items., Etc.) - 1k LOC
31 specifications for damage effects (effects triggered by the destruction of an enemy or other destructible object) - 254 LOC
Camera control code - 159 LOC
interpreter of the game menu description language / cutscene - 643 LOC
HUD and other UI code - 818 LOC
5 screens / modes outside the menu, for example, the initial animation, bonus screen, etc. - 789 LOC
Of course, all this code needed to be written, and this brings us to the next question.
How much work did it take?
Although two and a half years have passed since the project began, I have not worked on it all this time. For a couple of months I didn’t work on the project at all, in some others I spent only a few hours on it. But there were moments when I was working on Rigel Engine quite actively. Looking at the commit schedule on Github, you can get a rough idea of ​​how my work was distributed over time:
According to the schedule, we see that 1081 commits were made to the master branch. However, even before creating the repository, I worked on a closed one, in which there were 247 commits, which in total gives us 1328 commits. In addition, there were several prototype branches that I used for research and experimentation, but never merged with the main one; besides, before merging, I sometimes squeezed the big stories of commits into more concise ones.
It should also be said that writing the code was not the only part of the project — another important part was reverse engineering. I spent quite a few hours studying the disassembled code of the original executable file in Ida Pro (in the free version), taking notes, writing pseudo-code and planning the implementation of the elements of my version. In addition, I conducted active testing of the original game, launching it in DOSBox and on the original equipment (different 386 and 486 machines purchased on eBay). I collected test levels for separate observation of specific enemies and studying game mechanics, recorded video clips using DOSBox, and scanned the recordings frame by frame to confirm my conclusions made while studying assembly code. After the implementation of the enemy or game mechanics, I usually recorded a video clip from my version and compared it frame by frame with the original to confirm the accuracy of my implementation.
Here are some photos of my notes:
Reverse engineering of the camera control code.A large rectangle indicates a screen.Dotted lines indicate zones in which a player can move without moving the camera.If you're interested, the camera control code itself can be found here .
General notes that help in understanding the assembly code.On the left - the order of updating the original game at a high level.On the right are notes about bit fields indicating the state of some game objects.
Transcription assembly code in pseudocode.Usually I do it quite mechanistically, transcribing without thinking about what the code is doing, and then I use the version in pseudocode to understand the underlying logic.And based on it, I am already inventing my own implementation.See the ready code here .
Pseudocode cleaned version of the logic of the enemy.The headers indicate the states of the finite state machine, the code below explains what should happen in the corresponding states.It was created on the basis of a raw pseudocode obtained by transcribing an assembly code.Ready code can be found here .
In the end, the project turned out to be very exciting, and I learned a lot from it: about reverse engineering, 16-bit x86 assembler, low-level VGA programming, the strict restrictions that PC developers had to face in the early 90s; In addition, I made many discoveries about the internal features of the original game and how strange and bizarre some of them were reaized - this topic in itself deserves a separate series of posts.
What's next
In addition to adding the latest remaining functions and finishing support for the registered version, I have a few ideas for improving and extending the capabilities of the Rigel Engine, not to mention the cleanup and refactoring of the code - as usual, the best way to create software architecture becomes apparent only after the creation of this software is completed.
As for future improvements, here are some of the points that I was thinking about implementing:
Smooth motion with interpolation. The game updates its logic approximately 15 times per second, and in the original of the game it is also the frame rate for rendering. On the other hand, the Rigel Engine can easily operate at 60 FPS and higher. At the moment, these additional frames do not provide any advantages, but I think that they can be used for intermediate frames in order to implement smoother scrolling and movement of objects. The logic of the game will still work at the same speed, but the objects will move smoothly, and not “jump” with an increment of 8 pixels, as they are doing now. Earlier, I created a prototype of such a system, and it looks great, although it needs some work.
Gamepad support. In the original game there is support for joysticks, and DosBox can emulate them on modern gamepads, but their setup can be complicated - configuration and calibration in the game is required. Not to mention that not all buttons of the controller are supported, and to use the menu you still have to take the keyboard. Therefore, I believe that native support for controllers will significantly improve the gameplay.
Sound enhancement. Currently, all sound effects have the same volume. Objects that produce sound, for example, force fields, become sharply audible when they hit the screen, and just as sharply break off. I was curious how they would sound if the volume of the effects at a distance would fade out. For example, we could barely hear the force field when it is not on the screen yet, and as it approached it would grow louder.
Remote camera / view most of the level. The game was not designed for this, so this possibility can damage the gameplay - the player will begin to see enemies that are not active off-screen, and the like. But I still wonder how it will look and play. In the end, players very often complained about this game for not being able to see enough of the level. It would be curious to add the option of disabling the HUD or replacing it with a more minimalist one using transparency.
Increase graphics resolution. This feature is often found in many ports / recreations of games, and it would be great to add it here. The engine already allows you to replace the sprite graphics with your own images, but for now they cannot be of greater resolution, because everything is rendered into a small buffer with a subsequent increase in scale. First you need to replace this approach so that scaling can be performed for individual objects.
I have no “road map” for the future, so I can implement these items in any order. But before all this next step will be the integration of Dear ImGui to further build the options menu, which is not in the game yet; in addition, it will enable and disable the improvements listed above. In the end I’ll say that I will be grateful for any assistance in working on GitHub !