(In this series, I’m going to go over the process of porting Quake II to MS-DOS. Now of course the question will pop up why? And the answer is simple enough, after [HCI]Mara’akate added in gamespy support, something was noticeably off, and that is fewer and fewer people are playing Quake 1 these days. So what to do?
Well apparently Quake II has an active following, so it’s time to move our creeky but favorite OS, MS-DOS into the Quake II Arena!
So the first thing to do is to grab a copy of the GPL source to Quake 2, along with a copy of the game, and get started on making a ‘null’ version of the game. Null versions of the game have no graphical output, no sound, and ‘function’ at a very basic level. It’s something to strive for as a first base in hitting that home run of a functional port. Thankfully iD wrote really portable and modular software. Unfortunately they tended to let their ports drift as they were writing the engine so the null code doesn’t actually work out of the box. And the project makfiles leave a little to be desired for me, as they have a bunch of i386/Dec Alpha magic which doesn’t leave much in the room for weird ports like MS-DOS.
Now I should mention that before I’d gotten started the first thing I decided that like QuakeWorld for MS-DOS (and OS/2) I would use GCC as it is a known working compiler out of the box. If you can, don’t fight so many battles of unknowns at once. Another thing is that I am going to cross compile from OS X and test with DOSBox. Of course you may want to use something else, and I know my tastes drift, but for now this is what I’m using. I’m using my old OSX to DJGPP cross compiler with GCC 2.95.3, which serves me well.
So the first thing was to compile and run the null version with native tools on OS X. After a bit of struggle I got here:
Added packfile ./baseq2/pak0.pak (3307 files)
Added packfile ./baseq2/pak1.pak (279 files)
Added packfile ./baseq2/pak2.pak (2 files)
Sys_Error: ref_soft::R_BeginFrame() – catastrophic mode change failure
Well that’s great, how to figure this one out?
One thing I did to make it easier to work with the flow of Quake is to make the Sys_Error procedure contain a divide by zero. Now why would I purposely put a divide by zero in the code? Simple it lets me back trace the code when Quake catches it’s own faults so I can see what went wrong where, vs what would look like a clean exit. From my example:
* thread #1: tid = 0xf14f9, 0x000c0853 q2`Sys_Error(error=0x000c17d4) + 98 at sys_null.c:28, queue = ‘com.apple.main-thread’, stop reason = EXC_ARITHMETIC (code=EXC_I386_DIV, subcode=0x0)
* frame #0: 0x000c0853 q2`Sys_Error(error=0x000c17d4) + 98 at sys_null.c:28
frame #1: 0x0003a0cb q2`Com_Error(code=0, fmt=0x000c17d4) + 368 at common.c:215
frame #2: 0x000c0523 q2`VID_Error(err_level=0, fmt=0x000cc8a8) + 81 at vid_null.c:45
frame #3: 0x000a6d38 q2`R_BeginFrame(camera_separation=0) + 592 at r_main.c:1145
frame #4: 0x000a4cfc q2`R_Init(hInstance=0x00000000, wndProc=0x00000000) + 276 at r_main.c:333
frame #5: 0x000c0740 q2`VID_Init + 362 at vid_null.c:122
frame #6: 0x00007900 q2`CL_Init + 55 at cl_main.c:1795
frame #7: 0x0003c2cb q2`Qcommon_Init(argc=1, argv=0xbffffc40) + 754 at common.c:1469
frame #8: 0x000c09ac q2`main(argc=1, argv=0xbffffc40) + 24 at sys_null.c:128
frame #9: 0x000019ad q2`_start + 212
frame #10: 0x000018d8 q2`start + 40
Now I know that R_BeginFrame was where the error was occurring, and looking at the code:
if ( ( err = SWimp_SetMode( &vid.width, &vid.height, sw_mode->value, vid_fullscreen->value ) ) == rserr_ok )
ri.Sys_Error( ERR_FATAL, “ref_soft::R_BeginFrame() – catastrophic mode change failure\n” );
We can see that the vid.width/vid.height aren’t being setup correctly. Turns out there was a bunch more work to be done setting up vid_null!
After looking closer at the files, I notice as I’m stitching them together is that Quake II relies on DLL’s as part of it’s base functionality. I drifted out of Quake after Quake 1, so I never played II before. So I didn’t know this. Obviously DJGPP doesn’t support DLL’s that can be loaded and un-loaded at will (Yes I know about DXE’s, but as the FAQ states, they cannot be un-loaded. And I’m not going to fight DJGPP’s LIBC). So looking further in the source, I saw these fun defines:
So at one point there was support for ‘hard linking’ in the ‘REF’ video driver, and the ‘game’ logic driver. But it did kind of drift out of the code. But looking at the Win32 version I could see that putting this functionality back in should be easy. And to be honest if I learned any lesson from this, is that I should have been pulling the Win32 version apart by injecting null into it until it ran as a null platform, then use that as the basis. Lesson learned. Always start with a known good! Quake II was built with Visual C++ 6, but I only have Visual C++ 4.2 installed on Crossover. Yes I know again this is me being difficult. But it didn’t take much time to get a simple project that has two DLL’s and a Win32 exe running. Then I took on the ‘ref’ video driver and got that linking inside of the main EXE. Now with one DLL ‘eliminated’ it was time to work on the game dll.
The game DLL posed the biggest challenge because it passes a reference to internal functions to it, and exports various functions back to the game engine. So I ended up altering the engine to not call the game import/export directly but setting it up myself. The hardest thing was that I couldn’t pull in the game header file, but rather I had to copy the prototypes myself. Another interesting thing with the way Quake II works is that the game dll has to be able to be unloaded and loaded at will. It wasn’t hard to simulate this, but I wasn’t expecting it. Again this is probably because I never really played Quake II.
Now that I had Quake II building without DLL’s I could then take the next step of removing all the IO and re-replacing it with the null code, and now I had something that looked like it was doing something.
Added packfile ./baseq2/pak0.pak (3307 files)
Added packfile ./baseq2/pak1.pak (279 files)
Added packfile ./baseq2/pak2.pak (2 files)
768k surface cache
ref_soft version: SOFT 0.01
——- sound initialization ——-
Sys_FindFirst [./baseq2/autoexec.cfg]: UNIMPLEMENTED!
==== InitGame ====
——- Server Initialization ——-
0 entities inhibited
0 teams with 0 entities
====== Quake2 Initialized ======
==== ShutdownGame ====
Sys_Error: Draw_Pic: bad coordinates
This turns out because I didn’t allocate the screen properly. Looking at the code:
if ((x < 0) || (x + w > vid.width) ||
(y + h > vid.height))
ri.Sys_Error (ERR_FATAL,”Draw_Pic: bad coordinates”);
We can see it pretty plainly.
Now since we were going somewhere I started to write some MS-DOS code, and switch out of the null set of mind!
First a simple VGA mode 13 setup which gives us 320×200 resolution with 256 available colours. And for good measure I did a simple VGA palette setup that I knew worked from a prior program. Next we just blit our buffer onto the screen, and we get this:
Which is exciting and disappointing at the same time. I then took the palette code from DOS Quake, and got something just as ugly. I tried the code from OS/2. Same thing. I tried all kinds of things and was going nowhere.
At this point Mara’akate added in the Linux clock code, and now we had this!
It wasn’t until much more digging around I saw some 320×240 screen setups that I realized there was something wrong there, and then I saw this gem in the linux port’s code:
** System specific palette setting routine. A NULL palette means
** to use the existing palette. The palette is expected to be in
** a padded 4-byte xRGB format.
In traditional VGA palette setups it’s 768 bytes that needs to be read/and pushed to the card. I even checked Quake 1 is 768 bytes, but now in Quake II, it’s 1024 bytes! OOPS! Sometimes (ok a lot of times) you really need to check other ports or a ‘known good’ to see how they did things.
So where to go from here? Obviously things like better keyboard input, the mouse, sound and networking need to be done.
Quake 3 (and variants like OpenArena) actually has people playing, and shouldn’t be /too/ much harder to port
Quake3 has no software driver for the 3d support. Instead we would have to rely on mesa3d, which would require something incredibly powerful.
While Quake II has been found to be barely playable on a P1 160, I would imagine Q3’s requirements would be something far more beefier, and less likelier to have MS-DOS ISA sound card support.
Would this help?
It appears they have a newer version of Wattcp32, and some other improvements. It’ll certainly be interesting if anything to improve QuakeWorld for MS-DOS. I was otherwise unaware of Hexen II’s MS-DOS support, thanks!
Hexen 2 is based off of Quake 1.
it apparently has streaming music?
how compile to Quake II for MS-DOS ?
You will need to be able to build executables with DJGPP. I cross compile from OS X.
It really isn’t for beginners.
Have you considered the dos allegro library for video/audio handling?
If it supports AC97 and HDA then I’d be interested. Modern audio is something lacking in MS-DOS games today.
Allegro may make it simpler to write the basic video and audio code, as you know is done for dosbox with SDL. Allegro should also have functions for the other components which are currently set to “null”.
we need more modern MS-DOS games
Allegro doesn’t support either one. A quick search found two libraries that do, Judas and “WSS”.
Here is the Q2 source code for the Amiga version:
The makefile shows the list of files used to make the Amiga specific binary. It lists files for Amiga specific sound, mouse, and keyboard code; in addition to other functions. This may provide hints in porting and gluing components together for the DOS version.
From a glance, it appears that the Amiga audio is running through a separate library (much like the same functionality in SDL and Allegro for many platforms).
I also started porting Q2 some months ago but started with the SDL-Quake2 version for linux. The source code is available with some searching. These templates may help provide hints, too.
Georg Potthast has a driver for AC97 sound  for specific chipsets.
“…and that is fewer and fewer people are playing Quake 1 these days…”
take a look at
gamespy stats showed over 300 servers, of which 4 were active…
And the one doesn’t count, CustomTF for the people, since it’s entirely bots.
Thanks for providing the compile guides! I compiled a binary in DJGPP (without requiring the dynamic loaded libraries). I also nearly built a binary in VC6 without the dll files – mainly using Alien Arena source code as a guide for removing the export/import of functions and to reference functions internally. I also made a simple makefile by placing source in a single directory.
I noted that the DJGPP SvgaLib is available already built, but it doesn’t contain full functionality (although there are reports of building newer and more functional versions, such as from the mplayer project files). Instead, I followed your method of directly accessing the video mode in the initialization function and it mirrors your result. Also, I linked the WATT32 library which allowed the build to progress instead of halting on missed functions, such as ‘gethostbyname’.
Your methods are excellent of both compiling with null functions during testing and finding a working binary without the external dependencies by testing with the win32 build.
I noted that the inline assembly is in a different format that gcc, but there are reports that these functions may be compiled outside gcc and then linked with a gcc build.
The Winquake source has examples for use of virtual memory, but I think incorporating assembly functions will have by far the greatest impact, given your forward progress.
I actually ported over the virtual memory/zone.c stuff in a separate branch and it’s mostly complete. The Mod_LoadAlias and a few other cacheing routines aren’t there. Basically you can load the game, but models and sprites aren’t rendered because their frames aren’t cached.
In any case, I didn’t notice a real improvement or stability change by doing so. For now, it’s just dormant unless I decide to finish the rest of that up.
Quake 2 already has a very similar version using the Z_* functions and I noticed after I fixed a lot of the issues with sound bugs and tightening up the dos drivers any issues with running out of memory were gone. I was able to load the game and play it just fine with 64MB RAM allocated to dosbox. It’s hard to test on my real computers because the P1 has 384MB RAM and the P2 has 1GB RAM so running out of memory for these games isn’t an issue for me.
Your work is excellent! I didn’t have any issue with 64mb of RAM in dosbox either. Thank you for testing and sharing your results. 🙂
Hello. I compiled the game with the support of new sound cards, please test your.
(ZDOOM and QUAKE 1) for DOS
email: [email protected]
Is there any chance you releasing the source to your modifications?
Yes. I think to make it free.
In the future, there is the idea of graphics hardware acceleration (Open GL) for DOS!
There are already developments. It takes time. In the future, need help programmers OpenGL QUAKE
If you can release the snd_* files and other stuff (you should be as its under GPL). then I can implement the changes to QDOS and Q2DOS.
I also see you’re using QDOS source. Any derivative works must be released in source code form as per GPL.
If you can share your source modifications that would be cool, as you used our modifications. We would like to add that sound into Quake 2, along with other projects. As you may or not be aware, QWDOS and QDOS are GPL 2.0 projects. Which means that you have to make the source available, which we are now requesting you do so.
If I will lay out on my website the files: snd_dos.c and libau.a (library sound cards) this will be enough?
Or do I need to register somewhere about the GPL?
No, you just have to make the entire source code for the project available somewhere. Posting it to your web page is fine.
Also, ZDOOM is not my project, but that is also GPL and you are required to release the entire source code used for the project.
For Quake 1, you have to mention that you used the QDOS source code at http://dk.toastednet.org/QDOS and include the readme from that project as I also give credit to any other GPL’d codebase that I used.
I have added the source code QUAKE 1 on my web page. Notify me when do sound QUAKE 2
HI ruslan, we also need the source to the mpxplay library you modified. It uses AU_search which we can’t find and you appear to be using it somehow to set the card.
I added to my web page source code for the library soundcards.
If you have questions please contact us by email
It’s in our DJGPP 2.05 testing branch right now. Works great on Qemu. Thanks for providing the source, as we were going *NOWHERE* fast, but being able to see the other side really REALLY helped.
I assume we can credit you as ‘Ruslan Starodubov’ right?
I tested the work Quake2 for DOS. Good work! Few retarding in some scenes. Sound HDA works. (With a slight clicking of 44100).
I looked in the file code snddma_dos. There’s little wrong done.
It is necessary to do so for the function AU_search:
ln = COM_CheckParm(“-spk”);
AU_search(0) for LINE_OUT AU_search(1) for STEREO_SPEAKER_OUT
This is only for Intel HDA chips embedded in the notebook’s internal speakers.
Cleaning the buffer is not required, as is done in the function AU_start()
Option -HDA I think the excess (the user may not be aware of it). Just go through all PCI devices and then ISA device.
I realized that there were minor problems for syntax DJGPP 2.05. (version GCC> 3). I used DJGPP 2.03. It makes a short code.
Servel years ago, i have imagined about porting quake2 to dos, but i dont have the ability to do so.
Me too! Surprisingly, it really wasn’t too hard. Quake 2 is very portable on it’s own. And it’s a superset of the Quake 1 driver infrastructure so it was easy to upgrade on that.
Play with nullquake, and you can see just what functions you need to make it do anything. At a minimum if you can increment an integer, and blit a buffer to a screen, you too are 1/2 the way to quake! Add in reading a keyboard, and outputing audio and you are home free!
Check out the GIT repository, and you can see how the code evolved!
Thanks for replying, i will check the git.
And what is the next step in your plan?
Can I have a version with that fucked up palette?
lol it should be easy enough to make, just comment out the ‘padding’ part of the palette!
oops sorry i post the same in the ms-dos question but i cant delete the other comment on that…
when i try to build q2dos with clean or makealldxe than i got this:
could not find *.d
could not find *.o
could not find *.obj
….and all the “del”‘s of the clean.bat
and when i try to build makesse than it says:
“Q2DOSDEVBASE not defined!”
but i wrote in every batfile the q2dos location like this:
SET SEVENZIPPATH=C:\Program Files\7-Zip
but nothing works.
sounds like you’ve got a different build base.
What is your make, and where did you get it from?
i just download the https://bitbucket.org/neozeed/q2dos revision 2017-12-31.
i use windows 10.
i dont know. if it not working for me than can you prepare new latest base and sse build for download? i see that the latest revision of your build is 2016-11-25 but the sourcecode is much newer and you make many significant changes since then (or not?) and i want the new build 😛
at least the 2016-11-25 revision works very well on my ms-dos 7.1 machine with 85 fps 🙂
Dunno : Someone had try Q2DOS inside DOSBox-X with SDL1 to try set 3DFX OpenGL to 640×480 and see if works or not ?
I tried playing Q2DOS in DOSBox. The base game was a pain to setup at first, but it works great now. But I’m having trouble with the mission packs, since certain objects won’t spawn or function properly. I ended up having to noclip through parts of Units 2 and 3 of The Reckoning due to the tank bosses and green keycards not spawning.
Which exe are you using?
Not that I’d be able to help much I’m terrible at q2, and we kinda relied on the world to qa it.
I used qemu to test builds after the basic stuff as dosbox was just too slow. Although speaking of dosbox, the latest SVN is way faster than the release code. It only needs 2 fixes for Visual C++ 2003 as well..