shithub: choc

Download patch

ref: 8dab0a3e635db40359c8ddeb9afaa9eca626ee98
parent: 005747a6174d2d5b72e1af196a72cafb9b801a58
parent: 678a8f9aeea9fa1966b3e8a94974688fda4d8fe1
author: Simon Howard <[email protected]>
date: Fri Dec 10 17:44:01 EST 2010

Merge from trunk. This is slightly out of date as I did the merge
several days ago.

Subversion-branch: /branches/raven-branch
Subversion-revision: 2212

--- a/Makefile.am
+++ b/Makefile.am
@@ -53,6 +53,7 @@
         README.OPL                      \
         TODO                            \
         BUGS                            \
+        NOT-BUGS                        \
         rpm.spec
 
 MAINTAINERCLEANFILES =  $(AUX_DIST_GEN)
--- a/NEWS
+++ b/NEWS
@@ -4,8 +4,19 @@
      * The DOSbox OPL emulator (DBOPL) has been imported to replace
        the older FMOPL code.  The quality of OPL emulation is now
        therefore much better.
+     * The game can now run in screen modes at any color depth (not
+       just 8-bit modes).  This is mainly to work around problems with
+       Windows Vista/7, where 8-bit color modes don't always work
+       properly.
+     * Multiplayer servers now register themselves with an Internet
+       master server.  Use the -search command line parameter to
+       find servers on the Internet to play on.  You can also use
+       DoomSeeker (http://skulltag.net/doomseeker/) which supports
+       this functionality.
      * When running in windowed mode, it is now possible to
        dynamically resize the window by dragging the window borders.
+     * Names can be specified for servers with the -servername command
+       line parameter.
      * There are now keyboard, mouse and joystick bindings to cycle
        through available weapons, making play with joypads or mobile
        devices (ie. without a proper keyboard) much more practical.
@@ -15,10 +26,31 @@
        port, for cards that don't use port 0x388.
      * Up to 8 mouse buttons are now supported (including the
        mousewheel).
+     * The Python scripts used for building Chocolate Doom now work
+       with Python 3 (but also continue to work with Python 2)
+       (thanks arin).
+     * The font used for the textscreen library can be forced by
+       setting the TEXTSCREEN_FONT environment variable to "small" or
+       "normal".
+     * There is now a NOT-BUGS file included that lists some common
+       Vanilla Doom bugs/limitations that you might encounter.
 
+    Compatibility:
+     * The -timer and -avg options now work the same as Vanilla when
+       playing back demos (thanks xttl)
+     * A texture lookup bug was fixed that caused the wrong sky to be
+       displayed in Spooky01.wad (thanks Porsche Monty).
+     * The HacX v1.2 IWAD file is now supported, and can be used
+       standalone without the need for the Doom II IWAD (thanks
+       atyth).
+
     Bugs fixed:
+     * A workaround has been a bug in old versions of SDL_mixer
+       (v1.2.8 and earlier) that could cause the game to lock up.
+       Please upgrade to a newer version if you haven't already.
      * It is now possible to use OPL emulation at 11025Hz sound
-       sampling rate (thanks to the new OPL emulator).
+       sampling rate, due to the new OPL emulator (thanks Porsche
+       Monty).
      * The span renderer function (used for drawing floors and
        ceilings) now behaves the same as Vanilla Doom, so screenshots
        are pixel-perfect identical to Vanilla Doom (thanks Porsche
@@ -25,9 +57,19 @@
        Monty).
      * The zone memory system now aligns allocated memory to 8-byte
        boundaries on 64-bit systems, which may fix crashes on systems
-       such as sparc64.
+       such as sparc64 (thanks Ryan Freeman and Edd Barrett).
      * The configure script now checks for libm, fixing compile
-       problems on Fedora Linux.
+       problems on Fedora Linux (thanks Sander van Dijk).
+     * Sound distortion with certain music files when played back
+       using OPL (eg. Heretic title screen).
+     * Error in Windows when reading response files (thanks Porsche
+       Monty, xttl, Janizdreg).
+     * Windows Vista/7 8-bit color mode issues (the default is now to
+       run in 32-bit color depth on these versions) (thanks to
+       everybody who reported this and helped test the fix).
+     * Screen borders no longer flash when running on widescreen
+       monitors, if you choose a true-color screen mode (thanks
+       exp(x)).
 
 1.4.0 (2010-07-10):
 
--- /dev/null
+++ b/NOT-BUGS
@@ -1,0 +1,112 @@
+
+The aim of Chocolate Doom is to behave as closely to Vanilla Doom as
+possible.  As a result, you may experience problems that you would
+also experience when using Vanilla Doom.  These are not "bugs" as
+Chocolate Doom is behaving as intended.
+
+This is not intended to be a comprehensive list of Vanilla Doom bugs.
+For more information, consult the "engine bugs" page of the Doom Wiki.
+
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+== Game exits after title screen with message about game version ==
+
+The game may exit after the title screen is shown, with a message like
+the following:
+
+    Demo is from a different game version!
+    (read 106, should be 109)
+
+    *** You may need to upgrade your version of Doom to v1.9. ***
+        See: http://doomworld.com/files/patches.shtml
+        This appears to be v1.6/v1.666.
+
+This usually indicates that your IWAD file that you are using to play
+the game (usually named doom.wad or doom2.wad) is out of date.
+Chocolate Doom only supports the v1.9 IWAD file.
+
+To fix the problem, you must upgrade to the v1.9 IWAD file.  The URL
+in the message has downloadable upgrade patches that you can use to
+upgrade.
+
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+== Game exits when accessing the options menu ==
+
+The game may exit with the message "Bad V_DrawPatch" when accessing
+the options menu, if you have your mouse sensitivity set high.
+
+The Doom options menu has a slider that allows the mouse sensitivity
+to be controlled; however, it has only a very limited range. It is
+common for experienced players to set a mouse sensitivity that is much
+higher than what can be set via the options menu. The setup program
+allows a larger range of values to be set.
+
+However, setting very high sensitivity values causes the game to exit
+when accessing the options menu under Vanilla Doom. Because Chocolate
+Doom aims to emulate Vanilla Doom as closely as possible, it does the
+same thing.
+
+One solution to the problem is to set a lower mouse sensitivity.
+Alternatively, all of the settings in the options menu can be
+controlled through Doom's key bindings anyway:
+
+    End game: F7
+    Messages on/off: F8
+    Graphic detail high/low: F5
+    Screen size smaller/larger: -/+
+    Sound volume menu: F4
+
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+== Game exits with "Savegame buffer overrun" when saving the game ==
+
+If you are playing on a particularly large level, it is possible that
+when you save the game, the game will quit with the message "Savegame
+buffer overrun".
+
+Vanilla Doom has a limited size memory bufferthat it uses for saving
+games.  If you are playing on a large level, the buffer may be too
+small for the entire savegame to fit.  Chocolate Doom allows the limit
+to be disabled: in the setup tool, go to the "compatibility" menu and
+disable the "Vanilla savegame limit" option.
+
+If this error happens to you, your game has not been lost!  A file
+named temp.dsg is saved; rename this to doomsav0.dsg to make it appear
+in the first slot in the "load game" menu.  (On Unix systems, you will
+need to look in the .chocolate-doom/savegames directory in your home
+directory)
+
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+== Game ends suddenly when recording a demo ==
+
+If you are recording a very long demo, the game may exit suddenly.
+Vanilla Doom has a limited size memory buffer that it uses to save the
+demo into.  When the buffer is full, the game exits.  You can tell if
+this happens, as the demo file will be around 131,072 bytes in size.
+
+You can work around this by using the -maxdemo command line parameter
+to specify a larger buffer size.  Alternatively, the limit can be
+disabled: in the setup tool, go to the compatibility menu and disable
+the "Vanilla demo limit" option.
+
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+== Game exits with a message about "visplanes" ==
+
+The game may exit with one of these messages:
+
+    R_FindPlane: no more visplanes
+    R_DrawPlanes: visplane overflow (129)
+
+This is known as the "visplane overflow" limit and is one of the most
+well-known Vanilla Doom engine limits.  You should only ever experience
+this when trying to play an add-on level.  The level you are trying to
+play is too complex; it was most likely designed to work with a limit
+removing source port.
+
+More information can be found here:
+
+    http://rome.ro/lee_killough/editing/visplane.shtml
+
--- a/README
+++ b/README
@@ -65,9 +65,13 @@
    You are encouraged to sign up and contribute any useful information
    you may have regarding the port!
 
- * Chocolate Doom is not perfect.  See the BUGS file for a list of
-   known issues. New bug reports can be submitted to the Chocolate
-   Doom bug tracker on Sourceforge.  See:
+ * Chocolate Doom is not perfect. See the BUGS file for a list of
+   known issues. Because of the nature of the project, you may also
+   encounter Vanilla Doom bugs; these are intentionally present; see
+   the NOT-BUGS file for more information.
+
+   New bug reports can be submitted to the Chocolate Doom bug tracker
+   on Sourceforge.  See:
 
      http://sourceforge.net/projects/chocolate-doom
 
--- a/data/convert-icon
+++ b/data/convert-icon
@@ -1,7 +1,5 @@
-#!/usr/bin/python
+#!/usr/bin/env python
 #
-# $Id: convert-icon 704 2006-10-18 00:51:11Z fraggle $
-#
 # Copyright(C) 2005 Simon Howard
 #
 # This program is free software; you can redistribute it and/or
@@ -29,7 +27,7 @@
 try:
         import Image
 except ImportError:
-        print "WARNING: Could not update %s.  Please install the Python Imaging library." % sys.argv[2]
+        print("WARNING: Could not update %s.  Please install the Python Imaging library." % sys.argv[2])
         sys.exit(0)
 
 
@@ -70,5 +68,4 @@
     outfile.write("};\n")
 
 convert_image(sys.argv[1], sys.argv[2])
-
 
--- a/man/docgen
+++ b/man/docgen
@@ -150,11 +150,8 @@
 show_vanilla_options = True
 
 class Parameter:
-    def __cmp__(self, other):
-        if self.name < other.name:
-            return -1
-        else:
-            return 1
+    def __lt__(self, other):
+        return self.name < other.name
 
     def __init__(self):
         self.text = ""
@@ -389,7 +386,7 @@
     try:
         for line in f:
             line = line.replace("@content", content)
-            print line.rstrip()
+            print(line.rstrip())
 
     finally:
         f.close()
@@ -407,7 +404,7 @@
     read_wikipages()
 
     for t in targets:
-        print t.wiki_output()
+        print(t.wiki_output())
 
 def plaintext_output(targets, template_file):
 
@@ -419,13 +416,13 @@
     print_template(template_file, content)
 
 def usage():
-    print "Usage: %s [-V] [-c filename ]( -m | -w | -p ) <directory>" \
-            % sys.argv[0]
-    print "   -c :  Provide documentation for the specified configuration file"
-    print "   -m :  Manpage output"
-    print "   -w :  Wikitext output"
-    print "   -p :  Plaintext output"
-    print "   -V :  Don't show Vanilla Doom options"
+    print("Usage: %s [-V] [-c filename ]( -m | -w | -p ) <directory>" \
+            % sys.argv[0])
+    print("   -c :  Provide documentation for the specified configuration file")
+    print("   -m :  Manpage output")
+    print("   -w :  Wikitext output")
+    print("   -p :  Plaintext output")
+    print("   -V :  Don't show Vanilla Doom options")
     sys.exit(0)
 
 # Parse command line
--- a/opl/opl_sdl.c
+++ b/opl/opl_sdl.c
@@ -176,8 +176,8 @@
 
     for (i=0; i<nsamples; ++i)
     {
-        buffer[i * 2] = (int16_t) (mix_buffer[i] * 2);
-        buffer[i * 2 + 1] = (int16_t) (mix_buffer[i] * 2);
+        buffer[i * 2] = (int16_t) mix_buffer[i];
+        buffer[i * 2 + 1] = (int16_t) mix_buffer[i];
     }
 }
 
--- a/pkg/config.make.in
+++ b/pkg/config.make.in
@@ -25,6 +25,7 @@
             INSTALL      \
             NEWS         \
             BUGS         \
+            NOT-BUGS     \
             CMDLINE      \
             TODO
 
--- a/pkg/wince/wince-cabgen
+++ b/pkg/wince/wince-cabgen
@@ -3,10 +3,11 @@
 import os
 import re
 import shutil
+import struct
 import sys
 import tempfile
 
-CAB_HEADER = "MSCE"
+CAB_HEADER = "MSCE".encode("ascii")
 
 ARCHITECTURES = {
     "shx-sh3":        103,
@@ -58,16 +59,10 @@
 }
 
 def write_int16(f, value):
-    b1 = value & 0xff
-    b2 = (value >> 8) & 0xff
-    f.write("%c%c" % (b1, b2))
+    f.write(struct.pack("<H", value))
 
 def write_int32(f, value):
-    b1 = value & 0xff
-    b2 = (value >> 8) & 0xff
-    b3 = (value >> 16) & 0xff
-    b4 = (value >> 24) & 0xff
-    f.write("%c%c%c%c" % (b1, b2, b3, b4))
+    f.write(struct.pack("<I", value))
 
 # Pad a string with NUL characters so that it has a length that is 
 # a multiple of 4.  At least one NUL is always added.
@@ -208,7 +203,7 @@
         for i, s in self.string_list:
             write_int16(stream, i)
             write_int16(stream, len(s))
-            stream.write(s)
+            stream.write(s.encode("ascii"))
 
 class DirectoryList:
     def __init__(self, cab_header):
@@ -252,7 +247,7 @@
             #    dir_path = dir_path[1:]
             dir_path = [ dir ]
 
-            dir_path = map(lambda x: dictionary.get(x), dir_path)
+            dir_path = list(map(lambda x: dictionary.get(x), dir_path))
 
             self.directories[key] = self.index
             self.directories_list.append((self.index, dir_path))
@@ -334,7 +329,7 @@
             write_int16(stream, file_no)
             write_int32(stream, flags)
             write_int16(stream, len(filename))
-            stream.write(filename)
+            stream.write(filename.encode("ascii"))
 
 # TODO?
 
@@ -412,7 +407,7 @@
         # Map dirs that make up the path to strings.
 
         dictionary = self.cab_header.dictionary
-        dest_path = map(lambda x: dictionary.get(x), dest_path)
+        dest_path = list(map(lambda x: dictionary.get(x), dest_path))
 
         self.links.append((self.index, target_type, target_id,
                            base_dir, dest_path))
@@ -492,6 +487,7 @@
             section.write(stream)
             pos = stream.tell()
             if pos != old_pos + len(section):
+                print(section)
                 raise Exception("Section is %i bytes long, but %i written" % \
                                 (len(section), pos - old_pos))
             old_pos = pos
@@ -574,7 +570,7 @@
         basename = self.__shorten_name(self.files[0], 0)
         filename = os.path.join(dir, basename)
 
-        stream = file(filename, "w")
+        stream = open(filename, "wb")
         self.cab_header.write(stream)
         stream.close()
 
@@ -625,17 +621,17 @@
     # Expand $(xyz) path variables to their Windows equivalents:
 
     def replace_var(match):
-	var_name = match.group(1)
+        var_name = match.group(1)
 
-	if not var_name in DIR_VARIABLES:
-	    raise Exception("Unknown variable '%s'" % var_name)
-	else:
-	    return DIR_VARIABLES[var_name]
+        if not var_name in DIR_VARIABLES:
+            raise Exception("Unknown variable '%s'" % var_name)
+        else:
+            return DIR_VARIABLES[var_name]
 
     return re.sub(r"\$\((.*?)\)", replace_var, filename)
 
 def read_config_file(filename):
-    f = file(filename)
+    f = open(filename)
 
     data = f.readlines()
     data = "".join(data)
@@ -656,10 +652,10 @@
     files_list = config["files"]
 
     for dest, source_file in files_list.items():
-        print source_file
+        print(source_file)
 
 if len(sys.argv) < 3:
-    print "Usage: %s <config file> <output file>" % sys.argv[0]
+    print("Usage: %s <config file> <output file>" % sys.argv[0])
     sys.exit(0)
 
 if sys.argv[1] == "-d":
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -33,6 +33,7 @@
 net_io.c             net_io.h              \
 net_packet.c         net_packet.h          \
 net_sdl.c            net_sdl.h             \
+net_query.c          net_query.h           \
 net_server.c         net_server.h          \
 net_structrw.c       net_structrw.h        \
 z_native.c           z_zone.h
--- a/src/d_iwad.c
+++ b/src/d_iwad.c
@@ -47,6 +47,7 @@
     { "doom.wad",     doom,      retail,     "Doom" },
     { "doom1.wad",    doom,      shareware,  "Doom Shareware" },
     { "chex.wad",     doom,      shareware,  "Chex Quest" },
+    { "hacx.wad",     doom2,     commercial, "Hacx" },
     { "heretic.wad",  heretic,   retail,     "Heretic" },
     { "heretic1.wad", heretic,   shareware,  "Heretic Shareware" },
     { "hexen.wad",    hexen,     commercial, "Hexen" },
--- a/src/d_mode.h
+++ b/src/d_mode.h
@@ -62,6 +62,7 @@
 typedef enum
 {
     exe_doom_1_9,    // Doom 1.9: used for shareware, registered and commercial
+    exe_hacx,        // Hacx
     exe_ultimate,    // Ultimate Doom (retail)
     exe_final,       // Final Doom
     exe_chex,        // Chex Quest executable (based on Final Doom)
--- a/src/deh_io.c
+++ b/src/deh_io.c
@@ -30,21 +30,63 @@
 #include <string.h>
 
 #include "i_system.h"
+#include "w_wad.h"
 #include "z_zone.h"
 
 #include "deh_defs.h"
 #include "deh_io.h"
 
+typedef enum
+{
+    DEH_INPUT_FILE,
+    DEH_INPUT_LUMP
+} deh_input_type_t;
+
 struct deh_context_s
 {
-    FILE *stream;
+    deh_input_type_t type;
     char *filename;
+
+    // If the input comes from a memory buffer, pointer to the memory
+    // buffer.
+
+    unsigned char *input_buffer;
+    size_t input_buffer_len;
+    unsigned int input_buffer_pos;
+    int lumpnum;
+
+    // If the input comes from a file, the file stream for reading
+    // data.
+
+    FILE *stream;
+
+    // Current line number that we have reached:
+
     int linenum;
+
+    // Used by DEH_ReadLine:
+
     boolean last_was_newline;
     char *readbuffer;
     int readbuffer_size;
 };
 
+static deh_context_t *DEH_NewContext(void)
+{
+    deh_context_t *context;
+
+    context = Z_Malloc(sizeof(*context), PU_STATIC, NULL);
+
+    // Initial read buffer size of 128 bytes
+
+    context->readbuffer_size = 128;
+    context->readbuffer = Z_Malloc(context->readbuffer_size, PU_STATIC, NULL);
+    context->linenum = 0;
+    context->last_was_newline = true;
+
+    return context;
+}
+
 // Open a dehacked file for reading
 // Returns NULL if open failed
 
@@ -52,23 +94,42 @@
 {
     FILE *fstream;
     deh_context_t *context;
-    
+
     fstream = fopen(filename, "r");
 
     if (fstream == NULL)
         return NULL;
 
-    context = Z_Malloc(sizeof(*context), PU_STATIC, NULL);
+    context = DEH_NewContext();
+
+    context->type = DEH_INPUT_FILE;
     context->stream = fstream;
-    
-    // Initial read buffer size of 128 bytes
+    context->filename = strdup(filename);
 
-    context->readbuffer_size = 128;
-    context->readbuffer = Z_Malloc(context->readbuffer_size, PU_STATIC, NULL);
-    context->filename = filename;
-    context->linenum = 0;
-    context->last_was_newline = true;
+    return context;
+}
 
+// Open a WAD lump for reading.
+
+deh_context_t *DEH_OpenLump(int lumpnum)
+{
+    deh_context_t *context;
+    void *lump;
+
+    lump = W_CacheLumpNum(lumpnum, PU_STATIC);
+
+    context = DEH_NewContext();
+
+    context->type = DEH_INPUT_LUMP;
+    context->lumpnum = lumpnum;
+    context->input_buffer = lump;
+    context->input_buffer_len = W_LumpLength(lumpnum);
+    context->input_buffer_pos = 0;
+
+    context->filename = malloc(9);
+    strncpy(context->filename, lumpinfo[lumpnum].name, 8);
+    context->filename[8] = '\0';
+
     return context;
 }
 
@@ -76,33 +137,67 @@
 
 void DEH_CloseFile(deh_context_t *context)
 {
-    fclose(context->stream);
+    if (context->type == DEH_INPUT_FILE)
+    {
+        fclose(context->stream);
+    }
+    else if (context->type == DEH_INPUT_LUMP)
+    {
+        W_ReleaseLumpNum(context->lumpnum);
+    }
+
     Z_Free(context->readbuffer);
     Z_Free(context);
 }
 
+int DEH_GetCharFile(deh_context_t *context)
+{
+    if (feof(context->stream))
+    {
+        // end of file
+
+        return -1;
+    }
+
+    return fgetc(context->stream);
+}
+
+int DEH_GetCharLump(deh_context_t *context)
+{
+    int result;
+
+    if (context->input_buffer_pos >= context->input_buffer_len)
+    {
+        return -1;
+    }
+
+    result = context->input_buffer[context->input_buffer_pos];
+    ++context->input_buffer_pos;
+
+    return result;
+}
+
 // Reads a single character from a dehacked file
 
 int DEH_GetChar(deh_context_t *context)
 {
     int result;
-   
+
     // Read characters, but ignore carriage returns
     // Essentially this is a DOS->Unix conversion
 
-    do 
+    do
     {
-        if (feof(context->stream))
+        switch (context->type)
         {
-            // end of file
+            case DEH_INPUT_FILE:
+                result = DEH_GetCharFile(context);
+                break;
 
-            result = -1;
+            case DEH_INPUT_LUMP:
+                result = DEH_GetCharLump(context);
+                break;
         }
-        else
-        {
-            result = fgetc(context->stream);
-        }
-
     } while (result == '\r');
 
     // Track the current line number
@@ -111,9 +206,9 @@
     {
         ++context->linenum;
     }
-    
+
     context->last_was_newline = result == '\n';
-    
+
     return result;
 }
 
--- a/src/deh_io.h
+++ b/src/deh_io.h
@@ -30,6 +30,7 @@
 #include "deh_defs.h"
 
 deh_context_t *DEH_OpenFile(char *filename);
+deh_context_t *DEH_OpenLump(int lumpnum);
 void DEH_CloseFile(deh_context_t *context);
 int DEH_GetChar(deh_context_t *context);
 char *DEH_ReadLine(deh_context_t *context);
--- a/src/deh_main.c
+++ b/src/deh_main.c
@@ -32,6 +32,7 @@
 #include "doomtype.h"
 #include "d_iwad.h"
 #include "m_argv.h"
+#include "w_wad.h"
 
 #include "deh_defs.h"
 #include "deh_io.h"
@@ -246,9 +247,6 @@
         DEH_Error(context, "This is not a valid dehacked patch file!");
     }
 
-    deh_allow_long_strings = false;
-    deh_allow_long_cheats = false;
-    
     // Read the file
     
     for (;;) 
@@ -260,7 +258,9 @@
         // end of file?
 
         if (line == NULL)
+        {
             return;
+        }
 
         while (line[0] != '\0' && isspace(line[0]))
             ++line;
@@ -347,6 +347,48 @@
     return 1;
 }
 
+// Load dehacked file from WAD lump.
+
+int DEH_LoadLump(int lumpnum)
+{
+    deh_context_t *context;
+
+    // If it's in a lump, it's probably designed for a modern source port,
+    // so allow it to do long string and cheat replacements.
+
+    deh_allow_long_strings = true;
+    deh_allow_long_cheats = true;
+
+    context = DEH_OpenLump(lumpnum);
+
+    if (context == NULL)
+    {
+        fprintf(stderr, "DEH_LoadFile: Unable to open lump %i\n", lumpnum);
+        return 0;
+    }
+
+    DEH_ParseContext(context);
+
+    DEH_CloseFile(context);
+
+    return 1;
+}
+
+int DEH_LoadLumpByName(char *name)
+{
+    int lumpnum;
+
+    lumpnum = W_CheckNumForName(name);
+
+    if (lumpnum == -1)
+    {
+        fprintf(stderr, "DEH_LoadLumpByName: '%s' lump not found\n", name);
+        return 0;
+    }
+
+    return DEH_LoadLump(lumpnum);
+}
+
 // Checks the command line for -deh argument
 
 void DEH_Init(void)
@@ -386,5 +428,4 @@
         }
     }
 }
-
 
--- a/src/deh_main.h
+++ b/src/deh_main.h
@@ -41,6 +41,8 @@
 
 void DEH_Init(void);
 int DEH_LoadFile(char *filename);
+int DEH_LoadLump(int lumpnum);
+int DEH_LoadLumpByName(char *name);
 
 boolean DEH_ParseAssignment(char *line, char **variable_name, char **value);
 
--- a/src/doom/d_main.c
+++ b/src/doom/d_main.c
@@ -122,8 +122,6 @@
 boolean		autostart;
 int             startloadgame;
 
-FILE*		debugfile;
-
 boolean		advancedemo;
 
 // Store demo, do not accept any inputs
@@ -424,14 +422,6 @@
     if (demorecording)
 	G_BeginRecording ();
 		
-    if (M_CheckParm ("-debugfile"))
-    {
-	char    filename[20];
-	sprintf (filename,"debug%i.txt",consoleplayer);
-	printf ("debug output to: %s\n",filename);
-	debugfile = fopen (filename,"w");
-    }
-
     TryRunTics();
 
     I_SetWindowTitle(gamedescription);
@@ -611,8 +601,12 @@
 // These are from the original source: some of them are perhaps
 // not used in any dehacked patches
 
-static char *banners[] = 
+static char *banners[] =
 {
+    // doom2.wad
+    "                         "
+    "DOOM 2: Hell on Earth v%i.%i"
+    "                           ",
     // doom1.wad
     "                            "
     "DOOM Shareware Startup v%i.%i"
@@ -629,10 +623,6 @@
     "                         "
     "The Ultimate DOOM Startup v%i.%i"
     "                        ",
-    // doom2.wad
-    "                         "
-    "DOOM 2: Hell on Earth v%i.%i"
-    "                           ",
     // tnt.wad
     "                     "
     "DOOM 2: TNT - Evilution v%i.%i"
@@ -833,6 +823,18 @@
                     chex_iwadname));
 }
 
+// Check if the IWAD file is the Hacx IWAD.
+// Returns true if this is hacx.wad.
+
+static boolean CheckHacx(char *iwadname)
+{
+    char *hacx_iwadname = "hacx.wad";
+
+    return (strlen(iwadname) > strlen(hacx_iwadname)
+     && !strcasecmp(iwadname + strlen(iwadname) - strlen(hacx_iwadname),
+                    hacx_iwadname));
+}
+
 //      print title for every printed line
 char            title[128];
 
@@ -903,6 +905,7 @@
     GameVersion_t version;
 } gameversions[] = {
     {"Doom 1.9",             "1.9",        exe_doom_1_9},
+    {"Hacx",                 "hacx",       exe_hacx},
     {"Ultimate Doom",        "ultimate",   exe_ultimate},
     {"Final Doom",           "final",      exe_final},
     {"Chex Quest",           "chex",       exe_chex},
@@ -960,6 +963,12 @@
 
             gameversion = exe_chex;
         }
+        else if (CheckHacx(iwadfile))
+        {
+            // hacx exe: identified by iwad filename
+
+            gameversion = exe_hacx;
+        }
         else if (gamemode == shareware || gamemode == registered)
         {
             // original
@@ -1060,6 +1069,20 @@
     I_Endoom(endoom);
 }
 
+static void LoadHacxDeh(void)
+{
+    // If this is the HACX IWAD, we need to load the DEHACKED lump.
+
+    if (gameversion == exe_hacx)
+    {
+        if (!DEH_LoadLumpByName("DEHACKED"))
+        {
+            I_Error("DEHACKED lump not found.  Please check that this is the "
+                    "Hacx v1.2 IWAD.");
+        }
+    }
+}
+
 //
 // D_DoomMain
 //
@@ -1097,6 +1120,21 @@
     }
 
     //!
+    // @category net
+    //
+    // Query the Internet master server for a global list of active
+    // servers.
+    //
+
+    if (M_CheckParm("-search"))
+    {
+        printf("\nSearching for servers on Internet ...\n");
+        p = NET_MasterQuery(NET_QueryPrintCallback, NULL);
+        printf("\n%i server(s) found.\n", p);
+        exit(0);
+    }
+
+    //!
     // @arg <address>
     // @category net
     //
@@ -1109,6 +1147,7 @@
     if (p > 0)
     {
         NET_QueryAddress(myargv[p+1]);
+        exit(0);
     }
 
     //!
@@ -1117,8 +1156,13 @@
     // Search the local LAN for running servers.
     //
 
-    if (M_CheckParm("-search"))
-        NET_LANQuery();
+    if (M_CheckParm("-localsearch"))
+    {
+        printf("\nSearching for servers on local LAN ...\n");
+        p = NET_LANQuery(NET_QueryPrintCallback, NULL);
+        printf("\n%i server(s) found.\n", p);
+        exit(0);
+    }
 
 #endif
             
@@ -1365,6 +1409,7 @@
     D_IdentifyVersion();
     InitGameVersion();
     LoadChexDeh();
+    LoadHacxDeh();
     D_SetGameDescription();
     SetSaveGameDir(iwadfile);
 
@@ -1443,10 +1488,9 @@
 
     p = M_CheckParm ("-timer");
 
-    if (p && p < myargc-1 && deathmatch)
+    if (p && p < myargc-1)
     {
 	timelimit = atoi(myargv[p+1]);
-	printf("timer: %i\n", timelimit);
     }
 
     //!
@@ -1458,10 +1502,8 @@
 
     p = M_CheckParm ("-avg");
 
-    if (p && p < myargc-1 && deathmatch)
+    if (p && p < myargc-1)
     {
-        DEH_printf("Austin Virtual Gaming: Levels will end "
-                       "after 20 minutes\n");
 	timelimit = 20;
     }
 
--- a/src/doom/d_net.c
+++ b/src/doom/d_net.c
@@ -499,6 +499,7 @@
         if (i > 0)
         {
             addr = NET_FindLANServer();
+            NET_SV_RegisterWithMaster();
 
             if (addr == NULL)
             {
@@ -617,12 +618,22 @@
 
     // Show players here; the server might have specified a time limit
 
-    if (timelimit > 0)
+    if (timelimit > 0 && deathmatch)
     {
-	DEH_printf("Levels will end after %d minute", timelimit);
-	if (timelimit > 1)
-	    printf("s");
-	printf(".\n");
+        // Gross hack to work like Vanilla:
+
+        if (timelimit == 20 && M_CheckParm("-avg"))
+        {
+            DEH_printf("Austin Virtual Gaming: Levels will end "
+                           "after 20 minutes\n");
+        }
+        else
+        {
+            DEH_printf("Levels will end after %d minute", timelimit);
+            if (timelimit > 1)
+                printf("s");
+            printf(".\n");
+        }
     }
 }
 
@@ -634,9 +645,6 @@
 //
 void D_QuitNetGame (void)
 {
-    if (debugfile)
-	fclose (debugfile);
-
 #ifdef FEATURE_MULTIPLAYER
 
     NET_SV_Shutdown();
--- a/src/doom/doomstat.h
+++ b/src/doom/doomstat.h
@@ -255,7 +255,6 @@
 // File handling stuff.
 extern  char *          savegamedir;
 extern	char		basedefault[1024];
-extern  FILE*		debugfile;
 
 // if true, load all graphics at level load
 extern  boolean         precache;
--- a/src/doom/m_menu.c
+++ b/src/doom/m_menu.c
@@ -762,6 +762,8 @@
     switch (gameversion)
     {
         case exe_doom_1_9:
+        case exe_hacx:
+
             if (gamemode == commercial)
             {
                 // Doom 2
--- a/src/doom/p_setup.c
+++ b/src/doom/p_setup.c
@@ -755,17 +755,7 @@
     // Make sure all sounds are stopped before Z_FreeTags.
     S_Start ();			
 
-    
-#if 0 // UNUSED
-    if (debugfile)
-    {
-	Z_FreeTags (PU_LEVEL, INT_MAX);
-	Z_FileDumpHeap (debugfile);
-    }
-    else
-#endif
-	Z_FreeTags (PU_LEVEL, PU_PURGELEVEL-1);
-
+    Z_FreeTags (PU_LEVEL, PU_PURGELEVEL-1);
 
     // UNUSED W_Profile ();
     P_InitThinkers ();
--- a/src/doom/p_spec.c
+++ b/src/doom/p_spec.c
@@ -1389,10 +1389,9 @@
     if (W_CheckNumForName(DEH_String("texture2")) >= 0)
 	episode = 2;
 
-    
     // See if -TIMER was specified.
 
-    if (timelimit > 0)
+    if (timelimit > 0 && deathmatch)
     {
         levelTimer = true;
         levelTimeCount = timelimit * 60 * TICRATE;
@@ -1401,7 +1400,7 @@
     {
 	levelTimer = false;
     }
-    
+
     //	Init special SECTORs.
     sector = sectors;
     for (i=0 ; i<numsectors ; i++, sector++)
--- a/src/doom/r_data.c
+++ b/src/doom/r_data.c
@@ -413,6 +413,7 @@
 
 static void GenerateTextureHashTable(void)
 {
+    texture_t **rover;
     int i;
     int key;
 
@@ -429,12 +430,25 @@
 
         textures[i]->index = i;
 
-        // Hook into hash table
+        // Vanilla Doom does a linear search of the texures array
+        // and stops at the first entry it finds.  If there are two
+        // entries with the same name, the first one in the array
+        // wins. The new entry must therefore be added at the end
+        // of the hash chain, so that earlier entries win.
 
         key = W_LumpNameHash(textures[i]->name) % numtextures;
 
-        textures[i]->next = textures_hashtable[key];
-        textures_hashtable[key] = textures[i];
+        rover = &textures_hashtable[key];
+
+        while (*rover != NULL)
+        {
+            rover = &(*rover)->next;
+        }
+
+        // Hook into hash table
+
+        textures[i]->next = NULL;
+        *rover = textures[i];
     }
 }
 
--- a/src/i_sdlsound.c
+++ b/src/i_sdlsound.c
@@ -53,6 +53,8 @@
 #define MAX_SOUND_SLICE_TIME 70 /* ms */
 #define NUM_CHANNELS 16
 
+static boolean setpanning_workaround = false;
+
 static boolean sound_initialized = false;
 
 static sfxinfo_t *channels_playing[NUM_CHANNELS];
@@ -632,10 +634,19 @@
     if (right < 0) right = 0;
     else if (right > 255) right = 255;
 
+    // SDL_mixer version 1.2.8 and earlier has a bug in the Mix_SetPanning
+    // function.  A workaround is to call Mix_UnregisterAllEffects for
+    // the channel before calling it.  This is undesirable as it may lead
+    // to the channel volumes resetting briefly.
+
+    if (setpanning_workaround)
+    {
+        Mix_UnregisterAllEffects(handle);
+    }
+
     Mix_SetPanning(handle, left, right);
 }
 
-
 //
 // Starting a sound means adding it
 //  to the current list of active sounds
@@ -822,8 +833,34 @@
     }
 #endif
 
+    // SDL_mixer version 1.2.8 and earlier has a bug in the Mix_SetPanning
+    // function that can cause the game to lock up.  If we're using an old
+    // version, we need to apply a workaround.  But the workaround has its
+    // own drawbacks ...
+
+    {
+        const SDL_version *mixer_version;
+        int v;
+
+        mixer_version = Mix_Linked_Version();
+        v = SDL_VERSIONNUM(mixer_version->major,
+                           mixer_version->minor,
+                           mixer_version->patch);
+
+        if (v <= SDL_VERSIONNUM(1, 2, 8))
+        {
+            setpanning_workaround = true;
+            fprintf(stderr, "\n"
+              "ATTENTION: You are using an old version of SDL_mixer!\n"
+              "           This version has a bug that may cause "
+                          "your sound to stutter.\n"
+              "           Please upgrade to a newer version!\n"
+              "\n");
+        }
+    }
+
     Mix_AllocateChannels(NUM_CHANNELS);
-    
+
     SDL_PauseAudio(0);
 
     sound_initialized = true;
--- a/src/i_video.c
+++ b/src/i_video.c
@@ -139,6 +139,12 @@
 
 static char *window_title = "";
 
+// Intermediate 8-bit buffer that we draw to instead of 'screen'.
+// This is used when we are rendering in 32-bit screen mode.
+// When in a real 8-bit screen mode, screenbuffer == screen.
+
+static SDL_Surface *screenbuffer;
+
 // palette
 
 static SDL_Color palette[256];
@@ -172,6 +178,10 @@
 static int screen_width = SCREENWIDTH;
 static int screen_height = SCREENHEIGHT;
 
+// Color depth.
+
+int screen_bpp = 8;
+
 // Automatically adjust video settings if the selected mode is 
 // not a valid video mode.
 
@@ -911,17 +921,18 @@
 	return true;
     }
 
-    x_offset = (screen->w - screen_mode->width) / 2;
-    y_offset = (screen->h - screen_mode->height) / 2;
+    x_offset = (screenbuffer->w - screen_mode->width) / 2;
+    y_offset = (screenbuffer->h - screen_mode->height) / 2;
 
-    if (SDL_LockSurface(screen) >= 0)
+    if (SDL_LockSurface(screenbuffer) >= 0)
     {
         I_InitScale(I_VideoBuffer,
-                    (byte *) screen->pixels + (y_offset * screen->pitch)
-                                            + x_offset, 
+                    (byte *) screenbuffer->pixels
+                                + (y_offset * screen->pitch)
+                                + x_offset,
                     screen->pitch);
         result = screen_mode->DrawScreen(x1, y1, x2, y2);
-      	SDL_UnlockSurface(screen);
+      	SDL_UnlockSurface(screenbuffer);
     }
     else
     {
@@ -1055,19 +1066,37 @@
     // draw to screen
 
     BlitArea(0, 0, SCREENWIDTH, SCREENHEIGHT);
-    
-    // If we have a palette to set, the act of setting the palette
-    // updates the screen
 
     if (palette_to_set)
     {
-        SDL_SetColors(screen, palette, 0, 256);
+        SDL_SetColors(screenbuffer, palette, 0, 256);
         palette_to_set = false;
+
+        // In native 8-bit mode, if we have a palette to set, the act
+        // of setting the palette updates the screen
+
+        if (screenbuffer == screen)
+        {
+            return;
+        }
     }
-    else
+
+    // In 8in32 mode, we must blit from the fake 8-bit screen buffer
+    // to the real screen before doing a screen flip.
+
+    if (screenbuffer != screen)
     {
-        SDL_Flip(screen);
+        SDL_Rect dst_rect;
+
+        // Center the buffer within the full screen space.
+
+        dst_rect.x = (screen->w - screenbuffer->w) / 2;
+        dst_rect.y = (screen->h - screenbuffer->h) / 2;
+
+        SDL_BlitSurface(screenbuffer, NULL, screen, &dst_rect);
     }
+
+    SDL_Flip(screen);
 }
 
 
@@ -1348,16 +1377,62 @@
     }
 }
 
+// Auto-adjust to a valid color depth.
+
+static void AutoAdjustColorDepth(void)
+{
+    SDL_Rect **modes;
+    SDL_PixelFormat format;
+    const SDL_VideoInfo *info;
+    int flags;
+
+    if (fullscreen)
+    {
+        flags = SDL_FULLSCREEN;
+    }
+    else
+    {
+        flags = 0;
+    }
+
+    format.BitsPerPixel = screen_bpp;
+    format.BytesPerPixel = (screen_bpp + 7) / 8;
+
+    // Are any screen modes supported at the configured color depth?
+
+    modes = SDL_ListModes(&format, flags);
+
+    // If not, we must autoadjust to something sensible.
+
+    if (modes == NULL)
+    {
+        printf("I_InitGraphics: %ibpp color depth not supported.\n",
+               screen_bpp);
+
+        info = SDL_GetVideoInfo();
+
+        if (info != NULL && info->vfmt != NULL)
+        {
+            screen_bpp = info->vfmt->BitsPerPixel;
+        }
+    }
+}
+
 // If the video mode set in the configuration file is not available,
 // try to choose a different mode.
 
 static void I_AutoAdjustSettings(void)
 {
-    int old_screen_w, old_screen_h;
+    int old_screen_w, old_screen_h, old_screen_bpp;
 
     old_screen_w = screen_width;
     old_screen_h = screen_height;
+    old_screen_bpp = screen_bpp;
 
+    // Possibly adjust color depth.
+
+    AutoAdjustColorDepth();
+
     // If we are running fullscreen, try to autoadjust to a valid fullscreen
     // mode.  If this is impossible, switch to windowed.
 
@@ -1375,10 +1450,11 @@
 
     // Have the settings changed?  Show a message.
 
-    if (screen_width != old_screen_w || screen_height != old_screen_h)
+    if (screen_width != old_screen_w || screen_height != old_screen_h
+     || screen_bpp != old_screen_bpp)
     {
-        printf("I_InitGraphics: Auto-adjusted to %ix%i.\n",
-               screen_width, screen_height);
+        printf("I_InitGraphics: Auto-adjusted to %ix%ix%ibpp.\n",
+               screen_width, screen_height, screen_bpp);
 
         printf("NOTE: Your video settings have been adjusted.  "
                "To disable this behavior,\n"
@@ -1567,6 +1643,33 @@
 
     //!
     // @category video
+    // @arg <bpp>
+    //
+    // Specify the color depth of the screen, in bits per pixel.
+    //
+
+    i = M_CheckParm("-bpp");
+
+    if (i > 0)
+    {
+        screen_bpp = atoi(myargv[i + 1]);
+    }
+
+    // Because we love Eternity:
+
+    //!
+    // @category video
+    //
+    // Set the color depth of the screen to 32 bits per pixel.
+    //
+
+    if (M_CheckParm("-8in32"))
+    {
+        screen_bpp = 32;
+    }
+
+    //!
+    // @category video
     // @arg <WxY>
     //
     // Specify the screen mode (when running fullscreen) or the window
@@ -1752,8 +1855,13 @@
 
     // Set the video mode.
 
-    flags |= SDL_SWSURFACE | SDL_HWPALETTE | SDL_DOUBLEBUF;
+    flags |= SDL_SWSURFACE | SDL_DOUBLEBUF;
 
+    if (screen_bpp == 8)
+    {
+        flags |= SDL_HWPALETTE;
+    }
+
     if (fullscreen)
     {
         flags |= SDL_FULLSCREEN;
@@ -1763,13 +1871,19 @@
         flags |= SDL_RESIZABLE;
     }
 
-    screen = SDL_SetVideoMode(w, h, 8, flags);
+    screen = SDL_SetVideoMode(w, h, screen_bpp, flags);
 
     if (screen == NULL)
     {
-        I_Error("Error setting video mode: %s\n", SDL_GetError());
+        I_Error("Error setting video mode %ix%ix%ibpp: %s\n",
+                w, h, screen_bpp, SDL_GetError());
     }
 
+    // Blank out the full screen area in case there is any junk in
+    // the borders that won't otherwise be overwritten.
+
+    SDL_FillRect(screen, NULL, 0);
+
     // If mode was not set, it must be set now that we know the
     // screen size.
 
@@ -1791,6 +1905,22 @@
         }
     }
 
+    // Create the screenbuffer surface; if we have a real 8-bit palettized
+    // screen, then we can use the screen as the screenbuffer.
+
+    if (screen->format->BitsPerPixel == 8)
+    {
+        screenbuffer = screen;
+    }
+    else
+    {
+        screenbuffer = SDL_CreateRGBSurface(SDL_SWSURFACE,
+                                            mode->width, mode->height, 8,
+                                            0, 0, 0, 0);
+
+        SDL_FillRect(screenbuffer, NULL, 0);
+    }
+
     // Save screen mode.
 
     screen_mode = mode;
@@ -1908,24 +2038,13 @@
     // Start with a clear black screen
     // (screen will be flipped after we set the palette)
 
-    if (SDL_LockSurface(screen) >= 0)
-    {
-        byte *screenpixels;
-        int y;
+    SDL_FillRect(screenbuffer, NULL, 0);
 
-        screenpixels = (byte *) screen->pixels;
-
-        for (y=0; y<screen->h; ++y)
-            memset(screenpixels + screen->pitch * y, 0, screen->w);
-
-        SDL_UnlockSurface(screen);
-    }
-
     // Set the palette
 
     doompal = W_CacheLumpName(DEH_String("PLAYPAL"), PU_CACHE);
     I_SetPalette(doompal);
-    SDL_SetColors(screen, palette, 0, 256);
+    SDL_SetColors(screenbuffer, palette, 0, 256);
 
     CreateCursors();
 
@@ -1947,7 +2066,8 @@
     // Likewise if the screen pitch is not the same as the width
     // If we have to multiply, drawing is done to a separate 320x200 buf
 
-    native_surface = !SDL_MUSTLOCK(screen)
+    native_surface = screen == screenbuffer
+                  && !SDL_MUSTLOCK(screen)
                   && screen_mode == &mode_scale_1x
                   && screen->pitch == SCREENWIDTH
                   && aspect_ratio_correct;
@@ -2018,5 +2138,26 @@
     M_BindVariable("usegamma",                  &usegamma);
     M_BindVariable("vanilla_keyboard_mapping",  &vanilla_keyboard_mapping);
     M_BindVariable("novert",                    &novert);
+
+    // Windows Vista or later?  Set screen color depth to
+    // 32 bits per pixel, as 8-bit palettized screen modes
+    // don't work properly in recent versions.
+
+#if defined(_WIN32) && !defined(_WIN32_WCE)
+    {
+        OSVERSIONINFOEX version_info;
+
+        ZeroMemory(&version_info, sizeof(OSVERSIONINFOEX));
+        version_info.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
+
+        GetVersionEx((OSVERSIONINFO *) &version_info);
+
+        if (version_info.dwPlatformId == VER_PLATFORM_WIN32_NT
+         && version_info.dwMajorVersion >= 6)
+        {
+            screen_bpp = 32;
+        }
+    }
+#endif
 }
 
--- a/src/i_video.h
+++ b/src/i_video.h
@@ -116,6 +116,7 @@
 
 extern char *video_driver;
 extern boolean screenvisible;
+
 extern float mouse_acceleration;
 extern int mouse_threshold;
 extern int vanilla_keyboard_mapping;
--- a/src/m_argv.c
+++ b/src/m_argv.c
@@ -88,7 +88,7 @@
     response_filename = myargv[argv_index] + 1;
 
     // Read the response file into memory
-    handle = fopen(response_filename, "r");
+    handle = fopen(response_filename, "rb");
 
     if (handle == NULL)
     {
--- a/src/m_config.c
+++ b/src/m_config.c
@@ -555,6 +555,12 @@
     CONFIG_VARIABLE_INT(screen_height), 
 
     //!
+    // Color depth of the screen, in bits.
+    //
+
+    CONFIG_VARIABLE_INT(screen_bpp),
+
+    //!
     // If this is non-zero, the mouse will be "grabbed" when running
     // in windowed mode so that it can be used as an input device.
     // When running full screen, this has no effect.
--- a/src/net_dedicated.c
+++ b/src/net_dedicated.c
@@ -74,8 +74,8 @@
     CheckForClientOptions();
 
     NET_SV_Init();
-
     NET_SV_AddModule(&net_sdl_module);
+    NET_SV_RegisterWithMaster();
 
     while (true)
     {
--- a/src/net_defs.h
+++ b/src/net_defs.h
@@ -128,6 +128,14 @@
     NET_PACKET_TYPE_QUERY_RESPONSE,
 } net_packet_type_t;
 
+typedef enum
+{
+    NET_MASTER_PACKET_TYPE_ADD,
+    NET_MASTER_PACKET_TYPE_ADD_RESPONSE,
+    NET_MASTER_PACKET_TYPE_QUERY,
+    NET_MASTER_PACKET_TYPE_QUERY_RESPONSE
+} net_master_packet_type_t;
+
 // Settings specified when the client connects to the server.
 
 typedef struct
--- a/src/net_loop.c
+++ b/src/net_loop.c
@@ -137,9 +137,16 @@
 
 static net_addr_t *NET_CL_ResolveAddress(char *address)
 {
-    client_addr.module = &net_loop_client_module;
+    if (address == NULL)
+    {
+        client_addr.module = &net_loop_client_module;
 
-    return &client_addr;
+        return &client_addr;
+    }
+    else
+    {
+        return NULL;
+    }
 }
 
 net_module_t net_loop_client_module =
@@ -206,8 +213,15 @@
 
 static net_addr_t *NET_SV_ResolveAddress(char *address)
 {
-    server_addr.module = &net_loop_server_module;
-    return &server_addr;
+    if (address == NULL)
+    {
+        server_addr.module = &net_loop_server_module;
+        return &server_addr;
+    }
+    else
+    {
+        return NULL;
+    }
 }
 
 net_module_t net_loop_server_module =
--- a/src/net_query.c
+++ b/src/net_query.c
@@ -37,50 +37,166 @@
 #include "net_structrw.h"
 #include "net_sdl.h"
 
-typedef struct 
+// DNS address of the Internet master server.
+
+#define MASTER_SERVER_ADDRESS "master.chocolate-doom.org"
+
+// Time to wait for a response before declaring a timeout.
+
+#define QUERY_TIMEOUT_SECS 2
+
+// Number of query attempts to make before giving up on a server.
+
+#define QUERY_MAX_ATTEMPTS 3
+
+typedef enum
 {
+    QUERY_TARGET_SERVER,       // Normal server target.
+    QUERY_TARGET_MASTER,       // The master server.
+    QUERY_TARGET_BROADCAST     // Send a broadcast query
+} query_target_type_t;
+
+typedef enum
+{
+    QUERY_TARGET_QUEUED,       // Query not yet sent
+    QUERY_TARGET_QUERIED,      // Query sent, waiting response
+    QUERY_TARGET_RESPONDED,    // Response received
+    QUERY_TARGET_NO_RESPONSE
+} query_target_state_t;
+
+typedef struct
+{
+    query_target_type_t type;
+    query_target_state_t state;
     net_addr_t *addr;
     net_querydata_t data;
-} queryresponse_t;
+    unsigned int ping_time;
+    unsigned int query_time;
+    unsigned int query_attempts;
+    boolean printed;
+} query_target_t;
 
+// Transmit a query packet
+
+static boolean registered_with_master = false;
+
 static net_context_t *query_context;
-static queryresponse_t *responders;
-static int num_responses;
+static query_target_t *targets;
+static int num_targets;
 
-// Add a new address to the list of hosts that has responded
+static boolean query_loop_running = false;
+static boolean printed_header = false;
 
-static queryresponse_t *AddResponder(net_addr_t *addr, 
-                                     net_querydata_t *data)
+// Resolve the master server address.
+
+net_addr_t *NET_Query_ResolveMaster(net_context_t *context)
 {
-    queryresponse_t *response;
+    net_addr_t *addr;
 
-    responders = realloc(responders, 
-                         sizeof(queryresponse_t) * (num_responses + 1));
+    addr = NET_ResolveAddress(context, MASTER_SERVER_ADDRESS);
 
-    response = &responders[num_responses];
-    response->addr = addr;
-    response->data = *data;
-    ++num_responses;
+    if (addr == NULL)
+    {
+        fprintf(stderr, "Warning: Failed to resolve address "
+                        "for master server: %s\n", MASTER_SERVER_ADDRESS);
+    }
 
-    return response;
+    return addr;
 }
 
-// Returns true if the reply is from a host that has not previously
-// responded.
+// Send a registration packet to the master server to register
+// ourselves with the global list.
 
-static boolean CheckResponder(net_addr_t *addr)
+void NET_Query_AddToMaster(net_addr_t *master_addr)
 {
+    net_packet_t *packet;
+
+    packet = NET_NewPacket(10);
+    NET_WriteInt16(packet, NET_MASTER_PACKET_TYPE_ADD);
+    NET_SendPacket(master_addr, packet);
+    NET_FreePacket(packet);
+}
+
+// Process a packet received from the master server.
+
+void NET_Query_MasterResponse(net_packet_t *packet)
+{
+    unsigned int packet_type;
+    unsigned int result;
+
+    if (!NET_ReadInt16(packet, &packet_type)
+     || !NET_ReadInt16(packet, &result))
+    {
+        return;
+    }
+
+    if (packet_type == NET_MASTER_PACKET_TYPE_ADD_RESPONSE)
+    {
+        if (result != 0)
+        {
+            // Only show the message once.
+
+            if (!registered_with_master)
+            {
+                printf("Registered with master server at %s\n",
+                       MASTER_SERVER_ADDRESS);
+                registered_with_master = true;
+            }
+        }
+        else
+        {
+            // Always show rejections.
+
+            printf("Failed to register with master server at %s\n",
+                   MASTER_SERVER_ADDRESS);
+        }
+    }
+}
+
+// Send a query to the master server.
+
+static void NET_Query_SendMasterQuery(net_addr_t *addr)
+{
+    net_packet_t *packet;
+
+    packet = NET_NewPacket(10);
+    NET_WriteInt16(packet, NET_MASTER_PACKET_TYPE_QUERY);
+    NET_SendPacket(addr, packet);
+    NET_FreePacket(packet);
+}
+
+// Given the specified address, find the target associated.  If no
+// target is found, and 'create' is true, a new target is created.
+
+static query_target_t *GetTargetForAddr(net_addr_t *addr, boolean create)
+{
+    query_target_t *target;
     int i;
 
-    for (i=0; i<num_responses; ++i)
+    for (i=0; i<num_targets; ++i)
     {
-        if (responders[i].addr == addr)
+        if (targets[i].addr == addr)
         {
-            return false;
+            return &targets[i];
         }
     }
 
-    return true;
+    if (!create)
+    {
+        return NULL;
+    }
+
+    targets = realloc(targets, sizeof(query_target_t) * (num_targets + 1));
+
+    target = &targets[num_targets];
+    target->type = QUERY_TARGET_SERVER;
+    target->state = QUERY_TARGET_QUEUED;
+    target->printed = false;
+    target->query_attempts = 0;
+    target->addr = addr;
+    ++num_targets;
+
+    return target;
 }
 
 // Transmit a query packet
@@ -104,166 +220,254 @@
     NET_FreePacket(request);
 }
 
-static void formatted_printf(int wide, char *s, ...)
+static void NET_Query_ParseResponse(net_addr_t *addr, net_packet_t *packet,
+                                    net_query_callback_t callback,
+                                    void *user_data)
 {
-    va_list args;
-    int i;
-    
-    va_start(args, s);
-    i = vprintf(s, args);
-    va_end(args);
+    unsigned int packet_type;
+    net_querydata_t querydata;
+    query_target_t *target;
 
-    while (i < wide) 
+    // Read the header
+
+    if (!NET_ReadInt16(packet, &packet_type)
+     || packet_type != NET_PACKET_TYPE_QUERY_RESPONSE)
     {
-        putchar(' ');
-        ++i;
-    } 
-}
+        return;
+    }
 
-static char *GameDescription(GameMode_t mode, GameMission_t mission)
-{
-    switch (mode)
+    // Read query data
+
+    if (!NET_ReadQueryData(packet, &querydata))
     {
-        case shareware:
-            return "shareware";
-        case registered:
-            return "registered";
-        case retail:
-            return "ultimate";
-        case commercial:
-            if (mission == doom2)
-                return "doom2";
-            else if (mission == pack_tnt)
-                return "tnt";
-            else if (mission == pack_plut)
-                return "plutonia";
-        default:
-            return "unknown";
+        return;
     }
-}
 
-static void PrintHeader(void)
-{
-    int i;
+    // Find the target that responded, or potentially add a new target
+    // if it was not already known (for LAN broadcast search)
 
-    formatted_printf(18, "Address");
-    formatted_printf(8, "Players");
-    puts("Description");
+    target = GetTargetForAddr(addr, true);
 
-    for (i=0; i<70; ++i)
-        putchar('=');
-    putchar('\n');
+    if (target->state != QUERY_TARGET_RESPONDED)
+    {
+        target->state = QUERY_TARGET_RESPONDED;
+        memcpy(&target->data, &querydata, sizeof(net_querydata_t));
+
+        // Calculate RTT.
+
+        target->ping_time = I_GetTimeMS() - target->query_time;
+
+        // Invoke callback to signal that we have a new address.
+
+        callback(addr, &target->data, target->ping_time, user_data);
+    }
 }
 
-static void PrintResponse(queryresponse_t *response)
+// Parse a response packet from the master server.
+
+static void NET_Query_ParseMasterResponse(net_addr_t *master_addr,
+                                          net_packet_t *packet)
 {
-    formatted_printf(18, "%s: ", NET_AddrToString(response->addr));
-    formatted_printf(8, "%i/%i", response->data.num_players, 
-                                 response->data.max_players);
+    unsigned int packet_type;
+    query_target_t *target;
+    char *addr_str;
+    net_addr_t *addr;
 
-    if (response->data.gamemode != indetermined)
+    // Read the header.  We are only interested in query responses.
+
+    if (!NET_ReadInt16(packet, &packet_type)
+     || packet_type != NET_MASTER_PACKET_TYPE_QUERY_RESPONSE)
     {
-        printf("(%s) ", GameDescription(response->data.gamemode, 
-                                        response->data.gamemission));
+        return;
     }
 
-    if (response->data.server_state)
+    // Read a list of strings containing the addresses of servers
+    // that the master knows about.
+
+    for (;;)
     {
-        printf("(game running) ");
+        addr_str = NET_ReadString(packet);
+
+        if (addr_str == NULL)
+        {
+            break;
+        }
+
+        // Resolve address and add to targets list if it is not already
+        // there.
+
+        addr = NET_ResolveAddress(query_context, addr_str);
+
+        if (addr != NULL)
+        {
+            GetTargetForAddr(addr, true);
+        }
     }
 
-    NET_SafePuts(response->data.description);
+    // Mark the master as having responded.
+
+    target = GetTargetForAddr(master_addr, true);
+    target->state = QUERY_TARGET_RESPONDED;
 }
 
-static void NET_Query_ParsePacket(net_addr_t *addr, net_packet_t *packet)
+static void NET_Query_ParsePacket(net_addr_t *addr, net_packet_t *packet,
+                                  net_query_callback_t callback,
+                                  void *user_data)
 {
-    unsigned int packet_type;
-    net_querydata_t querydata;
-    queryresponse_t *response;
+    query_target_t *target;
 
-    // Have we already received a packet from this host?
+    // This might be the master server responding.
 
-    if (!CheckResponder(addr))
+    target = GetTargetForAddr(addr, false);
+
+    if (target != NULL && target->type == QUERY_TARGET_MASTER)
     {
-        return;
+        NET_Query_ParseMasterResponse(addr, packet);
     }
+    else
+    {
+        NET_Query_ParseResponse(addr, packet, callback, user_data);
+    }
+}
 
-    // Read the header
+static void NET_Query_GetResponse(net_query_callback_t callback,
+                                  void *user_data)
+{
+    net_addr_t *addr;
+    net_packet_t *packet;
 
-    if (!NET_ReadInt16(packet, &packet_type)
-     || packet_type != NET_PACKET_TYPE_QUERY_RESPONSE)
+    if (NET_RecvPacket(query_context, &addr, &packet))
     {
-        return;
+        NET_Query_ParsePacket(addr, packet, callback, user_data);
+        NET_FreePacket(packet);
     }
+}
 
-    // Read query data
+// Find a target we have not yet queried and send a query.
 
-    if (!NET_ReadQueryData(packet, &querydata))
+static void SendOneQuery(void)
+{
+    unsigned int now;
+    unsigned int i;
+
+    now = I_GetTimeMS();
+
+    for (i = 0; i < num_targets; ++i)
     {
+        // Not queried yet?
+        // Or last query timed out without a response?
+
+        if (targets[i].state == QUERY_TARGET_QUEUED
+         || (targets[i].state == QUERY_TARGET_QUERIED
+             && now - targets[i].query_time > QUERY_TIMEOUT_SECS * 1000))
+        {
+            break;
+        }
+    }
+
+    if (i >= num_targets)
+    {
         return;
     }
 
-    if (num_responses <= 0)
+    // Found a target to query.  Send a query; how to do this depends on
+    // the target type.
+
+    switch (targets[i].type)
     {
-        // If this is the first response, print the table header
+        case QUERY_TARGET_SERVER:
+            NET_Query_SendQuery(targets[i].addr);
+            break;
 
-        PrintHeader();
+        case QUERY_TARGET_BROADCAST:
+            NET_Query_SendQuery(NULL);
+            break;
+
+        case QUERY_TARGET_MASTER:
+            NET_Query_SendMasterQuery(targets[i].addr);
+            break;
     }
 
-    response = AddResponder(addr, &querydata);
+    //printf("Queried %s\n", NET_AddrToString(targets[i].addr));
+    targets[i].state = QUERY_TARGET_QUERIED;
+    targets[i].query_time = I_GetTimeMS();
+    ++targets[i].query_attempts;
+}
 
-    PrintResponse(response);
+// Time out servers that have been queried and not responded.
+
+static void CheckTargetTimeouts(void)
+{
+    unsigned int i;
+    unsigned int now;
+
+    now = I_GetTimeMS();
+
+    for (i = 0; i < num_targets; ++i)
+    {
+        // We declare a target to be "no response" when we've sent
+        // multiple query packets to it (QUERY_MAX_ATTEMPTS) and
+        // received no response to any of them.
+
+        if (targets[i].state == QUERY_TARGET_QUERIED
+         && targets[i].query_attempts >= QUERY_MAX_ATTEMPTS
+         && now - targets[i].query_time > QUERY_TIMEOUT_SECS * 1000)
+        {
+            targets[i].state = QUERY_TARGET_NO_RESPONSE;
+        }
+    }
 }
 
-static void NET_Query_GetResponse(void)
+// If all targets have responded or timed out, returns true.
+
+static boolean AllTargetsDone(void)
 {
-    net_addr_t *addr;
-    net_packet_t *packet;
+    unsigned int i;
 
-    if (NET_RecvPacket(query_context, &addr, &packet))
+    for (i = 0; i < num_targets; ++i)
     {
-        NET_Query_ParsePacket(addr, packet);
-        NET_FreePacket(packet);
+        if (targets[i].state != QUERY_TARGET_RESPONDED
+         && targets[i].state != QUERY_TARGET_NO_RESPONSE)
+        {
+            return false;
+        }
     }
+
+    return true;
 }
 
-static net_addr_t *NET_Query_QueryLoop(net_addr_t *addr, 
-                                       boolean find_one)
+// Stop the query loop
+
+static void NET_Query_ExitLoop(void)
 {
-    int start_time;
-    int last_send_time;
+    query_loop_running = false;
+}
 
-    last_send_time = -1;
-    start_time = I_GetTimeMS();
+// Loop waiting for responses.
+// The specified callback is invoked when a new server responds.
 
-    while (I_GetTimeMS() < start_time + 5000)
+static void NET_Query_QueryLoop(net_query_callback_t callback,
+                                void *user_data)
+{
+    query_loop_running = true;
+
+    while (query_loop_running && !AllTargetsDone())
     {
-        // Send a query once every second
+        // Send a query.  This will only send a single query.
+        // Because of the delay below, this is therefore rate limited.
 
-        if (last_send_time < 0 || I_GetTimeMS() > last_send_time + 1000)
-        {
-            NET_Query_SendQuery(addr);
-            last_send_time = I_GetTimeMS();
-        }
+        SendOneQuery();
 
         // Check for a response
 
-        NET_Query_GetResponse();
+        NET_Query_GetResponse(callback, user_data);
 
-        // Found a response?
-
-        if (find_one && num_responses > 0)
-            break;
-        
         // Don't thrash the CPU
-        
-        I_Sleep(100);
-    }
 
-    if (num_responses > 0)
-        return responders[0].addr;
-    else
-        return NULL;
+        I_Sleep(50);
+
+        CheckTargetTimeouts();
+    }
 }
 
 void NET_Query_Init(void)
@@ -272,51 +476,256 @@
     NET_AddModule(query_context, &net_sdl_module);
     net_sdl_module.InitClient();
 
-    responders = NULL;
-    num_responses = 0;
+    targets = NULL;
+    num_targets = 0;
+
+    printed_header = false;
 }
 
-void NET_QueryAddress(char *addr)
+// Callback that exits the query loop when the first server is found.
+
+static void NET_Query_ExitCallback(net_addr_t *addr, net_querydata_t *data,
+                                   unsigned int ping_time, void *user_data)
 {
-    net_addr_t *net_addr;
-    
-    NET_Query_Init();
+    NET_Query_ExitLoop();
+}
 
-    net_addr = NET_ResolveAddress(query_context, addr);
+// Search the targets list and find a target that has responded.
+// If none have responded, returns NULL.
 
-    if (net_addr == NULL)
+static query_target_t *FindFirstResponder(void)
+{
+    unsigned int i;
+
+    for (i = 0; i < num_targets; ++i)
     {
-        I_Error("NET_QueryAddress: Host '%s' not found!", addr);
+        if (targets[i].type == QUERY_TARGET_SERVER
+         && targets[i].state == QUERY_TARGET_RESPONDED)
+        {
+            return &targets[i];
+        }
     }
 
-    printf("\nQuerying '%s'...\n\n", addr);
+    return NULL;
+}
 
-    if (!NET_Query_QueryLoop(net_addr, true))
+// Return a count of the number of responses.
+
+static int GetNumResponses(void)
+{
+    unsigned int i;
+    int result;
+
+    result = 0;
+
+    for (i = 0; i < num_targets; ++i)
     {
-        I_Error("No response from '%s'", addr);
+        if (targets[i].type == QUERY_TARGET_SERVER
+         && targets[i].state == QUERY_TARGET_RESPONDED)
+        {
+            ++result;
+        }
     }
 
-    exit(0);
+    return result;
 }
 
+void NET_QueryAddress(char *addr_str)
+{
+    net_addr_t *addr;
+    query_target_t *target;
+
+    NET_Query_Init();
+
+    addr = NET_ResolveAddress(query_context, addr_str);
+
+    if (addr == NULL)
+    {
+        I_Error("NET_QueryAddress: Host '%s' not found!", addr_str);
+    }
+
+    // Add the address to the list of targets.
+
+    target = GetTargetForAddr(addr, true);
+
+    printf("\nQuerying '%s'...\n", addr_str);
+
+    // Run query loop.
+
+    NET_Query_QueryLoop(NET_Query_ExitCallback, NULL);
+
+    // Check if the target responded.
+
+    if (target->state == QUERY_TARGET_RESPONDED)
+    {
+        NET_QueryPrintCallback(addr, &target->data, target->ping_time, NULL);
+    }
+    else
+    {
+        I_Error("No response from '%s'", addr_str);
+    }
+}
+
 net_addr_t *NET_FindLANServer(void)
 {
+    query_target_t *target;
+    query_target_t *responder;
+
     NET_Query_Init();
 
-    return NET_Query_QueryLoop(NULL, true);
+    // Add a broadcast target to the list.
+
+    target = GetTargetForAddr(NULL, true);
+    target->type = QUERY_TARGET_BROADCAST;
+
+    // Run the query loop, and stop at the first target found.
+
+    NET_Query_QueryLoop(NET_Query_ExitCallback, NULL);
+
+    responder = FindFirstResponder();
+
+    if (responder != NULL)
+    {
+        return responder->addr;
+    }
+    else
+    {
+        return NULL;
+    }
 }
 
-void NET_LANQuery(void)
+int NET_LANQuery(net_query_callback_t callback, void *user_data)
 {
+    query_target_t *target;
+
     NET_Query_Init();
 
-    printf("\nSearching for servers on local LAN ...\n\n");
+    // Add a broadcast target to the list.
 
-    if (!NET_Query_QueryLoop(NULL, false))
+    target = GetTargetForAddr(NULL, true);
+    target->type = QUERY_TARGET_BROADCAST;
+
+    NET_Query_QueryLoop(callback, user_data);
+
+    return GetNumResponses();
+}
+
+int NET_MasterQuery(net_query_callback_t callback, void *user_data)
+{
+    net_addr_t *master;
+    query_target_t *target;
+
+    NET_Query_Init();
+
+    // Resolve master address and add to targets list.
+
+    master = NET_Query_ResolveMaster(query_context);
+
+    if (master == NULL)
     {
-        I_Error("No servers found");
+        return 0;
     }
 
-    exit(0);
+    target = GetTargetForAddr(master, true);
+    target->type = QUERY_TARGET_MASTER;
+
+    NET_Query_QueryLoop(callback, user_data);
+
+    // Check that we got a response from the master, and display
+    // a warning if we didn't.
+
+    if (target->state == QUERY_TARGET_NO_RESPONSE)
+    {
+        fprintf(stderr, "NET_MasterQuery: no response from master server.\n");
+    }
+
+    return GetNumResponses();
+}
+
+static void formatted_printf(int wide, char *s, ...)
+{
+    va_list args;
+    int i;
+
+    va_start(args, s);
+    i = vprintf(s, args);
+    va_end(args);
+
+    while (i < wide)
+    {
+        putchar(' ');
+        ++i;
+    }
+}
+
+static char *GameDescription(GameMode_t mode, GameMission_t mission)
+{
+    switch (mode)
+    {
+        case shareware:
+            return "shareware";
+        case registered:
+            return "registered";
+        case retail:
+            return "ultimate";
+        case commercial:
+            if (mission == doom2)
+                return "doom2";
+            else if (mission == pack_tnt)
+                return "tnt";
+            else if (mission == pack_plut)
+                return "plutonia";
+        default:
+            return "unknown";
+    }
+}
+
+static void PrintHeader(void)
+{
+    int i;
+
+    putchar('\n');
+    formatted_printf(5, "Ping");
+    formatted_printf(18, "Address");
+    formatted_printf(8, "Players");
+    puts("Description");
+
+    for (i=0; i<70; ++i)
+        putchar('=');
+    putchar('\n');
+}
+
+// Callback function that just prints information in a table.
+
+void NET_QueryPrintCallback(net_addr_t *addr,
+                            net_querydata_t *data,
+                            unsigned int ping_time,
+                            void *user_data)
+{
+    // If this is the first server, print the header.
+
+    if (!printed_header)
+    {
+        PrintHeader();
+        printed_header = true;
+    }
+
+    formatted_printf(5, "%4i", ping_time);
+    formatted_printf(18, "%s: ", NET_AddrToString(addr));
+    formatted_printf(8, "%i/%i", data->num_players, 
+                                 data->max_players);
+
+    if (data->gamemode != indetermined)
+    {
+        printf("(%s) ", GameDescription(data->gamemode, 
+                                        data->gamemission));
+    }
+
+    if (data->server_state)
+    {
+        printf("(game running) ");
+    }
+
+    NET_SafePuts(data->description);
 }
 
--- a/src/net_query.h
+++ b/src/net_query.h
@@ -27,9 +27,22 @@
 
 #include "net_defs.h"
 
+typedef void (*net_query_callback_t)(net_addr_t *addr,
+                                     net_querydata_t *querydata,
+                                     unsigned int ping_time,
+                                     void *user_data);
+
+extern int NET_LANQuery(net_query_callback_t callback, void *user_data);
+extern int NET_MasterQuery(net_query_callback_t callback, void *user_data);
 extern void NET_QueryAddress(char *addr);
-extern void NET_LANQuery(void);
 extern net_addr_t *NET_FindLANServer(void);
+
+extern void NET_QueryPrintCallback(net_addr_t *addr, net_querydata_t *data,
+                                   unsigned int ping_time, void *user_data);
+
+extern net_addr_t *NET_Query_ResolveMaster(net_context_t *context);
+extern void NET_Query_AddToMaster(net_addr_t *master_addr);
+extern void NET_Query_MasterResponse(net_packet_t *packet);
 
 #endif /* #ifndef NET_QUERY_H */
 
--- a/src/net_server.c
+++ b/src/net_server.c
@@ -41,10 +41,15 @@
 #include "net_io.h"
 #include "net_loop.h"
 #include "net_packet.h"
+#include "net_query.h"
 #include "net_server.h"
 #include "net_sdl.h"
 #include "net_structrw.h"
 
+// How often to refresh our registration with the master server.
+
+#define MASTER_REFRESH_PERIOD 20 * 60 /* 20 minutes */
+
 typedef enum
 {
     // waiting for the game to start
@@ -128,6 +133,11 @@
 static unsigned int sv_gamemission;
 static net_gamesettings_t sv_settings;
 
+// For registration with master server:
+
+static net_addr_t *master_server = NULL;
+static unsigned int master_refresh_time;
+
 // receive window
 
 static unsigned int recvwindow_start;
@@ -1067,6 +1077,7 @@
 {
     net_packet_t *reply;
     net_querydata_t querydata;
+    int p;
 
     // Version
 
@@ -1086,10 +1097,23 @@
     querydata.gamemode = sv_gamemode;
     querydata.gamemission = sv_gamemission;
 
-    // Server description.  This is currently hard-coded.
+    //!
+    // @arg <name>
+    //
+    // When starting a network server, specify a name for the server.
+    //
 
-    querydata.description = "Chocolate Doom server";
+    p = M_CheckParm("-servername");
 
+    if (p > 0 && p + 1 < myargc)
+    {
+        querydata.description = myargv[p + 1];
+    }
+    else
+    {
+        querydata.description = "Unnamed server";
+    }
+
     // Send it and we're done.
 
     reply = NET_NewPacket(64);
@@ -1106,6 +1130,14 @@
     net_client_t *client;
     unsigned int packet_type;
 
+    // Response from master server?
+
+    if (addr != NULL && addr == master_server)
+    {
+        NET_Query_MasterResponse(packet);
+        return;
+    }
+
     // Find which client this packet came from
 
     client = NET_SV_FindClient(addr);
@@ -1511,6 +1543,32 @@
     server_initialized = true;
 }
 
+void NET_SV_RegisterWithMaster(void)
+{
+    //!
+    // When running a server, don't register with the global master server.
+    //
+    // @category net
+    //
+
+    if (!M_CheckParm("-privateserver"))
+    {
+        master_server = NET_Query_ResolveMaster(server_context);
+    }
+    else
+    {
+        master_server = NULL;
+    }
+
+    // Send request.
+
+    if (master_server != NULL)
+    {
+        NET_Query_AddToMaster(master_server);
+        master_refresh_time = I_GetTimeMS();
+    }
+}
+
 // Run server code to check for new packets/send packets as the server
 // requires
 
@@ -1525,10 +1583,19 @@
         return;
     }
 
-    while (NET_RecvPacket(server_context, &addr, &packet)) 
+    while (NET_RecvPacket(server_context, &addr, &packet))
     {
         NET_SV_Packet(packet, addr);
         NET_FreePacket(packet);
+    }
+
+    // Possibly refresh our registration with the master server.
+
+    if (master_server != NULL
+     && I_GetTimeMS() - master_refresh_time > MASTER_REFRESH_PERIOD * 1000)
+    {
+        NET_Query_AddToMaster(master_server);
+        master_refresh_time = I_GetTimeMS();
     }
 
     // "Run" any clients that may have things to do, independent of responses
--- a/src/net_server.h
+++ b/src/net_server.h
@@ -41,5 +41,9 @@
 
 void NET_SV_AddModule(net_module_t *module);
 
+// Register server with master server.
+
+void NET_SV_RegisterWithMaster(void);
+
 #endif /* #ifndef NET_SERVER_H */
 
--- a/src/setup/display.c
+++ b/src/setup/display.c
@@ -32,6 +32,27 @@
 
 #include "display.h"
 
+typedef struct
+{
+    char *description;
+    int bpp;
+} pixel_depth_t;
+
+// List of supported pixel depths.
+
+static pixel_depth_t pixel_depths[] =
+{
+    { "8-bit",    8 },
+    { "16-bit",   16 },
+    { "24-bit",   24 },
+    { "32-bit",   32 },
+};
+
+// List of strings containing supported pixel depths.
+
+static char **supported_bpps;
+static int num_supported_bpps;
+
 typedef struct 
 {
     int w, h;
@@ -78,6 +99,7 @@
 static int fullscreen = 1;
 static int screen_width = 320;
 static int screen_height = 200;
+static int screen_bpp = 8;
 static int startup_delay = 1000;
 static int graphical_startup = 1;
 static int show_endoom = 1;
@@ -90,6 +112,10 @@
 
 static int selected_screen_width = 0, selected_screen_height;
 
+// Index into the supported_bpps of the selected pixel depth.
+
+static int selected_bpp = 0;
+
 static int system_video_env_set;
 
 // Set the SDL_VIDEODRIVER environment variable
@@ -133,6 +159,153 @@
     }
 }
 
+// Query SDL as to whether any fullscreen modes are available for the
+// specified pixel depth.
+
+static int PixelDepthSupported(int bpp)
+{
+    SDL_PixelFormat format;
+    SDL_Rect **modes;
+
+    format.BitsPerPixel = bpp;
+    format.BytesPerPixel = (bpp + 7) / 8;
+
+    modes = SDL_ListModes(&format, SDL_FULLSCREEN);
+
+    return modes != NULL;
+}
+
+// Query SDL and populate the supported_bpps array.
+
+static void IdentifyPixelDepths(void)
+{
+    unsigned int i;
+    unsigned int num_depths = sizeof(pixel_depths) / sizeof(*pixel_depths);
+
+    if (supported_bpps != NULL)
+    {
+        free(supported_bpps);
+    }
+
+    supported_bpps = malloc(sizeof(char *) * num_depths);
+    num_supported_bpps = 0;
+
+    // Check each bit depth to determine if modes are available.
+
+    for (i = 0; i < num_depths; ++i)
+    {
+        // If modes are available, add this bit depth to the list.
+
+        if (PixelDepthSupported(pixel_depths[i].bpp))
+        {
+            supported_bpps[num_supported_bpps] = pixel_depths[i].description;
+            ++num_supported_bpps;
+        }
+    }
+
+    // No supported pixel depths?  That's kind of a problem.  Add 8bpp
+    // as a fallback.
+
+    if (num_supported_bpps == 0)
+    {
+        supported_bpps[0] = pixel_depths[0].description;
+        ++num_supported_bpps;
+    }
+}
+
+// Get the screen pixel depth corresponding to what selected_bpp is set to.
+
+static int GetSelectedBPP(void)
+{
+    unsigned int num_depths = sizeof(pixel_depths) / sizeof(*pixel_depths);
+    unsigned int i;
+
+    // Find which pixel depth is selected, and set screen_bpp.
+
+    for (i = 0; i < num_depths; ++i)
+    {
+        if (pixel_depths[i].description == supported_bpps[selected_bpp])
+        {
+            return pixel_depths[i].bpp;
+        }
+    }
+
+    // Default fallback value.
+
+    return 8;
+}
+
+// Get the index into supported_bpps of the specified pixel depth string.
+
+static int GetSupportedBPPIndex(char *description)
+{
+    unsigned int i;
+
+    for (i = 0; i < num_supported_bpps; ++i)
+    {
+        if (supported_bpps[i] == description)
+        {
+            return i;
+        }
+    }
+
+    // Shouldn't happen; fall back to the first in the list.
+
+    return 0;
+}
+
+// Set selected_bpp to match screen_bpp.
+
+static int TrySetSelectedBPP(void)
+{
+    unsigned int num_depths = sizeof(pixel_depths) / sizeof(*pixel_depths);
+    unsigned int i;
+
+    // Search pixel_depths, find the bpp that corresponds to screen_bpp,
+    // then set selected_bpp to match.
+
+    for (i = 0; i < num_depths; ++i)
+    {
+        if (pixel_depths[i].bpp == screen_bpp)
+        {
+            selected_bpp = GetSupportedBPPIndex(pixel_depths[i].description);
+            return 1;
+        }
+    }
+
+    return 0;
+}
+
+static void SetSelectedBPP(void)
+{
+    const SDL_VideoInfo *info;
+
+    if (TrySetSelectedBPP())
+    {
+        return;
+    }
+
+    // screen_bpp does not match any supported pixel depth.  Query SDL
+    // to find out what it recommends using.
+
+    info = SDL_GetVideoInfo();
+
+    if (info != NULL && info->vfmt != NULL)
+    {
+        screen_bpp = info->vfmt->BitsPerPixel;
+    }
+
+    // Try again.
+
+    if (!TrySetSelectedBPP())
+    {
+        // Give up and just use the first in the list.
+
+        selected_bpp = 0;
+        screen_bpp = GetSelectedBPP();
+    }
+}
+
 static void ModeSelected(TXT_UNCAST_ARG(widget), TXT_UNCAST_ARG(mode))
 {
     TXT_CAST_ARG(screen_mode_t, mode);
@@ -177,6 +350,7 @@
 
 static void BuildFullscreenModesList(void)
 {
+    SDL_PixelFormat format;
     SDL_Rect **modes;
     screen_mode_t *m1;
     screen_mode_t *m2;
@@ -194,8 +368,11 @@
     // Get a list of fullscreen modes and find out how many
     // modes are in the list.
 
-    modes = SDL_ListModes(NULL, SDL_FULLSCREEN);
+    format.BitsPerPixel = screen_bpp;
+    format.BytesPerPixel = (screen_bpp + 7) / 8;
 
+    modes = SDL_ListModes(&format, SDL_FULLSCREEN);
+
     if (modes == NULL || modes == (SDL_Rect **) -1)
     {
         num_modes = 0;
@@ -317,10 +494,27 @@
 
     vidmode = FindBestMode(modes);
 
-    screen_width = modes[vidmode].w;
-    screen_height = modes[vidmode].h;
+    if (vidmode > 0)
+    {
+        screen_width = modes[vidmode].w;
+        screen_height = modes[vidmode].h;
+    }
 }
 
+// Callback invoked when the BPP selector is changed.
+
+static void UpdateBPP(TXT_UNCAST_ARG(widget), TXT_UNCAST_ARG(modes_table))
+{
+    TXT_CAST_ARG(txt_table_t, modes_table);
+
+    screen_bpp = GetSelectedBPP();
+
+    // Rebuild list of fullscreen modes.
+
+    BuildFullscreenModesList();
+    GenerateModesTable(NULL, modes_table);
+}
+
 #if defined(_WIN32) && !defined(_WIN32_WCE)
 
 static int win32_video_driver = 0;
@@ -372,6 +566,11 @@
 
     RestartTextscreen();
 
+    // Rebuild the list of supported pixel depths.
+
+    IdentifyPixelDepths();
+    SetSelectedBPP();
+
     // Rebuild the video modes list
 
     BuildFullscreenModesList();
@@ -385,9 +584,17 @@
 {
     txt_window_t *window;
     txt_table_t *modes_table;
+    txt_table_t *bpp_table;
     txt_checkbox_t *fs_checkbox;
     txt_checkbox_t *ar_checkbox;
+    txt_dropdown_list_t *bpp_selector;
 
+    // What color depths are supported?  Generate supported_bpps array
+    // and set selected_bpp to match the current value of screen_bpp.
+
+    IdentifyPixelDepths();
+    SetSelectedBPP();
+
     // First time in? Initialise selected_screen_{width,height}
 
     if (selected_screen_width == 0)
@@ -442,6 +649,7 @@
 
     TXT_AddWidgets(window,
                    TXT_NewSeparator("Screen mode"),
+                   bpp_table = TXT_NewTable(2),
                    modes_table,
                    TXT_NewSeparator("Misc."),
                    NULL);
@@ -458,6 +666,15 @@
                       TXT_NewCheckBox("Show ENDOOM screen", &show_endoom));
     }
 
+    TXT_AddWidgets(bpp_table,
+                   TXT_NewLabel("Color depth: "),
+                   bpp_selector = TXT_NewDropdownList(&selected_bpp,
+                                                      supported_bpps,
+                                                      num_supported_bpps),
+                   NULL);
+
+
+    TXT_SignalConnect(bpp_selector, "changed", UpdateBPP, modes_table);
     TXT_SignalConnect(fs_checkbox, "changed", GenerateModesTable, modes_table);
     TXT_SignalConnect(ar_checkbox, "changed", GenerateModesTable, modes_table);
 
@@ -486,5 +703,25 @@
         M_BindVariable("graphical_startup",        &graphical_startup);
     }
 
+    // Windows Vista or later?  Set screen color depth to
+    // 32 bits per pixel, as 8-bit palettized screen modes
+    // don't work properly in recent versions.
+
+#if defined(_WIN32) && !defined(_WIN32_WCE)
+    {
+        OSVERSIONINFOEX version_info;
+
+        ZeroMemory(&version_info, sizeof(OSVERSIONINFOEX));
+        version_info.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
+
+        GetVersionEx((OSVERSIONINFO *) &version_info);
+
+        if (version_info.dwPlatformId == VER_PLATFORM_WIN32_NT
+         && version_info.dwMajorVersion >= 6)
+        {
+            screen_bpp = 32;
+        }
+    }
+#endif
 }
 
--- a/src/setup/joystick.c
+++ b/src/setup/joystick.c
@@ -65,8 +65,8 @@
 static txt_button_t *joystick_button;
 
 static int *all_joystick_buttons[] = {
-        &joybstraferight, &joybstrafeleft, &joybfire, &joybspeed,
-        &joybuse, &joybstrafe, &joybjump
+    &joybstraferight, &joybstrafeleft, &joybfire, &joybspeed,
+    &joybuse, &joybstrafe, &joybprevweapon, &joybnextweapon, &joybjump
 };
 
 //
--- a/textscreen/txt_sdl.c
+++ b/textscreen/txt_sdl.c
@@ -119,6 +119,22 @@
 
 #endif
 
+static txt_font_t *FontForName(char *name)
+{
+    if (!strcmp(name, "small"))
+    {
+        return &small_font;
+    }
+    else if (!strcmp(name, "normal"))
+    {
+        return &main_font;
+    }
+    else
+    {
+        return NULL;
+    }
+}
+
 //
 // Select the font to use, based on screen resolution
 //
@@ -129,10 +145,23 @@
 static void ChooseFont(void)
 {
     SDL_Rect **modes;
+    char *env;
     int i;
 
-    font = &main_font;
+    // Allow normal selection to be overridden from an environment variable:
 
+    env = getenv("TEXTSCREEN_FONT");
+
+    if (env != NULL)
+    {
+        font = FontForName(env);
+
+        if (font != NULL)
+        {
+            return;
+        }
+    }
+
     // Check all modes
 
     modes = SDL_ListModes(NULL, SDL_FULLSCREEN);
@@ -139,6 +168,8 @@
 
     // If in doubt and we can't get a list, always prefer to
     // fall back to the normal font:
+
+    font = &main_font;
 
     if (modes == NULL || modes == (SDL_Rect **) -1 || *modes == NULL)
     {