Running Doom under Linux

[Edit: 2014-03-05: I’m closing this thread now.  Check out the references to chocolate-doom in the comments below as probably our last resort to actually compile Doom from a reasonably corrected source for our modern times.  Beyond that, I’m pretty much as clueless as you are]

I love Doom. Actually, Doom II was the first PC game I ever got in my hands onto. The game was fast-paced, gritty and scary like hell, even with its 8 bit architecture. The sound was ominous, the levels and monsters were darn frikin’ hard to go against … I totally agree with anyone that would put it onto their top list of games since the beginning of that instrustry we call “gaming”.

This post is about compiling Doom on a modern Linux distro.

The source of the Doom engine (which ran Doom, Doom II, and Ultimate Doom) was released in 1997 as a Linux port that used X with a separate sound server. It is still available here.

I got interested in the source code in 2003 and started taking notes about compilation, PCX and 8bit paletting, and WAD (de)construction. Now, in 2010, I re-read my notes and decided to give it a go again, this time putting those notes public, although there’s nothing really new here for anyone. With so many ports of Doom that have spawned over the years, there are better options today in playing Doom with full screen graphics, perhaps in 3D, and MP3 sound and music instead using 16 bit mono WAVs and on board MIDI.

But I like retro stuff and this is as close as we can get to the original. The SDL version, made by SDL creator Sam Lantinga, is a straightfoward port of the original with sound and can run on 640 x 480 without palletting. It’s a great way to see how SDL works. But before compiling that version, keep reading this post, there is at least one modification you should be aware of.

Fixing errors and compiling

The first thing about Doom, is that its creator, id software, did released the source code but not any of the data files to run it, called IWADs, which contain the images and sounds of the game. You must either have the commercial version of Doom, Doom II or Ultimate Doom, or the shareware version (a copy of the shareware IWAD is available here). Unzip it and put that file somewhere on your system. We’ll need to point to it later on.

Unzipping the file gives you two packages, the source code of DOOM and a separate sound server that Doom will imbed at run time (apparrently, id licenced a copyrighted sound engine at the time, which is why there is no original DOS release: ‘A mistake’ says John Carmack in the README.TXT file.)

Unzip the source code and make the following changes:

In i_video:49, replace

#include <errnos.h>

with

#include <errno.h>

In i_sound.c:166 and i_video.c:669, delete all declarations of

extern int errno;

and include this file somewhere at the top of i_sound.c:

#include <errno.h>

In s_sound.c:367, replace

#ifndef SNDSRV

with

#ifndef SNDSERV

In the Makefile, add the -DSNDSERV define for compilation.

Finally, and this affects both the original and SDLDoom, change d_main.c:587 from

doomuwad = malloc(strlen(doomwaddir)+1+8+1);

to

doomuwad = malloc(strlen(doomwaddir)+1+9+1);

This “off-by-one” error will generate a ASSERT error in malloc.c:3074 once the X display kicks in. The gdb debugger will tell you so, and yet we are very far from any video initialization at this moment. It took me a while to trap this.

Compile by simply calling ‘make’ at the command line. Everything should compile under 30 seconds. Don’t (?) worry about all the warnings you’ll see passing by. They shouldn’t affect the game at this point. Feel free to clean up the code if you like.

Compiling the sound server

Unzip the sound server’s package and change to that directory. Simply call ‘make’ at the command line.

Move the ‘sndserver’ binary located in the ‘linux’ directory to the same directory you put your IWAD, mentionned above.

Switching to 8 bit

Doom runs in 8 bit (256 palettized colors) at a whoppin size of 320 x 200 pixels. It’s better to simply call another X desktop instead of ruining your current setup.

Issue the following on the command line.

sudo startx -- :1 -depth 8 vt8

This starts another desktop at vt8 (i.e. Ctrl-Alt-F8. You can return to your original desktop with Ctrl-Alt-F7 or loggin off from that session). You’ll notice how rough your desktop will look because of the 8 bit palette it’s running on.

Note that you will be running your new desktop with super-user privileges, so be VERY careful.

Open up a console and set an environment variable called DOOMWADDIR like so:

export DOOMWADDIR=<my path>

and replace <my path> with the location of the IWAD and sound server. If you dip into the code, there always a way to set those changes to a permanent location without defining this value or doing it through some parameter.

You can now run the binary located at linux/linuxdoom in the source code directory.

You’ll see your desktop flashing because Doom will set up its palette to it.

Voilà. Enjoy playing Doom on an itsy bitsy scale. 🙂

You can alway resize your desktop to 640 x 480 to see it a little larger. Don’t forget to set it back to its original size. I believe if you don’t, it will screw up your default settings once you run X again from a fresh boot.

If you don’t want to go through all this trouble, make the change I recommend to d_main.c:587 in SDLDoom, compile it and call the -fullscreen parameter to play Doom at a reasonable scale.

Using 24 bit uncompressed TARGA files

Every time I have a coding rage, I always end up needing some sort of image buffer I can blit directly into and saving it to a file. My favorite file format is the good ol’ TARGA file format, better known as the file with the *.tga extension. You can find this format in games, namely Quake 3 for saving screenshots.

I’m not going to give a detailed view of the TARGA specs. I just want to use the lossless, true-color (24 bit colors) storing option the format provides.

TARGA’s are easy to implement but are little tricky: easy because they simply have an 18 byte header and the rest that follows are mere groups of 3 bytes representing each of the 24 bit pixels. But there are also options regarding color palettes and compression, and that’s always tricky.

Let me just rapidly point, for my own needs, the most important parts of the specs:

In my latest implementation, this is the TGA header I created in C/C++:

   wh = width >> 8;
   hh = height >> 8;

   char header[18] = { 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
      width - (wh << 8), wh,
      height - (hh << 8), hh,
      24, 0x20
   };

I first set the third byte to 2, indicating that I want a true-color, uncompressed image.

I then need to encode 2 byte values representing the width and height of the image. Those dimensions are stored in little endian format, which means that the lower part of the number comes first, then the higher part.

Since numbers are stored on 8 bit values, I isolate the higher part of the width and height like so:

   wh = width >> 8;
   hh = height >> 8;

It's then easy to get the lower part by shifting back 8 bits and substracting those values from the original dimensions.

   width - ( wh << 8 ), height - ( hh << 8 )

The 17th element is the bit per pixel (bpp) and I want the 24 bit version.

Finally, the final 18th element is more complicated as information about alpha depth is stored here. I'm not using alpha at this point so I can ignore this.

But there is also something about a "direction" and that cannot be ignored. In fact, lost into these 8 remaining bits is a special feature that determine whether the image is stored with the first line on top (which we all assume as normal) or with the first line at the bottom! The original format wrote the images upside-down. Setting this value to 0, my image will be mirorred upside-down but still aligned to the left. Lighting up that bit at the 6th position of this field sets it right (hence the hexadecimal value 0x20).

All of this can be verified with the Gimp - our favorite graphics package : when first saving a TARGA file, it will ask whether you want RLE compression uncheck this) and where you want the origin to start (select Top left).

One final note concerns the encoding of the 24 bit pixels themselves. While we are all used to the red-green-blue (RGB) encoding order, TARGAs uses blue-green-red (BGR). So don't forget to swap, or else weird things will show up in your program.

Happy hacking.

Eliminating those pesky ^M’s in Vim

One on my pet peeves is dealing with a file using the 0x0D end-of-line (EOL) marquer. Such is the legacy of the Mac standard. Unix variants use 0x0A generally but even though Mac OS X is based on Unix, its seems that software used in that environment continues to produce the same old format.

But I’ve seen changes recently: one of my friends, an avid Mac user, updated a Web site lately, all pages with the correct EOL character … or at least his software does; I’m not sure if he noticed the difference anyways.

<tone down>Shhhh. He still uses ISO-8859-1 encoding</tone down>

Still, there are plenty of legacy files out there and it kills me when I have to edit one of those with my favorite editor, vim, and see all the lines squeezed into one big mess, intercalated with ^M‘s .

It kills me because there is a simple trick to correct this … and I always forget it. Aaaaarrrrgh!

This is why blogs exist: to keep your own tips on-line so you can check them out once in a while (and if other people check them out too, that’s also a Good Thing ™).

Here’s vim’s quick shortcut to stop this nonsense: enter the usual colon (:) to enable the command mode and enter any typical search-and-replace command using {Ctrl-V}{Ctrl-M} as the search pattern. Replace with a newline code such as \r, or nothing if you suspect the ^M’s are doubled with extra line feeds, like so:

1,$s/{Ctrl-V}{Ctrl-M}/\r/g

What you’ll actually see is this:

1,$s/^M/\r/g

There. I feel much better now.

Now, don’t get me started on Microsoft’s 0x0A 0x0D sequence. 🙂 Vim always dealt with it seamlessly as its “converted” mode. You can’t see the difference as you edit such file but you still output the same garbage once you save it. I wonder why no one thought about a similar filter for the 0x0D case. But my guess is that it will slowly phase out … eventually.

return top