shithub: choc

Download patch

ref: 1b53d785acee74cfb4a5d9e08ca27f25d07e9b14
parent: 031dbfb4ccd727ef660f72774cb767885fd9eae6
parent: 51b9d1fccd8c885d8349b307b3553bdf5a638027
author: Fabian Greffrath <fabian@greffrath.com>
date: Mon Sep 21 03:46:37 EDT 2015

Merge branch 'master' into sdl2-branch

--- a/Makefile.am
+++ b/Makefile.am
@@ -8,7 +8,9 @@
         msvc/doom.vcproj                \
         msvc/heretic.vcproj             \
         msvc/hexen.vcproj               \
+        msvc/strife.vcproj              \
         msvc/inttypes.h                 \
+        msvc/libopl.vcproj              \
         msvc/libpcsound.vcproj          \
         msvc/libtextscreen.vcproj       \
         msvc/README                     \
@@ -15,6 +17,8 @@
         msvc/server.vcproj              \
         msvc/setup.vcproj               \
         msvc/stdint.h                   \
+        msvc/win_opendir.c              \
+        msvc/win_opendir.h              \
         msvc/win32.rc
 
 CODEBLOCKS_FILES=                              \
@@ -24,6 +28,8 @@
         codeblocks/game-res.rc                 \
         codeblocks/heretic.cbp                 \
         codeblocks/hexen.cbp                   \
+        codeblocks/strife.cbp                  \
+        codeblocks/libopl.cbp                  \
         codeblocks/libpcsound.cbp              \
         codeblocks/libtextscreen.cbp           \
         codeblocks/README                      \
--- a/NEWS
+++ b/NEWS
@@ -1,5 +1,47 @@
+2.2.1 (2015-09-10):
 
-2.2.0 (2015-05-??):
+    Chocolate Doom has not seen a great deal of "stable" patch
+    releases in its history. While the development tree sees major new
+    features and changes, the purpose of this release, and hopefully
+    others to follow like it, is to repair some deficiencies that
+    existed in 2.2.0.
+
+    General:
+    * Preferences for the OS X launcher are now stored with a unique
+      name to not conflict with other applications. (thanks Xeriphas1994)
+    * Unix desktop entry files are now brought up to full desktop
+      entry specification compliance. (thanks chungy, Fabian)
+    * Unix AppData entries are now included, allowing software centers
+      to display detailed information about the engines. (thanks chungy)
+    * Partial XDG base directory specification compliance on Unix
+      systems now exist to search for IWAD paths.  One benefit is that
+      $HOME/.local/share/games/doom is now a valid location to store
+      and automatically find IWADs. (thanks chungy)
+
+    Build systems:
+    * The Microsoft Visual Studio build system was not fully
+      functional in 2.2.0 and has been fixed. (thanks Linguica)
+    * The autoconf build system checks for windres only for Windows
+      toolchains.  Some Linux distributions mistakingly include the
+      program in their native toolchains. (thanks Fabian)
+    * A compiler hint for packed structs has been added, which
+      otherwise broke the games when built under recent GCC releases
+      for Windows. (thanks Fabian)
+
+    Doom:
+    * The GOG.com releases of The Ultimate Doom, Doom II, and Final
+      Doom are now detected and supported on Windows. (thanks chungy)
+    * An integer overflow was used in spawn angle calculation,
+      undefined C behavior which broke with Clang optimization.
+      (thanks David Majnemer for insight)
+
+    Setup tool:
+    * The help URL for the level warp menu now points to the proper
+      wiki page, rather than the multiplayer page.
+    * The manifest has been updated for Windows 10 compatibility.
+      (thanks chungy)
+
+2.2.0 (2015-06-09):
 
      * The Hexen four level demo IWAD is now supported. Thanks to
        Fabian Greffrath for his careful investigation and emulation of
--- a/README.Strife
+++ b/README.Strife
@@ -28,31 +28,23 @@
 been employed to disassemble and decompile the executable, which was cross-
 referenced against the Linux DOOM and DOS Heretic sources and painstakingly
 combed over multiple times, instruction-by-instruction, to ensure that the
-resulting Chocolate-Doom-based executable is as close as possible to the
+resulting Chocolate Doom-based executable is as close as possible to the
 original.
 
 
 * Is it Legal? *
 
-Reverse engineering is a protected activity so long as the original code is
-not used directly in the product. Due to the vast amount of information lost
-through the process of compilation, and the need to refactor large portions
-of code in order to eliminate non-portable idioms or to adapt them properly to
-Chocolate Doom's framework, the resulting code behaves the same, but is not
-the *same* code.
+Chocolate Strife was originally reverse-engineered from the DOS Strife
+binaries. Although reverse engineering is legally a protected activity, this
+nonetheless left some open questions about its legal status.
 
-In addition, James Monroe and John Carmack have both stated that they have no
-objections to the project. Because they are the original authors of the code,
-and neither Rogue nor their publisher, Velocity, Inc., exist any longer as legal
-entities, this is as close to legal permission as can be obtained.
+In 2014, a new commercial release of Strife was published (Strife: Veteran
+Edition) based on the Chocolate Strife code, and developed by the authors of
+Chocolate Strife under commercial license. The release of Strife: Veteran
+Edition, along with its GPL-licensed source code, constitutes tacit approval
+for the legal status of Chocolate Strife by its current copyright holder.
 
-The transformed results of the disassembly have been combined with the
-raven-branch version of the Chocolate Doom source port by Simon 'fraggle'
-Howard, with his direct assistance, and have been released for the benefit of
-the community under the GNU General Public License v2.0. See the file "COPYING"
-for more details
 
-
 * Is it Perfect? *
 
 Almost, but not entirely! That's where you come in. Help us by reporting any
@@ -126,48 +118,4 @@
 
 Aside from Chocolate Doom, portions of the code are derived from the Eternity
 Engine, Copyright 2011 Team Eternity, as published under the GNU GPL.
-
-
-* New in v2.0 Release *
-
-+ No infinite loop if menus are up during a screen fade.
-
-+ Peasant death sound during intro plays at normal volume level.
-
-+ Torches no longer produce pipping sounds (special thanks to fraggle).
-
-+ Fade to black occurs at the start of slideshow sequences as in vanilla.
-
-+ Network game support, with up to 8 players.
-
-+ No more infinite horizon effect if screen is resized while looking up or
-  down.
-
-+ Corrected default internal Acolyte and Beggar dialogue strings.
-
-+ Proper save game directory behavior (same as other Choco ports).
-
-+ Vanilla behavior for crush-and-raise floor types (special thanks to Gez).
-
-+ Bug fix for all crushing floor types.
-
-+ Broken power coupling awards erroneous quest flag #32, as in vanilla.
-
-+ Complete support for emulation of Strife v1.31, including abililty to save
-  on multiple save slots.
-
-+ Telefrags insta-kill as in vanilla (player cannot heal 10000 damage even with
-  a full inventory of health items).
-
-+ Frags are displayed on the keys popup during deathmatch.
-
-+ Multiplayer chat features, including ability to set player names, now match
-  vanilla behavior.
-
-+ Support for -random parameter.
-
-+ Vanilla behavior when pressing pause on menus with scroll bars (special
-  thanks to fraggle and Alexandre Xavier).
-
-+ Merged into Chocolate Doom trunk for v2.0 release.
 
--- a/codeblocks/config.h
+++ b/codeblocks/config.h
@@ -9,13 +9,13 @@
 #define PACKAGE_NAME "Chocolate Doom"
 
 /* Define to the full name and version of this package. */
-#define PACKAGE_STRING "Chocolate Doom 2.1.0"
+#define PACKAGE_STRING "Chocolate Doom 2.2.1"
 
 /* Define to the one symbol short name of this package. */
 #define PACKAGE_TARNAME "chocolate-doom"
 
 /* Define to the version of this package. */
-#define PACKAGE_VERSION "2.1.0"
+#define PACKAGE_VERSION "2.2.1"
 
 /* Change this when you create your awesome forked version */
 #define PROGRAM_PREFIX "chocolate-"
@@ -24,7 +24,7 @@
 #define STDC_HEADERS 1
 
 /* Version number of package */
-#define VERSION "2.1.0"
+#define VERSION "2.2.1"
 
 /* Define to 1 if your processor stores words with the most significant byte
    first (like Motorola and SPARC, unlike Intel and VAX). */
--- a/codeblocks/game-res.rc
+++ b/codeblocks/game-res.rc
@@ -1,8 +1,8 @@
 1 ICON "../data/doom.ico"
 
 1 VERSIONINFO
-PRODUCTVERSION 2,1,0,0
-FILEVERSION 2,1,0,0
+PRODUCTVERSION 2,2,1,0
+FILEVERSION 2,2,1,0
 FILETYPE 1
 {
  BLOCK "StringFileInfo"
@@ -9,13 +9,13 @@
  {
   BLOCK "040904E4"
   {
-   VALUE "FileVersion", "2.1.0"
-   VALUE "FileDescription", "2.1.0"
+   VALUE "FileVersion", "2.2.1"
+   VALUE "FileDescription", "2.2.1"
    VALUE "InternalName", "Chocolate Doom"
    VALUE "CompanyName", "Chocolate Doom"
    VALUE "LegalCopyright", "GNU General Public License"
    VALUE "ProductName", "Chocolate Doom"
-   VALUE "ProductVersion", "2.1.0"
+   VALUE "ProductVersion", "2.2.1"
   }
  }
  BLOCK "VarFileInfo"
--- a/codeblocks/setup-res.rc
+++ b/codeblocks/setup-res.rc
@@ -1,8 +1,8 @@
 1 ICON "../data/setup.ico"
 
 1 VERSIONINFO
-PRODUCTVERSION 2,1,0,0
-FILEVERSION 2,1,0,0
+PRODUCTVERSION 2,2,1,0
+FILEVERSION 2,2,1,0
 FILETYPE 1
 {
  BLOCK "StringFileInfo"
@@ -9,13 +9,13 @@
  {
   BLOCK "040904E4"
   {
-   VALUE "FileVersion", "2.1.0"
+   VALUE "FileVersion", "2.2.1"
    VALUE "FileDescription", "Chocolate Doom Setup"
    VALUE "InternalName", "chocolate-setup"
    VALUE "CompanyName", "Chocolate Doom"
    VALUE "LegalCopyright", "GNU General Public License"
    VALUE "ProductName", "Chocolate Doom Setup"
-   VALUE "ProductVersion", "2.1.0"
+   VALUE "ProductVersion", "2.2.1"
   }
  }
  BLOCK "VarFileInfo"
--- a/configure.ac
+++ b/configure.ac
@@ -1,11 +1,12 @@
-AC_INIT(Chocolate Doom, 2.1.0, fraggle@gmail.com, chocolate-doom)
+AC_INIT(Chocolate Doom, 2.2.1, fraggle@gmail.com, chocolate-doom)
 
 PACKAGE_SHORTNAME=${PACKAGE_NAME% Doom}
 PACKAGE_SHORTDESC="Conservative source port"
-PACKAGE_COPYRIGHT="Copyright (C) 1993-2013"
+PACKAGE_COPYRIGHT="Copyright (C) 1993-2015"
 PACKAGE_LICENSE="GNU General Public License, version 2"
 PACKAGE_MAINTAINER="Simon Howard"
 PACKAGE_URL="http://www.chocolate-doom.org/"
+PACKAGE_ISSUES="https://github.com/chocolate-doom/chocolate-doom/issues"
 
 AC_CONFIG_AUX_DIR(autotools)
 
@@ -103,7 +104,15 @@
     AC_CHECK_LIB(amd64, amd64_iopl)
 ])
 
-AC_CHECK_TOOL(WINDRES, windres, )
+case $host in
+  *cygwin* | *mingw* )
+    AC_CHECK_TOOL(WINDRES, windres, )
+    ;;
+  *)
+    WINDRES=
+    ;;
+esac
+
 AC_CHECK_TOOL(STRIP, strip, )
 
 AM_CONDITIONAL(HAVE_WINDRES, test "$WINDRES" != "")
@@ -143,6 +152,7 @@
 AC_SUBST(PACKAGE_LICENSE)
 AC_SUBST(PACKAGE_MAINTAINER)
 AC_SUBST(PACKAGE_URL)
+AC_SUBST(PACKAGE_ISSUES)
 
 dnl Shut up the datarootdir warnings.
 AC_DEFUN([AC_DATAROOTDIR_CHECKED])
@@ -160,11 +170,14 @@
 rpm.spec
 data/Makefile
 src/Makefile
+src/doom.appdata.xml
 src/doom.desktop
 src/doom-screensaver.desktop
 src/doom/Makefile
+src/heretic.appdata.xml
 src/heretic.desktop
 src/heretic/Makefile
+src/hexen.appdata.xml
 src/hexen.desktop
 src/hexen/Makefile
 src/resource.rc
@@ -172,6 +185,7 @@
 src/setup/Makefile
 src/setup/setup.desktop
 src/setup/setup-manifest.xml
+src/strife.appdata.xml
 src/strife.desktop
 src/strife/Makefile
 textscreen/Makefile
--- a/man/Makefile.am
+++ b/man/Makefile.am
@@ -1,4 +1,5 @@
 MANPAGE_GEN_FILES = environ.man           \
+                    iwad_paths.man        \
                     doom.template         \
                     heretic.template      \
                     hexen.template        \
--- a/man/doom.template
+++ b/man/doom.template
@@ -10,6 +10,8 @@
 to behave as similar to the original DOS version of Doom as is possible.
 .br
 @content
+.SH IWAD SEARCH PATHS
+@include iwad_paths.man
 .SH ENVIRONMENT
 This section describes environment variables that control Chocolate Doom's
 behavior.
--- a/man/environ.man
+++ b/man/environ.man
@@ -1,9 +1,6 @@
 .TP
 \fBDOOMWADDIR\fR, \fBDOOMWADPATH\fR
-These environment variables provide paths to search for Doom .WAD files when
-looking for a game IWAD file or a PWAD file specified with the `\-file' option.
-\fBDOOMWADDIR\fR specifies a single path in which to look for WAD files,
-while \fBDOOMWWADPATH\fR specifies a colon-separated list of paths to search.
+See the section, \fBIWAD SEARCH PATHS\fR above.
 .TP
 \fBPCSOUND_DRIVER\fR
 When running in PC speaker sound effect mode, this environment variable
--- a/man/heretic.template
+++ b/man/heretic.template
@@ -11,6 +11,8 @@
 possible.
 .br
 @content
+.SH IWAD SEARCH PATHS
+@include iwad_paths.man
 .SH ENVIRONMENT
 This section describes environment variables that control Chocolate Heretic's
 behavior.
--- a/man/hexen.template
+++ b/man/hexen.template
@@ -11,6 +11,8 @@
 possible.
 .br
 @content
+.SH IWAD SEARCH PATHS
+@include iwad_paths.man
 .SH ENVIRONMENT
 This section describes environment variables that control Chocolate Hexen's
 behavior.
--- /dev/null
+++ b/man/iwad_paths.man
@@ -1,0 +1,48 @@
+To play, an IWAD file is needed. This is a large file containing all of the
+levels, graphics, sound effects, music and other material that make up the
+game. IWAD files are named according to the game; the standard names are:
+.TP
+\fBdoom.wad, doom1.wad, doom2.wad, tnt.wad, plutonia.wad\fR
+Doom, Doom II, Final Doom
+.TP
+\fBheretic.wad, heretic1.wad, hexen.wad, strife1.wad\fR
+Heretic, Hexen and Strife (commercial Doom engine games).
+.TP
+\fBhacx.wad, chex.wad\fR
+Hacx and Chex Quest - more obscure games based on the Doom engine.
+.TP
+\fBfreedm.wad, freedoom1.wad, freedoom2.wad\fR
+The Freedoom open content IWAD files.
+.LP
+The following directory paths are searched in order to find an IWAD:
+.TP
+\fBCurrent working directory\fR
+Any IWAD files found in the current working directory will be used in
+preference to IWADs found in any other directories.
+.TP
+\fBDOOMWADDIR\fR
+This environment variable can be set to contain a path to a single directory
+in which to look for IWAD files. This environment variable is supported by
+most Doom source ports.
+.TP
+\fBDOOMWADPATH\fR
+This environment variable, if set, can contain a colon-separated list of
+directories in which to look for IWAD files, or alternatively full paths to
+specific IWAD files.
+.TP
+\fB$HOME/.local/share/games/doom\fR
+Writeable directory in the user's home directory. The path can be overridden
+using the \fBXDG_DATA_HOME\fR environment variable (see the XDG Base Directory
+Specification).
+.TP
+\fB/usr/local/share/games/doom, /usr/share/games/doom\fR
+System-wide locations that can be accessed by all users. The path
+\fB/usr/share/games/doom\fR is a standard path that is supported by most
+Doom source ports. These paths can be overridden using the \fBXDG_DATA_DIRS\fR
+environment variable (see the XDG Base Directory Specification).
+.LP
+The above can be overridden on a one-time basis by using the \fB\-iwad\fR
+command line parameter to provide the path to an IWAD file to use. This
+parameter can also be used to specify the name of a particular IWAD to use
+from one of the above paths. For example, '\fB-iwad doom.wad\fR' will search
+the above paths for the file \fBdoom.wad\fR to use.
--- a/man/strife.template
+++ b/man/strife.template
@@ -13,6 +13,8 @@
 
 @content
 
+.SH IWAD SEARCH PATHS
+@include iwad_paths.man
 .SH ENVIRONMENT
 This section describes environment variables that control Chocolate Strife's
 behavior.
--- a/msvc/config.h
+++ b/msvc/config.h
@@ -11,19 +11,19 @@
 #define PACKAGE_NAME "Chocolate Doom"
 
 /* Define to the full name and version of this package. */
-#define PACKAGE_STRING "Chocolate Doom 2.1.0"
+#define PACKAGE_STRING "Chocolate Doom 2.2.1"
 
 /* Define to the one symbol short name of this package. */
 #define PACKAGE_TARNAME "chocolate-doom"
 
 /* Define to the version of this package. */
-#define PACKAGE_VERSION "2.1.0"
+#define PACKAGE_VERSION "2.2.1"
 
 /* Change this when you create your awesome forked version */
 #define PROGRAM_PREFIX "chocolate-"
 
 /* Version number of package */
-#define VERSION "2.1.0"
+#define VERSION "2.2.1"
 
 /* Define to 1 if your processor stores words with the most significant byte
    first (like Motorola and SPARC, unlike Intel and VAX). */
--- a/msvc/win32.rc
+++ b/msvc/win32.rc
@@ -25,8 +25,8 @@
 #endif
 
 1 VERSIONINFO
-PRODUCTVERSION 2,1,0,0
-FILEVERSION 2,1,0,0
+PRODUCTVERSION 2,2,1,0
+FILEVERSION 2,2,1,0
 FILETYPE 1
 BEGIN
 	BLOCK "StringFileInfo"
@@ -34,12 +34,12 @@
 		BLOCK "040904E4"
 		BEGIN
 			VALUE "FileVersion", "1.0.0"
-			VALUE "FileDescription", "Chocolate Doom 2.1.0"
+			VALUE "FileDescription", "Chocolate Doom 2.2.1"
 			VALUE "InternalName", "chocolate-doom"
 			VALUE "CompanyName", "fraggle@gmail.com"
 			VALUE "LegalCopyright", "GNU General Public License"
 			VALUE "ProductName", "Chocolate Doom"
-			VALUE "ProductVersion", "2.1.0"
+			VALUE "ProductVersion", "2.2.1"
 		END
 	END
 	BLOCK "VarFileInfo"
--- a/opl/dbopl.h
+++ b/opl/dbopl.h
@@ -195,6 +195,7 @@
 void Chip__Chip(Chip *self);
 void Chip__WriteReg(Chip *self, Bit32u reg, Bit8u val );
 void Chip__GenerateBlock2(Chip *self, Bitu total, Bit32s* output );
+void Chip__GenerateBlock3(Chip *self, Bitu total, Bit32s* output );
 
 // haleyjd 09/09/10: Not standard C.
 #ifdef _MSC_VER
--- a/opl/opl.c
+++ b/opl/opl.c
@@ -65,15 +65,16 @@
 // Initialize the specified driver and detect an OPL chip.  Returns
 // true if an OPL is detected.
 
-static int InitDriver(opl_driver_t *_driver, unsigned int port_base)
+static opl_init_result_t InitDriver(opl_driver_t *_driver,
+                                    unsigned int port_base)
 {
-    int result1, result2;
+    opl_init_result_t result1, result2;
 
     // Initialize the driver.
 
     if (!_driver->init_func(port_base))
     {
-        return 0;
+        return OPL_INIT_NONE;
     }
 
     // The driver was initialized okay, so we now have somewhere
@@ -86,12 +87,12 @@
 
     result1 = OPL_Detect();
     result2 = OPL_Detect();
-    if (!result1 || !result2)
+    if (result1 == OPL_INIT_NONE || result2 == OPL_INIT_NONE)
     {
         printf("OPL_Init: No OPL detected using '%s' driver.\n", _driver->name);
         _driver->shutdown_func();
         driver = NULL;
-        return 0;
+        return OPL_INIT_NONE;
     }
 
     init_stage_reg_writes = 0;
@@ -103,15 +104,15 @@
 
 // Find a driver automatically by trying each in the list.
 
-static int AutoSelectDriver(unsigned int port_base)
+static opl_init_result_t AutoSelectDriver(unsigned int port_base)
 {
     int i;
-    int result;
+    opl_init_result_t result;
 
     for (i=0; drivers[i] != NULL; ++i)
     {
         result = InitDriver(drivers[i], port_base);
-        if (result)
+        if (result != OPL_INIT_NONE)
         {
             return result;
         }
@@ -119,13 +120,13 @@
 
     printf("OPL_Init: Failed to find a working driver.\n");
 
-    return 0;
+    return OPL_INIT_NONE;
 }
 
-// Initialize the OPL library.  Returns true if initialized
-// successfully.
+// Initialize the OPL library. Return value indicates type of OPL chip
+// detected, if any.
 
-int OPL_Init(unsigned int port_base)
+opl_init_result_t OPL_Init(unsigned int port_base)
 {
     char *driver_name;
     int i;
@@ -150,7 +151,7 @@
                 {
                     printf("OPL_Init: Failed to initialize "
                            "driver: '%s'.\n", driver_name);
-                    return 0;
+                    return OPL_INIT_NONE;
                 }
             }
         }
@@ -157,7 +158,7 @@
 
         printf("OPL_Init: unknown driver: '%s'.\n", driver_name);
 
-        return 0;
+        return OPL_INIT_NONE;
     }
     else
     {
@@ -278,7 +279,7 @@
 
 // Detect the presence of an OPL chip
 
-int OPL_Detect(void)
+opl_init_result_t OPL_Detect(void)
 {
     int result1, result2;
     int i;
@@ -323,11 +324,17 @@
         result2 = OPL_ReadPort(OPL_REGISTER_PORT_OPL3);
         if (result1 == 0x00)
         {
-            return 2;
+            return OPL_INIT_OPL3;
         }
-        return 1;
+        else
+        {
+            return OPL_INIT_OPL2;
+        }
     }
-    return 0;
+    else
+    {
+        return OPL_INIT_NONE;
+    }
 }
 
 // Initialize registers on startup
--- a/opl/opl.h
+++ b/opl/opl.h
@@ -23,8 +23,17 @@
 
 typedef void (*opl_callback_t)(void *data);
 
+// Result from OPL_Init(), indicating what type of OPL chip was detected,
+// if any.
 typedef enum
 {
+    OPL_INIT_NONE,
+    OPL_INIT_OPL2,
+    OPL_INIT_OPL3,
+} opl_init_result_t;
+
+typedef enum
+{
     OPL_REGISTER_PORT = 0,
     OPL_DATA_PORT = 1,
     OPL_REGISTER_PORT_OPL3 = 2
@@ -66,7 +75,7 @@
 
 // Initialize the OPL subsystem.
 
-int OPL_Init(unsigned int port_base);
+opl_init_result_t OPL_Init(unsigned int port_base);
 
 // Shut down the OPL subsystem.
 
@@ -99,7 +108,7 @@
 // Perform a detection sequence to determine that an
 // OPL chip is present.
 
-int OPL_Detect(void);
+opl_init_result_t OPL_Detect(void);
 
 // Initialize all registers, performed on startup.
 
--- a/pkg/osx/Info.plist.in
+++ b/pkg/osx/Info.plist.in
@@ -2,6 +2,8 @@
 <!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
 <plist version="1.0">
 <dict>
+        <key>CFBundleIdentifier</key>
+        <string>org.chocolate-doom.launcher</string>
 	<key>CFBundleDevelopmentRegion</key>
 	<string>English</string>
 	<key>CFBundleDisplayName</key>
@@ -26,6 +28,10 @@
 	<string>NSApplication</string>
 	<key>NSMainNibFile</key>
 	<string>launcher</string>
+        <key>NSHumanReadableCopyright</key>
+        <string>Copyright (C) 1993-2015, id Software and Raven Software, Simon Howard, James Haley, Samuel Villarreal and other contributors.
+
+Licensed under the GNU GPL v2.</string>
 
         <!-- file associations: -->
 
--- a/src/.gitignore
+++ b/src/.gitignore
@@ -14,5 +14,6 @@
 chocolate-setup
 *.exe
 *.desktop
+*.appdata.xml
 tags
 TAGS
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -217,6 +217,25 @@
         doom-screensaver.desktop.in \
         manifest.xml
 
+appdatadir = $(prefix)/share/appdata
+appdata_DATA =                              \
+        @PROGRAM_PREFIX@doom.appdata.xml    \
+        @PROGRAM_PREFIX@heretic.appdata.xml \
+        @PROGRAM_PREFIX@hexen.appdata.xml   \
+        @PROGRAM_PREFIX@strife.appdata.xml
+
+@PROGRAM_PREFIX@doom.appdata.xml : doom.appdata.xml
+	cp doom.appdata.xml $@
+
+@PROGRAM_PREFIX@heretic.appdata.xml : heretic.appdata.xml
+	cp heretic.appdata.xml $@
+
+@PROGRAM_PREFIX@hexen.appdata.xml : hexen.appdata.xml
+	cp hexen.appdata.xml $@
+
+@PROGRAM_PREFIX@strife.appdata.xml : strife.appdata.xml
+	cp strife.appdata.xml $@
+
 appdir = $(prefix)/share/applications
 app_DATA =                                 \
            @PROGRAM_PREFIX@doom.desktop    \
--- a/src/d_iwad.c
+++ b/src/d_iwad.c
@@ -142,22 +142,53 @@
     },
 };
 
-// Value installed by the Collector's Edition when it is installed
+// Values installed by the GOG.com and Collector's Edition versions
 
-static registry_value_t collectors_edition_value =
+static registry_value_t root_path_keys[] =
 {
-    HKEY_LOCAL_MACHINE,
-    SOFTWARE_KEY "\\Activision\\DOOM Collector's Edition\\v1.0",
-    "INSTALLPATH",
+    // Doom Collector's Edition
+
+    {
+        HKEY_LOCAL_MACHINE,
+        SOFTWARE_KEY "\\Activision\\DOOM Collector's Edition\\v1.0",
+        "INSTALLPATH",
+    },
+
+    // Ultimate Doom
+
+    {
+        HKEY_LOCAL_MACHINE,
+        SOFTWARE_KEY "\\GOG.com\\Games\\1435827232",
+        "PATH",
+    },
+
+    // Doom II
+
+    {
+        HKEY_LOCAL_MACHINE,
+        SOFTWARE_KEY "\\GOG.com\\Games\\1435848814",
+        "PATH",
+    },
+
+    // Final Doom
+
+    {
+        HKEY_LOCAL_MACHINE,
+        SOFTWARE_KEY "\\GOG.com\\Games\\1435848742",
+        "PATH",
+    },
 };
 
 // Subdirectories of the above install path, where IWADs are installed.
 
-static char *collectors_edition_subdirs[] = 
+static char *root_path_subdirs[] =
 {
+    ".",
     "Doom2",
     "Final Doom",
     "Ultimate Doom",
+    "TNT",
+    "Plutonia",
 };
 
 // Location where Steam is installed
@@ -268,30 +299,34 @@
     }
 }
 
-// Check for Doom: Collector's Edition
+// Check for GOG.com and Doom: Collector's Edition
 
-static void CheckCollectorsEdition(void)
+static void CheckInstallRootPaths(void)
 {
-    char *install_path;
-    char *subpath;
     unsigned int i;
 
-    install_path = GetRegistryString(&collectors_edition_value);
-
-    if (install_path == NULL)
+    for (i=0; i<arrlen(root_path_keys); ++i)
     {
-        return;
-    }
+        char *install_path;
+        char *subpath;
+        unsigned int j;
 
-    for (i=0; i<arrlen(collectors_edition_subdirs); ++i)
-    {
-        subpath = M_StringJoin(install_path, DIR_SEPARATOR_S,
-                               collectors_edition_subdirs[i], NULL);
+        install_path = GetRegistryString(&root_path_keys[i]);
 
-        AddIWADDir(subpath);
-    }
+        if (install_path == NULL)
+        {
+            continue;
+        }
 
-    free(install_path);
+        for (j=0; j<arrlen(root_path_subdirs); ++j)
+        {
+            subpath = M_StringJoin(install_path, DIR_SEPARATOR_S,
+                                   root_path_subdirs[j], NULL);
+            AddIWADDir(subpath);
+        }
+
+        free(install_path);
+    }
 }
 
 
@@ -509,47 +544,29 @@
     return mission;
 }
 
-//
-// Add directories from the list in the DOOMWADPATH environment variable.
-//
-
-static void AddDoomWadPath(void)
+// Add IWAD directories parsed from splitting a path string containing
+// paths separated by PATH_SEPARATOR. 'suffix' is a string to concatenate
+// to the end of the paths before adding them.
+static void AddIWADPath(char *path, char *suffix)
 {
-    char *doomwadpath;
-    char *p;
+    char *left, *p;
 
-    // Check the DOOMWADPATH environment variable.
+    path = M_StringDuplicate(path);
 
-    doomwadpath = getenv("DOOMWADPATH");
-
-    if (doomwadpath == NULL)
-    {
-        return;
-    }
-
-    doomwadpath = M_StringDuplicate(doomwadpath);
-
-    // Add the initial directory
-
-    AddIWADDir(doomwadpath);
-
     // Split into individual dirs within the list.
+    left = path;
 
-    p = doomwadpath;
-
     for (;;)
     {
-        p = strchr(p, PATH_SEPARATOR);
-
+        p = strchr(left, PATH_SEPARATOR);
         if (p != NULL)
         {
-            // Break at the separator and store the right hand side
+            // Break at the separator and use the left hand side
             // as another iwad dir
-  
             *p = '\0';
-            p += 1;
 
-            AddIWADDir(p);
+            AddIWADDir(M_StringJoin(left, suffix, NULL));
+            left = p + 1;
         }
         else
         {
@@ -556,9 +573,68 @@
             break;
         }
     }
+
+    AddIWADDir(M_StringJoin(left, suffix, NULL));
+
+    free(path);
 }
 
+// Add standard directories where IWADs are located on Unix systems.
+// To respect the freedesktop.org specification we support overriding
+// using standard environment variables. See the XDG Base Directory
+// Specification:
+// <http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html>
+static void AddXdgDirs(void)
+{
+    char *env, *tmp_env;
 
+    // Quote:
+    // > $XDG_DATA_HOME defines the base directory relative to which
+    // > user specific data files should be stored. If $XDG_DATA_HOME
+    // > is either not set or empty, a default equal to
+    // > $HOME/.local/share should be used.
+    env = getenv("XDG_DATA_HOME");
+    tmp_env = NULL;
+
+    if (env == NULL)
+    {
+        char *homedir = getenv("HOME");
+        if (homedir == NULL)
+        {
+            homedir = "/";
+        }
+
+        tmp_env = M_StringJoin(homedir, "/.local/share", NULL);
+        env = tmp_env;
+    }
+
+    // We support $XDG_DATA_HOME/games/doom (which will usually be
+    // ~/.local/share/games/doom) as a user-writeable extension to
+    // the usual /usr/share/games/doom location.
+    AddIWADDir(M_StringJoin(env, "/games/doom", NULL));
+    free(tmp_env);
+
+    // Quote:
+    // > $XDG_DATA_DIRS defines the preference-ordered set of base
+    // > directories to search for data files in addition to the
+    // > $XDG_DATA_HOME base directory. The directories in $XDG_DATA_DIRS
+    // > should be seperated with a colon ':'.
+    // >
+    // > If $XDG_DATA_DIRS is either not set or empty, a value equal to
+    // > /usr/local/share/:/usr/share/ should be used.
+    env = getenv("XDG_DATA_DIRS");
+    if (env == NULL)
+    {
+        // (Trailing / omitted from paths, as it is added below)
+        env = "/usr/local/share:/usr/share";
+    }
+
+    // The "standard" location for IWADs on Unix that is supported by most
+    // source ports is /usr/share/games/doom - we support this through the
+    // XDG_DATA_DIRS mechanism, through which it can be overridden.
+    AddIWADPath(env, "/games/doom");
+}
+
 //
 // Build a list of IWAD files
 //
@@ -565,7 +641,7 @@
 
 static void BuildIWADDirList(void)
 {
-    char *doomwaddir;
+    char *env;
 
     if (iwad_dirs_built)
     {
@@ -573,28 +649,28 @@
     }
 
     // Look in the current directory.  Doom always does this.
-
     AddIWADDir(".");
 
     // Add DOOMWADDIR if it is in the environment
+    env = getenv("DOOMWADDIR");
+    if (env != NULL)
+    {
+        AddIWADDir(env);
+    }
 
-    doomwaddir = getenv("DOOMWADDIR");
-
-    if (doomwaddir != NULL)
+    // Add dirs from DOOMWADPATH:
+    env = getenv("DOOMWADPATH");
+    if (env != NULL)
     {
-        AddIWADDir(doomwaddir);
-    }        
+        AddIWADPath(env, "");
+    }
 
-    // Add dirs from DOOMWADPATH
-
-    AddDoomWadPath();
-
 #ifdef _WIN32
 
     // Search the registry and find where IWADs have been installed.
 
     CheckUninstallStrings();
-    CheckCollectorsEdition();
+    CheckInstallRootPaths();
     CheckSteamEdition();
     CheckDOSDefaults();
 
@@ -603,12 +679,7 @@
     CheckSteamGUSPatches();
 
 #else
-
-    // Standard places where IWAD files are installed under Unix.
-
-    AddIWADDir("/usr/share/games/doom");
-    AddIWADDir("/usr/local/share/games/doom");
-
+    AddXdgDirs();
 #endif
 
     // Don't run this function again.
--- a/src/d_mode.c
+++ b/src/d_mode.c
@@ -120,6 +120,9 @@
     GameMission_t mission;
     GameVersion_t version;
 } valid_versions[] = {
+    { doom,     exe_doom_1_666 },
+    { doom,     exe_doom_1_7 },
+    { doom,     exe_doom_1_8 },
     { doom,     exe_doom_1_9 },
     { doom,     exe_hacx },
     { doom,     exe_ultimate },
--- a/src/deh_io.c
+++ b/src/deh_io.c
@@ -120,7 +120,7 @@
     context->input_buffer_pos = 0;
 
     context->filename = malloc(9);
-    M_StringCopy(context->filename, lumpinfo[lumpnum].name, 9);
+    M_StringCopy(context->filename, lumpinfo[lumpnum]->name, 9);
 
     return context;
 }
--- /dev/null
+++ b/src/doom.appdata.xml.in
@@ -1,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<component type="desktop">
+  <id>@PROGRAM_PREFIX@doom.desktop</id>
+  <metadata_license>CC0-1.0</metadata_license>
+  <project_license>GPL-2.0+</project_license>
+  <developer_name>@PACKAGE_MAINTAINER@</developer_name>
+  <url type="homepage">@PACKAGE_URL@</url>
+  <url type="bugtracker">@PACKAGE_ISSUES@</url>
+  <description>
+    <p>
+      @PACKAGE_SHORTNAME@ Doom is a conservative,
+      historically-accurate Doom source port, which is compatible with
+      the thousands of mods and levels that were made before the Doom
+      source code was released.  Unlike other source ports, the goal
+      is to preserve the original look, feel, limitations, and bugs of
+      the original DOS executable.
+    </p>
+    <p>
+      Full support for single- and multi-player games is provided, for
+      all of the original Doom games, Chex Quest, and Hacx.  Unlike
+      the original executable, network play is implemented on the IP
+      network stack, allowing it to function on modern LANs and the
+      Internet.
+    </p>
+  </description>
+  <screenshots>
+    <screenshot type="default">
+      <image>http://www.chocolate-doom.org/wiki/images/9/97/GNOME_FreeDM_DEMO4.png</image>
+      <caption>FreeDM, DM05: Metal</caption>
+    </screenshot>
+    <screenshot>
+      <image>http://www.chocolate-doom.org/wiki/images/a/a6/GNOME_Doom_II_DEMO2.png</image>
+      <caption>Doom II, Level 5: The Waste Tunnels</caption>
+    </screenshot>
+    <screenshot>
+      <image>http://www.chocolate-doom.org/wiki/images/4/41/GNOME_Doomsday_of_UAC.png</image>
+      <caption>Doomsday of UAC (uac_dead.wad)</caption>
+    </screenshot>
+    <screenshot>
+      <image>http://www.chocolate-doom.org/wiki/images/2/2a/GNOME_Freedoom_DTWID_DEMO3.png</image>
+      <caption>Doom the Way id Did, on Freedoom. Level 3-2: City of Corpses</caption>
+    </screenshot>
+  </screenshots>
+</component>
--- a/src/doom/d_main.c
+++ b/src/doom/d_main.c
@@ -607,6 +607,10 @@
     "                         "
     "DOOM 2: Hell on Earth v%i.%i"
     "                           ",
+    // doom2.wad v1.666
+    "                         "
+    "DOOM 2: Hell on Earth v%i.%i66"
+    "                          ",
     // doom1.wad
     "                            "
     "DOOM Shareware Startup v%i.%i"
@@ -619,6 +623,10 @@
     "                          "
     "DOOM System Startup v%i.%i"
     "                          ",
+    // Doom v1.666
+    "                          "
+    "DOOM System Startup v%i.%i66"
+    "                          "
     // doom.wad (Ultimate DOOM)
     "                         "
     "The Ultimate DOOM Startup v%i.%i"
@@ -731,12 +739,12 @@
 
         for (i=0; i<numlumps; ++i)
         {
-            if (!strncasecmp(lumpinfo[i].name, "MAP01", 8))
+            if (!strncasecmp(lumpinfo[i]->name, "MAP01", 8))
             {
                 gamemission = doom2;
                 break;
             } 
-            else if (!strncasecmp(lumpinfo[i].name, "E1M1", 8))
+            else if (!strncasecmp(lumpinfo[i]->name, "E1M1", 8))
             {
                 gamemission = doom;
                 break;
@@ -945,8 +953,12 @@
 
 static void InitGameVersion(void)
 {
+    byte *demolump;
+    char demolumpname[6];
+    int demoversion;
     int p;
     int i;
+    boolean status;
 
     //! 
     // @arg <version>
@@ -998,13 +1010,46 @@
 
             gameversion = exe_hacx;
         }
-        else if (gamemode == shareware || gamemode == registered)
+        else if (gamemode == shareware || gamemode == registered
+              || (gamemode == commercial && gamemission == doom2))
         {
             // original
-
             gameversion = exe_doom_1_9;
 
-            // TODO: Detect IWADs earlier than Doom v1.9.
+            // Detect version from demo lump
+            for (i = 1; i <= 3; ++i)
+            {
+                M_snprintf(demolumpname, 6, "demo%i", i);
+                if (W_CheckNumForName(demolumpname) > 0)
+                {
+                    demolump = W_CacheLumpName(demolumpname, PU_STATIC);
+                    demoversion = demolump[0];
+                    W_ReleaseLumpName(demolumpname);
+                    status = true;
+                    switch (demoversion)
+                    {
+                        case 106:
+                            gameversion = exe_doom_1_666;
+                            break;
+                        case 107:
+                            gameversion = exe_doom_1_7;
+                            break;
+                        case 108:
+                            gameversion = exe_doom_1_8;
+                            break;
+                        case 109:
+                            gameversion = exe_doom_1_9;
+                            break;
+                        default:
+                            status = false;
+                            break;
+                    }
+                    if (status)
+                    {
+                        break;
+                    }
+                }
+            }
         }
         else if (gamemode == retail)
         {
@@ -1012,20 +1057,13 @@
         }
         else if (gamemode == commercial)
         {
-            if (gamemission == doom2)
-            {
-                gameversion = exe_doom_1_9;
-            }
-            else
-            {
-                // Final Doom: tnt or plutonia
-                // Defaults to emulating the first Final Doom executable,
-                // which has the crash in the demo loop; however, having
-                // this as the default should mean that it plays back
-                // most demos correctly.
+            // Final Doom: tnt or plutonia
+            // Defaults to emulating the first Final Doom executable,
+            // which has the crash in the demo loop; however, having
+            // this as the default should mean that it plays back
+            // most demos correctly.
 
-                gameversion = exe_final;
-            }
+            gameversion = exe_final;
         }
     }
     
@@ -1493,7 +1531,7 @@
 
         if (D_AddFile(file))
         {
-            M_StringCopy(demolumpname, lumpinfo[numlumps - 1].name,
+            M_StringCopy(demolumpname, lumpinfo[numlumps - 1]->name,
                          sizeof(demolumpname));
         }
         else
@@ -1528,7 +1566,7 @@
 
         for (i = numiwadlumps; i < numlumps; ++i)
         {
-            if (!strncmp(lumpinfo[i].name, "DEHACKED", 8))
+            if (!strncmp(lumpinfo[i]->name, "DEHACKED", 8))
             {
                 DEH_LoadLump(i, false, false);
                 loaded++;
--- a/src/doom/g_game.c
+++ b/src/doom/g_game.c
@@ -614,7 +614,8 @@
 
     // The "Sky never changes in Doom II" bug was fixed in
     // the id Anthology version of doom2.exe for Final Doom.
-    if ((gamemode == commercial) && (gameversion == exe_final2))
+    if ((gamemode == commercial)
+     && (gameversion == exe_final2 || gameversion == exe_chex))
     {
         char *skytexturename;
 
@@ -1168,26 +1169,26 @@
         fixed_t xa, ya;
         signed int an;
 
-        an = (ANG45 * ((signed int) mthing->angle / 45));
-        // Right-shifting a negative signed integer is implementation-defined,
-        // so divide instead.
-        an /= 1 << ANGLETOFINESHIFT;
+        // This calculation overflows in Vanilla Doom, but here we deliberately
+        // avoid integer overflow as it is undefined behavior, so the value of
+        // 'an' will always be positive.
+        an = (ANG45 >> ANGLETOFINESHIFT) * ((signed int) mthing->angle / 45);
 
         switch (an)
         {
-            case -4096:
+            case 4096:  // -4096:
                 xa = finetangent[2048];    // finecosine[-4096]
                 ya = finetangent[0];       // finesine[-4096]
                 break;
-            case -3072:
+            case 5120:  // -3072:
                 xa = finetangent[3072];    // finecosine[-3072]
                 ya = finetangent[1024];    // finesine[-3072]
                 break;
-            case -2048:
+            case 6144:  // -2048:
                 xa = finesine[0];          // finecosine[-2048]
                 ya = finetangent[2048];    // finesine[-2048]
                 break;
-            case -1024:
+            case 7168:  // -1024:
                 xa = finesine[1024];       // finecosine[-1024]
                 ya = finetangent[3072];    // finesine[-1024]
                 break;
@@ -1195,7 +1196,6 @@
             case 1024:
             case 2048:
             case 3072:
-            case 4096:
                 xa = finecosine[an];
                 ya = finesine[an];
                 break;
--- a/src/doom/hu_stuff.c
+++ b/src/doom/hu_stuff.c
@@ -48,7 +48,7 @@
 #define HU_TITLE2	(mapnames_commercial[gamemap-1])
 #define HU_TITLEP	(mapnames_commercial[gamemap-1 + 32])
 #define HU_TITLET	(mapnames_commercial[gamemap-1 + 64])
-#define HU_TITLE_CHEX   (mapnames[gamemap - 1])
+#define HU_TITLE_CHEX   (mapnames_chex[(gameepisode-1)*9+gamemap-1])
 #define HU_TITLEHEIGHT	1
 #define HU_TITLEX	0
 #define HU_TITLEY	(167 - SHORT(hu_font[0]->height))
@@ -163,6 +163,60 @@
     "NEWLEVEL"
 };
 
+char*   mapnames_chex[] =   // Chex Quest names.
+{
+
+    HUSTR_E1M1,
+    HUSTR_E1M2,
+    HUSTR_E1M3,
+    HUSTR_E1M4,
+    HUSTR_E1M5,
+    HUSTR_E1M5,
+    HUSTR_E1M5,
+    HUSTR_E1M5,
+    HUSTR_E1M5,
+
+    HUSTR_E1M5,
+    HUSTR_E1M5,
+    HUSTR_E1M5,
+    HUSTR_E1M5,
+    HUSTR_E1M5,
+    HUSTR_E1M5,
+    HUSTR_E1M5,
+    HUSTR_E1M5,
+    HUSTR_E1M5,
+
+    HUSTR_E1M5,
+    HUSTR_E1M5,
+    HUSTR_E1M5,
+    HUSTR_E1M5,
+    HUSTR_E1M5,
+    HUSTR_E1M5,
+    HUSTR_E1M5,
+    HUSTR_E1M5,
+    HUSTR_E1M5,
+
+    HUSTR_E1M5,
+    HUSTR_E1M5,
+    HUSTR_E1M5,
+    HUSTR_E1M5,
+    HUSTR_E1M5,
+    HUSTR_E1M5,
+    HUSTR_E1M5,
+    HUSTR_E1M5,
+    HUSTR_E1M5,
+
+    "NEWLEVEL",
+    "NEWLEVEL",
+    "NEWLEVEL",
+    "NEWLEVEL",
+    "NEWLEVEL",
+    "NEWLEVEL",
+    "NEWLEVEL",
+    "NEWLEVEL",
+    "NEWLEVEL"
+};
+
 // List of names for levels in commercial IWADs
 // (doom2.wad, plutonia.wad, tnt.wad).  These are stored in a
 // single large array; WADs like pl2.wad have a MAP33, and rely on
@@ -351,10 +405,7 @@
          break;
     }
 
-    // Chex.exe always uses the episode 1 level title
-    // eg. E2M1 gives the title for E1M1
-
-    if (gameversion == exe_chex)
+    if (logical_gamemission == doom && gameversion == exe_chex)
     {
         s = HU_TITLE_CHEX;
     }
--- a/src/doom/m_menu.c
+++ b/src/doom/m_menu.c
@@ -751,6 +751,9 @@
 
     switch (gameversion)
     {
+        case exe_doom_1_666:
+        case exe_doom_1_7:
+        case exe_doom_1_8:
         case exe_doom_1_9:
         case exe_hacx:
 
@@ -1074,7 +1077,7 @@
     // Doom 1.9 had two menus when playing Doom 1
     // All others had only one
 
-    if (gameversion == exe_doom_1_9 && gamemode != commercial)
+    if (gameversion <= exe_doom_1_9 && gamemode != commercial)
     {
         choice = 0;
         M_SetupNextMenu(&ReadDef2);
--- a/src/doom/p_doors.c
+++ b/src/doom/p_doors.c
@@ -392,6 +392,12 @@
     }
 	
     // if the sector has an active thinker, use it
+
+    if (line->sidenum[side^1] == -1)
+    {
+        I_Error("EV_VerticalDoor: DR special type on 1-sided linedef");
+    }
+
     sec = sides[ line->sidenum[side^1]] .sector;
 
     if (sec->specialdata)
--- a/src/doom/r_data.c
+++ b/src/doom/r_data.c
@@ -832,7 +832,7 @@
 	if (flatpresent[i])
 	{
 	    lump = firstflat + i;
-	    flatmemory += lumpinfo[lump].size;
+	    flatmemory += lumpinfo[lump]->size;
 	    W_CacheLumpNum(lump, PU_CACHE);
 	}
     }
@@ -869,7 +869,7 @@
 	for (j=0 ; j<texture->patchcount ; j++)
 	{
 	    lump = texture->patches[j].patch;
-	    texturememory += lumpinfo[lump].size;
+	    texturememory += lumpinfo[lump]->size;
 	    W_CacheLumpNum(lump , PU_CACHE);
 	}
     }
@@ -898,7 +898,7 @@
 	    for (k=0 ; k<8 ; k++)
 	    {
 		lump = firstspritelump + sf->lump[k];
-		spritememory += lumpinfo[lump].size;
+		spritememory += lumpinfo[lump]->size;
 		W_CacheLumpNum(lump , PU_CACHE);
 	    }
 	}
--- a/src/doom/r_things.c
+++ b/src/doom/r_things.c
@@ -208,22 +208,22 @@
 	//  filling in the frames for whatever is found
 	for (l=start+1 ; l<end ; l++)
 	{
-	    if (!strncasecmp(lumpinfo[l].name, spritename, 4))
+	    if (!strncasecmp(lumpinfo[l]->name, spritename, 4))
 	    {
-		frame = lumpinfo[l].name[4] - 'A';
-		rotation = lumpinfo[l].name[5] - '0';
+		frame = lumpinfo[l]->name[4] - 'A';
+		rotation = lumpinfo[l]->name[5] - '0';
 
 		if (modifiedgame)
-		    patched = W_GetNumForName (lumpinfo[l].name);
+		    patched = W_GetNumForName (lumpinfo[l]->name);
 		else
 		    patched = l;
 
 		R_InstallSpriteLump (patched, frame, rotation, false);
 
-		if (lumpinfo[l].name[6])
+		if (lumpinfo[l]->name[6])
 		{
-		    frame = lumpinfo[l].name[6] - 'A';
-		    rotation = lumpinfo[l].name[7] - '0';
+		    frame = lumpinfo[l]->name[6] - 'A';
+		    rotation = lumpinfo[l]->name[7] - '0';
 		    R_InstallSpriteLump (l, frame, rotation, true);
 		}
 	    }
--- a/src/doom/s_sound.c
+++ b/src/doom/s_sound.c
@@ -46,7 +46,7 @@
 // Distance tp origin when sounds should be maxed out.
 // This should relate to movement clipping resolution
 // (see BLOCKMAP handling).
-// In the source code release: (160*FRACUNIT).  Changed back to the 
+// In the source code release: (160*FRACUNIT).  Changed back to the
 // Vanilla value of 200 (why was this changed?)
 
 #define S_CLOSE_DIST (200 * FRACUNIT)
@@ -59,7 +59,6 @@
 
 #define S_STEREO_SWING (96 * FRACUNIT)
 
-#define NORM_PITCH 128
 #define NORM_PRIORITY 64
 #define NORM_SEP 128
 
@@ -73,7 +72,9 @@
 
     // handle of the sound being played
     int handle;
-    
+
+    int pitch;
+
 } channel_t;
 
 // The set of channels available
@@ -85,7 +86,7 @@
 
 int sfxVolume = 8;
 
-// Maximum volume of music. 
+// Maximum volume of music.
 
 int musicVolume = 8;
 
@@ -95,7 +96,7 @@
 
 // Whether songs are mus_paused
 
-static boolean mus_paused;        
+static boolean mus_paused;
 
 // Music currently being played
 
@@ -112,10 +113,25 @@
 //
 
 void S_Init(int sfxVolume, int musicVolume)
-{  
+{
     int i;
 
-    I_SetOPLDriverVer(opl_v_new);
+    if (gameversion == exe_doom_1_666)
+    {
+        if (logical_gamemission == doom)
+        {
+            I_SetOPLDriverVer(opl_doom1_1_666);
+        }
+        else
+        {
+            I_SetOPLDriverVer(opl_doom2_1_666);
+        }
+    }
+    else
+    {
+        I_SetOPLDriverVer(opl_doom_1_9);
+    }
+
     I_PrecacheSounds(S_sfx, NUMSFX);
 
     S_SetSfxVolume(sfxVolume);
@@ -141,6 +157,12 @@
         S_sfx[i].lumpnum = S_sfx[i].usefulness = -1;
     }
 
+    // Doom defaults to pitch-shifting off.
+    if (snd_pitchshift == -1)
+    {
+        snd_pitchshift = 0;
+    }
+
     I_AtExit(S_Shutdown, true);
 }
 
@@ -237,10 +259,10 @@
         {
             mnum = spmus[gamemap-1];
         }
-    }        
+    }
 
     S_ChangeMusic(mnum, true);
-}        
+}
 
 void S_StopSound(mobj_t *origin)
 {
@@ -265,7 +287,7 @@
 {
     // channel number to use
     int                cnum;
-    
+
     channel_t*        c;
 
     // Find an open channel
@@ -296,7 +318,7 @@
 
         if (cnum == snd_channels)
         {
-            // FUCK!  No lower priority.  Sorry, Charlie.    
+            // FUCK!  No lower priority.  Sorry, Charlie.
             return -1;
         }
         else
@@ -337,12 +359,12 @@
 
     // From _GG1_ p.428. Appox. eucledian distance fast.
     approx_dist = adx + ady - ((adx < ady ? adx : ady)>>1);
-    
+
     if (gamemap != 8 && approx_dist > S_CLIPPING_DIST)
     {
         return 0;
     }
-    
+
     // angle of source to listener
     angle = R_PointToAngle2(listener->x,
                             listener->y,
@@ -384,12 +406,27 @@
         // distance effect
         *vol = (snd_SfxVolume
                 * ((S_CLIPPING_DIST - approx_dist)>>FRACBITS))
-            / S_ATTENUATOR; 
+            / S_ATTENUATOR;
     }
-    
+
     return (*vol > 0);
 }
 
+// clamp supplied integer to the range 0 <= x <= 255.
+
+static int Clamp(int x)
+{
+    if (x < 0)
+    {
+        return 0;
+    }
+    else if (x > 255)
+    {
+        return 255;
+    }
+    return x;
+}
+
 void S_StartSound(void *origin_p, int sfx_id)
 {
     sfxinfo_t *sfx;
@@ -396,6 +433,7 @@
     mobj_t *origin;
     int rc;
     int sep;
+    int pitch;
     int cnum;
     int volume;
 
@@ -411,9 +449,11 @@
     sfx = &S_sfx[sfx_id];
 
     // Initialize sound parameters
+    pitch = NORM_PITCH;
     if (sfx->link)
     {
         volume += sfx->volume;
+        pitch = sfx->pitch;
 
         if (volume < 1)
         {
@@ -438,7 +478,7 @@
 
         if (origin->x == players[consoleplayer].mo->x
          && origin->y == players[consoleplayer].mo->y)
-        {        
+        {
             sep = NORM_SEP;
         }
 
@@ -446,12 +486,23 @@
         {
             return;
         }
-    }        
+    }
     else
     {
         sep = NORM_SEP;
     }
 
+    // hacks to vary the sfx pitches
+    if (sfx_id >= sfx_sawup && sfx_id <= sfx_sawhit)
+    {
+        pitch += 8 - (M_Random()&15);
+    }
+    else if (sfx_id != sfx_itemup && sfx_id != sfx_tink)
+    {
+        pitch += 16 - (M_Random()&31);
+    }
+    pitch = Clamp(pitch);
+
     // kill old sound
     S_StopSound(origin);
 
@@ -474,8 +525,9 @@
         sfx->lumpnum = I_GetSfxLumpNum(sfx);
     }
 
-    channels[cnum].handle = I_StartSound(sfx, cnum, volume, sep);
-}        
+    channels[cnum].pitch = pitch;
+    channels[cnum].handle = I_StartSound(sfx, cnum, volume, sep, channels[cnum].pitch);
+}
 
 //
 // Stop and resume music, during game PAUSE.
@@ -549,7 +601,7 @@
                                                   c->origin,
                                                   &volume,
                                                   &sep);
-                    
+
                     if (!audible)
                     {
                         S_StopChannel(cnum);
@@ -576,7 +628,7 @@
     {
         I_Error("Attempt to set music volume at %d",
                 volume);
-    }    
+    }
 
     I_SetMusicVolume(volume);
 }
--- a/src/doom/st_stuff.c
+++ b/src/doom/st_stuff.c
@@ -528,7 +528,8 @@
 	{
 	  musnum = mus_runnin + (buf[0]-'0')*10 + buf[1]-'0' - 1;
 	  
-	  if (((buf[0]-'0')*10 + buf[1]-'0') > 35)
+	  if (((buf[0]-'0')*10 + buf[1]-'0') > 35
+       && gameversion >= exe_doom_1_8)
 	    plyr->message = DEH_String(STSTR_NOMUS);
 	  else
 	    S_ChangeMusic(musnum, 1);
@@ -617,13 +618,20 @@
       {
 	epsd = buf[0] - '0';
 	map = buf[1] - '0';
-      }
 
-      // Chex.exe always warps to episode 1.
+        // Chex.exe always warps to episode 1.
 
-      if (gameversion == exe_chex)
-      {
-        epsd = 1;
+        if (gameversion == exe_chex)
+        {
+            if (epsd > 1)
+            {
+                epsd = 1;
+            }
+            if (map > 5)
+            {
+                map = 5;
+            }
+        }
       }
 
       // Catch invalid maps.
--- a/src/doomtype.h
+++ b/src/doomtype.h
@@ -51,8 +51,14 @@
 //
 
 #ifdef __GNUC__
+
+#ifdef __clang__
 #define PACKEDATTR __attribute__((packed))
 #else
+#define PACKEDATTR __attribute__((packed,gcc_struct))
+#endif
+
+#else
 #define PACKEDATTR
 #endif
 
@@ -66,7 +72,7 @@
 
 #include <inttypes.h>
 
-#ifdef __cplusplus
+#if defined(__cplusplus) || defined(__bool_true_false_are_defined)
 
 // Use builtin bool type with C++.
 
--- a/src/gusconf.c
+++ b/src/gusconf.c
@@ -192,6 +192,7 @@
     data = Z_Malloc(len + 1, PU_STATIC, NULL);
     W_ReadLump(lumpnum, data);
 
+    data[len] = '\0';
     return data;
 }
 
--- /dev/null
+++ b/src/heretic.appdata.xml.in
@@ -1,0 +1,44 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<component type="desktop">
+  <id>@PROGRAM_PREFIX@heretic.desktop</id>
+  <metadata_license>CC0-1.0</metadata_license>
+  <project_license>GPL-2.0+</project_license>
+  <developer_name>@PACKAGE_MAINTAINER@</developer_name>
+  <url type="homepage">@PACKAGE_URL@</url>
+  <url type="bugtracker">@PACKAGE_ISSUES@</url>
+  <description>
+    <p>
+      @PACKAGE_SHORTNAME@ Heretic is a conservative,
+      historically-accurate Heretic source port, which is compatible
+      with mods and levels that were made before the Heretic source
+      code was released.  Unlike other source ports, the goal is to
+      preserve the original look, feel, limitations, and bugs of the
+      original DOS executable.
+    </p>
+    <p>
+      Full support for single- and multi-player games is provided.
+      Unlike the original executable, network play is implemented on
+      the IP network stack, allowing it to function on modern LANs and
+      the Internet.
+    </p>
+  </description>
+  <screenshots>
+    <screenshot type="default">
+      <image>http://www.chocolate-doom.org/wiki/images/9/93/GNOME_Heretic_E5M4.png</image>
+      <caption>Level E5M4: Courtyard</caption>
+    </screenshot>
+    <screenshot>
+      <image>http://www.chocolate-doom.org/wiki/images/1/14/GNOME_Heretic_Shareware_DEMO3.png</image>
+      <caption>Shareware Level E1M9: The Graveyard</caption>
+    </screenshot>
+    <screenshot>
+      <image>http://www.chocolate-doom.org/wiki/images/3/34/GNOME_Heretic_E4M1.png</image>
+      <caption>Level E4M1: Catafalque</caption>
+    </screenshot>
+    <screenshot>
+      <image>http://www.chocolate-doom.org/wiki/images/4/42/GNOME_Heretic_Shareware_DEMO1.png</image>
+      <caption>Shareware Level E1M3: The Gatehouse</caption>
+    </screenshot>
+  </screenshots>
+</component>
--- a/src/heretic/d_main.c
+++ b/src/heretic/d_main.c
@@ -62,7 +62,6 @@
 boolean debugmode;              // checkparm of -debug
 boolean ravpic;                 // checkparm of -ravpic
 boolean cdrom;                  // true if cd-rom mode active
-boolean singletics;             // debug flag to cancel adaptiveness
 boolean noartiskip;             // whether shift-enter skips an artifact
 
 skill_t startskill;
@@ -1019,7 +1018,7 @@
 
         if (D_AddFile(file))
         {
-            M_StringCopy(demolumpname, lumpinfo[numlumps - 1].name,
+            M_StringCopy(demolumpname, lumpinfo[numlumps - 1]->name,
                          sizeof(demolumpname));
         }
         else
--- a/src/heretic/g_game.c
+++ b/src/heretic/g_game.c
@@ -102,7 +102,6 @@
 
 int consoleplayer;              // player taking events and displaying
 int displayplayer;              // view being displayed
-int gametic;
 int levelstarttic;              // gametic at level start
 int totalkills, totalitems, totalsecret;        // for intermission
 
--- a/src/heretic/r_data.c
+++ b/src/heretic/r_data.c
@@ -671,7 +671,7 @@
         if (flatpresent[i])
         {
             lump = firstflat + i;
-            flatmemory += lumpinfo[lump].size;
+            flatmemory += lumpinfo[lump]->size;
             W_CacheLumpNum(lump, PU_CACHE);
         }
 
@@ -701,7 +701,7 @@
         for (j = 0; j < texture->patchcount; j++)
         {
             lump = texture->patches[j].patch;
-            texturememory += lumpinfo[lump].size;
+            texturememory += lumpinfo[lump]->size;
             W_CacheLumpNum(lump, PU_CACHE);
         }
     }
@@ -731,7 +731,7 @@
             for (k = 0; k < 8; k++)
             {
                 lump = firstspritelump + sf->lump[k];
-                spritememory += lumpinfo[lump].size;
+                spritememory += lumpinfo[lump]->size;
                 W_CacheLumpNum(lump, PU_CACHE);
             }
         }
--- a/src/heretic/r_things.c
+++ b/src/heretic/r_things.c
@@ -175,15 +175,15 @@
         // scan the lumps, filling in the frames for whatever is found
         //
         for (l = start + 1; l < end; l++)
-            if (!strncasecmp(lumpinfo[l].name, spritename, 4))
+            if (!strncasecmp(lumpinfo[l]->name, spritename, 4))
             {
-                frame = lumpinfo[l].name[4] - 'A';
-                rotation = lumpinfo[l].name[5] - '0';
+                frame = lumpinfo[l]->name[4] - 'A';
+                rotation = lumpinfo[l]->name[5] - '0';
                 R_InstallSpriteLump(l, frame, rotation, false);
-                if (lumpinfo[l].name[6])
+                if (lumpinfo[l]->name[6])
                 {
-                    frame = lumpinfo[l].name[6] - 'A';
-                    rotation = lumpinfo[l].name[7] - '0';
+                    frame = lumpinfo[l]->name[6] - 'A';
+                    rotation = lumpinfo[l]->name[7] - '0';
                     R_InstallSpriteLump(l, frame, rotation, true);
                 }
             }
--- a/src/heretic/s_sound.c
+++ b/src/heretic/s_sound.c
@@ -268,10 +268,8 @@
             sep = 512 - sep;
     }
 
-    // TODO: Play pitch-shifted sounds as in Vanilla Heretic
-
-    channel[i].pitch = (byte) (127 + (M_Random() & 7) - (M_Random() & 7));
-    channel[i].handle = I_StartSound(&S_sfx[sound_id], i, vol, sep);
+    channel[i].pitch = (byte) (NORM_PITCH + (M_Random() & 7) - (M_Random() & 7));
+    channel[i].handle = I_StartSound(&S_sfx[sound_id], i, vol, sep, channel[i].pitch);
     channel[i].mo = origin;
     channel[i].sound_id = sound_id;
     channel[i].priority = priority;
@@ -327,9 +325,8 @@
         S_sfx[sound_id].lumpnum = I_GetSfxLumpNum(&S_sfx[sound_id]);
     }
 
-    // TODO: Pitch shifting.
-    channel[i].pitch = (byte) (127 - (M_Random() & 3) + (M_Random() & 3));
-    channel[i].handle = I_StartSound(&S_sfx[sound_id], i, volume, 128);
+    channel[i].pitch = (byte) (NORM_PITCH - (M_Random() & 3) + (M_Random() & 3));
+    channel[i].handle = I_StartSound(&S_sfx[sound_id], i, volume, 128, channel[i].pitch);
     channel[i].mo = origin;
     channel[i].sound_id = sound_id;
     channel[i].priority = 1;    //super low priority.
@@ -516,7 +513,7 @@
 
 void S_Init(void)
 {
-    I_SetOPLDriverVer(opl_v_old);
+    I_SetOPLDriverVer(opl_doom2_1_666);
     soundCurve = Z_Malloc(MAX_SND_DIST, PU_STATIC, NULL);
     if (snd_Channels > 8)
     {
@@ -526,6 +523,12 @@
     S_SetMaxVolume(true);
 
     I_AtExit(S_ShutDown, true);
+
+    // Heretic defaults to pitch-shifting on
+    if (snd_pitchshift == -1)
+    {
+        snd_pitchshift = 1;
+    }
 
     I_PrecacheSounds(S_sfx, NUMSFX);
 }
--- /dev/null
+++ b/src/hexen.appdata.xml.in
@@ -1,0 +1,44 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<component type="desktop">
+  <id>@PROGRAM_PREFIX@hexen.desktop</id>
+  <metadata_license>CC0-1.0</metadata_license>
+  <project_license>GPL-2.0+</project_license>
+  <developer_name>@PACKAGE_MAINTAINER@</developer_name>
+  <url type="homepage">@PACKAGE_URL@</url>
+  <url type="bugtracker">@PACKAGE_ISSUES@</url>
+  <description>
+    <p>
+      @PACKAGE_SHORTNAME@ Hexen is a conservative,
+      historically-accurate Hexen source port, which is compatible
+      with mods and levels that were made before the Hexen source code
+      was released.  Unlike other source ports, the goal is to
+      preserve the original look, feel, limitations, and bugs of the
+      original DOS executable.
+    </p>
+    <p>
+      Full support for single- and multi-player games is provided.
+      Unlike the original executable, network play is implemented on
+      the IP network stack, allowing it to function on modern LANs and
+      the Internet.
+    </p>
+  </description>
+  <screenshots>
+    <screenshot type="default">
+      <image>http://www.chocolate-doom.org/wiki/images/0/0f/GNOME_Hexen_Guardian_of_Fire.png</image>
+      <caption>Level "Guardian of Fire"</caption>
+    </screenshot>
+    <screenshot>
+      <image>http://www.chocolate-doom.org/wiki/images/5/5c/GNOME_Hexen_Effluvium.png</image>
+      <caption>Level "Effluvium"</caption>
+    </screenshot>
+    <screenshot>
+      <image>http://www.chocolate-doom.org/wiki/images/c/c1/GNOME_Hexen_Dragon_Chapel.png</image>
+      <caption>Level "Dragon Chapel"</caption>
+    </screenshot>
+    <screenshot>
+      <image>http://www.chocolate-doom.org/wiki/images/a/a7/GNOME_Hexen_Darkmere.png</image>
+      <caption>Level "Darkmere"</caption>
+    </screenshot>
+  </screenshots>
+</component>
--- a/src/hexen/g_game.c
+++ b/src/hexen/g_game.c
@@ -88,7 +88,6 @@
 
 int consoleplayer;              // player taking events and displaying
 int displayplayer;              // view being displayed
-int gametic;
 int levelstarttic;              // gametic at level start
 
 char demoname[32];
--- a/src/hexen/h2_main.c
+++ b/src/hexen/h2_main.c
@@ -103,7 +103,6 @@
 boolean ravpic;                 // checkparm of -ravpic
 boolean cdrom = false;          // true if cd-rom mode active
 boolean cmdfrag;                // true if a CMD_FRAG packet should be sent out
-boolean singletics;             // debug flag to cancel adaptiveness
 boolean artiskip;               // whether shift-enter skips an artifact
 int maxzone = 0x800000;         // Maximum allocated for zone heap (8meg default)
 skill_t startskill;
@@ -270,15 +269,26 @@
 	maxplayers = 4;
     }
 
-    // The v1.0 IWAD file is missing a bunch of lumps.
-    if (gamemode != shareware && W_CheckNumForName("CLUS1MSG") == -1)
+    // The v1.0 IWAD file is missing a bunch of lumps that can cause the game
+    // to crash, so we exit with an error if the user tries to play with it.
+    // But we provide an override command line flag if they really want to
+    // do it.
+
+    //!
+    // If provided, the check for the v1.0 IWAD file is disabled, even though
+    // it will almost certainly cause the game to crash.
+    //
+    // @category compat
+    //
+
+    if (!M_ParmExists("-v10override")
+     && gamemode != shareware && W_CheckNumForName("CLUS1MSG") < 0)
     {
-        printf(
-            "** WARNING: You are playing with the Hexen v1.0 IWAD. This\n"
-            "** isn't supported by " PACKAGE_NAME ", and you may find that\n"
-            "** the game will crash. Please upgrade to the v1.1 IWAD file.\n"
-            "** See here for more information:\n"
-            "**   http://www.doomworld.com/classicdoom/info/patches.php\n");
+        I_Error(
+            "You are trying to use the Hexen v1.0 IWAD. This isn't\n"
+            "supported by " PACKAGE_NAME ". Please upgrade to the v1.1\n"
+            "IWAD file. See here for more information:\n"
+            "  http://www.doomworld.com/classicdoom/info/patches.php");
     }
 }
 
@@ -653,7 +663,7 @@
 
         if (W_AddFile(file) != NULL)
         {
-            M_StringCopy(demolumpname, lumpinfo[numlumps - 1].name,
+            M_StringCopy(demolumpname, lumpinfo[numlumps - 1]->name,
                          sizeof(demolumpname));
         }
         else
--- a/src/hexen/h2def.h
+++ b/src/hexen/h2def.h
@@ -169,7 +169,7 @@
 
 typedef union
 {
-    int i;
+    intptr_t i;
     struct mobj_s *m;
     struct player_s *p;
 } specialval_t;
--- a/src/hexen/r_data.c
+++ b/src/hexen/r_data.c
@@ -627,7 +627,7 @@
         if (flatpresent[i])
         {
             lump = firstflat + i;
-            flatmemory += lumpinfo[lump].size;
+            flatmemory += lumpinfo[lump]->size;
             W_CacheLumpNum(lump, PU_CACHE);
         }
 
@@ -658,7 +658,7 @@
         for (j = 0; j < texture->patchcount; j++)
         {
             lump = texture->patches[j].patch;
-            texturememory += lumpinfo[lump].size;
+            texturememory += lumpinfo[lump]->size;
             W_CacheLumpNum(lump, PU_CACHE);
         }
     }
@@ -688,7 +688,7 @@
             for (k = 0; k < 8; k++)
             {
                 lump = firstspritelump + sf->lump[k];
-                spritememory += lumpinfo[lump].size;
+                spritememory += lumpinfo[lump]->size;
                 W_CacheLumpNum(lump, PU_CACHE);
             }
         }
--- a/src/hexen/r_things.c
+++ b/src/hexen/r_things.c
@@ -178,15 +178,15 @@
         // scan the lumps, filling in the frames for whatever is found
         //
         for (l = start + 1; l < end; l++)
-            if (!strncmp(lumpinfo[l].name, namelist[i], 4))
+            if (!strncmp(lumpinfo[l]->name, namelist[i], 4))
             {
-                frame = lumpinfo[l].name[4] - 'A';
-                rotation = lumpinfo[l].name[5] - '0';
+                frame = lumpinfo[l]->name[4] - 'A';
+                rotation = lumpinfo[l]->name[5] - '0';
                 R_InstallSpriteLump(l, frame, rotation, false);
-                if (lumpinfo[l].name[6])
+                if (lumpinfo[l]->name[6])
                 {
-                    frame = lumpinfo[l].name[6] - 'A';
-                    rotation = lumpinfo[l].name[7] - '0';
+                    frame = lumpinfo[l]->name[6] - 'A';
+                    rotation = lumpinfo[l]->name[7] - '0';
                     R_InstallSpriteLump(l, frame, rotation, true);
                 }
             }
--- a/src/hexen/s_sound.c
+++ b/src/hexen/s_sound.c
@@ -498,17 +498,16 @@
 //              vol = SoundCurve[dist];
     }
 
-#if 0
-// TODO
-    if (S_sfx[sound_id].changePitch)
+    // if the sfxinfo_t is marked as 'can be pitch shifted'
+    if (S_sfx[sound_id].pitch)
     {
-        Channel[i].pitch = (byte) (127 + (M_Random() & 7) - (M_Random() & 7));
+        Channel[i].pitch = (byte) (NORM_PITCH + (M_Random() & 7) - (M_Random() & 7));
     }
     else
     {
-        Channel[i].pitch = 127;
+        Channel[i].pitch = NORM_PITCH;
     }
-#endif
+
     if (S_sfx[sound_id].lumpnum == 0)
     {
         S_sfx[sound_id].lumpnum = I_GetSfxLumpNum(&S_sfx[sound_id]);
@@ -517,7 +516,8 @@
     Channel[i].handle = I_StartSound(&S_sfx[sound_id],
                                      i,
                                      vol,
-                                     sep /* , Channel[i].pitch] */);
+                                     sep,
+                                     Channel[i].pitch);
     Channel[i].sound_id = sound_id;
     Channel[i].priority = priority;
     Channel[i].volume = volume;
@@ -771,7 +771,7 @@
                 if (sep > 192)
                     sep = 512 - sep;
             }
-            I_UpdateSoundParams(i, vol, sep /*, Channel[i].pitch */);
+            I_UpdateSoundParams(i, vol, sep);
             priority = S_sfx[Channel[i].sound_id].priority;
             priority *= PRIORITY_MAX_ADJUST - (dist / DIST_ADJUST);
             Channel[i].priority = priority;
@@ -787,7 +787,7 @@
 
 void S_Init(void)
 {
-    I_SetOPLDriverVer(opl_v_old);
+    I_SetOPLDriverVer(opl_doom2_1_666);
     SoundCurve = W_CacheLumpName("SNDCURVE", PU_STATIC);
 //      SoundCurve = Z_Malloc(MAX_SND_DIST, PU_STATIC, NULL);
 
@@ -798,6 +798,12 @@
     I_SetMusicVolume(snd_MusicVolume * 8);
 
     I_AtExit(S_ShutDown, true);
+
+    // Hexen defaults to pitch-shifting on
+    if (snd_pitchshift == -1)
+    {
+        snd_pitchshift = 1;
+    }
 
     I_PrecacheSounds(S_sfx, NUMSFX);
 
--- a/src/i_oplmusic.c
+++ b/src/i_oplmusic.c
@@ -307,7 +307,7 @@
     124, 124, 125, 125, 126, 126, 127, 127
 };
 
-static opl_driver_ver_t opl_drv_ver = opl_v_new;
+static opl_driver_ver_t opl_drv_ver = opl_doom_1_9;
 static boolean music_initialized = false;
 
 //static boolean musicpaused = false;
@@ -349,9 +349,15 @@
 // Configuration file variable, containing the port number for the
 // adlib chip.
 
+char *snd_dmxoption = "";
 int opl_io_port = 0x388;
-int opl_type = 0;
 
+// If true, OPL sound channels are reversed to their correct arrangement
+// (as intended by the MIDI standard) rather than the backwards one
+// used by DMX due to a bug.
+
+static boolean opl_stereo_correct = false;
+
 // Load instrument table from GENMIDI lump:
 
 static boolean LoadInstrumentTable(void)
@@ -470,7 +476,7 @@
     *rover = voice;
     voice->next = NULL;
 
-    if (next != NULL && double_voice && opl_drv_ver == opl_v_old)
+    if (next != NULL && double_voice && opl_drv_ver != opl_doom_1_9)
     {
         VoiceKeyOff(next);
         ReleaseVoice(next);
@@ -770,13 +776,32 @@
     ReleaseVoice(result);
 }
 
-// Alternate version of ReplaceExistingVoice() used when emulating old
-// versions of the DMX library used in Heretic and Hexen.
+// Alternate versions of ReplaceExistingVoice() used when emulating old
+// versions of the DMX library used in Doom 1.666, Heretic and Hexen.
 
-static void ReplaceExistingVoiceOld(opl_channel_data_t *channel)
+static void ReplaceExistingVoiceDoom1(void)
 {
     opl_voice_t *rover;
     opl_voice_t *result;
+
+    result = voice_alloced_list;
+
+    for (rover = voice_alloced_list; rover != NULL; rover = rover->next)
+    {
+        if (rover->channel > result->channel)
+        {
+            result = rover;
+        }
+    }
+
+    VoiceKeyOff(result);
+    ReleaseVoice(result);
+}
+
+static void ReplaceExistingVoiceDoom2(opl_channel_data_t *channel)
+{
+    opl_voice_t *rover;
+    opl_voice_t *result;
     opl_voice_t *roverend;
     int i;
     int priority;
@@ -918,6 +943,11 @@
 {
     opl_voice_t *voice;
 
+    if (!opl_opl3mode && opl_drv_ver == opl_doom1_1_666)
+    {
+        instrument_voice = 0;
+    }
+
     // Find a voice to use for this new note.
 
     voice = GetFreeVoice();
@@ -962,7 +992,7 @@
 {
     genmidi_instr_t *instrument;
     opl_channel_data_t *channel;
-    unsigned int note, key, volume;
+    unsigned int note, key, volume, voicenum;
     boolean double_voice;
 
 /*
@@ -1009,43 +1039,66 @@
 
     double_voice = (SHORT(instrument->flags) & GENMIDI_FLAG_2VOICE) != 0;
 
-    if (opl_drv_ver == opl_v_old)
+    switch (opl_drv_ver)
     {
-        if (voice_alloced_num == num_opl_voices)
-        {
-            ReplaceExistingVoiceOld(channel);
-        }
-        if (voice_alloced_num == num_opl_voices - 1 && double_voice)
-        {
-            ReplaceExistingVoiceOld(channel);
-        }
+        case opl_doom1_1_666:
+            voicenum = double_voice + 1;
+            if (!opl_opl3mode)
+            {
+                voicenum = 1;
+            }
+            while (voice_alloced_num > num_opl_voices - voicenum)
+            {
+                ReplaceExistingVoiceDoom1();
+            }
 
-        // Find and program a voice for this instrument.  If this
-        // is a double voice instrument, we must do this twice.
+            // Find and program a voice for this instrument.  If this
+            // is a double voice instrument, we must do this twice.
 
-        if (double_voice)
-        {
-            VoiceKeyOn(channel, instrument, 1, note, key, volume);
-        }
+            if (double_voice)
+            {
+                VoiceKeyOn(channel, instrument, 1, note, key, volume);
+            }
 
-        VoiceKeyOn(channel, instrument, 0, note, key, volume);
-    }
-    else
-    {
-        if (voice_free_list == NULL)
-        {
-            ReplaceExistingVoice();
-        }
+            VoiceKeyOn(channel, instrument, 0, note, key, volume);
+            break;
+        case opl_doom2_1_666:
+            if (voice_alloced_num == num_opl_voices)
+            {
+                ReplaceExistingVoiceDoom2(channel);
+            }
+            if (voice_alloced_num == num_opl_voices - 1 && double_voice)
+            {
+                ReplaceExistingVoiceDoom2(channel);
+            }
 
-        // Find and program a voice for this instrument.  If this
-        // is a double voice instrument, we must do this twice.
+            // Find and program a voice for this instrument.  If this
+            // is a double voice instrument, we must do this twice.
 
-        VoiceKeyOn(channel, instrument, 0, note, key, volume);
+            if (double_voice)
+            {
+                VoiceKeyOn(channel, instrument, 1, note, key, volume);
+            }
 
-        if (double_voice)
-        {
-            VoiceKeyOn(channel, instrument, 1, note, key, volume);
-        }
+            VoiceKeyOn(channel, instrument, 0, note, key, volume);
+            break;
+        default:
+        case opl_doom_1_9:
+            if (voice_free_list == NULL)
+            {
+                ReplaceExistingVoice();
+            }
+
+            // Find and program a voice for this instrument.  If this
+            // is a double voice instrument, we must do this twice.
+
+            VoiceKeyOn(channel, instrument, 0, note, key, volume);
+
+            if (double_voice)
+            {
+                VoiceKeyOn(channel, instrument, 1, note, key, volume);
+            }
+            break;
     }
 }
 
@@ -1086,6 +1139,16 @@
     unsigned int reg_pan;
     unsigned int i;
 
+    // The DMX library has the stereo channels backwards, maybe because
+    // Paul Radek had a Soundblaster card with the channels reversed, or
+    // perhaps it was just a bug in the OPL3 support that was never
+    // finished. By default we preserve this bug, but we also provide a
+    // secret DMXOPTION to fix it.
+    if (opl_stereo_correct)
+    {
+        pan = 144 - pan;
+    }
+
     if (opl_opl3mode)
     {
         if (pan >= 96)
@@ -1650,19 +1713,28 @@
 
 static boolean I_OPL_InitMusic(void)
 {
-    int opl_chip_type;
+    char *dmxoption;
+    opl_init_result_t chip_type;
 
     OPL_SetSampleRate(snd_samplerate);
 
-    opl_chip_type = OPL_Init(opl_io_port);
-    if (!opl_chip_type)
+    chip_type = OPL_Init(opl_io_port);
+    if (chip_type == OPL_INIT_NONE)
     {
         printf("Dude.  The Adlib isn't responding.\n");
         return false;
     }
 
-    if (opl_chip_type == 2 && opl_type)
+    // The DMXOPTION variable must be set to enable OPL3 support.
+    // As an extension, we also allow it to be set from the config file.
+    dmxoption = getenv("DMXOPTION");
+    if (dmxoption == NULL)
     {
+        dmxoption = snd_dmxoption != NULL ? snd_dmxoption : "";
+    }
+
+    if (chip_type == OPL_INIT_OPL3 && strstr(dmxoption, "-opl3") != NULL)
+    {
         opl_opl3mode = 1;
         num_opl_voices = OPL_NUM_VOICES * 2;
     }
@@ -1671,6 +1743,10 @@
         opl_opl3mode = 0;
         num_opl_voices = OPL_NUM_VOICES;
     }
+
+    // Secret, undocumented DMXOPTION that reverses the stereo channels
+    // into their correct orientation.
+    opl_stereo_correct = strstr(dmxoption, "-reverse") != NULL;
 
     // Initialize all registers.
 
--- a/src/i_pcsound.c
+++ b/src/i_pcsound.c
@@ -174,7 +174,8 @@
 static int I_PCS_StartSound(sfxinfo_t *sfxinfo,
                             int channel,
                             int vol,
-                            int sep)
+                            int sep,
+                            int pitch)
 {
     int result;
 
--- a/src/i_sdlmusic.c
+++ b/src/i_sdlmusic.c
@@ -752,7 +752,7 @@
 
     for (lumpnum = 0; lumpnum < numlumps; ++lumpnum)
     {
-        strncpy(name, lumpinfo[lumpnum].name, 8);
+        strncpy(name, lumpinfo[lumpnum]->name, 8);
         name[8] = '\0';
 
         if (!IsMusicLump(lumpnum))
--- a/src/i_sdlsound.c
+++ b/src/i_sdlsound.c
@@ -52,6 +52,7 @@
     sfxinfo_t *sfxinfo;
     Mix_Chunk chunk;
     int use_count;
+    int pitch;
     allocated_sound_t *prev, *next;
 };
 
@@ -59,7 +60,7 @@
 
 static boolean sound_initialized = false;
 
-static sfxinfo_t *channels_playing[NUM_CHANNELS];
+static allocated_sound_t *channels_playing[NUM_CHANNELS];
 
 static int mixer_freq;
 static Uint16 mixer_format;
@@ -136,10 +137,6 @@
 
     AllocatedSoundUnlink(snd);
 
-    // Unlink from higher-level code.
-
-    snd->sfxinfo->driver_data = NULL;
-
     // Keep track of the amount of allocated sound data:
 
     allocated_sounds_size -= snd->chunk.alen;
@@ -200,7 +197,7 @@
 
 // Allocate a block for a new sound effect.
 
-static Mix_Chunk *AllocateSound(sfxinfo_t *sfxinfo, size_t len)
+static allocated_sound_t *AllocateSound(sfxinfo_t *sfxinfo, size_t len)
 {
     allocated_sound_t *snd;
 
@@ -231,14 +228,11 @@
     snd->chunk.alen = len;
     snd->chunk.allocated = 1;
     snd->chunk.volume = MIX_MAX_VOLUME;
+    snd->pitch = NORM_PITCH;
 
     snd->sfxinfo = sfxinfo;
     snd->use_count = 0;
 
-    // driver_data pointer points to the allocated_sound structure.
-
-    sfxinfo->driver_data = snd;
-
     // Keep track of how much memory all these cached sounds are using...
 
     allocated_sounds_size += len;
@@ -245,7 +239,7 @@
 
     AllocatedSoundLink(snd);
 
-    return &snd->chunk;
+    return snd;
 }
 
 // Lock a sound, to indicate that it may not be freed.
@@ -279,15 +273,77 @@
     //printf("-- %s: Use count=%i\n", snd->sfxinfo->name, snd->use_count);
 }
 
-// When a sound stops, check if it is still playing.  If it is not, 
+// Search through the list of allocated sounds and return the one that matches
+// the supplied sfxinfo entry and pitch level.
+
+static allocated_sound_t * GetAllocatedSoundBySfxInfoAndPitch(sfxinfo_t *sfxinfo, int pitch)
+{
+    allocated_sound_t * p = allocated_sounds_head;
+
+    while (p != NULL)
+    {
+        if (p->sfxinfo == sfxinfo && p->pitch == pitch)
+        {
+            return p;
+        }
+        p = p->next;
+    }
+
+    return NULL;
+}
+
+// Allocate a new sound chunk and pitch-shift an existing sound up-or-down
+// into it.
+
+static allocated_sound_t * PitchShift(allocated_sound_t *insnd, int pitch)
+{
+    allocated_sound_t * outsnd;
+    Sint16 *inp, *outp;
+    Sint16 *srcbuf, *dstbuf;
+    Uint32 srclen, dstlen;
+
+    srcbuf = (Sint16 *)insnd->chunk.abuf;
+    srclen = insnd->chunk.alen;
+
+    // determine ratio pitch:NORM_PITCH and apply to srclen, then invert.
+    // This is an approximation of vanilla behaviour based on measurements
+    dstlen = (int)((1 + (1 - (float)pitch / NORM_PITCH)) * srclen);
+
+    // ensure that the new buffer is an even length
+    if ((dstlen % 2) == 0)
+    {
+        dstlen++;
+    }
+
+    outsnd = AllocateSound(insnd->sfxinfo, dstlen);
+
+    if (!outsnd)
+    {
+        return NULL;
+    }
+
+    outsnd->pitch = pitch;
+    dstbuf = (Sint16 *)outsnd->chunk.abuf;
+
+    // loop over output buffer. find corresponding input cell, copy over
+    for (outp = dstbuf; outp < dstbuf + dstlen/2; ++outp)
+    {
+        inp = srcbuf + (int)((float)(outp - dstbuf) / dstlen * srclen);
+        *outp = *inp;
+    }
+
+    return outsnd;
+}
+
+// When a sound stops, check if it is still playing.  If it is not,
 // we can mark the sound data as CACHE to be freed back for other
 // means.
 
 static void ReleaseSoundOnChannel(int channel)
 {
-    sfxinfo_t *sfxinfo = channels_playing[channel];
+    allocated_sound_t *snd = channels_playing[channel];
 
-    if (sfxinfo == NULL)
+    if (snd == NULL)
     {
         return;
     }
@@ -294,7 +350,14 @@
 
     channels_playing[channel] = NULL;
 
-    UnlockAllocatedSound(sfxinfo->driver_data);
+    UnlockAllocatedSound(snd);
+
+    // if the sound is a pitch-shift and it's not in use, immediately
+    // free it
+    if (snd->pitch != NORM_PITCH && snd->use_count <= 0)
+    {
+        FreeAllocatedSound(snd);
+    }
 }
 
 #ifdef HAVE_LIBSAMPLERATE
@@ -343,6 +406,7 @@
 //    uint32_t alen;
     int retn;
     int16_t *expanded;
+    allocated_sound_t *snd;
     Mix_Chunk *chunk;
 
     src_data.input_frames = length;
@@ -374,13 +438,14 @@
 
 //    alen = src_data.output_frames_gen * 4;
 
-    chunk = AllocateSound(sfxinfo, src_data.output_frames_gen * 4);
+    snd = AllocateSound(sfxinfo, src_data.output_frames_gen * 4);
 
-    if (chunk == NULL)
+    if (snd == NULL)
     {
         return false;
     }
 
+    chunk = &snd->chunk;
     expanded = (int16_t *) chunk->abuf;
 
     // Convert the result back into 16-bit integers.
@@ -533,11 +598,12 @@
                                    int length)
 {
     SDL_AudioCVT convertor;
+    allocated_sound_t *snd;
     Mix_Chunk *chunk;
     uint32_t expanded_length;
- 
-    // Calculate the length of the expanded version of the sample.    
 
+    // Calculate the length of the expanded version of the sample.
+
     expanded_length = (uint32_t) ((((uint64_t) length) * mixer_freq) / samplerate);
 
     // Double up twice: 8 -> 16 bit and mono -> stereo
@@ -546,13 +612,15 @@
 
     // Allocate a chunk in which to expand the sound
 
-    chunk = AllocateSound(sfxinfo, expanded_length);
+    snd = AllocateSound(sfxinfo, expanded_length);
 
-    if (chunk == NULL)
+    if (snd == NULL)
     {
         return false;
     }
 
+    chunk = &snd->chunk;
+
     // If we can, use the standard / optimized SDL conversion routines.
 
     if (samplerate <= mixer_freq
@@ -696,11 +764,12 @@
 #ifdef DEBUG_DUMP_WAVS
     {
         char filename[16];
+        allocated_sound_t * snd;
 
         M_snprintf(filename, sizeof(filename), "%s.wav",
-                   DEH_String(S_sfx[sound].name));
-        WriteWAV(filename, sound_chunks[sound].abuf,
-                 sound_chunks[sound].alen, mixer_freq);
+                   DEH_String(sfxinfo->name));
+        snd = GetAllocatedSoundBySfxInfoAndPitch(sfxinfo, NORM_PITCH);
+        WriteWAV(filename, snd->chunk.abuf, snd->chunk.alen,mixer_freq);
     }
 #endif
 
@@ -786,8 +855,7 @@
 static boolean LockSound(sfxinfo_t *sfxinfo)
 {
     // If the sound isn't loaded, load it now
-
-    if (sfxinfo->driver_data == NULL)
+    if (GetAllocatedSoundBySfxInfoAndPitch(sfxinfo, NORM_PITCH) == NULL)
     {
         if (!CacheSFX(sfxinfo))
         {
@@ -795,7 +863,7 @@
         }
     }
 
-    LockAllocatedSound(sfxinfo->driver_data);
+    LockAllocatedSound(GetAllocatedSoundBySfxInfoAndPitch(sfxinfo, NORM_PITCH));
 
     return true;
 }
@@ -857,7 +925,7 @@
 //  is set, but currently not used by mixing.
 //
 
-static int I_SDL_StartSound(sfxinfo_t *sfxinfo, int channel, int vol, int sep)
+static int I_SDL_StartSound(sfxinfo_t *sfxinfo, int channel, int vol, int sep, int pitch)
 {
     allocated_sound_t *snd;
 
@@ -875,19 +943,47 @@
 
     if (!LockSound(sfxinfo))
     {
-	return -1;
+        return -1;
     }
 
-    snd = sfxinfo->driver_data;
+    snd = GetAllocatedSoundBySfxInfoAndPitch(sfxinfo, pitch);
 
+    if (snd == NULL)
+    {
+        allocated_sound_t *newsnd;
+        // fetch the base sound effect, un-pitch-shifted
+        snd = GetAllocatedSoundBySfxInfoAndPitch(sfxinfo, NORM_PITCH);
+
+        if (snd == NULL)
+        {
+            return -1;
+        }
+
+        if (snd_pitchshift)
+        {
+            newsnd = PitchShift(snd, pitch);
+
+            if (newsnd)
+            {
+                LockAllocatedSound(newsnd);
+                UnlockAllocatedSound(snd);
+                snd = newsnd;
+            }
+        }
+    }
+    else
+    {
+        LockAllocatedSound(snd);
+    }
+
     // play sound
 
-    Mix_PlayChannelTimed(channel, &snd->chunk, 0, -1);
+    Mix_PlayChannel(channel, &snd->chunk, 0);
 
-    channels_playing[channel] = sfxinfo;
+    channels_playing[channel] = snd;
 
     // set separation, etc.
- 
+
     I_SDL_UpdateSoundParams(channel, vol, sep);
 
     return channel;
@@ -919,7 +1015,7 @@
     return Mix_Playing(handle);
 }
 
-// 
+//
 // Periodically called to update the sound system
 //
 
@@ -935,7 +1031,7 @@
         {
             // Sound has finished playing on this channel,
             // but sound data has not been released to cache
-            
+
             ReleaseSoundOnChannel(i);
         }
     }
@@ -942,7 +1038,7 @@
 }
 
 static void I_SDL_ShutdownSound(void)
-{    
+{
     if (!sound_initialized)
     {
         return;
--- a/src/i_sound.c
+++ b/src/i_sound.c
@@ -48,6 +48,11 @@
 
 char *snd_musiccmd = "";
 
+// Whether to vary the pitch of sound effects
+// Each game will set the default differently
+
+int snd_pitchshift = -1;
+
 // Low-level sound and music modules we are using
 
 static sound_module_t *sound_module;
@@ -68,7 +73,6 @@
 
 extern opl_driver_ver_t opl_drv_ver;
 extern int opl_io_port;
-extern int opl_type;
 
 // For native music module:
 
@@ -309,12 +313,12 @@
     }
 }
 
-int I_StartSound(sfxinfo_t *sfxinfo, int channel, int vol, int sep)
+int I_StartSound(sfxinfo_t *sfxinfo, int channel, int vol, int sep, int pitch)
 {
     if (sound_module != NULL)
     {
         CheckVolumeSeparation(&vol, &sep);
-        return sound_module->StartSound(sfxinfo, channel, vol, sep);
+        return sound_module->StartSound(sfxinfo, channel, vol, sep, pitch);
     }
     else
     {
@@ -346,7 +350,7 @@
 {
     if (sound_module != NULL && sound_module->CacheSounds != NULL)
     {
-	sound_module->CacheSounds(sounds, num_sounds);
+        sound_module->CacheSounds(sounds, num_sounds);
     }
 }
 
@@ -433,6 +437,7 @@
 
 void I_BindSoundVariables(void)
 {
+    extern char *snd_dmxoption;
     extern int use_libsamplerate;
     extern float libsamplerate_scale;
 
@@ -444,10 +449,11 @@
     M_BindIntVariable("snd_mport",               &snd_mport);
     M_BindIntVariable("snd_maxslicetime_ms",     &snd_maxslicetime_ms);
     M_BindStringVariable("snd_musiccmd",         &snd_musiccmd);
+    M_BindStringVariable("snd_dmxoption",        &snd_dmxoption);
     M_BindIntVariable("snd_samplerate",          &snd_samplerate);
     M_BindIntVariable("snd_cachesize",           &snd_cachesize);
     M_BindIntVariable("opl_io_port",             &opl_io_port);
-    M_BindIntVariable("opl_type",                &opl_type);
+    M_BindIntVariable("snd_pitchshift",          &snd_pitchshift);
 
     M_BindStringVariable("timidity_cfg_path",    &timidity_cfg_path);
     M_BindStringVariable("gus_patch_path",       &gus_patch_path);
--- a/src/i_sound.h
+++ b/src/i_sound.h
@@ -22,6 +22,8 @@
 
 #include "doomtype.h"
 
+// so that the individual game logic and sound driver code agree
+#define NORM_PITCH 127
 
 //
 // SoundFX struct.
@@ -32,7 +34,7 @@
 {
     // tag name, used for hexen.
     char *tagname;
-    
+
     // lump name.  If we are running with use_sfx_prefix=true, a
     // 'DS' (or 'DP' for PC speaker sounds) is prepended to this.
 
@@ -44,7 +46,7 @@
     // referenced sound if a link
     sfxinfo_t *link;
 
-    // pitch if a link
+    // pitch if a link (Doom), whether to pitch-shift (Hexen)
     int pitch;
 
     // volume if a link
@@ -56,7 +58,7 @@
     int usefulness;
 
     // lump number of sfx
-    int lumpnum;		
+    int lumpnum;
 
     // Maximum number of channels that the sound can be played on 
     // (Heretic)
@@ -76,13 +78,13 @@
 
     // lump number of music
     int lumpnum;
-    
+
     // music data
     void *data;
 
     // music handle once registered
     void *handle;
-    
+
 } musicinfo_t;
 
 typedef enum 
@@ -133,7 +135,7 @@
     // Start a sound on a given channel.  Returns the channel id
     // or -1 on failure.
 
-    int (*StartSound)(sfxinfo_t *sfxinfo, int channel, int vol, int sep);
+    int (*StartSound)(sfxinfo_t *sfxinfo, int channel, int vol, int sep, int pitch);
 
     // Stop the sound playing on the given channel.
 
@@ -154,7 +156,7 @@
 int I_GetSfxLumpNum(sfxinfo_t *sfxinfo);
 void I_UpdateSound(void);
 void I_UpdateSoundParams(int channel, int vol, int sep);
-int I_StartSound(sfxinfo_t *sfxinfo, int channel, int vol, int sep);
+int I_StartSound(sfxinfo_t *sfxinfo, int channel, int vol, int sep, int pitch);
 void I_StopSound(int channel);
 boolean I_SoundIsPlaying(int channel);
 void I_PrecacheSounds(sfxinfo_t *sounds, int num_sounds);
@@ -231,13 +233,15 @@
 extern int snd_cachesize;
 extern int snd_maxslicetime_ms;
 extern char *snd_musiccmd;
+extern int snd_pitchshift;
 
 void I_BindSoundVariables(void);
 
 // DMX version to emulate for OPL emulation:
 typedef enum {
-    opl_v_old,   // Hexen, Heretic
-    opl_v_new    // Doom, Strife
+    opl_doom1_1_666,    // Doom 1 v1.666
+    opl_doom2_1_666,    // Doom 2 v1.666, Hexen, Heretic
+    opl_doom_1_9        // Doom v1.9, Strife
 } opl_driver_ver_t;
 
 void I_SetOPLDriverVer(opl_driver_ver_t ver);
--- a/src/m_config.c
+++ b/src/m_config.c
@@ -801,6 +801,14 @@
     CONFIG_VARIABLE_INT(snd_maxslicetime_ms),
 
     //!
+    // If non-zero, sound effects will have their pitch varied up or
+    // down by a random amount during play. If zero, sound effects
+    // play back at their default pitch. The default is zero.
+    //
+
+    CONFIG_VARIABLE_INT(snd_pitchshift),
+
+    //!
     // External command to invoke to perform MIDI playback. If set to
     // the empty string, SDL_mixer's internal MIDI playback is used.
     // This only has any effect when snd_musicdevice is set to General
@@ -809,16 +817,18 @@
     CONFIG_VARIABLE_STRING(snd_musiccmd),
 
     //!
+    // Value to set for the DMXOPTION environment variable. If this contains
+    // "-opl3", output for an OPL3 chip is generated when in OPL MIDI
+    // playback mode.
+    //
+    CONFIG_VARIABLE_STRING(snd_dmxoption),
+
+    //!
     // The I/O port to use to access the OPL chip.  Only relevant when
     // using native OPL music playback.
     //
 
     CONFIG_VARIABLE_INT_HEX(opl_io_port),
-
-    //!
-    // OPL chip type.
-    //
-    CONFIG_VARIABLE_INT(opl_type),
 
     //!
     // @game doom heretic strife
--- a/src/setup/display.h
+++ b/src/setup/display.h
@@ -21,5 +21,6 @@
 
 extern int show_endoom;
 extern int graphical_startup;
+extern int png_screenshots;
 
 #endif /* #ifndef SETUP_DISPLAY_H */
--- a/src/setup/joystick.c
+++ b/src/setup/joystick.c
@@ -716,71 +716,77 @@
 // GUI
 //
 
-static void AddJoystickControl(txt_table_t *table, char *label, int *var)
+static void AddJoystickControl(TXT_UNCAST_ARG(table), char *label, int *var)
 {
+    TXT_CAST_ARG(txt_table_t, table);
     txt_joystick_input_t *joy_input;
 
     joy_input = TXT_NewJoystickInput(var);
 
-    TXT_AddWidget(table, TXT_NewLabel(label));
-    TXT_AddWidget(table, joy_input);
+    TXT_AddWidgets(table,
+                   TXT_NewLabel(label),
+                   joy_input,
+                   TXT_TABLE_EMPTY,
+                   NULL);
 }
 
 void ConfigJoystick(void)
 {
     txt_window_t *window;
-    txt_table_t *button_table, *axis_table;
-    txt_table_t *joystick_table;
 
     window = TXT_NewWindow("Gamepad/Joystick configuration");
-
+    TXT_SetTableColumns(window, 6);
+    TXT_SetColumnWidths(window, 18, 10, 2, 14, 10, 0);
     TXT_SetWindowHelpURL(window, WINDOW_HELP_URL);
 
     TXT_AddWidgets(window,
-                   joystick_table = TXT_NewTable(2),
-                   TXT_NewSeparator("Axes"),
-                   axis_table = TXT_NewTable(2),
-                   TXT_NewSeparator("Buttons"),
-                   button_table = TXT_NewTable(4),
-                   NULL);
-
-    TXT_SetColumnWidths(joystick_table, 13, 40);
-
-    TXT_AddWidgets(joystick_table,
                    TXT_NewLabel("Controller"),
                    joystick_button = TXT_NewButton("zzzz"),
-                   NULL);
+                   TXT_TABLE_EOL,
 
-    TXT_SetColumnWidths(axis_table, 20, 15);
-
-    TXT_AddWidgets(axis_table,
+                   TXT_NewSeparator("Axes"),
                    TXT_NewLabel("Forward/backward"),
                    y_axis_widget = TXT_NewJoystickAxis(&joystick_y_axis,
                                                        &joystick_y_invert,
                                                        JOYSTICK_AXIS_VERTICAL),
+                   TXT_TABLE_OVERFLOW_RIGHT,
+                   TXT_TABLE_OVERFLOW_RIGHT,
+                   TXT_TABLE_EMPTY,
+                   TXT_TABLE_EMPTY,
+
                    TXT_NewLabel("Turn left/right"),
-                   x_axis_widget = TXT_NewJoystickAxis(&joystick_x_axis,
-                                                       &joystick_x_invert,
-                                                       JOYSTICK_AXIS_HORIZONTAL),
+                   x_axis_widget =
+                        TXT_NewJoystickAxis(&joystick_x_axis,
+                                            &joystick_x_invert,
+                                            JOYSTICK_AXIS_HORIZONTAL),
+                   TXT_TABLE_OVERFLOW_RIGHT,
+                   TXT_TABLE_OVERFLOW_RIGHT,
+                   TXT_TABLE_EMPTY,
+                   TXT_TABLE_EMPTY,
+
                    TXT_NewLabel("Strafe left/right"),
                    TXT_NewJoystickAxis(&joystick_strafe_axis,
                                        &joystick_strafe_invert,
                                         JOYSTICK_AXIS_HORIZONTAL),
+                   TXT_TABLE_OVERFLOW_RIGHT,
+                   TXT_TABLE_OVERFLOW_RIGHT,
+                   TXT_TABLE_EMPTY,
+                   TXT_TABLE_EMPTY,
+
+                   TXT_NewSeparator("Buttons"),
                    NULL);
 
-    TXT_SetColumnWidths(button_table, 16, 12, 14, 11);
+    AddJoystickControl(window, "Fire/Attack", &joybfire);
+    AddJoystickControl(window, "Strafe Left", &joybstrafeleft);
 
-    AddJoystickControl(button_table, "Fire/Attack", &joybfire);
-    AddJoystickControl(button_table, "Strafe Left", &joybstrafeleft);
+    AddJoystickControl(window, "Use", &joybuse);
+    AddJoystickControl(window, "Strafe Right", &joybstraferight);
 
-    AddJoystickControl(button_table, "Use", &joybuse);
-    AddJoystickControl(button_table, "Strafe Right", &joybstraferight);
+    AddJoystickControl(window, "Previous weapon", &joybprevweapon);
+    AddJoystickControl(window, "Strafe", &joybstrafe);
 
-    AddJoystickControl(button_table, "Previous weapon", &joybprevweapon);
-    AddJoystickControl(button_table, "Strafe", &joybstrafe);
+    AddJoystickControl(window, "Next weapon", &joybnextweapon);
 
-    AddJoystickControl(button_table, "Next weapon", &joybnextweapon);
-
     // High values of joybspeed are used to activate the "always run mode"
     // trick in Vanilla Doom.  If this has been enabled, not only is the
     // joybspeed value meaningless, but the control itself is useless.
@@ -787,15 +793,15 @@
 
     if (joybspeed < 20)
     {
-        AddJoystickControl(button_table, "Speed", &joybspeed);
+        AddJoystickControl(window, "Speed", &joybspeed);
     }
 
     if (gamemission == hexen || gamemission == strife)
     {
-        AddJoystickControl(button_table, "Jump", &joybjump);
+        AddJoystickControl(window, "Jump", &joybjump);
     }
 
-    AddJoystickControl(button_table, "Activate menu", &joybmenu);
+    AddJoystickControl(window, "Activate menu", &joybmenu);
 
     TXT_SignalConnect(joystick_button, "pressed", CalibrateJoystick, NULL);
     TXT_SetWindowAction(window, TXT_HORIZ_CENTER, TestConfigAction());
--- a/src/setup/keyboard.c
+++ b/src/setup/keyboard.c
@@ -146,8 +146,9 @@
 
 // Add a label and keyboard input to the specified table.
 
-static void AddKeyControl(txt_table_t *table, char *name, int *var)
+static void AddKeyControl(TXT_UNCAST_ARG(table), char *name, int *var)
 {
+    TXT_CAST_ARG(txt_table_t, table);
     txt_key_input_t *key_input;
 
     TXT_AddWidget(table, TXT_NewLabel(name));
@@ -157,20 +158,26 @@
     TXT_SignalConnect(key_input, "set", KeySetCallback, var);
 }
 
-static void AddSectionLabel(txt_table_t *table, char *title, boolean add_space)
+static void AddSectionLabel(TXT_UNCAST_ARG(table), char *title,
+                            boolean add_space)
 {
+    TXT_CAST_ARG(txt_table_t, table);
     char buf[64];
 
     if (add_space)
     {
-        TXT_AddWidgets(table, TXT_NewStrut(0, 1), TXT_NewStrut(0, 1),
-                              NULL);
+        TXT_AddWidgets(table,
+                       TXT_NewStrut(0, 1),
+                       TXT_TABLE_EOL,
+                       NULL);
     }
 
     M_snprintf(buf, sizeof(buf), " - %s - ", title);
 
-    TXT_AddWidgets(table, TXT_NewLabel(buf),  TXT_NewStrut(0, 0),
-                          NULL);
+    TXT_AddWidgets(table,
+                   TXT_NewLabel(buf),
+                   TXT_TABLE_EOL,
+                   NULL);
 }
 static void ConfigExtraKeys(TXT_UNCAST_ARG(widget), TXT_UNCAST_ARG(unused))
 {
@@ -244,7 +251,8 @@
             AddKeyControl(table, "Chaos Device", &key_arti_teleport);
             AddKeyControl(table, "Banishment Device", &key_arti_teleportother);
             AddKeyControl(table, "Porkalator", &key_arti_egg);
-            AddKeyControl(table, "Icon of the Defender", &key_arti_invulnerability);
+            AddKeyControl(table, "Icon of the Defender",
+                          &key_arti_invulnerability);
         }
     }
     else
@@ -262,8 +270,8 @@
     AddKeyControl(table, "Weapon 6", &key_weapon6);
     AddKeyControl(table, "Weapon 7", &key_weapon7);
     AddKeyControl(table, "Weapon 8", &key_weapon8);
-    AddKeyControl(table, "Previous weapon",       &key_prevweapon);
-    AddKeyControl(table, "Next weapon",           &key_nextweapon);
+    AddKeyControl(table, "Previous weapon", &key_prevweapon);
+    AddKeyControl(table, "Next weapon", &key_nextweapon);
 }
 
 static void OtherKeysDialog(TXT_UNCAST_ARG(widget), TXT_UNCAST_ARG(unused))
@@ -353,9 +361,6 @@
 void ConfigKeyboard(void)
 {
     txt_window_t *window;
-    txt_table_t *movement_table;
-    txt_table_t *action_table;
-    txt_table_t *dialogs_table;
     txt_checkbox_t *run_control;
 
     always_run = joybspeed >= 20;
@@ -364,54 +369,57 @@
 
     TXT_SetWindowHelpURL(window, WINDOW_HELP_URL);
 
-    TXT_AddWidgets(window,
-                   TXT_NewSeparator("Movement"),
-                   movement_table = TXT_NewTable(4),
+    // The window is on a 5-column grid layout that looks like:
+    // Label | Control | | Label | Control
+    // There is a small gap between the two conceptual "columns" of
+    // controls, just for spacing.
+    TXT_SetTableColumns(window, 5);
+    TXT_SetColumnWidths(window, 15, 8, 2, 15, 8);
 
-                   TXT_NewSeparator("Action"),
-                   action_table = TXT_NewTable(4),
-                   dialogs_table = TXT_NewTable(2),
+    TXT_AddWidget(window, TXT_NewSeparator("Movement"));
+    AddKeyControl(window, "Move Forward", &key_up);
+    TXT_AddWidget(window, TXT_TABLE_EMPTY);
+    AddKeyControl(window, "Strafe Left", &key_strafeleft);
 
-                   TXT_NewSeparator("Misc."),
-                   run_control = TXT_NewCheckBox("Always run", &always_run),
-                   TXT_NewInvertedCheckBox("Use native keyboard mapping", 
-                                           &vanilla_keyboard_mapping),
-                   NULL);
+    AddKeyControl(window, "Move Backward", &key_down);
+    TXT_AddWidget(window, TXT_TABLE_EMPTY);
+    AddKeyControl(window, "Strafe Right", &key_straferight);
 
-    TXT_SetColumnWidths(movement_table, 15, 8, 15, 8);
+    AddKeyControl(window, "Turn Left", &key_left);
+    TXT_AddWidget(window, TXT_TABLE_EMPTY);
+    AddKeyControl(window, "Speed On", &key_speed);
 
-    TXT_SignalConnect(run_control, "changed", UpdateJoybSpeed, NULL);
+    AddKeyControl(window, "Turn Right", &key_right);
+    TXT_AddWidget(window, TXT_TABLE_EMPTY);
+    AddKeyControl(window, "Strafe On", &key_strafe);
 
-    AddKeyControl(movement_table, "Move Forward", &key_up);
-    AddKeyControl(movement_table, " Strafe Left", &key_strafeleft);
-    AddKeyControl(movement_table, "Move Backward", &key_down);
-    AddKeyControl(movement_table, " Strafe Right", &key_straferight);
-    AddKeyControl(movement_table, "Turn Left", &key_left);
-    AddKeyControl(movement_table, " Speed On", &key_speed);
-    AddKeyControl(movement_table, "Turn Right", &key_right);
-    AddKeyControl(movement_table, " Strafe On", &key_strafe);
-
     if (gamemission == hexen || gamemission == strife)
     {
-        AddKeyControl(movement_table, "Jump", &key_jump);
+        AddKeyControl(window, "Jump", &key_jump);
     }
 
-    TXT_SetColumnWidths(action_table, 15, 8, 15, 8);
+    TXT_AddWidget(window, TXT_NewSeparator("Action"));
+    AddKeyControl(window, "Fire/Attack", &key_fire);
+    TXT_AddWidget(window, TXT_TABLE_EMPTY);
+    AddKeyControl(window, "Use", &key_use);
 
-    AddKeyControl(action_table, "Fire/Attack", &key_fire);
-    AddKeyControl(action_table, " Use", &key_use);
-
-    // Other key bindings are stored in separate sub-dialogs:
-
-    TXT_SetColumnWidths(dialogs_table, 24, 24);
-
-    TXT_AddWidgets(dialogs_table,
+    TXT_AddWidgets(window,
                    TXT_NewButton2("More controls...", ConfigExtraKeys, NULL),
+                   TXT_TABLE_OVERFLOW_RIGHT,
+                   TXT_TABLE_EMPTY,
                    TXT_NewButton2("Other keys...", OtherKeysDialog, NULL),
+                   TXT_TABLE_OVERFLOW_RIGHT,
+
+                   TXT_NewSeparator("Misc."),
+                   run_control = TXT_NewCheckBox("Always run", &always_run),
+                   TXT_TABLE_EOL,
+                   TXT_NewInvertedCheckBox("Use native keyboard mapping",
+                                           &vanilla_keyboard_mapping),
+                   TXT_TABLE_EOL,
                    NULL);
 
+    TXT_SignalConnect(run_control, "changed", UpdateJoybSpeed, NULL);
     TXT_SetWindowAction(window, TXT_HORIZ_CENTER, TestConfigAction());
-
 }
 
 void BindKeyboardVariables(void)
--- a/src/setup/mainmenu.c
+++ b/src/setup/mainmenu.c
@@ -91,6 +91,8 @@
     show_endoom = 0;
     dclick_use = 0;
     novert = 1;
+    snd_dmxoption = "-opl3 -reverse";
+    png_screenshots = 1;
 }
 
 static int MainMenuKeyPress(txt_window_t *window, int key, void *user_data)
--- a/src/setup/mode.c
+++ b/src/setup/mode.c
@@ -140,6 +140,9 @@
 
     if (gamemission == strife)
     {
+        // Strife has a different default value than the other games
+        screenblocks = 10;
+
         M_BindStringVariable("back_flat",   &back_flat);
         M_BindStringVariable("nickname",    &nickname);
 
--- a/src/setup/mouse.c
+++ b/src/setup/mouse.c
@@ -67,8 +67,9 @@
     }
 }
 
-static void AddMouseControl(txt_table_t *table, char *label, int *var)
+static void AddMouseControl(TXT_UNCAST_ARG(table), char *label, int *var)
 {
+    TXT_CAST_ARG(txt_table_t, table);
     txt_mouse_input_t *mouse_input;
 
     TXT_AddWidget(table, TXT_NewLabel(label));
@@ -92,7 +93,7 @@
                    buttons_table = TXT_NewTable(2),
                    NULL);
 
-    TXT_SetColumnWidths(buttons_table, 29, 5);
+    TXT_SetColumnWidths(buttons_table, 24, 5);
 
     AddMouseControl(buttons_table, "Move backward", &mousebbackward);
     AddMouseControl(buttons_table, "Use", &mousebuse);
@@ -111,35 +112,28 @@
 void ConfigMouse(void)
 {
     txt_window_t *window;
-    txt_table_t *motion_table;
-    txt_table_t *buttons_table;
 
     window = TXT_NewWindow("Mouse configuration");
 
+    TXT_SetTableColumns(window, 2);
+
+    TXT_SetWindowAction(window, TXT_HORIZ_CENTER, TestConfigAction());
     TXT_SetWindowHelpURL(window, WINDOW_HELP_URL);
 
     TXT_AddWidgets(window,
                    TXT_NewCheckBox("Enable mouse", &usemouse),
+                   TXT_TABLE_OVERFLOW_RIGHT,
                    TXT_NewInvertedCheckBox("Allow vertical mouse movement", 
                                            &novert),
+                   TXT_TABLE_OVERFLOW_RIGHT,
                    TXT_NewCheckBox("Grab mouse in windowed mode", 
                                    &grabmouse),
+                   TXT_TABLE_OVERFLOW_RIGHT,
                    TXT_NewCheckBox("Double click acts as \"use\"",
                                    &dclick_use),
+                   TXT_TABLE_OVERFLOW_RIGHT,
 
                    TXT_NewSeparator("Mouse motion"),
-                   motion_table = TXT_NewTable(2),
-    
-                   TXT_NewSeparator("Buttons"),
-                   buttons_table = TXT_NewTable(2),
-                   TXT_NewButton2("More controls...",
-                                  ConfigExtraButtons,
-                                  NULL),
-                   NULL);
-
-    TXT_SetColumnWidths(motion_table, 27, 5);
-
-    TXT_AddWidgets(motion_table,
                    TXT_NewLabel("Speed"),
                    TXT_NewSpinControl(&mouseSensitivity, 1, 256),
                    TXT_NewLabel("Acceleration"),
@@ -146,15 +140,16 @@
                    TXT_NewFloatSpinControl(&mouse_acceleration, 1.0, 5.0),
                    TXT_NewLabel("Acceleration threshold"),
                    TXT_NewSpinControl(&mouse_threshold, 0, 32),
+
+                   TXT_NewSeparator("Buttons"),
                    NULL);
 
-    TXT_SetColumnWidths(buttons_table, 27, 5);
+    AddMouseControl(window, "Fire/Attack", &mousebfire);
+    AddMouseControl(window, "Move forward", &mousebforward);
+    AddMouseControl(window, "Strafe on", &mousebstrafe);
 
-    AddMouseControl(buttons_table, "Fire/Attack", &mousebfire);
-    AddMouseControl(buttons_table, "Move forward", &mousebforward);
-    AddMouseControl(buttons_table, "Strafe on", &mousebstrafe);
-    
-    TXT_SetWindowAction(window, TXT_HORIZ_CENTER, TestConfigAction());
+    TXT_AddWidget(window,
+                  TXT_NewButton2("More controls...", ConfigExtraButtons, NULL));
 }
 
 void BindMouseVariables(void)
--- a/src/setup/multiplayer.c
+++ b/src/setup/multiplayer.c
@@ -37,6 +37,7 @@
 #define MULTI_START_HELP_URL "http://www.chocolate-doom.org/setup-multi-start"
 #define MULTI_JOIN_HELP_URL "http://www.chocolate-doom.org/setup-multi-join"
 #define MULTI_CONFIG_HELP_URL "http://www.chocolate-doom.org/setup-multi-config"
+#define LEVEL_WARP_HELP_URL "http://www.chocolate-doom.org/setup-level-warp"
 
 #define NUM_WADS 10
 #define NUM_EXTRA_PARAMS 10
@@ -388,7 +389,6 @@
 static void LevelSelectDialog(TXT_UNCAST_ARG(widget), TXT_UNCAST_ARG(user_data))
 {
     txt_window_t *window;
-    txt_table_t *table;
     txt_button_t *button;
     const iwad_t *iwad;
     char buf[10];
@@ -403,7 +403,7 @@
     if (warptype == WARP_ExMy)
     {
         episodes = D_GetNumEpisodes(iwad->mission, iwad->mode);
-        table = TXT_NewTable(episodes);
+        TXT_SetTableColumns(window, episodes);
 
         // ExMy levels
 
@@ -418,7 +418,7 @@
 
                 if (!D_ValidEpisodeMap(iwad->mission, iwad->mode, x, y))
                 {
-                    TXT_AddWidget(table, NULL);
+                    TXT_AddWidget(window, NULL);
                     continue;
                 }
 
@@ -428,11 +428,11 @@
                                   SetExMyWarp, (void *) (x * 10 + y));
                 TXT_SignalConnect(button, "pressed",
                                   CloseLevelSelectDialog, window);
-                TXT_AddWidget(table, button);
+                TXT_AddWidget(window, button);
 
                 if (warpepisode == x && warpmap == y)
                 {
-                    TXT_SelectWidget(table, button);
+                    TXT_SelectWidget(window, button);
                 }
             }
         }
@@ -439,7 +439,7 @@
     }
     else
     {
-        table = TXT_NewTable(6);
+        TXT_SetTableColumns(window, 6);
 
         for (i=0; i<60; ++i)
         {
@@ -450,7 +450,7 @@
 
             if (!D_ValidEpisodeMap(iwad->mission, iwad->mode, 1, l))
             {
-                TXT_AddWidget(table, NULL);
+                TXT_AddWidget(window, NULL);
                 continue;
             }
 
@@ -460,16 +460,14 @@
                               SetMAPxyWarp, (void *) l);
             TXT_SignalConnect(button, "pressed",
                               CloseLevelSelectDialog, window);
-            TXT_AddWidget(table, button);
+            TXT_AddWidget(window, button);
 
             if (warpmap == l)
             {
-                TXT_SelectWidget(table, button);
+                TXT_SelectWidget(window, button);
             }
         }
     }
-
-    TXT_AddWidget(window, table);
 }
 
 static void IWADSelected(TXT_UNCAST_ARG(widget), TXT_UNCAST_ARG(unused))
@@ -710,37 +708,33 @@
 static void StartGameMenu(char *window_title, int multiplayer)
 {
     txt_window_t *window;
-    txt_table_t *gameopt_table;
-    txt_table_t *advanced_table;
     txt_widget_t *iwad_selector;
 
     window = TXT_NewWindow(window_title);
-    TXT_SetWindowHelpURL(window, MULTI_START_HELP_URL);
+    TXT_SetTableColumns(window, 2);
+    TXT_SetColumnWidths(window, 12, 6);
 
-    TXT_AddWidgets(window, 
-                   gameopt_table = TXT_NewTable(2),
-                   TXT_NewSeparator("Monster options"),
-                   TXT_NewInvertedCheckBox("Monsters enabled", &nomonsters),
-                   TXT_NewCheckBox("Fast monsters", &fast),
-                   TXT_NewCheckBox("Respawning monsters", &respawn),
-                   TXT_NewSeparator("Advanced"),
-                   advanced_table = TXT_NewTable(2),
-                   NULL);
+    if (multiplayer)
+    {
+        TXT_SetWindowHelpURL(window, MULTI_START_HELP_URL);
+    }
+    else
+    {
+        TXT_SetWindowHelpURL(window, LEVEL_WARP_HELP_URL);
+    }
 
     TXT_SetWindowAction(window, TXT_HORIZ_CENTER, WadWindowAction());
     TXT_SetWindowAction(window, TXT_HORIZ_RIGHT, StartGameAction(multiplayer));
 
-    TXT_SetColumnWidths(gameopt_table, 12, 6);
+    TXT_AddWidgets(window,
+                   TXT_NewLabel("Game"),
+                   iwad_selector = IWADSelector(),
+                   NULL);
 
-    TXT_AddWidgets(gameopt_table,
-           TXT_NewLabel("Game"),
-           iwad_selector = IWADSelector(),
-           NULL);
-
     if (gamemission == hexen)
     {
         txt_dropdown_list_t *cc_dropdown;
-        TXT_AddWidgets(gameopt_table,
+        TXT_AddWidgets(window,
                        TXT_NewLabel("Character class "),
                        cc_dropdown = TXT_NewDropdownList(&character_class,
                                                          character_classes, 3),
@@ -751,16 +745,16 @@
         TXT_SignalConnect(cc_dropdown, "changed", UpdateWarpType, NULL);
     }
 
-    TXT_AddWidgets(gameopt_table,
-           TXT_NewLabel("Skill"),
-           skillbutton = TXT_NewDropdownList(&skill, doom_skills, 5),
-           TXT_NewLabel("Level warp"),
-           warpbutton = TXT_NewButton2("????", LevelSelectDialog, NULL),
-           NULL);
+    TXT_AddWidgets(window,
+                   TXT_NewLabel("Skill"),
+                   skillbutton = TXT_NewDropdownList(&skill, doom_skills, 5),
+                   TXT_NewLabel("Level warp"),
+                   warpbutton = TXT_NewButton2("?", LevelSelectDialog, NULL),
+                   NULL);
 
     if (multiplayer)
     {
-        TXT_AddWidgets(gameopt_table,
+        TXT_AddWidgets(window,
                TXT_NewLabel("Game type"),
                GameTypeDropdown(),
                TXT_NewLabel("Time limit"),
@@ -768,23 +762,36 @@
                                TXT_NewLabel("minutes"),
                                NULL),
                NULL);
+    }
 
-        TXT_AddWidget(window,
-                      TXT_NewInvertedCheckBox("Register with master server",
-                                              &privateserver));
+    TXT_AddWidgets(window,
+                   TXT_NewSeparator("Monster options"),
+                   TXT_NewInvertedCheckBox("Monsters enabled", &nomonsters),
+                   TXT_TABLE_OVERFLOW_RIGHT,
+                   TXT_NewCheckBox("Fast monsters", &fast),
+                   TXT_TABLE_OVERFLOW_RIGHT,
+                   TXT_NewCheckBox("Respawning monsters", &respawn),
+                   TXT_TABLE_OVERFLOW_RIGHT,
+                   NULL);
 
-        TXT_AddWidgets(advanced_table,
+    if (multiplayer)
+    {
+        TXT_AddWidgets(window,
+                       TXT_NewSeparator("Advanced"),
                        TXT_NewLabel("UDP port"),
                        TXT_NewIntInputBox(&udpport, 5),
+                       TXT_NewInvertedCheckBox("Register with master server",
+                                               &privateserver),
+                       TXT_TABLE_OVERFLOW_RIGHT,
                        NULL);
     }
 
-    TXT_AddWidget(window,
-                  TXT_NewButton2("Add extra parameters...", 
-                                 OpenExtraParamsWindow, NULL));
+    TXT_AddWidgets(window,
+                   TXT_NewButton2("Add extra parameters...",
+                                  OpenExtraParamsWindow, NULL),
+                   TXT_TABLE_OVERFLOW_RIGHT,
+                   NULL);
 
-    TXT_SetColumnWidths(advanced_table, 12, 6);
-
     TXT_SignalConnect(iwad_selector, "changed", UpdateWarpType, NULL);
 
     UpdateWarpType(NULL, NULL);
@@ -985,24 +992,15 @@
 void JoinMultiGame(void)
 {
     txt_window_t *window;
-    txt_table_t *gameopt_table;
-    txt_table_t *serveropt_table;
     txt_inputbox_t *address_box;
 
     window = TXT_NewWindow("Join multiplayer game");
+    TXT_SetTableColumns(window, 2);
+    TXT_SetColumnWidths(window, 12, 12);
+
     TXT_SetWindowHelpURL(window, MULTI_JOIN_HELP_URL);
 
-    TXT_AddWidgets(window, 
-        gameopt_table = TXT_NewTable(2),
-        TXT_NewSeparator("Server"),
-        serveropt_table = TXT_NewTable(1),
-        TXT_NewStrut(0, 1),
-        TXT_NewButton2("Add extra parameters...", OpenExtraParamsWindow, NULL),
-        NULL);
-
-    TXT_SetColumnWidths(gameopt_table, 12, 12);
-
-    TXT_AddWidgets(gameopt_table,
+    TXT_AddWidgets(window,
                    TXT_NewLabel("Game"),
                    IWADSelector(),
                    NULL);
@@ -1009,7 +1007,7 @@
 
     if (gamemission == hexen)
     {
-        TXT_AddWidgets(gameopt_table,
+        TXT_AddWidgets(window,
                        TXT_NewLabel("Character class "),
                        TXT_NewDropdownList(&character_class,
                                            character_classes, 3),
@@ -1016,15 +1014,21 @@
                        NULL);
     }
 
-    TXT_AddWidgets(serveropt_table,
-                   TXT_NewHorizBox(
-                           TXT_NewLabel("Connect to address: "),
-                           address_box = TXT_NewInputBox(&connect_address, 30),
-                           NULL),
+    TXT_AddWidgets(window,
+                   TXT_NewSeparator("Server"),
+                   TXT_NewLabel("Connect to address: "),
+                   address_box = TXT_NewInputBox(&connect_address, 30),
+
                    TXT_NewButton2("Find server on Internet...",
                                   FindInternetServer, NULL),
+                   TXT_TABLE_OVERFLOW_RIGHT,
                    TXT_NewButton2("Find server on local network...",
                                   FindLANServer, NULL),
+                   TXT_TABLE_OVERFLOW_RIGHT,
+                   TXT_NewStrut(0, 1),
+                   TXT_TABLE_OVERFLOW_RIGHT,
+                   TXT_NewButton2("Add extra parameters...",
+                                  OpenExtraParamsWindow, NULL),
                    NULL);
 
     TXT_SelectWidget(window, address_box);
@@ -1101,7 +1105,7 @@
     window = TXT_NewWindow("Multiplayer Configuration");
     TXT_SetWindowHelpURL(window, MULTI_CONFIG_HELP_URL);
 
-    TXT_AddWidgets(window, 
+    TXT_AddWidgets(window,
                    TXT_NewStrut(0, 1),
                    TXT_NewHorizBox(TXT_NewLabel("Player name:  "),
                                    TXT_NewInputBox(&net_player_name, 25),
@@ -1124,7 +1128,7 @@
                        TXT_NewInputBox(&chat_macros[(i + 1) % 10], 40),
                        NULL);
     }
-    
+
     TXT_AddWidget(window, table);
 }
 
--- a/src/setup/setup-manifest.xml.in
+++ b/src/setup/setup-manifest.xml.in
@@ -32,6 +32,7 @@
       <supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}"/> <!-- Vista -->
       <supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/> <!-- 8 -->
       <supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/> <!-- 8.1 -->
+      <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/> <!-- 10 -->
     </application>
   </compatibility>
 
--- a/src/setup/setup.desktop.in
+++ b/src/setup/setup.desktop.in
@@ -4,4 +4,5 @@
 Icon=@PROGRAM_PREFIX@setup
 Type=Application
 Comment=Setup tool for @PACKAGE_SHORTNAME@
-Categories=Settings
+Categories=Settings;
+Keywords=first;person;shooter;doom;heretic;hexen;strife;vanilla;
--- a/src/setup/sound.c
+++ b/src/setup/sound.c
@@ -59,6 +59,13 @@
     "Native MIDI",
 };
 
+typedef enum
+{
+    OPLMODE_OPL2,
+    OPLMODE_OPL3,
+    NUM_OPLMODES,
+} oplmode_t;
+
 static char *opltype_strings[] =
 {
     "OPL2",
@@ -76,6 +83,8 @@
 int snd_cachesize = 64 * 1024 * 1024;
 int snd_maxslicetime_ms = 28;
 char *snd_musiccmd = "";
+int snd_pitchshift = 0;
+char *snd_dmxoption = "";
 
 static int numChannels = 8;
 static int sfxVolume = 8;
@@ -88,7 +97,6 @@
 static char *timidity_cfg_path = NULL;
 static char *gus_patch_path = NULL;
 static int gus_ram_kb = 1024;
-static int opl_type = 0;
 
 // DOS specific variables: these are unused but should be maintained
 // so that the config file can be shared between chocolate
@@ -103,6 +111,7 @@
 
 static int snd_sfxmode;
 static int snd_musicmode;
+static int snd_oplmode;
 
 static void UpdateSndDevices(TXT_UNCAST_ARG(widget), TXT_UNCAST_ARG(data))
 {
@@ -134,43 +143,80 @@
             snd_musicdevice = SNDDEVICE_GUS;
             break;
     }
+
+    switch (snd_oplmode)
+    {
+        default:
+        case OPLMODE_OPL2:
+            snd_dmxoption = "";
+            break;
+
+        case OPLMODE_OPL3:
+            snd_dmxoption = "-opl3";
+            break;
+    }
 }
 
+static txt_dropdown_list_t *OPLTypeSelector(void)
+{
+    txt_dropdown_list_t *result;
+
+    if (snd_dmxoption != NULL && strstr(snd_dmxoption, "-opl3") != NULL)
+    {
+        snd_oplmode = OPLMODE_OPL3;
+    }
+    else
+    {
+        snd_oplmode = OPLMODE_OPL2;
+    }
+
+    result = TXT_NewDropdownList(&snd_oplmode, opltype_strings, 2);
+
+    TXT_SignalConnect(result, "changed", UpdateSndDevices, NULL);
+
+    return result;
+}
+
 static void UpdateExtraTable(TXT_UNCAST_ARG(widget),
                              TXT_UNCAST_ARG(extra_table))
 {
     TXT_CAST_ARG(txt_table_t, extra_table);
 
+    TXT_ClearTable(extra_table);
+
     switch (snd_musicmode)
     {
-    case MUSICMODE_OPL:
-        TXT_InitTable(extra_table, 2);
-        TXT_SetColumnWidths(extra_table, 19, 4);
-        TXT_AddWidgets(extra_table,
-                        TXT_NewLabel("OPL type"),
-                        TXT_NewDropdownList(&opl_type, opltype_strings, 2),
-                        NULL);
-        break;
+        case MUSICMODE_OPL:
+            TXT_AddWidgets(extra_table,
+                           TXT_NewLabel("OPL type"),
+                           OPLTypeSelector(),
+                           NULL);
+            break;
 
-    case MUSICMODE_GUS:
-        TXT_InitTable(extra_table, 1);
-        TXT_AddWidgets(extra_table,
-                        TXT_NewLabel("GUS patch path:"),
-                        TXT_NewFileSelector(&gus_patch_path, 30,
-                                            "Select path to GUS patches",
-                                            TXT_DIRECTORY),
-                        NULL);
-        break;
+        case MUSICMODE_GUS:
+            TXT_AddWidgets(extra_table,
+                           TXT_NewLabel("GUS patch path:"),
+                           TXT_TABLE_OVERFLOW_RIGHT,
+                           TXT_NewFileSelector(&gus_patch_path, 34,
+                                               "Select path to GUS patches",
+                                               TXT_DIRECTORY),
+                           TXT_TABLE_OVERFLOW_RIGHT,
+                           NULL);
+            break;
 
-    case MUSICMODE_NATIVE:
-        TXT_InitTable(extra_table, 1);
-        TXT_AddWidgets(extra_table,
-                        TXT_NewLabel("Timidity configuration file:"),
-                        TXT_NewFileSelector(&timidity_cfg_path, 30,
-                                            "Select Timidity config file",
-                                            cfg_extension),
-                        NULL);
-        break;
+        case MUSICMODE_NATIVE:
+            TXT_AddWidgets(extra_table,
+                           TXT_NewLabel("Timidity configuration file:"),
+                           TXT_TABLE_OVERFLOW_RIGHT,
+                           TXT_NewFileSelector(&timidity_cfg_path, 34,
+                                               "Select Timidity config file",
+                                               cfg_extension),
+                           TXT_TABLE_OVERFLOW_RIGHT,
+                           NULL);
+            break;
+
+        default:
+            break;
     }
 }
 
@@ -177,8 +223,6 @@
 void ConfigSound(void)
 {
     txt_window_t *window;
-    txt_table_t *sfx_table;
-    txt_table_t *music_table;
     txt_table_t *extra_table;
     txt_dropdown_list_t *sfx_mode_control;
     txt_dropdown_list_t *music_mode_control;
@@ -233,20 +277,15 @@
     // Build the window
 
     window = TXT_NewWindow("Sound configuration");
-
     TXT_SetWindowHelpURL(window, WINDOW_HELP_URL);
+    TXT_SetTableColumns(window, 2);
+    TXT_SetColumnWidths(window, 19, 15);
 
     TXT_SetWindowPosition(window, TXT_HORIZ_CENTER, TXT_VERT_TOP,
                                   TXT_SCREEN_W / 2, 5);
 
     TXT_AddWidgets(window,
-               TXT_NewSeparator("Sound effects"),
-               sfx_table = TXT_NewTable(2),
-               NULL);
-
-    TXT_SetColumnWidths(sfx_table, 19, 15);
-
-    TXT_AddWidgets(sfx_table,
+                   TXT_NewSeparator("Sound effects"),
                    TXT_NewLabel("Sound effects"),
                    sfx_mode_control = TXT_NewDropdownList(&snd_sfxmode,
                                                           sfxmode_strings,
@@ -257,25 +296,28 @@
                    TXT_NewSpinControl(&sfxVolume, 0, 15),
                    NULL);
 
+    // Only show for games that implemented pitch shifting:
+    if (gamemission == doom || gamemission == heretic || gamemission == hexen)
+    {
+        TXT_AddWidgets(window,
+                       TXT_NewCheckBox("Pitch-shifted sounds",
+                                       &snd_pitchshift),
+                       TXT_TABLE_OVERFLOW_RIGHT,
+                       NULL);
+    }
+
     if (gamemission == strife)
     {
-        TXT_AddWidgets(sfx_table,
+        TXT_AddWidgets(window,
                        TXT_NewLabel("Voice volume"),
                        TXT_NewSpinControl(&voiceVolume, 0, 15),
+                       TXT_NewCheckBox("Show text with voices", &show_talk),
+                       TXT_TABLE_OVERFLOW_RIGHT,
                        NULL);
-        TXT_AddWidget(window,
-                      TXT_NewCheckBox("Show text with voices", &show_talk));
     }
 
     TXT_AddWidgets(window,
-               TXT_NewSeparator("Music"),
-               music_table = TXT_NewTable(2),
-               extra_table = TXT_NewTable(1),
-               NULL);
-
-    TXT_SetColumnWidths(music_table, 19, 15);
-
-    TXT_AddWidgets(music_table,
+                   TXT_NewSeparator("Music"),
                    TXT_NewLabel("Music"),
                    music_mode_control = TXT_NewDropdownList(&snd_musicmode,
                                                             musicmode_strings,
@@ -282,8 +324,11 @@
                                                             NUM_MUSICMODES),
                    TXT_NewLabel("Music volume"),
                    TXT_NewSpinControl(&musicVolume, 0, 15),
+                   extra_table = TXT_NewTable(2),
+                   TXT_TABLE_OVERFLOW_RIGHT,
                    NULL);
 
+    TXT_SetColumnWidths(extra_table, 19, 15);
 
     TXT_SignalConnect(sfx_mode_control, "changed", UpdateSndDevices, NULL);
     TXT_SignalConnect(music_mode_control, "changed", UpdateSndDevices, NULL);
@@ -316,11 +361,13 @@
     M_BindIntVariable("snd_mport",                &snd_mport);
     M_BindIntVariable("snd_maxslicetime_ms",      &snd_maxslicetime_ms);
     M_BindStringVariable("snd_musiccmd",          &snd_musiccmd);
+    M_BindStringVariable("snd_dmxoption",         &snd_dmxoption);
 
     M_BindIntVariable("snd_cachesize",            &snd_cachesize);
     M_BindIntVariable("opl_io_port",              &opl_io_port);
-    M_BindIntVariable("opl_type",                 &opl_type);
 
+    M_BindIntVariable("snd_pitchshift",           &snd_pitchshift);
+
     if (gamemission == strife)
     {
         M_BindIntVariable("voice_volume",         &voiceVolume);
@@ -329,6 +376,10 @@
 
     timidity_cfg_path = M_StringDuplicate("");
     gus_patch_path = M_StringDuplicate("");
+
+    // All versions of Heretic and Hexen did pitch-shifting.
+    // Most versions of Doom did not and Strife never did.
+    snd_pitchshift = gamemission == heretic || gamemission == hexen;
 
     // Default sound volumes - different games use different values.
 
--- a/src/setup/sound.h
+++ b/src/setup/sound.h
@@ -20,4 +20,6 @@
 void ConfigSound(void);
 void BindSoundVariables(void);
 
+extern char *snd_dmxoption;
+
 #endif /* #ifndef SETUP_SOUND_H */
--- a/src/setup/txt_joyaxis.c
+++ b/src/setup/txt_joyaxis.c
@@ -367,7 +367,7 @@
     joystick_axis->joystick = SDL_JoystickOpen(joystick_index);
     if (joystick_axis->joystick == NULL)
     {
-        // TODO: OpenErrorWindow();
+        TXT_MessageBox(NULL, "Please configure a controller first!");
         return;
     }
 
@@ -461,7 +461,7 @@
 
     TXT_DrawString(buf);
 
-    for (i=strlen(buf); i<JOYSTICK_AXIS_WIDTH; ++i)
+    for (i = strlen(buf); i < joystick_axis->widget.w; ++i)
     {
         TXT_DrawString(" ");
     }
--- a/src/setup/txt_joybinput.c
+++ b/src/setup/txt_joybinput.c
@@ -189,7 +189,7 @@
 
 static void OpenErrorWindow(void)
 {
-    TXT_MessageBox(NULL, "Please configure a joystick first!");
+    TXT_MessageBox(NULL, "Please configure a controller first!");
 }
 
 static void OpenPromptWindow(txt_joystick_input_t *joystick_input)
@@ -218,7 +218,7 @@
 
     // Open the prompt window
 
-    window = TXT_MessageBox(NULL, "Press the new joystick button...");
+    window = TXT_MessageBox(NULL, "Press the new button on the controller...");
 
     TXT_SDL_SetEventCallback(EventCallback, joystick_input);
     TXT_SignalConnect(window, "closed", PromptWindowClosed, joystick);
@@ -296,7 +296,8 @@
     return 0;
 }
 
-static void TXT_JoystickInputMousePress(TXT_UNCAST_ARG(widget), int x, int y, int b)
+static void TXT_JoystickInputMousePress(TXT_UNCAST_ARG(widget),
+                                        int x, int y, int b)
 {
     TXT_CAST_ARG(txt_joystick_input_t, widget);
 
--- /dev/null
+++ b/src/strife.appdata.xml.in
@@ -1,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<component type="desktop">
+  <id>@PROGRAM_PREFIX@strife.desktop</id>
+  <metadata_license>CC0-1.0</metadata_license>
+  <project_license>GPL-2.0+</project_license>
+  <developer_name>@PACKAGE_MAINTAINER@</developer_name>
+  <url type="homepage">@PACKAGE_URL@</url>
+  <url type="bugtracker">@PACKAGE_ISSUES@</url>
+  <description>
+    <p>
+      @PACKAGE_SHORTNAME@ Strife is a conservative,
+      historically-accurate recreation of the Strife engine.  It is
+      completely compatible with the original game and mods created
+      with the original engine in mind.  Made with a great reverse
+      engineering effort, it has the goal of preserving the original
+      look, feel, limitations, and bugs of the original DOS
+      executable.
+    </p>
+    <p>
+      Full support for single- and multi-player games is provided.
+      Unlike the original executable, network play is implemented on
+      the IP network stack, allowing it to function on modern LANs and
+      the Internet.
+    </p>
+  </description>
+  <screenshots>
+    <screenshot type="default">
+      <image>http://www.chocolate-doom.org/wiki/images/b/b2/GNOME_Strife_Rowan.png</image>
+      <caption>Talking to Rowan</caption>
+    </screenshot>
+    <screenshot>
+      <image>http://www.chocolate-doom.org/wiki/images/1/1f/GNOME_Strife_Town.png</image>
+      <caption>The Town</caption>
+    </screenshot>
+    <screenshot>
+      <image>http://www.chocolate-doom.org/wiki/images/8/8a/GNOME_Strife_Opening.png</image>
+      <caption>Opening Cinematic</caption>
+    </screenshot>
+    <screenshot>
+      <image>http://www.chocolate-doom.org/wiki/images/c/c4/GNOME_Strife_Sewage.png</image>
+      <caption>In the sewage</caption>
+    </screenshot>
+  </screenshots>
+</component>
--- a/src/strife/d_main.c
+++ b/src/strife/d_main.c
@@ -1629,7 +1629,7 @@
 
         if (D_AddFile (file))
         {
-            M_StringCopy(demolumpname, lumpinfo[numlumps - 1].name,
+            M_StringCopy(demolumpname, lumpinfo[numlumps - 1]->name,
                          sizeof(demolumpname));
         }
         else
--- a/src/strife/g_game.c
+++ b/src/strife/g_game.c
@@ -124,7 +124,6 @@
  
 int             consoleplayer;          // player taking events and displaying 
 int             displayplayer;          // view being displayed 
-int             gametic; 
 int             levelstarttic;          // gametic at level start 
 int             totalkills, /*totalitems,*/ totalsecret;    // for intermission 
  
--- a/src/strife/r_data.c
+++ b/src/strife/r_data.c
@@ -938,7 +938,7 @@
 	if (flatpresent[i])
 	{
 	    lump = firstflat + i;
-	    flatmemory += lumpinfo[lump].size;
+	    flatmemory += lumpinfo[lump]->size;
 	    W_CacheLumpNum(lump, PU_CACHE);
 	}
     }
@@ -975,7 +975,7 @@
 	for (j=0 ; j<texture->patchcount ; j++)
 	{
 	    lump = texture->patches[j].patch;
-	    texturememory += lumpinfo[lump].size;
+	    texturememory += lumpinfo[lump]->size;
 	    W_CacheLumpNum(lump , PU_CACHE);
 	}
     }
@@ -1004,7 +1004,7 @@
 	    for (k=0 ; k<8 ; k++)
 	    {
 		lump = firstspritelump + sf->lump[k];
-		spritememory += lumpinfo[lump].size;
+		spritememory += lumpinfo[lump]->size;
 		W_CacheLumpNum(lump , PU_CACHE);
 	    }
 	}
--- a/src/strife/r_things.c
+++ b/src/strife/r_things.c
@@ -211,22 +211,22 @@
 	//  filling in the frames for whatever is found
 	for (l=start+1 ; l<end ; l++)
 	{
-	    if (!strncasecmp(lumpinfo[l].name, spritename, 4))
+	    if (!strncasecmp(lumpinfo[l]->name, spritename, 4))
 	    {
-		frame = lumpinfo[l].name[4] - 'A';
-		rotation = lumpinfo[l].name[5] - '0';
+		frame = lumpinfo[l]->name[4] - 'A';
+		rotation = lumpinfo[l]->name[5] - '0';
 
 		if (modifiedgame)
-		    patched = W_GetNumForName (lumpinfo[l].name);
+		    patched = W_GetNumForName (lumpinfo[l]->name);
 		else
 		    patched = l;
 
 		R_InstallSpriteLump (patched, frame, rotation, false);
 
-		if (lumpinfo[l].name[6])
+		if (lumpinfo[l]->name[6])
 		{
-		    frame = lumpinfo[l].name[6] - 'A';
-		    rotation = lumpinfo[l].name[7] - '0';
+		    frame = lumpinfo[l]->name[6] - 'A';
+		    rotation = lumpinfo[l]->name[7] - '0';
 		    R_InstallSpriteLump (l, frame, rotation, true);
 		}
 	    }
--- a/src/strife/s_sound.c
+++ b/src/strife/s_sound.c
@@ -60,7 +60,6 @@
 
 #define S_STEREO_SWING (96 * FRACUNIT)
 
-#define NORM_PITCH 128
 #define NORM_PRIORITY 64
 #define NORM_SEP 128
 
@@ -74,6 +73,8 @@
 
     // handle of the sound being played
     int handle;
+
+    int pitch;
     
 } channel_t;
 
@@ -137,7 +138,7 @@
 {  
     int i;
 
-    I_SetOPLDriverVer(opl_v_new);
+    I_SetOPLDriverVer(opl_doom_1_9);
     I_PrecacheSounds(S_sfx, NUMSFX);
 
     S_SetSfxVolume(sfxVolume);
@@ -408,6 +409,7 @@
     mobj_t *origin;
     int rc;
     int sep;
+    int pitch;
     int cnum;
     int volume;
 
@@ -454,7 +456,7 @@
 
         if (origin->x == players[consoleplayer].mo->x
          && origin->y == players[consoleplayer].mo->y)
-        {        
+        {
             sep = NORM_SEP;
         }
 
@@ -462,11 +464,12 @@
         {
             return;
         }
-    }        
+    }
     else
     {
         sep = NORM_SEP;
     }
+    pitch = NORM_PITCH;
 
     // kill old sound [STRIFE] - nope!
     //S_StopSound(origin);
@@ -490,7 +493,7 @@
         sfx->lumpnum = I_GetSfxLumpNum(sfx);
     }
 
-    channels[cnum].handle = I_StartSound(sfx, cnum, volume, sep);
+    channels[cnum].handle = I_StartSound(sfx, cnum, volume, sep, pitch);
 }
 
 
@@ -615,9 +618,9 @@
 
         // get a channel for the voice
         i_voicehandle = S_GetChannel(NULL, &voice->sfx, true);
-        
+
         channels[i_voicehandle].handle 
-            = I_StartSound(&voice->sfx, i_voicehandle, snd_VoiceVolume, NORM_SEP);
+            = I_StartSound(&voice->sfx, i_voicehandle, snd_VoiceVolume, NORM_SEP, NORM_PITCH);
     }
 }
 
@@ -693,7 +696,7 @@
                                                   c->origin,
                                                   &volume,
                                                   &sep);
-                    
+
                     if (!audible)
                     {
                         S_StopChannel(cnum);
--- a/src/v_video.c
+++ b/src/v_video.c
@@ -719,7 +719,7 @@
 }
 
 void WritePNGfile(char *filename, byte *data,
-                  int width, int height,
+                  int inwidth, int inheight,
                   byte *palette)
 {
     png_structp ppng;
@@ -726,8 +726,14 @@
     png_infop pinfo;
     png_colorp pcolor;
     FILE *handle;
-    int i;
+    int i, j;
+    int width, height;
+    byte *rowbuf;
 
+    // scale up to accommodate aspect ratio correction
+    width = inwidth * 5;
+    height = inheight * 6;
+
     handle = fopen(filename, "wb");
     if (!handle)
     {
@@ -773,9 +779,26 @@
 
     png_write_info(ppng, pinfo);
 
-    for (i = 0; i < SCREENHEIGHT; i++)
+    rowbuf = malloc(width);
+
+    if (rowbuf)
     {
-        png_write_row(ppng, data + i*SCREENWIDTH);
+        for (i = 0; i < SCREENHEIGHT; i++)
+        {
+            // expand the row 5x
+            for (j = 0; j < SCREENWIDTH; j++)
+            {
+                memset(rowbuf + j * 5, *(data + i*SCREENWIDTH + j), 5);
+            }
+
+            // write the row 6 times
+            for (j = 0; j < 6; j++)
+            {
+                png_write_row(ppng, rowbuf);
+            }
+        }
+
+        free(rowbuf);
     }
 
     png_write_end(ppng, pinfo);
--- a/src/w_checksum.c
+++ b/src/w_checksum.c
@@ -33,7 +33,7 @@
     int i;
     int result;
 
-    for (i=0; i<num_open_wadfiles; ++i)
+    for (i = 0; i < num_open_wadfiles; ++i)
     {
         if (open_wadfiles[i] == handle)
         {
@@ -77,11 +77,11 @@
     // Go through each entry in the WAD directory, adding information
     // about each entry to the SHA1 hash.
 
-    for (i=0; i<numlumps; ++i)
+    for (i = 0; i < numlumps; ++i)
     {
-        ChecksumAddLump(&sha1_context, &lumpinfo[i]);
+        ChecksumAddLump(&sha1_context, lumpinfo[i]);
     }
-    
+
     SHA1_Final(digest, &sha1_context);
 }
 
--- a/src/w_main.c
+++ b/src/w_main.c
@@ -16,8 +16,10 @@
 //     Common code to parse command line, identifying WAD files to load.
 //
 
+#include "config.h"
 #include "doomfeatures.h"
 #include "d_iwad.h"
+#include "i_system.h"
 #include "m_argv.h"
 #include "w_main.h"
 #include "w_merge.h"
@@ -26,7 +28,6 @@
 
 // Parse the command line, merging WAD files that are sppecified.
 // Returns true if at least one file was added.
-
 boolean W_ParseCommandLine(void)
 {
     boolean modifiedgame = false;
@@ -194,5 +195,46 @@
 //    W_PrintDirectory();
 
     return modifiedgame;
+}
+
+// Lump names that are unique to particular game types. This lets us check
+// the user is not trying to play with the wrong executable, eg.
+// chocolate-doom -iwad hexen.wad.
+static const struct
+{
+    GameMission_t mission;
+    char *lumpname;
+} unique_lumps[] = {
+    { doom,    "POSSA1" },
+    { heretic, "IMPXA1" },
+    { hexen,   "ETTNA1" },
+    { strife,  "AGRDA1" },
+};
+
+void W_CheckCorrectIWAD(GameMission_t mission)
+{
+    int i;
+    lumpindex_t lumpnum;
+
+    for (i = 0; i < arrlen(unique_lumps); ++i)
+    {
+        if (mission != unique_lumps[i].mission)
+        {
+            lumpnum = W_CheckNumForName(unique_lumps[i].lumpname);
+
+            if (lumpnum >= 0)
+            {
+                I_Error("\nYou are trying to use a %s IWAD file with "
+                        "the %s%s binary.\nThis isn't going to work.\n"
+                        "You probably want to use the %s%s binary.",
+                        D_SuggestGameName(unique_lumps[i].mission,
+                                          indetermined),
+                        PROGRAM_PREFIX,
+                        D_GameMissionString(mission),
+                        PROGRAM_PREFIX,
+                        D_GameMissionString(unique_lumps[i].mission));
+            }
+        }
+    }
 }
 
--- a/src/w_main.h
+++ b/src/w_main.h
@@ -18,7 +18,10 @@
 #ifndef W_MAIN_H
 #define W_MAIN_H
 
+#include "d_mode.h"
+
 boolean W_ParseCommandLine(void);
+void W_CheckCorrectIWAD(GameMission_t mission);
 
 #endif /* #ifndef W_MAIN_H */
 
--- a/src/w_merge.c
+++ b/src/w_merge.c
@@ -39,7 +39,7 @@
 
 typedef struct
 {
-    lumpinfo_t *lumps;
+    lumpinfo_t **lumps;
     int numlumps;
 } searchlist_t;
 
@@ -74,7 +74,7 @@
 
     for (i=0; i<list->numlumps; ++i)
     {
-        if (!strncasecmp(list->lumps[i].name, name, 8))
+        if (!strncasecmp(list->lumps[i]->name, name, 8))
             return i;
     }
 
@@ -352,7 +352,7 @@
     
     for (i=0; i<iwad_sprites.numlumps; ++i)
     {
-        AddSpriteLump(&iwad_sprites.lumps[i]);
+        AddSpriteLump(iwad_sprites.lumps[i]);
     }
     
     // Add all sprites from the PWAD
@@ -360,7 +360,7 @@
 
     for (i=0; i<pwad_sprites.numlumps; ++i)
     {
-        AddSpriteLump(&pwad_sprites.lumps[i]);
+        AddSpriteLump(pwad_sprites.lumps[i]);
     }
 }
 
@@ -386,13 +386,13 @@
 static void DoMerge(void)
 {
     section_t current_section;
-    lumpinfo_t *newlumps;
+    lumpinfo_t **newlumps;
     int num_newlumps;
     int lumpindex;
     int i, n;
-    
+
     // Can't ever have more lumps than we already have
-    newlumps = malloc(sizeof(lumpinfo_t) * numlumps);
+    newlumps = calloc(numlumps, sizeof(lumpinfo_t *));
     num_newlumps = 0;
 
     // Add IWAD lumps
@@ -400,7 +400,7 @@
 
     for (i=0; i<iwad.numlumps; ++i)
     {
-        lumpinfo_t *lump = &iwad.lumps[i];
+        lumpinfo_t *lump = iwad.lumps[i];
 
         switch (current_section)
         {
@@ -414,7 +414,7 @@
                     current_section = SECTION_SPRITES;
                 }
 
-                newlumps[num_newlumps++] = *lump;
+                newlumps[num_newlumps++] = lump;
 
                 break;
 
@@ -432,7 +432,7 @@
                         newlumps[num_newlumps++] = pwad_flats.lumps[n];
                     }
 
-                    newlumps[num_newlumps++] = *lump;
+                    newlumps[num_newlumps++] = lump;
 
                     // back to normal reading
                     current_section = SECTION_NORMAL;
@@ -448,7 +448,7 @@
 
                     if (lumpindex < 0)
                     {
-                        newlumps[num_newlumps++] = *lump;
+                        newlumps[num_newlumps++] = lump;
                     }
                 }
 
@@ -460,11 +460,11 @@
 
                 if (!strncasecmp(lump->name, "S_END", 8))
                 {
-                    // add all the pwad sprites
+                    // add all the PWAD sprites
 
                     for (n=0; n<pwad_sprites.numlumps; ++n)
                     {
-                        if (SpriteLumpNeeded(&pwad_sprites.lumps[n]))
+                        if (SpriteLumpNeeded(pwad_sprites.lumps[n]))
                         {
                             newlumps[num_newlumps++] = pwad_sprites.lumps[n];
                         }
@@ -471,7 +471,7 @@
                     }
 
                     // copy the ending
-                    newlumps[num_newlumps++] = *lump;
+                    newlumps[num_newlumps++] = lump;
 
                     // back to normal reading
                     current_section = SECTION_NORMAL;
@@ -483,7 +483,7 @@
 
                     if (SpriteLumpNeeded(lump))
                     {
-                        newlumps[num_newlumps++] = *lump;
+                        newlumps[num_newlumps++] = lump;
                     }
                 }
 
@@ -496,7 +496,7 @@
 
     for (i=0; i<pwad.numlumps; ++i)
     {
-        lumpinfo_t *lump = &pwad.lumps[i];
+        lumpinfo_t *lump = pwad.lumps[i];
 
         switch (current_section)
         {
@@ -515,7 +515,7 @@
                 {
                     // Don't include the headers of sections
        
-                    newlumps[num_newlumps++] = *lump;
+                    newlumps[num_newlumps++] = lump;
                 }
                 break;
 
@@ -550,7 +550,6 @@
     free(lumpinfo);
     lumpinfo = newlumps;
     numlumps = num_newlumps;
-
 }
 
 void W_PrintDirectory(void)
@@ -560,8 +559,8 @@
     // debug
     for (i=0; i<numlumps; ++i)
     {
-        for (n=0; n<8 && lumpinfo[i].name[n] != '\0'; ++n)
-            putchar(lumpinfo[i].name[n]);
+        for (n=0; n<8 && lumpinfo[i]->name[n] != '\0'; ++n)
+            putchar(lumpinfo[i]->name[n]);
         putchar('\n');
     }
 }
@@ -579,7 +578,7 @@
     if (W_AddFile(filename) == NULL)
         return;
 
-    // iwad is at the start, pwad was appended to the end
+    // IWAD is at the start, PWAD was appended to the end
 
     iwad.lumps = lumpinfo;
     iwad.numlumps = old_numlumps;
@@ -606,25 +605,23 @@
 {
     int i;
 
-    // Go through the IWAD list given, replacing lumps with lumps of 
+    // Go through the IWAD list given, replacing lumps with lumps of
     // the same name from the PWAD
-
     for (i=0; i<list->numlumps; ++i)
     {
         int index;
 
-        index = FindInList(&pwad, list->lumps[i].name);
+        index = FindInList(&pwad, list->lumps[i]->name);
 
         if (index > 0)
         {
-            memcpy(&list->lumps[i], &pwad.lumps[index], 
+            memcpy(list->lumps[i], pwad.lumps[index],
                    sizeof(lumpinfo_t));
         }
     }
-    
 }
 
-// Merge sprites and flats in the way NWT does with its -af and -as 
+// Merge sprites and flats in the way NWT does with its -af and -as
 // command-line options.
 
 void W_NWTMergeFile(char *filename, int flags)
@@ -638,7 +635,7 @@
     if (W_AddFile(filename) == NULL)
         return;
 
-    // iwad is at the start, pwad was appended to the end
+    // IWAD is at the start, PWAD was appended to the end
 
     iwad.lumps = lumpinfo;
     iwad.numlumps = old_numlumps;
@@ -645,13 +642,13 @@
 
     pwad.lumps = lumpinfo + old_numlumps;
     pwad.numlumps = numlumps - old_numlumps;
-    
+
     // Setup sprite/flat lists
 
     SetupLists();
 
     // Merge in flats?
-    
+
     if (flags & W_NWT_MERGE_FLATS)
     {
         W_NWTAddLumps(&iwad_flats);
@@ -690,7 +687,7 @@
         return;
     }
 
-    // iwad is at the start, pwad was appended to the end
+    // IWAD is at the start, PWAD was appended to the end
 
     iwad.lumps = lumpinfo;
     iwad.numlumps = old_numlumps;
@@ -697,7 +694,7 @@
 
     pwad.lumps = lumpinfo + old_numlumps;
     pwad.numlumps = numlumps - old_numlumps;
-    
+
     // Setup sprite/flat lists
 
     SetupLists();
@@ -706,12 +703,12 @@
 
     for (i=0; i<iwad_sprites.numlumps; ++i)
     {
-        if (FindInList(&pwad, iwad_sprites.lumps[i].name) >= 0)
+        if (FindInList(&pwad, iwad_sprites.lumps[i]->name) >= 0)
         {
             // Replace this entry with an empty string.  This is what
             // nwt -merge does.
 
-            M_StringCopy(iwad_sprites.lumps[i].name, "", 8);
+            M_StringCopy(iwad_sprites.lumps[i]->name, "", 8);
         }
     }
 
--- a/src/w_wad.c
+++ b/src/w_wad.c
@@ -26,8 +26,6 @@
 
 #include "doomtype.h"
 
-#include "config.h"
-#include "d_iwad.h"
 #include "i_swap.h"
 #include "i_system.h"
 #include "i_video.h"
@@ -57,16 +55,17 @@
 //
 
 // Location of each lump on disk.
-lumpinfo_t *lumpinfo;
+lumpinfo_t **lumpinfo;
 unsigned int numlumps = 0;
 
 // Hash table for fast lookups
-static lumpinfo_t **lumphash;
+static lumpindex_t *lumphash;
 
 // Variables for the reload hack: filename of the PWAD to reload, and the
 // lumps from WADs before the reload file, so we can resent numlumps and
 // load the file again.
 static wad_file_t *reloadhandle = NULL;
+static lumpinfo_t *reloadlumps = NULL;
 static char *reloadname = NULL;
 static int reloadlump = -1;
 
@@ -87,46 +86,6 @@
     return result;
 }
 
-// Increase the size of the lumpinfo[] array to the specified size.
-static void ExtendLumpInfo(int newnumlumps)
-{
-    lumpinfo_t *newlumpinfo;
-    unsigned int i;
-
-    newlumpinfo = calloc(newnumlumps, sizeof(lumpinfo_t));
-
-    if (newlumpinfo == NULL)
-    {
-	I_Error ("Couldn't realloc lumpinfo");
-    }
-
-    // Copy over lumpinfo_t structures from the old array. If any of
-    // these lumps have been cached, we need to update the user
-    // pointers to the new location.
-    for (i = 0; i < numlumps && i < newnumlumps; ++i)
-    {
-        memcpy(&newlumpinfo[i], &lumpinfo[i], sizeof(lumpinfo_t));
-
-        if (newlumpinfo[i].cache != NULL)
-        {
-            Z_ChangeUser(newlumpinfo[i].cache, &newlumpinfo[i].cache);
-        }
-
-        // We shouldn't be generating a hash table until after all WADs have
-        // been loaded, but just in case...
-        if (lumpinfo[i].next != NULL)
-        {
-            int nextlumpnum = lumpinfo[i].next - lumpinfo;
-            newlumpinfo[i].next = &newlumpinfo[nextlumpnum];
-        }
-    }
-
-    // All done.
-    free(lumpinfo);
-    lumpinfo = newlumpinfo;
-    numlumps = newnumlumps;
-}
-
 //
 // LUMP BASED ROUTINES.
 //
@@ -143,14 +102,14 @@
 wad_file_t *W_AddFile (char *filename)
 {
     wadinfo_t header;
-    lumpinfo_t *lump_p;
-    unsigned int i;
+    lumpindex_t i;
     wad_file_t *wad_file;
     int length;
     int startlump;
     filelump_t *fileinfo;
     filelump_t *filerover;
-    int newnumlumps;
+    lumpinfo_t *filelumps;
+    int numfilelumps;
 
     // If the filename begins with a ~, it indicates that we should use the
     // reload hack.
@@ -180,15 +139,6 @@
 	return NULL;
     }
 
-    // If this is the reload file, we need to save the file handle so that we
-    // can close it later on when we do a reload.
-    if (reloadname)
-    {
-        reloadhandle = wad_file;
-    }
-
-    newnumlumps = numlumps;
-
     if (strcasecmp(filename+strlen(filename)-3 , "wad" ) )
     {
 	// single lump file
@@ -206,9 +156,9 @@
         // extension).
 
 	M_ExtractFileBase (filename, fileinfo->name);
-	newnumlumps++;
+	numfilelumps = 1;
     }
-    else 
+    else
     {
 	// WAD file
         W_Read(wad_file, 0, &header, sizeof(header));
@@ -221,8 +171,8 @@
 		I_Error ("Wad file %s doesn't have IWAD "
 			 "or PWAD id\n", filename);
 	    }
-	    
-	    // ???modifiedgame = true;		
+
+	    // ???modifiedgame = true;
 	}
 
 	header.numlumps = LONG(header.numlumps);
@@ -231,26 +181,36 @@
 	fileinfo = Z_Malloc(length, PU_STATIC, 0);
 
         W_Read(wad_file, header.infotableofs, fileinfo, length);
-	newnumlumps += header.numlumps;
+	numfilelumps = header.numlumps;
     }
 
     // Increase size of numlumps array to accomodate the new file.
+    filelumps = calloc(numfilelumps, sizeof(lumpinfo_t));
+    if (filelumps == NULL)
+    {
+        I_Error("Failed to allocate array for lumps from new file.");
+    }
+
     startlump = numlumps;
-    ExtendLumpInfo(newnumlumps);
+    numlumps += numfilelumps;
+    lumpinfo = realloc(lumpinfo, numlumps * sizeof(lumpinfo_t *));
+    if (lumpinfo == NULL)
+    {
+        I_Error("Failed to increase lumpinfo[] array size.");
+    }
 
-    lump_p = &lumpinfo[startlump];
-
     filerover = fileinfo;
 
-    for (i=startlump; i<numlumps; ++i)
+    for (i = startlump; i < numlumps; ++i)
     {
-	lump_p->wad_file = wad_file;
-	lump_p->position = LONG(filerover->filepos);
-	lump_p->size = LONG(filerover->size);
+        lumpinfo_t *lump_p = &filelumps[i - startlump];
+        lump_p->wad_file = wad_file;
+        lump_p->position = LONG(filerover->filepos);
+        lump_p->size = LONG(filerover->size);
         lump_p->cache = NULL;
-	strncpy(lump_p->name, filerover->name, 8);
+        strncpy(lump_p->name, filerover->name, 8);
+        lumpinfo[i] = lump_p;
 
-        ++lump_p;
         ++filerover;
     }
 
@@ -262,6 +222,14 @@
         lumphash = NULL;
     }
 
+    // If this is the reload file, we need to save some details about the
+    // file so that we can close it later on when we do a reload.
+    if (reloadname)
+    {
+        reloadhandle = wad_file;
+        reloadlumps = filelumps;
+    }
+
     return wad_file;
 }
 
@@ -282,10 +250,9 @@
 // Returns -1 if name not found.
 //
 
-int W_CheckNumForName (char* name)
+lumpindex_t W_CheckNumForName(char* name)
 {
-    lumpinfo_t *lump_p;
-    int i;
+    lumpindex_t i;
 
     // Do we have a hash table yet?
 
@@ -292,28 +259,28 @@
     if (lumphash != NULL)
     {
         int hash;
-        
+
         // We do! Excellent.
 
         hash = W_LumpNameHash(name) % numlumps;
-        
-        for (lump_p = lumphash[hash]; lump_p != NULL; lump_p = lump_p->next)
+
+        for (i = lumphash[hash]; i != -1; i = lumpinfo[i]->next)
         {
-            if (!strncasecmp(lump_p->name, name, 8))
+            if (!strncasecmp(lumpinfo[i]->name, name, 8))
             {
-                return lump_p - lumpinfo;
+                return i;
             }
         }
-    } 
+    }
     else
     {
         // We don't have a hash table generate yet. Linear search :-(
-        // 
+        //
         // scan backwards so patch lump files take precedence
 
-        for (i=numlumps-1; i >= 0; --i)
+        for (i = numlumps - 1; i >= 0; --i)
         {
-            if (!strncasecmp(lumpinfo[i].name, name, 8))
+            if (!strncasecmp(lumpinfo[i]->name, name, 8))
             {
                 return i;
             }
@@ -332,12 +299,12 @@
 // W_GetNumForName
 // Calls W_CheckNumForName, but bombs out if not found.
 //
-int W_GetNumForName (char* name)
+lumpindex_t W_GetNumForName(char* name)
 {
-    int	i;
+    lumpindex_t i;
 
     i = W_CheckNumForName (name);
-    
+
     if (i < 0)
     {
         I_Error ("W_GetNumForName: %s not found!", name);
@@ -351,7 +318,7 @@
 // W_LumpLength
 // Returns the buffer size needed to load the given lump.
 //
-int W_LumpLength (unsigned int lump)
+int W_LumpLength(lumpindex_t lump)
 {
     if (lump >= numlumps)
     {
@@ -358,7 +325,7 @@
 	I_Error ("W_LumpLength: %i >= numlumps", lump);
     }
 
-    return lumpinfo[lump].size;
+    return lumpinfo[lump]->size;
 }
 
 
@@ -368,29 +335,29 @@
 // Loads the lump into the given buffer,
 //  which must be >= W_LumpLength().
 //
-void W_ReadLump(unsigned int lump, void *dest)
+void W_ReadLump(lumpindex_t lump, void *dest)
 {
     int c;
     lumpinfo_t *l;
-	
+
     if (lump >= numlumps)
     {
-	I_Error ("W_ReadLump: %i >= numlumps", lump);
+        I_Error ("W_ReadLump: %i >= numlumps", lump);
     }
 
-    l = lumpinfo+lump;
-	
-    I_BeginRead ();
-	
+    l = lumpinfo[lump];
+
+    I_BeginRead();
+
     c = W_Read(l->wad_file, l->position, dest, l->size);
 
     if (c < l->size)
     {
-	I_Error ("W_ReadLump: only read %i of %i on lump %i",
-		 c, l->size, lump);	
+        I_Error("W_ReadLump: only read %i of %i on lump %i",
+                c, l->size, lump);
     }
 
-    I_EndRead ();
+    I_EndRead();
 }
 
 
@@ -408,7 +375,7 @@
 // when no longer needed (do not use Z_ChangeTag).
 //
 
-void *W_CacheLumpNum(int lumpnum, int tag)
+void *W_CacheLumpNum(lumpindex_t lumpnum, int tag)
 {
     byte *result;
     lumpinfo_t *lump;
@@ -418,7 +385,7 @@
 	I_Error ("W_CacheLumpNum: %i >= numlumps", lumpnum);
     }
 
-    lump = &lumpinfo[lumpnum];
+    lump = lumpinfo[lumpnum];
 
     // Get the pointer to return.  If the lump is in a memory-mapped
     // file, we can just return a pointer to within the memory-mapped
@@ -470,7 +437,7 @@
 // complicated ...
 //
 
-void W_ReleaseLumpNum(int lumpnum)
+void W_ReleaseLumpNum(lumpindex_t lumpnum)
 {
     lumpinfo_t *lump;
 
@@ -479,7 +446,7 @@
 	I_Error ("W_ReleaseLumpNum: %i >= numlumps", lumpnum);
     }
 
-    lump = &lumpinfo[lumpnum];
+    lump = lumpinfo[lumpnum];
 
     if (lump->wad_file->mapped != NULL)
     {
@@ -566,7 +533,7 @@
 
 void W_GenerateHashTable(void)
 {
-    unsigned int i;
+    lumpindex_t i;
 
     // Free the old hash table, if there is one:
     if (lumphash != NULL)
@@ -577,19 +544,23 @@
     // Generate hash table
     if (numlumps > 0)
     {
-        lumphash = Z_Malloc(sizeof(lumpinfo_t *) * numlumps, PU_STATIC, NULL);
-        memset(lumphash, 0, sizeof(lumpinfo_t *) * numlumps);
+        lumphash = Z_Malloc(sizeof(lumpindex_t) * numlumps, PU_STATIC, NULL);
 
-        for (i=0; i<numlumps; ++i)
+        for (i = 0; i < numlumps; ++i)
         {
+            lumphash[i] = -1;
+        }
+
+        for (i = 0; i < numlumps; ++i)
+        {
             unsigned int hash;
 
-            hash = W_LumpNameHash(lumpinfo[i].name) % numlumps;
+            hash = W_LumpNameHash(lumpinfo[i]->name) % numlumps;
 
             // Hook into the hash table
 
-            lumpinfo[i].next = lumphash[hash];
-            lumphash[hash] = &lumpinfo[i];
+            lumpinfo[i]->next = lumphash[hash];
+            lumphash[hash] = i;
         }
     }
 
@@ -605,7 +576,7 @@
 void W_Reload(void)
 {
     char *filename;
-    int i;
+    lumpindex_t i;
 
     if (reloadname == NULL)
     {
@@ -612,12 +583,12 @@
         return;
     }
 
-    // We must release any lumps being held in the PWAD we're about to reload:
+    // We must free any lumps being cached from the PWAD we're about to reload:
     for (i = reloadlump; i < numlumps; ++i)
     {
-        if (lumpinfo[i].cache != NULL)
+        if (lumpinfo[i]->cache != NULL)
         {
-            W_ReleaseLumpNum(i);
+            Z_Free(lumpinfo[i]->cache);
         }
     }
 
@@ -628,6 +599,8 @@
     filename = reloadname;
 
     W_CloseFile(reloadhandle);
+    free(reloadlumps);
+
     reloadname = NULL;
     reloadlump = -1;
     reloadhandle = NULL;
@@ -637,46 +610,5 @@
     // The WAD directory has changed, so we have to regenerate the
     // fast lookup hashtable:
     W_GenerateHashTable();
-}
-
-// Lump names that are unique to particular game types. This lets us check
-// the user is not trying to play with the wrong executable, eg.
-// chocolate-doom -iwad hexen.wad.
-static const struct
-{
-    GameMission_t mission;
-    char *lumpname;
-} unique_lumps[] = {
-    { doom,    "POSSA1" },
-    { heretic, "IMPXA1" },
-    { hexen,   "ETTNA1" },
-    { strife,  "AGRDA1" },
-};
-
-void W_CheckCorrectIWAD(GameMission_t mission)
-{
-    int i;
-    int lumpnum;
-
-    for (i = 0; i < arrlen(unique_lumps); ++i)
-    {
-        if (mission != unique_lumps[i].mission)
-        {
-            lumpnum = W_CheckNumForName(unique_lumps[i].lumpname);
-
-            if (lumpnum >= 0)
-            {
-                I_Error("\nYou are trying to use a %s IWAD file with "
-                        "the %s%s binary.\nThis isn't going to work.\n"
-                        "You probably want to use the %s%s binary.",
-                        D_SuggestGameName(unique_lumps[i].mission,
-                                          indetermined),
-                        PROGRAM_PREFIX,
-                        D_GameMissionString(mission),
-                        PROGRAM_PREFIX,
-                        D_GameMissionString(unique_lumps[i].mission));
-            }
-        }
-    }
 }
 
--- a/src/w_wad.h
+++ b/src/w_wad.h
@@ -23,8 +23,6 @@
 #include <stdio.h>
 
 #include "doomtype.h"
-#include "d_mode.h"
-
 #include "w_file.h"
 
 
@@ -37,6 +35,7 @@
 //
 
 typedef struct lumpinfo_s lumpinfo_t;
+typedef int lumpindex_t;
 
 struct lumpinfo_s
 {
@@ -47,33 +46,30 @@
     void       *cache;
 
     // Used for hash table lookups
-
-    lumpinfo_t *next;
+    lumpindex_t next;
 };
 
 
-extern lumpinfo_t *lumpinfo;
+extern lumpinfo_t **lumpinfo;
 extern unsigned int numlumps;
 
-wad_file_t *W_AddFile (char *filename);
-void    W_Reload (void);
+wad_file_t *W_AddFile(char *filename);
+void W_Reload(void);
 
-int	W_CheckNumForName (char* name);
-int	W_GetNumForName (char* name);
+lumpindex_t W_CheckNumForName(char *name);
+lumpindex_t W_GetNumForName(char *name);
 
-int	W_LumpLength (unsigned int lump);
-void    W_ReadLump (unsigned int lump, void *dest);
+int W_LumpLength(lumpindex_t lump);
+void W_ReadLump(lumpindex_t lump, void *dest);
 
-void*	W_CacheLumpNum (int lump, int tag);
-void*	W_CacheLumpName (char* name, int tag);
+void *W_CacheLumpNum(lumpindex_t lump, int tag);
+void *W_CacheLumpName(char *name, int tag);
 
-void    W_GenerateHashTable(void);
+void W_GenerateHashTable(void);
 
 extern unsigned int W_LumpNameHash(const char *s);
 
-void    W_ReleaseLumpNum(int lump);
-void    W_ReleaseLumpName(char *name);
-
-void W_CheckCorrectIWAD(GameMission_t mission);
+void W_ReleaseLumpNum(lumpindex_t lump);
+void W_ReleaseLumpName(char *name);
 
 #endif
--- a/textscreen/examples/guitest.c
+++ b/textscreen/examples/guitest.c
@@ -103,7 +103,7 @@
 
     toplabel = TXT_NewLabel("This is a multiline label.\n"
                             "A single label object contains \n"
-                            "all three of these lines.\n");
+                            "all three of these lines.");
     TXT_AddWidget(window, toplabel);
     TXT_SetWidgetAlign(toplabel, TXT_HORIZ_CENTER);
 
@@ -110,7 +110,7 @@
     //TXT_AddWidget(window, TXT_NewScrollPane(15, 4, table));
     TXT_AddWidget(window, table);
 
-    for (i=0; i<5; ++i)
+    for (i=0; i<3; ++i)
     {
         TXT_snprintf(buf, sizeof(buf), "Option %i in a table:", i + 1);
         TXT_AddWidget(table, TXT_NewLabel(buf));
@@ -119,6 +119,17 @@
         TXT_snprintf(buf, sizeof(buf), " Button %i-2 ", i + 1);
         TXT_AddWidget(table, TXT_NewButton(buf));
     }
+
+    TXT_AddWidgets(table,
+                   TXT_NewLabel("Still the same table, but:\n"
+                                "This label magically overflows\n"
+                                "across multiple cells! Cool, huh? "),
+                   TXT_TABLE_OVERFLOW_RIGHT,
+                   TXT_NewButton("Do nothing"),
+                   TXT_TABLE_OVERFLOW_DOWN,
+                   TXT_TABLE_OVERFLOW_DOWN,
+                   TXT_NewButton("Also nothing"),
+                   NULL);
 
     TXT_AddWidget(window, TXT_NewStrut(0, 1));
     value_label = TXT_NewLabel("");
--- a/textscreen/txt_checkbox.c
+++ b/textscreen/txt_checkbox.c
@@ -65,7 +65,7 @@
     TXT_SetWidgetBG(checkbox);
     TXT_DrawString(checkbox->label);
 
-    for (i=strlen(checkbox->label); i < w-5; ++i)
+    for (i=strlen(checkbox->label); i < w-4; ++i)
     {
         TXT_DrawString(" ");
     }
--- a/textscreen/txt_scrollpane.c
+++ b/textscreen/txt_scrollpane.c
@@ -402,7 +402,8 @@
 
         if ((key == KEY_UPARROW || key == KEY_DOWNARROW
           || key == KEY_LEFTARROW || key == KEY_RIGHTARROW
-          || key == KEY_PGUP || key == KEY_PGDN)
+          || key == KEY_PGUP || key == KEY_PGDN
+          || key == KEY_TAB)
          && scrollpane->child->widget_class == &txt_table_class)
         {
             if (PageSelectedWidget(scrollpane, key))
--- a/textscreen/txt_table.c
+++ b/textscreen/txt_table.c
@@ -26,6 +26,20 @@
 #include "txt_strut.h"
 #include "txt_table.h"
 
+txt_widget_t txt_table_overflow_right;
+txt_widget_t txt_table_overflow_down;
+txt_widget_t txt_table_eol;
+txt_widget_t txt_table_empty;
+
+// Returns true if the given widget in the table's widgets[] array refers
+// to an actual widget - not NULL, or one of the special overflow pointers.
+static int IsActualWidget(txt_widget_t *widget)
+{
+    return widget != NULL
+        && widget != &txt_table_overflow_right
+        && widget != &txt_table_overflow_down;
+}
+
 // Remove all entries from a table
 
 void TXT_ClearTable(TXT_UNCAST_ARG(table))
@@ -39,12 +53,12 @@
 
     for (i=table->columns; i<table->num_widgets; ++i)
     {
-        if (table->widgets[i] != NULL)
+        if (IsActualWidget(table->widgets[i]))
         {
             TXT_DestroyWidget(table->widgets[i]);
         }
     }
-    
+
     // Shrink the table to just the column strut widgets
 
     table->num_widgets = table->columns;
@@ -62,8 +76,104 @@
     return (table->num_widgets + table->columns - 1) / table->columns;
 }
 
-static void CalcRowColSizes(txt_table_t *table, 
-                            unsigned int *row_heights, 
+// Most widgets occupy just one cell of a table, but if the special
+// overflow constants are used, they can occupy multiple cells.
+// This function figures out for a widget in a given cell, which
+// cells it should actually occupy (always a rectangle).
+static void CellOverflowedSize(txt_table_t *table, int x, int y,
+                               int *w, int *h)
+{
+    txt_widget_t *widget;
+    int x1, y1;
+
+    if (!IsActualWidget(table->widgets[y * table->columns + x]))
+    {
+        *w = 0; *h = 0;
+        return;
+    }
+
+    *w = table->columns - x;
+    *h = 0;
+    for (y1 = y; y1 < TableRows(table); ++y1)
+    {
+        // Every overflow cell must point to either (x, y) or another
+        // overflow cell. This means the first in every row must be
+        // txt_table_overflow_down.
+
+        if (y1 * table->columns + x >= table->num_widgets)
+        {
+            break;
+        }
+
+        widget = table->widgets[y1 * table->columns + x];
+
+        if (y1 != y && widget != &txt_table_overflow_down)
+        {
+            break;
+        }
+
+        for (x1 = x + 1; x1 < x + *w; ++x1)
+        {
+            if (y1 * table->columns + x1 >= table->num_widgets)
+            {
+                break;
+            }
+
+            // Can be either type of overflow, except on the first row.
+            // Otherwise we impose a limit on the width.
+            widget = table->widgets[y1 * table->columns + x1];
+            if (widget != &txt_table_overflow_right
+             && (widget != &txt_table_overflow_down || y1 == y))
+            {
+                *w = x1 - x;
+                break;
+            }
+        }
+
+        ++*h;
+    }
+}
+
+static int IsOverflowingCell(txt_table_t *table, int x, int y)
+{
+    int w, h;
+    CellOverflowedSize(table, x, y, &w, &h);
+    return w > 1 || h > 1;
+}
+
+// Using the given column/row size tables, calculate the size of the given
+// widget, storing the result in (w, h).
+static void CalculateWidgetDimensions(txt_table_t *table,
+                                      int x, int y,
+                                      unsigned int *column_widths,
+                                      unsigned int *row_heights,
+                                      unsigned int *w, unsigned int *h)
+{
+    txt_widget_t *widget;
+    int cell_w, cell_h;
+    int x1, y1;
+
+    widget = table->widgets[y * table->columns + x];
+
+    // Find which cells this widget occupies.
+    CellOverflowedSize(table, x, y, &cell_w, &cell_h);
+
+    // Add up column / row widths / heights to get the actual dimensions.
+    *w = 0;
+    for (x1 = x; x1 < x + cell_w; ++x1)
+    {
+        *w += column_widths[x1];
+    }
+
+    *h = 0;
+    for (y1 = y; y1 < y + cell_h; ++y1)
+    {
+        *h += row_heights[y1];
+    }
+}
+
+static void CalcRowColSizes(txt_table_t *table,
+                            unsigned int *row_heights,
                             unsigned int *col_widths)
 {
     int x, y;
@@ -74,11 +184,11 @@
 
     memset(col_widths, 0, sizeof(int) * table->columns);
 
-    for (y=0; y<rows; ++y)
+    for (y = 0; y < rows; ++y)
     {
         row_heights[y] = 0;
 
-        for (x=0; x<table->columns; ++x)
+        for (x = 0; x < table->columns; ++x)
         {
             if (y * table->columns + x >= table->num_widgets)
                 break;
@@ -85,11 +195,20 @@
 
             widget = table->widgets[y * table->columns + x];
 
-            // NULL represents an empty spacer
-
-            if (widget != NULL)
+            if (IsActualWidget(widget))
             {
                 TXT_CalcWidgetSize(widget);
+            }
+
+            // In the first pass we ignore overflowing cells.
+            if (IsOverflowingCell(table, x, y))
+            {
+                continue;
+            }
+
+            // NULL represents an empty spacer
+            if (IsActualWidget(widget))
+            {
                 if (widget->h > row_heights[y])
                     row_heights[y] = widget->h;
                 if (widget->w > col_widths[x])
@@ -97,6 +216,37 @@
             }
         }
     }
+
+    // In the second pass, we go through again and process overflowing
+    // widgets, to ensure that they will fit.
+    for (y = 0; y < rows; ++y)
+    {
+        for (x = 0; x < table->columns; ++x)
+        {
+            unsigned int w, h;
+
+            if (y * table->columns + x >= table->num_widgets)
+                break;
+
+            widget = table->widgets[y * table->columns + x];
+            if (!IsActualWidget(widget))
+            {
+                continue;
+            }
+
+            // Expand column width and row heights as needed.
+            CalculateWidgetDimensions(table, x, y, col_widths, row_heights,
+                                      &w, &h);
+            if (w < widget->w)
+            {
+                col_widths[x] += widget->w - w;
+            }
+            if (h < widget->h)
+            {
+                row_heights[y] += widget->h - h;
+            }
+        }
+    }
 }
 
 static void TXT_CalcTableSize(TXT_UNCAST_ARG(table))
@@ -132,36 +282,67 @@
     free(column_widths);
 }
 
+static void FillRowToEnd(txt_table_t *table)
+{
+    while ((table->num_widgets % table->columns) != 0)
+    {
+        TXT_AddWidget(table, &txt_table_overflow_right);
+    }
+}
+
 void TXT_AddWidget(TXT_UNCAST_ARG(table), TXT_UNCAST_ARG(widget))
 {
     TXT_CAST_ARG(txt_table_t, table);
     TXT_CAST_ARG(txt_widget_t, widget);
+    int is_separator;
+    int i;
 
-    if (table->num_widgets > 0)
+    // Convenience alias for NULL:
+    if (widget == &txt_table_empty)
     {
-        txt_widget_t *last_widget;
+        widget = NULL;
+    }
+    else if (widget == &txt_table_eol)
+    {
+        FillRowToEnd(table);
+        return;
+    }
 
-        last_widget = table->widgets[table->num_widgets - 1];
+    // We have special handling for the separator widget:
+    is_separator = IsActualWidget(widget)
+                && widget->widget_class == &txt_separator_class;
 
-        if (widget != NULL && last_widget != NULL
-         && widget->widget_class == &txt_separator_class
-         && last_widget->widget_class == &txt_separator_class)
+    // If we add two separators consecutively, the new separator replaces the
+    // first. This allows us to override the "implicit" separator that is
+    // added at the top of a window when it is created.
+    if (is_separator)
+    {
+        for (i = table->num_widgets - 1; i >= 0; --i)
         {
-            // The previous widget added was a separator; replace 
-            // it with this one.
-            //
-            // This way, if the first widget added to a window is 
-            // a separator, it replaces the "default" separator that
-            // the window itself adds on creation.
+            txt_widget_t *last_widget;
+            last_widget = table->widgets[i];
 
-            table->widgets[table->num_widgets - 1] = widget;
-
-            TXT_DestroyWidget(last_widget);
-
-            return;
+            if (IsActualWidget(last_widget)
+             && widget->widget_class == &txt_separator_class
+             && last_widget->widget_class == &txt_separator_class)
+            {
+                table->widgets[i] = widget;
+                TXT_DestroyWidget(last_widget);
+                return;
+            }
+            else if (last_widget != &txt_table_overflow_right)
+            {
+                break;
+            }
         }
     }
 
+    // Separators begin on a new line.
+    if (is_separator)
+    {
+        FillRowToEnd(table);
+    }
+
     table->widgets = realloc(table->widgets,
                              sizeof(txt_widget_t *) * (table->num_widgets + 1));
     table->widgets[table->num_widgets] = widget;
@@ -168,11 +349,16 @@
     ++table->num_widgets;
 
     // Maintain parent pointer.
-
-    if (widget != NULL)
+    if (IsActualWidget(widget))
     {
         widget->parent = &table->widget;
     }
+
+    // Separators always take up the entire line.
+    if (is_separator)
+    {
+        FillRowToEnd(table);
+    }
 }
 
 // Add multiple widgets to a table.
@@ -217,7 +403,7 @@
     if (i >= 0 && i < table->num_widgets)
     {
         widget = table->widgets[i];
-        return widget != NULL
+        return IsActualWidget(widget)
             && TXT_SelectableWidget(widget)
             && widget->visible;
     }
@@ -280,7 +466,7 @@
     {
         cur_widget = table->widgets[i];
 
-        if (table->widget.focused && cur_widget != NULL)
+        if (table->widget.focused && IsActualWidget(cur_widget))
         {
             TXT_SetWidgetFocus(cur_widget, 0);
         }
@@ -313,7 +499,7 @@
 
     if (selected >= 0 && selected < table->num_widgets)
     {
-        if (table->widgets[selected] != NULL
+        if (IsActualWidget(table->widgets[selected])
          && TXT_SelectableWidget(table->widgets[selected])
          && TXT_WidgetKeyPress(table->widgets[selected], key))
         {
@@ -321,6 +507,29 @@
         }
     }
 
+    if (key == KEY_TAB)
+    {
+        int dir;
+        int i;
+
+        dir = TXT_GetModifierState(TXT_MOD_SHIFT) ? -1 : 1;
+
+        // Cycle through all widgets until we find one that can be selected.
+        for (i = table->selected_y * table->columns + table->selected_x + dir;
+             i >= 0 && i < table->num_widgets;
+             i += dir)
+        {
+            if (IsActualWidget(table->widgets[i])
+             && TXT_SelectableWidget(table->widgets[i]))
+            {
+                ChangeSelection(table, i % table->columns, i / table->columns);
+                return 1;
+            }
+        }
+
+        return 0;
+    }
+
     if (key == KEY_DOWNARROW)
     {
         int new_x, new_y;
@@ -330,7 +539,7 @@
         for (new_y = table->selected_y + 1; new_y < rows; ++new_y)
         {
             new_x = FindSelectableColumn(table, new_y, table->selected_x);
-                            
+
             if (new_x >= 0)
             {
                 // Found a selectable widget in this column!
@@ -339,7 +548,7 @@
 
                 return 1;
             }
-        } 
+        }
     }
 
     if (key == KEY_UPARROW)
@@ -360,7 +569,7 @@
 
                 return 1;
             }
-        } 
+        }
     }
 
     if (key == KEY_LEFTARROW)
@@ -428,13 +637,16 @@
     }
 }
 
-static void LayoutCell(txt_table_t *table, int x, int y, int col_width,
+static void LayoutCell(txt_table_t *table, int x, int y,
                        int draw_x, int draw_y)
 {
     txt_widget_t *widget;
+    int col_width;
 
     widget = table->widgets[y * table->columns + x];
 
+    col_width = widget->w;
+
     // Adjust x position based on alignment property
 
     switch (widget->align)
@@ -445,7 +657,7 @@
 
         case TXT_HORIZ_CENTER:
             TXT_CalcWidgetSize(widget);
-            
+
             // Separators are always drawn left-aligned.
 
             if (widget->widget_class != &txt_separator_class)
@@ -452,17 +664,16 @@
             {
                 draw_x += (col_width - widget->w) / 2;
             }
-            
+
             break;
 
         case TXT_HORIZ_RIGHT:
             TXT_CalcWidgetSize(widget);
-            
+
             if (widget->widget_class != &txt_separator_class)
             {
                 draw_x += col_width - widget->w;
             }
-            
             break;
     }
 
@@ -481,6 +692,7 @@
     TXT_CAST_ARG(txt_table_t, table);
     unsigned int *column_widths;
     unsigned int *row_heights;
+    txt_widget_t *widget;
     int draw_x, draw_y;
     int x, y;
     int i;
@@ -505,9 +717,9 @@
     }
 
     // Draw all cells
-    
+
     draw_y = table->widget.y;
-    
+
     for (y=0; y<rows; ++y)
     {
         draw_x = table->widget.x;
@@ -519,10 +731,14 @@
             if (i >= table->num_widgets)
                 break;
 
-            if (table->widgets[i] != NULL)
+            widget = table->widgets[i];
+
+            if (IsActualWidget(widget))
             {
-                LayoutCell(table, x, y, column_widths[x], 
-                           draw_x, draw_y);
+                CalculateWidgetDimensions(table, x, y,
+                                          column_widths, row_heights,
+                                          &widget->w, &widget->h);
+                LayoutCell(table, x, y, draw_x, draw_y);
             }
 
             draw_x += column_widths[x];
@@ -552,7 +768,7 @@
     {
         widget = table->widgets[i];
 
-        if (widget != NULL)
+        if (IsActualWidget(widget))
         {
             TXT_GotoXY(widget->x, widget->y);
             TXT_DrawWidget(widget);
@@ -574,7 +790,7 @@
 
         // NULL widgets are spacers
 
-        if (widget != NULL)
+        if (IsActualWidget(widget))
         {
             if (x >= widget->x && x < (signed) (widget->x + widget->w)
              && y >= widget->y && y < (signed) (widget->y + widget->h))
@@ -617,7 +833,7 @@
 
     for (i = 0; i < table->num_widgets; ++i)
     {
-        if (table->widgets[i] != NULL
+        if (IsActualWidget(table->widgets[i])
          && TXT_SelectableWidget(table->widgets[i]))
         {
             ChangeSelection(table, i % table->columns, i / table->columns);
@@ -641,7 +857,7 @@
 
     if (i < table->num_widgets)
     {
-        if (table->widgets[i] != NULL)
+        if (IsActualWidget(table->widgets[i]))
         {
             TXT_SetWidgetFocus(table->widgets[i], focused);
         }
@@ -777,6 +993,11 @@
     if (index >= 0 && index < table->num_widgets)
     {
         result = table->widgets[index];
+
+        if (!IsActualWidget(result))
+        {
+            result = NULL;
+        }
     }
 
     if (result != NULL && result->widget_class == &txt_table_class)
@@ -798,7 +1019,7 @@
 
     for (i=0; i<table->num_widgets; ++i)
     {
-        if (table->widgets[i] == NULL)
+        if (!IsActualWidget(table->widgets[i]))
         {
             continue;
         }
@@ -830,6 +1051,66 @@
     // Not found.
 
     return 0;
+}
+
+void TXT_SetTableColumns(TXT_UNCAST_ARG(table), int new_columns)
+{
+    TXT_CAST_ARG(txt_table_t, table);
+    txt_widget_t **new_widgets;
+    txt_widget_t *widget;
+    int new_num_widgets;
+    int i, j, x;
+
+    // We need as many full rows as are in the current list, plus the
+    // remainder from the last row.
+    new_num_widgets = (table->num_widgets / table->columns) * new_columns
+                    + (table->num_widgets % table->columns);
+    new_widgets = calloc(new_num_widgets, sizeof(txt_widget_t *));
+
+    // Reset and add one by one from the old table.
+    new_num_widgets = 0;
+
+    for (i = 0; i < table->num_widgets; ++i)
+    {
+        widget = table->widgets[i];
+        x = i % table->columns;
+
+        if (x < new_columns)
+        {
+            new_widgets[new_num_widgets] = widget;
+            ++new_num_widgets;
+        }
+        else if (IsActualWidget(widget))
+        {
+            TXT_DestroyWidget(widget);
+        }
+
+        // When we reach the last column of a row, we must pad it out with
+        // extra widgets to reach the next row.
+        if (x == table->columns - 1)
+        {
+            for (j = table->columns; j < new_columns; ++j)
+            {
+                // First row? We need to add struts that are used to apply
+                // the column widths.
+                if (i < table->columns)
+                {
+                    widget = &TXT_NewStrut(0, 0)->widget;
+                }
+                else
+                {
+                    widget = &txt_table_overflow_right;
+                }
+                new_widgets[new_num_widgets] = widget;
+                ++new_num_widgets;
+            }
+        }
+    }
+
+    free(table->widgets);
+    table->widgets = new_widgets;
+    table->num_widgets = new_num_widgets;
+    table->columns = new_columns;
 }
 
 // Sets the widths of columns in a table.
--- a/textscreen/txt_table.h
+++ b/textscreen/txt_table.h
@@ -22,6 +22,33 @@
  */
 
 /**
+ * Magic value that if used in a table, will indicate that the cell is
+ * empty and the widget in the cell to the left can overflow into it.
+ */
+
+#define TXT_TABLE_OVERFLOW_RIGHT (&txt_table_overflow_right)
+
+/**
+ * Magic value that if used in a table, will indicate that the cell is
+ * empty and the widget in the cell above it can overflow down into it.
+ */
+
+#define TXT_TABLE_OVERFLOW_DOWN (&txt_table_overflow_down)
+
+/**
+ * Magic value that if given to @ref TXT_AddWidget(), will pad out all
+ * columns until the end of line.
+ */
+#define TXT_TABLE_EOL (&txt_table_eol)
+
+/**
+ * Indicates an empty space to @ref TXT_AddWidgets(). Equivalent to
+ * TXT_AddWidget(table, NULL), except that NULL is used by TXT_AddWidgets()
+ * to indicate the end of input.
+ */
+#define TXT_TABLE_EMPTY (&txt_table_empty)
+
+/**
  * Table widget.
  *
  * A table is a widget that contains other widgets.  It may have
@@ -45,21 +72,22 @@
 
     // Widgets in this table
     // The widget at (x,y) in the table is widgets[columns * y + x]
-
     txt_widget_t **widgets;
     int num_widgets;
 
     // Number of columns
-
     int columns;
 
-    // Currently selected 
-
+    // Currently selected:
     int selected_x;
     int selected_y;
 };
 
 extern txt_widget_class_t txt_table_class;
+extern txt_widget_t txt_table_overflow_right;
+extern txt_widget_t txt_table_overflow_down;
+extern txt_widget_t txt_table_eol;
+extern txt_widget_t txt_table_empty;
 
 void TXT_InitTable(txt_table_t *table, int columns);
 
@@ -143,6 +171,22 @@
  */
 
 int TXT_SelectWidget(TXT_UNCAST_ARG(table), TXT_UNCAST_ARG(widget));
+
+/**
+ * Change the number of columns in the table.
+ *
+ * Existing widgets in the table will be preserved, unless the change
+ * reduces the number of columns, in which case the widgets from the
+ * 'deleted' columns will be freed.
+ *
+ * This function can be useful for changing the number of columns in
+ * a window, which by default are tables containing a single column.
+ *
+ * @param table         The table.
+ * @param new_columns   The new number of columns.
+ */
+
+void TXT_SetTableColumns(TXT_UNCAST_ARG(table), int new_columns);
 
 /**
  * Set the widths of the columns of the table.
--- a/textscreen/txt_window.c
+++ b/textscreen/txt_window.c
@@ -27,6 +27,12 @@
 #include "txt_separator.h"
 #include "txt_window.h"
 
+#ifdef _WIN32
+#define WIN32_LEAN_AND_MEAN
+#include <windows.h>
+#include <shellapi.h>
+#endif
+
 void TXT_SetWindowAction(txt_window_t *window,
                          txt_horiz_align_t position, 
                          txt_window_action_t *action)
@@ -507,8 +513,17 @@
     window->help_url = help_url;
 }
 
+#ifdef _WIN32
+
 void TXT_OpenURL(char *url)
 {
+    ShellExecute(NULL, "open", url, NULL, NULL, SW_SHOWNORMAL);
+}
+
+#else
+
+void TXT_OpenURL(char *url)
+{
     char *cmd;
     size_t cmd_len;
 
@@ -515,9 +530,7 @@
     cmd_len = strlen(url) + 30;
     cmd = malloc(cmd_len);
 
-#if defined(_WIN32)
-    TXT_snprintf(cmd, cmd_len, "cmd /c start \"%s\"", url);
-#elif defined(__MACOSX__)
+#if defined(__MACOSX__)
     TXT_snprintf(cmd, cmd_len, "open \"%s\"", url);
 #else
     // The Unix situation sucks as usual, but the closest thing to a
@@ -535,6 +548,8 @@
     system(cmd);
     free(cmd);
 }
+
+#endif /* #ifndef _WIN32 */
 
 void TXT_OpenWindowHelpURL(txt_window_t *window)
 {