Setting up a Development Environment for 32-bit DOS Games

Posted: 2018-05-07

In this short post, I’m going to explain to you how easy it is to get a cross-development environment for C and C++ from Linux to DOS working.

I guess the very first question I have to address is "Why on earth would you write a DOS game in 2018???"

And that’s a really good question, with - I hope - a good answer.

You see, first of all I’m super into retro computing and retro games. And I co-host the DOS Game Club podcast, so a general interest in DOS games would be part one of the answer.

But there’s more to it. In my professional life I am an embedded software developer. I became that because I really like programming on a low level, as close to the silicon as possible. So, by programming a DOS game, I can satisfy both interests. You get full access to the computers hardware in DOS, you have to code hardware drivers to make stuff work. I enjoy that kind of work, but I also understand it’s not for everyone.

If you’re still reading, that probably means you have at least a passing interest in the topic, so let’s continue.

But why do I want to write a 32-bit game for DOS? After all, the DOS gaming experience was defined by the 16 bit era of games. And yes, that’s true, too, but I grew up a bit later. I played so many 32 bit games with neat 256-color VGA graphics that that is the definition of "video game" in my head.

So, let’s stop wasting time and see how we can actually develop a game for 32 bit DOS.

As you may or may not know, I’m also a Linux nerd. I only use Windows or OSX if I really have to. So this guide will be primarily about setting up a cross-development toolchain from Linux to DOS.

There are different cross compilers available that can compile to DOS, but I think the most widely used one today is DJGPP. It’s a GCC port that has been around since the nineties. These days it supports the newest C++ standard revisions and can be built to run natively on DOS or to cross compile from a different operating system to DOS.

There are different ways to obtain DJGPP, but the easiest way is to use the excellent build-djgpp scripts by andrewwutw. You can just clone or download the git repository from https://github.com/andrewwutw/build-djgpp:

1
$ git clone https://github.com/andrewwutw/build-djgpp.git

And then run the included script and tell it the DJGPP version you want to build. The newest one supported at the time of writing this (May 2018) is 7.2.0. Of course, building DJGPP requires a bunch of tools to be present on your development machine already, so we have to make sure they are all available.

At the very least we need a working C and C++ compiler, unzip, bison, flex, make, makeinfo and patch.

If you’re doing any C or C++ development at all, you probably have most of those installed anyway. makeinfo is usually in the texinfo package on most Linux distributions, C and C++ compilers come as gcc and g++, respectively. The others are usually called just like the program. Use your distribution’s package manager to install them, like this on Debian or Ubuntu:

1
$ sudo apt-get install g++ gcc unzip bison flex make texinfo patch

We also want to tell the build script where to install our new cross-compiler to. By default it uses /usr/local/djgpp, for which it needs root access. We can select the install location by setting the DJGPP_PREFIX environment variable before running the build script. I’ll just install it into a djgpp directory inside my home directory. So all in all the call looks like this:

1
$ DJGPP_PREFIX=~/djgpp ./build-djgpp.sh 7.2.0

If everything goes well, after a while you will have a working compiler and toolchain installed in ~/djgpp/bin.

Now, to actually test the code it would be rather inconvenient to always turn on your actual DOS machine, if you even have one nowadays. Luckily, in our modern days, we have solutions for that, too.

Most retro gamers are probably aware of DOSBox. It’s the number one tool these days to run old DOS games on modern computers or even in a browser. Again, most Linux distributions come with an easily installable package for it, conveniently called dosbox.

And that is basically everything we need to get started. To test the newly set up toolchain, let’s write a simple DOS Hello World in C++. Actually, let’s just use the canonical Hello World that everybody has seen or written in this form or another:

1
2
3
4
#include <iostream>
int main() {
  std::cout << "Hello World" << std::endl;
}

What we need to do now is to compile it with our new DJGPP. Since we installed it into a location that is not usually in the executable search path, we have to add it:

1
$ export PATH=$PATH:$HOME/djgpp/bin

We can either run this command every time we open a shell to work in, or we can add it to our .bashrc to make sure we get it every time automatically.

Now let’s compile the program. To access the compiler, we have to use the correct name, because just typing gcc or g++ would use the system-wide default compilers, which will give you x86 or x64 Linux executables instead of DOS.

The compiler executable is prefixed with the multiarch tuple for it: i586-pc-msdosdjgpp, so we have to invoke the compiler accordingly:

1
$ i586-pc-msdosdjgpp-g++ -o helloworld.exe helloworld.cpp

Which should produce the executable for DOS. Let’s check what the file tool thinks:

1
2
3
$ file helloworld.exe
helloworld.exe: MS-DOS executable, COFF for MS-DOS, DJGPP go32 DOS
extender

That looks good, but there’s one more thing this tells us: "DJGPP go32 DOS extender". That’s the thing that allows us to run 32-bit code on a 16-bit operating system like DOS.

A DOS extender is a program that sets up everything such that the 32-bit program can run and still access the hardware. There are whole books covering the relationship between x86’s "real mode" (i.e. 16 bit) and "protected mode" (i.e. 32 bit), but at this point it’s only important to know that switching to and maintaining protected mode is quite a bit of work, and we want someone else to handle that.

So, to actually run the program, we also need the extender host server as well. The one for DJGPP is called CWSDPMI.EXE (link) and we just need to copy it into the same directory as the helloworld.exe.

Now we can start DOSBox and run the program in it. Luckily DOSBox allows running commands inside it from the DOSBox command line. That makes our life a lot easier when testing, especially when we put the command for that into a script file. We need tell DOSBox to mount the current directory as a virtual drive, change to that drive and run helloworld.exe:

1
$ dosbox -c "mount x $PWD" -c "x:" -c "helloworld.exe"

I think that’s all you have to know to get started when developing a DOS game on your Linux desktop. You may want to package the compilation and running into some nice little scripts or Makefiles or Eclipse run configuration so that you don’t have to type them manually every time, but you can do that whatever way you like and you are used to when developing non-cross code.

I hope this was helpful to a few people. In some future posts I want to cover more things to consider, like graphics or sound programming, input processing and time measurement. These things are (naturally) quite different from what you may be used to when writing programs for modern desktop operating systems.

We will have to dive into the basics of low level hardware programming for that. The good thing is, most of this knowledge does not only apply to DOS game programming but to other platforms as well. It is also still relevant when doing embedded software development today. So even if we are doing that for a DOS game, it may also be useful career-wise.