Porting Quake II to MS-DOS pt3

Well it mostly works now.  But did we ever have the biggest fun with the sound and SVGA.

So [HCI]Mara’akate integrated all the Q1 sound code, got it to compile, but nothing not a peep from the sound card.  While I was busy trashing the video code, he spent way too much time on the audio, and then for the heck of it I thought I’d look at the code, although I’m really stabbing in the dark when it comes to audio.  Imagine my surprise when I compiled the code, and ran it and got the sound blasting at full volume!  It turns out that I had my audio set to ‘high quality’ in the client, while he had his set to ‘low quality’.  What it does is govern the frequency between 11Khz and 22Khz.  And if you get this wrong you get *NO* audio. OOPS.  At least it was one of those feeling vindicated moments that his efforts for sound really did work.

Now with audio, it was my time to hurry up and get the video going.  I had basic VGA working so I figured I didn’t want to spend a lot of time on this, so I was going to go with the VESA 2.0 linear frame buffer.  Well this once more again proved to be a bit more involved as the only way I really have to test is emulation via DOSBox and Qemu.  And naturally both of them worked fine, but when [HCI]Mara’akate ran it on his real DOS Box (with GUS of course) it crashed wonderfully.

Now I had taken the VESA init code from Quake 1 to build a table of what 8bit modes are supported, and used the VESA code to switch, along with drawing to the buffer with a simple memcpy.  And we got nothing but crashes.

After looking more around, I found that you had to add 0x4000 to the VESA mode you want if you wanted to access it’s LFB.  Did that work? No still crashed.

Later in the adventure we noticed to get proper access you had enable ‘near pointer acess’ with a call to :


So naturally I would disable it when I’m done, making the call look like this:



WRONG. Oh so WRONG.  Well OK technically it did work, but if sound is enabled (and why wouldn’t it be?) it would immediately crash with an error in the DMA code.  We ended up wasting over a day trying to figure this one out until I just said screw it, let’s never dsiable the nearpointer, leave it unprotected.  Naturally that actually worked.  The hint is in map_in_conventional_memory, where __djgpp_nearptr_enable(); is called, but of course there is no calls to __djgpp_nearptr_disable();  I’ve thought about going back through the source to ‘clean’ it up to make it lock and unlock memory as needed, but this is 2015 not 1997 so good enough is well, good enough.

So now with VESA and Sound, I thought this would be a great time to tackle the dynamic loading of gamex86.  We will have to re-compile whatever gamex86 DLLs are out there as of course DOS is DOS.  The HX DOS guy Japheth seems to have died, as his site and all the info on that extender seems to have mostly vanished.  I have an old copy, but I never could make it call DOS stuff if you had WIN32 stuff going on.  And if he’s really dead it’s too late to ask him.

Ages ago I remembered this DLL support for DJGPP called DLM, so I thought I’d give that a try.  I was able to take our ‘null’ version of Quake II, and get it to where it can load and unload the gamex86.dlm at will.  So I figured this was going to be an easy win, right?

Wrong again.  Once I started to put in the MS-DOS specific code I got this fun crash:

DLM -> Exiting due to signal SIGSEGV at
0x0014f59d SYM: _dos_lockmem+0x9 DLM: q2.exe [0xf5000]
possibly because of undefined reference to symbol ‘___djgpp_base_address’

DLM call frame traceback :
0x0014dcaf SYM: _Sys_PageInProgram+0x37 DLM: q2.exe [0xf5000]
0x0014e1c4 SYM: _main+0x18 DLM: q2.exe [0xf5000]

So apparently it doesn’t export (or import) djgpp stuff like the base address calculations so we can’t do direct memory access, which means no video and no sound.  Ouch.  I’ll have to hit the lists to see about support.  I don’t like the DXE’s because they cannot be unloaded, so that isn’t much good.  And of course things like the JVM inside of gamex86.dll is right out as JAVA inside of MS-DOS? in 64MB of RAM? Dream on.

So for now, the gamex86.dll is statically linked into the executable.  You are restricted to vanilla gameplay for now.

As an added bonus, I used Rufus 2.2, to setup a bootable MS-DOS USB stick, slapped everything onto there, and booted up on my Xeon, and it works!

Quake II running on MS-DOS

Quake II running on MS-DOS

Now as a super bonus,  [HCI]Mara’akate went above and beyond by adding in a bunch of fixes, and updates from 3.24 and varous stuff from Knightmare.  And then the best part is integrating libogg, so it can now play the ogg sound track!  Really, just place the ogg files in the baseq2\music directory and away you go!

For those who care the bitbucket of the project is here, and binaries are here.


For anyone who is interested, I’ve updated the binaries, to include the latest version with built in quakespy technology!  Run an /slist2 and get a list of servers!

/slist2 in action!

/slist2 in action!

For the more adventurous trying to build from source, we are using GCC 2.95.3 and DJGPP 2.04.  All of the sub libraries that you need to build are already pre-compiled in source drop.

At this point the ‘alpha 2’ version contains:

  • VGA
  • SVGA
  • Mouse
  • Keyboard
  • SoundBlaster and Gravis UltraSound Family
  • CD-ROM music
  • OGG music
  • Networking (You need a packet driver)

Installing the packet driver will require a driver from the crynwr project.  You can find a description of some of the drivers here.  Sadly for new cards I think we are left in the dark.

I’ve also compiled a ‘server’ for Linux based on the code, and put it online @

Continued in pt4, and part 5.

This entry was posted in djgpp, games, gcc, MS-DOS, quake by neozeed. Bookmark the permalink.

About neozeed

What is there to tell? I've loved UNIX like things since I was first exposed to QNX in highschool (we had the Unisys ICONS!), and spent the better time of my teenage years trying to get my own UNIX... I should have bought Coherent in retrospect.. Anyways latched onto Linux in 1992, and then got some old BSD admin books and have been hooked on the VAX BSD & other big/ancient things since...!

41 thoughts on “Porting Quake II to MS-DOS pt3

  1. Awesome! Works great!

    I didn’t have any trouble starting your Q2 port while sound quality was set to low, although I was using a dosbox build.

    I also noted that the makefile is including assembly code. I looked for this since your DOS port runs so fast (core=dynamic)! It seems to run just as fast as Quake 1!

    I am somewhat sure that there is a bot (gladiator perhaps?) that is open source and for Q2. I wonder if it is simple to swap out the game code for the one with bots??

    Thank you!

    • The sound quality being set issue has been resolved now. It ended up being related to how s_khz is used a little differently in Q1. Yes, it does appear to get about the same speeds as Q1 on a real DOS computer 😀

      Regarding the bots, if we can figure out how to get DLM working properly we could recompile other mods.

      • I replaced the /game/ directory with the files from “gladiator bot” and then added the additional files to the makefile. By disabling functions (such as Q_vsnprintf and many others), then eventually it build a binary. It errors out immediately with “sys_error”, but I wondered if there is some basic reason why this is not possible, that instead the client/server architecture is necessary to run an external mod, particularly for the bot mods. 🙂

      • I tried to mash it together, and got an executable that does… .nothing as far as I can tell. No doubt I’m missing something big… and to do anything like this we need to find a DLL solution. apparently DXE3 can unload I need to test though.

    • I tested your Q2 DOS port in PCem and it works great! It works under the interpreter core (CPU emulation). There is also a dynarec core but it is unreleased and under development.

  2. It built successfully with DJGPP (using 2.04). I had to edit a line in a DJGPP file so the linker could handle the additional libraries:

    – .text 0x1000+SIZEOF_HEADERS : {
    + .text 0x6000+SIZEOF_HEADERS : {

  3. I got a “sys_error: Couldn’t load pics/conchars.pcx” on trying to run it. Not sure what’s causing that, given that the binary seems to be self-contained… Any idea how to fix this?

    • I’m glad you are so enthusiastic…. you should fork the project, and build in your stuff from there… (hit the fork button it’s on the left hand side).

  4. Hi, great work!
    I played Q2 under DOS extender before many years via HXDOS (with SB Live sound on older MB where SBEMU worked yet). I wouldn’t belive that now in 2015 someone can put such effort making the real DOS port but ID games are quite portable so why not 🙂
    I played singleplayer mission about 1/2hour and didn’t get any crash (on my main PC C2D E8600, 4G RAM, MS-DOS 6.22). Please how can I set SVGA videomode via command line option or else when video menu crashes?
    Any chance you could try to implement some newer soundcards supports? (as it was noticed in some older post via WSS or Judas library – both are for DJGPP, WSS is currently used in Mplayer DOS port and works fine on my Realtek ALC888 HDA). Even better it would be porting sound drivers from MPXplay that also supports SB Audigy that I currently have but it’s written for OpenWatcom C compiler so it’s a different dialect and may contain assembler modules or inline in different format…

    Also note to DXE – I’m not sure if it cannot be unloaded. There are confusing infos but here http://www.delorie.com/djgpp/doc/utils/utils_19.html
    is some note that unload is possible some way:
    Also you may load/unload the library manually by calling some special functions called dlload_MODNAME and dlunload_MODNAME functions (where MODNAME stands for the name of your dynamic module).

    Second note points to your video approach via nearpointers – I wouldn’t reccomend to enable nearptr as it completly disable protection of your code. I would rather use DPMI function
    __dpmi_physical_address_mapping () and then use movedata() to copy buffer to screen or direct mapping via __djgpp_map_physical_memory() – this requires DPMI 1.0 compliant server but CWSDPMI implements this func even it’s only DPMI 0.9…

    • I need to update the binaries, we solved the SVGA VESA stuff a while ago!

      I’ve started working with Watcom C++, as I found out the causeway extender is free, and it has DLL load/unload support! I’ve managed to get null Quake II running, so the next step would be the DLL support, then some basic keyboard/mouse/video which I figured I could crib out from Hexen (since, unlike DOOM they left their DOS bits in their source release).

      Id love to do WSS but it locks up hard. Sadly debugging on MS-DOS is such a royal PITA. And my ‘PC’ has one of those ‘killer’ nic’s which there aren’t any DOS drivers for.

      The WD debugger freaks out on DOSBox and Qemu, so I’m hunting around for ISO’s I made of 10.6 and 11.something long before I lost the physical discs.

      Oh and I should add that the Q1 sound code removed all protection, that is why the VESA code crashed, as I foolishly re-protected the system when I was done drawing.

      • Does it mean that setting SVGA modes will be possible in future release (not current)?

        I met some apps using causeway extender and it sometimes crashes or doesn’t like some memory managers. It seems to me less stable than djgpp programs with cwsdpmi. Will you keep some source compatability to be able compiling both under DJGPP (only static stuff) and Watcom?

        How did you test WSS? On real PC or under emulator? What app? There’s inluded simple play example or you can download the mplayer from my site http://rayer.g6.cz/download/download.htm How did you init soundcard – auto or specific mode? It may crashed during autoprobe due to some false detection of non-existing chip or so. I don’t know details but in mplayer config file there’s ao.dev variable that you can specify directly the chip you want to use by a number or -1 to autodetect.

        BTW another nice feature not hard to implement would be mouse wheel support – some newer mouse drivers like ctmouse (included in freedos) knows the wheel but there’s very few DOS apps that use it.

        BTW2 If you want to test RAM limitation on Real DOS Machine, you can use himemx.exe XMS driver (alternative to himem.sys) that allow you to specify command line option to provide only xx MB of XMS. As CWSDPMI first try to use XMS driver it will then limit DPMI memory too. I plan to test on one of my older DOS machines – PPro200/192M/AWE64…

      • paged SVGA? I can’t see the real need TBH. I’m on my work cycle so progress slows for me, but Frank is a patching machine, so he’ll be updating stuff like crazy.

        The good news is that I found my Watcom 10.6. Hopefully it’ll be of some help.

      • for what it’s worth your link to the mplayer sources, on mediafire is now expired.

        If you check the bitbucket the mousewheel was recently added.

        I suspect a PentiumPro 200Mhz will be far too slow, a high end PII or any PIII will probably give more than satisfactory experience.

        From what I remember initializing the wss library and shutting it down caused plenty of grief. Like the game.dll state, the sound card is opened and closed throughout various game events.

      • I havent tried the WSS or intel HD audio code yet as I don’t 100% understand it. You can already change SVGA modes. If you’re testing it on a real computer and you’re not getting those modes but you did in hexen 2 or quake 1 they were probably bank modes which we don’t have because they’re mostly useless modes. Use univbe if that’s the case.

        Does HoT have mouse wheel under DOS? If he does then I could take a look at how he did it, otherwise I can’t test it as all the DOS machines I have use PS2 or serial and none of them have wheels.

        I personally test everything on multiple real hardware. I have sucessfully tested it on the following:

        Pentium 1 166 with 256MB RAM, Trident64 with Gravis UltraSound MAX, LNE100TX PCI NIC

        Pentium 2 400 with 1GB RAM, Voodoo 5 5500, Gravis UltraSound Classic and Sound Blaster 16, LNE100TX PCI NIC

        Pentium 2 300 laptop with 384MB RAM, (Can’t remember the video chip offhand), Yamaha OPL3SAx (Sound Blaster compatible) with Avaya World Card Gold WIFI PCMCIA.

        I’ve been playing the games with either UMBPCI.SYS or using HIMEM with no EMM386 and SMARTDRV and they have been loading OK. An easy test is to just load city1.bsp.

      • No, I just needed to know how to set higher VESA mode than VGA 320×200 when video menu in game is not working. I just found I can do it via sw_mode console command. But the numbers doesn’t match console manual, e.g. mode 4 is actually 1024×768 in my case instead 800×600 as described. I also patched the binary a bit to use config.dos instead of config.cfg to not interfere with my windows Quake2 settings (I placed your DOS binary beside Win32 binary into same dir).

        Yes, I’m testing on real HW – Core 2 Duo E8600, 4G RAM, 7900GT, SB Audigy & HDA – not supported sound 🙁

        I do’t know if some other game use wheel but I can look for some sample code, I also planed to use in some of my programs so I have to implement it (some day :)…
        I have Microsoft IntelliMouse Explorer with USB cable but it was sold with USB to PS/2 adapter so I use PS/2 connection and wheel works fine e.g. in Arachne web browser (you have to use /O option for ctmouse driver to enable it).

      • If you can build from source, get the latest stuff. As always it’ll be far newer state than whatever I’m building at the moment.

        the sw_mode corresponds with the mode table that dumps out on startup (page up to see it), where 0 is the VGA default 320×200 mode 13 stuff, sw_mode 1 is the first VESA entry. Since they all can be different, and some cards support way more modes than others. I know it works fine on my Nvidia Ge-Force GTX 760. Also it works on Qemu 2.20.

      • I just measured the performance of demo1.dm2 and got 200 FPS at 1024×768, nice 🙂 I’m unable to set my LCD native 1600×1200 mode (my VESA have this mode), I tried various mode numbers but got max 1280×1024.

      • Wow, I just looked to commits and see:
        Frank Sapone bf4b4eb mouse wheel code from HoT
        11 hours ago
        My wish your command 😉
        Does the VESA engine dump complete video mode table to provide all supported modes or it uses only hardcoded standardized mode numbers? AFAIK modes >1280×1024 was not standardized to give specific VESA mode number but differen manufacturers could use different numbers. So in my vesa code I do walking through table to find the number according to desired W x H parameters…

        If I try compile the source makeq2.bat I got the known error
        In file included from h:/source/game/q_shared.h:40:0,
        from client/../qcommon/qcommon.h:23,
        from client/ref.h:23,
        from client/client.h:30,
        from client/cl_input.c:22:
        e:/djgpp/include/time.h:126:16: error: expected ‘=’, ‘,’, ‘;’, ‘asm’ or ‘__attri
        bute__’ before ‘unsigned’
        _EXTERN_INLINE unsigned long long
        but what is the fix? I think it’s something evil in wattcp headers – shouldn’t it be already fixed in the repo?

      • You’re getting those problems with DJGPP compile because youre probably using 2.05. This is the same issues we ran into. If you want to fix WATTCP and port it all over to 2.05, create a fork on bitbucket, fix it up and send a pull request.

        1280×1024 I believe might be the hardcoded max for the vid_dos but i havent looked too hard into it.

      • Yes, I upgraded a month ago. I’ll try to have a look if it can be solved by changing _EXTERN_INLINE definition or if it’s something deeper…
        I tried the latest binary and video menu now works OK, but still offers up to 1280×1024.
        BTW in bitbucket sources I could see that Frank added WSS files so I I hope he’s trying 🙂

      • older GCC’s can only include so many files. You can look where _EXTERN_INLINE is defined, and define it, but the next problem you will have is that the IOCTL calls will fail on the WATTCP sockets. We have been down this road before and it doesn’t work.

      • Hi, I easily solved the _EXTERN_INLINE by changing
        line 258 to:

        #if defined(__DJGPP__) && !defined(djgpp_cdefs_incl)
        #define djgpp_cdefs_incl

        this will search for next cdefs.h and include it so the macro _EXTERN_INLINE will take effect and expand. But either eit/without this midification I got warnings about djgpp.err from wattcp:

        gcc -g -Wall -O2 -I wattcp/inc -Did386 -DREF_HARD_LINKED -DGAME_HARD_LINKED -DU
        nc -I libvorb/inc -I libcurl/include -c client/cl_input.c -o client/cl_input.o
        In file included from wattcp/inc/sys/werrno.h:53:0,
        from wattcp/inc/sys/socket.h:51,
        from client/../libcurl/include/curl/curl.h:78,
        from client/client.h:54,
        from client/cl_input.c:22:
        wattcp/inc/sys/djgpp.err:89:0: warning: “EILSEQ” redefined [enabled by default]
        #define EILSEQ 74
        that is also defined in DJGPP’s
        #if (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) \
        || !defined(__STRICT_ANSI__) || defined(__cplusplus)

        #define EILSEQ 41

        #endif /* (__STDC_VERSION__ >= 199901L) || !__STRICT_ANSI__ */
        but djgpp.err from wattcp defines it as 74. It will need to add some ifdefs and investigated how it’s decided which of djerr.203/djerr.204/djgpp.err is used in what conditions. But anyway, I got produced the Q2.EXE 🙂

      • Grr, the line should be

        I found that djgpp.err is generated by dj_err.exe from wattcp\util\ and it seems to be recompiled with djdev 2.05 but I don’t have installed slang library to do so and cannot find the prebuilt, sh configure from sourcers fail as usually… :\
        But I could fix djgpp.err manually for now…

      • hm, there’s no dj_err.c anyway in your package…
        So I simply copied djerr.204 -> djgpp.err and this sort of redefine errors has gone. It still produces a lot of various warnings (types, signedness, unused vars, uninitialized vard) but the q2.exe is done.

        Also make clean doesn’t work. I guess that instead of
        find ./ -name ‘*.o’ -exec rm {} \;
        there could be used some racursion parameter to rm ro delete all *.in the tree.

      • Yes, finds functionality changed. Look at the DJGPP mail list, a week or so ago someone recommended another way to do it that worked OK.

        If you get it compiling OK test connecting to servers, because Jason had trouble with ioctl.

      • I also found hardcoded resolution constants and redefined them to
        #define MAXWIDTH 1920
        #define MAXHEIGHT 1200
        New modes was found and works 🙂

    • I tried the mouse wheel functionality but doesn’t work for me – I cannot bind it in custom config. While in Arachne works fine.

      Then I tried the networking (under real DOS, I luckily have NIC driver) and as you said I got ioctl error 🙁
      But it was able to get a server list so it cannot be completly wrong.

      I tried so hard many hours tonigt but didn’t make it working. First to tell I rather use GCC 4.8.4 because GCC 5.1.0 may introduce aditional issues like different inline behavior between c11 and c99 (issues with ntohl where extern __inline__ need to be updated to _EXTERN_INLINE to add __attribute__ ((__gnu_inline__)) for proper GCC version). So with 4.8.4 + djdev 2.05 I had less problems but…

      I 1st tried to use my wattcp 2.2 release 10 that I compiled 2 years ago with current djdev 2.04 and gcc 4.7.3. I tries various test programs like ping, trace… that came with library and it worked. I linked this libwatt.a with quake2 and it gives same ioctl errors. I tried it to recompile with my current 4.8.4/2.05 but the same.
      2nd I found wattcp 2.2 release 10 source and lib packages in djgpp repository: ftp://ftp.delorie.com/pub/djgpp/beta/v2tk/
      and again I tried linking and then full configure and compiling but still the same.

      So it lead me to think about the problem is not in wattcp itself but in quake2 networking code that interfaces wattcp library and it may happen that it compile differently with 2.03 and 2.04/2.05.
      Anyway because of different error codes always the right version of wattcp have to be used against djdev version – I mean the djgpp.err file. We really shouldn’t mix 2.03 and 2.04 libs and code.

      Are there some other libraries that quake2 use dependent on wattcp? I think curl maybe so I recompiled it too but still the same.
      So I’m out of ideas what else to try.
      Of course one should grab all the bunch of warning that occurs during quake2 compilation and carefully analyze them if some are related to wattcp code…

      • Its DJGPP’s 2.05 libraries that conflict with wattcp. Downgrade to 2.03, and it’ll work fine. I use GCC 2.95.3 of all ancient things, and it works fine.

        I don’t know about the mouse wheel, I guess it’s a matter if the driver supports the mouse. I’ve been messing with WSS, and the good news is that I can load and unload OK. The bad news is that I only get noise. Hopefully Ruslan will share his modifications (GPL and all that) so we can all benefit.

      • Yes, I belive you that with djdev 2.03 it works. I also tried your binary from 15.6. and it connects to server and I can play fine.
        But for my other project I need to keep quite recent libC so I used djdev 2.04 many years ago and now a month ago moved to 2.05. Maybe I have some backup of older djgpp toolchain but there must be a way to fix it to move forward. I hope that someone from djgpp groups could help with it. I never worked on djdev I just use it… I suspect some constant in some header are defined differently in quake2 and wattcp or so…

        Yes, ct mouse driver provides wheel functionality (it /O parameter is used) and the mouse library have to be modified to be able to read such event. But I didn’t tried in Hexen II yes if it works there.

        About WSS – can you try some real HW? What changes Ruslan did on it? I don’t know him. I don’t know if someone else touched WSS sourcers for recent 2 years except that DMP used WSS to add HDA support for their Vortex86EX (86duino) but as it’s not maintained they didn’t send back.

      • you’ll have to build a test program to look @ the mouse. I just vaguely recall mice count their measurements in mickeys. (haha).

        I’m not too worried about the sound just yet. But it’d be nice to have sound on my xeon.

Leave a Reply

Your email address will not be published.

Notify me of followup comments via e-mail. You can also subscribe without commenting.