shithub: choc

Download patch

ref: d9520b6415f8e373cf759f316094b986e57f0f5a
parent: 57011e785808847273461aaf6d8ed1df76ff1db9
parent: 52f81b4ef175358d1e1f7f9eecab2a1edb7f4b65
author: Simon Howard <fraggle@gmail.com>
date: Fri Feb 5 22:14:20 EST 2010

Merge from trunk.

Subversion-branch: /branches/strife-branch
Subversion-revision: 1849

diff: cannot open b/pkg/osx/Resources/launcher.nib//null: file does not exist: 'b/pkg/osx/Resources/launcher.nib//null' diff: cannot open b/pkg/osx/Resources//null: file does not exist: 'b/pkg/osx/Resources//null' diff: cannot open b/pkg/osx//null: file does not exist: 'b/pkg/osx//null'
--- a/.gitignore
+++ b/.gitignore
@@ -10,9 +10,12 @@
 config.status
 config.h
 autom4te.cache
+rpm.spec
 stamp-h
 stamp-h.in
 stamp-h1
+tags
+TAGS
 
 # These are the default patterns globally ignored by Subversion:
 *.o
--- a/INSTALL
+++ b/INSTALL
@@ -23,7 +23,7 @@
 On a Unix system, follow the standard instructions for installing an 
 autotools-based package:
 
- 1. Run './configure' to initialise the package.
+ 1. Run './configure' to initialize the package.
  2. Run 'make' to compile the package.
  3. Run 'make install' to install the package.
 
--- a/Makefile.am
+++ b/Makefile.am
@@ -32,9 +32,12 @@
         codeblocks/setup-res.rc
 
 DATA_FILES=                   \
+        data/README           \
         data/doom.ico         \
+        data/doom8.ico        \
         data/doom.png         \
         data/setup.ico        \
+        data/setup8.ico       \
         data/setup.png        \
         data/convert-icon
 
@@ -43,11 +46,13 @@
         $(MSVC_FILES)                   \
         $(CODEBLOCKS_FILES)             \
         $(DATA_FILES)                   \
+        .lvimrc                         \
         config.h                        \
         CMDLINE                         \
         HACKING                         \
         TODO                            \
-        BUGS
+        BUGS                            \
+        rpm.spec
 
 MAINTAINERCLEANFILES =  $(AUX_DIST_GEN)
 
@@ -56,6 +61,8 @@
 DIST_SUBDIRS=pkg $(SUBDIRS)
 
 if HAVE_PYTHON
+
+noinst_DATA=CMDLINE
 
 CMDLINE : src/
 	./man/docgen -p man/CMDLINE.template src/ > $@
--- a/NEWS
+++ b/NEWS
@@ -1,35 +1,68 @@
 ...
 
      * Chocolate Doom now runs on Windows Mobile/Windows CE!
-     * It is possible to rebind most/all of the keys that control
-       the menu, shortcuts, automap and weapon switching.  The
-       main reason for this is to support the Windows CE port
-       and other platforms where a full keyboard may not be present.
-     * Memory-mapped WAD I/O is disabled by default, as it caused
-       various issues, including a slowdown/crash with Plutonia 2
-       MAP23.  It can be explicitly re-enabled using the '-mmap'
-       command line parameter.
+     * It is possible to rebind most/all of the keys that control the
+       menu, shortcuts, automap and weapon switching.  The main
+       reason for this is to support the Windows CE port and other
+       platforms where a full keyboard may not be present.
+     * Chocolate Doom now includes a proper Mac OS X package; it is
+       no longer necessary to compile binaries for this system by
+       hand.  The package includes a simple graphical launcher
+       program and can be installed simply by dragging the "Chocolate
+       Doom" icon to the Applications folder. (thanks to Rikard Lang
+       for extensive testing and feedback)
      * The video mode auto-adjust code will automatically choose
        windowed mode if no fullscreen video modes are available.
-     * The zone memory size is automatically reduced on systems
-       with a small amount of memory.
-     * There is now a second, small textscreen font, so that the
-       ENDOOM screen and setup tool can be used on low resolution
-       devices (eg. PDAs/embedded devices)
-     * The textscreen library now has a scrollable pane widget.
-     * Doxygen documentation was added for the textscreen library.
-     * The "join game" window in the setup tool now has an option
-       to automatically join a game on the local network.
+     * The zone memory size is automatically reduced on systems with
+       a small amount of memory.
+     * The "join game" window in the setup tool now has an option to
+       automatically join a game on the local network.
+     * Chocolate Doom includes some initial hacks for compiling under
+       SDL 1.3.
+     * Recent versions of SDL_mixer include rewritten MIDI code on Mac
+       OS X.  If you are using a version of SDL_mixer with the new
+       code, music will now be enabled by default.
+     * Windows Vista and Windows 7 no longer prompt for elevated
+       privileges when running the setup tool (thanks hobbs and
+       MikeRS).
+     * The Windows binaries now have better looking icons (thanks
+       MikeRS).
+     * Magic values specified using the -spechit command line
+       parameter can now be hexadecimal.
+     * DOOMWADDIR/DOOMWADPATH can now specify the complete path to
+       IWAD files, rather than the path to the directory that contains
+       them.
+     * When recording shorttics demos, errors caused by the reduced
+       turning resolution are carried forward, possibly making turning
+       smoother.
 
     Compatibility:
      * The A_BossDeath behavior in v1.9 emulation mode was fixed
        (thanks entryway)
+     * The "loading" disk icon is drawn more like how it is drawn in
+       Vanilla Doom, also fixing a bug with chook3.wad.
+     * Desync on 64-bit systems with ep1-0500.lmp has (at long last)
+       been fixed (thanks exp(x)).
+     * Donut overrun emulation code imported from Prboom+ (thanks
+       entryway).
+     * The correct level name should now be shown in the automap for
+       pl2.wad MAP33 (thanks Janizdreg).
+     * In Chex Quest, the green radiation suit colormap is now used
+       instead of the red colormaps normally used when taking damage
+       or using the berserk pack.  This matches Vanilla chex.exe
+       behavior (thanks Fuzztooth).
+     * Impassible glass now displays and works the same as in Vanilla,
+       fixing wads such as OTTAWAU.WAD (thanks Never_Again).
 
     Bugs fixed:
+     * Memory-mapped WAD I/O is disabled by default, as it caused
+       various issues, including a slowdown/crash with Plutonia 2
+       MAP23.  It can be explicitly re-enabled using the '-mmap'
+       command line parameter.
      * Crash when saving games due to the ~/.chocolate-doom/savegames
        directory not being created (thanks to everyone who reported
        this).
-     * Chocolate Doom will now under Win95/98, as the
+     * Chocolate Doom will now run under Win95/98, as the
        SetProcessAffinityMask function is looked up dynamically.
      * Compilation under Linux with older versions of libc will now
        work (the semantics for sched_setaffinity were different in
@@ -36,13 +69,28 @@
        older versions)
      * Sound clipping when using libsamplerate was improved (thanks
        David Flater)
-     * The audio buffer size is now calculated based on the sample rate,
-       so there is not a noticeable delay when using a lower sample
-       rate.
-     * The manpage documentation for the DOOMWADPATH variable was fixed
-       (thanks MikeRS).
-     * Compilation with FEATURE_MULTIPLAYER and FEATURE_SOUND disabled
-       was fixed.
+     * The audio buffer size is now calculated based on the sample
+       rate, so there is not a noticeable delay when using a lower
+       sample rate.
+     * The manpage documentation for the DOOMWADPATH variable was
+       fixed (thanks MikeRS).
+     * Compilation with FEATURE_MULTIPLAYER and FEATURE_SOUND
+       disabled was fixed.
+     * Fixed crash when using the donut special type and the joining
+       linedef is one sided (thanks Alexander Waldmann).
+     * Key settings in a configuration file that are out of range
+       do not cause a crash (thanks entryway).
+     * Fix ear-piercing whistle when playing the MAP05 MIDI music
+       using timidity with EAWPATS (thanks entryway / HackNeyed).
+
+    libtextscreen:
+     * There is now a second, small textscreen font, so that the
+       ENDOOM screen and setup tool can be used on low resolution
+       devices (eg. PDAs/embedded devices)
+     * The textscreen library now has a scrollable pane widget. Thanks
+       to LionsPhil for contributing code to scroll up and down using
+       the keyboard.
+     * Doxygen documentation was added for the textscreen library.
 
 1.2.1 (2008-12-10):
 
--- a/TODO
+++ b/TODO
@@ -1,17 +1,22 @@
+Currently in progress:
+
+* OPL MIDI playback (see: opl-branch)
+* Heretic/Hexen support (see: raven-branch)
+* Strife support (see: strife-branch)
+
 To do:
 
-* Install packages:
-  - Debian/Ubuntu .deb packages, Fedora .rpm packages.
-  - Windows NSIS installer.
-  - MacOS X .dmg packages (should be universal binaries!)
+* Demo hashes for regression testing of this and other ports.
 * File selector for chocolate-setup, so that WADs can be selected from
   a browser, instead of simply typing the filenames.
 * Multiplayer:
   - Master server for locating servers automatically - makes setting up
     public servers easier.
+  - Use UPnP to automatically configure port forwarding for NATted
+    networks.
   - Incorporate local LAN search into setup interface.
   - Multiplayer options and configuration file (server name, etc)
-* Improve multiplayer startup: 
+* Improve multiplayer startup:
   - Select an IWAD automatically from the server's game type rather than
     all players having to specify -iwad.
   - Send list of WADs to load instead of all clients having to specify -file.
@@ -20,16 +25,14 @@
   - Test on and fix for architectures where ((-2) >> 1) != -1
   - Use size-specific types (eg. int32_t instead of int)
   - Don't make structure packing assumptions when loading levels.
-* Port to every OS and architecture under the sun
+  - Port to every OS and architecture under the sun
 
 Crazy pie in the sky ideas:
 
 * Automatic WAD installer - download and run TCs from a list automatically
   (automating the current "instructions on wiki" system).
-* Textscreen interface to the Compet-N database: menu driven system to 
+* Textscreen interface to the Compet-N database: menu driven system to
   automatically download and play speedruns.
 * DWANGO-like interface for finding players and setting up games.
-* Demo hashes for regression testing of this and other ports.
-* OPL emulation
 * Video capture mode?
 
--- a/autogen.sh
+++ b/autogen.sh
@@ -8,5 +8,5 @@
 autoconf
 automake
 
-./configure $@
+./configure "$@"
 
--- a/configure.in
+++ b/configure.in
@@ -1,5 +1,11 @@
 AC_INIT(Chocolate Doom, 1.2.1, fraggle@gmail.com, chocolate-doom)
 
+PACKAGE_SHORTDESC="Conservative Doom source port"
+PACKAGE_COPYRIGHT="Copyright (C) 1993-2010"
+PACKAGE_LICENSE="GNU General Public License, version 2"
+PACKAGE_MAINTAINER="Simon Howard"
+PACKAGE_URL="http://www.chocolate-doom.org/"
+
 AC_CONFIG_AUX_DIR(autotools)
 
 orig_CFLAGS="$CFLAGS"
@@ -120,28 +126,36 @@
 
 AC_SUBST(ac_aux_dir)
 
+AC_SUBST(PACKAGE_SHORTDESC)
+AC_SUBST(PACKAGE_COPYRIGHT)
+AC_SUBST(PACKAGE_LICENSE)
+AC_SUBST(PACKAGE_MAINTAINER)
+AC_SUBST(PACKAGE_URL)
+
 dnl Shut up the datarootdir warnings.
 AC_DEFUN([AC_DATAROOTDIR_CHECKED])
 
 AC_OUTPUT([
 Makefile
-wince/Makefile
-textscreen/Makefile
-textscreen/examples/Makefile
 man/Makefile
+pcsound/Makefile
+pkg/Makefile
+pkg/config.make
+pkg/osx/Info-gnustep.plist
+pkg/osx/Info.plist
+rpm.spec
 src/Makefile
+src/doom-screensaver.desktop
 src/doom/Makefile
 src/heretic/Makefile
 src/hexen/Makefile
-src/setup/Makefile
-src/strife/Makefile
-pcsound/Makefile
-pkg/Makefile
-pkg/wince/GNUmakefile
-pkg/win32/GNUmakefile
 src/resource.rc
 src/setup-res.rc
+src/setup/Makefile
 src/setup/setup-manifest.xml
-src/doom-screensaver.desktop
+src/strife/Makefile
+textscreen/Makefile
+textscreen/examples/Makefile
+wince/Makefile
 ])
 
--- a/data/README
+++ b/data/README
@@ -3,3 +3,10 @@
 
     http://www.flickr.com/photos/laffy4k/448920776/
 
+The "foo8.ico" files are 8-bit depth only, while the "foo.ico" files
+contain full 32-bit versions, scaled to different sizes and with proper
+alpha masks (as well as the 8-bit versions).  The 8-bit versions are
+used when setting the icon within SDL, as SDL under Windows displays
+full color icons in a very poor quality.  The full-color versions are
+used in the resource files.
+
binary files a/data/doom.ico b/data/doom.ico differ
binary files /dev/null b/data/doom8.ico differ
binary files a/data/setup.ico b/data/setup.ico differ
binary files /dev/null b/data/setup8.ico differ
--- a/man/Makefile.am
+++ b/man/Makefile.am
@@ -5,9 +5,9 @@
 
 man_MANS=chocolate-doom.6      \
          chocolate-server.6    \
-	 chocolate-setup.6     \
-	 default.cfg.5         \
-	 $(PACKAGE).cfg.5
+         chocolate-setup.6     \
+         default.cfg.5         \
+         $(PACKAGE).cfg.5
 
 chocolate-doom.6: ../src $(MANPAGE_GEN_FILES)
 	./docgen -m manpage.template ../src > $@
@@ -21,5 +21,6 @@
 endif
 
 EXTRA_DIST = $(man_MANS) $(MANPAGE_GEN_FILES)        \
-	     wikipages
+             wikipages                               \
+             CMDLINE.template
 
--- a/pcsound/.gitignore
+++ b/pcsound/.gitignore
@@ -3,4 +3,6 @@
 .deps
 libpcsound.a
 *.rc
+tags
+TAGS
 
--- /dev/null
+++ b/pkg/.gitignore
@@ -1,0 +1,3 @@
+Makefile
+Makefile.in
+config.make
--- a/pkg/Makefile.am
+++ b/pkg/Makefile.am
@@ -1,3 +1,35 @@
 
-DIST_SUBDIRS=wince win32
+OSX_FILES=                                                              \
+osx/Resources/128x128.png                                               \
+osx/Resources/app.icns                                                  \
+osx/Resources/app.png                                                   \
+osx/Resources/wadfile.icns                                              \
+osx/Resources/wadfile.png                                               \
+osx/Resources/launcher.nib/classes.nib                                  \
+osx/Resources/launcher.nib/info.nib                                     \
+osx/Resources/launcher.nib/keyedobjects.nib                             \
+osx/GNUmakefile                                                         \
+osx/Info.plist.in       osx/Info-gnustep.plist.in                       \
+osx/PkgInfo                                                             \
+osx/cp-with-libs                                                        \
+osx/main.m                                                              \
+osx/AppController.m     osx/AppController.h                             \
+osx/Execute.m           osx/Execute.h                                   \
+osx/IWADController.m    osx/IWADController.h                            \
+osx/IWADLocation.m      osx/IWADLocation.h                              \
+osx/LauncherManager.m   osx/LauncherManager.h
+
+WINCE_FILES=                                                            \
+wince/GNUmakefile                                                       \
+wince/common.py                                                         \
+wince/doom-cab.cfg                                                      \
+wince/heretic-cab.cfg                                                   \
+wince/hexen-cab.cfg                                                     \
+wince/wince-cabgen
+
+WIN32_FILES=                                                            \
+win32/GNUmakefile                                                       \
+win32/README
+
+EXTRA_DIST=$(OSX_FILES) $(WINCE_FILES) $(WIN32_FILES)
 
--- /dev/null
+++ b/pkg/config.make.in
@@ -1,0 +1,29 @@
+# Shared file included by the makefiles used to build packages.
+# This contains various information needed by the makefiles,
+# and is autogenerated by configure to include various
+# necessary details.
+
+# Tools needed:
+
+CC = @CC@
+STRIP = @STRIP@
+
+# Package name and version number:
+
+PROGRAM_PREFIX = @PROGRAM_PREFIX@
+PACKAGE = @PACKAGE@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+
+# Documentation files to distribute with packages.
+
+DOC_FILES = README       \
+            COPYING      \
+            ChangeLog    \
+            NEWS         \
+            BUGS         \
+            CMDLINE      \
+            TODO
+
--- /dev/null
+++ b/pkg/osx/.gitignore
@@ -1,0 +1,7 @@
+Info.plist
+Info-gnustep.plist
+launcher
+*.o
+*.d
+*.dmg
+staging
--- /dev/null
+++ b/pkg/osx/AppController.h
@@ -1,0 +1,53 @@
+/* ... */
+//-----------------------------------------------------------------------------
+//
+// Copyright(C) 2009 Simon Howard
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+// 02111-1307, USA.
+//
+//-----------------------------------------------------------------------------
+
+#ifndef LAUNCHER_APPCONTROLLER_H
+#define LAUNCHER_APPCONTROLLER_H
+
+#include <AppKit/AppKit.h>
+
+#include "LauncherManager.h"
+
+@interface AppController : NSObject
+{
+    LauncherManager *launcherManager;
+    BOOL filesAdded;
+}
+
++ (void)initialize;
+
+- (id)init;
+- (void)dealloc;
+
+- (void)awakeFromNib;
+
+- (void)applicationDidFinishLaunching:(NSNotification *)aNotif;
+- (BOOL)applicationShouldTerminate:(id)sender;
+- (void)applicationWillTerminate:(NSNotification *)aNotif;
+- (BOOL)application:(NSApplication *)application openFile:(NSString *)fileName;
+
+- (void)showPrefPanel:(id)sender;
+
+@end
+
+#endif
+
--- /dev/null
+++ b/pkg/osx/AppController.m
@@ -1,0 +1,124 @@
+/* ... */
+//-----------------------------------------------------------------------------
+//
+// Copyright(C) 2009 Simon Howard
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+// 02111-1307, USA.
+//
+//-----------------------------------------------------------------------------
+
+#include "AppController.h"
+
+#include "config.h"
+
+@implementation AppController
+
++ (void)initialize
+{
+    NSMutableDictionary *defaults = [NSMutableDictionary dictionary];
+
+    /*
+     * Register your app's defaults here by adding objects to the
+     * dictionary, eg
+     *
+     * [defaults setObject:anObject forKey:keyForThatObject];
+     *
+     */
+
+    [[NSUserDefaults standardUserDefaults] registerDefaults:defaults];
+    [[NSUserDefaults standardUserDefaults] synchronize];
+}
+
+- (id)init
+{
+    if ((self = [super init]))
+    {
+    }
+
+    self->filesAdded = NO;
+
+    return self;
+}
+
+- (void)dealloc
+{
+    [super dealloc];
+}
+
+- (void)awakeFromNib
+{
+    [[NSApp mainMenu] setTitle:@PACKAGE_NAME];
+}
+
+- (void)applicationDidFinishLaunching:(NSNotification *)aNotif
+{
+// Uncomment if your application is Renaissance-based
+//  [NSBundle loadGSMarkupNamed:@"Main" owner:self];
+}
+
+- (BOOL)applicationShouldTerminate:(id)sender
+{
+    return YES;
+}
+
+- (void)applicationWillTerminate:(NSNotification *)aNotif
+{
+}
+
+- (BOOL) application:(NSApplication *) application
+         openFile:(NSString *) fileName
+{
+    NSString *extension;
+
+    // If this is the first file added, clear out the existing
+    // command line.  This allows us to select multiple files
+    // in the finder and open them all together (for TCs, etc).
+
+    if (!self->filesAdded)
+    {
+        [self->launcherManager clearCommandLine];
+    }
+
+    // Add file with appropriate command line option based on extension:
+
+    extension = [fileName pathExtension];
+
+    if (![extension caseInsensitiveCompare: @"wad"])
+    {
+        [self->launcherManager addFileToCommandLine: fileName
+                               forArgument: @"-merge"];
+    }
+    else if (![extension caseInsensitiveCompare: @"deh"])
+    {
+        [self->launcherManager addFileToCommandLine: fileName
+                               forArgument: @"-deh"];
+    }
+    else
+    {
+        return NO;
+    }
+
+    self->filesAdded = YES;
+
+    return YES;
+}
+
+- (void)showPrefPanel:(id)sender
+{
+}
+
+@end
+
--- /dev/null
+++ b/pkg/osx/Execute.h
@@ -1,0 +1,31 @@
+/* ... */
+//-----------------------------------------------------------------------------
+//
+// Copyright(C) 2009 Simon Howard
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+// 02111-1307, USA.
+//
+//-----------------------------------------------------------------------------
+
+#ifndef LAUNCHER_EXECUTE_H
+#define LAUNCHER_EXECUTE_H
+
+void SetProgramLocation(const char *path);
+void ExecuteProgram(const char *executable, const char *iwad, const char *args);
+void OpenTerminalWindow(const char *doomwadpath);
+
+#endif /* #ifndef LAUNCHER_EXECUTE_H */
+
--- /dev/null
+++ b/pkg/osx/Execute.m
@@ -1,0 +1,208 @@
+/* ... */
+//-----------------------------------------------------------------------------
+//
+// Copyright(C) 2009 Simon Howard
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+// 02111-1307, USA.
+//
+//-----------------------------------------------------------------------------
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+
+#include <AppKit/AppKit.h>
+
+#include "config.h"
+
+#define RESPONSE_FILE "/tmp/launcher.rsp"
+#define TEMP_SCRIPT "/tmp/tempscript.sh"
+
+static char *executable_path;
+
+// Called on startup to save the location of the launcher program
+// (within a package, other executables should be in the same directory)
+
+void SetProgramLocation(const char *path)
+{
+    char *p;
+
+    executable_path = strdup(path);
+
+    p = strrchr(executable_path, '/');
+    *p = '\0';
+}
+
+// Write out the response file containing command line arguments.
+
+static void WriteResponseFile(const char *iwad, const char *args)
+{
+    FILE *fstream;
+
+    fstream = fopen(RESPONSE_FILE, "w");
+
+    if (iwad != NULL)
+    {
+        fprintf(fstream, "-iwad \"%s\"", iwad);
+    }
+
+    if (args != NULL)
+    {
+        fprintf(fstream, "%s", args);
+    }
+
+    fclose(fstream);
+}
+
+static void DoExec(const char *executable, const char *iwad, const char *args)
+{
+    char *argv[3];
+
+    argv[0] = malloc(strlen(executable_path) + strlen(executable) + 3);
+    sprintf(argv[0], "%s/%s", executable_path, executable);
+
+    if (iwad != NULL || args != NULL)
+    {
+        WriteResponseFile(iwad, args);
+
+        argv[1] = "@" RESPONSE_FILE;
+        argv[2] = NULL;
+    }
+    else
+    {
+        argv[1] = NULL;
+    }
+
+    execv(argv[0], argv);
+    exit(-1);
+}
+
+// Execute the specified executable contained in the same directory
+// as the launcher, with the specified arguments.
+
+void ExecuteProgram(const char *executable, const char *iwad, const char *args)
+{
+    pid_t childpid;
+    char *homedir;
+
+    childpid = fork();
+
+    if (childpid == 0)
+    {
+        signal(SIGCHLD, SIG_DFL);
+
+        // Change directory to home dir before launch, so that any demos
+        // are saved somewhere sensible.
+
+        homedir = getenv("HOME");
+
+        if (homedir != NULL)
+        {
+            chdir(homedir);
+        }
+
+        DoExec(executable, iwad, args);
+    }
+    else
+    {
+        signal(SIGCHLD, SIG_IGN);
+    }
+}
+
+// Write a sequence of commands that will display the specified message
+// via shell commands.
+
+static void WriteMessage(FILE *script, char *msg)
+{
+    char *p;
+
+    fprintf(script, "echo \"");
+
+    for (p=msg; *p != '\0'; ++p)
+    {
+        // Start new line?
+
+        if (*p == '\n')
+        {
+            fprintf(script, "\"\necho \"");
+            continue;
+        }
+
+        // Escaped character?
+
+        if (*p == '\\' || *p == '\"')
+        {
+            fprintf(script, "\\");
+        }
+
+        fprintf(script, "%c", *p);
+    }
+
+    fprintf(script, "\"\n");
+}
+
+// Open a terminal window with the PATH set appropriately, and DOOMWADPATH
+// set to the specified value.
+
+void OpenTerminalWindow(const char *doomwadpath)
+{
+    FILE *stream;
+
+    // Generate a shell script that sets the PATH to include the location
+    // where the Doom binaries are, and DOOMWADPATH to include the
+    // IWAD files that have been configured in the launcher interface.
+    // The script then deletes itself and starts a shell.
+
+    stream = fopen(TEMP_SCRIPT, "w");
+
+    fprintf(stream, "#!/bin/sh\n");
+    //fprintf(stream, "set -x\n");
+    fprintf(stream, "PATH=\"%s:$PATH\"\n", executable_path);
+    fprintf(stream, "DOOMWADPATH=\"%s\"\n", doomwadpath);
+    fprintf(stream, "export DOOMWADPATH\n");
+    fprintf(stream, "rm -f \"%s\"\n", TEMP_SCRIPT);
+
+    // Display a useful message:
+
+    fprintf(stream, "clear\n");
+    WriteMessage(stream,
+        "\n"
+        "This command line has the PATH variable configured so that you may\n"
+        "launch the game with whatever parameters you desire.\n"
+        "\n"
+        "For example:\n"
+        "\n"
+        "   " PACKAGE_TARNAME " -iwad doom2.wad -file sid.wad -warp 1\n"
+        "\n"
+        "Type 'exit' to exit.\n");
+
+    fprintf(stream, "exec $SHELL\n");
+    fprintf(stream, "\n");
+
+    fclose(stream);
+
+    chmod(TEMP_SCRIPT, 0755);
+
+    // Tell the terminal to open a window to run the script.
+
+    [[NSWorkspace sharedWorkspace] openFile: @TEMP_SCRIPT
+                                   withApplication: @"Terminal"];
+}
+
--- /dev/null
+++ b/pkg/osx/GNUmakefile
@@ -1,0 +1,105 @@
+
+# Makefile for building the OS X launcher program and DMG package.
+# It is also possible to build and run the launcher under Unix
+# systems using GNUstep, although this is only here for development
+# and debugging purposes.
+
+include ../config.make
+
+STAGING_DIR=staging
+DMG=$(PACKAGE_TARNAME)-$(PACKAGE_VERSION).dmg
+
+TOPLEVEL=../..
+TOPLEVEL_DOCS=$(patsubst %,../../%,$(DOC_FILES))
+
+ifndef GNUSTEP_MAKEFILES
+
+# DMG file containing package:
+
+$(DMG) : $(STAGING_DIR)
+	rm -f $@
+	hdiutil create -volname "$(PACKAGE_STRING)" -srcdir $(STAGING_DIR) $@
+
+endif
+
+# Staging dir build for package:
+
+APP_DIR=$(STAGING_DIR)/$(PACKAGE_NAME).app
+
+# OS X and GNUstep apps have a slightly different internal structure:
+# OS X apps have their files within a containing "Contents" directory
+# that does not exist in GNUstep apps.  Similarly, the binaries are
+# installed at the top level, rather than in a "MacOS" directory.
+# Finally, we must install a different Info.plist file.
+
+ifdef GNUSTEP_MAKEFILES
+APP_TOP_DIR=$(APP_DIR)
+APP_BIN_DIR=$(APP_DIR)
+SRC_INFO_PLIST=Info-gnustep.plist
+else
+APP_TOP_DIR=$(APP_DIR)/Contents
+APP_BIN_DIR=$(APP_DIR)/Contents/MacOS
+SRC_INFO_PLIST=Info.plist
+endif
+
+$(STAGING_DIR): launcher $(TOPLEVEL_DOCS)
+	rm -rf $(STAGING_DIR)
+	mkdir $(STAGING_DIR)
+
+	cp $(TOPLEVEL_DOCS) "$(STAGING_DIR)"
+
+	mkdir -p "$(APP_TOP_DIR)"
+	cp -R Resources "$(APP_TOP_DIR)"
+	cp PkgInfo "$(APP_TOP_DIR)"
+	cp $(SRC_INFO_PLIST) "$(APP_TOP_DIR)"
+
+	mkdir -p "$(APP_BIN_DIR)"
+
+	cp launcher "$(APP_BIN_DIR)"
+	$(STRIP) "$(APP_BIN_DIR)/launcher"
+
+	./cp-with-libs $(TOPLEVEL)/src/$(PROGRAM_PREFIX)doom "$(APP_BIN_DIR)"
+	$(STRIP) "$(APP_BIN_DIR)/$(PROGRAM_PREFIX)doom"
+	./cp-with-libs $(TOPLEVEL)/src/$(PROGRAM_PREFIX)heretic "$(APP_BIN_DIR)"
+	$(STRIP) "$(APP_BIN_DIR)/$(PROGRAM_PREFIX)heretic"
+	./cp-with-libs $(TOPLEVEL)/src/$(PROGRAM_PREFIX)hexen "$(APP_BIN_DIR)"
+	$(STRIP) "$(APP_BIN_DIR)/$(PROGRAM_PREFIX)hexen"
+	./cp-with-libs $(TOPLEVEL)/src/$(PROGRAM_PREFIX)setup "$(APP_BIN_DIR)"
+	$(STRIP) "$(APP_BIN_DIR)/$(PROGRAM_PREFIX)setup"
+
+	find $(STAGING_DIR) -name .svn -delete -exec rm -rf {} \; || true
+
+clean : launcher_clean
+	rm -f $(DMG)
+	rm -rf $(STAGING_DIR)
+
+# Launcher build:
+
+CFLAGS = -Wall -I$(TOPLEVEL)
+
+# Are we building using gs_make?
+
+ifdef GNUSTEP_MAKEFILES
+CFLAGS += $(shell gnustep-config --objc-flags)
+LDFLAGS = $(shell gnustep-config --gui-libs)
+else
+LDFLAGS = -framework Cocoa
+endif
+
+LAUNCHER_OBJS= \
+        AppController.o \
+        Execute.o \
+        IWADController.o \
+        IWADLocation.o \
+        LauncherManager.o \
+        main.o
+
+launcher : $(LAUNCHER_OBJS)
+	$(CC) $(LDFLAGS) $(LAUNCHER_OBJS) -o $@
+
+%.o : %.m
+	$(CC) -c $(CFLAGS) $^ -o $@
+
+launcher_clean :
+	rm -f $(LAUNCHER_OBJS) launcher
+
--- /dev/null
+++ b/pkg/osx/IWADController.h
@@ -1,0 +1,54 @@
+/* ... */
+//-----------------------------------------------------------------------------
+//
+// Copyright(C) 2009 Simon Howard
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+// 02111-1307, USA.
+//
+//-----------------------------------------------------------------------------
+
+#ifndef LAUNCHER_IWADCONTROLLER_H
+#define LAUNCHER_IWADCONTROLLER_H
+
+#include <AppKit/AppKit.h>
+#include <AppKit/NSNibLoading.h>
+
+@interface IWADController : NSObject
+{
+    id iwadSelector;
+    id configWindow;
+
+    id chex;
+    id doom1;
+    id doom2;
+    id plutonia;
+    id tnt;
+}
+
+- (void) closeConfigWindow: (id)sender;
+- (void) openConfigWindow: (id)sender;
+- (NSString *) getIWADLocation;
+- (void) awakeFromNib;
+- (BOOL) setDropdownList;
+- (void) setDropdownSelection;
+- (void) saveConfig;
+- (char *) doomWadPath;
+- (void) setEnvironment;
+
+@end
+
+#endif /* #ifndef LAUNCHER_IWADCONTROLLER_H */
+
--- /dev/null
+++ b/pkg/osx/IWADController.m
@@ -1,0 +1,347 @@
+/* ... */
+//-----------------------------------------------------------------------------
+//
+// Copyright(C) 2009 Simon Howard
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+// 02111-1307, USA.
+//
+//-----------------------------------------------------------------------------
+
+#include <stdlib.h>
+#include <string.h>
+#include <AppKit/AppKit.h>
+#include "IWADController.h"
+#include "IWADLocation.h"
+
+typedef enum
+{
+    IWAD_DOOM1,
+    IWAD_DOOM2,
+    IWAD_TNT,
+    IWAD_PLUTONIA,
+    IWAD_CHEX,
+    NUM_IWAD_TYPES
+} IWAD;
+
+static NSString *IWADLabels[NUM_IWAD_TYPES] =
+{
+    @"Doom",
+    @"Doom II: Hell on Earth",
+    @"Final Doom: TNT: Evilution",
+    @"Final Doom: Plutonia Experiment",
+    @"Chex Quest"
+};
+
+static NSString *IWADFilenames[NUM_IWAD_TYPES + 1] =
+{
+    @"doom.wad",
+    @"doom2.wad",
+    @"tnt.wad",
+    @"plutonia.wad",
+    @"chex.wad",
+    @"undefined"
+};
+
+@implementation IWADController
+
+- (void) getIWADList: (IWADLocation **) iwadList
+{
+    iwadList[IWAD_DOOM1] = self->doom1;
+    iwadList[IWAD_DOOM2] = self->doom2;
+    iwadList[IWAD_TNT] = self->tnt;
+    iwadList[IWAD_PLUTONIA] = self->plutonia;
+    iwadList[IWAD_CHEX] = self->chex;
+}
+
+- (IWAD) getSelectedIWAD
+{
+    unsigned int i;
+
+    for (i=0; i<NUM_IWAD_TYPES; ++i)
+    {
+        if ([self->iwadSelector titleOfSelectedItem] == IWADLabels[i])
+        {
+            return i;
+        }
+    }
+
+    return NUM_IWAD_TYPES;
+}
+
+// Get the location of the selected IWAD.
+
+- (NSString *) getIWADLocation
+{
+    IWAD selectedIWAD;
+    IWADLocation *iwadList[NUM_IWAD_TYPES];
+
+    selectedIWAD = [self getSelectedIWAD];
+
+    if (selectedIWAD == NUM_IWAD_TYPES)
+    {
+        return nil;
+    }
+    else
+    {
+        [self getIWADList: iwadList];
+
+	return [iwadList[selectedIWAD] getLocation];
+    }
+}
+
+- (void) setIWADConfig
+{
+    IWADLocation *iwadList[NUM_IWAD_TYPES];
+    NSUserDefaults *defaults;
+    NSString *key;
+    NSString *value;
+    unsigned int i;
+
+    [self getIWADList: iwadList];
+
+    // Load IWAD filename paths
+
+    defaults = [NSUserDefaults standardUserDefaults];
+
+    for (i=0; i<NUM_IWAD_TYPES; ++i)
+    {
+        key = IWADFilenames[i];
+        value = [defaults stringForKey:key];
+
+        if (value != nil)
+        {
+            [iwadList[i] setLocation:value];
+        }
+    }
+}
+
+// On startup, set the selected item in the IWAD dropdown
+
+- (void) setDropdownSelection
+{
+    NSUserDefaults *defaults;
+    NSString *selected;
+    unsigned int i;
+
+    defaults = [NSUserDefaults standardUserDefaults];
+    selected = [defaults stringForKey: @"selected_iwad"];
+
+    if (selected == nil)
+    {
+        return;
+    }
+
+    // Find this IWAD in the filenames list, and select it.
+
+    for (i=0; i<NUM_IWAD_TYPES; ++i)
+    {
+        if ([selected isEqualToString:IWADFilenames[i]])
+        {
+            [self->iwadSelector selectItemWithTitle:IWADLabels[i]];
+            break;
+        }
+    }
+}
+
+// Set the dropdown list to include an entry for each IWAD that has
+// been configured.  Returns true if at least one IWAD is configured.
+
+- (BOOL) setDropdownList
+{
+    IWADLocation *iwadList[NUM_IWAD_TYPES];
+    BOOL have_wads;
+    id location;
+    unsigned int i;
+    unsigned int enabled_wads;
+
+    // Build the new list.
+
+    [self getIWADList: iwadList];
+    [self->iwadSelector removeAllItems];
+
+    enabled_wads = 0;
+
+    for (i=0; i<NUM_IWAD_TYPES; ++i)
+    {
+        location = [iwadList[i] getLocation];
+
+        if (location != nil && [location length] > 0)
+        {
+            [self->iwadSelector addItemWithTitle: IWADLabels[i]];
+            ++enabled_wads;
+        }
+    }
+
+    // Enable/disable the dropdown depending on whether there
+    // were any configured IWADs.
+
+    have_wads = enabled_wads > 0;
+    [self->iwadSelector setEnabled: have_wads];
+
+    // Restore the old selection.
+
+    [self setDropdownSelection];
+
+    return have_wads;
+}
+
+- (void) saveConfig
+{
+    IWADLocation *iwadList[NUM_IWAD_TYPES];
+    IWAD selectedIWAD;
+    NSUserDefaults *defaults;
+    NSString *key;
+    NSString *value;
+    unsigned int i;
+
+    [self getIWADList: iwadList];
+
+    // Store all IWAD locations to user defaults.
+
+    defaults = [NSUserDefaults standardUserDefaults];
+
+    for (i=0; i<NUM_IWAD_TYPES; ++i)
+    {
+        key = IWADFilenames[i];
+        value = [iwadList[i] getLocation];
+
+        [defaults setObject:value forKey:key];
+    }
+
+    // Save currently selected IWAD.
+
+    selectedIWAD = [self getSelectedIWAD];
+    [defaults setObject:IWADFilenames[selectedIWAD]
+              forKey:@"selected_iwad"];
+}
+
+// Callback method invoked when the configuration button in the main
+// window is clicked.
+
+- (void) openConfigWindow: (id)sender
+{
+    if (![self->configWindow isVisible])
+    {
+        [self->configWindow makeKeyAndOrderFront: sender];
+    }
+}
+
+// Callback method invoked when the close button is clicked.
+
+- (void) closeConfigWindow: (id)sender
+{
+    [self->configWindow orderOut: sender];
+    [self saveConfig];
+    [self setDropdownList];
+}
+
+- (void) awakeFromNib
+{
+    [self->configWindow center];
+
+    // Set configuration for all IWADs from configuration file.
+
+    [self setIWADConfig];
+
+    // Populate the dropdown IWAD list.
+
+    if ([self setDropdownList])
+    {
+        [self setDropdownSelection];
+    }
+}
+
+// Generate a value to set for the DOOMWADPATH environment variable
+// that contains each of the configured IWAD files.
+
+- (char *) doomWadPath
+{
+    IWADLocation *iwadList[NUM_IWAD_TYPES];
+    NSString *location;
+    unsigned int i;
+    unsigned int len;
+    BOOL first;
+    char *env;
+
+    [self getIWADList: iwadList];
+
+    // Calculate length of environment string.
+
+    len = 0;
+
+    for (i=0; i<NUM_IWAD_TYPES; ++i)
+    {
+        location = [iwadList[i] getLocation];
+
+        if (location != nil && [location length] > 0)
+        {
+            len += [location length] + 1;
+        }
+    }
+
+    // Build string.
+
+    env = malloc(len);
+    strcpy(env, "");
+
+    first = YES;
+
+    for (i=0; i<NUM_IWAD_TYPES; ++i)
+    {
+        location = [iwadList[i] getLocation];
+
+        if (location != nil && [location length] > 0)
+        {
+            if (!first)
+            {
+                strcat(env, ":");
+            }
+
+            strcat(env, [location UTF8String]);
+            first = NO;
+        }
+    }
+
+    return env;
+}
+
+// Set the DOOMWADPATH environment variable to contain the path to each
+// of the configured IWAD files.
+
+- (void) setEnvironment
+{
+    char *doomwadpath;
+    char *env;
+
+    // Get the value for the path.
+
+    doomwadpath = [self doomWadPath];
+
+    env = malloc(strlen(doomwadpath) + 15);
+
+    sprintf(env, "DOOMWADPATH=%s", doomwadpath);
+
+    free(doomwadpath);
+
+    // Load into environment:
+
+    putenv(env);
+
+    //free(env);
+}
+
+@end
+
--- /dev/null
+++ b/pkg/osx/IWADLocation.h
@@ -1,0 +1,44 @@
+/* ... */
+//-----------------------------------------------------------------------------
+//
+// Copyright(C) 2009 Simon Howard
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+// 02111-1307, USA.
+//
+//-----------------------------------------------------------------------------
+
+#ifndef LAUNCHER_IWADLOCATION_H
+#define LAUNCHER_IWADLOCATION_H
+
+#include <AppKit/AppKit.h>
+
+#include "IWADController.h"
+
+@interface IWADLocation : NSObject
+{
+    IWADController *iwadController;
+
+    id locationConfigBox;
+}
+
+- (void) setButtonClicked: (id)sender;
+- (NSString *) getLocation;
+- (void) setLocation: (NSString *) value;
+
+@end
+
+#endif /* #ifndef LAUNCHER_IWADLOCATION_H */
+
--- /dev/null
+++ b/pkg/osx/IWADLocation.m
@@ -1,0 +1,74 @@
+/* ... */
+//-----------------------------------------------------------------------------
+//
+// Copyright(C) 2009 Simon Howard
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+// 02111-1307, USA.
+//
+//-----------------------------------------------------------------------------
+
+#include <AppKit/AppKit.h>
+#include "IWADLocation.h"
+
+static id WAD_TYPES[] =
+{
+    @"wad", @"WAD"
+};
+
+@implementation IWADLocation
+
+- (void) setButtonClicked: (id)sender
+{
+    NSArray *wadTypes = [NSArray arrayWithObjects: WAD_TYPES count: 2];
+    NSOpenPanel *openPanel;
+    NSArray *filenames;
+    int result;
+
+    [wadTypes retain];
+
+    // Open a file selector for the new file.
+
+    openPanel = [NSOpenPanel openPanel];
+    [openPanel setTitle: @"Add IWAD file"];
+    [openPanel setCanChooseFiles: YES];
+    [openPanel setCanChooseDirectories: NO];
+
+    result = [openPanel runModalForTypes: wadTypes];
+
+    // If the "OK" button was clicked, add the new IWAD file to the list.
+
+    if (result == NSOKButton)
+    {
+        filenames = [openPanel filenames];
+	[self setLocation: [filenames lastObject]];
+
+        [self->iwadController saveConfig];
+        [self->iwadController setDropdownList];
+    }
+}
+
+- (NSString *) getLocation
+{
+    return [self->locationConfigBox stringValue];
+}
+
+- (void) setLocation: (NSString *) filename
+{
+    [self->locationConfigBox setStringValue: filename];
+}
+
+@end
+
--- /dev/null
+++ b/pkg/osx/Info-gnustep.plist.in
@@ -1,0 +1,36 @@
+{
+    ApplicationName = "@PACKAGE_NAME@";
+    ApplicationDescription = "@PACKAGE_SHORTDESC@";
+    ApplicationIcon = app.png;
+    ApplicationRelease = @PACKAGE_VERSION@;
+    ApplicationURL = "@PACKAGE_URL@";
+    Authors = (
+        "@PACKAGE_MAINTAINER@ <@PACKAGE_BUGREPORT@>"
+    );
+    Copyright = "@PACKAGE_COPYRIGHT@";
+    CopyrightDescription = "@PACKAGE_LICENSE@";
+    FullVersionID = @PACKAGE_VERSION@;
+    GSMainMarkupFile = "";
+    NSExecutable = "launcher";
+    NSIcon = app.png;
+    NSMainNibFile = launcher.nib;
+    NSPrincipalClass = NSApplication;
+    NSRole = Application;
+    NSTypes = (
+        {
+            NSHumanReadableName = "Doom WAD file";
+            NSUnixExtensions = ( wad );
+            NSRole = Viewer;
+            NSMimeTypes = (
+                "application/x-doom"
+            );
+            NSIcon = "wadfile.png";
+        },
+        {
+            NSHumanReadableName = "Dehacked patch";
+            NSUnixExtensions = ( deh );
+            NSRole = Viewer;
+            NSIcon = "wadfile.png";
+        }
+    );
+}
--- /dev/null
+++ b/pkg/osx/Info.plist.in
@@ -1,0 +1,60 @@
+<?xml version="1.0" encoding="UTF-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>CFBundleDevelopmentRegion</key>
+	<string>English</string>
+	<key>CFBundleDisplayName</key>
+	<string>@PACKAGE_NAME@</string>
+	<key>CFBundleExecutable</key>
+	<string>launcher</string>
+	<key>CFBundleGetInfoString</key>
+	<string>@PACKAGE_STRING@</string>
+	<key>CFBundleIconFile</key>
+	<string>app.icns</string>
+	<key>CFBundleInfoDictionaryVersion</key>
+	<string>6.0</string>
+	<key>CFBundleName</key>
+	<string>@PACKAGE_NAME@</string>
+	<key>CFBundlePackageType</key>
+	<string>APPL</string>
+	<key>CFBundleShortVersionString</key>
+	<string>@PACKAGE_VERSION@</string>
+	<key>CFBundleVersion</key>
+	<string>@PACKAGE_VERSION@</string>
+	<key>NSPrincipalClass</key>
+	<string>NSApplication</string>
+	<key>NSMainNibFile</key>
+	<string>launcher</string>
+
+        <!-- file associations: -->
+
+        <key>CFBundleDocumentTypes</key>
+        <array>
+                <dict>
+                        <key>CFBundleTypeName</key>
+                        <string>Doom WAD file</string>
+                        <key>CFBundleTypeIconFile</key>
+                        <string>wadfile.icns</string>
+                        <key>CFBundleTypeRole</key>
+                        <string>Viewer</string>
+                        <key>CFBundleTypeExtensions</key>
+                        <array>
+                                <string>wad</string>
+                        </array>
+                </dict>
+                <dict>
+                        <key>CFBundleTypeName</key>
+                        <string>Dehacked patch</string>
+                        <key>CFBundleTypeIconFile</key>
+                        <string>wadfile.icns</string>
+                        <key>CFBundleTypeRole</key>
+                        <string>Viewer</string>
+                        <key>CFBundleTypeExtensions</key>
+                        <array>
+                                <string>deh</string>
+                        </array>
+                </dict>
+        </array>
+</dict>
+</plist>
--- /dev/null
+++ b/pkg/osx/LauncherManager.h
@@ -1,0 +1,52 @@
+/* ... */
+//-----------------------------------------------------------------------------
+//
+// Copyright(C) 2009 Simon Howard
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+// 02111-1307, USA.
+//
+//-----------------------------------------------------------------------------
+
+#ifndef LAUNCHER_LAUNCHERMANAGER_H
+#define LAUNCHER_LAUNCHERMANAGER_H
+
+#include <AppKit/AppKit.h>
+#include <AppKit/NSNibLoading.h>
+#include "IWADController.h"
+
+@interface LauncherManager : NSObject
+{
+    IWADController *iwadController;
+
+    id launcherWindow;
+    id launchButton;
+
+    id commandLineArguments;
+    id packageLabel;
+}
+
+- (void) launch: (id)sender;
+- (void) runSetup: (id)sender;
+- (void) awakeFromNib;
+- (void) clearCommandLine;
+- (void) addFileToCommandLine: (NSString *) fileName
+         forArgument: (NSString *) args;
+- (void) openTerminal: (id) sender;
+
+@end
+
+#endif /* #ifndef LAUNCHER_LAUNCHERMANAGER_H */
+
--- /dev/null
+++ b/pkg/osx/LauncherManager.m
@@ -1,0 +1,338 @@
+/* ... */
+//-----------------------------------------------------------------------------
+//
+// Copyright(C) 2009 Simon Howard
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+// 02111-1307, USA.
+//
+//-----------------------------------------------------------------------------
+
+#include <AppKit/AppKit.h>
+#include "Execute.h"
+#include "LauncherManager.h"
+#include "config.h"
+
+@implementation LauncherManager
+
+// Save configuration.  Invoked when we launch the game or quit.
+
+- (void) saveConfig
+{
+    NSUserDefaults *defaults;
+
+    // Save IWAD configuration and selected IWAD.
+
+    [self->iwadController saveConfig];
+
+    // Save command line arguments.
+
+    defaults = [NSUserDefaults standardUserDefaults];
+    [defaults setObject:[self->commandLineArguments stringValue]
+              forKey:@"command_line_args"];
+}
+
+// Load configuration, invoked on startup.
+
+- (void) setConfig
+{
+    NSUserDefaults *defaults;
+    NSString *args;
+
+    defaults = [NSUserDefaults standardUserDefaults];
+
+    args = [defaults stringForKey:@"command_line_args"];
+
+    if (args != nil)
+    {
+        [self->commandLineArguments setStringValue:args];
+    }
+}
+
+// Get the next command line argument from the command line.
+// The position counter used to iterate over arguments is in 'pos'.
+// The index of the argument that was found is saved in arg_pos.
+
+static NSString *GetNextArgument(NSString *commandLine, int *pos, int *arg_pos)
+{
+    NSRange arg_range;
+
+    // Skip past any whitespace
+
+    while (*pos < [commandLine length]
+        && isspace([commandLine characterAtIndex: *pos]))
+    {
+        ++*pos;
+    }
+
+    if (*pos >= [commandLine length])
+    {
+        *arg_pos = *pos;
+        return nil;
+    }
+
+    // We are at the start of the argument.  This may be a quoted
+    // string argument, or a "normal" one.
+
+    if ([commandLine characterAtIndex: *pos] == '\"')
+    {
+        // Quoted string, skip past first quote
+
+        ++*pos;
+
+        // Save start position:
+
+        *arg_pos = *pos;
+
+        while (*pos < [commandLine length]
+            && [commandLine characterAtIndex: *pos] != '\"')
+        {
+            ++*pos;
+        }
+
+        // Unexpected end of string?
+
+        if (*pos >= [commandLine length])
+        {
+            return nil;
+        }
+
+        arg_range = NSMakeRange(*arg_pos, *pos - *arg_pos);
+
+        // Skip past last quote
+
+        ++*pos;
+    }
+    else
+    {
+        // Normal argument
+
+        // Save position:
+
+        *arg_pos = *pos;
+
+        // Read until end:
+
+        while (*pos < [commandLine length]
+            && !isspace([commandLine characterAtIndex: *pos]))
+        {
+            ++*pos;
+        }
+
+        arg_range = NSMakeRange(*arg_pos, *pos - *arg_pos);
+    }
+
+    return [commandLine substringWithRange: arg_range];
+}
+
+// Given the specified command line argument, find the index
+// to insert the new file within the command line.  Returns -1 if the 
+// argument is not already within the arguments string.
+
+static int GetFileInsertIndex(NSString *commandLine, NSString *needle)
+{
+    NSString *arg;
+    int arg_pos;
+    int pos;
+
+    pos = 0;
+
+    // Find the command line parameter we are searching
+    // for (-merge, -deh, etc)
+
+    for (;;)
+    {
+        arg = GetNextArgument(commandLine, &pos, &arg_pos);
+
+        // Searched to end of string and never found?
+
+        if (arg == nil)
+        {
+            return -1;
+        }
+
+        if (![arg caseInsensitiveCompare: needle])
+        {
+            break;
+        }
+    }
+
+    // Now skip over existing files.  For example, if we
+    // have -file foo.wad bar.wad, the new file should be appended
+    // to the end of the list.
+
+    for (;;)
+    {
+        arg = GetNextArgument(commandLine, &pos, &arg_pos);
+
+        // If we search to the end of the string now, it is fine;
+        // the new string should be added to the end of the command
+        // line.  Otherwise, if we find an argument that begins
+        // with '-', it is a new command line parameter and the end
+        // of the list.
+
+        if (arg == nil || [arg characterAtIndex: 0] == '-')
+        {
+            break;
+        }
+    }
+
+    // arg_pos should now contain the offset to insert the new filename.
+
+    return arg_pos;
+}
+
+// Given the specified string, append a filename, quoted if necessary.
+
+static NSString *AppendQuotedFilename(NSString *str, NSString *fileName)
+{
+    int i;
+
+    // Search the filename for spaces, and quote if necessary.
+
+    for (i=0; i<[fileName length]; ++i)
+    {
+        if (isspace([fileName characterAtIndex: i]))
+        {
+            str = [str stringByAppendingString: @" \""];
+            str = [str stringByAppendingString: fileName];
+            str = [str stringByAppendingString: @"\" "];
+
+            return str;
+        }
+    }
+
+    str = [str stringByAppendingString: @" "];
+    str = [str stringByAppendingString: fileName];
+
+    return str;
+}
+
+// Clear out the existing command line options.
+// Invoked before the first file is added.
+
+- (void) clearCommandLine
+{
+    [self->commandLineArguments setStringValue: @""];
+}
+
+// Add a file to the command line to load with the game.
+
+- (void) addFileToCommandLine: (NSString *) fileName
+         forArgument: (NSString *) arg
+{
+    NSString *commandLine;
+    int insert_pos;
+
+    // Get the current command line
+
+    commandLine = [self->commandLineArguments stringValue];
+
+    // Find the location to insert the new filename:
+
+    insert_pos = GetFileInsertIndex(commandLine, arg);
+
+    // If position < 0, we should add the new argument and filename
+    // to the end.  Otherwise, append the new filename to the existing
+    // list of files.
+
+    if (insert_pos < 0)
+    {
+        commandLine = [commandLine stringByAppendingString: @" "];
+        commandLine = [commandLine stringByAppendingString: arg];
+        commandLine = AppendQuotedFilename(commandLine, fileName);
+    }
+    else
+    {
+        NSString *start;
+        NSString *end;
+
+        // Divide existing command line in half:
+
+        start = [commandLine substringToIndex: insert_pos];
+        end = [commandLine substringFromIndex: insert_pos];
+
+        // Construct new command line:
+
+        commandLine = AppendQuotedFilename(start, fileName);
+        commandLine = [commandLine stringByAppendingString: @" "];
+        commandLine = [commandLine stringByAppendingString: end];
+    }
+
+    [self->commandLineArguments setStringValue: commandLine];
+}
+
+- (void) launch: (id)sender
+{
+    NSString *iwad;
+    NSString *args;
+
+    [self saveConfig];
+
+    iwad = [self->iwadController getIWADLocation];
+    args = [self->commandLineArguments stringValue];
+
+    if (iwad == nil)
+    {
+        NSRunAlertPanel(@"No IWAD selected",
+                        @"You have not selected an IWAD (game) file.\n\n"
+                         "You must configure and select a valid IWAD file "
+                         "in order to launch the game.",
+                        @"OK", nil, nil);
+        return;
+    }
+
+    ExecuteProgram(PACKAGE_TARNAME, [iwad UTF8String],
+                                    [args UTF8String]);
+    [NSApp terminate:sender];
+}
+
+// Invoked when the "Setup Tool" button is clicked, to run the setup tool:
+
+- (void) runSetup: (id)sender
+{
+    [self saveConfig];
+
+    [self->iwadController setEnvironment];
+    ExecuteProgram("chocolate-setup", NULL, NULL);
+}
+
+// Invoked when the "Terminal" option is selected from the menu, to open
+// a terminal window.
+
+- (void) openTerminal: (id) sender
+{
+    char *doomwadpath;
+
+    [self saveConfig];
+
+    doomwadpath = [self->iwadController doomWadPath];
+
+    OpenTerminalWindow(doomwadpath);
+
+    free(doomwadpath);
+}
+
+- (void) awakeFromNib
+{
+    [self->packageLabel setStringValue: @PACKAGE_STRING];
+    [self->launcherWindow setTitle: @PACKAGE_NAME " Launcher"];
+    [self->launcherWindow center];
+    [self->launcherWindow setDefaultButtonCell: [self->launchButton cell]];
+    [self setConfig];
+}
+
+@end
+
--- /dev/null
+++ b/pkg/osx/PkgInfo
@@ -1,0 +1,1 @@
+APPL????
binary files /dev/null b/pkg/osx/Resources/128x128.png differ
binary files /dev/null b/pkg/osx/Resources/app.icns differ
binary files /dev/null b/pkg/osx/Resources/app.png differ
--- /dev/null
+++ b/pkg/osx/Resources/launcher.nib/classes.nib
@@ -1,0 +1,47 @@
+{
+    IBClasses = (
+        {
+            CLASS = AppController; 
+            LANGUAGE = ObjC; 
+            OUTLETS = {launcherManager = id; }; 
+            SUPERCLASS = NSObject; 
+        }, 
+        {CLASS = FirstResponder; LANGUAGE = ObjC; SUPERCLASS = NSObject; }, 
+        {
+            ACTIONS = {closeConfigWindow = id; openConfigWindow = id; }; 
+            CLASS = IWADController; 
+            LANGUAGE = ObjC; 
+            OUTLETS = {
+                chex = id; 
+                configWindow = id; 
+                doom1 = id; 
+                doom2 = id; 
+                iwadSelector = id; 
+                plutonia = id; 
+                tnt = id; 
+            }; 
+            SUPERCLASS = NSObject; 
+        }, 
+        {
+            ACTIONS = {setButtonClicked = id; }; 
+            CLASS = IWADLocation; 
+            LANGUAGE = ObjC; 
+            OUTLETS = {locationConfigBox = id; }; 
+            SUPERCLASS = NSObject; 
+        }, 
+        {
+            ACTIONS = {launch = id; openTerminal = id; runSetup = id; }; 
+            CLASS = LauncherManager; 
+            LANGUAGE = ObjC; 
+            OUTLETS = {
+                commandLineArguments = id; 
+                iwadController = id; 
+                launchButton = id; 
+                launcherWindow = id; 
+                packageLabel = id; 
+            }; 
+            SUPERCLASS = NSObject; 
+        }
+    ); 
+    IBVersion = 1; 
+}
\ No newline at end of file
--- /dev/null
+++ b/pkg/osx/Resources/launcher.nib/info.nib
@@ -1,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-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>IBDocumentLocation</key>
+	<string>325 73 612 260 0 0 1440 878 </string>
+	<key>IBEditorPositions</key>
+	<dict>
+		<key>29</key>
+		<string>221 322 205 44 0 0 1440 878 </string>
+	</dict>
+	<key>IBFramework Version</key>
+	<string>446.1</string>
+	<key>IBOpenObjects</key>
+	<array>
+		<integer>29</integer>
+		<integer>21</integer>
+		<integer>227</integer>
+	</array>
+	<key>IBSystem Version</key>
+	<string>8S2167</string>
+</dict>
+</plist>
binary files /dev/null b/pkg/osx/Resources/launcher.nib/keyedobjects.nib differ
binary files /dev/null b/pkg/osx/Resources/wadfile.icns differ
binary files /dev/null b/pkg/osx/Resources/wadfile.png differ
--- /dev/null
+++ b/pkg/osx/cp-with-libs
@@ -1,0 +1,100 @@
+#!/bin/bash
+#
+# Copy a program to the specified destination, along
+# with libraries it depends upon.
+
+src_bin=$1
+dest_dir=$2
+
+# Returns true if the specified file is a dylib.
+
+is_dylib() {
+	case "$1" in
+		*.dylib)
+			true
+			;;
+		*)
+			false
+			;;
+	esac
+}
+
+# Returns true if the specified file is in a system location
+# (/System or /usr):
+
+is_sys_lib() {
+	case "$1" in
+		/System/*)
+			true
+			;;
+		/usr/*)
+			true
+			;;
+		*)
+			false
+			;;
+	esac
+}
+
+# Install the specified file to the location in dest_dir, along with
+# any libraries it depends upon (recursively):
+
+install_with_deps() {
+	local src_file
+	local bin_name
+	local dest_file
+	local lib_name
+
+	src_file=$1
+	bin_name=$(basename "$src_file")
+	dest_file="$dest_dir/$bin_name"
+
+	# Already copied into the package? Don't copy again.
+	# Prevents endless recursion.
+
+	if [ -e "$dest_file" ]; then
+		return
+	fi	
+
+	echo "Installing $bin_name..."
+
+	# Copy file into package.
+
+	cp "$src_file" "$dest_file"
+
+	# Copy libraries that this file depends on:
+
+	otool -L "$src_file" | tail -n +2 | sed 's/^.//; s/ (.*//' | while read; do
+		
+		# Don't copy system libraries
+
+		if is_sys_lib "$REPLY"; then
+			continue
+		fi
+
+		#echo "    - $bin_name depends on $REPLY"
+
+		# Copy this library into the package, and:
+		# recursively install any libraries that _this_ library depends on:
+
+		install_with_deps "$REPLY"
+
+		# Change destination binary to depend on the
+		# copy inside the package:
+
+		lib_name=$(basename "$REPLY")
+		install_name_tool -change "$REPLY" "@executable_path/$lib_name" \
+		                  "$dest_file"
+	done
+
+	# If this is a library that we have installed, change its id:
+
+	if is_dylib "$dest_file"; then
+		install_name_tool -id "@executable_path/$bin_name" "$dest_file"
+	fi
+}
+
+# Install the file, and recursively install any libraries:
+
+install_with_deps "$src_bin"
+
--- /dev/null
+++ b/pkg/osx/main.m
@@ -1,0 +1,32 @@
+/* ... */
+//-----------------------------------------------------------------------------
+//
+// Copyright(C) 2009 Simon Howard
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+// 02111-1307, USA.
+//
+//-----------------------------------------------------------------------------
+
+#include <AppKit/AppKit.h>
+#include "Execute.h"
+
+int main(int argc, const char *argv[])
+{
+    SetProgramLocation(argv[0]);
+
+    return NSApplicationMain (argc, argv);
+}
+
--- /dev/null
+++ b/pkg/win32/GNUmakefile
@@ -1,0 +1,34 @@
+
+include ../config.make
+
+TOPLEVEL=../..
+
+EXE_FILES=$(TOPLEVEL)/src/$(PROGRAM_PREFIX)doom.exe     \
+          $(TOPLEVEL)/src/$(PROGRAM_PREFIX)heretic.exe  \
+          $(TOPLEVEL)/src/$(PROGRAM_PREFIX)hexen.exe    \
+          $(TOPLEVEL)/src/$(PROGRAM_PREFIX)server.exe   \
+          $(TOPLEVEL)/src/$(PROGRAM_PREFIX)setup.exe
+
+DLL_FILES=$(TOPLEVEL)/src/SDL.dll                      \
+          $(TOPLEVEL)/src/SDL_mixer.dll                \
+          $(TOPLEVEL)/src/SDL_net.dll
+
+ZIP=$(PACKAGE_TARNAME)-$(PACKAGE_VERSION)-win32.zip
+
+$(ZIP) : staging
+	zip -j -r $@ staging/
+
+staging: $(EXE_FILES) $(DLL_FILES) $(patsubst %,../../%,$(DOC_FILES))
+	rm -rf staging
+	mkdir staging
+	cp $(EXE_FILES) $(DLL_FILES) staging/
+	$(STRIP) staging/*.exe
+	for f in $(DOC_FILES); do                    \
+		cp $(TOPLEVEL)/$$f staging/$$f.txt;  \
+		unix2dos staging/$$f.txt;            \
+	done
+
+clean:
+	rm -f $(ZIP)
+	rm -rf staging
+
--- a/pkg/win32/GNUmakefile.am
+++ /dev/null
@@ -1,37 +1,0 @@
-
-TOPLEVEL=../..
-
-EXE_FILES=$(TOPLEVEL)/src/@PROGRAM_PREFIX@doom.exe     \
-          $(TOPLEVEL)/src/@PROGRAM_PREFIX@heretic.exe  \
-          $(TOPLEVEL)/src/@PROGRAM_PREFIX@hexen.exe    \
-          $(TOPLEVEL)/src/@PROGRAM_PREFIX@server.exe   \
-          $(TOPLEVEL)/src/@PROGRAM_PREFIX@setup.exe
-
-DLL_FILES=$(TOPLEVEL)/src/SDL.dll                      \
-          $(TOPLEVEL)/src/SDL_mixer.dll                \
-          $(TOPLEVEL)/src/SDL_net.dll
-
-DOC_FILES=README       \
-          COPYING      \
-          ChangeLog    \
-          NEWS         \
-          BUGS         \
-          CMDLINE      \
-          HH-TODO      \
-          TODO
-
-noinst_DATA=@PACKAGE_TARNAME@-@PACKAGE_VERSION@-win32.zip
-
-@PACKAGE_TARNAME@-@PACKAGE_VERSION@-win32.zip : staging
-	zip -j -r $@ staging/
-
-staging: $(EXE_FILES) $(DLL_FILES) $(patsubst %,../../%,$(DOC_FILES))
-	rm -rf staging
-	mkdir staging
-	cp $(EXE_FILES) $(DLL_FILES) staging/
-	$(STRIP) staging/*.exe
-	for f in $(DOC_FILES); do                    \
-		cp $(TOPLEVEL)/$$f staging/$$f.txt;  \
-		unix2dos staging/$$f.txt;            \
-	done
-
--- /dev/null
+++ b/pkg/wince/GNUmakefile
@@ -1,0 +1,35 @@
+
+include ../config.make
+
+# Doom:
+
+DOOM_CAB=$(PROGRAM_PREFIX)doom-$(PACKAGE_VERSION).cab
+DOOM_CFG=doom-cab.cfg
+DOOM_DEPS=$(shell ./wince-cabgen -d $(DOOM_CFG))
+
+# Heretic:
+
+HERETIC_CAB=$(PROGRAM_PREFIX)heretic-$(PACKAGE_VERSION).cab
+HERETIC_CFG=heretic-cab.cfg
+HERETIC_DEPS=$(shell ./wince-cabgen -d $(HERETIC_CFG))
+
+# Hexen:
+
+HEXEN_CAB=$(PROGRAM_PREFIX)hexen-$(PACKAGE_VERSION).cab
+HEXEN_CFG=hexen-cab.cfg
+HEXEN_DEPS=$(shell ./wince-cabgen -d $(HEXEN_CFG))
+
+all: $(DOOM_CAB) $(HERETIC_CAB) $(HEXEN_CAB)
+
+$(DOOM_CAB) : $(DOOM_CFG) $(DOOM_DEPS)
+	./wince-cabgen $< $@
+
+$(HERETIC_CAB) : $(HERETIC_CFG) $(HERETIC_DEPS)
+	./wince-cabgen $< $@
+
+$(HEXEN_CAB) : $(HEXEN_CFG) $(HEXEN_DEPS)
+	./wince-cabgen $< $@
+
+clean:
+	rm -f $(DOOM_CAB) $(HERETIC_CAB) $(HEXEN_CAB)
+
--- a/pkg/wince/GNUmakefile.am
+++ /dev/null
@@ -1,30 +1,0 @@
-
-# Doom:
-
-DOOM_CAB=chocolate-doom-@PACKAGE_VERSION@.cab
-DOOM_CFG=doom-cab.cfg
-DOOM_DEPS=$(shell ./wince-cabgen -d $(DOOM_CFG))
-
-$(DOOM_CAB) : $(DOOM_CFG) $(DOOM_DEPS)
-	./wince-cabgen $< $@
-
-# Heretic:
-
-HERETIC_CAB=chocolate-heretic-@PACKAGE_VERSION@.cab
-HERETIC_CFG=heretic-cab.cfg
-HERETIC_DEPS=$(shell ./wince-cabgen -d $(HERETIC_CFG))
-
-$(HERETIC_CAB) : $(HERETIC_CFG) $(HERETIC_DEPS)
-	./wince-cabgen $< $@
-
-# Hexen:
-
-HEXEN_CAB=chocolate-hexen-@PACKAGE_VERSION@.cab
-HEXEN_CFG=hexen-cab.cfg
-HEXEN_DEPS=$(shell ./wince-cabgen -d $(HEXEN_CFG))
-
-$(HEXEN_CAB) : $(HEXEN_CFG) $(HEXEN_DEPS)
-	./wince-cabgen $< $@
-
-noinst_DATA = $(DOOM_CAB) $(HERETIC_CAB) $(HEXEN_CAB)
-
--- /dev/null
+++ b/rpm.spec.in
@@ -1,0 +1,59 @@
+
+Name: @PACKAGE@
+Summary: @PACKAGE_SHORTDESC@
+Version: @VERSION@
+Release: 1
+Source: http://mesh.dl.sourceforge.net/project/chocolate-doom/@PACKAGE@/@VERSION@/@PACKAGE@-@VERSION@.tar.gz
+URL: @PACKAGE_URL@
+Group: Amusements/Games
+BuildRoot: /var/tmp/@PACKAGE@-buildroot
+License: @PACKAGE_LICENSE@
+Packager: @PACKAGE_MAINTAINER@ <@PACKAGE_BUGREPORT@>
+Prefix: %{_prefix}
+Autoreq: 0
+Requires: libSDL-1.2.so.0, libSDL_mixer-1.2.so.0, libSDL_net-1.2.so.0
+
+%description
+%(sed -n "/==/ q; p " < README)
+
+See @PACKAGE_URL@ for more information.
+
+%prep
+rm -rf $RPM_BUILD_ROOT
+
+%setup -q
+
+%build
+./configure \
+ 	--prefix=/usr \
+	--exec-prefix=/usr \
+	--bindir=/usr/bin \
+	--sbindir=/usr/sbin \
+	--sysconfdir=/etc \
+	--datadir=/usr/share \
+	--includedir=/usr/include \
+	--libdir=/usr/lib \
+	--libexecdir=/usr/lib \
+	--localstatedir=/var/lib \
+	--sharedstatedir=/usr/com \
+	--mandir=/usr/share/man \
+	--infodir=/usr/share/info
+make
+
+%install
+%makeinstall
+
+%clean
+rm -rf $RPM_BUILD_ROOT
+
+%files
+%doc %{_mandir}/man5/*
+%doc %{_mandir}/man6/*
+%doc NEWS
+%doc AUTHORS
+%doc README
+%doc COPYING
+%doc CMDLINE
+%doc BUGS
+/usr/games/*
+
--- a/src/.gitignore
+++ b/src/.gitignore
@@ -5,3 +5,5 @@
 chocolate-doom
 chocolate-server
 *.exe
+tags
+TAGS
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -51,6 +51,7 @@
 d_iwad.c             d_iwad.h              \
 d_mode.c             d_mode.h              \
                      d_ticcmd.h            \
+deh_str.c            deh_str.h             \
 i_cdmus.c            i_cdmus.h             \
 i_endoom.c           i_endoom.h            \
 i_joystick.c         i_joystick.h          \
@@ -80,8 +81,12 @@
 
 # source files needed for FEATURE_DEHACKED
 
-FEATURE_DEHACKED_SOURCE_FILES=             \
-deh_str.c            deh_str.h
+FEATURE_DEHACKED_SOURCE_FILES =            \
+deh_defs.h                                 \
+deh_io.c             deh_io.h              \
+deh_main.c           deh_main.h            \
+deh_mapping.c        deh_mapping.h         \
+deh_text.c
 
 # source files needed for FEATURE_MULTIPLAYER
 
@@ -112,12 +117,15 @@
 i_sdlmusic.c                               \
 mus2mid.c            mus2mid.h
 
+# Some games support dehacked patches, some don't:
+
 SOURCE_FILES = $(COMMON_SOURCE_FILES)              \
                $(GAME_SOURCE_FILES)                \
-               $(FEATURE_DEHACKED_SOURCE_FILES)    \
                $(FEATURE_WAD_MERGE_SOURCE_FILES)   \
                $(FEATURE_SOUND_SOURCE_FILES)
 
+SOURCE_FILES_WITH_DEH = $(SOURCE_FILES)                    \
+                        $(FEATURE_DEHACKED_SOURCE_FILES)
 
 EXTRA_LIBS =                                               \
                $(top_builddir)/wince/libc_wince.a          \
@@ -129,9 +137,9 @@
                @SDLNET_LIBS@ 
 
 if HAVE_WINDRES
-@PROGRAM_PREFIX@doom_SOURCES=$(SOURCE_FILES) resource.rc
+@PROGRAM_PREFIX@doom_SOURCES=$(SOURCE_FILES_WITH_DEH) resource.rc
 else
-@PROGRAM_PREFIX@doom_SOURCES=$(SOURCE_FILES)
+@PROGRAM_PREFIX@doom_SOURCES=$(SOURCE_FILES_WITH_DEH)
 endif
 
 @PROGRAM_PREFIX@doom_LDADD = doom/libdoom.a $(EXTRA_LIBS)
@@ -153,9 +161,9 @@
 @PROGRAM_PREFIX@hexen_LDADD = hexen/libhexen.a $(EXTRA_LIBS)
 
 if HAVE_WINDRES
-@PROGRAM_PREFIX@strife_SOURCES=$(SOURCE_FILES) resource.rc
+@PROGRAM_PREFIX@strife_SOURCES=$(SOURCE_FILES_WITH_DEH) resource.rc
 else
-@PROGRAM_PREFIX@strife_SOURCES=$(SOURCE_FILES)
+@PROGRAM_PREFIX@strife_SOURCES=$(SOURCE_FILES_WITH_DEH)
 endif
 
 @PROGRAM_PREFIX@strife_LDADD = strife/libstrife.a $(EXTRA_LIBS)
@@ -191,7 +199,7 @@
 
 if HAVE_PYTHON
 
-icon.c : $(top_builddir)/data/doom.ico
+icon.c : $(top_builddir)/data/doom8.ico
 	$(top_builddir)/data/convert-icon $^ $@
 
 endif
--- a/src/d_iwad.c
+++ b/src/d_iwad.c
@@ -331,44 +331,85 @@
 
 #endif
 
+// Returns true if the specified path is a path to a file
+// of the specified name.
+
+static boolean DirIsFile(char *path, char *filename)
+{
+    size_t path_len;
+    size_t filename_len;
+
+    path_len = strlen(path);
+    filename_len = strlen(filename);
+
+    return path_len >= filename_len + 1
+        && path[path_len - filename_len - 1] == DIR_SEPARATOR
+        && !strcasecmp(&path[path_len - filename_len], filename);
+}
+
+// Check if the specified directory contains the specified IWAD
+// file, returning the full path to the IWAD if found, or NULL
+// if not found.
+
+static char *CheckDirectoryHasIWAD(char *dir, char *iwadname)
+{
+    char *filename; 
+
+    // As a special case, the "directory" may refer directly to an
+    // IWAD file if the path comes from DOOMWADDIR or DOOMWADPATH.
+    
+    if (DirIsFile(dir, iwadname) && M_FileExists(dir))
+    {
+        return strdup(dir);
+    }
+
+    // Construct the full path to the IWAD if it is located in
+    // this directory, and check if it exists.
+
+    filename = malloc(strlen(dir) + strlen(iwadname) + 3);
+
+    if (!strcmp(dir, "."))
+    {
+        strcpy(filename, iwadname);
+    }
+    else
+    {
+        sprintf(filename, "%s%c%s", dir, DIR_SEPARATOR, iwadname);
+    }
+
+    if (M_FileExists(filename))
+    {
+        return filename;
+    }
+
+    free(filename);
+
+    return NULL;
+}
+
 // Search a directory to try to find an IWAD
 // Returns the location of the IWAD if found, otherwise NULL.
 
 static char *SearchDirectoryForIWAD(char *dir, int mask, GameMission_t *mission)
 {
+    char *filename;
     size_t i;
 
     for (i=0; i<arrlen(iwads); ++i) 
     {
-        char *filename; 
-        char *iwadname;
-
         if (((1 << iwads[i].mission) & mask) == 0)
         {
             continue;
         }
 
-        iwadname = DEH_String(iwads[i].name);
-        
-        filename = malloc(strlen(dir) + strlen(iwadname) + 3);
+        filename = CheckDirectoryHasIWAD(dir, DEH_String(iwads[i].name));
 
-        if (!strcmp(dir, "."))
+        if (filename != NULL)
         {
-            strcpy(filename, iwadname);
-        }
-        else
-        {
-            sprintf(filename, "%s%c%s", dir, DIR_SEPARATOR, iwadname);
-        }
-
-        if (M_FileExists(filename))
-        {
             *mission = iwads[i].mission;
 
             return filename;
         }
-
-        free(filename);
     }
 
     return NULL;
@@ -529,7 +570,6 @@
 {
     char *buf;
     int i;
-    boolean exists;
     
     // Absolute path?
 
@@ -544,14 +584,21 @@
 
     for (i=0; i<num_iwad_dirs; ++i)
     {
+        // As a special case, if this is in DOOMWADDIR or DOOMWADPATH,
+        // the "directory" may actually refer directly to an IWAD
+        // file.
+
+        if (DirIsFile(iwad_dirs[i], name) && M_FileExists(iwad_dirs[i]))
+        {
+            return strdup(iwad_dirs[i]);
+        }
+
         // Construct a string for the full path
 
         buf = malloc(strlen(iwad_dirs[i]) + strlen(name) + 5);
         sprintf(buf, "%s%c%s", iwad_dirs[i], DIR_SEPARATOR, name);
 
-        exists = M_FileExists(buf);
-
-        if (exists)
+        if (M_FileExists(buf))
         {
             return buf;
         }
--- /dev/null
+++ b/src/deh_defs.h
@@ -1,0 +1,68 @@
+// Emacs style mode select   -*- C++ -*- 
+//-----------------------------------------------------------------------------
+//
+// Copyright(C) 2005 Simon Howard
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+// 02111-1307, USA.
+//
+//-----------------------------------------------------------------------------
+//
+// Definitions for use in the dehacked code
+//
+//-----------------------------------------------------------------------------
+
+#ifndef DEH_DEFS_H
+#define DEH_DEFS_H
+
+#include "md5.h"
+
+typedef struct deh_context_s deh_context_t;
+typedef struct deh_section_s deh_section_t;
+typedef void (*deh_section_init_t)(void);
+typedef void *(*deh_section_start_t)(deh_context_t *context, char *line);
+typedef void (*deh_section_end_t)(deh_context_t *context, void *tag);
+typedef void (*deh_line_parser_t)(deh_context_t *context, char *line, void *tag);
+typedef void (*deh_md5_hash_t)(md5_context_t *context);
+
+struct deh_section_s
+{
+    char *name;
+
+    // Called on startup to initialize code
+
+    deh_section_init_t init;
+    
+    // This is called when a new section is started.  The pointer
+    // returned is used as a tag for the following calls.
+
+    deh_section_start_t start;
+
+    // This is called for each line in the section
+
+    deh_line_parser_t line_parser;
+
+    // This is called at the end of the section for any cleanup
+
+    deh_section_end_t end;
+
+    // Called when generating an MD5 sum of the dehacked state
+
+    deh_md5_hash_t md5_hash;
+};
+
+#endif /* #ifndef DEH_DEFS_H */
+
+
--- /dev/null
+++ b/src/deh_io.c
@@ -1,0 +1,211 @@
+// Emacs style mode select   -*- C++ -*- 
+//-----------------------------------------------------------------------------
+//
+// Copyright(C) 2005 Simon Howard
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+// 02111-1307, USA.
+//
+//-----------------------------------------------------------------------------
+//
+// Dehacked I/O code (does all reads from dehacked files)
+//
+//-----------------------------------------------------------------------------
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "i_system.h"
+#include "z_zone.h"
+
+#include "deh_defs.h"
+#include "deh_io.h"
+
+struct deh_context_s
+{
+    FILE *stream;
+    char *filename;
+    int linenum;
+    boolean last_was_newline;
+    char *readbuffer;
+    int readbuffer_size;
+};
+
+// Open a dehacked file for reading
+// Returns NULL if open failed
+
+deh_context_t *DEH_OpenFile(char *filename)
+{
+    FILE *fstream;
+    deh_context_t *context;
+    
+    fstream = fopen(filename, "r");
+
+    if (fstream == NULL)
+        return NULL;
+
+    context = Z_Malloc(sizeof(*context), PU_STATIC, NULL);
+    context->stream = fstream;
+    
+    // Initial read buffer size of 128 bytes
+
+    context->readbuffer_size = 128;
+    context->readbuffer = Z_Malloc(context->readbuffer_size, PU_STATIC, NULL);
+    context->filename = filename;
+    context->linenum = 0;
+    context->last_was_newline = true;
+
+    return context;
+}
+
+// Close dehacked file
+
+void DEH_CloseFile(deh_context_t *context)
+{
+    fclose(context->stream);
+    Z_Free(context->readbuffer);
+    Z_Free(context);
+}
+
+// Reads a single character from a dehacked file
+
+int DEH_GetChar(deh_context_t *context)
+{
+    int result;
+   
+    // Read characters, but ignore carriage returns
+    // Essentially this is a DOS->Unix conversion
+
+    do 
+    {
+        if (feof(context->stream))
+        {
+            // end of file
+
+            result = -1;
+        }
+        else
+        {
+            result = fgetc(context->stream);
+        }
+
+    } while (result == '\r');
+
+    // Track the current line number
+
+    if (context->last_was_newline)
+    {
+        ++context->linenum;
+    }
+    
+    context->last_was_newline = result == '\n';
+    
+    return result;
+}
+
+// Increase the read buffer size
+
+static void IncreaseReadBuffer(deh_context_t *context)
+{
+    char *newbuffer;
+    int newbuffer_size;
+
+    newbuffer_size = context->readbuffer_size * 2;
+    newbuffer = Z_Malloc(newbuffer_size, PU_STATIC, NULL);
+
+    memcpy(newbuffer, context->readbuffer, context->readbuffer_size);
+
+    Z_Free(context->readbuffer);
+
+    context->readbuffer = newbuffer;
+    context->readbuffer_size = newbuffer_size;
+}
+
+// Read a whole line
+
+char *DEH_ReadLine(deh_context_t *context)
+{
+    int c;
+    int pos;
+
+    for (pos = 0;;)
+    {
+        c = DEH_GetChar(context);
+
+        if (c < 0)
+        {
+            // end of file
+
+            return NULL;
+        }
+
+        // cope with lines of any length: increase the buffer size
+
+        if (pos >= context->readbuffer_size)
+        {
+            IncreaseReadBuffer(context);
+        }
+
+        if (c == '\n')
+        {
+            // end of line: a full line has been read
+
+            context->readbuffer[pos] = '\0';
+            break;
+        }
+        else if (c != '\0')
+        {
+            // normal character; don't allow NUL characters to be
+            // added.
+
+            context->readbuffer[pos] = (char) c;
+            ++pos;
+        }
+    }
+    
+    return context->readbuffer;
+}
+
+void DEH_Warning(deh_context_t *context, char *msg, ...)
+{
+    va_list args;
+
+    va_start(args, msg);
+    
+    fprintf(stderr, "%s:%i: warning: ", context->filename, context->linenum);
+    vfprintf(stderr, msg, args);
+    fprintf(stderr, "\n");
+
+    va_end(args);
+}
+
+void DEH_Error(deh_context_t *context, char *msg, ...)
+{
+    va_list args;
+
+    va_start(args, msg);
+    
+    fprintf(stderr, "%s:%i: ", context->filename, context->linenum);
+    vfprintf(stderr, msg, args);
+    fprintf(stderr, "\n");
+
+    va_end(args);
+
+    I_Error("Error parsing dehacked file");
+}
+
+
--- /dev/null
+++ b/src/deh_io.h
@@ -1,0 +1,40 @@
+// Emacs style mode select   -*- C++ -*- 
+//-----------------------------------------------------------------------------
+//
+// Copyright(C) 2005 Simon Howard
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+// 02111-1307, USA.
+//
+//-----------------------------------------------------------------------------
+//
+// Dehacked I/O code (does all reads from dehacked files)
+//
+//-----------------------------------------------------------------------------
+
+#ifndef DEH_IO_H
+#define DEH_IO_H
+
+#include "deh_defs.h"
+
+deh_context_t *DEH_OpenFile(char *filename);
+void DEH_CloseFile(deh_context_t *context);
+int DEH_GetChar(deh_context_t *context);
+char *DEH_ReadLine(deh_context_t *context);
+void DEH_Error(deh_context_t *context, char *msg, ...);
+void DEH_Warning(deh_context_t *context, char *msg, ...);
+
+#endif /* #ifndef DEH_IO_H */
+
--- /dev/null
+++ b/src/deh_main.c
@@ -1,0 +1,384 @@
+// Emacs style mode select   -*- C++ -*- 
+//-----------------------------------------------------------------------------
+//
+// Copyright(C) 2005 Simon Howard
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+// 02111-1307, USA.
+//
+//-----------------------------------------------------------------------------
+//
+// Main dehacked code
+//
+//-----------------------------------------------------------------------------
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+
+#include "doomtype.h"
+#include "d_iwad.h"
+#include "m_argv.h"
+
+#include "deh_defs.h"
+#include "deh_io.h"
+
+extern deh_section_t *deh_section_types[];
+extern char *deh_signatures[];
+
+// If true, we can do long string replacements.
+
+boolean deh_allow_long_strings = false;
+
+// If true, we can do cheat replacements longer than the originals.
+
+boolean deh_allow_long_cheats = false;
+
+// If false, dehacked cheat replacements are ignored.
+
+boolean deh_apply_cheats = true;
+
+void DEH_Checksum(md5_digest_t digest)
+{
+    md5_context_t md5_context;
+    unsigned int i;
+
+    MD5_Init(&md5_context);
+
+    for (i=0; deh_section_types[i] != NULL; ++i)
+    {
+        if (deh_section_types[i]->md5_hash != NULL)
+        {
+            deh_section_types[i]->md5_hash(&md5_context);
+        }
+    }
+
+    MD5_Final(digest, &md5_context);
+}
+
+// Called on startup to call the Init functions
+
+static void InitializeSections(void)
+{
+    unsigned int i;
+
+    for (i=0; deh_section_types[i] != NULL; ++i)
+    {
+        if (deh_section_types[i]->init != NULL)
+        {
+            deh_section_types[i]->init();
+        }
+    }
+}
+
+// Given a section name, get the section structure which corresponds
+
+static deh_section_t *GetSectionByName(char *name)
+{
+    unsigned int i;
+
+    for (i=0; deh_section_types[i] != NULL; ++i)
+    {
+        if (!strcasecmp(deh_section_types[i]->name, name))
+        {
+            return deh_section_types[i];
+        }
+    }
+
+    return NULL;
+}
+
+// Is the string passed just whitespace?
+
+static boolean IsWhitespace(char *s)
+{
+    for (; *s; ++s)
+    {
+        if (!isspace(*s))
+            return false;
+    }
+
+    return true;
+}
+
+// Strip whitespace from the start and end of a string
+
+static char *CleanString(char *s)
+{
+    char *strending;
+
+    // Leading whitespace
+
+    while (*s && isspace(*s))
+        ++s;
+
+    // Trailing whitespace
+   
+    strending = s + strlen(s) - 1;
+
+    while (strlen(s) > 0 && isspace(*strending))
+    {
+        *strending = '\0';
+        --strending;
+    }
+
+    return s;
+}
+
+// This pattern is used a lot of times in different sections, 
+// an assignment is essentially just a statement of the form:
+//
+// Variable Name = Value
+//
+// The variable name can include spaces or any other characters.
+// The string is split on the '=', essentially.
+//
+// Returns true if read correctly
+
+boolean DEH_ParseAssignment(char *line, char **variable_name, char **value)
+{
+    char *p;
+
+    // find the equals
+    
+    p = strchr(line, '=');
+
+    if (p == NULL && p-line > 2)
+    {
+        return false;
+    }
+
+    // variable name at the start
+    // turn the '=' into a \0 to terminate the string here
+
+    *p = '\0';
+    *variable_name = CleanString(line);
+    
+    // value immediately follows the '='
+    
+    *value = CleanString(p+1);
+    
+    return true;
+}
+
+static boolean CheckSignatures(deh_context_t *context)
+{
+    size_t i;
+    char *line;
+    
+    // Read the first line
+
+    line = DEH_ReadLine(context);
+
+    if (line == NULL)
+    {
+        return false;
+    }
+
+    // Check all signatures to see if one matches
+
+    for (i=0; deh_signatures[i] != NULL; ++i)
+    {
+        if (!strcmp(deh_signatures[i], line))
+        {
+            return true;
+        }
+    }
+
+    return false;
+}
+
+// Parses a comment string in a dehacked file.
+
+static void DEH_ParseComment(char *comment)
+{
+    // Allow comments containing this special value to allow string
+    // replacements longer than those permitted by DOS dehacked.
+    // This allows us to use a dehacked patch for doing string 
+    // replacements for emulating Chex Quest.
+    //
+    // If you use this, your dehacked patch may not work in Vanilla
+    // Doom.
+
+    if (strstr(comment, "*allow-long-strings*") != NULL)
+    {
+        deh_allow_long_strings = true;
+    }
+
+    // Allow magic comments to allow longer cheat replacements than
+    // those permitted by DOS dehacked.  This is also for Chex
+    // Quest.
+
+    if (strstr(comment, "*allow-long-cheats*") != NULL)
+    {
+        deh_allow_long_cheats = true;
+    }
+}
+
+// Parses a dehacked file by reading from the context
+
+static void DEH_ParseContext(deh_context_t *context)
+{
+    deh_section_t *current_section = NULL;
+    char section_name[20];
+    void *tag = NULL;
+    char *line;
+    
+    // Read the header and check it matches the signature
+
+    if (!CheckSignatures(context))
+    {
+        DEH_Error(context, "This is not a valid dehacked patch file!");
+    }
+
+    deh_allow_long_strings = false;
+    deh_allow_long_cheats = false;
+    
+    // Read the file
+    
+    for (;;) 
+    {
+        // read a new line
+ 
+        line = DEH_ReadLine(context);
+
+        // end of file?
+
+        if (line == NULL)
+            return;
+
+        while (line[0] != '\0' && isspace(line[0]))
+            ++line;
+
+        if (line[0] == '#')
+        {
+            // comment
+
+            DEH_ParseComment(line);
+            continue;
+        }
+
+        if (IsWhitespace(line))
+        {
+            if (current_section != NULL)
+            {
+                // end of section
+
+                if (current_section->end != NULL)
+                {
+                    current_section->end(context, tag);
+                }
+
+                //printf("end %s tag\n", current_section->name);
+                current_section = NULL;
+            }
+        }
+        else
+        {
+            if (current_section != NULL)
+            {
+                // parse this line
+
+                current_section->line_parser(context, line, tag);
+            }
+            else
+            {
+                // possibly the start of a new section
+
+                sscanf(line, "%19s", section_name);
+
+                current_section = GetSectionByName(section_name);
+                
+                if (current_section != NULL)
+                {
+                    tag = current_section->start(context, line);
+                    //printf("started %s tag\n", section_name);
+                }
+                else
+                {
+                    //printf("unknown section name %s\n", section_name);
+                }
+            }
+        }
+    }
+}
+
+// Parses a dehacked file
+
+int DEH_LoadFile(char *filename)
+{
+    deh_context_t *context;
+
+    printf(" loading %s\n", filename);
+
+    context = DEH_OpenFile(filename);
+
+    if (context == NULL)
+    {
+        fprintf(stderr, "DEH_LoadFile: Unable to open %s\n", filename);
+        return 0;
+    }
+    
+    DEH_ParseContext(context);
+    
+    DEH_CloseFile(context);
+
+    return 1;
+}
+
+// Checks the command line for -deh argument
+
+void DEH_Init(void)
+{
+    char *filename;
+    int p;
+
+    InitializeSections();
+
+    //!
+    // @category mod
+    //
+    // Ignore cheats in dehacked files.
+    //
+
+    if (M_CheckParm("-nocheats") > 0) 
+    {
+	deh_apply_cheats = false;
+    }
+
+    //!
+    // @arg <files>
+    // @category mod
+    //
+    // Load the given dehacked patch(es)
+    //
+
+    p = M_CheckParm("-deh");
+
+    if (p > 0)
+    {
+        ++p;
+
+        while (p < myargc && myargv[p][0] != '-')
+        {
+            filename = D_TryFindWADByName(myargv[p]);
+            DEH_LoadFile(filename);
+            ++p;
+        }
+    }
+}
+
+
--- /dev/null
+++ b/src/deh_main.h
@@ -1,0 +1,54 @@
+// Emacs style mode select   -*- C++ -*- 
+//-----------------------------------------------------------------------------
+//
+// Copyright(C) 2005 Simon Howard
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+// 02111-1307, USA.
+//
+//-----------------------------------------------------------------------------
+//
+// Dehacked entrypoint and common code
+//
+//-----------------------------------------------------------------------------
+
+#ifndef DEH_MAIN_H
+#define DEH_MAIN_H
+
+#include "doomtype.h"
+#include "doomfeatures.h"
+#include "md5.h"
+#include "deh_str.h"
+
+// These are the limits that dehacked uses (from dheinit.h in the dehacked
+// source).  If these limits are exceeded, it does not generate an error, but
+// a warning is displayed.
+
+#define DEH_VANILLA_NUMSTATES 966
+#define DEH_VANILLA_NUMSFX 107
+
+void DEH_Init(void);
+int DEH_LoadFile(char *filename);
+
+boolean DEH_ParseAssignment(char *line, char **variable_name, char **value);
+
+void DEH_Checksum(md5_digest_t digest);
+
+extern boolean deh_allow_long_strings;
+extern boolean deh_allow_long_cheats;
+extern boolean deh_apply_cheats;
+
+#endif /* #ifndef DEH_MAIN_H */
+
--- /dev/null
+++ b/src/deh_mapping.c
@@ -1,0 +1,133 @@
+// Emacs style mode select   -*- C++ -*- 
+//-----------------------------------------------------------------------------
+//
+// Copyright(C) 2005 Simon Howard
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+// 02111-1307, USA.
+//
+//-----------------------------------------------------------------------------
+//
+// Dehacked "mapping" code
+// Allows the fields in structures to be mapped out and accessed by
+// name
+//
+//-----------------------------------------------------------------------------
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "doomtype.h"
+#include "i_system.h"
+#include "deh_mapping.h"
+
+//
+// Set the value of a particular field in a structure by name
+//
+
+boolean DEH_SetMapping(deh_context_t *context, deh_mapping_t *mapping, 
+                       void *structptr, char *name, int value)
+{
+    int i;
+
+    for (i=0; mapping->entries[i].name != NULL; ++i)
+    {
+        deh_mapping_entry_t *entry = &mapping->entries[i];
+
+        if (!strcasecmp(entry->name, name))
+        {
+            void *location;
+
+            if (entry->location == NULL)
+            {
+                DEH_Warning(context, "Field '%s' is unsupported", name);
+                return false;
+            }
+
+            location = (uint8_t *)structptr + ((uint8_t *)entry->location - (uint8_t *)mapping->base);
+
+     //       printf("Setting %p::%s to %i (%i bytes)\n",
+     //               structptr, name, value, entry->size);
+                
+            switch (entry->size)
+            {
+                case 1:
+                    * ((uint8_t *) location) = value;
+                    break;
+                case 2:
+                    * ((uint16_t *) location) = value;
+                    break;
+                case 4:
+                    * ((uint32_t *) location) = value;
+                    break;
+                default:
+                    DEH_Error(context, "Unknown field type for '%s' (BUG)", name);
+                    return false;
+            }
+
+            return true;
+        }
+    }
+
+    // field with this name not found
+
+    DEH_Warning(context, "Field named '%s' not found", name);
+
+    return false;
+}
+
+void DEH_StructMD5Sum(md5_context_t *context, deh_mapping_t *mapping,
+                      void *structptr)
+{
+    int i;
+
+    // Go through each mapping
+
+    for (i=0; mapping->entries[i].name != NULL; ++i)
+    {
+        deh_mapping_entry_t *entry = &mapping->entries[i];
+        void *location;
+
+        if (entry->location == NULL)
+        {
+            // Unsupported field
+
+            continue;
+        }
+
+        // Add in data for this field
+
+        location = (uint8_t *)structptr + ((uint8_t *)entry->location - (uint8_t *)mapping->base);
+
+        switch (entry->size)
+        {
+            case 1:
+                MD5_UpdateInt32(context, *((uint8_t *) location));
+                break;
+            case 2:
+                MD5_UpdateInt32(context, *((uint16_t *) location));
+                break;
+            case 4:
+                MD5_UpdateInt32(context, *((uint32_t *) location));
+                break;
+            default:
+                I_Error("Unknown dehacked mapping field type for '%s' (BUG)", 
+                        entry->name);
+                break;
+        }
+    }
+}
+
--- /dev/null
+++ b/src/deh_mapping.h
@@ -1,0 +1,90 @@
+// Emacs style mode select   -*- C++ -*- 
+//-----------------------------------------------------------------------------
+//
+// Copyright(C) 2005 Simon Howard
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+// 02111-1307, USA.
+//
+//-----------------------------------------------------------------------------
+//
+// Dehacked "mapping" code
+// Allows the fields in structures to be mapped out and accessed by
+// name
+//
+//-----------------------------------------------------------------------------
+
+#ifndef DEH_MAPPING_H
+#define DEH_MAPPING_H
+
+#include "doomtype.h"
+#include "deh_io.h"
+#include "md5.h"
+
+#define DEH_BEGIN_MAPPING(mapping_name, structname)           \
+    static structname deh_mapping_base;                       \
+    static deh_mapping_t mapping_name =                       \
+    {                                                         \
+        &deh_mapping_base,                                    \
+        {
+
+#define DEH_MAPPING(deh_name, fieldname)                      \
+             {deh_name, &deh_mapping_base.fieldname,          \
+                 sizeof(deh_mapping_base.fieldname)},
+
+#define DEH_UNSUPPORTED_MAPPING(deh_name)                     \
+             {deh_name, NULL, -1},
+            
+#define DEH_END_MAPPING                                       \
+             {NULL, NULL, -1}                                 \
+        }                                                     \
+    };
+
+    
+
+#define MAX_MAPPING_ENTRIES 32
+
+typedef struct deh_mapping_s deh_mapping_t;
+typedef struct deh_mapping_entry_s deh_mapping_entry_t;
+
+struct deh_mapping_entry_s 
+{
+    // field name
+   
+    char *name;
+
+    // location relative to the base in the deh_mapping_t struct
+    // If this is NULL, it is an unsupported mapping
+
+    void *location;
+
+    // field size
+
+    int size;
+};
+
+struct deh_mapping_s
+{
+    void *base;
+    deh_mapping_entry_t entries[MAX_MAPPING_ENTRIES];
+};
+
+boolean DEH_SetMapping(deh_context_t *context, deh_mapping_t *mapping, 
+                       void *structptr, char *name, int value);
+void DEH_StructMD5Sum(md5_context_t *context, deh_mapping_t *mapping,
+                      void *structptr);
+
+#endif /* #ifndef DEH_MAPPING_H */
+
--- /dev/null
+++ b/src/deh_text.c
@@ -1,0 +1,127 @@
+// Emacs style mode select   -*- C++ -*- 
+//-----------------------------------------------------------------------------
+//
+// Copyright(C) 2005 Simon Howard
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+// 02111-1307, USA.
+//
+//-----------------------------------------------------------------------------
+//
+// Parses Text substitution sections in dehacked files
+//
+//-----------------------------------------------------------------------------
+
+#include <stdio.h>
+#include <string.h>
+
+#include "doomtype.h"
+
+#include "z_zone.h"
+
+#include "deh_defs.h"
+#include "deh_io.h"
+#include "deh_main.h"
+
+// Given a string length, find the maximum length of a 
+// string that can replace it.
+
+static int TXT_MaxStringLength(int len)
+{
+    // Enough bytes for the string and the NUL terminator
+
+    len += 1;
+
+    // All strings in doom.exe are on 4-byte boundaries, so we may be able
+    // to support a slightly longer string.
+    // Extend up to the next 4-byte boundary
+
+    len += (4 - (len % 4)) % 4;
+            
+    // Less one for the NUL terminator.
+
+    return len - 1;
+}
+
+static void *DEH_TextStart(deh_context_t *context, char *line)
+{
+    char *from_text, *to_text;
+    int fromlen, tolen;
+    int i;
+    
+    if (sscanf(line, "Text %i %i", &fromlen, &tolen) != 2)
+    {
+        DEH_Warning(context, "Parse error on section start");
+        return NULL;
+    }
+
+    // Only allow string replacements that are possible in Vanilla Doom.  
+    // Chocolate Doom is unforgiving!
+
+    if (!deh_allow_long_strings && tolen > TXT_MaxStringLength(fromlen))
+    {
+        DEH_Error(context, "Replacement string is longer than the maximum "
+                           "possible in doom.exe");
+        return NULL;
+    }
+
+    from_text = Z_Malloc(fromlen + 1, PU_STATIC, NULL);
+    to_text = Z_Malloc(tolen + 1, PU_STATIC, NULL);
+
+    // read in the "from" text
+
+    for (i=0; i<fromlen; ++i)
+    {
+        int c;
+
+        c = DEH_GetChar(context);
+            
+        from_text[i] = c;
+    }
+
+    from_text[fromlen] = '\0';
+
+    // read in the "to" text
+
+    for (i=0; i<tolen; ++i)
+    {
+        int c;
+
+        c = DEH_GetChar(context);
+            
+        to_text[i] = c;
+    }
+    to_text[tolen] = '\0';
+
+    DEH_AddStringReplacement(from_text, to_text);
+    
+    return NULL;
+}
+
+static void DEH_TextParseLine(deh_context_t *context, char *line, void *tag)
+{
+    // not used
+}
+
+deh_section_t deh_section_text =
+{
+    "Text",
+    NULL,
+    DEH_TextStart,
+    DEH_TextParseLine,
+    NULL,
+    NULL,
+};
+
--- a/src/doom/.gitignore
+++ b/src/doom/.gitignore
@@ -5,3 +5,5 @@
 chocolate-doom
 chocolate-server
 *.exe
+tags
+TAGS
--- a/src/doom/Makefile.am
+++ b/src/doom/Makefile.am
@@ -63,15 +63,11 @@
 FEATURE_DEHACKED_SOURCE_FILES =            \
 deh_ammo.c                                 \
 deh_cheat.c                                \
-deh_defs.h                                 \
+deh_doom.c                                 \
 deh_frame.c                                \
-deh_io.c             deh_io.h              \
-deh_main.c           deh_main.h            \
-deh_mapping.c        deh_mapping.h         \
 deh_misc.c           deh_misc.h            \
 deh_ptr.c                                  \
 deh_sound.c                                \
-deh_text.c                                 \
 deh_thing.c                                \
 deh_weapon.c
 
--- a/src/doom/deh_defs.h
+++ /dev/null
@@ -1,68 +1,0 @@
-// Emacs style mode select   -*- C++ -*- 
-//-----------------------------------------------------------------------------
-//
-// Copyright(C) 2005 Simon Howard
-//
-// This program is free software; you can redistribute it and/or
-// modify it under the terms of the GNU General Public License
-// as published by the Free Software Foundation; either version 2
-// of the License, or (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with this program; if not, write to the Free Software
-// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
-// 02111-1307, USA.
-//
-//-----------------------------------------------------------------------------
-//
-// Definitions for use in the dehacked code
-//
-//-----------------------------------------------------------------------------
-
-#ifndef DEH_DEFS_H
-#define DEH_DEFS_H
-
-#include "md5.h"
-
-typedef struct deh_context_s deh_context_t;
-typedef struct deh_section_s deh_section_t;
-typedef void (*deh_section_init_t)(void);
-typedef void *(*deh_section_start_t)(deh_context_t *context, char *line);
-typedef void (*deh_section_end_t)(deh_context_t *context, void *tag);
-typedef void (*deh_line_parser_t)(deh_context_t *context, char *line, void *tag);
-typedef void (*deh_md5_hash_t)(md5_context_t *context);
-
-struct deh_section_s
-{
-    char *name;
-
-    // Called on startup to initialize code
-
-    deh_section_init_t init;
-    
-    // This is called when a new section is started.  The pointer
-    // returned is used as a tag for the following calls.
-
-    deh_section_start_t start;
-
-    // This is called for each line in the section
-
-    deh_line_parser_t line_parser;
-
-    // This is called at the end of the section for any cleanup
-
-    deh_section_end_t end;
-
-    // Called when generating an MD5 sum of the dehacked state
-
-    deh_md5_hash_t md5_hash;
-};
-
-#endif /* #ifndef DEH_DEFS_H */
-
-
--- /dev/null
+++ b/src/doom/deh_doom.c
@@ -1,0 +1,74 @@
+// Emacs style mode select   -*- C++ -*-
+//-----------------------------------------------------------------------------
+//
+// Copyright(C) 2005 Simon Howard
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+// 02111-1307, USA.
+//
+//-----------------------------------------------------------------------------
+//
+// Top-level dehacked definitions for Doom dehacked.
+//
+//-----------------------------------------------------------------------------
+
+#include <stdlib.h>
+#include "deh_defs.h"
+#include "deh_main.h"
+
+char *deh_signatures[] =
+{
+    "Patch File for DeHackEd v2.3",
+    "Patch File for DeHackEd v3.0",
+    NULL
+};
+
+// deh_ammo.c:
+extern deh_section_t deh_section_ammo;
+// deh_cheat.c:
+extern deh_section_t deh_section_cheat;
+// deh_frame.c:
+extern deh_section_t deh_section_frame;
+// deh_misc.c:
+extern deh_section_t deh_section_misc;
+// deh_ptr.c:
+extern deh_section_t deh_section_pointer;
+// deh_sound.c
+extern deh_section_t deh_section_sound;
+// deh_text.c:
+extern deh_section_t deh_section_text;
+// deh_thing.c:
+extern deh_section_t deh_section_thing;
+// deh_weapon.c:
+extern deh_section_t deh_section_weapon;
+
+//
+// List of section types:
+//
+
+deh_section_t *deh_section_types[] =
+{
+    &deh_section_ammo,
+    &deh_section_cheat,
+    &deh_section_frame,
+    &deh_section_misc,
+    &deh_section_pointer,
+    &deh_section_sound,
+    &deh_section_text,
+    &deh_section_thing,
+    &deh_section_weapon,
+    NULL
+};
+
--- a/src/doom/deh_io.c
+++ /dev/null
@@ -1,211 +1,0 @@
-// Emacs style mode select   -*- C++ -*- 
-//-----------------------------------------------------------------------------
-//
-// Copyright(C) 2005 Simon Howard
-//
-// This program is free software; you can redistribute it and/or
-// modify it under the terms of the GNU General Public License
-// as published by the Free Software Foundation; either version 2
-// of the License, or (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with this program; if not, write to the Free Software
-// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
-// 02111-1307, USA.
-//
-//-----------------------------------------------------------------------------
-//
-// Dehacked I/O code (does all reads from dehacked files)
-//
-//-----------------------------------------------------------------------------
-
-#include <stdarg.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-
-#include "i_system.h"
-#include "z_zone.h"
-
-#include "deh_defs.h"
-#include "deh_io.h"
-
-struct deh_context_s
-{
-    FILE *stream;
-    char *filename;
-    int linenum;
-    boolean last_was_newline;
-    char *readbuffer;
-    int readbuffer_size;
-};
-
-// Open a dehacked file for reading
-// Returns NULL if open failed
-
-deh_context_t *DEH_OpenFile(char *filename)
-{
-    FILE *fstream;
-    deh_context_t *context;
-    
-    fstream = fopen(filename, "r");
-
-    if (fstream == NULL)
-        return NULL;
-
-    context = Z_Malloc(sizeof(*context), PU_STATIC, NULL);
-    context->stream = fstream;
-    
-    // Initial read buffer size of 128 bytes
-
-    context->readbuffer_size = 128;
-    context->readbuffer = Z_Malloc(context->readbuffer_size, PU_STATIC, NULL);
-    context->filename = filename;
-    context->linenum = 0;
-    context->last_was_newline = true;
-
-    return context;
-}
-
-// Close dehacked file
-
-void DEH_CloseFile(deh_context_t *context)
-{
-    fclose(context->stream);
-    Z_Free(context->readbuffer);
-    Z_Free(context);
-}
-
-// Reads a single character from a dehacked file
-
-int DEH_GetChar(deh_context_t *context)
-{
-    int result;
-   
-    // Read characters, but ignore carriage returns
-    // Essentially this is a DOS->Unix conversion
-
-    do 
-    {
-        if (feof(context->stream))
-        {
-            // end of file
-
-            result = -1;
-        }
-        else
-        {
-            result = fgetc(context->stream);
-        }
-
-    } while (result == '\r');
-
-    // Track the current line number
-
-    if (context->last_was_newline)
-    {
-        ++context->linenum;
-    }
-    
-    context->last_was_newline = result == '\n';
-    
-    return result;
-}
-
-// Increase the read buffer size
-
-static void IncreaseReadBuffer(deh_context_t *context)
-{
-    char *newbuffer;
-    int newbuffer_size;
-
-    newbuffer_size = context->readbuffer_size * 2;
-    newbuffer = Z_Malloc(newbuffer_size, PU_STATIC, NULL);
-
-    memcpy(newbuffer, context->readbuffer, context->readbuffer_size);
-
-    Z_Free(context->readbuffer);
-
-    context->readbuffer = newbuffer;
-    context->readbuffer_size = newbuffer_size;
-}
-
-// Read a whole line
-
-char *DEH_ReadLine(deh_context_t *context)
-{
-    int c;
-    int pos;
-
-    for (pos = 0;;)
-    {
-        c = DEH_GetChar(context);
-
-        if (c < 0)
-        {
-            // end of file
-
-            return NULL;
-        }
-
-        // cope with lines of any length: increase the buffer size
-
-        if (pos >= context->readbuffer_size)
-        {
-            IncreaseReadBuffer(context);
-        }
-
-        if (c == '\n')
-        {
-            // end of line: a full line has been read
-
-            context->readbuffer[pos] = '\0';
-            break;
-        }
-        else if (c != '\0')
-        {
-            // normal character; don't allow NUL characters to be
-            // added.
-
-            context->readbuffer[pos] = (char) c;
-            ++pos;
-        }
-    }
-    
-    return context->readbuffer;
-}
-
-void DEH_Warning(deh_context_t *context, char *msg, ...)
-{
-    va_list args;
-
-    va_start(args, msg);
-    
-    fprintf(stderr, "%s:%i: warning: ", context->filename, context->linenum);
-    vfprintf(stderr, msg, args);
-    fprintf(stderr, "\n");
-
-    va_end(args);
-}
-
-void DEH_Error(deh_context_t *context, char *msg, ...)
-{
-    va_list args;
-
-    va_start(args, msg);
-    
-    fprintf(stderr, "%s:%i: ", context->filename, context->linenum);
-    vfprintf(stderr, msg, args);
-    fprintf(stderr, "\n");
-
-    va_end(args);
-
-    I_Error("Error parsing dehacked file");
-}
-
-
--- a/src/doom/deh_io.h
+++ /dev/null
@@ -1,40 +1,0 @@
-// Emacs style mode select   -*- C++ -*- 
-//-----------------------------------------------------------------------------
-//
-// Copyright(C) 2005 Simon Howard
-//
-// This program is free software; you can redistribute it and/or
-// modify it under the terms of the GNU General Public License
-// as published by the Free Software Foundation; either version 2
-// of the License, or (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with this program; if not, write to the Free Software
-// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
-// 02111-1307, USA.
-//
-//-----------------------------------------------------------------------------
-//
-// Dehacked I/O code (does all reads from dehacked files)
-//
-//-----------------------------------------------------------------------------
-
-#ifndef DEH_IO_H
-#define DEH_IO_H
-
-#include "deh_defs.h"
-
-deh_context_t *DEH_OpenFile(char *filename);
-void DEH_CloseFile(deh_context_t *context);
-int DEH_GetChar(deh_context_t *context);
-char *DEH_ReadLine(deh_context_t *context);
-void DEH_Error(deh_context_t *context, char *msg, ...);
-void DEH_Warning(deh_context_t *context, char *msg, ...);
-
-#endif /* #ifndef DEH_IO_H */
-
--- a/src/doom/deh_main.c
+++ /dev/null
@@ -1,423 +1,0 @@
-// Emacs style mode select   -*- C++ -*- 
-//-----------------------------------------------------------------------------
-//
-// Copyright(C) 2005 Simon Howard
-//
-// This program is free software; you can redistribute it and/or
-// modify it under the terms of the GNU General Public License
-// as published by the Free Software Foundation; either version 2
-// of the License, or (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with this program; if not, write to the Free Software
-// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
-// 02111-1307, USA.
-//
-//-----------------------------------------------------------------------------
-//
-// Main dehacked code
-//
-//-----------------------------------------------------------------------------
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <ctype.h>
-
-#include "doomtype.h"
-#include "d_iwad.h"
-#include "m_argv.h"
-
-#include "deh_defs.h"
-#include "deh_io.h"
-
-static char *deh_signatures[] = 
-{
-    "Patch File for DeHackEd v2.3",
-    "Patch File for DeHackEd v3.0",
-};
-
-// deh_ammo.c:
-extern deh_section_t deh_section_ammo;
-// deh_cheat.c:
-extern deh_section_t deh_section_cheat;
-// deh_frame.c:
-extern deh_section_t deh_section_frame;
-// deh_misc.c:
-extern deh_section_t deh_section_misc;
-// deh_ptr.c:
-extern deh_section_t deh_section_pointer;
-// deh_sound.c
-extern deh_section_t deh_section_sound;
-// deh_text.c:
-extern deh_section_t deh_section_text;
-// deh_thing.c: 
-extern deh_section_t deh_section_thing;
-// deh_weapon.c: 
-extern deh_section_t deh_section_weapon;
-
-// If true, we can do long string replacements.
-
-boolean deh_allow_long_strings = false;
-
-// If true, we can do cheat replacements longer than the originals.
-
-boolean deh_allow_long_cheats = false;
-
-// If false, dehacked cheat replacements are ignored.
-
-boolean deh_apply_cheats = true;
-
-//
-// List of section types:
-//
-
-static deh_section_t *section_types[] =
-{
-    &deh_section_ammo,
-    &deh_section_cheat,
-    &deh_section_frame,
-    &deh_section_misc,
-    &deh_section_pointer,
-    &deh_section_sound,
-    &deh_section_text,
-    &deh_section_thing,
-    &deh_section_weapon,
-};
-
-void DEH_Checksum(md5_digest_t digest)
-{
-    md5_context_t md5_context;
-    unsigned int i;
-
-    MD5_Init(&md5_context);
-
-    for (i=0; i<arrlen(section_types); ++i)
-    {
-        if (section_types[i]->md5_hash != NULL)
-        {
-            section_types[i]->md5_hash(&md5_context);
-        }
-    }
-
-    MD5_Final(digest, &md5_context);
-}
-
-// Called on startup to call the Init functions
-
-static void InitializeSections(void)
-{
-    unsigned int i;
-
-    for (i=0; i<arrlen(section_types); ++i)
-    {
-        if (section_types[i]->init != NULL)
-        {
-            section_types[i]->init();
-        }
-    }
-}
-
-// Given a section name, get the section structure which corresponds
-
-static deh_section_t *GetSectionByName(char *name)
-{
-    unsigned int i;
-
-    for (i=0; i<arrlen(section_types); ++i)
-    {
-        if (!strcasecmp(section_types[i]->name, name))
-        {
-            return section_types[i];
-        }
-    }
-
-    return NULL;
-}
-
-// Is the string passed just whitespace?
-
-static boolean IsWhitespace(char *s)
-{
-    for (; *s; ++s)
-    {
-        if (!isspace(*s))
-            return false;
-    }
-
-    return true;
-}
-
-// Strip whitespace from the start and end of a string
-
-static char *CleanString(char *s)
-{
-    char *strending;
-
-    // Leading whitespace
-
-    while (*s && isspace(*s))
-        ++s;
-
-    // Trailing whitespace
-   
-    strending = s + strlen(s) - 1;
-
-    while (strlen(s) > 0 && isspace(*strending))
-    {
-        *strending = '\0';
-        --strending;
-    }
-
-    return s;
-}
-
-// This pattern is used a lot of times in different sections, 
-// an assignment is essentially just a statement of the form:
-//
-// Variable Name = Value
-//
-// The variable name can include spaces or any other characters.
-// The string is split on the '=', essentially.
-//
-// Returns true if read correctly
-
-boolean DEH_ParseAssignment(char *line, char **variable_name, char **value)
-{
-    char *p;
-
-    // find the equals
-    
-    p = strchr(line, '=');
-
-    if (p == NULL && p-line > 2)
-    {
-        return false;
-    }
-
-    // variable name at the start
-    // turn the '=' into a \0 to terminate the string here
-
-    *p = '\0';
-    *variable_name = CleanString(line);
-    
-    // value immediately follows the '='
-    
-    *value = CleanString(p+1);
-    
-    return true;
-}
-
-static boolean CheckSignatures(deh_context_t *context)
-{
-    size_t i;
-    char *line;
-    
-    // Read the first line
-
-    line = DEH_ReadLine(context);
-
-    if (line == NULL)
-    {
-        return false;
-    }
-
-    // Check all signatures to see if one matches
-
-    for (i=0; i<arrlen(deh_signatures); ++i)
-    {
-        if (!strcmp(deh_signatures[i], line))
-        {
-            return true;
-        }
-    }
-
-    return false;
-}
-
-// Parses a comment string in a dehacked file.
-
-static void DEH_ParseComment(char *comment)
-{
-    // Allow comments containing this special value to allow string
-    // replacements longer than those permitted by DOS dehacked.
-    // This allows us to use a dehacked patch for doing string 
-    // replacements for emulating Chex Quest.
-    //
-    // If you use this, your dehacked patch may not work in Vanilla
-    // Doom.
-
-    if (strstr(comment, "*allow-long-strings*") != NULL)
-    {
-        deh_allow_long_strings = true;
-    }
-
-    // Allow magic comments to allow longer cheat replacements than
-    // those permitted by DOS dehacked.  This is also for Chex
-    // Quest.
-
-    if (strstr(comment, "*allow-long-cheats*") != NULL)
-    {
-        deh_allow_long_cheats = true;
-    }
-}
-
-// Parses a dehacked file by reading from the context
-
-static void DEH_ParseContext(deh_context_t *context)
-{
-    deh_section_t *current_section = NULL;
-    char section_name[20];
-    void *tag = NULL;
-    char *line;
-    
-    // Read the header and check it matches the signature
-
-    if (!CheckSignatures(context))
-    {
-        DEH_Error(context, "This is not a valid dehacked patch file!");
-    }
-
-    deh_allow_long_strings = false;
-    deh_allow_long_cheats = false;
-    
-    // Read the file
-    
-    for (;;) 
-    {
-        // read a new line
- 
-        line = DEH_ReadLine(context);
-
-        // end of file?
-
-        if (line == NULL)
-            return;
-
-        while (line[0] != '\0' && isspace(line[0]))
-            ++line;
-
-        if (line[0] == '#')
-        {
-            // comment
-
-            DEH_ParseComment(line);
-            continue;
-        }
-
-        if (IsWhitespace(line))
-        {
-            if (current_section != NULL)
-            {
-                // end of section
-
-                if (current_section->end != NULL)
-                {
-                    current_section->end(context, tag);
-                }
-
-                //printf("end %s tag\n", current_section->name);
-                current_section = NULL;
-            }
-        }
-        else
-        {
-            if (current_section != NULL)
-            {
-                // parse this line
-
-                current_section->line_parser(context, line, tag);
-            }
-            else
-            {
-                // possibly the start of a new section
-
-                sscanf(line, "%19s", section_name);
-
-                current_section = GetSectionByName(section_name);
-                
-                if (current_section != NULL)
-                {
-                    tag = current_section->start(context, line);
-                    //printf("started %s tag\n", section_name);
-                }
-                else
-                {
-                    //printf("unknown section name %s\n", section_name);
-                }
-            }
-        }
-    }
-}
-
-// Parses a dehacked file
-
-int DEH_LoadFile(char *filename)
-{
-    deh_context_t *context;
-
-    printf(" loading %s\n", filename);
-
-    context = DEH_OpenFile(filename);
-
-    if (context == NULL)
-    {
-        fprintf(stderr, "DEH_LoadFile: Unable to open %s\n", filename);
-        return 0;
-    }
-    
-    DEH_ParseContext(context);
-    
-    DEH_CloseFile(context);
-
-    return 1;
-}
-
-// Checks the command line for -deh argument
-
-void DEH_Init(void)
-{
-    char *filename;
-    int p;
-
-    InitializeSections();
-
-    //!
-    // @category mod
-    //
-    // Ignore cheats in dehacked files.
-    //
-
-    if (M_CheckParm("-nocheats") > 0) 
-    {
-	deh_apply_cheats = false;
-    }
-
-    //!
-    // @arg <files>
-    // @category mod
-    //
-    // Load the given dehacked patch(es)
-    //
-
-    p = M_CheckParm("-deh");
-
-    if (p > 0)
-    {
-        ++p;
-
-        while (p < myargc && myargv[p][0] != '-')
-        {
-            filename = D_TryFindWADByName(myargv[p]);
-            DEH_LoadFile(filename);
-            ++p;
-        }
-    }
-}
-
-
--- a/src/doom/deh_main.h
+++ /dev/null
@@ -1,54 +1,0 @@
-// Emacs style mode select   -*- C++ -*- 
-//-----------------------------------------------------------------------------
-//
-// Copyright(C) 2005 Simon Howard
-//
-// This program is free software; you can redistribute it and/or
-// modify it under the terms of the GNU General Public License
-// as published by the Free Software Foundation; either version 2
-// of the License, or (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with this program; if not, write to the Free Software
-// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
-// 02111-1307, USA.
-//
-//-----------------------------------------------------------------------------
-//
-// Dehacked entrypoint and common code
-//
-//-----------------------------------------------------------------------------
-
-#ifndef DEH_MAIN_H
-#define DEH_MAIN_H
-
-#include "doomtype.h"
-#include "doomfeatures.h"
-#include "md5.h"
-#include "deh_str.h"
-
-// These are the limits that dehacked uses (from dheinit.h in the dehacked
-// source).  If these limits are exceeded, it does not generate an error, but
-// a warning is displayed.
-
-#define DEH_VANILLA_NUMSTATES 966
-#define DEH_VANILLA_NUMSFX 107
-
-void DEH_Init(void);
-int DEH_LoadFile(char *filename);
-
-boolean DEH_ParseAssignment(char *line, char **variable_name, char **value);
-
-void DEH_Checksum(md5_digest_t digest);
-
-extern boolean deh_allow_long_strings;
-extern boolean deh_allow_long_cheats;
-extern boolean deh_apply_cheats;
-
-#endif /* #ifndef DEH_MAIN_H */
-
--- a/src/doom/deh_mapping.c
+++ /dev/null
@@ -1,133 +1,0 @@
-// Emacs style mode select   -*- C++ -*- 
-//-----------------------------------------------------------------------------
-//
-// Copyright(C) 2005 Simon Howard
-//
-// This program is free software; you can redistribute it and/or
-// modify it under the terms of the GNU General Public License
-// as published by the Free Software Foundation; either version 2
-// of the License, or (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with this program; if not, write to the Free Software
-// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
-// 02111-1307, USA.
-//
-//-----------------------------------------------------------------------------
-//
-// Dehacked "mapping" code
-// Allows the fields in structures to be mapped out and accessed by
-// name
-//
-//-----------------------------------------------------------------------------
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-
-#include "doomtype.h"
-#include "i_system.h"
-#include "deh_mapping.h"
-
-//
-// Set the value of a particular field in a structure by name
-//
-
-boolean DEH_SetMapping(deh_context_t *context, deh_mapping_t *mapping, 
-                       void *structptr, char *name, int value)
-{
-    int i;
-
-    for (i=0; mapping->entries[i].name != NULL; ++i)
-    {
-        deh_mapping_entry_t *entry = &mapping->entries[i];
-
-        if (!strcasecmp(entry->name, name))
-        {
-            void *location;
-
-            if (entry->location == NULL)
-            {
-                DEH_Warning(context, "Field '%s' is unsupported", name);
-                return false;
-            }
-
-            location = (uint8_t *)structptr + ((uint8_t *)entry->location - (uint8_t *)mapping->base);
-
-     //       printf("Setting %p::%s to %i (%i bytes)\n",
-     //               structptr, name, value, entry->size);
-                
-            switch (entry->size)
-            {
-                case 1:
-                    * ((uint8_t *) location) = value;
-                    break;
-                case 2:
-                    * ((uint16_t *) location) = value;
-                    break;
-                case 4:
-                    * ((uint32_t *) location) = value;
-                    break;
-                default:
-                    DEH_Error(context, "Unknown field type for '%s' (BUG)", name);
-                    return false;
-            }
-
-            return true;
-        }
-    }
-
-    // field with this name not found
-
-    DEH_Warning(context, "Field named '%s' not found", name);
-
-    return false;
-}
-
-void DEH_StructMD5Sum(md5_context_t *context, deh_mapping_t *mapping,
-                      void *structptr)
-{
-    int i;
-
-    // Go through each mapping
-
-    for (i=0; mapping->entries[i].name != NULL; ++i)
-    {
-        deh_mapping_entry_t *entry = &mapping->entries[i];
-        void *location;
-
-        if (entry->location == NULL)
-        {
-            // Unsupported field
-
-            continue;
-        }
-
-        // Add in data for this field
-
-        location = (uint8_t *)structptr + ((uint8_t *)entry->location - (uint8_t *)mapping->base);
-
-        switch (entry->size)
-        {
-            case 1:
-                MD5_UpdateInt32(context, *((uint8_t *) location));
-                break;
-            case 2:
-                MD5_UpdateInt32(context, *((uint16_t *) location));
-                break;
-            case 4:
-                MD5_UpdateInt32(context, *((uint32_t *) location));
-                break;
-            default:
-                I_Error("Unknown dehacked mapping field type for '%s' (BUG)", 
-                        entry->name);
-                break;
-        }
-    }
-}
-
--- a/src/doom/deh_mapping.h
+++ /dev/null
@@ -1,90 +1,0 @@
-// Emacs style mode select   -*- C++ -*- 
-//-----------------------------------------------------------------------------
-//
-// Copyright(C) 2005 Simon Howard
-//
-// This program is free software; you can redistribute it and/or
-// modify it under the terms of the GNU General Public License
-// as published by the Free Software Foundation; either version 2
-// of the License, or (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with this program; if not, write to the Free Software
-// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
-// 02111-1307, USA.
-//
-//-----------------------------------------------------------------------------
-//
-// Dehacked "mapping" code
-// Allows the fields in structures to be mapped out and accessed by
-// name
-//
-//-----------------------------------------------------------------------------
-
-#ifndef DEH_MAPPING_H
-#define DEH_MAPPING_H
-
-#include "doomtype.h"
-#include "deh_io.h"
-#include "md5.h"
-
-#define DEH_BEGIN_MAPPING(mapping_name, structname)           \
-    static structname deh_mapping_base;                       \
-    static deh_mapping_t mapping_name =                       \
-    {                                                         \
-        &deh_mapping_base,                                    \
-        {
-
-#define DEH_MAPPING(deh_name, fieldname)                      \
-             {deh_name, &deh_mapping_base.fieldname,          \
-                 sizeof(deh_mapping_base.fieldname)},
-
-#define DEH_UNSUPPORTED_MAPPING(deh_name)                     \
-             {deh_name, NULL, -1},
-            
-#define DEH_END_MAPPING                                       \
-             {NULL, NULL, -1}                                 \
-        }                                                     \
-    };
-
-    
-
-#define MAX_MAPPING_ENTRIES 32
-
-typedef struct deh_mapping_s deh_mapping_t;
-typedef struct deh_mapping_entry_s deh_mapping_entry_t;
-
-struct deh_mapping_entry_s 
-{
-    // field name
-   
-    char *name;
-
-    // location relative to the base in the deh_mapping_t struct
-    // If this is NULL, it is an unsupported mapping
-
-    void *location;
-
-    // field size
-
-    int size;
-};
-
-struct deh_mapping_s
-{
-    void *base;
-    deh_mapping_entry_t entries[MAX_MAPPING_ENTRIES];
-};
-
-boolean DEH_SetMapping(deh_context_t *context, deh_mapping_t *mapping, 
-                       void *structptr, char *name, int value);
-void DEH_StructMD5Sum(md5_context_t *context, deh_mapping_t *mapping,
-                      void *structptr);
-
-#endif /* #ifndef DEH_MAPPING_H */
-
--- a/src/doom/deh_text.c
+++ /dev/null
@@ -1,127 +1,0 @@
-// Emacs style mode select   -*- C++ -*- 
-//-----------------------------------------------------------------------------
-//
-// Copyright(C) 2005 Simon Howard
-//
-// This program is free software; you can redistribute it and/or
-// modify it under the terms of the GNU General Public License
-// as published by the Free Software Foundation; either version 2
-// of the License, or (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with this program; if not, write to the Free Software
-// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
-// 02111-1307, USA.
-//
-//-----------------------------------------------------------------------------
-//
-// Parses Text substitution sections in dehacked files
-//
-//-----------------------------------------------------------------------------
-
-#include <stdio.h>
-#include <string.h>
-
-#include "doomtype.h"
-
-#include "z_zone.h"
-
-#include "deh_defs.h"
-#include "deh_io.h"
-#include "deh_main.h"
-
-// Given a string length, find the maximum length of a 
-// string that can replace it.
-
-static int TXT_MaxStringLength(int len)
-{
-    // Enough bytes for the string and the NUL terminator
-
-    len += 1;
-
-    // All strings in doom.exe are on 4-byte boundaries, so we may be able
-    // to support a slightly longer string.
-    // Extend up to the next 4-byte boundary
-
-    len += (4 - (len % 4)) % 4;
-            
-    // Less one for the NUL terminator.
-
-    return len - 1;
-}
-
-static void *DEH_TextStart(deh_context_t *context, char *line)
-{
-    char *from_text, *to_text;
-    int fromlen, tolen;
-    int i;
-    
-    if (sscanf(line, "Text %i %i", &fromlen, &tolen) != 2)
-    {
-        DEH_Warning(context, "Parse error on section start");
-        return NULL;
-    }
-
-    // Only allow string replacements that are possible in Vanilla Doom.  
-    // Chocolate Doom is unforgiving!
-
-    if (!deh_allow_long_strings && tolen > TXT_MaxStringLength(fromlen))
-    {
-        DEH_Error(context, "Replacement string is longer than the maximum "
-                           "possible in doom.exe");
-        return NULL;
-    }
-
-    from_text = Z_Malloc(fromlen + 1, PU_STATIC, NULL);
-    to_text = Z_Malloc(tolen + 1, PU_STATIC, NULL);
-
-    // read in the "from" text
-
-    for (i=0; i<fromlen; ++i)
-    {
-        int c;
-
-        c = DEH_GetChar(context);
-            
-        from_text[i] = c;
-    }
-
-    from_text[fromlen] = '\0';
-
-    // read in the "to" text
-
-    for (i=0; i<tolen; ++i)
-    {
-        int c;
-
-        c = DEH_GetChar(context);
-            
-        to_text[i] = c;
-    }
-    to_text[tolen] = '\0';
-
-    DEH_AddStringReplacement(from_text, to_text);
-    
-    return NULL;
-}
-
-static void DEH_TextParseLine(deh_context_t *context, char *line, void *tag)
-{
-    // not used
-}
-
-deh_section_t deh_section_text =
-{
-    "Text",
-    NULL,
-    DEH_TextStart,
-    DEH_TextParseLine,
-    NULL,
-    NULL,
-};
-
--- a/src/doom/g_game.c
+++ b/src/doom/g_game.c
@@ -588,10 +588,20 @@
 
     if (lowres_turn)
     {
-        // round angleturn to the nearest 256 boundary
+        static signed short carry = 0;
+        signed short desired_angleturn;
+
+        desired_angleturn = cmd->angleturn + carry;
+
+        // round angleturn to the nearest 256 unit boundary
         // for recording demos with single byte values for turn
 
-        cmd->angleturn = (cmd->angleturn + 128) & 0xff00;
+        cmd->angleturn = (desired_angleturn + 128) & 0xff00;
+
+        // Carry forward the error from the reduced resolution to the
+        // next tic, so that successive small movements can accumulate.
+
+        carry = desired_angleturn - cmd->angleturn;
     }
 } 
  
--- a/src/doom/hu_stuff.c
+++ b/src/doom/hu_stuff.c
@@ -52,9 +52,9 @@
 // Locally used constants, shortcuts.
 //
 #define HU_TITLE	(mapnames[(gameepisode-1)*9+gamemap-1])
-#define HU_TITLE2	(mapnames2[gamemap-1])
-#define HU_TITLEP	(mapnamesp[gamemap-1])
-#define HU_TITLET	(mapnamest[gamemap-1])
+#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_TITLEHEIGHT	1
 #define HU_TITLEX	0
@@ -171,8 +171,16 @@
     "NEWLEVEL"
 };
 
-char*	mapnames2[] =	// DOOM 2 map names.
+// 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
+// the layout in the Vanilla executable, where it is possible to
+// overflow the end of one array into the next.
+
+char *mapnames_commercial[] =
 {
+    // DOOM 2 map names.
+
     HUSTR_1,
     HUSTR_2,
     HUSTR_3,
@@ -206,12 +214,10 @@
     HUSTR_29,
     HUSTR_30,
     HUSTR_31,
-    HUSTR_32
-};
+    HUSTR_32,
 
+    // Plutonia WAD map names.
 
-char*	mapnamesp[] =	// Plutonia WAD map names.
-{
     PHUSTR_1,
     PHUSTR_2,
     PHUSTR_3,
@@ -245,12 +251,10 @@
     PHUSTR_29,
     PHUSTR_30,
     PHUSTR_31,
-    PHUSTR_32
-};
+    PHUSTR_32,
+    
+    // TNT WAD map names.
 
-
-char *mapnamest[] =	// TNT WAD map names.
-{
     THUSTR_1,
     THUSTR_2,
     THUSTR_3,
--- a/src/doom/p_map.c
+++ b/src/doom/p_map.c
@@ -885,7 +885,17 @@
 	
 	dist = FixedMul (attackrange, in->frac);
 
-	if (li->frontsector->floorheight != li->backsector->floorheight)
+        // Return false if there is no back sector.  This should never
+        // be the case if the line is two-sided; however, some WADs
+        // (eg. ottawau.wad) use this as an "impassible glass" trick
+        // and rely on Vanilla Doom's (unintentional) support for this.
+
+        if (li->backsector == NULL)
+        {
+            return false;
+        }
+
+        if (li->frontsector->floorheight != li->backsector->floorheight)
 	{
 	    slope = FixedDiv (openbottom - shootz , dist);
 	    if (slope > bottomslope)
@@ -973,7 +983,14 @@
 		
 	dist = FixedMul (attackrange, in->frac);
 
-	if (li->frontsector->floorheight != li->backsector->floorheight)
+        // Check if backsector is NULL.  See comment in PTR_AimTraverse.
+
+	if (li->backsector == NULL)
+        {
+            goto hitline;
+        }
+
+        if (li->frontsector->floorheight != li->backsector->floorheight)
 	{
 	    slope = FixedDiv (openbottom - shootz , dist);
 	    if (slope > aimslope)
--- a/src/doom/p_setup.c
+++ b/src/doom/p_setup.c
@@ -167,6 +167,7 @@
     line_t*		ldef;
     int			linedef;
     int			side;
+    int                 sidenum;
 	
     numsegs = W_LumpLength (lump) / sizeof(mapseg_t);
     segs = Z_Malloc (numsegs*sizeof(seg_t),PU_LEVEL,0);	
@@ -179,7 +180,7 @@
     {
 	li->v1 = &vertexes[SHORT(ml->v1)];
 	li->v2 = &vertexes[SHORT(ml->v2)];
-					
+
 	li->angle = (SHORT(ml->angle))<<16;
 	li->offset = (SHORT(ml->offset))<<16;
 	linedef = SHORT(ml->linedef);
@@ -188,10 +189,28 @@
 	side = SHORT(ml->side);
 	li->sidedef = &sides[ldef->sidenum[side]];
 	li->frontsector = sides[ldef->sidenum[side]].sector;
-	if (ldef-> flags & ML_TWOSIDED)
-	    li->backsector = sides[ldef->sidenum[side^1]].sector;
-	else
+
+        if (ldef-> flags & ML_TWOSIDED)
+        {
+            sidenum = ldef->sidenum[side ^ 1];
+
+            // If the sidenum is out of range, this may be a "glass hack"
+            // impassible window.  Point at side #0 (this may not be
+            // the correct Vanilla behavior; however, it seems to work for
+            // OTTAWAU.WAD, which is the one place I've seen this trick
+            // used).
+
+            if (sidenum < 0 || sidenum >= numsides)
+            {
+                sidenum = 0;
+            }
+
+            li->backsector = sides[sidenum].sector;
+        }
+        else
+        {
 	    li->backsector = 0;
+        }
     }
 	
     W_ReleaseLumpNum(lump);
--- a/src/doom/p_sight.c
+++ b/src/doom/p_sight.c
@@ -173,7 +173,7 @@
 	    continue;
 	
 	line->validcount = validcount;
-		
+
 	v1 = line->v1;
 	v2 = line->v2;
 	s1 = P_DivlineSide (v1->x,v1->y, &strace);
@@ -193,6 +193,14 @@
 	// line isn't crossed?
 	if (s1 == s2)
 	    continue;	
+
+        // Backsector may be NULL if this is an "impassible
+        // glass" hack line.
+
+        if (line->backsector == NULL)
+        {
+            return false;
+        }
 
 	// stop because it is not two sided anyway
 	// might do this after updating validcount?
--- a/src/doom/st_stuff.c
+++ b/src/doom/st_stuff.c
@@ -260,9 +260,6 @@
  // Height, in lines. 
 #define ST_OUTHEIGHT		1
 
-#define ST_MAPWIDTH	\
-    (strlen(mapnames[(gameepisode-1)*9+(gamemap-1)]))
-
 #define ST_MAPTITLEX \
     (SCREENWIDTH - ST_MAPWIDTH * ST_CHATFONTWIDTH)
 
@@ -418,10 +415,6 @@
 cheatseq_t cheat_mypos = CHEAT("idmypos", 0);
 
 
-// 
-extern char*	mapnames[];
-
-
 //
 // STATUS BAR CODE
 //
@@ -984,6 +977,17 @@
 	palette = RADIATIONPAL;
     else
 	palette = 0;
+
+    // In Chex Quest, the player never sees red.  Instead, the
+    // radiation suit palette is used to tint the screen green,
+    // as though the player is being covered in goo by an
+    // attacking flemoid.
+
+    if (gameversion == exe_chex
+     && palette >= STARTREDPALS && palette < STARTREDPALS + NUMREDPALS)
+    {
+        palette = RADIATIONPAL;
+    }
 
     if (palette != st_palette)
     {
--- a/src/i_video.c
+++ b/src/i_video.c
@@ -503,6 +503,7 @@
 {
     if (initialized)
     {
+        SDL_SetCursor(cursors[1]);
         SDL_ShowCursor(1);
         SDL_WM_GrabInput(SDL_GRAB_OFF);
 
--- a/src/mus2mid.c
+++ b/src/mus2mid.c
@@ -1,4 +1,4 @@
-// Emacs style mode select   -*- C++ -*- 
+// Emacs style mode select   -*- C++ -*-
 //-----------------------------------------------------------------------------
 //
 // Copyright(C) 1993-1996 Id Software, Inc.
@@ -31,594 +31,663 @@
 #include "memio.h"
 #include "mus2mid.h"
 
+#define NUM_CHANNELS 16
+
+#define MIDI_PERCUSSION_CHAN 9
+#define MUS_PERCUSSION_CHAN 15
+
 // MUS event codes
-typedef enum 
+typedef enum
 {
-	mus_releasekey = 0x00,
-	mus_presskey = 0x10,
-	mus_pitchwheel = 0x20,
-	mus_systemevent = 0x30,
-	mus_changecontroller = 0x40,
-	mus_scoreend = 0x60
+    mus_releasekey = 0x00,
+    mus_presskey = 0x10,
+    mus_pitchwheel = 0x20,
+    mus_systemevent = 0x30,
+    mus_changecontroller = 0x40,
+    mus_scoreend = 0x60
 } musevent;
 
 // MIDI event codes
-typedef enum 
+typedef enum
 {
-	midi_releasekey = 0x80,
-	midi_presskey = 0x90,
-	midi_aftertouchkey = 0xA0,
-	midi_changecontroller = 0xB0,
-	midi_changepatch = 0xC0,
-	midi_aftertouchchannel = 0xD0,
-	midi_pitchwheel = 0xE0
+    midi_releasekey = 0x80,
+    midi_presskey = 0x90,
+    midi_aftertouchkey = 0xA0,
+    midi_changecontroller = 0xB0,
+    midi_changepatch = 0xC0,
+    midi_aftertouchchannel = 0xD0,
+    midi_pitchwheel = 0xE0
 } midievent;
 
-
 // Structure to hold MUS file header
-typedef struct 
+typedef struct
 {
-	byte id[4];
-	unsigned short scorelength;
-	unsigned short scorestart;
-	unsigned short primarychannels;
-	unsigned short secondarychannels;
-	unsigned short instrumentcount;
+    byte id[4];
+    unsigned short scorelength;
+    unsigned short scorestart;
+    unsigned short primarychannels;
+    unsigned short secondarychannels;
+    unsigned short instrumentcount;
 } PACKEDATTR musheader;
 
 // Standard MIDI type 0 header + track header
-static byte midiheader[] = 
+static const byte midiheader[] =
 {
-	'M', 'T', 'h', 'd',     // Main header
-	0x00, 0x00, 0x00, 0x06, // Header size
-	0x00, 0x00,             // MIDI type (0)
-	0x00, 0x01,             // Number of tracks
-	0x00, 0x46,             // Resolution
-	'M', 'T', 'r', 'k',		// Start of track
-	0x00, 0x00, 0x00, 0x00  // Placeholder for track length
+    'M', 'T', 'h', 'd',     // Main header
+    0x00, 0x00, 0x00, 0x06, // Header size
+    0x00, 0x00,             // MIDI type (0)
+    0x00, 0x01,             // Number of tracks
+    0x00, 0x46,             // Resolution
+    'M', 'T', 'r', 'k',        // Start of track
+    0x00, 0x00, 0x00, 0x00  // Placeholder for track length
 };
 
 // Cached channel velocities
-static byte channelvelocities[] = 
-{ 
-	127, 127, 127, 127, 127, 127, 127, 127, 
-	127, 127, 127, 127, 127, 127, 127, 127 
+static byte channelvelocities[] =
+{
+    127, 127, 127, 127, 127, 127, 127, 127,
+    127, 127, 127, 127, 127, 127, 127, 127
 };
 
 // Timestamps between sequences of MUS events
 
-static unsigned int queuedtime = 0; 
+static unsigned int queuedtime = 0;
 
 // Counter for the length of the track
 
 static unsigned int tracksize;
 
-static byte mus2midi_translation[] = 
-{ 
-	0x00, 0x20, 0x01, 0x07, 0x0A, 0x0B, 0x5B, 0x5D,
-	0x40, 0x43, 0x78, 0x7B, 0x7E, 0x7F, 0x79 
+static const byte controller_map[] =
+{
+    0x00, 0x20, 0x01, 0x07, 0x0A, 0x0B, 0x5B, 0x5D,
+    0x40, 0x43, 0x78, 0x7B, 0x7E, 0x7F, 0x79
 };
 
+static int channel_map[NUM_CHANNELS];
+
 // Write timestamp to a MIDI file.
 
-static boolean midi_writetime(unsigned int time, MEMFILE *midioutput) 
+static boolean WriteTime(unsigned int time, MEMFILE *midioutput)
 {
-	unsigned int buffer = time & 0x7F;
-	byte writeval;
+    unsigned int buffer = time & 0x7F;
+    byte writeval;
 
-	while ((time >>= 7) != 0) 
-	{
-		buffer <<= 8;
-		buffer |= ((time & 0x7F) | 0x80);
-	}
+    while ((time >>= 7) != 0)
+    {
+        buffer <<= 8;
+        buffer |= ((time & 0x7F) | 0x80);
+    }
 
-	for (;;) 
-	{
-		writeval = (byte)(buffer & 0xFF);
+    for (;;)
+    {
+        writeval = (byte)(buffer & 0xFF);
 
-		if (mem_fwrite(&writeval, 1, 1, midioutput) != 1) 
-		{
-			return true;
-		}
+        if (mem_fwrite(&writeval, 1, 1, midioutput) != 1)
+        {
+            return true;
+        }
 
-		++tracksize;
+        ++tracksize;
 
-		if ((buffer & 0x80) != 0) 
-		{
-			buffer >>= 8;
-		} 
-		else 
-		{
-			queuedtime = 0;
-			return false;
-		}
-	}
+        if ((buffer & 0x80) != 0)
+        {
+            buffer >>= 8;
+        }
+        else
+        {
+            queuedtime = 0;
+            return false;
+        }
+    }
 }
 
 
 // Write the end of track marker
-static boolean midi_writeendtrack(MEMFILE *midioutput) 
+static boolean WriteEndTrack(MEMFILE *midioutput)
 {
-	byte endtrack[] = {0xFF, 0x2F, 0x00};
+    byte endtrack[] = {0xFF, 0x2F, 0x00};
 
-	if (midi_writetime(queuedtime, midioutput)) 
-	{
-		return true;
-	}
+    if (WriteTime(queuedtime, midioutput))
+    {
+        return true;
+    }
 
-	if (mem_fwrite(endtrack, 1, 3, midioutput) != 3) 
-	{
-		return true;
-	}
+    if (mem_fwrite(endtrack, 1, 3, midioutput) != 3)
+    {
+        return true;
+    }
 
-	tracksize += 3;
-	return false;
+    tracksize += 3;
+    return false;
 }
 
 // Write a key press event
-static boolean midi_writepresskey(byte channel, byte key, 
-                                  byte velocity, MEMFILE *midioutput) 
+static boolean WritePressKey(byte channel, byte key,
+                             byte velocity, MEMFILE *midioutput)
 {
-	byte working = midi_presskey | channel;
+    byte working = midi_presskey | channel;
 
-	if (midi_writetime(queuedtime, midioutput)) 
-	{
-		return true;
-	}
+    if (WriteTime(queuedtime, midioutput))
+    {
+        return true;
+    }
 
-	if (mem_fwrite(&working, 1, 1, midioutput) != 1) 
-	{
-		return true;
-	}
+    if (mem_fwrite(&working, 1, 1, midioutput) != 1)
+    {
+        return true;
+    }
 
-	working = key & 0x7F;
+    working = key & 0x7F;
 
-	if (mem_fwrite(&working, 1, 1, midioutput) != 1) 
-	{
-		return true;
-	}
-	
-	working = velocity & 0x7F;
-	
-	if (mem_fwrite(&working, 1, 1, midioutput) != 1) 
-	{
-		return true;
-	}
+    if (mem_fwrite(&working, 1, 1, midioutput) != 1)
+    {
+        return true;
+    }
 
-	tracksize += 3;
+    working = velocity & 0x7F;
 
-	return false;
+    if (mem_fwrite(&working, 1, 1, midioutput) != 1)
+    {
+        return true;
+    }
+
+    tracksize += 3;
+
+    return false;
 }
 
 // Write a key release event
-static boolean midi_writereleasekey(byte channel, byte key, 
-                                    MEMFILE *midioutput) 
+static boolean WriteReleaseKey(byte channel, byte key,
+                               MEMFILE *midioutput)
 {
-	byte working = midi_releasekey | channel;
+    byte working = midi_releasekey | channel;
 
-	if (midi_writetime(queuedtime, midioutput)) 
-	{
-		return true;
-	}
+    if (WriteTime(queuedtime, midioutput))
+    {
+        return true;
+    }
 
-	if (mem_fwrite(&working, 1, 1, midioutput) != 1) 
-	{
-		return true;
-	}
+    if (mem_fwrite(&working, 1, 1, midioutput) != 1)
+    {
+        return true;
+    }
 
-	working = key & 0x7F;
+    working = key & 0x7F;
 
-	if (mem_fwrite(&working, 1, 1, midioutput) != 1) 
-	{
-		return true;
-	}
+    if (mem_fwrite(&working, 1, 1, midioutput) != 1)
+    {
+        return true;
+    }
 
-	working = 0;
+    working = 0;
 
-	if (mem_fwrite(&working, 1, 1, midioutput) != 1) 
-	{
-		return true;
-	}
+    if (mem_fwrite(&working, 1, 1, midioutput) != 1)
+    {
+        return true;
+    }
 
-	tracksize += 3;
+    tracksize += 3;
 
-	return false;
+    return false;
 }
 
 // Write a pitch wheel/bend event
-static boolean midi_writepitchwheel(byte channel, short wheel, 
-                                    MEMFILE *midioutput) 
+static boolean WritePitchWheel(byte channel, short wheel,
+                               MEMFILE *midioutput)
 {
-	byte working = midi_pitchwheel | channel;
+    byte working = midi_pitchwheel | channel;
 
-	if (midi_writetime(queuedtime, midioutput)) 
-	{
-		return true;
-	}
+    if (WriteTime(queuedtime, midioutput))
+    {
+        return true;
+    }
 
-	if (mem_fwrite(&working, 1, 1, midioutput) != 1) 
-	{
-		return true;
-	}
+    if (mem_fwrite(&working, 1, 1, midioutput) != 1)
+    {
+        return true;
+    }
 
-	working = wheel & 0x7F;
+    working = wheel & 0x7F;
 
-	if (mem_fwrite(&working, 1, 1, midioutput) != 1) 
-	{
-		return true;
-	}
+    if (mem_fwrite(&working, 1, 1, midioutput) != 1)
+    {
+        return true;
+    }
 
-	working = (wheel >> 7) & 0x7F;
+    working = (wheel >> 7) & 0x7F;
 
-	if (mem_fwrite(&working, 1, 1, midioutput) != 1) 
-	{
-		return true;
-	}
+    if (mem_fwrite(&working, 1, 1, midioutput) != 1)
+    {
+        return true;
+    }
 
-	tracksize += 3;
-	return false;
+    tracksize += 3;
+    return false;
 }
 
 // Write a patch change event
-static boolean midi_writechangepatch(byte channel, byte patch,
-                                     MEMFILE *midioutput) 
+static boolean WriteChangePatch(byte channel, byte patch,
+                                MEMFILE *midioutput)
 {
-	byte working = midi_changepatch | channel;
-	
-	if (midi_writetime(queuedtime, midioutput)) 
-	{
-		return true;
-	}
+    byte working = midi_changepatch | channel;
 
-	if (mem_fwrite(&working, 1, 1, midioutput) != 1) 
-	{
-		return true;
-	}
+    if (WriteTime(queuedtime, midioutput))
+    {
+        return true;
+    }
 
-	working = patch & 0x7F;
+    if (mem_fwrite(&working, 1, 1, midioutput) != 1)
+    {
+        return true;
+    }
 
-	if (mem_fwrite(&working, 1, 1, midioutput) != 1) 
-	{
-		return true;
-	}
+    working = patch & 0x7F;
 
-	tracksize += 2;
+    if (mem_fwrite(&working, 1, 1, midioutput) != 1)
+    {
+        return true;
+    }
 
-	return false;
+    tracksize += 2;
+
+    return false;
 }
 
-
-
 // Write a valued controller change event
-static boolean midi_writechangecontroller_valued(byte channel, 
-                                                 byte control, 
-                                                 byte value, 
-                                                 MEMFILE *midioutput) 
+
+static boolean WriteChangeController_Valued(byte channel,
+                                            byte control,
+                                            byte value,
+                                            MEMFILE *midioutput)
 {
-	byte working = midi_changecontroller | channel;
+    byte working = midi_changecontroller | channel;
 
-	if (midi_writetime(queuedtime, midioutput)) 
-	{
-		return true;
-	}
+    if (WriteTime(queuedtime, midioutput))
+    {
+        return true;
+    }
 
-	if (mem_fwrite(&working, 1, 1, midioutput) != 1) 
-	{
-		return true;
-	}
+    if (mem_fwrite(&working, 1, 1, midioutput) != 1)
+    {
+        return true;
+    }
 
-	working = control & 0x7F;
+    working = control & 0x7F;
 
-	if (mem_fwrite(&working, 1, 1, midioutput) != 1) 
-	{
-		return true;
-	}
-	// Quirk in vanilla DOOM? MUS controller values should be 
-	// 7-bit, not 8-bit.
+    if (mem_fwrite(&working, 1, 1, midioutput) != 1)
+    {
+        return true;
+    }
 
-	working = value;// & 0x7F; 
+    // Quirk in vanilla DOOM? MUS controller values should be
+    // 7-bit, not 8-bit.
 
-	// Fix on said quirk to stop MIDI players from complaining that 
-	// the value is out of range:
+    working = value;// & 0x7F;
 
-	if (working & 0x80) 
-	{
-		working = 0x7F;
-	}
+    // Fix on said quirk to stop MIDI players from complaining that
+    // the value is out of range:
 
-	if (mem_fwrite(&working, 1, 1, midioutput) != 1) 
-	{
-		return true;
-	}
+    if (working & 0x80)
+    {
+        working = 0x7F;
+    }
 
-	tracksize += 3;
+    if (mem_fwrite(&working, 1, 1, midioutput) != 1)
+    {
+        return true;
+    }
 
-	return false;
+    tracksize += 3;
+
+    return false;
 }
 
 // Write a valueless controller change event
-static boolean midi_writechangecontroller_valueless(byte channel, 
-                                                    byte control, 
-                                                    MEMFILE *midioutput) 
+static boolean WriteChangeController_Valueless(byte channel,
+                                               byte control,
+                                               MEMFILE *midioutput)
 {
-	return midi_writechangecontroller_valued(channel, control, 0, 
-			 			 midioutput);
+    return WriteChangeController_Valued(channel, control, 0,
+                                             midioutput);
 }
 
-static boolean read_musheader(MEMFILE *file, musheader *header)
+// Allocate a free MIDI channel.
+
+static int AllocateMIDIChannel(void)
 {
-	boolean result;
+    int result;
+    int max;
+    int i;
 
-	result = (mem_fread(&header->id, sizeof(byte), 4, file) == 4)
-              && (mem_fread(&header->scorelength, sizeof(short), 1, file) == 1)
-              && (mem_fread(&header->scorestart, sizeof(short), 1, file) == 1)
-              && (mem_fread(&header->primarychannels, sizeof(short), 1, file) == 1)
-              && (mem_fread(&header->secondarychannels, sizeof(short), 1, file) == 1)
-              && (mem_fread(&header->instrumentcount, sizeof(short), 1, file) == 1);
+    // Find the current highest-allocated channel.
 
-	if (result)
-	{
-		header->scorelength = SHORT(header->scorelength);
-		header->scorestart = SHORT(header->scorestart);
-		header->primarychannels = SHORT(header->primarychannels);
-		header->secondarychannels = SHORT(header->secondarychannels);
-		header->instrumentcount = SHORT(header->instrumentcount);
-	}
+    max = -1;
 
-	return result;
+    for (i=0; i<NUM_CHANNELS; ++i)
+    {
+        if (channel_map[i] > max)
+        {
+            max = channel_map[i];
+        }
+    }
+
+    // max is now equal to the highest-allocated MIDI channel.  We can
+    // now allocate the next available channel.  This also works if
+    // no channels are currently allocated (max=-1)
+
+    result = max + 1;
+
+    // Don't allocate the MIDI percussion channel!
+
+    if (result == MIDI_PERCUSSION_CHAN)
+    {
+        ++result;
+    }
+
+    return result;
 }
 
+// Given a MUS channel number, get the MIDI channel number to use
+// in the outputted file.
 
-// Read a MUS file from a stream (musinput) and output a MIDI file to 
+static int GetMIDIChannel(int mus_channel)
+{
+    // Find the MIDI channel to use for this MUS channel.
+    // MUS channel 15 is the percusssion channel.
+
+    if (mus_channel == MUS_PERCUSSION_CHAN)
+    {
+        return MIDI_PERCUSSION_CHAN;
+    }
+    else
+    {
+        // If a MIDI channel hasn't been allocated for this MUS channel
+        // yet, allocate the next free MIDI channel.
+
+        if (channel_map[mus_channel] == -1)
+        {
+            channel_map[mus_channel] = AllocateMIDIChannel();
+        }
+
+        return channel_map[mus_channel];
+    }
+}
+
+static boolean ReadMusHeader(MEMFILE *file, musheader *header)
+{
+    boolean result;
+
+    result = mem_fread(&header->id, sizeof(byte), 4, file) == 4
+          && mem_fread(&header->scorelength, sizeof(short), 1, file) == 1
+          && mem_fread(&header->scorestart, sizeof(short), 1, file) == 1
+          && mem_fread(&header->primarychannels, sizeof(short), 1, file) == 1
+          && mem_fread(&header->secondarychannels, sizeof(short), 1, file) == 1
+          && mem_fread(&header->instrumentcount, sizeof(short), 1, file) == 1;
+
+    if (result)
+    {
+        header->scorelength = SHORT(header->scorelength);
+        header->scorestart = SHORT(header->scorestart);
+        header->primarychannels = SHORT(header->primarychannels);
+        header->secondarychannels = SHORT(header->secondarychannels);
+        header->instrumentcount = SHORT(header->instrumentcount);
+    }
+
+    return result;
+}
+
+
+// Read a MUS file from a stream (musinput) and output a MIDI file to
 // a stream (midioutput).
 //
 // Returns 0 on success or 1 on failure.
 
-boolean mus2mid(MEMFILE *musinput, MEMFILE *midioutput) 
+boolean mus2mid(MEMFILE *musinput, MEMFILE *midioutput)
 {
-	// Header for the MUS file
-	musheader musfileheader;         
+    // Header for the MUS file
+    musheader musfileheader;
 
-	// Descriptor for the current MUS event
-	byte eventdescriptor;   
-	int channel; // Channel number
-	musevent event; 
-	
+    // Descriptor for the current MUS event
+    byte eventdescriptor;
+    int channel; // Channel number
+    musevent event;
 
-	// Bunch of vars read from MUS lump
-	byte key;
-	byte controllernumber;
-	byte controllervalue;
 
-	// Buffer used for MIDI track size record
-	byte tracksizebuffer[4]; 
+    // Bunch of vars read from MUS lump
+    byte key;
+    byte controllernumber;
+    byte controllervalue;
 
-	// Flag for when the score end marker is hit.
-	int hitscoreend = 0; 
+    // Buffer used for MIDI track size record
+    byte tracksizebuffer[4];
 
-	// Temp working byte
-	byte working; 
-	// Used in building up time delays
-	unsigned int timedelay; 
+    // Flag for when the score end marker is hit.
+    int hitscoreend = 0;
 
-	// Grab the header
+    // Temp working byte
+    byte working;
+    // Used in building up time delays
+    unsigned int timedelay;
 
-	if (!read_musheader(musinput, &musfileheader))
-	{
-		return true;
-	}
+    // Initialise channel map to mark all channels as unused.
 
+    for (channel=0; channel<NUM_CHANNELS; ++channel)
+    {
+        channel_map[channel] = -1;
+    }
+
+    // Grab the header
+
+    if (!ReadMusHeader(musinput, &musfileheader))
+    {
+        return true;
+    }
+
 #ifdef CHECK_MUS_HEADER
-	// Check MUS header
-	if (musfileheader.id[0] != 'M' 
-	 || musfileheader.id[1] != 'U' 
-	 || musfileheader.id[2] != 'S' 
-	 || musfileheader.id[3] != 0x1A) 
-	{
-		return true;
-	}
+    // Check MUS header
+    if (musfileheader.id[0] != 'M'
+     || musfileheader.id[1] != 'U'
+     || musfileheader.id[2] != 'S'
+     || musfileheader.id[3] != 0x1A)
+    {
+        return true;
+    }
 #endif
 
-	// Seek to where the data is held
-	if (mem_fseek(musinput, (long)musfileheader.scorestart, 
-                      MEM_SEEK_SET) != 0) 
-	{
-		return true;
-	}
+    // Seek to where the data is held
+    if (mem_fseek(musinput, (long)musfileheader.scorestart,
+                  MEM_SEEK_SET) != 0)
+    {
+        return true;
+    }
 
-	// So, we can assume the MUS file is faintly legit. Let's start 
-	// writing MIDI data...
+    // So, we can assume the MUS file is faintly legit. Let's start
+    // writing MIDI data...
 
-	mem_fwrite(midiheader, 1, sizeof(midiheader), midioutput);
-	tracksize = 0;
+    mem_fwrite(midiheader, 1, sizeof(midiheader), midioutput);
+    tracksize = 0;
 
-	// Now, process the MUS file:
-	while (!hitscoreend) 
-	{
-		// Handle a block of events:
+    // Now, process the MUS file:
+    while (!hitscoreend)
+    {
+        // Handle a block of events:
 
-		while (!hitscoreend) 
-		{
-			// Fetch channel number and event code:
+        while (!hitscoreend)
+        {
+            // Fetch channel number and event code:
 
-			if (mem_fread(&eventdescriptor, 1, 1, musinput) != 1) 
-			{
-				return true;
-			}
+            if (mem_fread(&eventdescriptor, 1, 1, musinput) != 1)
+            {
+                return true;
+            }
 
-			channel = eventdescriptor & 0x0F;
-			event = eventdescriptor & 0x70;
+            channel = GetMIDIChannel(eventdescriptor & 0x0F);
+            event = eventdescriptor & 0x70;
 
-			// Swap channels 15 and 9.
-			// MIDI channel 9 = percussion.
-			// MUS channel 15 = percussion.
+            switch (event)
+            {
+                case mus_releasekey:
+                    if (mem_fread(&key, 1, 1, musinput) != 1)
+                    {
+                        return true;
+                    }
 
-			if (channel == 15) 
-			{
-				channel = 9;
-			} 
-			else if (channel == 9) 
-			{
-				channel = 15;
-			}
-			
-			switch (event) 
-			{
-				case mus_releasekey:
-					if (mem_fread(&key, 1, 1, musinput) != 1) 
-					{
-						return true;
-					}
+                    if (WriteReleaseKey(channel, key, midioutput))
+                    {
+                        return true;
+                    }
 
-					if (midi_writereleasekey(channel, key, midioutput)) 
-					{
-						return true;
-					}
+                    break;
 
-					break;
-				
-				case mus_presskey:
-					if (mem_fread(&key, 1, 1, musinput) != 1) 
-					{
-						return true;
-					}
+                case mus_presskey:
+                    if (mem_fread(&key, 1, 1, musinput) != 1)
+                    {
+                        return true;
+                    }
 
-					if (key & 0x80) 
-					{
-						if (mem_fread(&channelvelocities[channel], 1, 1, musinput) != 1) 
-						{
-							return true;
-						}
+                    if (key & 0x80)
+                    {
+                        if (mem_fread(&channelvelocities[channel], 1, 1, musinput) != 1)
+                        {
+                            return true;
+                        }
 
-						channelvelocities[channel] &= 0x7F;
-					}
+                        channelvelocities[channel] &= 0x7F;
+                    }
 
-					if (midi_writepresskey(channel, key, channelvelocities[channel], midioutput)) 
-					{
-						return true;
-					}
+                    if (WritePressKey(channel, key,
+                                      channelvelocities[channel], midioutput))
+                    {
+                        return true;
+                    }
 
-					break;
+                    break;
 
-				case mus_pitchwheel:
-					if (mem_fread(&key, 1, 1, musinput) != 1) 
-					{
-						break;
-					}
-					if (midi_writepitchwheel(channel, (short)(key * 64), midioutput)) 
-					{
-						return true;
-					}
+                case mus_pitchwheel:
+                    if (mem_fread(&key, 1, 1, musinput) != 1)
+                    {
+                        break;
+                    }
+                    if (WritePitchWheel(channel, (short)(key * 64), midioutput))
+                    {
+                        return true;
+                    }
 
-					break;
-				
-				case mus_systemevent:
-					if (mem_fread(&controllernumber, 1, 1, musinput) != 1) 
-					{
-						return true;
-					}
-					if (controllernumber < 10 || controllernumber > 14) 
-					{
-						return true;
-					}
+                    break;
 
-					if (midi_writechangecontroller_valueless(channel, mus2midi_translation[controllernumber], midioutput)) 
-					{
-						return true;
-					}
+                case mus_systemevent:
+                    if (mem_fread(&controllernumber, 1, 1, musinput) != 1)
+                    {
+                        return true;
+                    }
+                    if (controllernumber < 10 || controllernumber > 14)
+                    {
+                        return true;
+                    }
 
-					break;
-				
-				case mus_changecontroller:
-					if (mem_fread(&controllernumber, 1, 1, musinput) != 1) 
-					{
-						return true;
-					}
+                    if (WriteChangeController_Valueless(channel,
+                                                        controller_map[controllernumber],
+                                                        midioutput))
+                    {
+                        return true;
+                    }
 
-					if (mem_fread(&controllervalue, 1, 1, musinput) != 1) 
-					{
-						return true;
-					}
+                    break;
 
-					if (controllernumber == 0) 
-					{
-						if (midi_writechangepatch(channel, controllervalue, midioutput)) 
-						{
-							return true;
-						}
-					} 
-					else 
-					{
-						if (controllernumber < 1 || controllernumber > 9) 
-						{
-							return true;
-						}
+                case mus_changecontroller:
+                    if (mem_fread(&controllernumber, 1, 1, musinput) != 1)
+                    {
+                        return true;
+                    }
 
-						if (midi_writechangecontroller_valued(channel, mus2midi_translation[controllernumber], controllervalue, midioutput)) 
-						{
-							return true;
-						}
-					}
+                    if (mem_fread(&controllervalue, 1, 1, musinput) != 1)
+                    {
+                        return true;
+                    }
 
-					break;
-				
-				case mus_scoreend:
-					hitscoreend = 1;
-					break;
+                    if (controllernumber == 0)
+                    {
+                        if (WriteChangePatch(channel, controllervalue,
+                                             midioutput))
+                        {
+                            return true;
+                        }
+                    }
+                    else
+                    {
+                        if (controllernumber < 1 || controllernumber > 9)
+                        {
+                            return true;
+                        }
 
-				default:
-					return true;
-					break;
-			}
+                        if (WriteChangeController_Valued(channel,
+                                                         controller_map[controllernumber],
+                                                         controllervalue,
+                                                         midioutput))
+                        {
+                            return true;
+                        }
+                    }
 
-			if (eventdescriptor & 0x80) 
-			{
-				break;
-			}
-		}
-		// Now we need to read the time code:
-		if (!hitscoreend) 
-		{
-			timedelay = 0;
-			for (;;) 
-			{
-				if (mem_fread(&working, 1, 1, musinput) != 1) 
-				{
-					return true;
-				}
+                    break;
 
-				timedelay = timedelay * 128 + (working & 0x7F);
-				if ((working & 0x80) == 0) 
-				{
-					break;
-				}
-			}
-			queuedtime += timedelay;
-		}
-	}
+                case mus_scoreend:
+                    hitscoreend = 1;
+                    break;
 
-	// End of track
-	if (midi_writeendtrack(midioutput)) 
-	{
-		return true;
-	}
+                default:
+                    return true;
+                    break;
+            }
 
-	// Write the track size into the stream
-	if (mem_fseek(midioutput, 18, MEM_SEEK_SET)) 
-	{
-		return true;
-	}
+            if (eventdescriptor & 0x80)
+            {
+                break;
+            }
+        }
+        // Now we need to read the time code:
+        if (!hitscoreend)
+        {
+            timedelay = 0;
+            for (;;)
+            {
+                if (mem_fread(&working, 1, 1, musinput) != 1)
+                {
+                    return true;
+                }
 
-        tracksizebuffer[0] = (tracksize >> 24) & 0xff;
-        tracksizebuffer[1] = (tracksize >> 16) & 0xff;
-        tracksizebuffer[2] = (tracksize >> 8) & 0xff;
-        tracksizebuffer[3] = tracksize & 0xff;
+                timedelay = timedelay * 128 + (working & 0x7F);
+                if ((working & 0x80) == 0)
+                {
+                    break;
+                }
+            }
+            queuedtime += timedelay;
+        }
+    }
 
-	if (mem_fwrite(tracksizebuffer, 1, 4, midioutput) != 4) 
-	{
-		return true;
-	}
+    // End of track
+    if (WriteEndTrack(midioutput))
+    {
+        return true;
+    }
 
-	return false;
+    // Write the track size into the stream
+    if (mem_fseek(midioutput, 18, MEM_SEEK_SET))
+    {
+        return true;
+    }
+
+    tracksizebuffer[0] = (tracksize >> 24) & 0xff;
+    tracksizebuffer[1] = (tracksize >> 16) & 0xff;
+    tracksizebuffer[2] = (tracksize >> 8) & 0xff;
+    tracksizebuffer[3] = tracksize & 0xff;
+
+    if (mem_fwrite(tracksizebuffer, 1, 4, midioutput) != 4)
+    {
+        return true;
+    }
+
+    return false;
 }
 
--- a/src/resource.rc.in
+++ b/src/resource.rc.in
@@ -13,7 +13,7 @@
    VALUE "FileDescription", "@PACKAGE_STRING@"
    VALUE "InternalName", "@PACKAGE_TARNAME@"
    VALUE "CompanyName", "@PACKAGE_BUGREPORT@"
-   VALUE "LegalCopyright", "GNU General Public License"
+   VALUE "LegalCopyright", "@PACKAGE_COPYRIGHT@.  Licensed under @PACKAGE_LICENSE@"
    VALUE "ProductName", "@PACKAGE_NAME@"
    VALUE "ProductVersion", "@PACKAGE_VERSION@"
   }
--- a/src/setup/.gitignore
+++ b/src/setup/.gitignore
@@ -1,7 +1,8 @@
-Makefile
 Makefile.in
+Makefile
 .deps
+chocolate-setup
 *.rc
-chocolate-doom
-chocolate-server
 *.exe
+tags
+TAGS
--- a/src/setup/Makefile.am
+++ b/src/setup/Makefile.am
@@ -27,3 +27,10 @@
 EXTRA_DIST=                                     \
     setup_icon.c
 
+if HAVE_PYTHON
+
+setup_icon.c : $(top_builddir)/data/setup8.ico
+	$(top_builddir)/data/convert-icon $^ $@
+
+endif
+
--- a/src/setup/execute.c
+++ b/src/setup/execute.c
@@ -172,29 +172,38 @@
 
 static wchar_t *BuildCommandLine(const char *program, const char *arg)
 {
+    wchar_t exe_path[MAX_PATH];
     wchar_t *result;
-    char *sep;
+    wchar_t *sep;
 
-    result = calloc(strlen(myargv[0]) + strlen(program) + strlen(arg) + 6,
+    // Get the path to this .exe file.
+
+    GetModuleFileNameW(NULL, exe_path, MAX_PATH);
+
+    // Allocate buffer to contain result string.
+
+    result = calloc(wcslen(exe_path) + strlen(program) + strlen(arg) + 6,
                     sizeof(wchar_t));
 
     wcscpy(result, L"\"");
 
-    sep = strrchr(myargv[0], DIR_SEPARATOR);
+    // Copy the path part of the filename (including ending \)
+    // into the result buffer:
 
+    sep = wcsrchr(exe_path, DIR_SEPARATOR);
+
     if (sep != NULL)
     {
-        ConcatWCString(result, myargv[0]);
-
-        // Cut off the string after the last directory separator,
-        // before appending the actual program.
-
-        result[sep - myargv[0] + 2] = '\0';
-        
+        wcsncpy(result + 1, exe_path, sep - exe_path + 1);
+        result[sep - exe_path + 2] = '\0';
     }
 
+    // Concatenate the name of the program:
+
     ConcatWCString(result, program);
 
+    // End of program name, start of argument:
+
     wcscat(result, L"\" \"");
 
     ConcatWCString(result, arg);
@@ -290,7 +299,7 @@
 
         execvp(argv[0], (char **) argv);
 
-        exit(-1);
+        exit(0x80);
     }
     else
     {
@@ -299,7 +308,7 @@
 
         waitpid(childpid, &result, 0);
 
-        if (WIFEXITED(result)) 
+        if (WIFEXITED(result) && WEXITSTATUS(result) != 0x80) 
         {
             return WEXITSTATUS(result);
         }
--- a/src/strife/.gitignore
+++ b/src/strife/.gitignore
@@ -5,3 +5,5 @@
 chocolate-doom
 chocolate-server
 *.exe
+tags
+TAGS
--- a/src/strife/Makefile.am
+++ b/src/strife/Makefile.am
@@ -63,15 +63,11 @@
 FEATURE_DEHACKED_SOURCE_FILES =            \
 deh_ammo.c                                 \
 deh_cheat.c                                \
-deh_defs.h                                 \
+deh_strife.c                               \
 deh_frame.c                                \
-deh_io.c             deh_io.h              \
-deh_main.c           deh_main.h            \
-deh_mapping.c        deh_mapping.h         \
 deh_misc.c           deh_misc.h            \
 deh_ptr.c                                  \
 deh_sound.c                                \
-deh_text.c                                 \
 deh_thing.c                                \
 deh_weapon.c
 
--- a/src/strife/deh_defs.h
+++ /dev/null
@@ -1,68 +1,0 @@
-// Emacs style mode select   -*- C++ -*- 
-//-----------------------------------------------------------------------------
-//
-// Copyright(C) 2005 Simon Howard
-//
-// This program is free software; you can redistribute it and/or
-// modify it under the terms of the GNU General Public License
-// as published by the Free Software Foundation; either version 2
-// of the License, or (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with this program; if not, write to the Free Software
-// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
-// 02111-1307, USA.
-//
-//-----------------------------------------------------------------------------
-//
-// Definitions for use in the dehacked code
-//
-//-----------------------------------------------------------------------------
-
-#ifndef DEH_DEFS_H
-#define DEH_DEFS_H
-
-#include "md5.h"
-
-typedef struct deh_context_s deh_context_t;
-typedef struct deh_section_s deh_section_t;
-typedef void (*deh_section_init_t)(void);
-typedef void *(*deh_section_start_t)(deh_context_t *context, char *line);
-typedef void (*deh_section_end_t)(deh_context_t *context, void *tag);
-typedef void (*deh_line_parser_t)(deh_context_t *context, char *line, void *tag);
-typedef void (*deh_md5_hash_t)(md5_context_t *context);
-
-struct deh_section_s
-{
-    char *name;
-
-    // Called on startup to initialize code
-
-    deh_section_init_t init;
-    
-    // This is called when a new section is started.  The pointer
-    // returned is used as a tag for the following calls.
-
-    deh_section_start_t start;
-
-    // This is called for each line in the section
-
-    deh_line_parser_t line_parser;
-
-    // This is called at the end of the section for any cleanup
-
-    deh_section_end_t end;
-
-    // Called when generating an MD5 sum of the dehacked state
-
-    deh_md5_hash_t md5_hash;
-};
-
-#endif /* #ifndef DEH_DEFS_H */
-
-
--- a/src/strife/deh_io.c
+++ /dev/null
@@ -1,211 +1,0 @@
-// Emacs style mode select   -*- C++ -*- 
-//-----------------------------------------------------------------------------
-//
-// Copyright(C) 2005 Simon Howard
-//
-// This program is free software; you can redistribute it and/or
-// modify it under the terms of the GNU General Public License
-// as published by the Free Software Foundation; either version 2
-// of the License, or (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with this program; if not, write to the Free Software
-// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
-// 02111-1307, USA.
-//
-//-----------------------------------------------------------------------------
-//
-// Dehacked I/O code (does all reads from dehacked files)
-//
-//-----------------------------------------------------------------------------
-
-#include <stdarg.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-
-#include "i_system.h"
-#include "z_zone.h"
-
-#include "deh_defs.h"
-#include "deh_io.h"
-
-struct deh_context_s
-{
-    FILE *stream;
-    char *filename;
-    int linenum;
-    boolean last_was_newline;
-    char *readbuffer;
-    int readbuffer_size;
-};
-
-// Open a dehacked file for reading
-// Returns NULL if open failed
-
-deh_context_t *DEH_OpenFile(char *filename)
-{
-    FILE *fstream;
-    deh_context_t *context;
-    
-    fstream = fopen(filename, "r");
-
-    if (fstream == NULL)
-        return NULL;
-
-    context = Z_Malloc(sizeof(*context), PU_STATIC, NULL);
-    context->stream = fstream;
-    
-    // Initial read buffer size of 128 bytes
-
-    context->readbuffer_size = 128;
-    context->readbuffer = Z_Malloc(context->readbuffer_size, PU_STATIC, NULL);
-    context->filename = filename;
-    context->linenum = 0;
-    context->last_was_newline = true;
-
-    return context;
-}
-
-// Close dehacked file
-
-void DEH_CloseFile(deh_context_t *context)
-{
-    fclose(context->stream);
-    Z_Free(context->readbuffer);
-    Z_Free(context);
-}
-
-// Reads a single character from a dehacked file
-
-int DEH_GetChar(deh_context_t *context)
-{
-    int result;
-   
-    // Read characters, but ignore carriage returns
-    // Essentially this is a DOS->Unix conversion
-
-    do 
-    {
-        if (feof(context->stream))
-        {
-            // end of file
-
-            result = -1;
-        }
-        else
-        {
-            result = fgetc(context->stream);
-        }
-
-    } while (result == '\r');
-
-    // Track the current line number
-
-    if (context->last_was_newline)
-    {
-        ++context->linenum;
-    }
-    
-    context->last_was_newline = result == '\n';
-    
-    return result;
-}
-
-// Increase the read buffer size
-
-static void IncreaseReadBuffer(deh_context_t *context)
-{
-    char *newbuffer;
-    int newbuffer_size;
-
-    newbuffer_size = context->readbuffer_size * 2;
-    newbuffer = Z_Malloc(newbuffer_size, PU_STATIC, NULL);
-
-    memcpy(newbuffer, context->readbuffer, context->readbuffer_size);
-
-    Z_Free(context->readbuffer);
-
-    context->readbuffer = newbuffer;
-    context->readbuffer_size = newbuffer_size;
-}
-
-// Read a whole line
-
-char *DEH_ReadLine(deh_context_t *context)
-{
-    int c;
-    int pos;
-
-    for (pos = 0;;)
-    {
-        c = DEH_GetChar(context);
-
-        if (c < 0)
-        {
-            // end of file
-
-            return NULL;
-        }
-
-        // cope with lines of any length: increase the buffer size
-
-        if (pos >= context->readbuffer_size)
-        {
-            IncreaseReadBuffer(context);
-        }
-
-        if (c == '\n')
-        {
-            // end of line: a full line has been read
-
-            context->readbuffer[pos] = '\0';
-            break;
-        }
-        else if (c != '\0')
-        {
-            // normal character; don't allow NUL characters to be
-            // added.
-
-            context->readbuffer[pos] = (char) c;
-            ++pos;
-        }
-    }
-    
-    return context->readbuffer;
-}
-
-void DEH_Warning(deh_context_t *context, char *msg, ...)
-{
-    va_list args;
-
-    va_start(args, msg);
-    
-    fprintf(stderr, "%s:%i: warning: ", context->filename, context->linenum);
-    vfprintf(stderr, msg, args);
-    fprintf(stderr, "\n");
-
-    va_end(args);
-}
-
-void DEH_Error(deh_context_t *context, char *msg, ...)
-{
-    va_list args;
-
-    va_start(args, msg);
-    
-    fprintf(stderr, "%s:%i: ", context->filename, context->linenum);
-    vfprintf(stderr, msg, args);
-    fprintf(stderr, "\n");
-
-    va_end(args);
-
-    I_Error("Error parsing dehacked file");
-}
-
-
--- a/src/strife/deh_io.h
+++ /dev/null
@@ -1,40 +1,0 @@
-// Emacs style mode select   -*- C++ -*- 
-//-----------------------------------------------------------------------------
-//
-// Copyright(C) 2005 Simon Howard
-//
-// This program is free software; you can redistribute it and/or
-// modify it under the terms of the GNU General Public License
-// as published by the Free Software Foundation; either version 2
-// of the License, or (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with this program; if not, write to the Free Software
-// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
-// 02111-1307, USA.
-//
-//-----------------------------------------------------------------------------
-//
-// Dehacked I/O code (does all reads from dehacked files)
-//
-//-----------------------------------------------------------------------------
-
-#ifndef DEH_IO_H
-#define DEH_IO_H
-
-#include "deh_defs.h"
-
-deh_context_t *DEH_OpenFile(char *filename);
-void DEH_CloseFile(deh_context_t *context);
-int DEH_GetChar(deh_context_t *context);
-char *DEH_ReadLine(deh_context_t *context);
-void DEH_Error(deh_context_t *context, char *msg, ...);
-void DEH_Warning(deh_context_t *context, char *msg, ...);
-
-#endif /* #ifndef DEH_IO_H */
-
--- a/src/strife/deh_main.c
+++ /dev/null
@@ -1,423 +1,0 @@
-// Emacs style mode select   -*- C++ -*- 
-//-----------------------------------------------------------------------------
-//
-// Copyright(C) 2005 Simon Howard
-//
-// This program is free software; you can redistribute it and/or
-// modify it under the terms of the GNU General Public License
-// as published by the Free Software Foundation; either version 2
-// of the License, or (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with this program; if not, write to the Free Software
-// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
-// 02111-1307, USA.
-//
-//-----------------------------------------------------------------------------
-//
-// Main dehacked code
-//
-//-----------------------------------------------------------------------------
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <ctype.h>
-
-#include "doomtype.h"
-#include "d_iwad.h"
-#include "m_argv.h"
-
-#include "deh_defs.h"
-#include "deh_io.h"
-
-static char *deh_signatures[] = 
-{
-    "Patch File for DeHackEd v2.3",
-    "Patch File for DeHackEd v3.0",
-};
-
-// deh_ammo.c:
-extern deh_section_t deh_section_ammo;
-// deh_cheat.c:
-extern deh_section_t deh_section_cheat;
-// deh_frame.c:
-extern deh_section_t deh_section_frame;
-// deh_misc.c:
-extern deh_section_t deh_section_misc;
-// deh_ptr.c:
-extern deh_section_t deh_section_pointer;
-// deh_sound.c
-extern deh_section_t deh_section_sound;
-// deh_text.c:
-extern deh_section_t deh_section_text;
-// deh_thing.c: 
-extern deh_section_t deh_section_thing;
-// deh_weapon.c: 
-extern deh_section_t deh_section_weapon;
-
-// If true, we can do long string replacements.
-
-boolean deh_allow_long_strings = false;
-
-// If true, we can do cheat replacements longer than the originals.
-
-boolean deh_allow_long_cheats = false;
-
-// If false, dehacked cheat replacements are ignored.
-
-boolean deh_apply_cheats = true;
-
-//
-// List of section types:
-//
-
-static deh_section_t *section_types[] =
-{
-    &deh_section_ammo,
-    &deh_section_cheat,
-    &deh_section_frame,
-    &deh_section_misc,
-    &deh_section_pointer,
-    &deh_section_sound,
-    &deh_section_text,
-    &deh_section_thing,
-    &deh_section_weapon,
-};
-
-void DEH_Checksum(md5_digest_t digest)
-{
-    md5_context_t md5_context;
-    unsigned int i;
-
-    MD5_Init(&md5_context);
-
-    for (i=0; i<arrlen(section_types); ++i)
-    {
-        if (section_types[i]->md5_hash != NULL)
-        {
-            section_types[i]->md5_hash(&md5_context);
-        }
-    }
-
-    MD5_Final(digest, &md5_context);
-}
-
-// Called on startup to call the Init functions
-
-static void InitializeSections(void)
-{
-    unsigned int i;
-
-    for (i=0; i<arrlen(section_types); ++i)
-    {
-        if (section_types[i]->init != NULL)
-        {
-            section_types[i]->init();
-        }
-    }
-}
-
-// Given a section name, get the section structure which corresponds
-
-static deh_section_t *GetSectionByName(char *name)
-{
-    unsigned int i;
-
-    for (i=0; i<arrlen(section_types); ++i)
-    {
-        if (!strcasecmp(section_types[i]->name, name))
-        {
-            return section_types[i];
-        }
-    }
-
-    return NULL;
-}
-
-// Is the string passed just whitespace?
-
-static boolean IsWhitespace(char *s)
-{
-    for (; *s; ++s)
-    {
-        if (!isspace(*s))
-            return false;
-    }
-
-    return true;
-}
-
-// Strip whitespace from the start and end of a string
-
-static char *CleanString(char *s)
-{
-    char *strending;
-
-    // Leading whitespace
-
-    while (*s && isspace(*s))
-        ++s;
-
-    // Trailing whitespace
-   
-    strending = s + strlen(s) - 1;
-
-    while (strlen(s) > 0 && isspace(*strending))
-    {
-        *strending = '\0';
-        --strending;
-    }
-
-    return s;
-}
-
-// This pattern is used a lot of times in different sections, 
-// an assignment is essentially just a statement of the form:
-//
-// Variable Name = Value
-//
-// The variable name can include spaces or any other characters.
-// The string is split on the '=', essentially.
-//
-// Returns true if read correctly
-
-boolean DEH_ParseAssignment(char *line, char **variable_name, char **value)
-{
-    char *p;
-
-    // find the equals
-    
-    p = strchr(line, '=');
-
-    if (p == NULL && p-line > 2)
-    {
-        return false;
-    }
-
-    // variable name at the start
-    // turn the '=' into a \0 to terminate the string here
-
-    *p = '\0';
-    *variable_name = CleanString(line);
-    
-    // value immediately follows the '='
-    
-    *value = CleanString(p+1);
-    
-    return true;
-}
-
-static boolean CheckSignatures(deh_context_t *context)
-{
-    size_t i;
-    char *line;
-    
-    // Read the first line
-
-    line = DEH_ReadLine(context);
-
-    if (line == NULL)
-    {
-        return false;
-    }
-
-    // Check all signatures to see if one matches
-
-    for (i=0; i<arrlen(deh_signatures); ++i)
-    {
-        if (!strcmp(deh_signatures[i], line))
-        {
-            return true;
-        }
-    }
-
-    return false;
-}
-
-// Parses a comment string in a dehacked file.
-
-static void DEH_ParseComment(char *comment)
-{
-    // Allow comments containing this special value to allow string
-    // replacements longer than those permitted by DOS dehacked.
-    // This allows us to use a dehacked patch for doing string 
-    // replacements for emulating Chex Quest.
-    //
-    // If you use this, your dehacked patch may not work in Vanilla
-    // Doom.
-
-    if (strstr(comment, "*allow-long-strings*") != NULL)
-    {
-        deh_allow_long_strings = true;
-    }
-
-    // Allow magic comments to allow longer cheat replacements than
-    // those permitted by DOS dehacked.  This is also for Chex
-    // Quest.
-
-    if (strstr(comment, "*allow-long-cheats*") != NULL)
-    {
-        deh_allow_long_cheats = true;
-    }
-}
-
-// Parses a dehacked file by reading from the context
-
-static void DEH_ParseContext(deh_context_t *context)
-{
-    deh_section_t *current_section = NULL;
-    char section_name[20];
-    void *tag = NULL;
-    char *line;
-    
-    // Read the header and check it matches the signature
-
-    if (!CheckSignatures(context))
-    {
-        DEH_Error(context, "This is not a valid dehacked patch file!");
-    }
-
-    deh_allow_long_strings = false;
-    deh_allow_long_cheats = false;
-    
-    // Read the file
-    
-    for (;;) 
-    {
-        // read a new line
- 
-        line = DEH_ReadLine(context);
-
-        // end of file?
-
-        if (line == NULL)
-            return;
-
-        while (line[0] != '\0' && isspace(line[0]))
-            ++line;
-
-        if (line[0] == '#')
-        {
-            // comment
-
-            DEH_ParseComment(line);
-            continue;
-        }
-
-        if (IsWhitespace(line))
-        {
-            if (current_section != NULL)
-            {
-                // end of section
-
-                if (current_section->end != NULL)
-                {
-                    current_section->end(context, tag);
-                }
-
-                //printf("end %s tag\n", current_section->name);
-                current_section = NULL;
-            }
-        }
-        else
-        {
-            if (current_section != NULL)
-            {
-                // parse this line
-
-                current_section->line_parser(context, line, tag);
-            }
-            else
-            {
-                // possibly the start of a new section
-
-                sscanf(line, "%19s", section_name);
-
-                current_section = GetSectionByName(section_name);
-                
-                if (current_section != NULL)
-                {
-                    tag = current_section->start(context, line);
-                    //printf("started %s tag\n", section_name);
-                }
-                else
-                {
-                    //printf("unknown section name %s\n", section_name);
-                }
-            }
-        }
-    }
-}
-
-// Parses a dehacked file
-
-int DEH_LoadFile(char *filename)
-{
-    deh_context_t *context;
-
-    printf(" loading %s\n", filename);
-
-    context = DEH_OpenFile(filename);
-
-    if (context == NULL)
-    {
-        fprintf(stderr, "DEH_LoadFile: Unable to open %s\n", filename);
-        return 0;
-    }
-    
-    DEH_ParseContext(context);
-    
-    DEH_CloseFile(context);
-
-    return 1;
-}
-
-// Checks the command line for -deh argument
-
-void DEH_Init(void)
-{
-    char *filename;
-    int p;
-
-    InitializeSections();
-
-    //!
-    // @category mod
-    //
-    // Ignore cheats in dehacked files.
-    //
-
-    if (M_CheckParm("-nocheats") > 0) 
-    {
-	deh_apply_cheats = false;
-    }
-
-    //!
-    // @arg <files>
-    // @category mod
-    //
-    // Load the given dehacked patch(es)
-    //
-
-    p = M_CheckParm("-deh");
-
-    if (p > 0)
-    {
-        ++p;
-
-        while (p < myargc && myargv[p][0] != '-')
-        {
-            filename = D_TryFindWADByName(myargv[p]);
-            DEH_LoadFile(filename);
-            ++p;
-        }
-    }
-}
-
-
--- a/src/strife/deh_main.h
+++ /dev/null
@@ -1,54 +1,0 @@
-// Emacs style mode select   -*- C++ -*- 
-//-----------------------------------------------------------------------------
-//
-// Copyright(C) 2005 Simon Howard
-//
-// This program is free software; you can redistribute it and/or
-// modify it under the terms of the GNU General Public License
-// as published by the Free Software Foundation; either version 2
-// of the License, or (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with this program; if not, write to the Free Software
-// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
-// 02111-1307, USA.
-//
-//-----------------------------------------------------------------------------
-//
-// Dehacked entrypoint and common code
-//
-//-----------------------------------------------------------------------------
-
-#ifndef DEH_MAIN_H
-#define DEH_MAIN_H
-
-#include "doomtype.h"
-#include "doomfeatures.h"
-#include "md5.h"
-#include "deh_str.h"
-
-// These are the limits that dehacked uses (from dheinit.h in the dehacked
-// source).  If these limits are exceeded, it does not generate an error, but
-// a warning is displayed.
-
-#define DEH_VANILLA_NUMSTATES 966
-#define DEH_VANILLA_NUMSFX 107
-
-void DEH_Init(void);
-int DEH_LoadFile(char *filename);
-
-boolean DEH_ParseAssignment(char *line, char **variable_name, char **value);
-
-void DEH_Checksum(md5_digest_t digest);
-
-extern boolean deh_allow_long_strings;
-extern boolean deh_allow_long_cheats;
-extern boolean deh_apply_cheats;
-
-#endif /* #ifndef DEH_MAIN_H */
-
--- a/src/strife/deh_mapping.c
+++ /dev/null
@@ -1,133 +1,0 @@
-// Emacs style mode select   -*- C++ -*- 
-//-----------------------------------------------------------------------------
-//
-// Copyright(C) 2005 Simon Howard
-//
-// This program is free software; you can redistribute it and/or
-// modify it under the terms of the GNU General Public License
-// as published by the Free Software Foundation; either version 2
-// of the License, or (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with this program; if not, write to the Free Software
-// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
-// 02111-1307, USA.
-//
-//-----------------------------------------------------------------------------
-//
-// Dehacked "mapping" code
-// Allows the fields in structures to be mapped out and accessed by
-// name
-//
-//-----------------------------------------------------------------------------
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-
-#include "doomtype.h"
-#include "i_system.h"
-#include "deh_mapping.h"
-
-//
-// Set the value of a particular field in a structure by name
-//
-
-boolean DEH_SetMapping(deh_context_t *context, deh_mapping_t *mapping, 
-                       void *structptr, char *name, int value)
-{
-    int i;
-
-    for (i=0; mapping->entries[i].name != NULL; ++i)
-    {
-        deh_mapping_entry_t *entry = &mapping->entries[i];
-
-        if (!strcasecmp(entry->name, name))
-        {
-            void *location;
-
-            if (entry->location == NULL)
-            {
-                DEH_Warning(context, "Field '%s' is unsupported", name);
-                return false;
-            }
-
-            location = (uint8_t *)structptr + ((uint8_t *)entry->location - (uint8_t *)mapping->base);
-
-     //       printf("Setting %p::%s to %i (%i bytes)\n",
-     //               structptr, name, value, entry->size);
-                
-            switch (entry->size)
-            {
-                case 1:
-                    * ((uint8_t *) location) = value;
-                    break;
-                case 2:
-                    * ((uint16_t *) location) = value;
-                    break;
-                case 4:
-                    * ((uint32_t *) location) = value;
-                    break;
-                default:
-                    DEH_Error(context, "Unknown field type for '%s' (BUG)", name);
-                    return false;
-            }
-
-            return true;
-        }
-    }
-
-    // field with this name not found
-
-    DEH_Warning(context, "Field named '%s' not found", name);
-
-    return false;
-}
-
-void DEH_StructMD5Sum(md5_context_t *context, deh_mapping_t *mapping,
-                      void *structptr)
-{
-    int i;
-
-    // Go through each mapping
-
-    for (i=0; mapping->entries[i].name != NULL; ++i)
-    {
-        deh_mapping_entry_t *entry = &mapping->entries[i];
-        void *location;
-
-        if (entry->location == NULL)
-        {
-            // Unsupported field
-
-            continue;
-        }
-
-        // Add in data for this field
-
-        location = (uint8_t *)structptr + ((uint8_t *)entry->location - (uint8_t *)mapping->base);
-
-        switch (entry->size)
-        {
-            case 1:
-                MD5_UpdateInt32(context, *((uint8_t *) location));
-                break;
-            case 2:
-                MD5_UpdateInt32(context, *((uint16_t *) location));
-                break;
-            case 4:
-                MD5_UpdateInt32(context, *((uint32_t *) location));
-                break;
-            default:
-                I_Error("Unknown dehacked mapping field type for '%s' (BUG)", 
-                        entry->name);
-                break;
-        }
-    }
-}
-
--- a/src/strife/deh_mapping.h
+++ /dev/null
@@ -1,90 +1,0 @@
-// Emacs style mode select   -*- C++ -*- 
-//-----------------------------------------------------------------------------
-//
-// Copyright(C) 2005 Simon Howard
-//
-// This program is free software; you can redistribute it and/or
-// modify it under the terms of the GNU General Public License
-// as published by the Free Software Foundation; either version 2
-// of the License, or (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with this program; if not, write to the Free Software
-// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
-// 02111-1307, USA.
-//
-//-----------------------------------------------------------------------------
-//
-// Dehacked "mapping" code
-// Allows the fields in structures to be mapped out and accessed by
-// name
-//
-//-----------------------------------------------------------------------------
-
-#ifndef DEH_MAPPING_H
-#define DEH_MAPPING_H
-
-#include "doomtype.h"
-#include "deh_io.h"
-#include "md5.h"
-
-#define DEH_BEGIN_MAPPING(mapping_name, structname)           \
-    static structname deh_mapping_base;                       \
-    static deh_mapping_t mapping_name =                       \
-    {                                                         \
-        &deh_mapping_base,                                    \
-        {
-
-#define DEH_MAPPING(deh_name, fieldname)                      \
-             {deh_name, &deh_mapping_base.fieldname,          \
-                 sizeof(deh_mapping_base.fieldname)},
-
-#define DEH_UNSUPPORTED_MAPPING(deh_name)                     \
-             {deh_name, NULL, -1},
-            
-#define DEH_END_MAPPING                                       \
-             {NULL, NULL, -1}                                 \
-        }                                                     \
-    };
-
-    
-
-#define MAX_MAPPING_ENTRIES 32
-
-typedef struct deh_mapping_s deh_mapping_t;
-typedef struct deh_mapping_entry_s deh_mapping_entry_t;
-
-struct deh_mapping_entry_s 
-{
-    // field name
-   
-    char *name;
-
-    // location relative to the base in the deh_mapping_t struct
-    // If this is NULL, it is an unsupported mapping
-
-    void *location;
-
-    // field size
-
-    int size;
-};
-
-struct deh_mapping_s
-{
-    void *base;
-    deh_mapping_entry_t entries[MAX_MAPPING_ENTRIES];
-};
-
-boolean DEH_SetMapping(deh_context_t *context, deh_mapping_t *mapping, 
-                       void *structptr, char *name, int value);
-void DEH_StructMD5Sum(md5_context_t *context, deh_mapping_t *mapping,
-                      void *structptr);
-
-#endif /* #ifndef DEH_MAPPING_H */
-
--- /dev/null
+++ b/src/strife/deh_strife.c
@@ -1,0 +1,74 @@
+// Emacs style mode select   -*- C++ -*-
+//-----------------------------------------------------------------------------
+//
+// Copyright(C) 2005 Simon Howard
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+// 02111-1307, USA.
+//
+//-----------------------------------------------------------------------------
+//
+// Top-level dehacked definitions for Strife dehacked (sehacked)
+//
+//-----------------------------------------------------------------------------
+
+#include <stdlib.h>
+#include "deh_defs.h"
+#include "deh_main.h"
+
+char *deh_signatures[] =
+{
+    "Patch File for DeHackEd v2.3",
+    "Patch File for DeHackEd v3.0",
+    NULL
+};
+
+// deh_ammo.c:
+extern deh_section_t deh_section_ammo;
+// deh_cheat.c:
+extern deh_section_t deh_section_cheat;
+// deh_frame.c:
+extern deh_section_t deh_section_frame;
+// deh_misc.c:
+extern deh_section_t deh_section_misc;
+// deh_ptr.c:
+extern deh_section_t deh_section_pointer;
+// deh_sound.c
+extern deh_section_t deh_section_sound;
+// deh_text.c:
+extern deh_section_t deh_section_text;
+// deh_thing.c:
+extern deh_section_t deh_section_thing;
+// deh_weapon.c:
+extern deh_section_t deh_section_weapon;
+
+//
+// List of section types:
+//
+
+deh_section_t *deh_section_types[] =
+{
+    &deh_section_ammo,
+    &deh_section_cheat,
+    &deh_section_frame,
+    &deh_section_misc,
+    &deh_section_pointer,
+    &deh_section_sound,
+    &deh_section_text,
+    &deh_section_thing,
+    &deh_section_weapon,
+    NULL
+};
+
--- a/src/strife/deh_text.c
+++ /dev/null
@@ -1,127 +1,0 @@
-// Emacs style mode select   -*- C++ -*- 
-//-----------------------------------------------------------------------------
-//
-// Copyright(C) 2005 Simon Howard
-//
-// This program is free software; you can redistribute it and/or
-// modify it under the terms of the GNU General Public License
-// as published by the Free Software Foundation; either version 2
-// of the License, or (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with this program; if not, write to the Free Software
-// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
-// 02111-1307, USA.
-//
-//-----------------------------------------------------------------------------
-//
-// Parses Text substitution sections in dehacked files
-//
-//-----------------------------------------------------------------------------
-
-#include <stdio.h>
-#include <string.h>
-
-#include "doomtype.h"
-
-#include "z_zone.h"
-
-#include "deh_defs.h"
-#include "deh_io.h"
-#include "deh_main.h"
-
-// Given a string length, find the maximum length of a 
-// string that can replace it.
-
-static int TXT_MaxStringLength(int len)
-{
-    // Enough bytes for the string and the NUL terminator
-
-    len += 1;
-
-    // All strings in doom.exe are on 4-byte boundaries, so we may be able
-    // to support a slightly longer string.
-    // Extend up to the next 4-byte boundary
-
-    len += (4 - (len % 4)) % 4;
-            
-    // Less one for the NUL terminator.
-
-    return len - 1;
-}
-
-static void *DEH_TextStart(deh_context_t *context, char *line)
-{
-    char *from_text, *to_text;
-    int fromlen, tolen;
-    int i;
-    
-    if (sscanf(line, "Text %i %i", &fromlen, &tolen) != 2)
-    {
-        DEH_Warning(context, "Parse error on section start");
-        return NULL;
-    }
-
-    // Only allow string replacements that are possible in Vanilla Doom.  
-    // Chocolate Doom is unforgiving!
-
-    if (!deh_allow_long_strings && tolen > TXT_MaxStringLength(fromlen))
-    {
-        DEH_Error(context, "Replacement string is longer than the maximum "
-                           "possible in doom.exe");
-        return NULL;
-    }
-
-    from_text = Z_Malloc(fromlen + 1, PU_STATIC, NULL);
-    to_text = Z_Malloc(tolen + 1, PU_STATIC, NULL);
-
-    // read in the "from" text
-
-    for (i=0; i<fromlen; ++i)
-    {
-        int c;
-
-        c = DEH_GetChar(context);
-            
-        from_text[i] = c;
-    }
-
-    from_text[fromlen] = '\0';
-
-    // read in the "to" text
-
-    for (i=0; i<tolen; ++i)
-    {
-        int c;
-
-        c = DEH_GetChar(context);
-            
-        to_text[i] = c;
-    }
-    to_text[tolen] = '\0';
-
-    DEH_AddStringReplacement(from_text, to_text);
-    
-    return NULL;
-}
-
-static void DEH_TextParseLine(deh_context_t *context, char *line, void *tag)
-{
-    // not used
-}
-
-deh_section_t deh_section_text =
-{
-    "Text",
-    NULL,
-    DEH_TextStart,
-    DEH_TextParseLine,
-    NULL,
-    NULL,
-};
-
--- a/src/strife/g_game.c
+++ b/src/strife/g_game.c
@@ -588,10 +588,20 @@
 
     if (lowres_turn)
     {
-        // round angleturn to the nearest 256 boundary
+        static signed short carry = 0;
+        signed short desired_angleturn;
+
+        desired_angleturn = cmd->angleturn + carry;
+
+        // round angleturn to the nearest 256 unit boundary
         // for recording demos with single byte values for turn
 
-        cmd->angleturn = (cmd->angleturn + 128) & 0xff00;
+        cmd->angleturn = (desired_angleturn + 128) & 0xff00;
+
+        // Carry forward the error from the reduced resolution to the
+        // next tic, so that successive small movements can accumulate.
+
+        carry = desired_angleturn - cmd->angleturn;
     }
 } 
  
--- a/src/strife/hu_stuff.c
+++ b/src/strife/hu_stuff.c
@@ -52,9 +52,9 @@
 // Locally used constants, shortcuts.
 //
 #define HU_TITLE	(mapnames[(gameepisode-1)*9+gamemap-1])
-#define HU_TITLE2	(mapnames2[gamemap-1])
-#define HU_TITLEP	(mapnamesp[gamemap-1])
-#define HU_TITLET	(mapnamest[gamemap-1])
+#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_TITLEHEIGHT	1
 #define HU_TITLEX	0
@@ -171,8 +171,16 @@
     "NEWLEVEL"
 };
 
-char*	mapnames2[] =	// DOOM 2 map names.
+// 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
+// the layout in the Vanilla executable, where it is possible to
+// overflow the end of one array into the next.
+
+char *mapnames_commercial[] =
 {
+    // DOOM 2 map names.
+
     HUSTR_1,
     HUSTR_2,
     HUSTR_3,
@@ -206,12 +214,10 @@
     HUSTR_29,
     HUSTR_30,
     HUSTR_31,
-    HUSTR_32
-};
+    HUSTR_32,
 
+    // Plutonia WAD map names.
 
-char*	mapnamesp[] =	// Plutonia WAD map names.
-{
     PHUSTR_1,
     PHUSTR_2,
     PHUSTR_3,
@@ -245,12 +251,10 @@
     PHUSTR_29,
     PHUSTR_30,
     PHUSTR_31,
-    PHUSTR_32
-};
+    PHUSTR_32,
+    
+    // TNT WAD map names.
 
-
-char *mapnamest[] =	// TNT WAD map names.
-{
     THUSTR_1,
     THUSTR_2,
     THUSTR_3,
--- a/src/strife/p_map.c
+++ b/src/strife/p_map.c
@@ -885,7 +885,17 @@
 	
 	dist = FixedMul (attackrange, in->frac);
 
-	if (li->frontsector->floorheight != li->backsector->floorheight)
+        // Return false if there is no back sector.  This should never
+        // be the case if the line is two-sided; however, some WADs
+        // (eg. ottawau.wad) use this as an "impassible glass" trick
+        // and rely on Vanilla Doom's (unintentional) support for this.
+
+        if (li->backsector == NULL)
+        {
+            return false;
+        }
+
+        if (li->frontsector->floorheight != li->backsector->floorheight)
 	{
 	    slope = FixedDiv (openbottom - shootz , dist);
 	    if (slope > bottomslope)
@@ -973,7 +983,14 @@
 		
 	dist = FixedMul (attackrange, in->frac);
 
-	if (li->frontsector->floorheight != li->backsector->floorheight)
+        // Check if backsector is NULL.  See comment in PTR_AimTraverse.
+
+	if (li->backsector == NULL)
+        {
+            goto hitline;
+        }
+
+        if (li->frontsector->floorheight != li->backsector->floorheight)
 	{
 	    slope = FixedDiv (openbottom - shootz , dist);
 	    if (slope > aimslope)
--- a/src/strife/p_setup.c
+++ b/src/strife/p_setup.c
@@ -167,6 +167,7 @@
     line_t*		ldef;
     int			linedef;
     int			side;
+    int                 sidenum;
 	
     numsegs = W_LumpLength (lump) / sizeof(mapseg_t);
     segs = Z_Malloc (numsegs*sizeof(seg_t),PU_LEVEL,0);	
@@ -179,7 +180,7 @@
     {
 	li->v1 = &vertexes[SHORT(ml->v1)];
 	li->v2 = &vertexes[SHORT(ml->v2)];
-					
+
 	li->angle = (SHORT(ml->angle))<<16;
 	li->offset = (SHORT(ml->offset))<<16;
 	linedef = SHORT(ml->linedef);
@@ -188,10 +189,28 @@
 	side = SHORT(ml->side);
 	li->sidedef = &sides[ldef->sidenum[side]];
 	li->frontsector = sides[ldef->sidenum[side]].sector;
-	if (ldef-> flags & ML_TWOSIDED)
-	    li->backsector = sides[ldef->sidenum[side^1]].sector;
-	else
+
+        if (ldef-> flags & ML_TWOSIDED)
+        {
+            sidenum = ldef->sidenum[side ^ 1];
+
+            // If the sidenum is out of range, this may be a "glass hack"
+            // impassible window.  Point at side #0 (this may not be
+            // the correct Vanilla behavior; however, it seems to work for
+            // OTTAWAU.WAD, which is the one place I've seen this trick
+            // used).
+
+            if (sidenum < 0 || sidenum >= numsides)
+            {
+                sidenum = 0;
+            }
+
+            li->backsector = sides[sidenum].sector;
+        }
+        else
+        {
 	    li->backsector = 0;
+        }
     }
 	
     W_ReleaseLumpNum(lump);
--- a/src/strife/p_sight.c
+++ b/src/strife/p_sight.c
@@ -173,7 +173,7 @@
 	    continue;
 	
 	line->validcount = validcount;
-		
+
 	v1 = line->v1;
 	v2 = line->v2;
 	s1 = P_DivlineSide (v1->x,v1->y, &strace);
@@ -193,6 +193,14 @@
 	// line isn't crossed?
 	if (s1 == s2)
 	    continue;	
+
+        // Backsector may be NULL if this is an "impassible
+        // glass" hack line.
+
+        if (line->backsector == NULL)
+        {
+            return false;
+        }
 
 	// stop because it is not two sided anyway
 	// might do this after updating validcount?
--- a/src/strife/st_stuff.c
+++ b/src/strife/st_stuff.c
@@ -260,9 +260,6 @@
  // Height, in lines. 
 #define ST_OUTHEIGHT		1
 
-#define ST_MAPWIDTH	\
-    (strlen(mapnames[(gameepisode-1)*9+(gamemap-1)]))
-
 #define ST_MAPTITLEX \
     (SCREENWIDTH - ST_MAPWIDTH * ST_CHATFONTWIDTH)
 
@@ -418,10 +415,6 @@
 cheatseq_t cheat_mypos = CHEAT("idmypos", 0);
 
 
-// 
-extern char*	mapnames[];
-
-
 //
 // STATUS BAR CODE
 //
@@ -984,6 +977,17 @@
 	palette = RADIATIONPAL;
     else
 	palette = 0;
+
+    // In Chex Quest, the player never sees red.  Instead, the
+    // radiation suit palette is used to tint the screen green,
+    // as though the player is being covered in goo by an
+    // attacking flemoid.
+
+    if (gameversion == exe_chex
+     && palette >= STARTREDPALS && palette < STARTREDPALS + NUMREDPALS)
+    {
+        palette = RADIATIONPAL;
+    }
 
     if (palette != st_palette)
     {
--- a/textscreen/.gitignore
+++ b/textscreen/.gitignore
@@ -2,3 +2,5 @@
 Makefile.in
 .deps
 *.a
+tags
+TAGS
--- a/textscreen/examples/.gitignore
+++ b/textscreen/examples/.gitignore
@@ -4,3 +4,5 @@
 guitest
 calculator
 *.exe
+tags
+TAGS
--- a/textscreen/txt_sdl.c
+++ b/textscreen/txt_sdl.c
@@ -289,10 +289,10 @@
     int x_end;
     int y_end;
 
-    x_end = LimitToRange(x + w, 0, TXT_SCREEN_W - 1);
-    y_end = LimitToRange(y + h, 0, TXT_SCREEN_H - 1);
-    x = LimitToRange(x, 0, TXT_SCREEN_W - 1);
-    y = LimitToRange(y, 0, TXT_SCREEN_H - 1);
+    x_end = LimitToRange(x + w, 0, TXT_SCREEN_W);
+    y_end = LimitToRange(y + h, 0, TXT_SCREEN_H);
+    x = LimitToRange(x, 0, TXT_SCREEN_W);
+    y = LimitToRange(y, 0, TXT_SCREEN_H);
 
     for (y1=y; y1<y_end; ++y1)
     {
--- /dev/null
+++ b/wince/.gitignore
@@ -1,0 +1,5 @@
+Makefile
+Makefile.in
+.deps
+tags
+TAGS
--- a/wince/Makefile.am
+++ b/wince/Makefile.am
@@ -4,6 +4,7 @@
 if WINDOWS_CE
 
 libc_wince_a_SOURCES =                            \
+        libc_wince.h                              \
         env.c            env.h                    \
 	errno.c          errno.h                  \
         fileops.c        fileops.h