Update: Quake 2 .NET Port with Visual Studio 2008 (v9.0) and Managed C++
Posted by gregd1024 on November 25, 2007
This article is an update to my previous post on 11/18 where I said I’ll post the source code in a couple says. Sorry that it took a little longer than a couple days. 🙂
The last port of Quake II to managed C++ .NET was done by a company called Vertigo Software in 2003. Just like Id Software‘s original release in 2001, Vertigo made their source code available under the GNU General Public License (otherwise known as the “GPL”). Back then, before .NET 2005, managed C++ had a very different syntax. When creating objects there was no way to tell whether that object was a managed type or native type. When dereferencing pointers there was no indication on whether the object was managed or native. Many keywords specific to .NET were prefaced with a double underscore (“__”). An array of managed types could be declared without being initialized. There were many, many other differences in the old syntax compared to what we have today. With the advent of .NET 2005 Microsoft changed pretty much every aspect of managed C++ from the keywords themselves to how destructors and finalizers work (destructors and finalizers basically have reversed rolls in .NET 2005 – not knowing this fact would cause most ports to crash). Microsoft’s reasoning for changing nearly everything was to make it more “logical” and conform more closely with other .NET languages. I’m not sure I agree with the “logical” part (like why I can’t declare a normal array of managed types inside a managed class or managed struct) but it does “feel” more like programming in C# without sacrificing the control of C++. For more details see Microsoft’s conversion guide here.
Microsoft will be removing old syntax support in the next version of Visual Studio (v10.0). Part of the reason I did this port was to keep the managed C++ version alive. Now let’s get into the details!
Download source code only [1.5MB] (you’ll have to install your own pak file and Quake config file).
Requirements: DirectX SDK (download here). Must have before compiling! See “Requirements” section for details.
Important note: if you compile the source code in “Release Managed” mode, for some strange reason it won’t run when started from Visual Studio, but will work fine when started from the command line or Windows explorer. However, “Debug Managed” mode works in both cases. The other configurations such as “Release Native” and “Debug Native” also worked.
Even though I pre-compiled just the managed builds in the third download above, if you want to get a zip file containing pre-compiled versions of all the build configurations (managed and native) send me an email via the Contact page of my blog (https://gregs-blog.com/contact). I’ll send you a link of where you can download it (note: that download is about 240MB).
The Quake II source is organized into six project files:
All the projects are contained in the “quake2_VC9_Port.sln” solution located in the source’s root directory. Here is a brief description of each project:
The “game” project contains the nuts and bolts of Quake II. It compiles into a dll (gamex86.dll) which the quake2.exe calls in order to do game logic. If this dll is changed, then how the game plays is also changed. This is how the “capture the flag” version of Quake works – the gamex86.dll is replaced with the dll from ctf.vcproj.
The “ctf” project contains the game logic used in the “capture the flag” version of Quake in multiplayer mode and outputs the corresponding gamex86.dll.
The “quake2” project creates the quake2.exe file. It contains the game logic that is common and doesn’t need to be changed by gamex86.dll. It does stuff like sound management, video blitting (but not actual rendering or OpenGL), file management, etc.
The “radar” project was added by Vertigo Software and is not in the original Quake II game. It is an add-on that allows the player to bring up a HUD (or separate window) showing where objects and enemies are located in the world map relative to the player’s current position. This project produces a dll called “radar.dll”.
The “ref_gl” project contains all the OpenGL code used in the game. It produces the “ref_gl.dll” file.
The “ref_soft” project contains all the software-rendering code used in the game. It is only called when running in software rendering mode or if your video card doesn’t support OpenGL (not likely these days). This project outputs “ref_soft.dll.”
In order for the source to compile you will need the DirectX SDK installed on your computer. Quake II uses DirectX for sound and keyboard/mouse/joystick input. You can download the DirectX SDK here. I personally used the June 2007 release, but it should work with much earlier versions.
After the DirectX SDK installs, check for this environment variable: DXSDK_DIR. It should have been set automatically by the DirectX installation, but if it is not there, set it manually to the root directory in which the SDK was installed (on my computer it was: “C:\Program Files\DirectX SDK (June 2007)\”).
If you have downloaded the source-only zip file, then you will need to copy the following files into the source’s root folder:
Copy any pak files from Quake’s “baseq2” directory (if you just have the Quake II demo, there will only be pak0.pak).
Copy config.cfg from Quake’s “baseq2” directory.
Copy the entire “players” folder from Quake’s baseq2 directory.
Given the default folder names of the zip file, your source tree should now contain the following:
These files and directories are needed for the post build events to work (see “Build Changes” for more details).
Game Directory Structure
There are two important directories in the Quake II installation. The first is the root folder and the second is “<root>\baseq2\.” This applies to both the commercial game and the source code tree. However, in the source tree, the root folder is not the source’s root but rather the directory in which “quake2.vcproj” uses as its output. For example, I have the Debug Managed build of “quake2.vcproj” output to “<drive>:\quake2_VC9_Port\Debug Managed\.” It is that path which is the root and thus contains: “<drive>:\quake2_VC9_Port\Debug Managed\baseq2\.”
The root folder contains all the dll’s except for gamex86.dll. Gamex86.dll goes under “<root>\baseq2\.” Other files under baseq2 are the following:
Configuration settings – config.cfg
Game data file(s) – pak0.pak
Saved games (under “.\baseq2\save\”)
Network player models (under “.\baseq2\players\”)
Since I obviously cannot distribute the non-demo commercial version of the Quake II “.pak” file(s), if you bought the full version of the game and want to run those levels under this source code, you’d copy all the “.pak” files from the retail game’s baseq2 directory into the source’s baseq2 directory. In fact, it is safe to copy all of the retail baseq2 files and overwrite any in the source tree’s baseq2. There is one caveat though – your saved games will not load under this source because the source’s version string will not match what’s in the save-file(s). But hey, you have the source code now – just find the line which does the version check and remove it! 😉
The following is a list of what build options I had to add, modify, or remove from Vertigo’s source:
- Added the following post build events for “quake2.vcproj”:
- copy “.\config.cfg” “.\$(ConfigurationName)\baseq2\config.cfg”
- copy “.\pak0.pak” “.\$(ConfigurationName)\baseq2\pak0.pak”
- xcopy /E /Y /C “.\players\*.*” “.\$(ConfigurationName)\baseq2\players\*.*”
- Working directory for “quake2.vcproj” needed to be: “.\$(ConfigurationName)”
- Output file for “game.vcproj” needed to be: “..\$(ConfigurationName)\baseq2\gamex86.dll”
- The “Additional Include Directories” needed to be “$(DXSDK_DIR)\Include” for these projects:
- The “Wp64” option needed to be removed from all projects.
- The “GS” option needed to be removed from “quake2.vcproj.”
The post build events are used to copy the configuration file, data file(s), and player models into the game’s data directory. The configuration file holds settings such as key bindings, video resolution, sound settings, and more. The pak file contains the world map, texture bitmaps, object models (for enemies, items, etc.), and any other data the game needs. All the files inside “.\players\” (which are models and pcx images) are not necessary for Quake II to run in single player mode. In fact, in multiplayer mode I was still able to start a game server and walk around in the world. However, I was unable to customize my player’s skin (an error message would appear at the bottom of the screen saying, “no valid player models found”). When debugging, the working directory for “quake2.vcproj” needs to be the exe’s output directory such that the paths are consistent with what Quake II expects. The output for “game.vcproj” needs to be changed in order for “gamex86.dll” to be copied to the baseq2 directory (see previous sections for details on how this dll works). The Additional Include Directories need to include the DirectX SDK so that the compiler can see the DirectInput and DirectSound header files. The “Wp64” option is removed from all projects in order to get rid of a certain compiler warning complaining that this option is going obsolete in the next version of Visual Studio (v10.0). Removing the “GS” option was only necessary to get rid of a warning in one of the build configurations, but since I like to be consistent I removed it from all the projects.
You may have noticed something about the first group of post build events – if “config.cfg” is moved to “baseq2” then doesn’t this mean my Quake II game settings are reset after every compile? You’re right; you actually only need this file copied the very first time the solution is compiled. After that it only needs to be copied if the one in “baseq2” has been removed.
Compiling the Source
There’s not much to say about compiling the source. Open the solution file (“quake2_VC9_Port.sln”) under the root directory of the zipped attachment. Once Visual Studio has loaded the project, make sure one of the debug build configurations are selected (I use Debug Managed). It compiles like any other Visual Studio project – press “F7” (or whatever key your Build command is bound) to start compiling. The first time around though, I recommend doing a “Rebuild All” just in case.
Hopefully all six projects build successfully with zero errors and zero warnings (if not, please go to my Contact page and send me an email). If you extracted the zip file using its default directory settings, you can go to “<drive>:\quake2_VC9_Port\<build name>\” and run Quake2.exe.
Of course, you could have originally hit “F5” (or whatever key your Debug command is bound) instead of “F7” in order to run the source in debug mode through the IDE.
One issue that I was unable to resolve was with the radar plugin – for some strange reason it always opens a new window for the control no matter whether you’re in fullscreen mode or window mode. In addition, it always starts up behind the main rendering window. While this is not that big of a deal in windowed mode, it is really annoying when playing in fullscreen. It’s possible that there is no problem with the code, but rather Vista is at fault. I don’t know, I never tried testing it in XP. Note: you activate the radar by typing “radar” in the console (the console activates by hitting the “~” key; press again to make it disappear). The radar is only enabled in Debug Managed and Release Managed builds; native mode does not have the radar. Feel free to debug this yourself and, if you fix it, please send me an email (via my Contact page).
Things Not Tested
I haven’t fully tested network play. Starting a server and walking around works fine, but I don’t know whether joining an existing server would work. Also note: this port focused exclusively on the Win32 platform. There were other projects in the Quake II source, such as a Linux version, that I did not touch. I have no idea whether these projects still work.
My Comment Log
If you take a look at the source code you’ll probably notice a lot of lines have comments that begin with this string: “***GREGS_VC9_PORT_MOD***.” A line beginning with this comment indicates that it was changed from Vertigo’s source in order to make the port work. Every one of these lines also contains a description of what was changed and, if applicable, why it was needed. You may be wondering why I did this. Well, the main reason was curiosity – I wanted to know how many lines I’d eventually end up changing. This special comment allows me to easily determine that simply by doing a solution-scope search. The second and equally important reason is simple, it serves as a really good reference for future projects that I port.
A Note About The “Safe” CRT Functions
By default, if you call regular C language CRT functions in a managed C++ project, the compiler will continually spit out warning #4996. This is a really annoying warning that basically suggests you use the corresponding “safe” version of whatever CRT function you’re calling. The safe version typically has the same name as the regular CRT call except it is suffixed with “_s” (i.e. “sprintf” would become “sprintf_s”, fopen would become “fopen_s”, and so on). When you call the safe functions you’re linking against a special version of the CRT which tries to eliminate buffer overruns. Unfortunately, I found out that these functions have bugs! Sometimes there would be runtime errors saying “string not NULL terminated” or “string too short for operation” – neither of which were true. I’d originally planned to port all the CRT functions to their safe counterparts instead of disabling the warning, but after discovering these qwerks and banging my head against the wall not finding a solution, I gave up on that idea. Some of the safe calls were left in (the one’s that worked before I discovered this problem) but only a very small number. Therefore, you will see the “#pragma warning (disable:4996)” preprocessor directive on top of a lot of files.
Performance: Native C++ Versus .NET
On modern computer hardware and the commodity-priced video cards out there, it’s difficult to come up with speed comparisons between .NET versus native C++ when running Quake II. What I mean is this – pretty much all computers with a decent 3D video card run the game at its maximum frame rate anyway regardless of whether native or managed mode is used. However, I did see a difference in performance when I turned on just software rendering and used a high resolution. The speed differences weren’t that much – the .NET version was about 9% slower (94 FPS in native versus 85 FPS in managed).
However, non graphics related things, such as initializing the game and loading maps between levels were noticeably slower in the .NET version. I would wait about seven to ten seconds for the game to load in a managed build versus about two seconds in native mode. The same amount of slowdown was noticeable when switching between levels.
For those who want to see a screenshot:
All in all, this port turned out to be much more work than I expected. My original thinking was that I’d fix a few dozen or so compile errors and be done in a few hours. Not so! I probably fixed about 2,000 to 2,500 errors (don’t know because the maximum that’s displayed is 100 to 200 before the compiler can’t recover) and had to weed out over 4,000 warnings.
Next I’m going to port Quake III Arena, but not right away; I must rest first….