shithub: choc

Download patch

ref: 677729c658b39f075e563fbc9f5a898641f7de54
parent: 8a77b34e936a3fd752db3bc1113e3d7bd0555440
parent: b4f2d75b34b6e2b9bbb2fa6449125d3446a93a73
author: Simon Howard <[email protected]>
date: Fri Feb 5 18:08:12 EST 2010

Merge from trunk.

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

--- 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, [email protected], 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,27 +126,35 @@
 
 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
-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
+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,101 @@
+
+# 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/$(PACKAGE_TARNAME) "$(APP_BIN_DIR)"
+	$(STRIP) "$(APP_BIN_DIR)/$(PACKAGE_TARNAME)"
+	./cp-with-libs $(TOPLEVEL)/setup/chocolate-setup "$(APP_BIN_DIR)"
+	$(STRIP) "$(APP_BIN_DIR)/chocolate-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/@[email protected]     \
-          $(TOPLEVEL)/src/@[email protected]  \
-          $(TOPLEVEL)/src/@[email protected]    \
-          $(TOPLEVEL)/src/@[email protected]   \
-          $(TOPLEVEL)/src/@[email protected]
-
-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@-@[email protected]
-
-@PACKAGE_TARNAME@-@[email protected] : 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
+
+all: $(DOOM_CAB) $(HERETIC_CAB) $(HEXEN_CAB)
+
+clean:
+	rm -f $(DOOM_CAB) $(HERETIC_CAB) $(HEXEN_CAB)
+
+# Doom:
+
+DOOM_CAB=$(PROGRAM_PREFIX)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=$(PROGRAM_PREFIX)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=$(PROGRAM_PREFIX)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 $< $@
+
--- a/pkg/wince/GNUmakefile.am
+++ /dev/null
@@ -1,30 +1,0 @@
-
-# Doom:
-
-DOOM_CAB=chocolate-doom-@[email protected]
-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-@[email protected]
-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-@[email protected]
-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@-@[email protected]
+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
@@ -190,7 +190,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
@@ -330,44 +330,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;
@@ -528,7 +569,6 @@
 {
     char *buf;
     int i;
-    boolean exists;
     
     // Absolute path?
 
@@ -543,14 +583,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;
         }
--- a/src/doom/.gitignore
+++ b/src/doom/.gitignore
@@ -5,3 +5,5 @@
 chocolate-doom
 chocolate-server
 *.exe
+tags
+TAGS
--- 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/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