shithub: choc

Download patch

ref: 120d90c67b2a4aa0a8883c4897241dee2222acd2
parent: 79268587fc730e17cbd974a5583c7185604b59a3
parent: 22fc405736dc4796958de221c07d52432f1b271b
author: Simon Howard <[email protected]>
date: Thu Sep 9 19:13:06 EDT 2010

Merge from raven-branch.

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

--- a/.gitignore
+++ b/.gitignore
@@ -1,24 +1,21 @@
-CMDLINE
-INSTALL
-Makefile
 Makefile.in
-TAGS
-aclocal.m4
-autom4te.cache
+Makefile
+INSTALL
+CMDLINE
 autotools
-bin
-config.h
+aclocal.m4
+configure
 config.hin
 config.log
 config.status
-configure
-lib
-obj
+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/.lvimrc
+++ b/.lvimrc
@@ -16,8 +16,9 @@
 let tagfiles = findfile("tags", ".;", -1)
 
 " Add tag files for libraries:
-call add(tagfiles, topdir . "textscreen/tags")
+call add(tagfiles, topdir . "opl/tags")
 call add(tagfiles, topdir . "pcsound/tags")
+call add(tagfiles, topdir . "textscreen/tags")
 
 for tagfile in tagfiles
     " Don't go beyond the project top level when adding parent dirs:
--- a/BUGS
+++ b/BUGS
@@ -6,21 +6,6 @@
   effects are cut off at the wrong distance. This needs further 
   investigation.
 
-* Music plays back differently.
-
-  Vanilla Doom was typically played with a SoundBlaster (or compatible)
-  card.  It programmed the registers for the OPL music chip directly
-  in order to emulate the various General MIDI instruments.  However,
-  Chocolate Doom uses the OS's native MIDI playback interfaces to play
-  MIDI sound.  As the OPL is programmed differently, the music sounds
-  different to the original, even when using an original SoundBlaster
-  card.
-
-  This can be worked around in the future: OPL emulation code exists that
-  simulates an OPL chip in software.  Furthermore, depending on the OS,
-  it may be possible to program the OPL directly in order to get the 
-  same sound.
-
 * A small number of Doom bugs are almost impossible to emulate.
 
   An example of this can be seen in Ledmeister's "Blackbug" demo which 
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,890 @@
+2010-05-30 04:03:44 fraggle
+	
+	Add INSTALL to all distribution packages, add note in README.
+
+2010-05-30 03:56:58 fraggle
+	
+	Clarify/update install instructions.
+
+2010-05-14 19:42:32 fraggle
+	
+	Don't grab the mouse when the demo sequence advances.
+
+2010-05-03 17:47:25 fraggle
+	
+	Oops.
+
+2010-05-03 16:58:52 fraggle
+	
+	Update NEWS.
+
+2010-05-01 22:47:26 fraggle
+	
+	Further sanity checking on use of strcpy() with dehacked string
+	replacements.
+
+2010-05-01 22:20:30 fraggle
+	
+	Silence printf(DEH_String(...)) warnings, by providing a DEH_printf
+	function that checks the format string is a valid replacement.  Also
+	add DEH_fprintf and DEH_snprintf functions to use throughout the code
+	to do similar checking.
+
+2010-05-01 20:22:52 fraggle
+	
+	Fix compiler warnings with savegame and response file code.
+
+2010-04-30 20:58:30 fraggle
+	
+	Merge contents of OPL-TODO into TODO file.
+
+2010-04-30 20:38:24 fraggle
+	
+	Add textscreen Doxyfile to dist.  Add .desktop file to svn:ignore.  Add
+	opl ctags file to localvimrc.
+
+2010-04-25 00:53:03 fraggle
+	
+	Add -reject_pad_with_ff parameter to allow padding value to be
+	specified.
+
+2010-04-23 21:46:29 fraggle
+	
+	Add REJECT buffer overflow emulation, based on code from PrBoom+
+	(thanks entryway).  Fixes YDFEAR25.LMP.
+
+2010-04-22 22:38:51 fraggle
+	
+	Disable OPL debugging messages.
+
+2010-03-08 18:52:59 fraggle
+	
+	Add OPL-TODO to dist, set svn:ignore properties.
+
+2010-03-08 18:50:29 fraggle
+	
+	Use native MIDI music by default.
+
+2010-03-08 01:14:23 fraggle
+	
+	Merge opl-branch to trunk.
+	
+	OPL support still isn't perfect, and it certainly isn't complete.
+	However, for now, it's good enough.
+
+2010-02-10 20:21:21 fraggle
+	
+	Bump version number, update ChangeLog and NEWS.
+
+2010-01-31 18:21:50 fraggle
+	
+	Change Windows resource file to use PACKAGE_COPYRIGHT and
+	PACKAGE_LICENSE macros.
+
+2010-01-30 16:14:04 fraggle
+	
+	Change directory to home directory before launching the game, so that
+	recorded demos go somewhere sensible.
+
+2010-01-30 16:04:24 fraggle
+	
+	Set launch button as default button, so that it is possible to launch
+	the game by pressing return.
+
+2010-01-30 00:37:17 fraggle
+	
+	Rename mus2mid functions to be consistent with coding standard.
+
+2010-01-29 23:28:35 fraggle
+	
+	Remove unused PACKAGE_LONGDESC.
+
+2010-01-29 19:17:56 fraggle
+	
+	When doing a MUS to MID conversion, allocate MIDI channels so that the
+	lowest-numbered MIDI channels are used before higher-numbered ones.
+	Fixes ear-piercing whistle sound in the MAP05 music when playing with
+	timidity and EAWPATS (thanks entryway / HackNeyed).
+
+2010-01-29 03:55:20 fraggle
+	
+	Fix indentation/style etc.  in mus2mid.c.
+
+2010-01-27 19:16:26 fraggle
+	
+	Add tags files to svn:ignore properties.
+
+2010-01-26 19:21:18 fraggle
+	
+	Minor fix of British spelling -> American.
+
+2010-01-26 19:18:18 fraggle
+	
+	Fix glass hack windows where a linedef is flagged as two sided but has
+	only one side.  Fixes WADs such as OTTAWAU.WAD (thanks Never_Again).
+
+2010-01-23 23:06:45 fraggle
+	
+	Add menu item to launcher to open a terminal window that can be used
+	to start the game.  Add missing 'edit' menu.  Set svn:ignore property
+	for osx directory.
+
+2010-01-18 19:40:50 fraggle
+	
+	Fix package source URL.
+
+2010-01-18 19:29:48 fraggle
+	
+	Tweak package description slightly.
+
+2010-01-18 19:14:54 fraggle
+	
+	Define project short description, copyright, maintainer and URL in
+	configure.in.  Use these for the Info-gnustep.plist file.  Add
+	generated .spec file for building RPM packages.
+
+2010-01-17 16:58:37 fraggle
+	
+	Update NEWS.
+
+2010-01-17 16:31:03 fraggle
+	
+	Restore the original cursor when shutting down video code, this should
+	hopefully fix the problem with the mouse cursor disappearing when
+	exiting on Win9x (thanks Janizdreg).
+
+2010-01-16 19:20:11 fraggle
+	
+	Update TODO file.
+
+2010-01-15 19:29:28 fraggle
+	
+	Don't open the configuration window when the launcher is first run;
+	display an error message if the user tries to launch the game without
+	an IWAD selected.
+
+2010-01-15 19:14:02 fraggle
+	
+	Fix GNUstep info panel.
+
+2010-01-15 18:51:35 fraggle
+	
+	Center the launcher window and config window on startup.
+
+2010-01-15 18:40:37 fraggle
+	
+	Add wadfile.png for GNUstep build.
+
+2010-01-15 18:13:05 fraggle
+	
+	Extend osx makefile to allow building of a working GNUstep
+	application, for testing purposes.  Add GNUstep version of Info.plist,
+	remove app-skeleton directory and move contents up.
+
+2010-01-14 18:47:03 fraggle
+	
+	In Chex Quest, use the radiation suit colormap instead of the red
+	colormaps that are usually used when taking damage (or using the
+	berserk pack).  This matches the Vanilla chex.exe behavior (thanks
+	Fuzztooth).
+
+2010-01-12 20:16:25 fraggle
+	
+	Oops.
+
+2010-01-12 20:15:34 fraggle
+	
+	Strip executables when building Windows CE packages.
+
+2010-01-12 20:14:11 fraggle
+	
+	Rearrange order of Makefile generation to alphabetical order.
+
+2010-01-12 20:12:56 fraggle
+	
+	Move Makefile definitions for CC and STRIP into config.make, use
+	versions from autoconf.
+
+2010-01-12 20:09:54 fraggle
+	
+	Set main menu title based on package name, not fixed string.
+
+2010-01-12 20:09:01 fraggle
+	
+	Place commercial level name strings consecutively in the same array,
+	so that overflowing the end of one list accesses the start of the
+	next.  This trick is used by pl2.wad for its MAP33 secret level.
+
+2010-01-12 01:32:24 fraggle
+	
+	Add missing connection for plutonia.wad set button.
+
+2010-01-12 01:20:48 fraggle
+	
+	Add document icon file and use for file associations.
+
+2010-01-11 19:10:42 fraggle
+	
+	Insert new files into the command line at the correct location,
+	allowing multiple files to be opened at once.
+
+2010-01-11 01:35:04 fraggle
+	
+	When launching a file from the finder, add the new file into the
+	command line at the appropriate position in the command line string.
+
+2010-01-10 20:46:15 fraggle
+	
+	Change "@executable_path@" to "@executable_path"
+
+2010-01-10 18:48:21 fraggle
+	
+	Install docs with a single cp, rather than using a for loop.
+
+2010-01-10 18:42:35 fraggle
+	
+	Recursively copy library dependencies into destination package.
+	Identify libraries to be installed based on the path in which they are
+	located, rather than whether there is "libSDL" in the name.  Use
+	install_name_tool to change the search path so that the system looks
+	for libraries in @executable_path@ rather than their location on the
+	machine upon which the program was built.
+
+2010-01-09 21:06:31 fraggle
+	
+	Clear existing arguments when adding a file.
+
+2010-01-09 20:42:30 fraggle
+	
+	Add file to command line when opened; add link from AppController to
+	LauncherManager.
+
+2010-01-09 18:54:04 fraggle
+	
+	Initial code to identify file type by extension and add file to
+	command line.
+
+2010-01-09 18:38:48 fraggle
+	
+	Hook in AppController as delegate for application, add file
+	associations to property list file.
+
+2010-01-05 17:20:58 fraggle
+	
+	Add "clean" target to package makefiles.
+
+2010-01-05 15:52:12 fraggle
+	
+	Move config.make up to pkg/ directory.  Use static makefiles to
+	generate all packages, rather than dynamically generated makefiles.
+	Add pkg/osx to dist.  Make OS X staging directory depend on top level
+	documentation files.  Generate CMDLINE as part of standard build if it
+	is not already present.  Set svn:ignore properties.
+
+2010-01-04 22:53:44 fraggle
+	
+	Fix single space error when listing libraries.
+
+2010-01-04 22:45:45 fraggle
+	
+	Copy binaries into app dir along with libraries.
+
+2010-01-04 22:24:48 fraggle
+	
+	Include documentation files in package.
+
+2010-01-04 22:19:53 fraggle
+	
+	Fix GNUstep build.
+
+2010-01-04 22:11:11 fraggle
+	
+	Generate Info.plist and config.make in configure and remove temporary
+	versions.  Include config.h from top level.
+
+2010-01-04 22:01:32 fraggle
+	
+	Import OS X launcher code to trunk.
+
+2010-01-03 03:49:11 fraggle
+	
+	Add quotes around $@ in autogen script (thanks exp[x])
+
+2009-12-28 20:57:20 fraggle
+	
+	When recording low resolution (non-longtics) Vanilla demos, carry
+	forward the error from angleturn caused by the reduced resolution, so
+	that consecutive errors can accumulate, possibly making turning
+	slightly smoother.
+
+2009-12-27 01:42:13 fraggle
+	
+	Oops.
+
+2009-12-27 00:11:18 fraggle
+	
+	Allow DOOMWADDIR/DOOMWADPATH to contain the complete path to IWAD
+	files, as well as directories in which to search for IWAD files.
+
+2009-12-18 22:11:06 fraggle
+	
+	Fix poor quality application icons seen when the game is running.  Add
+	back 8-bit icon files alongside files including both 8-bit and high
+	quality 32-bit versions.  Use the high quality icon files for resource
+	files includes, and the low quality ones for in-game SDL.
+
+2009-12-18 21:11:32 fraggle
+	
+	Update generated source files containing icon data.
+
+2009-12-18 21:10:35 fraggle
+	
+	Make ExecuteCommand() under Unix return a failure when the executable
+	cannot be executed.
+
+2009-12-14 20:57:04 fraggle
+	
+	Use GetModuleFileNameW to get the (Unicode) path to the Doom
+	executable.  This hopefully fixes problems with Unicode directory
+	names.
+
+2009-12-14 18:54:25 fraggle
+	
+	Add Chocolate Doom/setup icons with scaled versions for various
+	different icon sizes (thanks MikeRS).
+
+2009-12-12 01:20:49 fraggle
+	
+	Fix textscreen black border bug.
+
+2009-12-09 02:40:39 fraggle
+	
+	Fix the setup tool on Windows Vista/7 to not prompt for elevated
+	permissions and to disable the "Program Compatibility Assistant"
+	(thanks hobbs and MikeRS).
+
+2009-11-29 22:50:17 fraggle
+	
+	Add other missing files to dist.
+
+2009-11-29 22:25:51 fraggle
+	
+	Include .lvimrc in dist.
+
+2009-11-21 03:56:59 fraggle
+	
+	Add Makefile to build Win32 packages.
+
+2009-11-21 02:05:56 fraggle
+	
+	Use execvp() rather than execv(), to look up Doom binary in the PATH
+	if necessary.
+
+2009-11-21 00:40:58 fraggle
+	
+	Apply configuration file invalid key setting fix to setup code.
+
+2009-11-21 00:38:16 fraggle
+	
+	Don't crash if key settings are set in a configuration file that are
+	out of range (thanks entryway).
+
+2009-11-21 00:24:59 fraggle
+	
+	Fix crash with chocolate-setup under Windows (thanks Janizdreg).
+
+2009-11-19 21:49:13 fraggle
+	
+	Rework the OS X MIDI disabling code, as SDL_mixer 1.2.11 fixes the
+	crash.  Check and disable MIDI by default if using an older version of
+	SDL on OS X.
+
+2009-11-19 21:07:31 fraggle
+	
+	Make chocolate-setup use its own location in the filesystem to find
+	the location of the chocolate-doom executable.  Remove INSTALL_DIR
+	define.
+
+2009-11-05 19:57:55 fraggle
+	
+	Perform bounds checking on values passed to TXT_UpdateScreenArea() to
+	avoid crashes.
+
+2009-10-26 19:28:12 fraggle
+	
+	Initial hacks for compiling under SDL 1.3.
+
+2009-10-17 21:13:54 fraggle
+	
+	Fix error in last change.
+
+2009-10-17 20:39:37 fraggle
+	
+	Use M_StrToInt() when processing values passed with -spechit, so that
+	hex values can be specified.
+
+2009-10-17 20:29:46 fraggle
+	
+	Import donut overrun emulation code from PrBoom+ (Thanks entryway).
+
+2009-10-16 19:10:30 fraggle
+	
+	Fix compilation under MSVC (thanks entryway).
+
+2009-10-10 23:58:25 fraggle
+	
+	Rename pkg/wince/Makefile to pkg/wince/GNUmakefile (it uses GNU
+	extensions).
+
+2009-10-10 22:46:14 fraggle
+	
+	Add pkg directory to make dist.
+
+2009-10-10 02:02:58 fraggle
+	
+	Don't crash when using the donut special type and the joining linedef
+	is one sided (thanks Alexander Waldmann).
+
+2009-10-05 21:25:53 fraggle
+	
+	Fix desync in ep1-0500.lmp on 64-bit (thanks exp(x)).
+
+2009-10-05 00:38:14 fraggle
+	
+	Provide pointer to STARTUPINFO structure when calling CreateProcessW,
+	to stop crash under normal Windows (not CE) when launching Doom from
+	the setup tools (thanks Janizdreg).
+
+2009-10-01 20:08:21 fraggle
+	
+	Oops.
+
+2009-10-01 02:04:00 fraggle
+	
+	Oops.
+
+2009-10-01 00:07:03 fraggle
+	
+	Change British English spellings to American English, for consistency.
+
+2009-09-20 16:27:40 fraggle
+	
+	Use "const char" in libtextscreen where appropriate (thanks entryway).
+
+2009-09-11 22:56:47 fraggle
+	
+	Add (lack of) copyright notice for SDL workaround.
+
+2009-09-07 20:43:04 fraggle
+	
+	Fix compilation under MacOS X.
+
+2009-09-06 19:15:52 fraggle
+	
+	Fixes for MSVC compile (thanks entryway).
+
+2009-08-28 00:27:47 fraggle
+	
+	Allow PGUP/PGDN to scroll up and down in scroll panes (thanks
+	LionsPhil).
+
+2009-07-20 23:27:59 fraggle
+	
+	Remove redundant variable assignment (thanks Quasar/Yagisan)
+
+2009-07-20 01:37:41 fraggle
+	
+	Save and display the loading disk icon as a fixed 16x16 square, from
+	an image drawn at the bottom right corner of the screen.  This seems
+	to be the same as how Vanilla behaves, and fixes chook3.wad, that uses
+	an STDISK replacement with an offset that pushes the image to the
+	left.
+
+2009-07-13 23:43:06 fraggle
+	
+	Add stdio.h include to fix MSVC build (thanks Kaiser)
+
+2009-07-12 17:47:12 fraggle
+	
+	Fix compile with libsamplerate.
+
+2009-07-12 15:00:50 fraggle
+	
+	On Windows CE, use the Windows API to find the amount of available
+	memory, so that at least two megabytes are always left available to
+	the OS.
+
+2009-07-11 12:15:32 fraggle
+	
+	Add missing item to NEWS.
+
+2009-07-07 20:46:55 fraggle
+	
+	Update NEWS.
+
+2009-07-07 20:38:00 fraggle
+	
+	Fix launching of the game from the setup tool in Windows CE.
+
+2009-06-21 20:33:35 fraggle
+	
+	Add Makefile for building CAB files, dependency calculation.
+
+2009-06-21 20:19:43 fraggle
+	
+	Use correct filename for SDL_net DLL.
+
+2009-06-21 20:03:38 fraggle
+	
+	Remove temporary files after generating CAB file.
+
+2009-06-20 23:13:44 fraggle
+	
+	Add script to generate Windows CE install package.
+
+2009-06-16 20:47:13 fraggle
+	
+	Automatically allocate a smaller zone size if it was not possible to
+	allocate the default 16 MiB.
+
+2009-06-13 18:10:18 fraggle
+	
+	Don't post zero key events.
+
+2009-06-12 20:07:55 fraggle
+	
+	On Windows CE systems without a keyboard, patch the default settings
+	to use hardware keys.
+
+2009-06-12 18:58:42 fraggle
+	
+	Remove debug messages.
+
+2009-06-12 18:35:39 fraggle
+	
+	Set the USER environment variable based on the owner information from
+	the registry.
+
+2009-06-12 18:34:27 fraggle
+	
+	Always grab input on Windows CE.
+
+2009-06-11 22:34:36 fraggle
+	
+	Include libc_wince.a in chocolate-server build.
+
+2009-06-11 20:41:20 fraggle
+	
+	Grab the input in setup when reading a new key binding, so that
+	Windows CE buttons are read properly.  Map buttons to PC function
+	keys.
+
+2009-06-11 19:19:05 fraggle
+	
+	Include libc_wince.h on Windows CE.
+
+2009-06-11 19:18:12 fraggle
+	
+	Declare getenv/putenv on Windows CE for recent SDL versions that do
+	not declare it.
+
+2009-06-10 20:03:08 fraggle
+	
+	Add key bindings for pause, message refresh.
+
+2009-06-08 20:26:29 fraggle
+	
+	Remove debugging code.
+
+2009-06-08 19:15:57 fraggle
+	
+	Use SDL's getenv/putenv implementation, and populate at startup.
+
+2009-06-08 00:41:10 fraggle
+	
+	Use CreateFileW instead of OpenFile (doesn't exist on Windows CE)
+
+2009-06-07 20:08:08 fraggle
+	
+	Fix header includes (thanks exp[x])
+
+2009-06-07 19:18:02 fraggle
+	
+	Don't add DirectX/Windib selector on Windows CE.
+
+2009-06-07 18:53:25 fraggle
+	
+	Use home dir to store configuration and savegames under Windows CE.
+
+2009-06-07 18:33:19 fraggle
+	
+	Fix setup tool display configuration dialog when fullscreen is not
+	supported.
+
+2009-06-07 18:10:05 fraggle
+	
+	Make auto-adjust code switch to windowed mode if no fullscreen modes
+	are available.
+
+2009-06-07 17:41:46 fraggle
+	
+	Catch errors when initialising SDL.  Use the small textscreen font by
+	default on Windows CE if no fullscreen modes are available.
+
+2009-06-07 17:39:08 fraggle
+	
+	Add missing SDL_thread include.
+
+2009-06-07 17:35:43 fraggle
+	
+	Don't try to use the SDL DirectX driver under Windows CE.
+
+2009-06-07 16:21:41 fraggle
+	
+	Fix setup tool compile on Windows CE.
+
+2009-06-07 16:15:40 fraggle
+	
+	Remove call to setbuf.
+
+2009-06-07 15:35:27 fraggle
+	
+	Add IWAD search dirs for Windows CE.
+
+2009-06-07 15:20:46 fraggle
+	
+	Exit with an error on failure to allocate zone memory.
+
+2009-06-07 03:10:21 fraggle
+	
+	Use MessageBoxW instead of MessageBox (doesn't exist on Windows CE)
+
+2009-06-07 02:59:49 fraggle
+	
+	Add README file for Windows CE library.
+
+2009-06-07 02:56:21 fraggle
+	
+	Detect Windows CE target and build/include libc_wince files as
+	necessary.
+
+2009-06-07 02:50:47 rtc_marine
+	
+	- Update textscreen codeblocks project to include txt_scrollpane.* and
+	txt_smallfont.h
+
+2009-06-07 02:33:58 fraggle
+	
+	Include libc_wince.h when on Windows CE.
+
+2009-06-07 02:32:15 fraggle
+	
+	Add CPU affinity function for Windows CE.
+
+2009-06-07 02:27:58 fraggle
+	
+	Add libc_wince.h header, and EISDIR error value.
+
+2009-06-07 02:27:30 fraggle
+	
+	Use GetUserNameExW, not GetUserName (doesn't exist on WinCE)
+
+2009-06-07 02:26:45 fraggle
+	
+	Fix compile with FEATURE_MULTIPLAYER disabled.
+
+2009-06-07 02:24:40 fraggle
+	
+	Fix compile with FEATURE_SOUND disabled.
+
+2009-06-07 01:56:23 fraggle
+	
+	Add Windows CE implementations of some ANSI C functions that are
+	missing.
+
+2009-06-06 22:13:44 fraggle
+	
+	Don't check for Steam/CD installer versions on Windows CE.
+
+2009-06-05 17:58:48 fraggle
+	
+	Add key binding variables for automap and weapon keys.
+
+2009-06-04 00:37:02 fraggle
+	
+	Increase height of menu bindings dialog.
+
+2009-06-04 00:35:05 fraggle
+	
+	Use newer keyboard bindings dialog layout from raven-branch.
+
+2009-06-04 00:20:37 fraggle
+	
+	Add unique key groups for menu navigation and shortcuts.
+
+2009-06-04 00:20:06 fraggle
+	
+	Use key for confirming menu messages, not typed char.
+
+2009-06-03 21:45:54 fraggle
+	
+	Add dialog to setup tool for editing menu shortcuts.
+
+2009-06-03 21:18:04 fraggle
+	
+	Add config file variables to increase/decrease screen size.
+
+2009-06-03 20:59:26 fraggle
+	
+	Fix shortcut keys for menu items.
+
+2009-06-03 20:55:50 fraggle
+	
+	Add configuration file entries for menu key bindings.
+
+2009-06-03 20:37:19 fraggle
+	
+	Add key_ variables for the keys used to control the menu.
+
+2009-05-26 23:14:24 fraggle
+	
+	Fix tags for functions using TXT_UNCAST_ARG.
+
+2009-05-26 22:13:18 fraggle
+	
+	Set appropriate vim 'tags' variable for ctags files.
+
+2009-05-21 20:18:38 fraggle
+	
+	Set display settings window position based on screen dimensions,
+	rather than hard coding position.
+
+2009-05-19 18:07:49 fraggle
+	
+	Fix manpage documentation for DOOMWADPATH (thanks MikeRS)
+
+2009-05-18 19:30:49 fraggle
+	
+	Fix A_BossDeath behavior in v1.9 emulation mode (thanks entryway)
+
+2009-05-17 14:54:19 fraggle
+	
+	Always use an SDL buffer size that is a power of two.  Reduce buffer
+	size to 70ms.
+
+2009-05-12 19:03:20 fraggle
+	
+	Add option to "join game" dialog in setup tool to autojoin a LAN game.
+
+2009-05-12 19:01:27 fraggle
+	
+	Make txt_inputboxes emit a "changed" signal when their value is
+	changed.
+
+2009-05-07 22:59:38 fraggle
+	
+	Calculate SDL buffer size automatically based on sample rate.
+
+2009-05-05 01:00:53 fraggle
+	
+	Better ASCII chart.
+
+2009-05-05 00:46:27 fraggle
+	
+	Minor smallfont fixups.
+
+2009-05-01 22:05:57 fraggle
+	
+	Add copyright headers to textscreen examples.
+
+2009-04-26 17:59:08 fraggle
+	
+	More smallfont fixups.
+
+2009-04-23 20:58:11 fraggle
+	
+	Fix up some extended ASCII characters.
+
+2009-04-23 19:19:52 fraggle
+	
+	Oops.
+
+2009-04-23 19:18:43 fraggle
+	
+	Add small textscreen font for low resolution displays, based on the
+	Atari-Small font by Tom Fine.
+
+2009-03-15 14:44:23 fraggle
+	
+	Fix clipped sounds when using libsamplerate (thanks David Flater)
+
+2009-03-14 15:28:41 fraggle
+	
+	Add check to allow sched_setaffinity code to work on older versions of
+	libc.
+
+2009-03-12 18:55:27 fraggle
+	
+	Define INVALID_SET_FILE_POINTER if it is not defined, to fix
+	compilation under MSVC6 (thanks Quasar)
+
+2009-03-08 22:51:25 fraggle
+	
+	Add "make doc" target to run Doxygen, and add a Doxyfile.  Add @file
+	tags to start of header files so that Doxygen will process them.
+
+2009-03-07 00:35:08 fraggle
+	
+	Add documentation for high-level txt_desktop.h functions.
+
+2009-03-07 00:24:45 fraggle
+	
+	Add documentation for high-level textscreen functions.
+
+2009-03-06 20:01:32 fraggle
+	
+	Fix signed/unsigned conversion warning.
+
+2009-03-03 19:26:20 fraggle
+	
+	Look up SetProcessAffinityMask function at runtime, so that the
+	program should work under Win9x again.
+
+2009-01-30 23:53:47 fraggle
+	
+	Fix layout of widgets within scroll panes.  Scroll scroll panes in
+	response to keyboard events.
+
+2009-01-29 23:26:03 fraggle
+	
+	Shrink text box slightly.
+
+2009-01-29 23:00:14 fraggle
+	
+	Allow clicking within scroll bars to set position.
+
+2009-01-29 22:54:13 fraggle
+	
+	Add scrollable pane widget to textscreen library.
+
+2009-01-17 14:05:31 fraggle
+	
+	Fix '-mmap' command line parameter.
+
+2009-01-07 22:05:13 fraggle
+	
+	Create the ~/.chocolate-doom/savegames directory on startup if it does
+	not exist.
+
+2009-01-07 21:51:37 fraggle
+	
+	Replace -nommap with -mmap; do not use mmap()ed file access by
+	default.  Fixes Plutonia 2, and several other minor things.
+
+2008-12-10 20:25:05 fraggle
+	
+	Bump version to 1.2.1, update NEWS and ChangeLog.
+
 2008-12-10 20:20:10 fraggle
 	
 	Fix crash when playing Doom 1 levels.
--- a/INSTALL
+++ b/INSTALL
@@ -2,14 +2,19 @@
 Chocolate Doom installation
 ===========================
 
-These are instructions for how to install Chocolate Doom on Unix-like
-Operating Systems.
+These are instructions for how to install and set up Chocolate Doom
+for play.
 
-Dependencies
-------------
+Building Chocolate Doom
+-----------------------
 
-Chocolate Doom requires the following to be installed:
+Before you can play Chocolate Doom, you need to compile a binary that
+you can run.  If you are using Windows or Mac OS X, precompiled
+binaries are available on the website for download, and you can skip
+this section.
 
+For compilation, Chocolate Doom requires the following to be installed:
+
  * A C compiler (gcc is recommended)
  * make (GNU make is recommended)
  * LibSDL (see http://www.libsdl.org/)
@@ -17,25 +22,24 @@
  * SDL_net (see http://www.libsdl.org/projects/SDL_net/)
  * Python (optional)
 
-Building Chocolate Doom
------------------------
+Follow the standard instructions for installing an autotools-based
+package:
 
-On a Unix system, follow the standard instructions for installing an 
-autotools-based package:
-
  1. Run './configure' to initialize the package.
  2. Run 'make' to compile the package.
  3. Run 'make install' to install the package.
 
-Advanced topics such as cross-compilation are beyond the scope of this 
+Advanced topics such as cross-compilation are beyond the scope of this
 document.  Please see the GNU autoconf / automake documentation for more
 information.
 
-Installing an IWAD file
------------------------
+Obtaining an IWAD file
+----------------------
 
-To play Doom, an IWAD file is needed. This contains the Doom game data.  The
-file usually has one of the following filenames:
+To play Doom, you need an IWAD file.  This file contains the game data
+that is used in gameplay (graphics, sounds, etc).  The full versions of
+the Doom games are proprietary and need to be bought.  The IWAD file
+has one of the following names:
 
    doom1.wad                   (Shareware Doom)
    doom.wad                    (Registered / Ultimate Doom)
@@ -42,59 +46,84 @@
    doom2.wad                   (Doom 2)
    tnt.wad                     (Final Doom: TNT: Evilution)
    plutonia.wad                (Final Doom: Plutonia Experiment)
+   chex.wad                    (Chex Quest)
 
-When you have this file (see the next section, "Obtaining an IWAD file", for 
-how to get this file), install it through one of the following methods:
+If you don't have a copy of the commercial version, you can download
+the shareware version (extract the file named doom1.wad):
 
- * Put the file into the /usr/share/games/doom or 
-   /usr/local/share/games/doom directories.
- * Install it into a directory and set the environment variable DOOMWADDIR to
-   be the path to that directory.
- * Install multiple IWADs into separate directories and set the environment
-   variable DOOMWADPATH to be a colon-separated list of directories to search
-   (similar to the Unix PATH environment variable).
- * Run Chocolate Doom with the '-iwad' command line parameter to specify the
-   IWAD file to use, eg.
+ * http://www.doomworld.com/idgames/index.php?id=7053
+   (idstuff/doom/win95/doom95.zip in your nearest /idgames mirror)
 
-       chocolate-doom -iwad /root/doom2.wad
+If you have a commercial version, obtaining the IWAD file may slightly
+complicated.  The method depends on how you obtained your copy of the
+game:
 
-Obtaining an IWAD file
-----------------------
+ * There have been several CD-based versions of Doom.  Generally, the
+   IWAD files can be found on the CD and copied off directly.
 
-Obtaining the IWAD file may be a complicated process under Unix.  The method
-depends on how you obtained your copy of the game:
+ * The IWAD files might not be directly available on the CD.  Look for
+   a program named "deice.exe".  In the same directory, there should
+   be a single large file with a numbered extension (eg.
+   "resource.1"); to extract this, follow the same instructions as for
+   the floppy disk version (see below).
 
- * There have been several CD-based versions of Doom.  Generally, the IWAD
-   files can be found on the CD and copied off directly.
+ * If you have the floppy disk version of Doom, first copy the
+   contents of all the floppy disks into a directory together.  You
+   will have several large files with numbered extensions.
+   Concatenate these into a single file, eg.
 
- * The IWAD files may not be directly available on the CD.  Look for a program
-   named "deice.exe".  In the same directory, there should be a single large
-   file with a numbered extension (eg. "resource.1"); to extract this, follow
-   the same instructions as for the floppy disk version (see below).
+     (Unix instructions)
+     cat doom_se.1 doom_se.2 doom_se.3 doom_se.4 doom_se.5 > doom_se.exe
 
- * If you have the floppy disk version of Doom, first copy the contents of all
-   the floppy disks into a directory together.  You will have several large
-   files with numbered extensions.  Concatenate these into a single file, eg.
+     (Windows/DOS instructions)
+     copy doom_se.1+doom_se.2+doom_se.3+doom_se.4+doom_se+5 doom_se.exe
 
-       cat doom_se.1 doom_se.2 doom_se.3 doom_se.4 doom_se.5 > doom_se.exe
+   The resulting file is self-extracting LHA file.  If you have a DOS
+   emulator (such as DOSbox), you can run it to extract the files;
+   alternatively, you can use the Unix LHA tool to extract the
+   archive.
 
-   The resulting file is self-extracting LHA file.  If you have a DOS emulator
-   (such as DOSbox), you can run it to extract the files; alternatively, you
-   can use the Unix LHA tool to extract the archive.
-
  * The Doom games are also available for download on Steam
-   (http://www.steampowered.com/).  To find the IWAD files, look in your Steam
-   directory, under the "steamapps/common" path.  
+   (http://www.steampowered.com/).  To find the IWAD files, look in
+   your Steam directory, under the "steamapps/common" path.
 
+Running the game
+----------------
+
+When you have an IWAD file, install it through one of the following
+methods:
+
+ * Under Mac OS X, you can specify the locations of the IWAD files
+   through the graphical launcher program.  Click the "Configure..."
+   button, and then click "Set..." for each IWAD location to choose
+   its location.
+
+ * Under Unix, put the file into the /usr/share/games/doom or
+   /usr/local/share/games/doom directories.
+
+ * Place it in a directory and set the environment variable DOOMWADDIR
+   to be the path to that directory.
+
+ * Install multiple IWADs into separate directories and set the
+   environment variable DOOMWADPATH to be a colon-separated list of
+   directories to search (similar to the Unix PATH environment
+   variable).
+
+ * Run Chocolate Doom with the '-iwad' command line parameter to
+   specify the IWAD file to use, eg.
+
+       chocolate-doom -iwad /root/doom2.wad
+
 Playing with Chex Quest
 -----------------------
 
-Chex Quest is a game based on Doom with some minor modifications that was 
-distributed with boxes of Chex cereal in 1997.  It is possible to play 
-Chex Quest using Chocolate Doom.  To do this, the following files are
-needed:
+Chex Quest is a game based on Doom with some minor modifications that
+was distributed with boxes of Chex cereal in 1997.  It is possible to
+play Chex Quest using Chocolate Doom.  To do this, the following files
+are needed:
 
  * The IWAD file 'chex.wad', from the Chex Quest CD.
+
  * The dehacked patch 'chex.deh', which can be found in the /idgames
    repository in utils/exe_edit/patches/chexdeh.zip.
 
@@ -106,30 +135,30 @@
 Installing upgrades
 -------------------
 
-Chocolate Doom requires a Doom 1.9 IWAD file.  Generally, if you install a
-recent version of Doom you should automatically have a 1.9 IWAD.  However, if
-you are installing from a very old CD version or from floppy disks, you might
-find you have an older version.  
+Chocolate Doom requires a Doom 1.9 IWAD file.  Generally, if you
+install a recent version of Doom you should automatically have a 1.9
+IWAD.  However, if you are installing from a very old CD version or
+from floppy disks, you might find you have an older version.
 
-The most obvious symptom of an out of date IWAD file is that the game will
-exit at the title screen before the demo starts, with the message "Demo is
-from a different game version!".  If this happens, your IWAD file is out of
-date and you need to upgrade.
+The most obvious symptom of an out of date IWAD file is that the game
+will exit at the title screen before the demo starts, with the message
+"Demo is from a different game version!".  If this happens, your IWAD
+file is out of date and you need to upgrade.
 
-Id Software released upgrade patches that will update your game to 1.9.  The 
-following sites have the patches:
+Id Software released upgrade patches that will update your game to
+1.9.  The following sites have the patches:
 
-  http://www.doomworld.com/files/patches.shtml 
+  http://www.doomworld.com/files/patches.shtml
   http://www.doom2.net/doom2/utils.html
   ftp://ftp.idsoftware.com/idstuff/doom2
 
-As the patches are binary patches that run as DOS executables, you will
-need a DOS emulator (such as DOSBox) to install them.  
+As the patches are binary patches that run as DOS executables, you
+will need a DOS emulator (such as DOSBox) to install them.
 
 Music support
 -------------
 
-Support for Doom's MIDI music is available through timidity:
+Support for Doom's MIDI music is available through Timidity:
 
   http://timidity.sourceforge.net/
 
--- a/Makefile.am
+++ b/Makefile.am
@@ -50,6 +50,7 @@
         config.h                        \
         CMDLINE                         \
         HACKING                         \
+        README.OPL                      \
         TODO                            \
         BUGS                            \
         rpm.spec
@@ -57,7 +58,9 @@
 MAINTAINERCLEANFILES =  $(AUX_DIST_GEN)
 
 docdir=$(prefix)/share/doc/@PACKAGE@
-SUBDIRS=wince textscreen pcsound src man 
+
+SUBDIRS=wince textscreen opl pcsound src man
+
 DIST_SUBDIRS=pkg $(SUBDIRS)
 
 if HAVE_PYTHON
--- a/NEWS
+++ b/NEWS
@@ -1,5 +1,67 @@
-...
+1.5.0 (2010-??-??):
 
+    Big changes in this version:
+     * The DOSbox OPL emulator (DBOPL) has been imported to replace
+       the older FMOPL code.  The quality of OPL emulation is now
+       therefore much better.
+     * When running in windowed mode, it is now possible to
+       dynamically resize the window by dragging the window borders.
+     * There are now keyboard, mouse and joystick bindings to cycle
+       through available weapons, making play with joypads or mobile
+       devices (ie. without a proper keyboard) much more practical.
+     * There is now a key binding to change the multiplayer spy key
+       (usually F12).
+     * There is now a configuration file parameter to set the OPL I/O
+       port, for cards that don't use port 0x388.
+     * Up to 8 mouse buttons are now supported (including the
+       mousewheel).
+
+    Bugs fixed:
+     * It is now possible to use OPL emulation at 11025Hz sound
+       sampling rate (thanks to the new OPL emulator).
+     * The span renderer function (used for drawing floors and
+       ceilings) now behaves the same as Vanilla Doom, so screenshots
+       are pixel-perfect identical to Vanilla Doom (thanks Porsche
+       Monty).
+     * The zone memory system now aligns allocated memory to 8-byte
+       boundaries on 64-bit systems, which may fix crashes on systems
+       such as sparc64.
+     * The configure script now checks for libm, fixing compile
+       problems on Fedora Linux.
+
+1.4.0 (2010-07-10):
+
+     The biggest change in this version is the addition of OPL
+     emulation.  This emulates Vanilla Doom's MIDI playback when
+     using a Yamaha OPL synthesizer chip, as was found on
+     SoundBlaster compatible cards.
+
+     A software OPL emulator is included as most modern computers do
+     not have a hardware OPL chip any more.  If you do have one, you
+     can configure Chocolate Doom to use it; see README.OPL.
+
+     The OPL playback feature is not yet perfect or 100% complete,
+     but is judged to be good enough for general use.  If you find
+     music that does not play back properly, please report it as a
+     bug.
+
+     Other changes:
+     * The REJECT overflow emulation code from PrBoom+ has been
+       imported.  This fixes demo desync on some demos, although
+       others will still desync.
+     * Warnings are now generated for invalid dehacked replacements of
+       printf format strings.  Some potential buffer overflows are
+       also checked.
+     * The installation instructions (INSTALL file) have been
+       clarified and made more platform-agnostic.
+     * The mouse is no longer warped to the center of the screen when
+       the demo sequence advances.
+     * Key bindings can now be changed for the demo recording quit key
+       (normally 'q') and the multiplayer messaging keys (normally
+       't', 'g', 'i', 'b' and 'r').
+
+1.3.0 (2010-02-10):
+
      * 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
@@ -35,6 +97,8 @@
      * When recording shorttics demos, errors caused by the reduced
        turning resolution are carried forward, possibly making turning
        smoother.
+     * The source tarball can now be used to build an RPM package:
+         rpmbuild -tb chocolate-doom-VER.tar.gz
 
     Compatibility:
      * The A_BossDeath behavior in v1.9 emulation mode was fixed
--- a/README
+++ b/README
@@ -13,33 +13,37 @@
  * As far as possible, provide all the same features that are available
    using the DOS version.
 
+== Setting up gameplay ==
+
+For instructions on how to set up Chocolate Doom for play, see the
+INSTALL file.
+
 == Configuration File ==
 
 Chocolate Doom is compatible with the DOS Doom configuration file
-(normally named 'default.cfg').  Existing configuration files for
-DOS Doom should therefore simply work out of the box.  However,
-Chocolate Doom also provides some extra settings.  These are stored
-in a separate file named 'chocolate-doom.cfg'.
+(normally named 'default.cfg').  Existing configuration files for DOS
+Doom should therefore simply work out of the box.  However, Chocolate
+Doom also provides some extra settings.  These are stored in a
+separate file named 'chocolate-doom.cfg'.
 
 The configuration can be edited using the chocolate-setup tool.
 
 == Command-line options ==
 
-For a complete list of command-line options, see the CMDLINE 
-file.
+For a complete list of command-line options, see the CMDLINE file.
 
 == Playing TCs ==
 
-With Vanilla Doom there is no way to include sprites in PWAD files.  
-Chocolate Doom's '-file' command line option behaves exactly the
-same as Vanilla Doom, and trying to play TCs by adding the WAD files
-using '-file' will not work.
+With Vanilla Doom there is no way to include sprites in PWAD files.
+Chocolate Doom's '-file' command line option behaves exactly the same
+as Vanilla Doom, and trying to play TCs by adding the WAD files using
+'-file' will not work.
 
 Many Total Conversions (TCs) are distributed as a PWAD file which must
 be merged into the main IWAD.  Typically a copy of DEUSF.EXE is
 included which performs this merge.  Chocolate Doom includes a new
-option, '-merge', which will simulate this merge.  Essentially, the 
-WAD directory is merged in memory, removing the need to modify the 
+option, '-merge', which will simulate this merge.  Essentially, the
+WAD directory is merged in memory, removing the need to modify the
 IWAD on disk.
 
 To play TCs using Chocolate Doom, run like this:
@@ -53,26 +57,27 @@
 
 == Other information ==
 
- * More information, including information about how to play various classic
-   TCs, is available on the Chocolate Doom website: 
+ * More information, including information about how to play various
+   classic TCs, is available on the Chocolate Doom website:
 
      http://www.chocolate-doom.org/
 
-   You are encouraged to sign up and contribute any useful information you may
-   have regarding the port!
+   You are encouraged to sign up and contribute any useful information
+   you may have regarding the port!
 
- * Chocolate Doom is not perfect.  See the BUGS file for a list of known
-   issues. New bug reports can be submitted to the Chocolate Doom bug
-   tracker on Sourceforge.  See:
+ * Chocolate Doom is not perfect.  See the BUGS file for a list of
+   known issues. New bug reports can be submitted to the Chocolate
+   Doom bug tracker on Sourceforge.  See:
 
      http://sourceforge.net/projects/chocolate-doom
 
- * Source code patches are welcome, but please follow the style guidelines -
-   see the file named HACKING included with the source distribution.
+ * Source code patches are welcome, but please follow the style
+   guidelines - see the file named HACKING included with the source
+   distribution.
 
- * Chocolate Doom is distributed under the GNU GPL.  See the COPYING file 
-   for more information.
+ * Chocolate Doom is distributed under the GNU GPL.  See the COPYING
+   file for more information.
 
- * Please send any feedback, questions or suggestions to [email protected].  
-   Thanks!
+ * Please send any feedback, questions or suggestions to
+   [email protected].  Thanks!
 
--- /dev/null
+++ b/README.OPL
@@ -1,0 +1,107 @@
+== Chocolate Doom hardware OPL support notes ==
+
+Chocolate Doom is able to play MIDI music as it sounds in Vanilla Doom
+with an OPL chip (as found in the Yamaha Adlib card, the Sound Blaster
+and its clones).  Most modern computers do not include an OPL chip any
+more, as CPUs are fast enough to do decent software MIDI synthesis.
+For this reason, a software OPL emulator is included as a substitute.
+
+However, no software emulator sounds exactly like a real (hardware)
+OPL chip, so if you do have a sound card with hardware OPL, here's how
+to configure Chocolate Doom to use it.
+
+=== Sound cards with OPL chips ===
+
+If you have an ISA sound card, it almost certainly includes an OPL
+chip.  Modern computers don't have slots for ISA cards though, so you
+must be running a pretty old machine.
+
+If you have a PCI sound card, you probably don't have an OPL chip.
+However, there are some exceptions to this. The following cards are
+known to include "legacy" OPL support:
+
+    * C-Media CMI8738 (*)
+    * Forte Media FM801
+    * Cards based on the Yamaha YMF724 (*)
+
+Other cards that apparently have OPL support but have not been tested:
+
+    * S3 SonicVibes
+    * AZTech PCI 168 (AZT 3328 chipset)
+    * ESS Solo-1 sound cards (ES1938, ES1946, ES1969 chipset)
+    * Conexant Riptide Audio/Modem combo cards
+    * Cards based on the Crystal Semiconductors CS4281
+    * Cards based on the Avance Logic ALS300
+    * Cards based on the Avance Logic ALS4000
+
+If you desperately want hardware OPL music, you may be able to find
+one of these cards for sale cheap on eBay.
+
+For the cards listed above with (*) next to them, OPL support is
+disabled by default and must be explictly enabled in software.
+
+If your machine is not a PC, you don't have an OPL chip, and you will
+have to use the software OPL.
+
+=== Operating System support ===
+
+If you're certain that you have a sound card with hardware OPL, you
+may need to take extra steps to configure your operating system to
+allow access to it.  To do hardware OPL, Chocolate Doom must access
+the chip directly, which is usually not possible in modern operating
+systems unless you are running as the superuser (root/Administrator).
+
+=== Windows 9x ===
+
+If you're running Windows 95, 98 or Me, there is no need to configure
+anything.  Windows allows direct access to the OPL chip.  You can
+confirm that hardware OPL is working by checking for this message in
+stdout.txt:
+
+    OPL_Init: Using driver 'Win32'.
+
+=== Windows NT (including 2000, XP and later) ===
+
+If you're running an NT-based system, it is not possible to directly
+access the OPL chip, even when running as Administrator.  Fortunately,
+it is possible to use the "ioperm.sys" driver developed for Cygwin:
+
+    http://openwince.sourceforge.net/ioperm/
+
+It is not necessary to have Cygwin installed to use this.  Copy the
+ioperm.sys file into the same directory as the Chocolate Doom
+executable and it should be automatically loaded.
+
+You can confirm that hardware OPL is working by checking for this
+message in stdout.txt:
+
+    OPL_Init: Using driver 'Win32'.
+
+=== Linux ===
+
+If you are using a system based on the Linux kernel, you can access
+the OPL chip directly, but you must be running as root.  You can
+confirm that hardware OPL is working, by checking for this message on
+startup:
+
+    OPL_Init: Using driver 'Linux'.
+
+If you are using one of the PCI cards in the list above with a (*)
+next to it, you may need to manually enable FM legacy support.  Add
+the following to your /etc/modprobe.conf file to do this:
+
+    options snd-ymfpci fm_port=0x388
+    options snd-cmipci fm_port=0x388
+
+=== OpenBSD/NetBSD ===
+
+You must be running as root to access the hardware OPL directly. You
+can confirm that hadware OPL is working by checking for this message
+on startup:
+
+    OPL_Init: Using driver 'OpenBSD'.
+
+=== FreeBSD ===
+
+There is no native OPL backend for FreeBSD yet.  Sorry!
+
--- a/TODO
+++ b/TODO
@@ -1,6 +1,5 @@
 Currently in progress:
 
-* OPL MIDI playback (see: opl-branch)
 * Heretic/Hexen support (see: raven-branch)
 * Strife support (see: strife-branch)
 
@@ -35,4 +34,23 @@
   automatically download and play speedruns.
 * DWANGO-like interface for finding players and setting up games.
 * Video capture mode?
+
+== OPL TODO list ==
+
+Needs research:
+
+ * Strategy when no more voices are available is still wrong
+ * Scale levels don't exactly match Vanilla (off-by-one?)
+
+Bad MIDIs:
+
+ * doom2.wad MAP01
+ * gothicdm MAP05
+ * tnt.wad MAP30
+ * Alien Vendetta (title screen, MAP01, etc)
+
+Other tasks:
+
+ * Get a better software OPL emulator
+ * DMXOPTIONS opl3/phase option support.
 
--- a/codeblocks/config.h
+++ b/codeblocks/config.h
@@ -9,19 +9,19 @@
 #define PACKAGE_NAME "Chocolate Doom"
 
 /* Define to the full name and version of this package. */
-#define PACKAGE_STRING "Chocolate Doom 1.2.1"
+#define PACKAGE_STRING "Chocolate Doom 1.4.0"
 
 /* Define to the one symbol short name of this package. */
 #define PACKAGE_TARNAME "chocolate-doom"
 
 /* Define to the version of this package. */
-#define PACKAGE_VERSION "1.2.1"
+#define PACKAGE_VERSION "1.4.0"
 
 /* Define to 1 if you have the ANSI C header files. */
 #define STDC_HEADERS 1
 
 /* Version number of package */
-#define VERSION "1.2.1"
+#define VERSION "1.4.0"
 
 /* Define to 1 if your processor stores words with the most significant byte
    first (like Motorola and SPARC, unlike Intel and VAX). */
--- a/codeblocks/game-res.rc
+++ b/codeblocks/game-res.rc
@@ -1,8 +1,8 @@
 1 ICON "../data/doom.ico"
 
 1 VERSIONINFO
-PRODUCTVERSION 1,2,1,0
-FILEVERSION 1,2,1,0
+PRODUCTVERSION 1,4,0,0
+FILEVERSION 1,4,0,0
 FILETYPE 1
 {
  BLOCK "StringFileInfo"
@@ -9,13 +9,13 @@
  {
   BLOCK "040904E4"
   {
-   VALUE "FileVersion", "1.2.1"
-   VALUE "FileDescription", "1.2.1"
+   VALUE "FileVersion", "1.4.0"
+   VALUE "FileDescription", "1.4.0"
    VALUE "InternalName", "Chocolate-Doom"
    VALUE "CompanyName", "Chocolate-Doom"
    VALUE "LegalCopyright", "GNU General Public License"
    VALUE "ProductName", "Chocolate-Doom"
-   VALUE "ProductVersion", "1.2.1"
+   VALUE "ProductVersion", "1.4.0"
   }
  }
 }
--- a/codeblocks/setup-res.rc
+++ b/codeblocks/setup-res.rc
@@ -3,8 +3,8 @@
 CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST "setup-manifest.xml"
 
 1 VERSIONINFO
-PRODUCTVERSION 1,2,1,0
-FILEVERSION 1,2,1,0
+PRODUCTVERSION 1,4,0,0
+FILEVERSION 1,4,0,0
 FILETYPE 1
 {
  BLOCK "StringFileInfo"
@@ -11,13 +11,13 @@
  {
   BLOCK "040904E4"
   {
-   VALUE "FileVersion", "1.2.1"
+   VALUE "FileVersion", "1.4.0"
    VALUE "FileDescription", "Chocolate-Doom Setup"
    VALUE "InternalName", "chocolate-setup"
    VALUE "CompanyName", "[email protected]"
    VALUE "LegalCopyright", "GNU General Public License"
    VALUE "ProductName", "Chocolate-Doom Setup"
-   VALUE "ProductVersion", "1.2.1"
+   VALUE "ProductVersion", "1.4.0"
   }
  }
 }
--- a/configure.in
+++ b/configure.in
@@ -1,4 +1,4 @@
-AC_INIT(Chocolate Doom, 1.2.1, [email protected], chocolate-doom)
+AC_INIT(Chocolate Doom, 1.4.0, [email protected], chocolate-doom)
 
 PACKAGE_SHORTDESC="Conservative Doom source port"
 PACKAGE_COPYRIGHT="Copyright (C) 1993-2010"
@@ -74,9 +74,16 @@
     # Check for libsamplerate.
 
     AC_CHECK_LIB(samplerate, src_new)
+    AC_CHECK_LIB(m, log)
 
     AC_CHECK_HEADERS([linux/kd.h dev/isa/spkrio.h dev/speaker/speaker.h])
-    AC_CHECK_FUNCS(mmap sched_setaffinity)
+    AC_CHECK_FUNCS(mmap sched_setaffinity ioperm)
+
+    # OpenBSD I/O i386 library for I/O port access.
+    # (64 bit has the same thing with a different name!)
+
+    AC_CHECK_LIB(i386, i386_iopl)
+    AC_CHECK_LIB(amd64, amd64_iopl)
 ])
 
 AC_CHECK_TOOL(WINDRES, windres, )
@@ -138,6 +145,8 @@
 AC_OUTPUT([
 Makefile
 man/Makefile
+opl/Makefile
+opl/examples/Makefile
 pcsound/Makefile
 pkg/Makefile
 pkg/config.make
--- a/man/manpage.template
+++ b/man/manpage.template
@@ -28,6 +28,17 @@
 options are "Linux" for the Linux console mode driver, "BSD" for the
 NetBSD/OpenBSD PC speaker driver, and "SDL" for SDL-based emulated PC speaker 
 playback (using the digital output).
+.TP
+\fBOPL_DRIVER\fR
+When using OPL MIDI playback, this environment variable specifies an
+OPL backend driver to use.  Valid options are "SDL" for an SDL-based
+software emulated OPL chip, "Linux" for the Linux hardware OPL driver,
+and "OpenBSD" for the OpenBSD/NetBSD hardware OPL driver.
+
+Generally speaking, a real hardware OPL chip sounds better than software
+emulation; however, modern machines do not often include one.  If
+present, it may still require extra work to set up and elevated
+security privileges to access.
 .SH FILES
 .TP
 \fB$HOME/.chocolate-doom/default.cfg\fR
--- a/msvc/config.h
+++ b/msvc/config.h
@@ -11,16 +11,16 @@
 #define PACKAGE_NAME "Chocolate Doom"
 
 /* Define to the full name and version of this package. */
-#define PACKAGE_STRING "Chocolate Doom 1.2.1"
+#define PACKAGE_STRING "Chocolate Doom 1.4.0"
 
 /* Define to the one symbol short name of this package. */
 #define PACKAGE_TARNAME "chocolate-doom"
 
 /* Define to the version of this package. */
-#define PACKAGE_VERSION "1.2.1"
+#define PACKAGE_VERSION "1.4.0"
 
 /* Version number of package */
-#define VERSION "1.2.1"
+#define VERSION "1.4.0"
 
 /* Define to 1 if your processor stores words with the most significant byte
    first (like Motorola and SPARC, unlike Intel and VAX). */
--- a/msvc/win32.rc
+++ b/msvc/win32.rc
@@ -32,8 +32,8 @@
 #endif
 
 1 VERSIONINFO
-PRODUCTVERSION 1,2,1,0
-FILEVERSION 1,2,1,0
+PRODUCTVERSION 1,4,0,0
+FILEVERSION 1,4,0,0
 FILETYPE 1
 BEGIN
 	BLOCK "StringFileInfo"
@@ -40,13 +40,13 @@
 	BEGIN
 		BLOCK "040904E4"
 		BEGIN
-			VALUE "FileVersion", "1.2.1"
-			VALUE "FileDescription", "Chocolate Doom 1.2.1"
+			VALUE "FileVersion", "1.4.0"
+			VALUE "FileDescription", "Chocolate Doom 1.4.0"
 			VALUE "InternalName", "chocolate-doom"
 			VALUE "CompanyName", "[email protected]"
 			VALUE "LegalCopyright", "GNU General Public License"
 			VALUE "ProductName", "Chocolate Doom"
-			VALUE "ProductVersion", "1.2.1"
+			VALUE "ProductVersion", "1.4.0"
 		END
 	END
 END
--- /dev/null
+++ b/opl/.gitignore
@@ -1,0 +1,7 @@
+Makefile.in
+Makefile
+.deps
+libopl.a
+*.rc
+tags
+TAGS
--- /dev/null
+++ b/opl/Makefile.am
@@ -1,0 +1,19 @@
+
+AM_CFLAGS=@SDLMIXER_CFLAGS@
+
+SUBDIRS = . examples
+
+noinst_LIBRARIES=libopl.a
+
+libopl_a_SOURCES =                                \
+                            opl_internal.h        \
+        opl.c               opl.h                 \
+        opl_linux.c                               \
+        opl_obsd.c                                \
+        opl_queue.c         opl_queue.h           \
+        opl_sdl.c                                 \
+        opl_timer.c         opl_timer.h           \
+        opl_win32.c                               \
+        ioperm_sys.c        ioperm_sys.h          \
+        dbopl.c             dbopl.h
+
--- /dev/null
+++ b/opl/dbopl.c
@@ -1,0 +1,1602 @@
+/*
+ *  Copyright (C) 2002-2010  The DOSBox Team
+ *
+ *  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.
+ */
+
+//
+// Chocolate Doom-related discussion:
+//
+// This is the DosBox OPL emulator code (src/hardware/dbopl.cpp) r3635,
+// converted to C.  The bulk of the work was done using the minus-minus
+// script in the Chocolate Doom SVN repository, then the result tweaked
+// by hand until working.
+//
+
+
+/*
+	DOSBox implementation of a combined Yamaha YMF262 and Yamaha YM3812 emulator.
+	Enabling the opl3 bit will switch the emulator to stereo opl3 output instead of regular mono opl2
+	Except for the table generation it's all integer math
+	Can choose different types of generators, using muls and bigger tables, try different ones for slower platforms
+	The generation was based on the MAME implementation but tried to have it use less memory and be faster in general
+	MAME uses much bigger envelope tables and this will be the biggest cause of it sounding different at times
+
+	//TODO Don't delay first operator 1 sample in opl3 mode
+	//TODO Maybe not use class method pointers but a regular function pointers with operator as first parameter
+	//TODO Fix panning for the Percussion channels, would any opl3 player use it and actually really change it though?
+	//TODO Check if having the same accuracy in all frequency multipliers sounds better or not
+
+	//DUNNO Keyon in 4op, switch to 2op without keyoff.
+*/
+
+/* $Id: dbopl.cpp,v 1.10 2009-06-10 19:54:51 harekiet Exp $ */
+
+
+#include <math.h>
+#include <stdlib.h>
+#include <string.h>
+//#include "dosbox.h"
+#include "dbopl.h"
+
+
+#define GCC_UNLIKELY(x) x
+
+#define TRUE 1
+#define FALSE 0
+
+#ifndef PI
+#define PI 3.14159265358979323846
+#endif
+
+#define OPLRATE		((double)(14318180.0 / 288.0))
+#define TREMOLO_TABLE 52
+
+//Try to use most precision for frequencies
+//Else try to keep different waves in synch
+//#define WAVE_PRECISION	1
+#ifndef WAVE_PRECISION
+//Wave bits available in the top of the 32bit range
+//Original adlib uses 10.10, we use 10.22
+#define WAVE_BITS	10
+#else
+//Need some extra bits at the top to have room for octaves and frequency multiplier
+//We support to 8 times lower rate
+//128 * 15 * 8 = 15350, 2^13.9, so need 14 bits
+#define WAVE_BITS	14
+#endif
+#define WAVE_SH		( 32 - WAVE_BITS )
+#define WAVE_MASK	( ( 1 << WAVE_SH ) - 1 )
+
+//Use the same accuracy as the waves
+#define LFO_SH ( WAVE_SH - 10 )
+//LFO is controlled by our tremolo 256 sample limit
+#define LFO_MAX ( 256 << ( LFO_SH ) )
+
+
+//Maximum amount of attenuation bits
+//Envelope goes to 511, 9 bits
+#if (DBOPL_WAVE == WAVE_TABLEMUL )
+//Uses the value directly
+#define ENV_BITS	( 9 )
+#else
+//Add 3 bits here for more accuracy and would have to be shifted up either way
+#define ENV_BITS	( 9 )
+#endif
+//Limits of the envelope with those bits and when the envelope goes silent
+#define ENV_MIN		0
+#define ENV_EXTRA	( ENV_BITS - 9 )
+#define ENV_MAX		( 511 << ENV_EXTRA )
+#define ENV_LIMIT	( ( 12 * 256) >> ( 3 - ENV_EXTRA ) )
+#define ENV_SILENT( _X_ ) ( (_X_) >= ENV_LIMIT )
+
+//Attack/decay/release rate counter shift
+#define RATE_SH		24
+#define RATE_MASK	( ( 1 << RATE_SH ) - 1 )
+//Has to fit within 16bit lookuptable
+#define MUL_SH		16
+
+//Check some ranges
+#if ENV_EXTRA > 3
+#error Too many envelope bits
+#endif
+
+static inline void Operator__SetState(Operator *self, Bit8u s );
+static inline Bit32u Chip__ForwardNoise(Chip *self);
+
+// C++'s template<> sure is useful sometimes.
+
+static Channel* Channel__BlockTemplate(Channel *self, Chip* chip,
+                                Bit32u samples, Bit32s* output,
+                                SynthMode mode );
+#define BLOCK_TEMPLATE(mode) \
+    static Channel* Channel__BlockTemplate_ ## mode(Channel *self, Chip* chip, \
+                                             Bit32u samples, Bit32s* output) \
+    { \
+       return Channel__BlockTemplate(self, chip, samples, output, mode); \
+    }
+
+BLOCK_TEMPLATE(sm2AM)
+BLOCK_TEMPLATE(sm2FM)
+BLOCK_TEMPLATE(sm3AM)
+BLOCK_TEMPLATE(sm3FM)
+BLOCK_TEMPLATE(sm3FMFM)
+BLOCK_TEMPLATE(sm3AMFM)
+BLOCK_TEMPLATE(sm3FMAM)
+BLOCK_TEMPLATE(sm3AMAM)
+BLOCK_TEMPLATE(sm2Percussion)
+BLOCK_TEMPLATE(sm3Percussion)
+
+//How much to substract from the base value for the final attenuation
+static const Bit8u KslCreateTable[16] = {
+	//0 will always be be lower than 7 * 8
+	64, 32, 24, 19, 
+	16, 12, 11, 10, 
+	 8,  6,  5,  4,
+	 3,  2,  1,  0,
+};
+
+#define M(_X_) ((Bit8u)( (_X_) * 2))
+static const Bit8u FreqCreateTable[16] = {
+	M(0.5), M(1 ), M(2 ), M(3 ), M(4 ), M(5 ), M(6 ), M(7 ),
+	M(8  ), M(9 ), M(10), M(10), M(12), M(12), M(15), M(15)
+};
+#undef M
+
+//We're not including the highest attack rate, that gets a special value
+static const Bit8u AttackSamplesTable[13] = {
+	69, 55, 46, 40,
+	35, 29, 23, 20,
+	19, 15, 11, 10,
+	9
+};
+//On a real opl these values take 8 samples to reach and are based upon larger tables
+static const Bit8u EnvelopeIncreaseTable[13] = {
+	4,  5,  6,  7,
+	8, 10, 12, 14,
+	16, 20, 24, 28,
+	32, 
+};
+
+#if ( DBOPL_WAVE == WAVE_HANDLER ) || ( DBOPL_WAVE == WAVE_TABLELOG )
+static Bit16u ExpTable[ 256 ];
+#endif
+
+#if ( DBOPL_WAVE == WAVE_HANDLER )
+//PI table used by WAVEHANDLER
+static Bit16u SinTable[ 512 ];
+#endif
+
+#if ( DBOPL_WAVE > WAVE_HANDLER )
+//Layout of the waveform table in 512 entry intervals
+//With overlapping waves we reduce the table to half it's size
+
+//	|    |//\\|____|WAV7|//__|/\  |____|/\/\|
+//	|\\//|    |    |WAV7|    |  \/|    |    |
+//	|06  |0126|17  |7   |3   |4   |4 5 |5   |
+
+//6 is just 0 shifted and masked
+
+static Bit16s WaveTable[ 8 * 512 ];
+//Distance into WaveTable the wave starts
+static const Bit16u WaveBaseTable[8] = {
+	0x000, 0x200, 0x200, 0x800,
+	0xa00, 0xc00, 0x100, 0x400,
+
+};
+//Mask the counter with this
+static const Bit16u WaveMaskTable[8] = {
+	1023, 1023, 511, 511,
+	1023, 1023, 512, 1023,
+};
+
+//Where to start the counter on at keyon
+static const Bit16u WaveStartTable[8] = {
+	512, 0, 0, 0,
+	0, 512, 512, 256,
+};
+#endif
+
+#if ( DBOPL_WAVE == WAVE_TABLEMUL )
+static Bit16u MulTable[ 384 ];
+#endif
+
+static Bit8u KslTable[ 8 * 16 ];
+static Bit8u TremoloTable[ TREMOLO_TABLE ];
+//Start of a channel behind the chip struct start
+static Bit16u ChanOffsetTable[32];
+//Start of an operator behind the chip struct start
+static Bit16u OpOffsetTable[64];
+
+//The lower bits are the shift of the operator vibrato value
+//The highest bit is right shifted to generate -1 or 0 for negation
+//So taking the highest input value of 7 this gives 3, 7, 3, 0, -3, -7, -3, 0
+static const Bit8s VibratoTable[ 8 ] = {	
+	1 - 0x00, 0 - 0x00, 1 - 0x00, 30 - 0x00, 
+	1 - 0x80, 0 - 0x80, 1 - 0x80, 30 - 0x80 
+};
+
+//Shift strength for the ksl value determined by ksl strength
+static const Bit8u KslShiftTable[4] = {
+	31,1,2,0
+};
+
+//Generate a table index and table shift value using input value from a selected rate
+static void EnvelopeSelect( Bit8u val, Bit8u *index, Bit8u *shift ) {
+	if ( val < 13 * 4 ) {				//Rate 0 - 12
+		*shift = 12 - ( val >> 2 );
+		*index = val & 3;
+	} else if ( val < 15 * 4 ) {		//rate 13 - 14
+		*shift = 0;
+		*index = val - 12 * 4;
+	} else {							//rate 15 and up
+		*shift = 0;
+		*index = 12;
+	}
+}
+
+#if ( DBOPL_WAVE == WAVE_HANDLER )
+/*
+	Generate the different waveforms out of the sine/exponetial table using handlers
+*/
+static inline Bits MakeVolume( Bitu wave, Bitu volume ) {
+	Bitu total = wave + volume;
+	Bitu index = total & 0xff;
+	Bitu sig = ExpTable[ index ];
+	Bitu exp = total >> 8;
+#if 0
+	//Check if we overflow the 31 shift limit
+	if ( exp >= 32 ) {
+		LOG_MSG( "WTF %d %d", total, exp );
+	}
+#endif
+	return (sig >> exp);
+};
+
+static Bits DB_FASTCALL WaveForm0( Bitu i, Bitu volume ) {
+	Bits neg = 0 - (( i >> 9) & 1);//Create ~0 or 0
+	Bitu wave = SinTable[i & 511];
+	return (MakeVolume( wave, volume ) ^ neg) - neg;
+}
+static Bits DB_FASTCALL WaveForm1( Bitu i, Bitu volume ) {
+	Bit32u wave = SinTable[i & 511];
+	wave |= ( ( (i ^ 512 ) & 512) - 1) >> ( 32 - 12 );
+	return MakeVolume( wave, volume );
+}
+static Bits DB_FASTCALL WaveForm2( Bitu i, Bitu volume ) {
+	Bitu wave = SinTable[i & 511];
+	return MakeVolume( wave, volume );
+}
+static Bits DB_FASTCALL WaveForm3( Bitu i, Bitu volume ) {
+	Bitu wave = SinTable[i & 255];
+	wave |= ( ( (i ^ 256 ) & 256) - 1) >> ( 32 - 12 );
+	return MakeVolume( wave, volume );
+}
+static Bits DB_FASTCALL WaveForm4( Bitu i, Bitu volume ) {
+	//Twice as fast
+	i <<= 1;
+	Bits neg = 0 - (( i >> 9) & 1);//Create ~0 or 0
+	Bitu wave = SinTable[i & 511];
+	wave |= ( ( (i ^ 512 ) & 512) - 1) >> ( 32 - 12 );
+	return (MakeVolume( wave, volume ) ^ neg) - neg;
+}
+static Bits DB_FASTCALL WaveForm5( Bitu i, Bitu volume ) {
+	//Twice as fast
+	i <<= 1;
+	Bitu wave = SinTable[i & 511];
+	wave |= ( ( (i ^ 512 ) & 512) - 1) >> ( 32 - 12 );
+	return MakeVolume( wave, volume );
+}
+static Bits DB_FASTCALL WaveForm6( Bitu i, Bitu volume ) {
+	Bits neg = 0 - (( i >> 9) & 1);//Create ~0 or 0
+	return (MakeVolume( 0, volume ) ^ neg) - neg;
+}
+static Bits DB_FASTCALL WaveForm7( Bitu i, Bitu volume ) {
+	//Negative is reversed here
+	Bits neg = (( i >> 9) & 1) - 1;
+	Bitu wave = (i << 3);
+	//When negative the volume also runs backwards
+	wave = ((wave ^ neg) - neg) & 4095;
+	return (MakeVolume( wave, volume ) ^ neg) - neg;
+}
+
+static const WaveHandler WaveHandlerTable[8] = {
+	WaveForm0, WaveForm1, WaveForm2, WaveForm3,
+	WaveForm4, WaveForm5, WaveForm6, WaveForm7
+};
+
+#endif
+
+/*
+	Operator
+*/
+
+//We zero out when rate == 0
+static inline void Operator__UpdateAttack(Operator *self, const Chip* chip ) {
+	Bit8u rate = self->reg60 >> 4;
+	if ( rate ) {
+		Bit8u val = (rate << 2) + self->ksr;
+		self->attackAdd = chip->attackRates[ val ];
+		self->rateZero &= ~(1 << ATTACK);
+	} else {
+		self->attackAdd = 0;
+		self->rateZero |= (1 << ATTACK);
+	}
+}
+static inline void Operator__UpdateDecay(Operator *self, const Chip* chip ) {
+	Bit8u rate = self->reg60 & 0xf;
+	if ( rate ) {
+		Bit8u val = (rate << 2) + self->ksr;
+		self->decayAdd = chip->linearRates[ val ];
+		self->rateZero &= ~(1 << DECAY);
+	} else {
+		self->decayAdd = 0;
+		self->rateZero |= (1 << DECAY);
+	}
+}
+static inline void Operator__UpdateRelease(Operator *self, const Chip* chip ) {
+	Bit8u rate = self->reg80 & 0xf;
+	if ( rate ) {
+		Bit8u val = (rate << 2) + self->ksr;
+		self->releaseAdd = chip->linearRates[ val ];
+		self->rateZero &= ~(1 << RELEASE);
+		if ( !(self->reg20 & MASK_SUSTAIN ) ) {
+			self->rateZero &= ~( 1 << SUSTAIN );
+		}	
+	} else {
+		self->rateZero |= (1 << RELEASE);
+		self->releaseAdd = 0;
+		if ( !(self->reg20 & MASK_SUSTAIN ) ) {
+			self->rateZero |= ( 1 << SUSTAIN );
+		}	
+	}
+}
+
+static inline void Operator__UpdateAttenuation(Operator *self) {
+	Bit8u kslBase = (Bit8u)((self->chanData >> SHIFT_KSLBASE) & 0xff);
+	Bit32u tl = self->reg40 & 0x3f;
+	Bit8u kslShift = KslShiftTable[ self->reg40 >> 6 ];
+	//Make sure the attenuation goes to the right bits
+	self->totalLevel = tl << ( ENV_BITS - 7 );	//Total level goes 2 bits below max
+	self->totalLevel += ( kslBase << ENV_EXTRA ) >> kslShift;
+}
+
+static void Operator__UpdateFrequency(Operator *self) {
+	Bit32u freq = self->chanData & (( 1 << 10 ) - 1);
+	Bit32u block = (self->chanData >> 10) & 0xff;
+#ifdef WAVE_PRECISION
+	block = 7 - block;
+	self->waveAdd = ( freq * self->freqMul ) >> block;
+#else
+	self->waveAdd = ( freq << block ) * self->freqMul;
+#endif
+	if ( self->reg20 & MASK_VIBRATO ) {
+		self->vibStrength = (Bit8u)(freq >> 7);
+
+#ifdef WAVE_PRECISION
+		self->vibrato = ( self->vibStrength * self->freqMul ) >> block;
+#else
+		self->vibrato = ( self->vibStrength << block ) * self->freqMul;
+#endif
+	} else {
+		self->vibStrength = 0;
+		self->vibrato = 0;
+	}
+}
+
+static void Operator__UpdateRates(Operator *self, const Chip* chip ) {
+	//Mame seems to reverse this where enabling ksr actually lowers
+	//the rate, but pdf manuals says otherwise?
+	Bit8u newKsr = (Bit8u)((self->chanData >> SHIFT_KEYCODE) & 0xff);
+	if ( !( self->reg20 & MASK_KSR ) ) {
+		newKsr >>= 2;
+	}
+	if ( self->ksr == newKsr )
+		return;
+	self->ksr = newKsr;
+	Operator__UpdateAttack( self, chip );
+	Operator__UpdateDecay( self, chip );
+	Operator__UpdateRelease( self, chip );
+}
+
+static inline Bit32s Operator__RateForward(Operator *self, Bit32u add ) {
+	self->rateIndex += add;
+	Bit32s ret = self->rateIndex >> RATE_SH;
+	self->rateIndex = self->rateIndex & RATE_MASK;
+	return ret;
+}
+
+static Bits Operator__TemplateVolume(Operator *self, OperatorState yes) {
+	Bit32s vol = self->volume;
+	Bit32s change;
+	switch ( yes ) {
+	case OFF:
+		return ENV_MAX;
+	case ATTACK:
+		change = Operator__RateForward( self, self->attackAdd );
+		if ( !change )
+			return vol;
+		vol += ( (~vol) * change ) >> 3;
+		if ( vol < ENV_MIN ) {
+			self->volume = ENV_MIN;
+			self->rateIndex = 0;
+			Operator__SetState( self, DECAY );
+			return ENV_MIN;
+		}
+		break;
+	case DECAY:
+		vol += Operator__RateForward( self, self->decayAdd );
+		if ( GCC_UNLIKELY(vol >= self->sustainLevel) ) {
+			//Check if we didn't overshoot max attenuation, then just go off
+			if ( GCC_UNLIKELY(vol >= ENV_MAX) ) {
+				self->volume = ENV_MAX;
+				Operator__SetState( self, OFF );
+				return ENV_MAX;
+			}
+			//Continue as sustain
+			self->rateIndex = 0;
+			Operator__SetState( self, SUSTAIN );
+		}
+		break;
+	case SUSTAIN:
+		if ( self->reg20 & MASK_SUSTAIN ) {
+			return vol;
+		}
+		//In sustain phase, but not sustaining, do regular release
+	case RELEASE: 
+		vol += Operator__RateForward( self, self->releaseAdd );;
+		if ( GCC_UNLIKELY(vol >= ENV_MAX) ) {
+			self->volume = ENV_MAX;
+			Operator__SetState( self, OFF );
+			return ENV_MAX;
+		}
+		break;
+	}
+	self->volume = vol;
+	return vol;
+}
+
+#define TEMPLATE_VOLUME(mode) \
+    static Bits Operator__TemplateVolume ## mode(Operator *self) \
+    { \
+        return Operator__TemplateVolume(self, mode); \
+    }
+
+TEMPLATE_VOLUME(OFF)
+TEMPLATE_VOLUME(RELEASE)
+TEMPLATE_VOLUME(SUSTAIN)
+TEMPLATE_VOLUME(ATTACK)
+TEMPLATE_VOLUME(DECAY)
+
+static const VolumeHandler VolumeHandlerTable[5] = {
+        &Operator__TemplateVolumeOFF,
+        &Operator__TemplateVolumeRELEASE,
+        &Operator__TemplateVolumeSUSTAIN,
+        &Operator__TemplateVolumeDECAY,
+        &Operator__TemplateVolumeATTACK,
+};
+
+static inline Bitu Operator__ForwardVolume(Operator *self) {
+	return self->currentLevel + (self->volHandler)(self);
+}
+
+
+static inline Bitu Operator__ForwardWave(Operator *self) {
+	self->waveIndex += self->waveCurrent;	
+	return self->waveIndex >> WAVE_SH;
+}
+
+static void Operator__Write20(Operator *self, const Chip* chip, Bit8u val ) {
+	Bit8u change = (self->reg20 ^ val );
+	if ( !change ) 
+		return;
+	self->reg20 = val;
+	//Shift the tremolo bit over the entire register, saved a branch, YES!
+	self->tremoloMask = (Bit8s)(val) >> 7;
+	self->tremoloMask &= ~(( 1 << ENV_EXTRA ) -1);
+	//Update specific features based on changes
+	if ( change & MASK_KSR ) {
+		Operator__UpdateRates( self, chip );
+	}
+	//With sustain enable the volume doesn't change
+	if ( self->reg20 & MASK_SUSTAIN || ( !self->releaseAdd ) ) {
+		self->rateZero |= ( 1 << SUSTAIN );
+	} else {
+		self->rateZero &= ~( 1 << SUSTAIN );
+	}
+	//Frequency multiplier or vibrato changed
+	if ( change & (0xf | MASK_VIBRATO) ) {
+		self->freqMul = chip->freqMul[ val & 0xf ];
+		Operator__UpdateFrequency(self);
+	}
+}
+
+static void Operator__Write40(Operator *self, const Chip *chip, Bit8u val ) {
+	if (!(self->reg40 ^ val )) 
+		return;
+	self->reg40 = val;
+	Operator__UpdateAttenuation( self );
+}
+
+static void Operator__Write60(Operator *self, const Chip* chip, Bit8u val ) {
+	Bit8u change = self->reg60 ^ val;
+	self->reg60 = val;
+	if ( change & 0x0f ) {
+		Operator__UpdateDecay( self, chip );
+	}
+	if ( change & 0xf0 ) {
+		Operator__UpdateAttack( self, chip );
+	}
+}
+
+static void Operator__Write80(Operator *self, const Chip* chip, Bit8u val ) {
+	Bit8u change = (self->reg80 ^ val );
+	if ( !change ) 
+		return;
+	self->reg80 = val;
+	Bit8u sustain = val >> 4;
+	//Turn 0xf into 0x1f
+	sustain |= ( sustain + 1) & 0x10;
+	self->sustainLevel = sustain << ( ENV_BITS - 5 );
+	if ( change & 0x0f ) {
+		Operator__UpdateRelease( self, chip );
+	}
+}
+
+static void Operator__WriteE0(Operator *self, const Chip* chip, Bit8u val ) {
+	if ( !(self->regE0 ^ val) ) 
+		return;
+	//in opl3 mode you can always selet 7 waveforms regardless of waveformselect
+	Bit8u waveForm = val & ( ( 0x3 & chip->waveFormMask ) | (0x7 & chip->opl3Active ) );
+	self->regE0 = val;
+#if( DBOPL_WAVE == WAVE_HANDLER )
+	self->waveHandler = WaveHandlerTable[ waveForm ];
+#else
+	self->waveBase = WaveTable + WaveBaseTable[ waveForm ];
+	self->waveStart = WaveStartTable[ waveForm ] << WAVE_SH;
+	self->waveMask = WaveMaskTable[ waveForm ];
+#endif
+}
+
+static inline void Operator__SetState(Operator *self, Bit8u s ) {
+	self->state = s;
+	self->volHandler = VolumeHandlerTable[ s ];
+}
+
+static inline int Operator__Silent(Operator *self) {
+	if ( !ENV_SILENT( self->totalLevel + self->volume ) )
+		return FALSE;
+	if ( !(self->rateZero & ( 1 << self->state ) ) )
+		return FALSE;
+	return TRUE;
+}
+
+static inline void Operator__Prepare(Operator *self, const Chip* chip )  {
+	self->currentLevel = self->totalLevel + (chip->tremoloValue & self->tremoloMask);
+	self->waveCurrent = self->waveAdd;
+	if ( self->vibStrength >> chip->vibratoShift ) {
+		Bit32s add = self->vibrato >> chip->vibratoShift;
+		//Sign extend over the shift value
+		Bit32s neg = chip->vibratoSign;
+		//Negate the add with -1 or 0
+		add = ( add ^ neg ) - neg; 
+		self->waveCurrent += add;
+	}
+}
+
+static void Operator__KeyOn(Operator *self, Bit8u mask ) {
+	if ( !self->keyOn ) {
+		//Restart the frequency generator
+#if( DBOPL_WAVE > WAVE_HANDLER )
+		self->waveIndex = self->waveStart;
+#else
+		self->waveIndex = 0;
+#endif
+		self->rateIndex = 0;
+		Operator__SetState( self, ATTACK );
+	}
+	self->keyOn |= mask;
+}
+
+static void Operator__KeyOff(Operator *self, Bit8u mask ) {
+	self->keyOn &= ~mask;
+	if ( !self->keyOn ) {
+		if ( self->state != OFF ) {
+			Operator__SetState( self, RELEASE );
+		}
+	}
+}
+
+static inline Bits Operator__GetWave(Operator *self, Bitu index, Bitu vol ) {
+#if( DBOPL_WAVE == WAVE_HANDLER )
+	return self->waveHandler( index, vol << ( 3 - ENV_EXTRA ) );
+#elif( DBOPL_WAVE == WAVE_TABLEMUL )
+	return(self->waveBase[ index & self->waveMask ] * MulTable[ vol >> ENV_EXTRA ]) >> MUL_SH;
+#elif( DBOPL_WAVE == WAVE_TABLELOG )
+	Bit32s wave = self->waveBase[ index & self->waveMask ];
+	Bit32u total = ( wave & 0x7fff ) + vol << ( 3 - ENV_EXTRA );
+	Bit32s sig = ExpTable[ total & 0xff ];
+	Bit32u exp = total >> 8;
+	Bit32s neg = wave >> 16;
+	return((sig ^ neg) - neg) >> exp;
+#else
+#error "No valid wave routine"
+#endif
+}
+
+static inline Bits Operator__GetSample(Operator *self, Bits modulation ) {
+	Bitu vol = Operator__ForwardVolume(self);
+	if ( ENV_SILENT( vol ) ) {
+		//Simply forward the wave
+		self->waveIndex += self->waveCurrent;
+		return 0;
+	} else {
+		Bitu index = Operator__ForwardWave(self);
+		index += modulation;
+		return Operator__GetWave( self, index, vol );
+	}
+}
+
+static void Operator__Operator(Operator *self) {
+	self->chanData = 0;
+	self->freqMul = 0;
+	self->waveIndex = 0;
+	self->waveAdd = 0;
+	self->waveCurrent = 0;
+	self->keyOn = 0;
+	self->ksr = 0;
+	self->reg20 = 0;
+	self->reg40 = 0;
+	self->reg60 = 0;
+	self->reg80 = 0;
+	self->regE0 = 0;
+	Operator__SetState( self, OFF );
+	self->rateZero = (1 << OFF);
+	self->sustainLevel = ENV_MAX;
+	self->currentLevel = ENV_MAX;
+	self->totalLevel = ENV_MAX;
+	self->volume = ENV_MAX;
+	self->releaseAdd = 0;
+}
+
+/*
+	Channel
+*/
+
+static void Channel__Channel(Channel *self) {
+        Operator__Operator(&self->op[0]);
+        Operator__Operator(&self->op[1]);
+	self->old[0] = self->old[1] = 0;
+	self->chanData = 0;
+	self->regB0 = 0;
+	self->regC0 = 0;
+	self->maskLeft = -1;
+	self->maskRight = -1;
+	self->feedback = 31;
+	self->fourMask = 0;
+	self->synthHandler = Channel__BlockTemplate_sm2FM;
+};
+
+static inline Operator* Channel__Op( Channel *self, Bitu index ) {
+        return &( ( self + (index >> 1) )->op[ index & 1 ]);
+}
+
+static void Channel__SetChanData(Channel *self, const Chip* chip, Bit32u data ) {
+	Bit32u change = self->chanData ^ data;
+	self->chanData = data;
+	Channel__Op( self, 0 )->chanData = data;
+	Channel__Op( self, 1 )->chanData = data;
+	//Since a frequency update triggered this, always update frequency
+        Operator__UpdateFrequency(Channel__Op( self, 0 ));
+        Operator__UpdateFrequency(Channel__Op( self, 1 ));
+	if ( change & ( 0xff << SHIFT_KSLBASE ) ) {
+                Operator__UpdateAttenuation(Channel__Op( self, 0 ));
+                Operator__UpdateAttenuation(Channel__Op( self, 1 ));
+	}
+	if ( change & ( 0xff << SHIFT_KEYCODE ) ) {
+                Operator__UpdateRates(Channel__Op( self, 0 ), chip);
+                Operator__UpdateRates(Channel__Op( self, 1 ), chip);
+	}
+}
+
+static void Channel__UpdateFrequency(Channel *self, const Chip* chip, Bit8u fourOp ) {
+	//Extrace the frequency bits
+	Bit32u data = self->chanData & 0xffff;
+	Bit32u kslBase = KslTable[ data >> 6 ];
+	Bit32u keyCode = ( data & 0x1c00) >> 9;
+	if ( chip->reg08 & 0x40 ) {
+		keyCode |= ( data & 0x100)>>8;	/* notesel == 1 */
+	} else {
+		keyCode |= ( data & 0x200)>>9;	/* notesel == 0 */
+	}
+	//Add the keycode and ksl into the highest bits of chanData
+	data |= (keyCode << SHIFT_KEYCODE) | ( kslBase << SHIFT_KSLBASE );
+        Channel__SetChanData( self + 0, chip, data );
+	if ( fourOp & 0x3f ) {
+                Channel__SetChanData( self + 1, chip, data );
+	}
+}
+
+static void Channel__WriteA0(Channel *self, const Chip* chip, Bit8u val ) {
+	Bit8u fourOp = chip->reg104 & chip->opl3Active & self->fourMask;
+	//Don't handle writes to silent fourop channels
+	if ( fourOp > 0x80 )
+		return;
+	Bit32u change = (self->chanData ^ val ) & 0xff;
+	if ( change ) {
+		self->chanData ^= change;
+		Channel__UpdateFrequency( self, chip, fourOp );
+	}
+}
+
+static void Channel__WriteB0(Channel *self, const Chip* chip, Bit8u val ) {
+	Bit8u fourOp = chip->reg104 & chip->opl3Active & self->fourMask;
+	//Don't handle writes to silent fourop channels
+	if ( fourOp > 0x80 )
+		return;
+	Bitu change = (self->chanData ^ ( val << 8 ) ) & 0x1f00;
+	if ( change ) {
+		self->chanData ^= change;
+		Channel__UpdateFrequency( self, chip, fourOp );
+	}
+	//Check for a change in the keyon/off state
+	if ( !(( val ^ self->regB0) & 0x20))
+		return;
+	self->regB0 = val;
+	if ( val & 0x20 ) {
+                Operator__KeyOn( Channel__Op(self, 0), 0x1 );
+                Operator__KeyOn( Channel__Op(self, 1), 0x1 );
+		if ( fourOp & 0x3f ) {
+                        Operator__KeyOn( Channel__Op(self + 1, 0), 1 );
+                        Operator__KeyOn( Channel__Op(self + 1, 1), 1 );
+		}
+	} else {
+                Operator__KeyOff( Channel__Op(self, 0), 0x1 );
+                Operator__KeyOff( Channel__Op(self, 1), 0x1 );
+		if ( fourOp & 0x3f ) {
+                        Operator__KeyOff( Channel__Op(self + 1, 0), 1 );
+                        Operator__KeyOff( Channel__Op(self + 1, 1), 1 );
+		}
+	}
+}
+
+static void Channel__WriteC0(Channel *self, const Chip* chip, Bit8u val ) {
+	Bit8u change = val ^ self->regC0;
+	if ( !change )
+		return;
+	self->regC0 = val;
+	self->feedback = ( val >> 1 ) & 7;
+	if ( self->feedback ) {
+		//We shift the input to the right 10 bit wave index value
+		self->feedback = 9 - self->feedback;
+	} else {
+		self->feedback = 31;
+	}
+	//Select the new synth mode
+	if ( chip->opl3Active ) {
+		//4-op mode enabled for this channel
+		if ( (chip->reg104 & self->fourMask) & 0x3f ) {
+			Channel* chan0, *chan1;
+			//Check if it's the 2nd channel in a 4-op
+			if ( !(self->fourMask & 0x80 ) ) {
+				chan0 = self;
+				chan1 = self + 1;
+			} else {
+				chan0 = self - 1;
+				chan1 = self;
+			}
+
+			Bit8u synth = ( (chan0->regC0 & 1) << 0 )| (( chan1->regC0 & 1) << 1 );
+			switch ( synth ) {
+			case 0:
+				chan0->synthHandler = Channel__BlockTemplate_sm3FMFM;
+				break;
+			case 1:
+				chan0->synthHandler = Channel__BlockTemplate_sm3AMFM;
+				break;
+			case 2:
+				chan0->synthHandler = Channel__BlockTemplate_sm3FMAM ;
+				break;
+			case 3:
+				chan0->synthHandler = Channel__BlockTemplate_sm3AMAM ;
+				break;
+			}
+		//Disable updating percussion channels
+		} else if ((self->fourMask & 0x40) && ( chip->regBD & 0x20) ) {
+
+		//Regular dual op, am or fm
+		} else if ( val & 1 ) {
+			self->synthHandler = Channel__BlockTemplate_sm3AM;
+		} else {
+			self->synthHandler = Channel__BlockTemplate_sm3FM;
+		}
+		self->maskLeft = ( val & 0x10 ) ? -1 : 0;
+		self->maskRight = ( val & 0x20 ) ? -1 : 0;
+	//opl2 active
+	} else { 
+		//Disable updating percussion channels
+		if ( (self->fourMask & 0x40) && ( chip->regBD & 0x20 ) ) {
+
+		//Regular dual op, am or fm
+		} else if ( val & 1 ) {
+			self->synthHandler = Channel__BlockTemplate_sm2AM;
+		} else {
+			self->synthHandler = Channel__BlockTemplate_sm2FM;
+		}
+	}
+}
+
+static void Channel__ResetC0(Channel *self, const Chip* chip ) {
+	Bit8u val = self->regC0;
+	self->regC0 ^= 0xff;
+	Channel__WriteC0( self, chip, val );
+};
+
+static inline void Channel__GeneratePercussion(Channel *self, Chip* chip,
+                                               Bit32s* output, int opl3Mode ) {
+	Channel* chan = self;
+
+	//BassDrum
+	Bit32s mod = (Bit32u)((self->old[0] + self->old[1])) >> self->feedback;
+	self->old[0] = self->old[1];
+	self->old[1] = Operator__GetSample( Channel__Op(self, 0), mod ); 
+
+	//When bassdrum is in AM mode first operator is ignoed
+	if ( chan->regC0 & 1 ) {
+		mod = 0;
+	} else {
+		mod = self->old[0];
+	}
+	Bit32s sample = Operator__GetSample( Channel__Op(self, 1), mod ); 
+
+	//Precalculate stuff used by other outputs
+	Bit32u noiseBit = Chip__ForwardNoise(chip) & 0x1;
+	Bit32u c2 = Operator__ForwardWave(Channel__Op(self, 2));
+	Bit32u c5 = Operator__ForwardWave(Channel__Op(self, 5));
+	Bit32u phaseBit = (((c2 & 0x88) ^ ((c2<<5) & 0x80)) | ((c5 ^ (c5<<2)) & 0x20)) ? 0x02 : 0x00;
+
+	//Hi-Hat
+	Bit32u hhVol = Operator__ForwardVolume(Channel__Op(self, 2));
+	if ( !ENV_SILENT( hhVol ) ) {
+		Bit32u hhIndex = (phaseBit<<8) | (0x34 << ( phaseBit ^ (noiseBit << 1 )));
+		sample += Operator__GetWave( Channel__Op(self, 2), hhIndex, hhVol );
+	}
+	//Snare Drum
+	Bit32u sdVol = Operator__ForwardVolume( Channel__Op(self, 3) );
+	if ( !ENV_SILENT( sdVol ) ) {
+		Bit32u sdIndex = ( 0x100 + (c2 & 0x100) ) ^ ( noiseBit << 8 );
+		sample += Operator__GetWave( Channel__Op(self, 3), sdIndex, sdVol );
+	}
+	//Tom-tom
+	sample += Operator__GetSample( Channel__Op(self, 4), 0 );
+
+	//Top-Cymbal
+	Bit32u tcVol = Operator__ForwardVolume(Channel__Op(self, 5));
+	if ( !ENV_SILENT( tcVol ) ) {
+		Bit32u tcIndex = (1 + phaseBit) << 8;
+		sample += Operator__GetWave( Channel__Op(self, 5), tcIndex, tcVol );
+	}
+	sample <<= 1;
+	if ( opl3Mode ) {
+		output[0] += sample;
+		output[1] += sample;
+	} else {
+		output[0] += sample;
+	}
+}
+
+Channel* Channel__BlockTemplate(Channel *self, Chip* chip,
+                                Bit32u samples, Bit32s* output,
+                                SynthMode mode ) {
+        Bitu i;
+
+	switch( mode ) {
+	case sm2AM:
+	case sm3AM:
+		if ( Operator__Silent(Channel__Op(self, 0))
+                 && Operator__Silent(Channel__Op(self, 1))) {
+			self->old[0] = self->old[1] = 0;
+			return(self + 1);
+		}
+		break;
+	case sm2FM:
+	case sm3FM:
+		if ( Operator__Silent(Channel__Op(self, 1))) {
+			self->old[0] = self->old[1] = 0;
+			return (self + 1);
+		}
+		break;
+	case sm3FMFM:
+		if ( Operator__Silent(Channel__Op(self, 3))) {
+			self->old[0] = self->old[1] = 0;
+			return (self + 2);
+		}
+		break;
+	case sm3AMFM:
+		if ( Operator__Silent( Channel__Op(self, 0) )
+                 && Operator__Silent( Channel__Op(self, 3) )) {
+			self->old[0] = self->old[1] = 0;
+			return (self + 2);
+		}
+		break;
+	case sm3FMAM:
+		if ( Operator__Silent( Channel__Op(self, 1))
+                 && Operator__Silent( Channel__Op(self, 3))) {
+			self->old[0] = self->old[1] = 0;
+			return (self + 2);
+		}
+		break;
+	case sm3AMAM:
+		if ( Operator__Silent( Channel__Op(self, 0) )
+                 && Operator__Silent( Channel__Op(self, 2) )
+                 && Operator__Silent( Channel__Op(self, 3) )) {
+			self->old[0] = self->old[1] = 0;
+			return (self + 2);
+		}
+		break;
+
+        default:
+                abort();
+	}
+	//Init the operators with the the current vibrato and tremolo values
+        Operator__Prepare( Channel__Op( self, 0 ), chip );
+        Operator__Prepare( Channel__Op( self, 1 ), chip );
+	if ( mode > sm4Start ) {
+                Operator__Prepare( Channel__Op( self, 2 ), chip );
+                Operator__Prepare( Channel__Op( self, 3 ), chip );
+	}
+	if ( mode > sm6Start ) {
+                Operator__Prepare( Channel__Op( self, 4 ), chip );
+                Operator__Prepare( Channel__Op( self, 5 ), chip );
+	}
+	for ( i = 0; i < samples; i++ ) {
+		//Early out for percussion handlers
+		if ( mode == sm2Percussion ) {
+			Channel__GeneratePercussion( self, chip, output + i, FALSE );
+			continue;	//Prevent some unitialized value bitching
+		} else if ( mode == sm3Percussion ) {
+			Channel__GeneratePercussion( self, chip, output + i * 2, TRUE );
+			continue;	//Prevent some unitialized value bitching
+		}
+
+		//Do unsigned shift so we can shift out all bits but still stay in 10 bit range otherwise
+		Bit32s mod = (Bit32u)((self->old[0] + self->old[1])) >> self->feedback;
+		self->old[0] = self->old[1];
+		self->old[1] = Operator__GetSample( Channel__Op(self, 0), mod );
+		Bit32s sample = 0;
+		Bit32s out0 = self->old[0];
+		if ( mode == sm2AM || mode == sm3AM ) {
+			sample = out0 + Operator__GetSample( Channel__Op(self, 1), 0 );
+		} else if ( mode == sm2FM || mode == sm3FM ) {
+			sample = Operator__GetSample( Channel__Op(self, 1), out0 );
+		} else if ( mode == sm3FMFM ) {
+			Bits next = Operator__GetSample( Channel__Op(self, 1), out0 );
+			next = Operator__GetSample( Channel__Op(self, 2), next );
+			sample = Operator__GetSample( Channel__Op(self, 3), next );
+		} else if ( mode == sm3AMFM ) {
+			sample = out0;
+			Bits next = Operator__GetSample( Channel__Op(self, 1), 0 );
+			next = Operator__GetSample( Channel__Op(self, 2), next );
+			sample += Operator__GetSample( Channel__Op(self, 3), next );
+		} else if ( mode == sm3FMAM ) {
+			sample = Operator__GetSample( Channel__Op(self, 1), out0 );
+			Bits next = Operator__GetSample( Channel__Op(self, 2), 0 );
+			sample += Operator__GetSample( Channel__Op(self, 3), next );
+		} else if ( mode == sm3AMAM ) {
+			sample = out0;
+			Bits next = Operator__GetSample( Channel__Op(self, 1), 0 );
+			sample += Operator__GetSample( Channel__Op(self, 2), next );
+			sample += Operator__GetSample( Channel__Op(self, 3), 0 );
+		}
+		switch( mode ) {
+		case sm2AM:
+		case sm2FM:
+			output[ i ] += sample;
+			break;
+		case sm3AM:
+		case sm3FM:
+		case sm3FMFM:
+		case sm3AMFM:
+		case sm3FMAM:
+		case sm3AMAM:
+			output[ i * 2 + 0 ] += sample & self->maskLeft;
+			output[ i * 2 + 1 ] += sample & self->maskRight;
+			break;
+                default:
+                        abort();
+		}
+	}
+	switch( mode ) {
+	case sm2AM:
+	case sm2FM:
+	case sm3AM:
+	case sm3FM:
+		return ( self + 1 );
+	case sm3FMFM:
+	case sm3AMFM:
+	case sm3FMAM:
+	case sm3AMAM:
+		return ( self + 2 );
+	case sm2Percussion:
+	case sm3Percussion:
+		return( self + 3 );
+        default:
+                abort();
+	}
+	return 0;
+}
+
+/*
+	Chip
+*/
+
+void Chip__Chip(Chip *self) {
+        int i;
+
+        for (i=0; i<18; ++i) {
+                Channel__Channel(&self->chan[i]);
+        }
+
+	self->reg08 = 0;
+	self->reg04 = 0;
+	self->regBD = 0;
+	self->reg104 = 0;
+	self->opl3Active = 0;
+}
+
+static inline Bit32u Chip__ForwardNoise(Chip *self) {
+	self->noiseCounter += self->noiseAdd;
+	Bitu count = self->noiseCounter >> LFO_SH;
+	self->noiseCounter &= WAVE_MASK;
+	for ( ; count > 0; --count ) {
+		//Noise calculation from mame
+		self->noiseValue ^= ( 0x800302 ) & ( 0 - (self->noiseValue & 1 ) );
+		self->noiseValue >>= 1;
+	}
+	return self->noiseValue;
+}
+
+static inline Bit32u Chip__ForwardLFO(Chip *self, Bit32u samples ) {
+	//Current vibrato value, runs 4x slower than tremolo
+	self->vibratoSign = ( VibratoTable[ self->vibratoIndex >> 2] ) >> 7;
+	self->vibratoShift = ( VibratoTable[ self->vibratoIndex >> 2] & 7) + self->vibratoStrength; 
+	self->tremoloValue = TremoloTable[ self->tremoloIndex ] >> self->tremoloStrength;
+
+	//Check hom many samples there can be done before the value changes
+	Bit32u todo = LFO_MAX - self->lfoCounter;
+	Bit32u count = (todo + self->lfoAdd - 1) / self->lfoAdd;
+	if ( count > samples ) {
+		count = samples;
+		self->lfoCounter += count * self->lfoAdd;
+	} else {
+		self->lfoCounter += count * self->lfoAdd;
+		self->lfoCounter &= (LFO_MAX - 1);
+		//Maximum of 7 vibrato value * 4
+		self->vibratoIndex = ( self->vibratoIndex + 1 ) & 31;
+		//Clip tremolo to the the table size
+		if ( self->tremoloIndex + 1 < TREMOLO_TABLE  )
+			++self->tremoloIndex;
+		else
+			self->tremoloIndex = 0;
+	}
+	return count;
+}
+
+
+static void Chip__WriteBD(Chip *self, Bit8u val ) {
+	Bit8u change = self->regBD ^ val;
+	if ( !change )
+		return;
+	self->regBD = val;
+	//TODO could do this with shift and xor?
+	self->vibratoStrength = (val & 0x40) ? 0x00 : 0x01;
+	self->tremoloStrength = (val & 0x80) ? 0x00 : 0x02;
+	if ( val & 0x20 ) {
+		//Drum was just enabled, make sure channel 6 has the right synth
+		if ( change & 0x20 ) {
+			if ( self->opl3Active ) {
+				self->chan[6].synthHandler
+                                    = Channel__BlockTemplate_sm3Percussion; 
+			} else {
+				self->chan[6].synthHandler
+                                    = Channel__BlockTemplate_sm2Percussion;
+			}
+		}
+		//Bass Drum
+		if ( val & 0x10 ) {
+                        Operator__KeyOn( &self->chan[6].op[0], 0x2 );
+                        Operator__KeyOn( &self->chan[6].op[1], 0x2 );
+		} else {
+                        Operator__KeyOff( &self->chan[6].op[0], 0x2 );
+                        Operator__KeyOff( &self->chan[6].op[1], 0x2 );
+		}
+		//Hi-Hat
+		if ( val & 0x1 ) {
+                        Operator__KeyOn( &self->chan[7].op[0], 0x2 );
+		} else {
+                        Operator__KeyOff( &self->chan[7].op[0], 0x2 );
+		}
+		//Snare
+		if ( val & 0x8 ) {
+                        Operator__KeyOn( &self->chan[7].op[1], 0x2 );
+		} else {
+                        Operator__KeyOff( &self->chan[7].op[1], 0x2 );
+		}
+		//Tom-Tom
+		if ( val & 0x4 ) {
+                        Operator__KeyOn( &self->chan[8].op[0], 0x2 );
+		} else {
+                        Operator__KeyOff( &self->chan[8].op[0], 0x2 );
+		}
+		//Top Cymbal
+		if ( val & 0x2 ) {
+                        Operator__KeyOn( &self->chan[8].op[1], 0x2 );
+		} else {
+                        Operator__KeyOff( &self->chan[8].op[1], 0x2 );
+		}
+	//Toggle keyoffs when we turn off the percussion
+	} else if ( change & 0x20 ) {
+		//Trigger a reset to setup the original synth handler
+                Channel__ResetC0( &self->chan[6], self );
+                Operator__KeyOff( &self->chan[6].op[0], 0x2 );
+                Operator__KeyOff( &self->chan[6].op[1], 0x2 );
+                Operator__KeyOff( &self->chan[7].op[0], 0x2 );
+                Operator__KeyOff( &self->chan[7].op[1], 0x2 );
+                Operator__KeyOff( &self->chan[8].op[0], 0x2 );
+                Operator__KeyOff( &self->chan[8].op[1], 0x2 );
+	}
+}
+
+
+#define REGOP( _FUNC_ )															\
+	index = ( ( reg >> 3) & 0x20 ) | ( reg & 0x1f );								\
+	if ( OpOffsetTable[ index ] ) {													\
+		Operator* regOp = (Operator*)( ((char *)self ) + OpOffsetTable[ index ] );	\
+                Operator__ ## _FUNC_ (regOp, self, val); \
+	}
+
+#define REGCHAN( _FUNC_ )																\
+	index = ( ( reg >> 4) & 0x10 ) | ( reg & 0xf );										\
+	if ( ChanOffsetTable[ index ] ) {													\
+		Channel* regChan = (Channel*)( ((char *)self ) + ChanOffsetTable[ index ] );	\
+                Channel__ ## _FUNC_ (regChan, self, val); \
+	}
+
+void Chip__WriteReg(Chip *self, Bit32u reg, Bit8u val ) {
+	Bitu index;
+	switch ( (reg & 0xf0) >> 4 ) {
+	case 0x00 >> 4:
+		if ( reg == 0x01 ) {
+			self->waveFormMask = ( val & 0x20 ) ? 0x7 : 0x0; 
+		} else if ( reg == 0x104 ) {
+			//Only detect changes in lowest 6 bits
+			if ( !((self->reg104 ^ val) & 0x3f) )
+				return;
+			//Always keep the highest bit enabled, for checking > 0x80
+			self->reg104 = 0x80 | ( val & 0x3f );
+		} else if ( reg == 0x105 ) {
+                        int i;
+
+			//MAME says the real opl3 doesn't reset anything on opl3 disable/enable till the next write in another register
+			if ( !((self->opl3Active ^ val) & 1 ) )
+				return;
+			self->opl3Active = ( val & 1 ) ? 0xff : 0;
+			//Update the 0xc0 register for all channels to signal the switch to mono/stereo handlers
+			for ( i = 0; i < 18;i++ ) {
+                                Channel__ResetC0( &self->chan[i], self );
+			}
+		} else if ( reg == 0x08 ) {
+			self->reg08 = val;
+		}
+	case 0x10 >> 4:
+		break;
+	case 0x20 >> 4:
+	case 0x30 >> 4:
+		REGOP( Write20 );
+		break;
+	case 0x40 >> 4:
+	case 0x50 >> 4:
+		REGOP( Write40 );
+		break;
+	case 0x60 >> 4:
+	case 0x70 >> 4:
+		REGOP( Write60 );
+		break;
+	case 0x80 >> 4:
+	case 0x90 >> 4:
+		REGOP( Write80 );
+		break;
+	case 0xa0 >> 4:
+		REGCHAN( WriteA0 );
+		break;
+	case 0xb0 >> 4:
+		if ( reg == 0xbd ) {
+			Chip__WriteBD( self, val );
+		} else {
+			REGCHAN( WriteB0 );
+		}
+		break;
+	case 0xc0 >> 4:
+		REGCHAN( WriteC0 );
+	case 0xd0 >> 4:
+		break;
+	case 0xe0 >> 4:
+	case 0xf0 >> 4:
+		REGOP( WriteE0 );
+		break;
+	}
+}
+
+Bit32u Chip__WriteAddr(Chip *self, Bit32u port, Bit8u val ) {
+	switch ( port & 3 ) {
+	case 0:
+		return val;
+	case 2:
+		if ( self->opl3Active || (val == 0x05) )
+			return 0x100 | val;
+		else
+			return val;
+	}
+	return 0;
+}
+
+void Chip__GenerateBlock2(Chip *self, Bitu total, Bit32s* output ) {
+	while ( total > 0 ) {
+                Channel *ch;
+		int count;
+
+		Bit32u samples = Chip__ForwardLFO( self, total );
+		memset(output, 0, sizeof(Bit32s) * samples);
+		count = 0;
+		for ( ch = self->chan; ch < self->chan + 9; ) {
+			count++;
+			ch = (ch->synthHandler)( ch, self, samples, output );
+		}
+		total -= samples;
+		output += samples;
+	}
+}
+
+void Chip__GenerateBlock3(Chip *self, Bitu total, Bit32s* output  ) {
+	while ( total > 0 ) {
+                int count;
+                Channel *ch;
+
+		Bit32u samples = Chip__ForwardLFO( self, total );
+		memset(output, 0, sizeof(Bit32s) * samples *2);
+		count = 0;
+		for ( ch = self->chan; ch < self->chan + 18; ) {
+			count++;
+			ch = (ch->synthHandler)( ch, self, samples, output );
+		}
+		total -= samples;
+		output += samples * 2;
+	}
+}
+
+void Chip__Setup(Chip *self, Bit32u rate ) {
+	double original = OPLRATE;
+        Bit32u i;
+//	double original = rate;
+	double scale = original / (double)rate;
+
+	//Noise counter is run at the same precision as general waves
+	self->noiseAdd = (Bit32u)( 0.5 + scale * ( 1 << LFO_SH ) );
+	self->noiseCounter = 0;
+	self->noiseValue = 1;	//Make sure it triggers the noise xor the first time
+	//The low frequency oscillation counter
+	//Every time his overflows vibrato and tremoloindex are increased
+	self->lfoAdd = (Bit32u)( 0.5 + scale * ( 1 << LFO_SH ) );
+	self->lfoCounter = 0;
+	self->vibratoIndex = 0;
+	self->tremoloIndex = 0;
+
+	//With higher octave this gets shifted up
+	//-1 since the freqCreateTable = *2
+#ifdef WAVE_PRECISION
+	double freqScale = ( 1 << 7 ) * scale * ( 1 << ( WAVE_SH - 1 - 10));
+	for ( i = 0; i < 16; i++ ) {
+		self->freqMul[i] = (Bit32u)( 0.5 + freqScale * FreqCreateTable[ i ] );
+	}
+#else
+	Bit32u freqScale = (Bit32u)( 0.5 + scale * ( 1 << ( WAVE_SH - 1 - 10)));
+	for ( i = 0; i < 16; i++ ) {
+		self->freqMul[i] = freqScale * FreqCreateTable[ i ];
+	}
+#endif
+
+	//-3 since the real envelope takes 8 steps to reach the single value we supply
+	for ( i = 0; i < 76; i++ ) {
+		Bit8u index, shift;
+		EnvelopeSelect( i, &index, &shift );
+		self->linearRates[i] = (Bit32u)( scale * (EnvelopeIncreaseTable[ index ] << ( RATE_SH + ENV_EXTRA - shift - 3 )));
+	}
+	//Generate the best matching attack rate
+	for ( i = 0; i < 62; i++ ) {
+		Bit8u index, shift;
+		EnvelopeSelect( i, &index, &shift );
+		//Original amount of samples the attack would take
+		Bit32s original = (Bit32u)( (AttackSamplesTable[ index ] << shift) / scale);
+		 
+		Bit32s guessAdd = (Bit32u)( scale * (EnvelopeIncreaseTable[ index ] << ( RATE_SH - shift - 3 )));
+		Bit32s bestAdd = guessAdd;
+		Bit32u bestDiff = 1 << 30;
+                Bit32u passes;
+
+		for ( passes = 0; passes < 16; passes ++ ) {
+			Bit32s volume = ENV_MAX;
+			Bit32s samples = 0;
+			Bit32u count = 0;
+			while ( volume > 0 && samples < original * 2 ) {
+				count += guessAdd;
+				Bit32s change = count >> RATE_SH;
+				count &= RATE_MASK;
+				if ( GCC_UNLIKELY(change) ) { // less than 1 % 
+					volume += ( ~volume * change ) >> 3;
+				}
+				samples++;
+
+			}
+			Bit32s diff = original - samples;
+			Bit32u lDiff = labs( diff );
+			//Init last on first pass
+			if ( lDiff < bestDiff ) {
+				bestDiff = lDiff;
+				bestAdd = guessAdd;
+				if ( !bestDiff )
+					break;
+			}
+			//Below our target
+			if ( diff < 0 ) {
+				//Better than the last time
+				Bit32s mul = ((original - diff) << 12) / original;
+				guessAdd = ((guessAdd * mul) >> 12);
+				guessAdd++;
+			} else if ( diff > 0 ) {
+				Bit32s mul = ((original - diff) << 12) / original;
+				guessAdd = (guessAdd * mul) >> 12;
+				guessAdd--;
+			}
+		}
+		self->attackRates[i] = bestAdd;
+	}
+	for ( i = 62; i < 76; i++ ) {
+		//This should provide instant volume maximizing
+		self->attackRates[i] = 8 << RATE_SH;
+	}
+	//Setup the channels with the correct four op flags
+	//Channels are accessed through a table so they appear linear here
+	self->chan[ 0].fourMask = 0x00 | ( 1 << 0 );
+	self->chan[ 1].fourMask = 0x80 | ( 1 << 0 );
+	self->chan[ 2].fourMask = 0x00 | ( 1 << 1 );
+	self->chan[ 3].fourMask = 0x80 | ( 1 << 1 );
+	self->chan[ 4].fourMask = 0x00 | ( 1 << 2 );
+	self->chan[ 5].fourMask = 0x80 | ( 1 << 2 );
+
+	self->chan[ 9].fourMask = 0x00 | ( 1 << 3 );
+	self->chan[10].fourMask = 0x80 | ( 1 << 3 );
+	self->chan[11].fourMask = 0x00 | ( 1 << 4 );
+	self->chan[12].fourMask = 0x80 | ( 1 << 4 );
+	self->chan[13].fourMask = 0x00 | ( 1 << 5 );
+	self->chan[14].fourMask = 0x80 | ( 1 << 5 );
+
+	//mark the percussion channels
+	self->chan[ 6].fourMask = 0x40;
+	self->chan[ 7].fourMask = 0x40;
+	self->chan[ 8].fourMask = 0x40;
+
+	//Clear Everything in opl3 mode
+	Chip__WriteReg( self, 0x105, 0x1 );
+	for ( i = 0; i < 512; i++ ) {
+		if ( i == 0x105 )
+			continue;
+		Chip__WriteReg( self, i, 0xff );
+		Chip__WriteReg( self, i, 0x0 );
+	}
+	Chip__WriteReg( self, 0x105, 0x0 );
+	//Clear everything in opl2 mode
+	for ( i = 0; i < 255; i++ ) {
+		Chip__WriteReg( self, i, 0xff );
+		Chip__WriteReg( self, i, 0x0 );
+	}
+}
+
+static int doneTables = FALSE;
+void DBOPL_InitTables( void ) {
+        int i, oct;
+
+	if ( doneTables )
+		return;
+	doneTables = TRUE;
+#if ( DBOPL_WAVE == WAVE_HANDLER ) || ( DBOPL_WAVE == WAVE_TABLELOG )
+	//Exponential volume table, same as the real adlib
+	for ( i = 0; i < 256; i++ ) {
+		//Save them in reverse
+		ExpTable[i] = (int)( 0.5 + ( pow(2.0, ( 255 - i) * ( 1.0 /256 ) )-1) * 1024 );
+		ExpTable[i] += 1024; //or remove the -1 oh well :)
+		//Preshift to the left once so the final volume can shift to the right
+		ExpTable[i] *= 2;
+	}
+#endif
+#if ( DBOPL_WAVE == WAVE_HANDLER )
+	//Add 0.5 for the trunc rounding of the integer cast
+	//Do a PI sinetable instead of the original 0.5 PI
+	for ( i = 0; i < 512; i++ ) {
+		SinTable[i] = (Bit16s)( 0.5 - log10( sin( (i + 0.5) * (PI / 512.0) ) ) / log10(2.0)*256 );
+	}
+#endif
+#if ( DBOPL_WAVE == WAVE_TABLEMUL )
+	//Multiplication based tables
+	for ( i = 0; i < 384; i++ ) {
+		int s = i * 8;
+		//TODO maybe keep some of the precision errors of the original table?
+		double val = ( 0.5 + ( pow(2.0, -1.0 + ( 255 - s) * ( 1.0 /256 ) )) * ( 1 << MUL_SH ));
+		MulTable[i] = (Bit16u)(val);
+	}
+
+	//Sine Wave Base
+	for ( i = 0; i < 512; i++ ) {
+		WaveTable[ 0x0200 + i ] = (Bit16s)(sin( (i + 0.5) * (PI / 512.0) ) * 4084);
+		WaveTable[ 0x0000 + i ] = -WaveTable[ 0x200 + i ];
+	}
+	//Exponential wave
+	for ( i = 0; i < 256; i++ ) {
+		WaveTable[ 0x700 + i ] = (Bit16s)( 0.5 + ( pow(2.0, -1.0 + ( 255 - i * 8) * ( 1.0 /256 ) ) ) * 4085 );
+		WaveTable[ 0x6ff - i ] = -WaveTable[ 0x700 + i ];
+	}
+#endif
+#if ( DBOPL_WAVE == WAVE_TABLELOG )
+	//Sine Wave Base
+	for ( i = 0; i < 512; i++ ) {
+		WaveTable[ 0x0200 + i ] = (Bit16s)( 0.5 - log10( sin( (i + 0.5) * (PI / 512.0) ) ) / log10(2.0)*256 );
+		WaveTable[ 0x0000 + i ] = ((Bit16s)0x8000) | WaveTable[ 0x200 + i];
+	}
+	//Exponential wave
+	for ( i = 0; i < 256; i++ ) {
+		WaveTable[ 0x700 + i ] = i * 8;
+		WaveTable[ 0x6ff - i ] = ((Bit16s)0x8000) | i * 8;
+	} 
+#endif
+
+	//	|    |//\\|____|WAV7|//__|/\  |____|/\/\|
+	//	|\\//|    |    |WAV7|    |  \/|    |    |
+	//	|06  |0126|27  |7   |3   |4   |4 5 |5   |
+
+#if (( DBOPL_WAVE == WAVE_TABLELOG ) || ( DBOPL_WAVE == WAVE_TABLEMUL ))
+	for ( i = 0; i < 256; i++ ) {
+		//Fill silence gaps
+		WaveTable[ 0x400 + i ] = WaveTable[0];
+		WaveTable[ 0x500 + i ] = WaveTable[0];
+		WaveTable[ 0x900 + i ] = WaveTable[0];
+		WaveTable[ 0xc00 + i ] = WaveTable[0];
+		WaveTable[ 0xd00 + i ] = WaveTable[0];
+		//Replicate sines in other pieces
+		WaveTable[ 0x800 + i ] = WaveTable[ 0x200 + i ];
+		//double speed sines
+		WaveTable[ 0xa00 + i ] = WaveTable[ 0x200 + i * 2 ];
+		WaveTable[ 0xb00 + i ] = WaveTable[ 0x000 + i * 2 ];
+		WaveTable[ 0xe00 + i ] = WaveTable[ 0x200 + i * 2 ];
+		WaveTable[ 0xf00 + i ] = WaveTable[ 0x200 + i * 2 ];
+	} 
+#endif
+
+	//Create the ksl table
+	for ( oct = 0; oct < 8; oct++ ) {
+		int base = oct * 8;
+		for ( i = 0; i < 16; i++ ) {
+			int val = base - KslCreateTable[i];
+			if ( val < 0 )
+				val = 0;
+			//*4 for the final range to match attenuation range
+			KslTable[ oct * 16 + i ] = val * 4;
+		}
+	}
+	//Create the Tremolo table, just increase and decrease a triangle wave
+	for ( i = 0; i < TREMOLO_TABLE / 2; i++ ) {
+		Bit8u val = i << ENV_EXTRA;
+		TremoloTable[i] = val;
+		TremoloTable[TREMOLO_TABLE - 1 - i] = val;
+	}
+	//Create a table with offsets of the channels from the start of the chip
+        Chip *chip = NULL;
+	for ( i = 0; i < 32; i++ ) {
+		Bitu index = i & 0xf;
+		if ( index >= 9 ) {
+			ChanOffsetTable[i] = 0;
+			continue;
+		}
+		//Make sure the four op channels follow eachother
+		if ( index < 6 ) {
+			index = (index % 3) * 2 + ( index / 3 );
+		}
+		//Add back the bits for highest ones
+		if ( i >= 16 )
+			index += 9;
+		Bitu blah = (Bitu) ( &(chip->chan[ index ]) );
+		ChanOffsetTable[i] = blah;
+	}
+	//Same for operators
+	for ( i = 0; i < 64; i++ ) {
+		if ( i % 8 >= 6 || ( (i / 8) % 4 == 3 ) ) {
+			OpOffsetTable[i] = 0;
+			continue;
+		}
+		Bitu chNum = (i / 8) * 3 + (i % 8) % 3;
+		//Make sure we use 16 and up for the 2nd range to match the chanoffset gap
+		if ( chNum >= 12 )
+			chNum += 16 - 12;
+		Bitu opNum = ( i % 8 ) / 3;
+		Channel* chan = NULL;
+		Bitu blah = (Bitu) ( &(chan->op[opNum]) );
+		OpOffsetTable[i] = ChanOffsetTable[ chNum ] + blah;
+	}
+#if 0
+	//Stupid checks if table's are correct
+	for ( Bitu i = 0; i < 18; i++ ) {
+		Bit32u find = (Bit16u)( &(chip->chan[ i ]) );
+		for ( Bitu c = 0; c < 32; c++ ) {
+			if ( ChanOffsetTable[c] == find ) {
+				find = 0;
+				break;
+			}
+		}
+		if ( find ) {
+			find = find;
+		}
+	}
+	for ( Bitu i = 0; i < 36; i++ ) {
+		Bit32u find = (Bit16u)( &(chip->chan[ i / 2 ].op[i % 2]) );
+		for ( Bitu c = 0; c < 64; c++ ) {
+			if ( OpOffsetTable[c] == find ) {
+				find = 0;
+				break;
+			}
+		}
+		if ( find ) {
+			find = find;
+		}
+	}
+#endif
+}
+
+/*
+
+Bit32u Handler::WriteAddr( Bit32u port, Bit8u val ) {
+	return chip.WriteAddr( port, val );
+
+}
+void Handler::WriteReg( Bit32u addr, Bit8u val ) {
+	chip.WriteReg( addr, val );
+}
+
+void Handler::Generate( MixerChannel* chan, Bitu samples ) {
+	Bit32s buffer[ 512 * 2 ];
+	if ( GCC_UNLIKELY(samples > 512) )
+		samples = 512;
+	if ( !chip.opl3Active ) {
+		chip.GenerateBlock2( samples, buffer );
+		chan->AddSamples_m32( samples, buffer );
+	} else {
+		chip.GenerateBlock3( samples, buffer );
+		chan->AddSamples_s32( samples, buffer );
+	}
+}
+
+void Handler::Init( Bitu rate ) {
+	InitTables();
+	chip.Setup( rate );
+}
+*/
+
--- /dev/null
+++ b/opl/dbopl.h
@@ -1,0 +1,203 @@
+/*
+ *  Copyright (C) 2002-2010  The DOSBox Team
+ *
+ *  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 <inttypes.h>
+
+//Use 8 handlers based on a small logatirmic wavetabe and an exponential table for volume
+#define WAVE_HANDLER	10
+//Use a logarithmic wavetable with an exponential table for volume
+#define WAVE_TABLELOG	11
+//Use a linear wavetable with a multiply table for volume
+#define WAVE_TABLEMUL	12
+
+//Select the type of wave generator routine
+#define DBOPL_WAVE WAVE_TABLEMUL
+
+typedef struct _Chip Chip;
+typedef struct _Operator Operator;
+typedef struct _Channel Channel;
+
+typedef uintptr_t       Bitu;
+typedef intptr_t        Bits;
+typedef uint32_t        Bit32u;
+typedef int32_t         Bit32s;
+typedef uint16_t        Bit16u;
+typedef int16_t         Bit16s;
+typedef uint8_t         Bit8u;
+typedef int8_t          Bit8s;
+
+#if (DBOPL_WAVE == WAVE_HANDLER)
+typedef Bits ( DB_FASTCALL *WaveHandler) ( Bitu i, Bitu volume );
+#endif
+
+#define DB_FASTCALL
+
+typedef Bits (*VolumeHandler)(Operator *self);
+typedef Channel* (*SynthHandler)(Channel *self, Chip* chip, Bit32u samples, Bit32s* output );
+
+//Different synth modes that can generate blocks of data
+typedef enum {
+	sm2AM,
+	sm2FM,
+	sm3AM,
+	sm3FM,
+	sm4Start,
+	sm3FMFM,
+	sm3AMFM,
+	sm3FMAM,
+	sm3AMAM,
+	sm6Start,
+	sm2Percussion,
+	sm3Percussion,
+} SynthMode;
+
+//Shifts for the values contained in chandata variable
+enum {
+	SHIFT_KSLBASE = 16,
+	SHIFT_KEYCODE = 24,
+};
+
+//Masks for operator 20 values
+enum {
+        MASK_KSR = 0x10,
+        MASK_SUSTAIN = 0x20,
+        MASK_VIBRATO = 0x40,
+        MASK_TREMOLO = 0x80,
+};
+
+typedef enum {
+        OFF,
+        RELEASE,
+        SUSTAIN,
+        DECAY,
+        ATTACK,
+} OperatorState;
+
+struct _Operator {
+	VolumeHandler volHandler;
+
+#if (DBOPL_WAVE == WAVE_HANDLER)
+	WaveHandler waveHandler;	//Routine that generate a wave 
+#else
+	Bit16s* waveBase;
+	Bit32u waveMask;
+	Bit32u waveStart;
+#endif
+	Bit32u waveIndex;			//WAVE_BITS shifted counter of the frequency index
+	Bit32u waveAdd;				//The base frequency without vibrato
+	Bit32u waveCurrent;			//waveAdd + vibratao
+
+	Bit32u chanData;			//Frequency/octave and derived data coming from whatever channel controls this
+	Bit32u freqMul;				//Scale channel frequency with this, TODO maybe remove?
+	Bit32u vibrato;				//Scaled up vibrato strength
+	Bit32s sustainLevel;		//When stopping at sustain level stop here
+	Bit32s totalLevel;			//totalLevel is added to every generated volume
+	Bit32u currentLevel;		//totalLevel + tremolo
+	Bit32s volume;				//The currently active volume
+	
+	Bit32u attackAdd;			//Timers for the different states of the envelope
+	Bit32u decayAdd;
+	Bit32u releaseAdd;
+	Bit32u rateIndex;			//Current position of the evenlope
+
+	Bit8u rateZero;				//Bits for the different states of the envelope having no changes
+	Bit8u keyOn;				//Bitmask of different values that can generate keyon
+	//Registers, also used to check for changes
+	Bit8u reg20, reg40, reg60, reg80, regE0;
+	//Active part of the envelope we're in
+	Bit8u state;
+	//0xff when tremolo is enabled
+	Bit8u tremoloMask;
+	//Strength of the vibrato
+	Bit8u vibStrength;
+	//Keep track of the calculated KSR so we can check for changes
+	Bit8u ksr;
+};
+
+struct _Channel {
+	Operator op[2];
+	SynthHandler synthHandler;
+	Bit32u chanData;		//Frequency/octave and derived values
+	Bit32s old[2];			//Old data for feedback
+
+	Bit8u feedback;			//Feedback shift
+	Bit8u regB0;			//Register values to check for changes
+	Bit8u regC0;
+	//This should correspond with reg104, bit 6 indicates a Percussion channel, bit 7 indicates a silent channel
+	Bit8u fourMask;
+	Bit8s maskLeft;		//Sign extended values for both channel's panning
+	Bit8s maskRight;
+
+};
+
+struct _Chip {
+	//This is used as the base counter for vibrato and tremolo
+	Bit32u lfoCounter;
+	Bit32u lfoAdd;
+	
+
+	Bit32u noiseCounter;
+	Bit32u noiseAdd;
+	Bit32u noiseValue;
+
+	//Frequency scales for the different multiplications
+	Bit32u freqMul[16];
+	//Rates for decay and release for rate of this chip
+	Bit32u linearRates[76];
+	//Best match attack rates for the rate of this chip
+	Bit32u attackRates[76];
+
+	//18 channels with 2 operators each
+	Channel chan[18];
+
+	Bit8u reg104;
+	Bit8u reg08;
+	Bit8u reg04;
+	Bit8u regBD;
+	Bit8u vibratoIndex;
+	Bit8u tremoloIndex;
+	Bit8s vibratoSign;
+	Bit8u vibratoShift;
+	Bit8u tremoloValue;
+	Bit8u vibratoStrength;
+	Bit8u tremoloStrength;
+	//Mask for allowed wave forms
+	Bit8u waveFormMask;
+	//0 or -1 when enabled
+	Bit8s opl3Active;
+
+};
+
+/*
+struct Handler : public Adlib::Handler {
+	DBOPL::Chip chip;
+	virtual Bit32u WriteAddr( Bit32u port, Bit8u val );
+	virtual void WriteReg( Bit32u addr, Bit8u val );
+	virtual void Generate( MixerChannel* chan, Bitu samples );
+	virtual void Init( Bitu rate );
+};
+*/
+
+
+void Chip__Setup(Chip *self, Bit32u rate );
+void DBOPL_InitTables( void );
+void Chip__Chip(Chip *self);
+void Chip__WriteReg(Chip *self, Bit32u reg, Bit8u val );
+void Chip__GenerateBlock2(Chip *self, Bitu total, Bit32s* output );
+
+
--- /dev/null
+++ b/opl/examples/.gitignore
@@ -1,0 +1,7 @@
+Makefile.in
+Makefile
+.deps
+droplay
+*.exe
+tags
+TAGS
--- /dev/null
+++ b/opl/examples/Makefile.am
@@ -1,0 +1,8 @@
+
+AM_CFLAGS = -I..
+
+noinst_PROGRAMS=droplay
+
+droplay_LDADD = ../libopl.a @LDFLAGS@ @SDL_LIBS@ @SDLMIXER_LIBS@
+droplay_SOURCES = droplay.c
+
--- /dev/null
+++ b/opl/examples/droplay.c
@@ -1,0 +1,217 @@
+// Emacs style mode select   -*- C++ -*- 
+//-----------------------------------------------------------------------------
+//
+// 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.
+//
+// DESCRIPTION:
+//     Demonstration program for OPL library to play back DRO
+//     format files.
+//
+//-----------------------------------------------------------------------------
+
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "SDL.h"
+
+#include "opl.h"
+
+#define HEADER_STRING "DBRAWOPL"
+#define ADLIB_PORT 0x388
+
+void WriteReg(unsigned int reg, unsigned int val)
+{
+    int i;
+
+    // This was recorded from an OPL2, but we are probably playing
+    // back on an OPL3, so we need to enable the original OPL2
+    // channels.  Doom does this already, but other games don't.
+
+    if ((reg & 0xf0) == OPL_REGS_FEEDBACK)
+    {
+        val |= 0x30;
+    }
+
+    OPL_WritePort(OPL_REGISTER_PORT, reg);
+
+    for (i=0; i<6; ++i)
+    {
+        OPL_ReadPort(OPL_REGISTER_PORT);
+    }
+
+    OPL_WritePort(OPL_DATA_PORT, val);
+
+    for (i=0; i<35; ++i)
+    {
+        OPL_ReadPort(OPL_REGISTER_PORT);
+    }
+}
+
+void ClearAllRegs(void)
+{
+    int i;
+
+    for (i=0; i<=0xff; ++i)
+    {
+	WriteReg(i, 0x00);
+    }
+}
+
+void Init(void)
+{
+    if (SDL_Init(SDL_INIT_TIMER) < 0)
+    {
+        fprintf(stderr, "Unable to initialise SDL timer\n");
+        exit(-1);
+    }
+
+    if (!OPL_Init(ADLIB_PORT))
+    {
+        fprintf(stderr, "Unable to initialise OPL layer\n");
+        exit(-1);
+    }
+}
+
+void Shutdown(void)
+{
+    OPL_Shutdown();
+}
+
+struct timer_data
+{
+    int running;
+    FILE *fstream;
+};
+
+void TimerCallback(void *data)
+{
+    struct timer_data *timer_data = data;
+    int delay;
+
+    if (!timer_data->running)
+    {
+        return;
+    }
+
+    // Read data until we must make a delay.
+
+    for (;;)
+    {
+        int reg, val;
+
+        // End of file?
+
+        if (feof(timer_data->fstream))
+        {
+            timer_data->running = 0;
+            return;
+        }
+
+        reg = fgetc(timer_data->fstream);
+        val = fgetc(timer_data->fstream);
+
+        // Register value of 0 or 1 indicates a delay.
+
+        if (reg == 0x00)
+        {
+            delay = val;
+            break;
+        }
+        else if (reg == 0x01)
+        {
+            val |= (fgetc(timer_data->fstream) << 8);
+            delay = val;
+            break;
+        }
+        else
+        {
+            WriteReg(reg, val);
+        }
+    }
+
+    // Schedule the next timer callback.
+
+    OPL_SetCallback(delay, TimerCallback, timer_data);
+}
+
+void PlayFile(char *filename)
+{
+    struct timer_data timer_data;
+    int running;
+    char buf[8];
+
+    timer_data.fstream = fopen(filename, "rb");
+
+    if (timer_data.fstream == NULL)
+    {
+        fprintf(stderr, "Failed to open %s\n", filename);
+        exit(-1);
+    }
+
+    if (fread(buf, 1, 8, timer_data.fstream) < 8)
+    {
+        fprintf(stderr, "failed to read raw OPL header\n");
+        exit(-1);
+    }
+
+    if (strncmp(buf, HEADER_STRING, 8) != 0)
+    {
+        fprintf(stderr, "Raw OPL header not found\n");
+        exit(-1);
+    }
+
+    fseek(timer_data.fstream, 28, SEEK_SET);
+    timer_data.running = 1;
+
+    // Start callback loop sequence.
+
+    OPL_SetCallback(0, TimerCallback, &timer_data);
+
+    // Sleep until the playback finishes.
+
+    do
+    {
+        OPL_Lock();
+        running = timer_data.running;
+        OPL_Unlock();
+
+        SDL_Delay(100);
+    } while (running);
+
+    fclose(timer_data.fstream);
+}
+
+int main(int argc, char *argv[])
+{
+    if (argc < 2)
+    {
+        printf("Usage: %s <filename>\n", argv[0]);
+        exit(-1);
+    }
+
+    Init();
+
+    PlayFile(argv[1]);
+
+    ClearAllRegs();
+    Shutdown();
+
+    return 0;
+}
+
--- /dev/null
+++ b/opl/ioperm_sys.c
@@ -1,0 +1,361 @@
+// Emacs style mode select   -*- C++ -*-
+//-----------------------------------------------------------------------------
+//
+// Copyright(C) 2002, 2003 Marcel Telka
+// 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.
+//
+// DESCRIPTION:
+//     Interface to the ioperm.sys driver, based on code from the
+//     Cygwin ioperm library.
+//
+//-----------------------------------------------------------------------------
+
+#ifdef _WIN32
+
+#include <stdio.h>
+
+#define WIN32_LEAN_AND_MEAN
+#include <windows.h>
+#include <winioctl.h>
+
+#include <errno.h>
+
+#include "ioperm_sys.h"
+
+#define IOPERM_FILE L"\\\\.\\ioperm"
+
+#define IOCTL_IOPERM               \
+    CTL_CODE(FILE_DEVICE_UNKNOWN, 0xA00, METHOD_BUFFERED, FILE_ANY_ACCESS)
+
+struct ioperm_data
+{
+    unsigned long from;
+    unsigned long num;
+    int turn_on;
+};
+
+// Function pointers for advapi32.dll.  This DLL does not exist on
+// Windows 9x, so they are dynamically loaded from the DLL at runtime.
+
+static SC_HANDLE WINAPI (*MyOpenSCManagerW)(wchar_t *lpMachineName,
+                                            wchar_t *lpDatabaseName,
+                                            DWORD dwDesiredAccess) = NULL;
+static SC_HANDLE WINAPI (*MyCreateServiceW)(SC_HANDLE hSCManager,
+                                            wchar_t *lpServiceName,
+                                            wchar_t *lpDisplayName,
+                                            DWORD dwDesiredAccess,
+                                            DWORD dwServiceType,
+                                            DWORD dwStartType,
+                                            DWORD dwErrorControl,
+                                            wchar_t *lpBinaryPathName,
+                                            wchar_t *lpLoadOrderGroup,
+                                            LPDWORD lpdwTagId,
+                                            wchar_t *lpDependencies,
+                                            wchar_t *lpServiceStartName,
+                                            wchar_t *lpPassword);
+static SC_HANDLE WINAPI (*MyOpenServiceW)(SC_HANDLE hSCManager,
+                                          wchar_t *lpServiceName,
+                                          DWORD dwDesiredAccess);
+static BOOL WINAPI (*MyStartServiceW)(SC_HANDLE hService,
+                                      DWORD dwNumServiceArgs,
+                                      wchar_t **lpServiceArgVectors);
+static BOOL WINAPI (*MyControlService)(SC_HANDLE hService,
+                                       DWORD dwControl,
+                                       LPSERVICE_STATUS lpServiceStatus);
+static BOOL WINAPI (*MyCloseServiceHandle)(SC_HANDLE hSCObject);
+static BOOL WINAPI (*MyDeleteService)(SC_HANDLE hService);
+
+static struct
+{
+    char *name;
+    void **fn;
+} dll_functions[] = {
+    { "OpenSCManagerW",     (void **) &MyOpenSCManagerW },
+    { "CreateServiceW",     (void **) &MyCreateServiceW },
+    { "OpenServiceW",       (void **) &MyOpenServiceW },
+    { "StartServiceW",      (void **) &MyStartServiceW },
+    { "ControlService",     (void **) &MyControlService },
+    { "CloseServiceHandle", (void **) &MyCloseServiceHandle },
+    { "DeleteService",      (void **) &MyDeleteService },
+};
+
+// Globals
+
+static SC_HANDLE scm = NULL;
+static SC_HANDLE svc = NULL;
+static int service_was_created = 0;
+static int service_was_started = 0;
+
+static int LoadLibraryPointers(void)
+{
+    HMODULE dll;
+    int i;
+
+    // Already loaded?
+
+    if (MyOpenSCManagerW != NULL)
+    {
+        return 1;
+    }
+
+    dll = LoadLibraryW(L"advapi32.dll");
+
+    if (dll == NULL)
+    {
+        fprintf(stderr, "LoadLibraryPointers: Failed to open advapi32.dll\n");
+        return 0;
+    }
+
+    for (i = 0; i < sizeof(dll_functions) / sizeof(*dll_functions); ++i)
+    {
+        *dll_functions[i].fn = GetProcAddress(dll, dll_functions[i].name);
+
+        if (*dll_functions[i].fn == NULL)
+        {
+            fprintf(stderr, "LoadLibraryPointers: Failed to get address "
+                            "for '%s'\n", dll_functions[i].name);
+            return 0;
+        }
+    }
+
+    return 1;
+}
+
+int IOperm_EnablePortRange(unsigned int from, unsigned int num, int turn_on)
+{
+    HANDLE h;
+    struct ioperm_data ioperm_data;
+    DWORD BytesReturned;
+    BOOL r;
+
+    h = CreateFileW(IOPERM_FILE, GENERIC_READ, 0, NULL,
+                    OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
+
+    if (h == INVALID_HANDLE_VALUE)
+    {
+        errno = ENODEV;
+        return -1;
+    }
+
+    ioperm_data.from = from;
+    ioperm_data.num = num;
+    ioperm_data.turn_on = turn_on;
+
+    r = DeviceIoControl(h, IOCTL_IOPERM,
+                        &ioperm_data, sizeof ioperm_data,
+                        NULL, 0,
+                        &BytesReturned, NULL);
+
+    if (!r)
+    {
+        errno = EPERM;
+    }
+
+    CloseHandle(h);
+
+    return r != 0;
+}
+
+// Load ioperm.sys driver.
+// Returns 1 for success, 0 for failure.
+// Remember to call IOperm_UninstallDriver to uninstall the driver later.
+
+int IOperm_InstallDriver(void)
+{
+    wchar_t driver_path[MAX_PATH];
+    int error;
+    int result = 1;
+
+    if (!LoadLibraryPointers())
+    {
+        return 0;
+    }
+
+    scm = MyOpenSCManagerW(NULL, NULL, SC_MANAGER_ALL_ACCESS);
+
+    if (scm == NULL)
+    {
+        error = GetLastError();
+        fprintf(stderr, "IOperm_InstallDriver: OpenSCManager failed (%i).\n",
+                        error);
+        return 0;
+    }
+
+    // Get the full path to the driver file.
+
+    GetFullPathNameW(L"ioperm.sys", MAX_PATH, driver_path, NULL);
+
+    // Create the service.
+
+    svc = MyCreateServiceW(scm,
+                           L"ioperm",
+                           L"ioperm support for Cygwin driver",
+                           SERVICE_ALL_ACCESS,
+                           SERVICE_KERNEL_DRIVER,
+                           SERVICE_AUTO_START,
+                           SERVICE_ERROR_NORMAL,
+                           driver_path,
+                           NULL,
+                           NULL,
+                           NULL,
+                           NULL,
+                           NULL);
+
+    if (svc == NULL)
+    {
+        error = GetLastError();
+
+        if (error != ERROR_SERVICE_EXISTS)
+        {
+            fprintf(stderr,
+                    "IOperm_InstallDriver: Failed to create service (%i).\n",
+                    error);
+        }
+        else
+        {
+            svc = MyOpenServiceW(scm, L"ioperm", SERVICE_ALL_ACCESS);
+
+            if (svc == NULL)
+            {
+                error = GetLastError();
+
+                fprintf(stderr,
+                        "IOperm_InstallDriver: Failed to open service (%i).\n",
+                        error);
+            }
+        }
+
+        if (svc == NULL)
+        {
+            MyCloseServiceHandle(scm);
+            return 0;
+        }
+    }
+    else
+    {
+        service_was_created = 1;
+    }
+
+    // Start the service.  If the service already existed, it might have
+    // already been running as well.
+
+    if (!MyStartServiceW(svc, 0, NULL))
+    {
+        error = GetLastError();
+
+        if (error != ERROR_SERVICE_ALREADY_RUNNING)
+        {
+            fprintf(stderr, "IOperm_InstallDriver: Failed to start service (%i).\n",
+                            error);
+
+            result = 0;
+        }
+        else
+        {
+            printf("IOperm_InstallDriver: ioperm driver already running.\n");
+        }
+    }
+    else
+    {
+        printf("IOperm_InstallDriver: ioperm driver installed.\n");
+        service_was_started = 1;
+    }
+
+    // If we failed to start the driver running, we need to clean up
+    // before finishing.
+
+    if (result == 0)
+    {
+        IOperm_UninstallDriver();
+    }
+
+    return result;
+}
+
+int IOperm_UninstallDriver(void)
+{
+    SERVICE_STATUS stat;
+    int result = 1;
+    int error;
+
+    // If we started the service, stop it.
+
+    if (service_was_started)
+    {
+        if (!MyControlService(svc, SERVICE_CONTROL_STOP, &stat))
+        {
+            error = GetLastError();
+
+            if (error == ERROR_SERVICE_NOT_ACTIVE)
+            {
+                fprintf(stderr,
+                        "IOperm_UninstallDriver: Service not active? (%i)\n",
+                        error);
+            }
+            else
+            {
+                fprintf(stderr,
+                        "IOperm_UninstallDriver: Failed to stop service (%i).\n",
+                        error);
+                result = 0;
+            }
+        }
+    }
+
+    // If we created the service, delete it.
+
+    if (service_was_created)
+    {
+        if (!MyDeleteService(svc))
+        {
+            error = GetLastError();
+
+            fprintf(stderr,
+                    "IOperm_UninstallDriver: DeleteService failed (%i).\n",
+                    error);
+
+            result = 0;
+        }
+        else if (service_was_started)
+        {
+            printf("IOperm_UnInstallDriver: ioperm driver uninstalled.\n");
+        }
+    }
+
+    // Close handles.
+
+    if (svc != NULL)
+    {
+        MyCloseServiceHandle(svc);
+        svc = NULL;
+    }
+
+    if (scm != NULL)
+    {
+        MyCloseServiceHandle(scm);
+        scm = NULL;
+    }
+
+    service_was_created = 0;
+    service_was_started = 0;
+
+    return result;
+}
+
+#endif /* #ifndef _WIN32 */
+
--- /dev/null
+++ b/opl/ioperm_sys.h
@@ -1,0 +1,36 @@
+// Emacs style mode select   -*- C++ -*-
+//-----------------------------------------------------------------------------
+//
+// Copyright(C) 2002, 2003 Marcel Telka
+// 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.
+//
+// DESCRIPTION:
+//     Interface to the ioperm.sys driver, based on code from the
+//     Cygwin ioperm library.
+//
+//-----------------------------------------------------------------------------
+
+#ifndef IOPERM_SYS_H
+#define IOPERM_SYS_H
+
+int IOperm_EnablePortRange(unsigned int from, unsigned int num, int turn_on);
+int IOperm_InstallDriver(void);
+int IOperm_UninstallDriver(void);
+
+#endif /* #ifndef IOPERM_SYS_H */
+
--- /dev/null
+++ b/opl/opl.c
@@ -1,0 +1,466 @@
+// Emacs style mode select   -*- C++ -*-
+//-----------------------------------------------------------------------------
+//
+// 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.
+//
+// DESCRIPTION:
+//     OPL interface.
+//
+//-----------------------------------------------------------------------------
+
+#include "config.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#ifdef _WIN32_WCE
+#include "libc_wince.h"
+#endif
+
+#include "SDL.h"
+
+#include "opl.h"
+#include "opl_internal.h"
+
+//#define OPL_DEBUG_TRACE
+
+#ifdef HAVE_IOPERM
+extern opl_driver_t opl_linux_driver;
+#endif
+#if defined(HAVE_LIBI386) || defined(HAVE_LIBAMD64)
+extern opl_driver_t opl_openbsd_driver;
+#endif
+#ifdef _WIN32
+extern opl_driver_t opl_win32_driver;
+#endif
+extern opl_driver_t opl_sdl_driver;
+
+static opl_driver_t *drivers[] =
+{
+#ifdef HAVE_IOPERM
+    &opl_linux_driver,
+#endif
+#if defined(HAVE_LIBI386) || defined(HAVE_LIBAMD64)
+    &opl_openbsd_driver,
+#endif
+#ifdef _WIN32
+    &opl_win32_driver,
+#endif
+    &opl_sdl_driver,
+    NULL
+};
+
+static opl_driver_t *driver = NULL;
+static int init_stage_reg_writes = 1;
+
+unsigned int opl_sample_rate = 22050;
+
+//
+// Init/shutdown code.
+//
+
+// Initialize the specified driver and detect an OPL chip.  Returns
+// true if an OPL is detected.
+
+static int InitDriver(opl_driver_t *_driver, unsigned int port_base)
+{
+    // Initialize the driver.
+
+    if (!_driver->init_func(port_base))
+    {
+        return 0;
+    }
+
+    // The driver was initialized okay, so we now have somewhere
+    // to write to.  It doesn't mean there's an OPL chip there,
+    // though.  Perform the detection sequence to make sure.
+    // (it's done twice, like how Doom does it).
+
+    driver = _driver;
+    init_stage_reg_writes = 1;
+
+    if (!OPL_Detect() || !OPL_Detect())
+    {
+        printf("OPL_Init: No OPL detected using '%s' driver.\n", _driver->name);
+        _driver->shutdown_func();
+        driver = NULL;
+        return 0;
+    }
+
+    // Initialize all registers.
+
+    OPL_InitRegisters();
+
+    init_stage_reg_writes = 0;
+
+    printf("OPL_Init: Using driver '%s'.\n", driver->name);
+
+    return 1;
+}
+
+// Find a driver automatically by trying each in the list.
+
+static int AutoSelectDriver(unsigned int port_base)
+{
+    int i;
+
+    for (i=0; drivers[i] != NULL; ++i)
+    {
+        if (InitDriver(drivers[i], port_base))
+        {
+            return 1;
+        }
+    }
+
+    printf("OPL_Init: Failed to find a working driver.\n");
+
+    return 0;
+}
+
+// Initialize the OPL library.  Returns true if initialized
+// successfully.
+
+int OPL_Init(unsigned int port_base)
+{
+    char *driver_name;
+    int i;
+
+    driver_name = getenv("OPL_DRIVER");
+
+    if (driver_name != NULL)
+    {
+        // Search the list until we find the driver with this name.
+
+        for (i=0; drivers[i] != NULL; ++i)
+        {
+            if (!strcmp(driver_name, drivers[i]->name))
+            {
+                if (InitDriver(drivers[i], port_base))
+                {
+                    return 1;
+                }
+                else
+                {
+                    printf("OPL_Init: Failed to initialize "
+                           "driver: '%s'.\n", driver_name);
+                    return 0;
+                }
+            }
+        }
+
+        printf("OPL_Init: unknown driver: '%s'.\n", driver_name);
+
+        return 0;
+    }
+    else
+    {
+        return AutoSelectDriver(port_base);
+    }
+}
+
+// Shut down the OPL library.
+
+void OPL_Shutdown(void)
+{
+    if (driver != NULL)
+    {
+        driver->shutdown_func();
+        driver = NULL;
+    }
+}
+
+// Set the sample rate used for software OPL emulation.
+
+void OPL_SetSampleRate(unsigned int rate)
+{
+    opl_sample_rate = rate;
+}
+
+void OPL_WritePort(opl_port_t port, unsigned int value)
+{
+    if (driver != NULL)
+    {
+#ifdef OPL_DEBUG_TRACE
+        printf("OPL_write: %i, %x\n", port, value);
+        fflush(stdout);
+#endif
+        driver->write_port_func(port, value);
+    }
+}
+
+unsigned int OPL_ReadPort(opl_port_t port)
+{
+    if (driver != NULL)
+    {
+        unsigned int result;
+
+#ifdef OPL_DEBUG_TRACE
+        printf("OPL_read: %i...\n", port);
+        fflush(stdout);
+#endif
+
+        result = driver->read_port_func(port);
+
+#ifdef OPL_DEBUG_TRACE
+        printf("OPL_read: %i -> %x\n", port, result);
+        fflush(stdout);
+#endif
+
+        return result;
+    }
+    else
+    {
+        return 0;
+    }
+}
+
+//
+// Higher-level functions, based on the lower-level functions above
+// (register write, etc).
+//
+
+unsigned int OPL_ReadStatus(void)
+{
+    return OPL_ReadPort(OPL_REGISTER_PORT);
+}
+
+// Write an OPL register value
+
+void OPL_WriteRegister(int reg, int value)
+{
+    int i;
+
+    OPL_WritePort(OPL_REGISTER_PORT, reg);
+
+    // For timing, read the register port six times after writing the
+    // register number to cause the appropriate delay
+
+    for (i=0; i<6; ++i)
+    {
+        // An oddity of the Doom OPL code: at startup initialization,
+        // the spacing here is performed by reading from the register
+        // port; after initialization, the data port is read, instead.
+
+        if (init_stage_reg_writes)
+        {
+            OPL_ReadPort(OPL_REGISTER_PORT);
+        }
+        else
+        {
+            OPL_ReadPort(OPL_DATA_PORT);
+        }
+    }
+
+    OPL_WritePort(OPL_DATA_PORT, value);
+
+    // Read the register port 24 times after writing the value to
+    // cause the appropriate delay
+
+    for (i=0; i<24; ++i)
+    {
+        OPL_ReadStatus();
+    }
+}
+
+// Detect the presence of an OPL chip
+
+int OPL_Detect(void)
+{
+    int result1, result2;
+    int i;
+
+    // Reset both timers:
+    OPL_WriteRegister(OPL_REG_TIMER_CTRL, 0x60);
+
+    // Enable interrupts:
+    OPL_WriteRegister(OPL_REG_TIMER_CTRL, 0x80);
+
+    // Read status
+    result1 = OPL_ReadStatus();
+
+    // Set timer:
+    OPL_WriteRegister(OPL_REG_TIMER1, 0xff);
+
+    // Start timer 1:
+    OPL_WriteRegister(OPL_REG_TIMER_CTRL, 0x21);
+
+    // Wait for 80 microseconds
+    // This is how Doom does it:
+
+    for (i=0; i<200; ++i)
+    {
+        OPL_ReadStatus();
+    }
+
+    OPL_Delay(1);
+
+    // Read status
+    result2 = OPL_ReadStatus();
+
+    // Reset both timers:
+    OPL_WriteRegister(OPL_REG_TIMER_CTRL, 0x60);
+
+    // Enable interrupts:
+    OPL_WriteRegister(OPL_REG_TIMER_CTRL, 0x80);
+
+    return (result1 & 0xe0) == 0x00
+        && (result2 & 0xe0) == 0xc0;
+}
+
+// Initialize registers on startup
+
+void OPL_InitRegisters(void)
+{
+    int r;
+
+    // Initialize level registers
+
+    for (r=OPL_REGS_LEVEL; r <= OPL_REGS_LEVEL + OPL_NUM_OPERATORS; ++r)
+    {
+        OPL_WriteRegister(r, 0x3f);
+    }
+
+    // Initialize other registers
+    // These two loops write to registers that actually don't exist,
+    // but this is what Doom does ...
+    // Similarly, the <= is also intenational.
+
+    for (r=OPL_REGS_ATTACK; r <= OPL_REGS_WAVEFORM + OPL_NUM_OPERATORS; ++r)
+    {
+        OPL_WriteRegister(r, 0x00);
+    }
+
+    // More registers ...
+
+    for (r=1; r < OPL_REGS_LEVEL; ++r)
+    {
+        OPL_WriteRegister(r, 0x00);
+    }
+
+    // Re-initialize the low registers:
+
+    // Reset both timers and enable interrupts:
+    OPL_WriteRegister(OPL_REG_TIMER_CTRL,      0x60);
+    OPL_WriteRegister(OPL_REG_TIMER_CTRL,      0x80);
+
+    // "Allow FM chips to control the waveform of each operator":
+    OPL_WriteRegister(OPL_REG_WAVEFORM_ENABLE, 0x20);
+
+    // Keyboard split point on (?)
+    OPL_WriteRegister(OPL_REG_FM_MODE,         0x40);
+}
+
+//
+// Timer functions.
+//
+
+void OPL_SetCallback(unsigned int ms, opl_callback_t callback, void *data)
+{
+    if (driver != NULL)
+    {
+        driver->set_callback_func(ms, callback, data);
+    }
+}
+
+void OPL_ClearCallbacks(void)
+{
+    if (driver != NULL)
+    {
+        driver->clear_callbacks_func();
+    }
+}
+
+void OPL_Lock(void)
+{
+    if (driver != NULL)
+    {
+        driver->lock_func();
+    }
+}
+
+void OPL_Unlock(void)
+{
+    if (driver != NULL)
+    {
+        driver->unlock_func();
+    }
+}
+
+typedef struct
+{
+    int finished;
+
+    SDL_mutex *mutex;
+    SDL_cond *cond;
+} delay_data_t;
+
+static void DelayCallback(void *_delay_data)
+{
+    delay_data_t *delay_data = _delay_data;
+
+    SDL_LockMutex(delay_data->mutex);
+    delay_data->finished = 1;
+
+    SDL_CondSignal(delay_data->cond);
+
+    SDL_UnlockMutex(delay_data->mutex);
+}
+
+void OPL_Delay(unsigned int ms)
+{
+    delay_data_t delay_data;
+
+    if (driver == NULL)
+    {
+        return;
+    }
+
+    // Create a callback that will signal this thread after the
+    // specified time.
+
+    delay_data.finished = 0;
+    delay_data.mutex = SDL_CreateMutex();
+    delay_data.cond = SDL_CreateCond();
+
+    OPL_SetCallback(ms, DelayCallback, &delay_data);
+
+    // Wait until the callback is invoked.
+
+    SDL_LockMutex(delay_data.mutex);
+
+    while (!delay_data.finished)
+    {
+        SDL_CondWait(delay_data.cond, delay_data.mutex);
+    }
+
+    SDL_UnlockMutex(delay_data.mutex);
+
+    // Clean up.
+
+    SDL_DestroyMutex(delay_data.mutex);
+    SDL_DestroyCond(delay_data.cond);
+}
+
+void OPL_SetPaused(int paused)
+{
+    if (driver != NULL)
+    {
+        driver->set_paused_func(paused);
+    }
+}
+
--- /dev/null
+++ b/opl/opl.h
@@ -1,0 +1,137 @@
+// Emacs style mode select   -*- C++ -*- 
+//-----------------------------------------------------------------------------
+//
+// 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.
+//
+// DESCRIPTION:
+//     OPL interface.
+//
+//-----------------------------------------------------------------------------
+
+
+#ifndef OPL_OPL_H
+#define OPL_OPL_H
+
+typedef void (*opl_callback_t)(void *data);
+
+typedef enum
+{
+    OPL_REGISTER_PORT = 0,
+    OPL_DATA_PORT = 1
+} opl_port_t;
+
+#define OPL_NUM_OPERATORS   21
+#define OPL_NUM_VOICES      9
+
+#define OPL_REG_WAVEFORM_ENABLE   0x01
+#define OPL_REG_TIMER1            0x02
+#define OPL_REG_TIMER2            0x03
+#define OPL_REG_TIMER_CTRL        0x04
+#define OPL_REG_FM_MODE           0x08
+
+// Operator registers (21 of each):
+
+#define OPL_REGS_TREMOLO          0x20
+#define OPL_REGS_LEVEL            0x40
+#define OPL_REGS_ATTACK           0x60
+#define OPL_REGS_SUSTAIN          0x80
+#define OPL_REGS_WAVEFORM         0xE0
+
+// Voice registers (9 of each):
+
+#define OPL_REGS_FREQ_1           0xA0
+#define OPL_REGS_FREQ_2           0xB0
+#define OPL_REGS_FEEDBACK         0xC0
+
+//
+// Low-level functions.
+//
+
+// Initialize the OPL subsystem.
+
+int OPL_Init(unsigned int port_base);
+
+// Shut down the OPL subsystem.
+
+void OPL_Shutdown(void);
+
+// Set the sample rate used for software emulation.
+
+void OPL_SetSampleRate(unsigned int rate);
+
+// Write to one of the OPL I/O ports:
+
+void OPL_WritePort(opl_port_t port, unsigned int value);
+
+// Read from one of the OPL I/O ports:
+
+unsigned int OPL_ReadPort(opl_port_t port);
+
+//
+// Higher-level functions.
+//
+
+// Read the cuurrent status byte of the OPL chip.
+
+unsigned int OPL_ReadStatus(void);
+
+// Write to an OPL register.
+
+void OPL_WriteRegister(int reg, int value);
+
+// Perform a detection sequence to determine that an
+// OPL chip is present.
+
+int OPL_Detect(void);
+
+// Initialize all registers, performed on startup.
+
+void OPL_InitRegisters(void);
+
+//
+// Timer callback functions.
+//
+
+// Set a timer callback.  After the specified number of milliseconds
+// have elapsed, the callback will be invoked.
+
+void OPL_SetCallback(unsigned int ms, opl_callback_t callback, void *data);
+
+// Clear all OPL callbacks that have been set.
+
+void OPL_ClearCallbacks(void);
+
+// Begin critical section, during which, OPL callbacks will not be
+// invoked.
+
+void OPL_Lock(void);
+
+// End critical section.
+
+void OPL_Unlock(void);
+
+// Block until the specified number of milliseconds have elapsed.
+
+void OPL_Delay(unsigned int ms);
+
+// Pause the OPL callbacks.
+
+void OPL_SetPaused(int paused);
+
+#endif
+
--- /dev/null
+++ b/opl/opl_internal.h
@@ -1,0 +1,64 @@
+// Emacs style mode select   -*- C++ -*- 
+//-----------------------------------------------------------------------------
+//
+// 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.
+//
+// DESCRIPTION:
+//     OPL internal interface.
+//
+//-----------------------------------------------------------------------------
+
+
+#ifndef OPL_INTERNAL_H
+#define OPL_INTERNAL_H
+
+#include "opl.h"
+
+typedef int (*opl_init_func)(unsigned int port_base);
+typedef void (*opl_shutdown_func)(void);
+typedef unsigned int (*opl_read_port_func)(opl_port_t port);
+typedef void (*opl_write_port_func)(opl_port_t port, unsigned int value);
+typedef void (*opl_set_callback_func)(unsigned int ms,
+                                      opl_callback_t callback,
+                                      void *data);
+typedef void (*opl_clear_callbacks_func)(void);
+typedef void (*opl_lock_func)(void);
+typedef void (*opl_unlock_func)(void);
+typedef void (*opl_set_paused_func)(int paused);
+
+typedef struct
+{
+    char *name;
+
+    opl_init_func init_func;
+    opl_shutdown_func shutdown_func;
+    opl_read_port_func read_port_func;
+    opl_write_port_func write_port_func;
+    opl_set_callback_func set_callback_func;
+    opl_clear_callbacks_func clear_callbacks_func;
+    opl_lock_func lock_func;
+    opl_unlock_func unlock_func;
+    opl_set_paused_func set_paused_func;
+} opl_driver_t;
+
+// Sample rate to use when doing software emulation.
+
+extern unsigned int opl_sample_rate;
+
+#endif /* #ifndef OPL_INTERNAL_H */
+
--- /dev/null
+++ b/opl/opl_linux.c
@@ -1,0 +1,110 @@
+// Emacs style mode select   -*- C++ -*- 
+//-----------------------------------------------------------------------------
+//
+// 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.
+//
+// DESCRIPTION:
+//     OPL Linux interface.
+//
+//-----------------------------------------------------------------------------
+
+#include "config.h"
+
+#ifdef HAVE_IOPERM
+
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include <sys/io.h>
+
+#include "opl.h"
+#include "opl_internal.h"
+#include "opl_timer.h"
+
+static unsigned int opl_port_base;
+
+static int OPL_Linux_Init(unsigned int port_base)
+{
+    // Try to get permissions:
+
+    if (ioperm(port_base, 2, 1) < 0)
+    {
+        fprintf(stderr, "Failed to get I/O port permissions for 0x%x: %s\n",
+                        port_base, strerror(errno));
+
+        if (errno == EPERM)
+        {
+            fprintf(stderr,
+                    "\tYou may need to run the program as root in order\n"
+                    "\tto acquire I/O port permissions for OPL MIDI playback.\n");
+        }
+
+        return 0;
+    }
+
+    opl_port_base = port_base;
+
+    // Start callback thread
+
+    if (!OPL_Timer_StartThread())
+    {
+        ioperm(port_base, 2, 0);
+        return 0;
+    }
+
+    return 1;
+}
+
+static void OPL_Linux_Shutdown(void)
+{
+    // Stop callback thread
+
+    OPL_Timer_StopThread();
+
+    // Release permissions
+
+    ioperm(opl_port_base, 2, 0);
+}
+
+static unsigned int OPL_Linux_PortRead(opl_port_t port)
+{
+    return inb(opl_port_base + port);
+}
+
+static void OPL_Linux_PortWrite(opl_port_t port, unsigned int value)
+{
+    outb(value, opl_port_base + port);
+}
+
+opl_driver_t opl_linux_driver =
+{
+    "Linux",
+    OPL_Linux_Init,
+    OPL_Linux_Shutdown,
+    OPL_Linux_PortRead,
+    OPL_Linux_PortWrite,
+    OPL_Timer_SetCallback,
+    OPL_Timer_ClearCallbacks,
+    OPL_Timer_Lock,
+    OPL_Timer_Unlock,
+    OPL_Timer_SetPaused
+};
+
+#endif /* #ifdef HAVE_IOPERM */
+
--- /dev/null
+++ b/opl/opl_obsd.c
@@ -1,0 +1,125 @@
+// Emacs style mode select   -*- C++ -*- 
+//-----------------------------------------------------------------------------
+//
+// 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.
+//
+// DESCRIPTION:
+//     OPL OpenBSD interface (also NetBSD)
+//
+//-----------------------------------------------------------------------------
+
+#include "config.h"
+
+// OpenBSD has a i386_iopl on i386 and amd64_iopl on x86_64,
+// even though they do the same thing.  Take care of this
+// here, and map set_iopl to point to the appropriate name.
+
+#if defined(HAVE_LIBI386)
+
+#include <sys/types.h>
+#include <machine/sysarch.h>
+#include <i386/pio.h>
+#define set_iopl i386_iopl
+
+#elif defined(HAVE_LIBAMD64)
+
+#include <sys/types.h>
+#include <machine/sysarch.h>
+#include <amd64/pio.h>
+#define set_iopl amd64_iopl
+
+#else
+#define NO_OBSD_DRIVER
+#endif
+
+// If the above succeeded, proceed with the rest.
+
+#ifndef NO_OBSD_DRIVER
+
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+
+#include "opl.h"
+#include "opl_internal.h"
+#include "opl_timer.h"
+
+static unsigned int opl_port_base;
+
+static int OPL_OpenBSD_Init(unsigned int port_base)
+{
+    // Try to get permissions:
+
+    if (set_iopl(3) < 0)
+    {
+        fprintf(stderr, "Failed to get raise I/O privilege level: "
+                        "check that you are running as root.\n");
+        return 0;
+    }
+
+    opl_port_base = port_base;
+
+    // Start callback thread
+
+    if (!OPL_Timer_StartThread())
+    {
+        set_iopl(0);
+        return 0;
+    }
+
+    return 1;
+}
+
+static void OPL_OpenBSD_Shutdown(void)
+{
+    // Stop callback thread
+
+    OPL_Timer_StopThread();
+
+    // Release I/O port permissions:
+
+    set_iopl(0);
+}
+
+static unsigned int OPL_OpenBSD_PortRead(opl_port_t port)
+{
+    return inb(opl_port_base + port);
+}
+
+static void OPL_OpenBSD_PortWrite(opl_port_t port, unsigned int value)
+{
+    outb(opl_port_base + port, value);
+}
+
+opl_driver_t opl_openbsd_driver =
+{
+    "OpenBSD",
+    OPL_OpenBSD_Init,
+    OPL_OpenBSD_Shutdown,
+    OPL_OpenBSD_PortRead,
+    OPL_OpenBSD_PortWrite,
+    OPL_Timer_SetCallback,
+    OPL_Timer_ClearCallbacks,
+    OPL_Timer_Lock,
+    OPL_Timer_Unlock,
+    OPL_Timer_SetPaused
+};
+
+#endif /* #ifndef NO_OBSD_DRIVER */
+
--- /dev/null
+++ b/opl/opl_queue.c
@@ -1,0 +1,280 @@
+// Emacs style mode select   -*- C++ -*- 
+//-----------------------------------------------------------------------------
+//
+// 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.
+//
+// DESCRIPTION:
+//     Queue of waiting callbacks, stored in a binary min heap, so that we
+//     can always get the first callback.
+//
+//-----------------------------------------------------------------------------
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "opl_queue.h"
+
+#define MAX_OPL_QUEUE 64
+
+typedef struct
+{
+    opl_callback_t callback;
+    void *data;
+    unsigned int time;
+} opl_queue_entry_t;
+
+struct opl_callback_queue_s
+{
+    opl_queue_entry_t entries[MAX_OPL_QUEUE];
+    unsigned int num_entries;
+};
+
+opl_callback_queue_t *OPL_Queue_Create(void)
+{
+    opl_callback_queue_t *queue;
+
+    queue = malloc(sizeof(opl_callback_queue_t));
+    queue->num_entries = 0;
+
+    return queue;
+}
+
+void OPL_Queue_Destroy(opl_callback_queue_t *queue)
+{
+    free(queue);
+}
+
+int OPL_Queue_IsEmpty(opl_callback_queue_t *queue)
+{
+    return queue->num_entries == 0;
+}
+
+void OPL_Queue_Clear(opl_callback_queue_t *queue)
+{
+    queue->num_entries = 0;
+}
+
+void OPL_Queue_Push(opl_callback_queue_t *queue,
+                    opl_callback_t callback, void *data,
+                    unsigned int time)
+{
+    int entry_id;
+    int parent_id;
+
+    if (queue->num_entries >= MAX_OPL_QUEUE)
+    {
+        fprintf(stderr, "OPL_Queue_Push: Exceeded maximum callbacks\n");
+        return;
+    }
+
+    // Add to last queue entry.
+
+    entry_id = queue->num_entries;
+    ++queue->num_entries;
+
+    // Shift existing entries down in the heap.
+
+    while (entry_id > 0)
+    {
+        parent_id = (entry_id - 1) / 2;
+
+        // Is the heap condition satisfied?
+
+        if (time >= queue->entries[parent_id].time)
+        {
+            break;
+        }
+
+        // Move the existing entry down in the heap.
+
+        memcpy(&queue->entries[entry_id],
+               &queue->entries[parent_id],
+               sizeof(opl_queue_entry_t));
+
+        // Advance to the parent.
+
+        entry_id = parent_id;
+    }
+
+    // Insert new callback data.
+
+    queue->entries[entry_id].callback = callback;
+    queue->entries[entry_id].data = data;
+    queue->entries[entry_id].time = time;
+}
+
+int OPL_Queue_Pop(opl_callback_queue_t *queue,
+                  opl_callback_t *callback, void **data)
+{
+    opl_queue_entry_t *entry;
+    int child1, child2;
+    int i, next_i;
+
+    // Empty?
+
+    if (queue->num_entries <= 0)
+    {
+        return 0;
+    }
+
+    // Store the result:
+
+    *callback = queue->entries[0].callback;
+    *data = queue->entries[0].data;
+
+    // Decrease the heap size, and keep pointer to the last entry in
+    // the heap, which must now be percolated down from the top.
+
+    --queue->num_entries;
+    entry = &queue->entries[queue->num_entries];
+
+    // Percolate down.
+
+    i = 0;
+
+    for (;;)
+    {
+        child1 = i * 2 + 1;
+        child2 = i * 2 + 2;
+
+        if (child1 < queue->num_entries
+         && queue->entries[child1].time < entry->time)
+        {
+            // Left child is less than entry.
+            // Use the minimum of left and right children.
+
+            if (child2 < queue->num_entries
+             && queue->entries[child2].time < queue->entries[child1].time)
+            {
+                next_i = child2;
+            }
+            else
+            {
+                next_i = child1;
+            }
+        }
+        else if (child2 < queue->num_entries
+              && queue->entries[child2].time < entry->time)
+        {
+            // Right child is less than entry.  Go down the right side.
+
+            next_i = child2;
+        }
+        else
+        {
+            // Finished percolating.
+            break;
+        }
+
+        // Percolate the next value up and advance.
+
+        memcpy(&queue->entries[i],
+               &queue->entries[next_i],
+               sizeof(opl_queue_entry_t));
+        i = next_i;
+    }
+
+    // Store the old last-entry at its new position.
+
+    memcpy(&queue->entries[i], entry, sizeof(opl_queue_entry_t));
+
+    return 1;
+}
+
+unsigned int OPL_Queue_Peek(opl_callback_queue_t *queue)
+{
+    if (queue->num_entries > 0)
+    {
+        return queue->entries[0].time;
+    }
+    else
+    {
+        return 0;
+    }
+}
+
+#ifdef TEST
+
+#include <assert.h>
+
+static void PrintQueueNode(opl_callback_queue_t *queue, int node, int depth)
+{
+    int i;
+
+    if (node >= queue->num_entries)
+    {
+        return;
+    }
+
+    for (i=0; i<depth * 3; ++i)
+    {
+        printf(" ");
+    }
+
+    printf("%i\n", queue->entries[node].time);
+
+    PrintQueueNode(queue, node * 2 + 1, depth + 1);
+    PrintQueueNode(queue, node * 2 + 2, depth + 1);
+}
+
+static void PrintQueue(opl_callback_queue_t *queue)
+{
+    PrintQueueNode(queue, 0, 0);
+}
+
+int main()
+{
+    opl_callback_queue_t *queue;
+    int iteration;
+
+    queue = OPL_Queue_Create();
+
+    for (iteration=0; iteration<5000; ++iteration)
+    {
+        opl_callback_t callback;
+        void *data;
+        unsigned int time;
+        unsigned int newtime;
+        int i;
+
+        for (i=0; i<MAX_OPL_QUEUE; ++i)
+        {
+            time = rand() % 0x10000;
+            OPL_Queue_Push(queue, NULL, NULL, time);
+        }
+
+        time = 0;
+
+        for (i=0; i<MAX_OPL_QUEUE; ++i)
+        {
+            assert(!OPL_Queue_IsEmpty(queue));
+            newtime = OPL_Queue_Peek(queue);
+            assert(OPL_Queue_Pop(queue, &callback, &data));
+
+            assert(newtime >= time);
+            time = newtime;
+        }
+
+        assert(OPL_Queue_IsEmpty(queue));
+        assert(!OPL_Queue_Pop(queue, &callback, &data));
+    }
+}
+
+#endif
+
--- /dev/null
+++ b/opl/opl_queue.h
@@ -1,0 +1,45 @@
+// Emacs style mode select   -*- C++ -*- 
+//-----------------------------------------------------------------------------
+//
+// 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.
+//
+// DESCRIPTION:
+//     OPL callback queue.
+//
+//-----------------------------------------------------------------------------
+
+#ifndef OPL_QUEUE_H
+#define OPL_QUEUE_H
+
+#include "opl.h"
+
+typedef struct opl_callback_queue_s opl_callback_queue_t;
+
+opl_callback_queue_t *OPL_Queue_Create(void);
+int OPL_Queue_IsEmpty(opl_callback_queue_t *queue);
+void OPL_Queue_Clear(opl_callback_queue_t *queue);
+void OPL_Queue_Destroy(opl_callback_queue_t *queue);
+void OPL_Queue_Push(opl_callback_queue_t *queue,
+                    opl_callback_t callback, void *data,
+                    unsigned int time);
+int OPL_Queue_Pop(opl_callback_queue_t *queue,
+                  opl_callback_t *callback, void **data);
+unsigned int OPL_Queue_Peek(opl_callback_queue_t *queue);
+
+#endif /* #ifndef OPL_QUEUE_H */
+
--- /dev/null
+++ b/opl/opl_sdl.c
@@ -1,0 +1,510 @@
+// Emacs style mode select   -*- C++ -*- 
+//-----------------------------------------------------------------------------
+//
+// 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.
+//
+// DESCRIPTION:
+//     OPL SDL interface.
+//
+//-----------------------------------------------------------------------------
+
+#include "config.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <assert.h>
+
+#include "SDL.h"
+#include "SDL_mixer.h"
+
+#include "dbopl.h"
+
+#include "opl.h"
+#include "opl_internal.h"
+
+#include "opl_queue.h"
+
+#define MAX_SOUND_SLICE_TIME 100 /* ms */
+
+typedef struct
+{
+    unsigned int rate;        // Number of times the timer is advanced per sec.
+    unsigned int enabled;     // Non-zero if timer is enabled.
+    unsigned int value;       // Last value that was set.
+    unsigned int expire_time; // Calculated time that timer will expire.
+} opl_timer_t;
+
+// When the callback mutex is locked using OPL_Lock, callback functions
+// are not invoked.
+
+static SDL_mutex *callback_mutex = NULL;
+
+// Queue of callbacks waiting to be invoked.
+
+static opl_callback_queue_t *callback_queue;
+
+// Mutex used to control access to the callback queue.
+
+static SDL_mutex *callback_queue_mutex = NULL;
+
+// Current time, in number of samples since startup:
+
+static int current_time;
+
+// If non-zero, playback is currently paused.
+
+static int opl_sdl_paused;
+
+// Time offset (in samples) due to the fact that callbacks
+// were previously paused.
+
+static unsigned int pause_offset;
+
+// OPL software emulator structure.
+
+static Chip opl_chip;
+
+// Temporary mixing buffer used by the mixing callback.
+
+static int32_t *mix_buffer = NULL;
+
+// Register number that was written.
+
+static int register_num = 0;
+
+// Timers; DBOPL does not do timer stuff itself.
+
+static opl_timer_t timer1 = { 12500, 0, 0, 0 };
+static opl_timer_t timer2 = { 3125, 0, 0, 0 };
+
+// SDL parameters.
+
+static int sdl_was_initialized = 0;
+static int mixing_freq, mixing_channels;
+static Uint16 mixing_format;
+
+static int SDLIsInitialized(void)
+{
+    int freq, channels;
+    Uint16 format;
+
+    return Mix_QuerySpec(&freq, &format, &channels);
+}
+
+// Advance time by the specified number of samples, invoking any
+// callback functions as appropriate.
+
+static void AdvanceTime(unsigned int nsamples)
+{
+    opl_callback_t callback;
+    void *callback_data;
+
+    SDL_LockMutex(callback_queue_mutex);
+
+    // Advance time.
+
+    current_time += nsamples;
+
+    if (opl_sdl_paused)
+    {
+        pause_offset += nsamples;
+    }
+
+    // Are there callbacks to invoke now?  Keep invoking them
+    // until there are none more left.
+
+    while (!OPL_Queue_IsEmpty(callback_queue)
+        && current_time >= OPL_Queue_Peek(callback_queue) + pause_offset)
+    {
+        // Pop the callback from the queue to invoke it.
+
+        if (!OPL_Queue_Pop(callback_queue, &callback, &callback_data))
+        {
+            break;
+        }
+
+        // The mutex stuff here is a bit complicated.  We must
+        // hold callback_mutex when we invoke the callback (so that
+        // the control thread can use OPL_Lock() to prevent callbacks
+        // from being invoked), but we must not be holding
+        // callback_queue_mutex, as the callback must be able to
+        // call OPL_SetCallback to schedule new callbacks.
+
+        SDL_UnlockMutex(callback_queue_mutex);
+
+        SDL_LockMutex(callback_mutex);
+        callback(callback_data);
+        SDL_UnlockMutex(callback_mutex);
+
+        SDL_LockMutex(callback_queue_mutex);
+    }
+
+    SDL_UnlockMutex(callback_queue_mutex);
+}
+
+// Call the OPL emulator code to fill the specified buffer.
+
+static void FillBuffer(int16_t *buffer, unsigned int nsamples)
+{
+    unsigned int i;
+
+    // This seems like a reasonable assumption.  mix_buffer is
+    // 1 second long, which should always be much longer than the
+    // SDL mix buffer.
+
+    assert(nsamples < mixing_freq);
+
+    Chip__GenerateBlock2(&opl_chip, nsamples, mix_buffer);
+
+    // Mix into the destination buffer, doubling up into stereo.
+
+    for (i=0; i<nsamples; ++i)
+    {
+        buffer[i * 2] = (int16_t) (mix_buffer[i] * 2);
+        buffer[i * 2 + 1] = (int16_t) (mix_buffer[i] * 2);
+    }
+}
+
+// Callback function to fill a new sound buffer:
+
+static void OPL_Mix_Callback(void *udata,
+                             Uint8 *byte_buffer,
+                             int buffer_bytes)
+{
+    int16_t *buffer;
+    unsigned int buffer_len;
+    unsigned int filled = 0;
+
+    // Buffer length in samples (quadrupled, because of 16-bit and stereo)
+
+    buffer = (int16_t *) byte_buffer;
+    buffer_len = buffer_bytes / 4;
+
+    // Repeatedly call the OPL emulator update function until the buffer is
+    // full.
+
+    while (filled < buffer_len)
+    {
+        unsigned int next_callback_time;
+        unsigned int nsamples;
+
+        SDL_LockMutex(callback_queue_mutex);
+
+        // Work out the time until the next callback waiting in
+        // the callback queue must be invoked.  We can then fill the
+        // buffer with this many samples.
+
+        if (opl_sdl_paused || OPL_Queue_IsEmpty(callback_queue))
+        {
+            nsamples = buffer_len - filled;
+        }
+        else
+        {
+            next_callback_time = OPL_Queue_Peek(callback_queue) + pause_offset;
+
+            nsamples = next_callback_time - current_time;
+
+            if (nsamples > buffer_len - filled)
+            {
+                nsamples = buffer_len - filled;
+            }
+        }
+
+        SDL_UnlockMutex(callback_queue_mutex);
+
+        // Add emulator output to buffer.
+
+        FillBuffer(buffer + filled * 2, nsamples);
+        filled += nsamples;
+
+        // Invoke callbacks for this point in time.
+
+        AdvanceTime(nsamples);
+    }
+}
+
+static void OPL_SDL_Shutdown(void)
+{
+    Mix_HookMusic(NULL, NULL);
+
+    if (sdl_was_initialized)
+    {
+        Mix_CloseAudio();
+        SDL_QuitSubSystem(SDL_INIT_AUDIO);
+        OPL_Queue_Destroy(callback_queue);
+        free(mix_buffer);
+        sdl_was_initialized = 0;
+    }
+
+/*
+    if (opl_chip != NULL)
+    {
+        OPLDestroy(opl_chip);
+        opl_chip = NULL;
+    }
+    */
+
+    if (callback_mutex != NULL)
+    {
+        SDL_DestroyMutex(callback_mutex);
+        callback_mutex = NULL;
+    }
+
+    if (callback_queue_mutex != NULL)
+    {
+        SDL_DestroyMutex(callback_queue_mutex);
+        callback_queue_mutex = NULL;
+    }
+}
+
+static unsigned int GetSliceSize(void)
+{
+    int limit;
+    int n;
+
+    limit = (opl_sample_rate * MAX_SOUND_SLICE_TIME) / 1000;
+
+    // Try all powers of two, not exceeding the limit.
+
+    for (n=0;; ++n)
+    {
+        // 2^n <= limit < 2^n+1 ?
+
+        if ((1 << (n + 1)) > limit)
+        {
+            return (1 << n);
+        }
+    }
+
+    // Should never happen?
+
+    return 1024;
+}
+
+static int OPL_SDL_Init(unsigned int port_base)
+{
+    // Check if SDL_mixer has been opened already
+    // If not, we must initialize it now
+
+    if (!SDLIsInitialized())
+    {
+        if (SDL_Init(SDL_INIT_AUDIO) < 0)
+        {
+            fprintf(stderr, "Unable to set up sound.\n");
+            return 0;
+        }
+
+        if (Mix_OpenAudio(opl_sample_rate, AUDIO_S16SYS, 2, GetSliceSize()) < 0)
+        {
+            fprintf(stderr, "Error initialising SDL_mixer: %s\n", Mix_GetError());
+
+            SDL_QuitSubSystem(SDL_INIT_AUDIO);
+            return 0;
+        }
+
+        SDL_PauseAudio(0);
+
+        // When this module shuts down, it has the responsibility to 
+        // shut down SDL.
+
+        sdl_was_initialized = 1;
+    }
+    else
+    {
+        sdl_was_initialized = 0;
+    }
+
+    opl_sdl_paused = 0;
+    pause_offset = 0;
+
+    // Queue structure of callbacks to invoke.
+
+    callback_queue = OPL_Queue_Create();
+    current_time = 0;
+
+    // Get the mixer frequency, format and number of channels.
+
+    Mix_QuerySpec(&mixing_freq, &mixing_format, &mixing_channels);
+
+    // Only supports AUDIO_S16SYS
+
+    if (mixing_format != AUDIO_S16SYS || mixing_channels != 2)
+    {
+        fprintf(stderr, 
+                "OPL_SDL only supports native signed 16-bit LSB, "
+                "stereo format!\n");
+
+        OPL_SDL_Shutdown();
+        return 0;
+    }
+
+    // Mix buffer:
+
+    mix_buffer = malloc(mixing_freq * sizeof(uint32_t));
+
+    // Create the emulator structure:
+
+    DBOPL_InitTables();
+    Chip__Chip(&opl_chip);
+    Chip__Setup(&opl_chip, mixing_freq);
+
+    callback_mutex = SDL_CreateMutex();
+    callback_queue_mutex = SDL_CreateMutex();
+
+    // TODO: This should be music callback? or-?
+    Mix_HookMusic(OPL_Mix_Callback, NULL);
+
+    return 1;
+}
+
+static unsigned int OPL_SDL_PortRead(opl_port_t port)
+{
+    unsigned int result = 0;
+
+    if (timer1.enabled && current_time > timer1.expire_time)
+    {
+        result |= 0x80;   // Either have expired
+        result |= 0x40;   // Timer 1 has expired
+    }
+
+    if (timer2.enabled && current_time > timer2.expire_time)
+    {
+        result |= 0x80;   // Either have expired
+        result |= 0x20;   // Timer 2 has expired
+    }
+
+    return result;
+}
+
+static void OPLTimer_CalculateEndTime(opl_timer_t *timer)
+{
+    int tics;
+
+    // If the timer is enabled, calculate the time when the timer
+    // will expire.
+
+    if (timer->enabled)
+    {
+        tics = 0x100 - timer->value;
+        timer->expire_time = current_time
+                           + (tics * opl_sample_rate) / timer->rate;
+    }
+}
+
+static void WriteRegister(unsigned int reg_num, unsigned int value)
+{
+    switch (reg_num)
+    {
+        case OPL_REG_TIMER1:
+            timer1.value = value;
+            OPLTimer_CalculateEndTime(&timer1);
+            break;
+
+        case OPL_REG_TIMER2:
+            timer2.value = value;
+            OPLTimer_CalculateEndTime(&timer2);
+            break;
+
+        case OPL_REG_TIMER_CTRL:
+            if (value & 0x80)
+            {
+                timer1.enabled = 0;
+                timer2.enabled = 0;
+            }
+            else
+            {
+                if ((value & 0x40) == 0)
+                {
+                    timer1.enabled = (value & 0x01) != 0;
+                    OPLTimer_CalculateEndTime(&timer1);
+                }
+
+                if ((value & 0x20) == 0)
+                {
+                    timer1.enabled = (value & 0x02) != 0;
+                    OPLTimer_CalculateEndTime(&timer2);
+                }
+            }
+
+            break;
+
+        default:
+            Chip__WriteReg(&opl_chip, reg_num, value);
+            break;
+    }
+}
+
+static void OPL_SDL_PortWrite(opl_port_t port, unsigned int value)
+{
+    if (port == OPL_REGISTER_PORT)
+    {
+        register_num = value;
+    }
+    else if (port == OPL_DATA_PORT)
+    {
+        WriteRegister(register_num, value);
+    }
+}
+
+static void OPL_SDL_SetCallback(unsigned int ms,
+                                opl_callback_t callback,
+                                void *data)
+{
+    SDL_LockMutex(callback_queue_mutex);
+    OPL_Queue_Push(callback_queue, callback, data,
+                   current_time - pause_offset + (ms * mixing_freq) / 1000);
+    SDL_UnlockMutex(callback_queue_mutex);
+}
+
+static void OPL_SDL_ClearCallbacks(void)
+{
+    SDL_LockMutex(callback_queue_mutex);
+    OPL_Queue_Clear(callback_queue);
+    SDL_UnlockMutex(callback_queue_mutex);
+}
+
+static void OPL_SDL_Lock(void)
+{
+    SDL_LockMutex(callback_mutex);
+}
+
+static void OPL_SDL_Unlock(void)
+{
+    SDL_UnlockMutex(callback_mutex);
+}
+
+static void OPL_SDL_SetPaused(int paused)
+{
+    opl_sdl_paused = paused;
+}
+
+opl_driver_t opl_sdl_driver =
+{
+    "SDL",
+    OPL_SDL_Init,
+    OPL_SDL_Shutdown,
+    OPL_SDL_PortRead,
+    OPL_SDL_PortWrite,
+    OPL_SDL_SetCallback,
+    OPL_SDL_ClearCallbacks,
+    OPL_SDL_Lock,
+    OPL_SDL_Unlock,
+    OPL_SDL_SetPaused
+};
+
--- /dev/null
+++ b/opl/opl_timer.c
@@ -1,0 +1,251 @@
+// Emacs style mode select   -*- C++ -*- 
+//-----------------------------------------------------------------------------
+//
+// 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.
+//
+// DESCRIPTION:
+//     OPL timer thread.
+//     Once started using OPL_Timer_StartThread, the thread sleeps,
+//     waking up to invoke callbacks set using OPL_Timer_SetCallback.
+//
+//-----------------------------------------------------------------------------
+
+#include "SDL.h"
+
+#include "opl_timer.h"
+#include "opl_queue.h"
+
+typedef enum
+{
+    THREAD_STATE_STOPPED,
+    THREAD_STATE_RUNNING,
+    THREAD_STATE_STOPPING,
+} thread_state_t;
+
+static SDL_Thread *timer_thread = NULL;
+static thread_state_t timer_thread_state;
+static int current_time;
+
+// If non-zero, callbacks are currently paused.
+
+static int opl_timer_paused;
+
+// Offset in milliseconds to adjust time due to the fact that playback
+// was paused.
+
+static unsigned int pause_offset = 0;
+
+// Queue of callbacks waiting to be invoked.
+// The callback queue mutex is held while the callback queue structure
+// or current_time is being accessed.
+
+static opl_callback_queue_t *callback_queue;
+static SDL_mutex *callback_queue_mutex;
+
+// The timer mutex is held while timer callback functions are being
+// invoked, so that the calling code can prevent clashes.
+
+static SDL_mutex *timer_mutex;
+
+// Returns true if there is a callback at the head of the queue ready
+// to be invoked.  Otherwise, next_time is set to the time when the
+// timer thread must wake up again to check.
+
+static int CallbackWaiting(unsigned int *next_time)
+{
+    // If paused, just wait in 50ms increments until unpaused.
+    // Update pause_offset so after we unpause, the callback 
+    // times will be right.
+
+    if (opl_timer_paused)
+    {
+        *next_time = current_time + 50;
+        pause_offset += 50;
+        return 0;
+    }
+
+    // If there are no queued callbacks, sleep for 50ms at a time
+    // until a callback is added.
+
+    if (OPL_Queue_IsEmpty(callback_queue))
+    {
+        *next_time = current_time + 50;
+        return 0;
+    }
+
+    // Read the time of the first callback in the queue.
+    // If the time for the callback has not yet arrived,
+    // we must sleep until the callback time.
+
+    *next_time = OPL_Queue_Peek(callback_queue) + pause_offset;
+
+    return *next_time <= current_time;
+}
+
+static unsigned int GetNextTime(void)
+{
+    opl_callback_t callback;
+    void *callback_data;
+    unsigned int next_time;
+    int have_callback;
+
+    // Keep running through callbacks until there are none ready to
+    // run. When we run out of callbacks, next_time will be set.
+
+    do
+    {
+        SDL_LockMutex(callback_queue_mutex);
+
+        // Check if the callback at the head of the list is ready to
+        // be invoked.  If so, pop from the head of the queue.
+
+        have_callback = CallbackWaiting(&next_time);
+
+        if (have_callback)
+        {
+            OPL_Queue_Pop(callback_queue, &callback, &callback_data);
+        }
+
+        SDL_UnlockMutex(callback_queue_mutex);
+
+        // Now invoke the callback, if we have one.
+        // The timer mutex is held while the callback is invoked.
+
+        if (have_callback)
+        {
+            SDL_LockMutex(timer_mutex);
+            callback(callback_data);
+            SDL_UnlockMutex(timer_mutex);
+        }
+    } while (have_callback);
+
+    return next_time;
+}
+
+static int ThreadFunction(void *unused)
+{
+    unsigned int next_time;
+    unsigned int now;
+
+    // Keep running until OPL_Timer_StopThread is called.
+
+    while (timer_thread_state == THREAD_STATE_RUNNING)
+    {
+        // Get the next time that we must sleep until, and
+        // wait until that time.
+
+        next_time = GetNextTime();
+        now = SDL_GetTicks();
+
+        if (next_time > now)
+        {
+            SDL_Delay(next_time - now);
+        }
+
+        // Update the current time.
+
+        SDL_LockMutex(callback_queue_mutex);
+        current_time = next_time;
+        SDL_UnlockMutex(callback_queue_mutex);
+    }
+
+    timer_thread_state = THREAD_STATE_STOPPED;
+
+    return 0;
+}
+
+static void InitResources(void)
+{
+    callback_queue = OPL_Queue_Create();
+    timer_mutex = SDL_CreateMutex();
+    callback_queue_mutex = SDL_CreateMutex();
+}
+
+static void FreeResources(void)
+{
+    OPL_Queue_Destroy(callback_queue);
+    SDL_DestroyMutex(callback_queue_mutex);
+    SDL_DestroyMutex(timer_mutex);
+}
+
+int OPL_Timer_StartThread(void)
+{
+    InitResources();
+
+    timer_thread_state = THREAD_STATE_RUNNING;
+    current_time = SDL_GetTicks();
+    opl_timer_paused = 0;
+    pause_offset = 0;
+
+    timer_thread = SDL_CreateThread(ThreadFunction, NULL);
+
+    if (timer_thread == NULL)
+    {
+        timer_thread_state = THREAD_STATE_STOPPED;
+        FreeResources();
+
+        return 0;
+    }
+
+    return 1;
+}
+
+void OPL_Timer_StopThread(void)
+{
+    timer_thread_state = THREAD_STATE_STOPPING;
+
+    while (timer_thread_state != THREAD_STATE_STOPPED)
+    {
+        SDL_Delay(1);
+    }
+
+    FreeResources();
+}
+
+void OPL_Timer_SetCallback(unsigned int ms, opl_callback_t callback, void *data)
+{
+    SDL_LockMutex(callback_queue_mutex);
+    OPL_Queue_Push(callback_queue, callback, data,
+                   current_time + ms - pause_offset);
+    SDL_UnlockMutex(callback_queue_mutex);
+}
+
+void OPL_Timer_ClearCallbacks(void)
+{
+    SDL_LockMutex(callback_queue_mutex);
+    OPL_Queue_Clear(callback_queue);
+    SDL_UnlockMutex(callback_queue_mutex);
+}
+
+void OPL_Timer_Lock(void)
+{
+    SDL_LockMutex(timer_mutex);
+}
+
+void OPL_Timer_Unlock(void)
+{
+    SDL_UnlockMutex(timer_mutex);
+}
+
+void OPL_Timer_SetPaused(int paused)
+{
+    SDL_LockMutex(callback_queue_mutex);
+    opl_timer_paused = paused;
+    SDL_UnlockMutex(callback_queue_mutex);
+}
+
--- /dev/null
+++ b/opl/opl_timer.h
@@ -1,0 +1,42 @@
+// Emacs style mode select   -*- C++ -*- 
+//-----------------------------------------------------------------------------
+//
+// 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.
+//
+// DESCRIPTION:
+//     OPL timer thread.
+//
+//-----------------------------------------------------------------------------
+
+#ifndef OPL_TIMER_H
+#define OPL_TIMER_H
+
+#include "opl.h"
+
+int OPL_Timer_StartThread(void);
+void OPL_Timer_StopThread(void);
+void OPL_Timer_SetCallback(unsigned int ms,
+                           opl_callback_t callback,
+                           void *data);
+void OPL_Timer_ClearCallbacks(void);
+void OPL_Timer_Lock(void);
+void OPL_Timer_Unlock(void);
+void OPL_Timer_SetPaused(int paused);
+
+#endif /* #ifndef OPL_TIMER_H */
+
--- /dev/null
+++ b/opl/opl_win32.c
@@ -1,0 +1,172 @@
+// Emacs style mode select   -*- C++ -*- 
+//-----------------------------------------------------------------------------
+//
+// 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.
+//
+// DESCRIPTION:
+//     OPL Win32 native interface.
+//
+//-----------------------------------------------------------------------------
+
+#include "config.h"
+
+#ifdef _WIN32
+
+#include <stdio.h>
+
+#define WIN32_LEAN_AND_MEAN
+#include <windows.h>
+
+#include "opl.h"
+#include "opl_internal.h"
+#include "opl_timer.h"
+
+#include "ioperm_sys.h"
+
+static unsigned int opl_port_base;
+
+// MingW?
+
+#if defined(__GNUC__) && defined(__i386__)
+
+static unsigned int OPL_Win32_PortRead(opl_port_t port)
+{
+    unsigned char result;
+
+    __asm__ volatile (
+       "movl %1, %%edx\n"
+       "inb  %%dx, %%al\n"
+       "movb %%al, %0"
+       :   "=m" (result)
+       :   "r" (opl_port_base + port)
+       :   "edx", "al", "memory"
+    );
+
+    return result;
+}
+
+static void OPL_Win32_PortWrite(opl_port_t port, unsigned int value)
+{
+    __asm__ volatile (
+       "movl %0, %%edx\n"
+       "movb %1, %%al\n"
+       "outb %%al, %%dx"
+       :
+       :   "r" (opl_port_base + port), "r" ((unsigned char) value)
+       :   "edx", "al"
+    );
+}
+
+// TODO: MSVC version
+// #elif defined(_MSC_VER) && defined(_M_IX6) ...
+
+#else
+
+// Not x86, or don't know how to do port R/W on this compiler.
+
+#define NO_PORT_RW
+
+static unsigned int OPL_Win32_PortRead(opl_port_t port)
+{
+    return 0;
+}
+
+static void OPL_Win32_PortWrite(opl_port_t port, unsigned int value)
+{
+}
+
+#endif
+
+static int OPL_Win32_Init(unsigned int port_base)
+{
+#ifndef NO_PORT_RW
+
+    OSVERSIONINFO version_info;
+
+    opl_port_base = port_base;
+
+    // Check the OS version.
+
+    memset(&version_info, 0, sizeof(version_info));
+    version_info.dwOSVersionInfoSize = sizeof(version_info);
+
+    GetVersionEx(&version_info);
+
+    // On NT-based systems, we must acquire I/O port permissions
+    // using the ioperm.sys driver.
+
+    if (version_info.dwPlatformId == VER_PLATFORM_WIN32_NT)
+    {
+        // Install driver.
+
+        if (!IOperm_InstallDriver())
+        {
+            return 0;
+        }
+
+        // Open port range.
+
+        if (!IOperm_EnablePortRange(opl_port_base, 2, 1))
+        {
+            IOperm_UninstallDriver();
+            return 0;
+        }
+    }
+
+    // Start callback thread
+
+    if (!OPL_Timer_StartThread())
+    {
+        IOperm_UninstallDriver();
+        return 0;
+    }
+
+    return 1;
+
+#endif
+
+    return 0;
+}
+
+static void OPL_Win32_Shutdown(void)
+{
+    // Stop callback thread
+
+    OPL_Timer_StopThread();
+
+    // Unload IOperm library.
+
+    IOperm_UninstallDriver();
+}
+
+opl_driver_t opl_win32_driver =
+{
+    "Win32",
+    OPL_Win32_Init,
+    OPL_Win32_Shutdown,
+    OPL_Win32_PortRead,
+    OPL_Win32_PortWrite,
+    OPL_Timer_SetCallback,
+    OPL_Timer_ClearCallbacks,
+    OPL_Timer_Lock,
+    OPL_Timer_Unlock,
+    OPL_Timer_SetPaused
+};
+
+#endif /* #ifdef _WIN32 */
+
--- a/pkg/config.make.in
+++ b/pkg/config.make.in
@@ -22,6 +22,7 @@
 DOC_FILES = README       \
             COPYING      \
             ChangeLog    \
+            INSTALL      \
             NEWS         \
             BUGS         \
             CMDLINE      \
--- a/pkg/osx/GNUmakefile
+++ b/pkg/osx/GNUmakefile
@@ -6,6 +6,10 @@
 
 include ../config.make
 
+# Build so that the package will work on older versions.
+
+export MACOSX_DEPLOYMENT_TARGET=10.4
+
 STAGING_DIR=staging
 DMG=$(PACKAGE_TARNAME)-$(PACKAGE_VERSION).dmg
 
--- a/pkg/win32/GNUmakefile
+++ b/pkg/win32/GNUmakefile
@@ -13,6 +13,8 @@
           $(TOPLEVEL)/src/SDL_mixer.dll                \
           $(TOPLEVEL)/src/SDL_net.dll
 
+DOC_FILES += README.OPL
+
 ZIP=$(PACKAGE_TARNAME)-$(PACKAGE_VERSION)-win32.zip
 
 $(ZIP) : staging
--- a/rpm.spec.in
+++ b/rpm.spec.in
@@ -49,9 +49,11 @@
 %files
 %doc %{_mandir}/man5/*
 %doc %{_mandir}/man6/*
+%doc README
+%doc README.OPL
+%doc INSTALL
 %doc NEWS
 %doc AUTHORS
-%doc README
 %doc COPYING
 %doc CMDLINE
 %doc BUGS
--- a/src/.gitignore
+++ b/src/.gitignore
@@ -3,7 +3,11 @@
 .deps
 *.rc
 chocolate-doom
+chocolate-heretic
+chocolate-hexen
 chocolate-server
+chocolate-setup
 *.exe
+*.desktop
 tags
 TAGS
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -11,6 +11,7 @@
                  @PROGRAM_PREFIX@setup
 
 AM_CFLAGS = -I$(top_builddir)/textscreen            \
+            -I$(top_builddir)/opl                   \
             -I$(top_builddir)/pcsound               \
             @SDLMIXER_CFLAGS@ @SDLNET_CFLAGS@
 
@@ -72,6 +73,7 @@
 v_video.c            v_video.h             \
                      v_patch.h             \
 w_checksum.c         w_checksum.h          \
+w_main.c             w_main.h              \
 w_wad.c              w_wad.h               \
 w_file.c             w_file.h              \
 w_file_stdc.c                              \
@@ -115,6 +117,8 @@
 i_pcsound.c                                \
 i_sdlsound.c                               \
 i_sdlmusic.c                               \
+i_oplmusic.c                               \
+midifile.c           midifile.h            \
 mus2mid.c            mus2mid.h
 
 # Some games support dehacked patches, some don't:
@@ -131,10 +135,11 @@
                $(top_builddir)/wince/libc_wince.a          \
                $(top_builddir)/textscreen/libtextscreen.a  \
                $(top_builddir)/pcsound/libpcsound.a        \
+               $(top_builddir)/opl/libopl.a                \
                @LDFLAGS@                                   \
                @SDL_LIBS@                                  \
                @SDLMIXER_LIBS@                             \
-               @SDLNET_LIBS@ 
+               @SDLNET_LIBS@
 
 if HAVE_WINDRES
 @PROGRAM_PREFIX@doom_SOURCES=$(SOURCE_FILES_WITH_DEH) resource.rc
@@ -145,9 +150,9 @@
 @PROGRAM_PREFIX@doom_LDADD = doom/libdoom.a $(EXTRA_LIBS)
 
 if HAVE_WINDRES
-@PROGRAM_PREFIX@heretic_SOURCES=$(SOURCE_FILES) resource.rc
+@PROGRAM_PREFIX@heretic_SOURCES=$(SOURCE_FILES_WITH_DEH) resource.rc
 else
-@PROGRAM_PREFIX@heretic_SOURCES=$(SOURCE_FILES)
+@PROGRAM_PREFIX@heretic_SOURCES=$(SOURCE_FILES_WITH_DEH)
 endif
 
 @PROGRAM_PREFIX@heretic_LDADD = heretic/libheretic.a $(EXTRA_LIBS)
@@ -203,4 +208,7 @@
 	$(top_builddir)/data/convert-icon $^ $@
 
 endif
+
+midiread : midifile.c
+	$(CC) -DTEST $(CFLAGS) @LDFLAGS@ $^ -o $@
 
--- a/src/deh_main.c
+++ b/src/deh_main.c
@@ -39,6 +39,8 @@
 extern deh_section_t *deh_section_types[];
 extern char *deh_signatures[];
 
+static boolean deh_initialized = false;
+
 // If true, we can do long string replacements.
 
 boolean deh_allow_long_strings = false;
@@ -322,6 +324,12 @@
 {
     deh_context_t *context;
 
+    if (!deh_initialized)
+    {
+        InitializeSections();
+        deh_initialized = true;
+    }
+
     printf(" loading %s\n", filename);
 
     context = DEH_OpenFile(filename);
@@ -345,8 +353,6 @@
 {
     char *filename;
     int p;
-
-    InitializeSections();
 
     //!
     // @category mod
--- a/src/deh_mapping.c
+++ b/src/deh_mapping.c
@@ -34,12 +34,9 @@
 #include "i_system.h"
 #include "deh_mapping.h"
 
-//
-// Set the value of a particular field in a structure by name
-//
-
-boolean DEH_SetMapping(deh_context_t *context, deh_mapping_t *mapping, 
-                       void *structptr, char *name, int value)
+static deh_mapping_entry_t *GetMappingEntryByName(deh_context_t *context,
+                                                  deh_mapping_t *mapping,
+                                                  char *name)
 {
     int i;
 
@@ -49,44 +46,121 @@
 
         if (!strcasecmp(entry->name, name))
         {
-            void *location;
-
             if (entry->location == NULL)
             {
                 DEH_Warning(context, "Field '%s' is unsupported", name);
-                return false;
+                return NULL;
             }
 
-            location = (uint8_t *)structptr + ((uint8_t *)entry->location - (uint8_t *)mapping->base);
+            return entry;
+        }
+    }
 
-     //       printf("Setting %p::%s to %i (%i bytes)\n",
-     //               structptr, name, value, entry->size);
-                
-            switch (entry->size)
-            {
-                case 1:
-                    * ((uint8_t *) location) = value;
-                    break;
-                case 2:
-                    * ((uint16_t *) location) = value;
-                    break;
-                case 4:
-                    * ((uint32_t *) location) = value;
-                    break;
-                default:
-                    DEH_Error(context, "Unknown field type for '%s' (BUG)", name);
-                    return false;
-            }
+    // Not found.
+
+    DEH_Warning(context, "Field named '%s' not found", name);
 
-            return true;
-        }
+    return NULL;
+}
+
+//
+// Get the location of the specified field in the specified structure.
+//
+
+static void *GetStructField(void *structptr,
+                            deh_mapping_t *mapping,
+                            deh_mapping_entry_t *entry)
+{
+    unsigned int offset;
+
+    offset = (uint8_t *)entry->location - (uint8_t *)mapping->base;
+
+    return (uint8_t *)structptr + offset;
+}
+
+//
+// Set the value of a particular field in a structure by name
+//
+
+boolean DEH_SetMapping(deh_context_t *context, deh_mapping_t *mapping,
+                       void *structptr, char *name, int value)
+{
+    deh_mapping_entry_t *entry;
+    void *location;
+
+    entry = GetMappingEntryByName(context, mapping, name);
+
+    if (entry == NULL)
+    {
+        return false;
     }
 
-    // field with this name not found
+    // Sanity check:
 
-    DEH_Warning(context, "Field named '%s' not found", name);
+    if (entry->is_string)
+    {
+        DEH_Error(context, "Tried to set '%s' as integer (BUG)", name);
+        return false;
+    }
 
-    return false;
+    location = GetStructField(structptr, mapping, entry);
+
+    //       printf("Setting %p::%s to %i (%i bytes)\n",
+    //               structptr, name, value, entry->size);
+
+    // Set field content based on its type:
+
+    switch (entry->size)
+    {
+        case 1:
+            * ((uint8_t *) location) = value;
+            break;
+        case 2:
+            * ((uint16_t *) location) = value;
+            break;
+        case 4:
+            * ((uint32_t *) location) = value;
+            break;
+        default:
+            DEH_Error(context, "Unknown field type for '%s' (BUG)", name);
+            return false;
+    }
+
+    return true;
+}
+
+//
+// Set the value of a string field in a structure by name
+//
+
+boolean DEH_SetStringMapping(deh_context_t *context, deh_mapping_t *mapping,
+                             void *structptr, char *name, char *value)
+{
+    deh_mapping_entry_t *entry;
+    void *location;
+
+    entry = GetMappingEntryByName(context, mapping, name);
+
+    if (entry == NULL)
+    {
+        return false;
+    }
+
+    // Sanity check:
+
+    if (!entry->is_string)
+    {
+        DEH_Error(context, "Tried to set '%s' as string (BUG)", name);
+        return false;
+    }
+
+    location = GetStructField(structptr, mapping, entry);
+
+    // Copy value into field:
+
+    strncpy(location, value, entry->size);
+
+    return true;
 }
 
 void DEH_StructMD5Sum(md5_context_t *context, deh_mapping_t *mapping,
--- a/src/deh_mapping.h
+++ b/src/deh_mapping.h
@@ -42,18 +42,24 @@
 
 #define DEH_MAPPING(deh_name, fieldname)                      \
              {deh_name, &deh_mapping_base.fieldname,          \
-                 sizeof(deh_mapping_base.fieldname)},
+                 sizeof(deh_mapping_base.fieldname),          \
+                 false},
 
+#define DEH_MAPPING_STRING(deh_name, fieldname)               \
+             {deh_name, &deh_mapping_base.fieldname,          \
+                 sizeof(deh_mapping_base.fieldname),          \
+                 true},
+
 #define DEH_UNSUPPORTED_MAPPING(deh_name)                     \
-             {deh_name, NULL, -1},
-            
+             {deh_name, NULL, -1, false},
+
 #define DEH_END_MAPPING                                       \
              {NULL, NULL, -1}                                 \
         }                                                     \
     };
 
-    
 
+
 #define MAX_MAPPING_ENTRIES 32
 
 typedef struct deh_mapping_s deh_mapping_t;
@@ -73,6 +79,10 @@
     // field size
 
     int size;
+
+    // if true, this is a string value.
+
+    boolean is_string;
 };
 
 struct deh_mapping_s
@@ -81,8 +91,10 @@
     deh_mapping_entry_t entries[MAX_MAPPING_ENTRIES];
 };
 
-boolean DEH_SetMapping(deh_context_t *context, deh_mapping_t *mapping, 
+boolean DEH_SetMapping(deh_context_t *context, deh_mapping_t *mapping,
                        void *structptr, char *name, int value);
+boolean DEH_SetStringMapping(deh_context_t *context, deh_mapping_t *mapping,
+                             void *structptr, char *name, char *value);
 void DEH_StructMD5Sum(md5_context_t *context, deh_mapping_t *mapping,
                       void *structptr);
 
--- a/src/deh_str.c
+++ b/src/deh_str.c
@@ -27,6 +27,7 @@
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
+#include <stdarg.h>
 
 #include "doomtype.h"
 #include "deh_str.h"
@@ -177,5 +178,231 @@
     sub->to_text = to_text;
 
     DEH_AddToHashtable(sub);
+}
+
+typedef enum
+{
+    FORMAT_ARG_INVALID,
+    FORMAT_ARG_INT,
+    FORMAT_ARG_FLOAT,
+    FORMAT_ARG_CHAR,
+    FORMAT_ARG_STRING,
+    FORMAT_ARG_PTR,
+    FORMAT_ARG_SAVE_POS
+} format_arg_t;
+
+// Get the type of a format argument.
+// We can mix-and-match different format arguments as long as they
+// are for the same data type.
+
+static format_arg_t FormatArgumentType(char c)
+{
+    switch (c)
+    {
+        case 'd': case 'i': case 'o': case 'u': case 'x': case 'X':
+            return FORMAT_ARG_INT;
+
+        case 'e': case 'E': case 'f': case 'F': case 'g': case 'G':
+        case 'a': case 'A':
+            return FORMAT_ARG_FLOAT;
+
+        case 'c': case 'C':
+            return FORMAT_ARG_CHAR;
+
+        case 's': case 'S':
+            return FORMAT_ARG_STRING;
+
+        case 'p':
+            return FORMAT_ARG_PTR;
+
+        case 'n':
+            return FORMAT_ARG_SAVE_POS;
+
+        default:
+            return FORMAT_ARG_INVALID;
+    }
+}
+
+// Given the specified string, get the type of the first format
+// string encountered.
+
+static format_arg_t NextFormatArgument(char **str)
+{
+    format_arg_t argtype;
+
+    // Search for the '%' starting the next string.
+
+    while (**str != '\0')
+    {
+        if (**str == '%')
+        {
+            ++*str;
+
+            // Don't stop for double-%s.
+
+            if (**str != '%')
+            {
+                break;
+            }
+        }
+
+        ++*str;
+    }
+
+    // Find the type of the format string.
+
+    while (**str != '\0')
+    {
+        argtype = FormatArgumentType(**str);
+
+        if (argtype != FORMAT_ARG_INVALID)
+        {
+            ++*str;
+
+            return argtype;
+        }
+
+        ++*str;
+    }
+
+    // Stop searching, we have reached the end.
+
+    *str = NULL;
+
+    return FORMAT_ARG_INVALID;
+}
+
+// Check if the specified argument type is a valid replacement for
+// the original.
+
+static boolean ValidArgumentReplacement(format_arg_t original,
+                                        format_arg_t replacement)
+{
+    // In general, the original and replacement types should be
+    // identical.  However, there are some cases where the replacement
+    // is valid and the types don't match.
+
+    // Characters can be represented as ints.
+
+    if (original == FORMAT_ARG_CHAR && replacement == FORMAT_ARG_INT)
+    {
+        return true;
+    }
+
+    // Strings are pointers.
+
+    if (original == FORMAT_ARG_STRING && replacement == FORMAT_ARG_PTR)
+    {
+        return true;
+    }
+
+    return original == replacement;
+}
+
+// Return true if the specified string contains no format arguments.
+
+static boolean ValidFormatReplacement(char *original, char *replacement)
+{
+    char *rover1;
+    char *rover2;
+    int argtype1, argtype2;
+
+    // Check each argument in turn and compare types.
+
+    rover1 = original; rover2 = replacement;
+
+    for (;;)
+    {
+        argtype1 = NextFormatArgument(&rover1);
+        argtype2 = NextFormatArgument(&rover2);
+
+        if (argtype2 == FORMAT_ARG_INVALID)
+        {
+            // No more arguments left to read from the replacement string.
+
+            break;
+        }
+        else if (argtype1 == FORMAT_ARG_INVALID)
+        {
+            // Replacement string has more arguments than the original.
+
+            return false;
+        }
+        else if (!ValidArgumentReplacement(argtype1, argtype2))
+        {
+            // Not a valid replacement argument.
+
+            return false;
+        }
+    }
+
+    return true;
+}
+
+// Get replacement format string, checking arguments.
+
+static char *FormatStringReplacement(char *s)
+{
+    char *repl;
+
+    repl = DEH_String(s);
+
+    if (!ValidFormatReplacement(s, repl))
+    {
+        printf("WARNING: Unsafe dehacked replacement provided for "
+               "printf format string: %s\n", s);
+
+        return s;
+    }
+
+    return repl;
+}
+
+// printf(), performing a replacement on the format string.
+
+void DEH_printf(char *fmt, ...)
+{
+    va_list args;
+    char *repl;
+
+    repl = FormatStringReplacement(fmt);
+
+    va_start(args, fmt);
+
+    vprintf(repl, args);
+
+    va_end(args);
+}
+
+// fprintf(), performing a replacement on the format string.
+
+void DEH_fprintf(FILE *fstream, char *fmt, ...)
+{
+    va_list args;
+    char *repl;
+
+    repl = FormatStringReplacement(fmt);
+
+    va_start(args, fmt);
+
+    vfprintf(fstream, repl, args);
+
+    va_end(args);
+}
+
+// snprintf(), performing a replacement on the format string.
+
+void DEH_snprintf(char *buffer, size_t len, char *fmt, ...)
+{
+    va_list args;
+    char *repl;
+
+    repl = FormatStringReplacement(fmt);
+
+    va_start(args, fmt);
+
+    vsnprintf(buffer, len, repl, args);
+
+    va_end(args);
 }
 
--- a/src/deh_str.h
+++ b/src/deh_str.h
@@ -27,6 +27,8 @@
 #ifndef DEH_STR_H
 #define DEH_STR_H
 
+#include <stdio.h>
+
 #include "doomfeatures.h"
 
 // Used to do dehacked text substitutions throughout the program
@@ -34,11 +36,18 @@
 #ifdef FEATURE_DEHACKED
 
 char *DEH_String(char *s);
+void DEH_printf(char *fmt, ...);
+void DEH_fprintf(FILE *fstream, char *fmt, ...);
+void DEH_snprintf(char *buffer, size_t len, char *fmt, ...);
 void DEH_AddStringReplacement(char *from_text, char *to_text);
 
+
 #else
 
 #define DEH_String(x) (x)
+#define DEH_printf printf
+#define DEH_fprintf fprintf
+#define DEH_snprintf snprintf
 
 #endif
 
--- a/src/doom/.gitignore
+++ b/src/doom/.gitignore
@@ -1,9 +1,5 @@
 Makefile
 Makefile.in
 .deps
-*.rc
-chocolate-doom
-chocolate-server
-*.exe
 tags
 TAGS
--- a/src/doom/am_map.c
+++ b/src/doom/am_map.c
@@ -500,7 +500,7 @@
   
     for (i=0;i<10;i++)
     {
-	sprintf(namebuf, DEH_String("AMMNUM%d"), i);
+	DEH_snprintf(namebuf, 9, "AMMNUM%d", i);
 	marknums[i] = W_CacheLumpName(namebuf, PU_STATIC);
     }
 
@@ -513,7 +513,7 @@
   
     for (i=0;i<10;i++)
     {
-	sprintf(namebuf, DEH_String("AMMNUM%d"), i);
+	DEH_snprintf(namebuf, 9, "AMMNUM%d", i);
 	W_ReleaseLumpName(namebuf);
     }
 }
@@ -1020,7 +1020,7 @@
 	   || fl->b.x < 0 || fl->b.x >= f_w
 	   || fl->b.y < 0 || fl->b.y >= f_h)
     {
-	fprintf(stderr, DEH_String("fuck %d \r"), fuck++);
+        DEH_fprintf(stderr, "fuck %d \r", fuck++);
 	return;
     }
 
--- a/src/doom/d_main.c
+++ b/src/doom/d_main.c
@@ -45,8 +45,8 @@
 #include "d_iwad.h"
 
 #include "z_zone.h"
+#include "w_main.h"
 #include "w_wad.h"
-#include "w_merge.h"
 #include "s_sound.h"
 #include "v_video.h"
 
@@ -358,7 +358,13 @@
     M_BindWeaponControls();
     M_BindMapControls();
     M_BindMenuControls();
+    M_BindChatControls(MAXPLAYERS);
 
+    key_multi_msgplayer[0] = HUSTR_KEYGREEN;
+    key_multi_msgplayer[1] = HUSTR_KEYINDIGO;
+    key_multi_msgplayer[2] = HUSTR_KEYBROWN;
+    key_multi_msgplayer[3] = HUSTR_KEYRED;
+
 #ifdef FEATURE_MULTIPLAYER
     NET_BindVariables();
 #endif
@@ -827,7 +833,6 @@
 //      print title for every printed line
 char            title[128];
 
-
 static boolean D_AddFile(char *filename)
 {
     wad_file_t *handle;
@@ -1069,7 +1074,7 @@
 
     I_PrintBanner(PACKAGE_STRING);
 
-    printf (DEH_String("Z_Init: Init zone memory allocation daemon. \n"));
+    DEH_printf("Z_Init: Init zone memory allocation daemon. \n");
     Z_Init ();
 
 #ifdef FEATURE_MULTIPLAYER
@@ -1188,7 +1193,7 @@
 	deathmatch = 2;
 
     if (devparm)
-	printf(DEH_String(D_DEVSTR));
+	DEH_printf(D_DEVSTR);
     
     // find which dir to use for config files
 
@@ -1236,7 +1241,7 @@
 	    scale = 10;
 	if (scale > 400)
 	    scale = 400;
-	printf (DEH_String("turbo scale: %i%%\n"),scale);
+        DEH_printf("turbo scale: %i%%\n", scale);
 	forwardmove[0] = forwardmove[0]*scale/100;
 	forwardmove[1] = forwardmove[1]*scale/100;
 	sidemove[0] = sidemove[0]*scale/100;
@@ -1244,11 +1249,11 @@
     }
     
     // init subsystems
-    printf(DEH_String("V_Init: allocate screens.\n"));
-    V_Init();
+    DEH_printf("V_Init: allocate screens.\n");
+    V_Init ();
 
     // Load configuration files before initialising other subsystems.
-    printf(DEH_String("M_LoadDefaults: Load system defaults.\n"));
+    DEH_printf("M_LoadDefaults: Load system defaults.\n");
     M_SetConfigFilenames("default.cfg", PROGRAM_PREFIX "doom.cfg");
     D_BindVariables();
     M_LoadDefaults();
@@ -1256,161 +1261,10 @@
     // Save configuration at exit.
     I_AtExit(M_SaveDefaults, false);
 
-    printf (DEH_String("W_Init: Init WADfiles.\n"));
+    DEH_printf("W_Init: Init WADfiles.\n");
     D_AddFile(iwadfile);
+    modifiedgame = W_ParseCommandLine();
 
-#ifdef FEATURE_WAD_MERGE
-
-    // Merged PWADs are loaded first, because they are supposed to be 
-    // modified IWADs.
-
-    //!
-    // @arg <files>
-    // @category mod
-    //
-    // Simulates the behavior of deutex's -merge option, merging a PWAD
-    // into the main IWAD.  Multiple files may be specified.
-    //
-
-    p = M_CheckParm("-merge");
-
-    if (p > 0)
-    {
-        for (p = p + 1; p<myargc && myargv[p][0] != '-'; ++p)
-        {
-            char *filename;
-
-            filename = D_TryFindWADByName(myargv[p]);
-
-            printf(" merging %s\n", filename);
-            W_MergeFile(filename);
-        }
-    }
-
-    // NWT-style merging:
-
-    // NWT's -merge option:
-
-    //!
-    // @arg <files>
-    // @category mod
-    //
-    // Simulates the behavior of NWT's -merge option.  Multiple files
-    // may be specified.
-
-    p = M_CheckParm("-nwtmerge");
-
-    if (p > 0)
-    {
-        for (p = p + 1; p<myargc && myargv[p][0] != '-'; ++p)
-        {
-            char *filename;
-
-            filename = D_TryFindWADByName(myargv[p]);
-
-            printf(" performing NWT-style merge of %s\n", filename);
-            W_NWTDashMerge(filename);
-        }
-    }
-    
-    // Add flats
-
-    //!
-    // @arg <files>
-    // @category mod
-    //
-    // Simulates the behavior of NWT's -af option, merging flats into
-    // the main IWAD directory.  Multiple files may be specified.
-    //
-
-    p = M_CheckParm("-af");
-
-    if (p > 0)
-    {
-        for (p = p + 1; p<myargc && myargv[p][0] != '-'; ++p)
-        {
-            char *filename;
-
-            filename = D_TryFindWADByName(myargv[p]);
-
-            printf(" merging flats from %s\n", filename);
-            W_NWTMergeFile(filename, W_NWT_MERGE_FLATS);
-        }
-    }
-
-    //!
-    // @arg <files>
-    // @category mod
-    //
-    // Simulates the behavior of NWT's -as option, merging sprites
-    // into the main IWAD directory.  Multiple files may be specified.
-    //
-
-    p = M_CheckParm("-as");
-
-    if (p > 0)
-    {
-        for (p = p + 1; p<myargc && myargv[p][0] != '-'; ++p)
-        {
-            char *filename;
-
-            filename = D_TryFindWADByName(myargv[p]);
-
-            printf(" merging sprites from %s\n", filename);
-            W_NWTMergeFile(filename, W_NWT_MERGE_SPRITES);
-        }
-    }
-
-    //!
-    // @arg <files>
-    // @category mod
-    //
-    // Equivalent to "-af <files> -as <files>".
-    //
-
-    p = M_CheckParm("-aa");
-
-    if (p > 0)
-    {
-        for (p = p + 1; p<myargc && myargv[p][0] != '-'; ++p)
-        {
-            char *filename;
-
-            filename = D_TryFindWADByName(myargv[p]);
-
-            printf(" merging sprites and flats from %s\n", filename);
-            W_NWTMergeFile(filename, W_NWT_MERGE_SPRITES | W_NWT_MERGE_FLATS);
-        }
-    }
-
-#endif
-
-    //!
-    // @arg <files>
-    // @vanilla
-    //
-    // Load the specified PWAD files.
-    //
-
-    p = M_CheckParm ("-file");
-    if (p)
-    {
-	// the parms after p are wadfile/lump names,
-	// until end of parms or another - preceded parm
-	modifiedgame = true;            // homebrew levels
-	while (++p != myargc && myargv[p][0] != '-')
-        {
-            char *filename;
-
-            filename = D_TryFindWADByName(myargv[p]);
-
-	    D_AddFile(filename);
-        }
-    }
-
-    // Debug:
-//    W_PrintDirectory();
-
     // add any files specified on the command line with -file wadfile
     // to the wad list
     //
@@ -1603,8 +1457,8 @@
 
     if (p && p < myargc-1 && deathmatch)
     {
-	printf(DEH_String("Austin Virtual Gaming: Levels will end "
-			  "after 20 minutes\n"));
+        DEH_printf("Austin Virtual Gaming: Levels will end "
+                       "after 20 minutes\n");
 	timelimit = 20;
     }
 
@@ -1686,16 +1540,16 @@
     I_PrintStartupBanner(gamedescription);
     PrintDehackedBanners();
 
-    printf (DEH_String("M_Init: Init miscellaneous info.\n"));
+    DEH_printf("M_Init: Init miscellaneous info.\n");
     M_Init ();
 
-    printf (DEH_String("R_Init: Init DOOM refresh daemon - "));
+    DEH_printf("R_Init: Init DOOM refresh daemon - ");
     R_Init ();
 
-    printf (DEH_String("\nP_Init: Init Playloop state.\n"));
+    DEH_printf("\nP_Init: Init Playloop state.\n");
     P_Init ();
 
-    printf (DEH_String("I_Init: Setting up machine state.\n"));
+    DEH_printf("I_Init: Setting up machine state.\n");
     I_CheckIsScreensaver();
     I_InitTimer();
     I_InitJoystick();
@@ -1705,18 +1559,18 @@
     NET_Init ();
 #endif
 
-    printf (DEH_String("S_Init: Setting up sound.\n"));
+    DEH_printf("S_Init: Setting up sound.\n");
     S_Init (sfxVolume * 8, musicVolume * 8);
 
-    printf (DEH_String("D_CheckNetGame: Checking network game status.\n"));
+    DEH_printf("D_CheckNetGame: Checking network game status.\n");
     D_CheckNetGame ();
 
     PrintGameVersion();
 
-    printf (DEH_String("HU_Init: Setting up heads up display.\n"));
+    DEH_printf("HU_Init: Setting up heads up display.\n");
     HU_Init ();
 
-    printf (DEH_String("ST_Init: Init status bar.\n"));
+    DEH_printf("ST_Init: Init status bar.\n");
     ST_Init ();
 
     // If Doom II without a MAP01 lump, this is a store demo.  
--- a/src/doom/d_net.c
+++ b/src/doom/d_net.c
@@ -374,17 +374,17 @@
             ++num_players;
     }
 
-    printf (DEH_String("startskill %i  deathmatch: %i  startmap: %i  startepisode: %i\n"),
-	    startskill, deathmatch, startmap, startepisode);
+    DEH_printf("startskill %i  deathmatch: %i  startmap: %i  startepisode: %i\n",
+               startskill, deathmatch, startmap, startepisode);
 	
-    printf(DEH_String("player %i of %i (%i nodes)\n"),
-	    consoleplayer+1, num_players, num_players);
+    DEH_printf("player %i of %i (%i nodes)\n",
+               consoleplayer+1, num_players, num_players);
 
     // Show players here; the server might have specified a time limit
 
     if (timelimit > 0)
     {
-	printf(DEH_String("Levels will end after %d minute"),timelimit);
+	DEH_printf("Levels will end after %d minute", timelimit);
 	if (timelimit > 1)
 	    printf("s");
 	printf(".\n");
--- a/src/doom/f_finale.c
+++ b/src/doom/f_finale.c
@@ -663,7 +663,7 @@
 	laststage = stage;
     }
 	
-    sprintf (name, DEH_String("END%i"), stage);
+    DEH_snprintf(name, 10, "END%i", stage);
     V_DrawPatch((SCREENWIDTH - 13 * 8) / 2, 
                 (SCREENHEIGHT - 8 * 8) / 2, 
                 W_CacheLumpName (name,PU_CACHE));
--- a/src/doom/g_game.c
+++ b/src/doom/g_game.c
@@ -176,14 +176,37 @@
     &key_weapon8
 };
 
+// Set to -1 or +1 to switch to the previous or next weapon.
+
+static int next_weapon = 0;
+
+// Used for prev/next weapon keys.
+
+static const struct
+{
+    weapontype_t weapon;
+    weapontype_t weapon_num;
+} weapon_order_table[] = {
+    { wp_fist,            wp_fist },
+    { wp_chainsaw,        wp_fist },
+    { wp_pistol,          wp_pistol },
+    { wp_shotgun,         wp_shotgun },
+    { wp_supershotgun,    wp_shotgun },
+    { wp_chaingun,        wp_chaingun },
+    { wp_missile,         wp_missile },
+    { wp_plasma,          wp_plasma },
+    { wp_bfg,             wp_bfg }
+};
+
 #define SLOWTURNTICS	6 
  
 #define NUMKEYS		256 
+#define MAX_JOY_BUTTONS 20
 
 static boolean  gamekeydown[NUMKEYS]; 
 static int      turnheld;		// for accelerative turning 
  
-static boolean  mousearray[4]; 
+static boolean  mousearray[MAX_MOUSE_BUTTONS + 1];
 static boolean *mousebuttons = &mousearray[1];  // allow [-1]
 
 // mouse values are used once 
@@ -197,8 +220,6 @@
 static boolean  dclickstate2;
 static int      dclicks2;
 
-#define MAX_JOY_BUTTONS 20
-
 // joystick values are repeated 
 static int      joyxmove;
 static int      joyymove;
@@ -337,8 +358,64 @@
 		 
     return sum; 
 } 
- 
 
+static boolean WeaponSelectable(weapontype_t weapon)
+{
+    // Can't select a weapon if we don't own it.
+
+    if (!players[consoleplayer].weaponowned[weapon])
+    {
+        return false;
+    }
+
+    // Can't select the fist if we have the chainsaw, unless
+    // we also have the berserk pack.
+
+    if (weapon == wp_fist
+     && players[consoleplayer].weaponowned[wp_chainsaw]
+     && !players[consoleplayer].powers[pw_strength])
+    {
+        return false;
+    }
+
+    return true;
+}
+
+static int G_NextWeapon(int direction)
+{
+    weapontype_t weapon;
+    int i;
+
+    // Find index in the table.
+
+    if (players[consoleplayer].pendingweapon == wp_nochange)
+    {
+        weapon = players[consoleplayer].readyweapon;
+    }
+    else
+    {
+        weapon = players[consoleplayer].pendingweapon;
+    }
+
+    for (i=0; i<arrlen(weapon_order_table); ++i)
+    {
+        if (weapon_order_table[i].weapon == weapon)
+        {
+            break;
+        }
+    }
+
+    // Switch weapon.
+
+    do
+    {
+        i += direction;
+        i = (i + arrlen(weapon_order_table)) % arrlen(weapon_order_table);
+    } while (!WeaponSelectable(weapon_order_table[i].weapon));
+
+    return weapon_order_table[i].weapon_num;
+}
+
 //
 // G_BuildTiccmd
 // Builds a ticcmd from all of the available inputs
@@ -465,20 +542,34 @@
 	dclicks = 0;                   
     } 
 
-    // chainsaw overrides 
+    // If the previous or next weapon button is pressed, the
+    // next_weapon variable is set to change weapons when
+    // we generate a ticcmd.  Choose a new weapon.
 
-    for (i=0; i<arrlen(weapon_keys); ++i)
+    if (next_weapon != 0)
     {
-        int key = *weapon_keys[i];
+        i = G_NextWeapon(next_weapon);
+        cmd->buttons |= BT_CHANGE;
+        cmd->buttons |= i << BT_WEAPONSHIFT;
+        next_weapon = 0;
+    }
+    else
+    {
+        // Check weapon keys.
 
-        if (gamekeydown[key])
+        for (i=0; i<arrlen(weapon_keys); ++i)
         {
-	    cmd->buttons |= BT_CHANGE; 
-	    cmd->buttons |= i<<BT_WEAPONSHIFT; 
-	    break; 
+            int key = *weapon_keys[i];
+
+            if (gamekeydown[key])
+            {
+                cmd->buttons |= BT_CHANGE;
+                cmd->buttons |= i<<BT_WEAPONSHIFT;
+                break;
+            }
         }
     }
-    
+
     // mouse
     if (mousebuttons[mousebforward]) 
     {
@@ -657,7 +748,6 @@
         players[consoleplayer].message = "Press escape to quit.";
     }
 } 
- 
 
 static void SetJoyButtons(unsigned int buttons_mask)
 {
@@ -665,10 +755,54 @@
 
     for (i=0; i<MAX_JOY_BUTTONS; ++i)
     {
-        joybuttons[i] = (buttons_mask & (1 << i)) != 0;
+        int button_on = (buttons_mask & (1 << i)) != 0;
+
+        // Detect button press:
+
+        if (!joybuttons[i] && button_on)
+        {
+            // Weapon cycling:
+
+            if (i == joybprevweapon)
+            {
+                next_weapon = -1;
+            }
+            else if (i == joybnextweapon)
+            {
+                next_weapon = 1;
+            }
+        }
+
+        joybuttons[i] = button_on;
     }
 }
- 
+
+static void SetMouseButtons(unsigned int buttons_mask)
+{
+    int i;
+
+    for (i=0; i<MAX_MOUSE_BUTTONS; ++i)
+    {
+        unsigned int button_on = (buttons_mask & (1 << i)) != 0;
+
+        // Detect button press:
+
+        if (!mousebuttons[i] && button_on)
+        {
+            if (i == mousebprevweapon)
+            {
+                next_weapon = -1;
+            }
+            else if (i == mousebnextweapon)
+            {
+                next_weapon = 1;
+            }
+        }
+
+	mousebuttons[i] = button_on;
+    }
+}
+
 //
 // G_Responder  
 // Get info needed to make ticcmd_ts for the players.
@@ -677,7 +811,7 @@
 { 
     // allow spy mode changes even during the demo
     if (gamestate == GS_LEVEL && ev->type == ev_keydown 
-     && ev->data1 == KEY_F12 && (singledemo || !deathmatch) )
+     && ev->data1 == key_spy && (singledemo || !deathmatch) )
     {
 	// spy mode 
 	do 
@@ -737,6 +871,18 @@
         testcontrols_mousespeed = abs(ev->data2);
     }
 
+    // If the next/previous weapon keys are pressed, set the next_weapon
+    // variable to change weapons when the next ticcmd is generated.
+
+    if (ev->type == ev_keydown && ev->data1 == key_prevweapon)
+    {
+        next_weapon = -1;
+    }
+    else if (ev->type == ev_keydown && ev->data1 == key_nextweapon)
+    {
+        next_weapon = 1;
+    }
+
     switch (ev->type) 
     { 
       case ev_keydown: 
@@ -757,9 +903,7 @@
 	return false;   // always let key up events filter down 
 		 
       case ev_mouse: 
-	mousebuttons[0] = ev->data1 & 1; 
-	mousebuttons[1] = ev->data1 & 2; 
-	mousebuttons[2] = ev->data1 & 4; 
+        SetMouseButtons(ev->data1);
 	mousex = ev->data2*(mouseSensitivity+5)/10; 
 	mousey = ev->data3*(mouseSensitivity+5)/10; 
 	return true;    // eat events 
@@ -1428,6 +1572,8 @@
         return;
     }
 
+    savegame_error = false;
+
     if (!P_ReadSaveGameHeader())
     {
         fclose(save_stream);
@@ -1495,6 +1641,8 @@
         return;
     }
 
+    savegame_error = false;
+
     P_WriteSaveGameHeader(savedescription);
  
     P_ArchivePlayers (); 
@@ -1780,7 +1928,7 @@
 { 
     byte *demo_start;
 
-    if (gamekeydown['q'])           // press q to end demo recording 
+    if (gamekeydown[key_demo_quit])           // press q to end demo recording 
 	G_CheckDemoStatus (); 
 
     demo_start = demo_p;
--- a/src/doom/hu_stuff.c
+++ b/src/doom/hu_stuff.c
@@ -302,7 +302,7 @@
     j = HU_FONTSTART;
     for (i=0;i<HU_FONTSIZE;i++)
     {
-	sprintf(buffer, DEH_String("STCFN%.3d"), j++);
+	DEH_snprintf(buffer, 9, "STCFN%.3d", j++);
 	hu_font[i] = (patch_t *) W_CacheLumpName(buffer, PU_STATIC);
     }
 
@@ -529,14 +529,6 @@
     int			i;
     int			numplayers;
     
-    static char		destination_keys[MAXPLAYERS] =
-    {
-	HUSTR_KEYGREEN,
-	HUSTR_KEYINDIGO,
-	HUSTR_KEYBROWN,
-	HUSTR_KEYRED
-    };
-    
     static int		num_nobrainers = 0;
 
     numplayers = 0;
@@ -565,7 +557,7 @@
 	    message_counter = HU_MSGTIMEOUT;
 	    eatkey = true;
 	}
-	else if (netgame && ev->data2 == HU_INPUTTOGGLE)
+	else if (netgame && ev->data2 == key_multi_msg)
 	{
 	    eatkey = chat_on = true;
 	    HUlib_resetIText(&w_chat);
@@ -575,7 +567,7 @@
 	{
 	    for (i=0; i<MAXPLAYERS ; i++)
 	    {
-		if (ev->data2 == destination_keys[i])
+		if (ev->data2 == key_multi_msgplayer[i])
 		{
 		    if (playeringame[i] && i!=consoleplayer)
 		    {
--- a/src/doom/m_menu.c
+++ b/src/doom/m_menu.c
@@ -707,7 +707,7 @@
 	quickSaveSlot = -2;	// means to pick a slot now
 	return;
     }
-    sprintf(tempstring,DEH_String(QSPROMPT),savegamestrings[quickSaveSlot]);
+    DEH_snprintf(tempstring, 80, QSPROMPT, savegamestrings[quickSaveSlot]);
     M_StartMessage(tempstring,M_QuickSaveResponse,true);
 }
 
@@ -739,7 +739,7 @@
 	M_StartMessage(DEH_String(QSAVESPOT),NULL,false);
 	return;
     }
-    sprintf(tempstring,DEH_String(QLPROMPT),savegamestrings[quickSaveSlot]);
+    DEH_snprintf(tempstring, 80, QLPROMPT, savegamestrings[quickSaveSlot]);
     M_StartMessage(tempstring,M_QuickLoadResponse,true);
 }
 
--- a/src/doom/p_saveg.c
+++ b/src/doom/p_saveg.c
@@ -44,6 +44,7 @@
 
 FILE *save_stream;
 int savegamelength;
+boolean savegame_error;
 
 // Get the filename of a temporary file to write the savegame to.  After
 // the file has been successfully saved, it will be renamed to the 
@@ -75,7 +76,7 @@
         filename = malloc(strlen(savegamedir) + 32);
     }
 
-    sprintf(basename, DEH_String(SAVEGAMENAME "%d.dsg"), slot);
+    DEH_snprintf(basename, 32, SAVEGAMENAME "%d.dsg", slot);
 
     sprintf(filename, "%s%s", savegamedir, basename);
 
@@ -88,14 +89,31 @@
 {
     byte result;
 
-    fread(&result, 1, 1, save_stream);
+    if (fread(&result, 1, 1, save_stream) < 1)
+    {
+        if (!savegame_error)
+        {
+            fprintf(stderr, "saveg_read8: Unexpected end of file while "
+                            "reading save game\n");
 
+            savegame_error = true;
+        }
+    }
+
     return result;
 }
 
 static void saveg_write8(byte value)
 {
-    fwrite(&value, 1, 1, save_stream);
+    if (fwrite(&value, 1, 1, save_stream) < 1)
+    {
+        if (!savegame_error)
+        {
+            fprintf(stderr, "saveg_write8: Error while writing save game\n");
+
+            savegame_error = true;
+        }
+    }
 }
 
 static short saveg_read16(void)
--- a/src/doom/p_saveg.h
+++ b/src/doom/p_saveg.h
@@ -64,6 +64,7 @@
 void P_UnArchiveSpecials (void);
 
 extern FILE *save_stream;
+extern boolean savegame_error;
 
 
 #endif
--- a/src/doom/p_setup.c
+++ b/src/doom/p_setup.c
@@ -33,6 +33,7 @@
 
 #include "deh_main.h"
 #include "i_swap.h"
+#include "m_argv.h"
 #include "m_bbox.h"
 
 #include "g_game.h"
@@ -76,6 +77,7 @@
 int		numsides;
 side_t*		sides;
 
+static int      totallines;
 
 // BLOCKMAP
 // Created from axis aligned bounding box
@@ -534,7 +536,6 @@
     line_t**		linebuffer;
     int			i;
     int			j;
-    int			total;
     line_t*		li;
     sector_t*		sector;
     subsector_t*	ss;
@@ -552,21 +553,21 @@
 
     // count number of lines in each sector
     li = lines;
-    total = 0;
+    totallines = 0;
     for (i=0 ; i<numlines ; i++, li++)
     {
-	total++;
+	totallines++;
 	li->frontsector->linecount++;
 
 	if (li->backsector && li->backsector != li->frontsector)
 	{
 	    li->backsector->linecount++;
-	    total++;
+	    totallines++;
 	}
     }
 
     // build line tables for each sector	
-    linebuffer = Z_Malloc (total*sizeof(line_t *), PU_LEVEL, 0);
+    linebuffer = Z_Malloc (totallines*sizeof(line_t *), PU_LEVEL, 0);
 
     for (i=0; i<numsectors; ++i)
     {
@@ -643,7 +644,88 @@
 	
 }
 
+// Pad the REJECT lump with extra data when the lump is too small,
+// to simulate a REJECT buffer overflow in Vanilla Doom.
 
+static void PadRejectArray(byte *array, unsigned int len)
+{
+    unsigned int i;
+    unsigned int byte_num;
+    byte *dest;
+    unsigned int padvalue;
+
+    // Values to pad the REJECT array with:
+
+    unsigned int rejectpad[4] =
+    {
+        ((totallines * 4 + 3) & ~3) + 24,     // Size
+        0,                                    // Part of z_zone block header
+        50,                                   // PU_LEVEL
+        0x1d4a11                              // DOOM_CONST_ZONEID
+    };
+
+    // Copy values from rejectpad into the destination array.
+
+    dest = array;
+
+    for (i=0; i<len && i<sizeof(rejectpad); ++i)
+    {
+        byte_num = i % 4;
+        *dest = (rejectpad[i / 4] >> (byte_num * 8)) & 0xff;
+        ++dest;
+    }
+
+    // We only have a limited pad size.  Print a warning if the
+    // REJECT lump is too small.
+
+    if (len > sizeof(rejectpad))
+    {
+        fprintf(stderr, "PadRejectArray: REJECT lump too short to pad! (%i > %i)\n",
+                        len, sizeof(rejectpad));
+
+        // Pad remaining space with 0 (or 0xff, if specified on command line).
+
+        if (M_CheckParm("-reject_pad_with_ff"))
+        {
+            padvalue = 0xff;
+        }
+        else
+        {
+            padvalue = 0xf00;
+        }
+
+        memset(array + sizeof(rejectpad), padvalue, len - sizeof(rejectpad));
+    }
+}
+
+static void P_LoadReject(int lumpnum)
+{
+    int minlength;
+    int lumplen;
+
+    // Calculate the size that the REJECT lump *should* be.
+
+    minlength = (numsectors * numsectors + 7) / 8;
+
+    // If the lump meets the minimum length, it can be loaded directly.
+    // Otherwise, we need to allocate a buffer of the correct size
+    // and pad it with appropriate data.
+
+    lumplen = W_LumpLength(lumpnum);
+
+    if (lumplen >= minlength)
+    {
+        rejectmatrix = W_CacheLumpNum(lumpnum, PU_LEVEL);
+    }
+    else
+    {
+        rejectmatrix = Z_Malloc(minlength, PU_LEVEL, &rejectmatrix);
+        W_ReadLump(lumpnum, rejectmatrix);
+
+        PadRejectArray(rejectmatrix + lumplen, minlength - lumplen);
+    }
+}
+
 //
 // P_SetupLevel
 //
@@ -692,9 +774,9 @@
     if ( gamemode == commercial)
     {
 	if (map<10)
-	    sprintf (lumpname, DEH_String("map0%i"), map);
+	    DEH_snprintf(lumpname, 9, "map0%i", map);
 	else
-	    sprintf (lumpname, DEH_String("map%i"), map);
+	    DEH_snprintf(lumpname, 9, "map%i", map);
     }
     else
     {
@@ -719,9 +801,9 @@
     P_LoadSubsectors (lumpnum+ML_SSECTORS);
     P_LoadNodes (lumpnum+ML_NODES);
     P_LoadSegs (lumpnum+ML_SEGS);
-	
-    rejectmatrix = W_CacheLumpNum (lumpnum+ML_REJECT,PU_LEVEL);
+
     P_GroupLines ();
+    P_LoadReject (lumpnum+ML_REJECT);
 
     bodyqueslot = 0;
     deathmatch_p = deathmatchstarts;
--- a/src/doom/r_draw.c
+++ b/src/doom/r_draw.c
@@ -597,51 +597,57 @@
 // Draws the actual span.
 void R_DrawSpan (void) 
 { 
-    fixed_t		xfrac;
-    fixed_t		yfrac; 
-    byte*		dest; 
-    int			count;
-    int			spot; 
-	 
-#ifdef RANGECHECK 
+    unsigned int position, step;
+    byte *dest;
+    int count;
+    int spot;
+    unsigned int xtemp, ytemp;
+
+#ifdef RANGECHECK
     if (ds_x2 < ds_x1
 	|| ds_x1<0
-	|| ds_x2>=SCREENWIDTH  
+	|| ds_x2>=SCREENWIDTH
 	|| (unsigned)ds_y>SCREENHEIGHT)
     {
 	I_Error( "R_DrawSpan: %i to %i at %i",
 		 ds_x1,ds_x2,ds_y);
     }
-//	dscount++; 
-#endif 
+//	dscount++;
+#endif
 
-    
-    xfrac = ds_xfrac; 
-    yfrac = ds_yfrac; 
-	 
+    // Pack position and step variables into a single 32-bit integer,
+    // with x in the top 16 bits and y in the bottom 16 bits.  For
+    // each 16-bit part, the top 6 bits are the integer part and the
+    // bottom 10 bits are the fractional part of the pixel position.
+
+    position = ((ds_xfrac << 10) & 0xffff0000)
+             | ((ds_yfrac >> 6)  & 0x0000ffff);
+    step = ((ds_xstep << 10) & 0xffff0000)
+         | ((ds_ystep >> 6)  & 0x0000ffff);
+
     dest = ylookup[ds_y] + columnofs[ds_x1];
 
     // We do not check for zero spans here?
-    count = ds_x2 - ds_x1; 
+    count = ds_x2 - ds_x1;
 
-    do 
+    do
     {
-	// Current texture index in u,v.
-	spot = ((yfrac>>(16-6))&(63*64)) + ((xfrac>>16)&63);
+	// Calculate current texture index in u,v.
+        ytemp = (position >> 4) & 0x0fc0;
+        xtemp = (position >> 26);
+        spot = xtemp | ytemp;
 
 	// Lookup pixel from flat texture tile,
 	//  re-index using light/colormap.
 	*dest++ = ds_colormap[ds_source[spot]];
 
-	// Next step in u,v.
-	xfrac += ds_xstep; 
-	yfrac += ds_ystep;
-	
-    } while (count--); 
-} 
+        position += step;
 
+    } while (count--);
+}
 
 
+
 // UNUSED.
 // Loop unrolled by 4.
 #if 0
@@ -718,18 +724,18 @@
 //
 // Again..
 //
-void R_DrawSpanLow (void) 
-{ 
-    fixed_t		xfrac;
-    fixed_t		yfrac; 
-    byte*		dest; 
-    int			count;
-    int			spot; 
-	 
-#ifdef RANGECHECK 
+void R_DrawSpanLow (void)
+{
+    unsigned int position, step;
+    unsigned int xtemp, ytemp;
+    byte *dest;
+    int count;
+    int spot;
+
+#ifdef RANGECHECK
     if (ds_x2 < ds_x1
 	|| ds_x1<0
-	|| ds_x2>=SCREENWIDTH  
+	|| ds_x2>=SCREENWIDTH
 	|| (unsigned)ds_y>SCREENHEIGHT)
     {
 	I_Error( "R_DrawSpan: %i to %i at %i",
@@ -736,31 +742,36 @@
 		 ds_x1,ds_x2,ds_y);
     }
 //	dscount++; 
-#endif 
-	 
-    xfrac = ds_xfrac; 
-    yfrac = ds_yfrac; 
-    
-    count = (ds_x2 - ds_x1); 
+#endif
 
+    position = ((ds_xfrac << 10) & 0xffff0000)
+             | ((ds_yfrac >> 6)  & 0x0000ffff);
+    step = ((ds_xstep << 10) & 0xffff0000)
+         | ((ds_ystep >> 6)  & 0x0000ffff);
+
+    count = (ds_x2 - ds_x1);
+
     // Blocky mode, need to multiply by 2.
     ds_x1 <<= 1;
     ds_x2 <<= 1;
-    
+
     dest = ylookup[ds_y] + columnofs[ds_x1];
-  
-    do 
-    { 
-	spot = ((yfrac>>(16-6))&(63*64)) + ((xfrac>>16)&63);
+
+    do
+    {
+	// Calculate current texture index in u,v.
+        ytemp = (position >> 4) & 0x0fc0;
+        xtemp = (position >> 26);
+        spot = xtemp | ytemp;
+
 	// Lowres/blocky mode does it twice,
 	//  while scale is adjusted appropriately.
-	*dest++ = ds_colormap[ds_source[spot]]; 
 	*dest++ = ds_colormap[ds_source[spot]];
-	
-	xfrac += ds_xstep; 
-	yfrac += ds_ystep; 
+	*dest++ = ds_colormap[ds_source[spot]];
 
-    } while (count--); 
+	position += step;
+
+    } while (count--);
 }
 
 //
--- a/src/doom/s_sound.c
+++ b/src/doom/s_sound.c
@@ -618,6 +618,15 @@
     char namebuf[9];
     void *handle;
 
+    // The Doom IWAD file has two versions of the intro music: d_intro
+    // and d_introa.  The latter is used for OPL playback.
+
+    if (musicnum == mus_intro && (snd_musicdevice == SNDDEVICE_ADLIB
+                               || snd_musicdevice == SNDDEVICE_SB))
+    {
+        musicnum = mus_introa;
+    }
+
     if (musicnum <= mus_None || musicnum >= NUMMUSIC)
     {
         I_Error("Bad music number %d", musicnum);
--- a/src/doom/st_stuff.c
+++ b/src/doom/st_stuff.c
@@ -1085,10 +1085,10 @@
     // Load the numbers, tall and short
     for (i=0;i<10;i++)
     {
-	sprintf(namebuf, DEH_String("STTNUM%d"), i);
+	DEH_snprintf(namebuf, 9, "STTNUM%d", i);
         callback(namebuf, &tallnum[i]);
 
-	sprintf(namebuf, DEH_String("STYSNUM%d"), i);
+	DEH_snprintf(namebuf, 9, "STYSNUM%d", i);
         callback(namebuf, &shortnum[i]);
     }
 
@@ -1100,7 +1100,7 @@
     // key cards
     for (i=0;i<NUMCARDS;i++)
     {
-	sprintf(namebuf, DEH_String("STKEYS%d"), i);
+	DEH_snprintf(namebuf, 9, "STKEYS%d", i);
         callback(namebuf, &keys[i]);
     }
 
@@ -1110,7 +1110,7 @@
     // arms ownership widgets
     for (i=0; i<6; i++)
     {
-	sprintf(namebuf, DEH_String("STGNUM%d"), i+2);
+	DEH_snprintf(namebuf, 9, "STGNUM%d", i+2);
 
 	// gray #
         callback(namebuf, &arms[i][0]);
@@ -1120,7 +1120,7 @@
     }
 
     // face backgrounds for different color players
-    sprintf(namebuf, DEH_String("STFB%d"), consoleplayer);
+    DEH_snprintf(namebuf, 9, "STFB%d", consoleplayer);
     callback(namebuf, &faceback);
 
     // status bar background bits
@@ -1132,23 +1132,23 @@
     {
 	for (j=0; j<ST_NUMSTRAIGHTFACES; j++)
 	{
-	    sprintf(namebuf, DEH_String("STFST%d%d"), i, j);
+	    DEH_snprintf(namebuf, 9, "STFST%d%d", i, j);
             callback(namebuf, &faces[facenum]);
             ++facenum;
 	}
-	sprintf(namebuf, DEH_String("STFTR%d0"), i);	// turn right
+	DEH_snprintf(namebuf, 9, "STFTR%d0", i);	// turn right
         callback(namebuf, &faces[facenum]);
         ++facenum;
-	sprintf(namebuf, DEH_String("STFTL%d0"), i);	// turn left
+	DEH_snprintf(namebuf, 9, "STFTL%d0", i);	// turn left
         callback(namebuf, &faces[facenum]);
         ++facenum;
-	sprintf(namebuf, DEH_String("STFOUCH%d"), i);	// ouch!
+	DEH_snprintf(namebuf, 9, "STFOUCH%d", i);	// ouch!
         callback(namebuf, &faces[facenum]);
         ++facenum;
-	sprintf(namebuf, DEH_String("STFEVL%d"), i);	// evil grin ;)
+	DEH_snprintf(namebuf, 9, "STFEVL%d", i);	// evil grin ;)
         callback(namebuf, &faces[facenum]);
         ++facenum;
-	sprintf(namebuf, DEH_String("STFKILL%d"), i);	// pissed off
+	DEH_snprintf(namebuf, 9, "STFKILL%d", i);	// pissed off
         callback(namebuf, &faces[facenum]);
         ++facenum;
     }
--- a/src/doom/wi_stuff.c
+++ b/src/doom/wi_stuff.c
@@ -1571,16 +1571,16 @@
     if (gamemode == commercial)
     {
 	for (i=0 ; i<NUMCMAPS ; i++)
-	{								
-	    sprintf(name, DEH_String("CWILV%2.2d"), i);
+	{
+	    DEH_snprintf(name, 9, "CWILV%2.2d", i);
             callback(name, &lnames[i]);
-	}					
+	}
     }
     else
     {
 	for (i=0 ; i<NUMMAPS ; i++)
 	{
-	    sprintf(name, DEH_String("WILV%d%d"), wbs->epsd, i);
+	    DEH_snprintf(name, 9, "WILV%d%d", wbs->epsd, i);
             callback(name, &lnames[i]);
 	}
 
@@ -1592,7 +1592,7 @@
 
 	// splat
         callback(DEH_String("WISPLAT"), &splat[0]);
-	
+
 	if (wbs->epsd < 3)
 	{
 	    for (j=0;j<NUMANIMS[wbs->epsd];j++)
@@ -1601,17 +1601,16 @@
 		for (i=0;i<a->nanims;i++)
 		{
 		    // MONDO HACK!
-		    if (wbs->epsd != 1 || j != 8) 
+		    if (wbs->epsd != 1 || j != 8)
 		    {
 			// animations
-			sprintf(name, DEH_String("WIA%d%.2d%.2d"), 
-				      wbs->epsd, j, i);  
+			DEH_snprintf(name, 9, "WIA%d%.2d%.2d", wbs->epsd, j, i);
                         callback(name, &a->p[i]);
 		    }
 		    else
 		    {
 			// HACK ALERT!
-			a->p[i] = anims[1][4].p[i]; 
+			a->p[i] = anims[1][4].p[i];
 		    }
 		}
 	    }
@@ -1624,7 +1623,7 @@
     for (i=0;i<10;i++)
     {
 	 // numbers 0-9
-	sprintf(name, DEH_String("WINUM%d"), i);     
+	DEH_snprintf(name, 9, "WINUM%d", i);
         callback(name, &num[i]);
     }
 
@@ -1665,13 +1664,13 @@
     callback(DEH_String("WICOLON"), &colon);
 
     // "time"
-    callback(DEH_String("WITIME"), &timepatch);   
+    callback(DEH_String("WITIME"), &timepatch);
 
     // "sucks"
-    callback(DEH_String("WISUCKS"), &sucks);  
+    callback(DEH_String("WISUCKS"), &sucks);
 
     // "par"
-    callback(DEH_String("WIPAR"), &par);   
+    callback(DEH_String("WIPAR"), &par);
 
     // "killers" (vertical)
     callback(DEH_String("WIKILRS"), &killers);
@@ -1680,16 +1679,16 @@
     callback(DEH_String("WIVCTMS"), &victims);
 
     // "total"
-    callback(DEH_String("WIMSTT"), &total);   
+    callback(DEH_String("WIMSTT"), &total);
 
     for (i=0 ; i<MAXPLAYERS ; i++)
     {
 	// "1,2,3,4"
-	sprintf(name, DEH_String("STPB%d"), i);      
+	DEH_snprintf(name, 9, "STPB%d", i);
         callback(name, &p[i]);
 
 	// "1,2,3,4"
-	sprintf(name, DEH_String("WIBP%d"), i+1);     
+	DEH_snprintf(name, 9, "WIBP%d", i+1);
         callback(name, &bp[i]);
     }
 
@@ -1697,19 +1696,21 @@
 
     if (gamemode == commercial)
     {
-	strcpy(name, DEH_String("INTERPIC"));
+	strncpy(name, DEH_String("INTERPIC"), 9);
+        name[8] = '\0';
     }
     else if (gamemode == retail && wbs->epsd == 3)
     {
-	strcpy(name, DEH_String("INTERPIC"));
+	strncpy(name, DEH_String("INTERPIC"), 9);
+        name[8] = '\0';
     }
-    else 
+    else
     {
-	sprintf(name, DEH_String("WIMAP%d"), wbs->epsd);
+	DEH_snprintf(name, 9, "WIMAP%d", wbs->epsd);
     }
-    
+
     // Draw backdrop and save to a temporary buffer
-  
+
     callback(name, &background);
 }
 
@@ -1722,7 +1723,7 @@
 {
     if (gamemode == commercial)
     {
-	NUMCMAPS = 32;								
+	NUMCMAPS = 32;
 	lnames = (patch_t **) Z_Malloc(sizeof(patch_t*) * NUMCMAPS,
 				       PU_STATIC, NULL);
     }
--- a/src/heretic/.gitignore
+++ b/src/heretic/.gitignore
@@ -1,7 +1,6 @@
 Makefile
 Makefile.in
 .deps
-*.rc
-chocolate-doom
-chocolate-server
-*.exe
+tags
+TAGS
+
--- a/src/heretic/Makefile.am
+++ b/src/heretic/Makefile.am
@@ -20,6 +20,7 @@
 in_lude.c                                            \
 m_random.c             m_random.h                    \
 mn_menu.c                                            \
+                       p_action.h                    \
 p_ceilng.c                                           \
 p_doors.c                                            \
 p_enemy.c                                            \
@@ -55,5 +56,15 @@
 i_sound.c                                            \
 i_ibm.c   
 
-libheretic_a_SOURCES=$(SOURCE_FILES)
+FEATURE_DEHACKED_SOURCE_FILES =            \
+deh_ammo.c                                 \
+deh_frame.c                                \
+deh_htext.c                                \
+deh_htic.c                                 \
+deh_sound.c                                \
+deh_thing.c                                \
+deh_weapon.c
+
+libheretic_a_SOURCES=$(SOURCE_FILES)                  \
+                     $(FEATURE_DEHACKED_SOURCE_FILES)
 
--- a/src/heretic/am_map.c
+++ b/src/heretic/am_map.c
@@ -27,6 +27,7 @@
 #include <stdio.h>
 
 #include "doomdef.h"
+#include "deh_str.h"
 #include "i_video.h"
 #include "m_controls.h"
 #include "p_local.h"
@@ -408,7 +409,7 @@
     sprintf(namebuf, "AMMNUM%d", i);
     marknums[i] = W_CacheLumpName(namebuf, PU_STATIC);
   }*/
-    maplump = W_CacheLumpName("AUTOPAGE", PU_STATIC);
+    maplump = W_CacheLumpName(DEH_String("AUTOPAGE"), PU_STATIC);
 }
 
 /*void AM_unloadPics(void)
@@ -1473,6 +1474,7 @@
 
 void AM_Drawer(void)
 {
+    char *level_name;
     int numepisodes;
 
     if (!automapactive)
@@ -1505,7 +1507,8 @@
 
     if (gameepisode <= numepisodes && gamemap < 10)
     {
-        MN_DrTextA(LevelNames[(gameepisode - 1) * 9 + gamemap - 1], 20, 145);
+        level_name = LevelNames[(gameepisode - 1) * 9 + gamemap - 1];
+        MN_DrTextA(DEH_String(level_name), 20, 145);
     }
 //  I_Update();
 //  V_MarkRect(f_x, f_y, f_w, f_h);
--- a/src/heretic/ct_chat.c
+++ b/src/heretic/ct_chat.c
@@ -29,6 +29,7 @@
 #include <ctype.h>
 #include "doomdef.h"
 #include "doomkeys.h"
+#include "deh_str.h"
 #include "p_local.h"
 #include "s_sound.h"
 #include "v_video.h"
@@ -115,7 +116,7 @@
         memset(plr_lastmsg[i], 0, MESSAGESIZE);
         memset(chat_msg[i], 0, MESSAGESIZE);
     }
-    FontABaseLump = W_GetNumForName("FONTA_S") + 1;
+    FontABaseLump = W_GetNumForName(DEH_String("FONTA_S")) + 1;
     return;
 }
 
@@ -300,7 +301,9 @@
                 CT_AddChar(i, 0);       // set the end of message character
                 if (numplayers > 2)
                 {
-                    strcpy(plr_lastmsg[i], CT_FromPlrText[i]);
+                    strncpy(plr_lastmsg[i], DEH_String(CT_FromPlrText[i]),
+                            MESSAGESIZE + 9);
+                    plr_lastmsg[i][MESSAGESIZE + 8] = '\0';
                     strcat(plr_lastmsg[i], chat_msg[i]);
                 }
                 else
@@ -320,13 +323,13 @@
                     if (numplayers > 1)
                     {
                         P_SetMessage(&players[consoleplayer],
-                                     "-MESSAGE SENT-", true);
+                                     DEH_String("-MESSAGE SENT-"), true);
                         S_StartSound(NULL, sfx_chat);
                     }
                     else
                     {
                         P_SetMessage(&players[consoleplayer],
-                                     "THERE ARE NO OTHER PLAYERS IN THE GAME!",
+                                     DEH_String("THERE ARE NO OTHER PLAYERS IN THE GAME!"),
                                      true);
                         S_StartSound(NULL, sfx_chat);
                     }
@@ -376,7 +379,7 @@
                 x += patch->width;
             }
         }
-        V_DrawPatch(x, 10, W_CacheLumpName("FONTA59", PU_CACHE));
+        V_DrawPatch(x, 10, W_CacheLumpName(DEH_String("FONTA59"), PU_CACHE));
         BorderTopRefresh = true;
         UpdateState |= I_MESSAGES;
     }
--- a/src/heretic/d_main.c
+++ b/src/heretic/d_main.c
@@ -33,6 +33,7 @@
 #include "config.h"
 #include "ct_chat.h"
 #include "doomdef.h"
+#include "deh_main.h"
 #include "d_iwad.h"
 #include "i_endoom.h"
 #include "i_joystick.h"
@@ -45,6 +46,7 @@
 #include "m_controls.h"
 #include "p_local.h"
 #include "s_sound.h"
+#include "w_main.h"
 #include "v_video.h"
 
 #define STARTUP_WINDOW_X 17
@@ -184,12 +186,12 @@
     {
         if (!netgame)
         {
-            V_DrawPatch(160, viewwindowy + 5, W_CacheLumpName("PAUSED",
+            V_DrawPatch(160, viewwindowy + 5, W_CacheLumpName(DEH_String("PAUSED"),
                                                               PU_CACHE));
         }
         else
         {
-            V_DrawPatch(160, 70, W_CacheLumpName("PAUSED", PU_CACHE));
+            V_DrawPatch(160, 70, W_CacheLumpName(DEH_String("PAUSED"), PU_CACHE));
         }
     }
     // Handle player messages
@@ -315,7 +317,7 @@
     V_DrawRawScreen(W_CacheLumpName(pagename, PU_CACHE));
     if (demosequence == 1)
     {
-        V_DrawPatch(4, 160, W_CacheLumpName("ADVISOR", PU_CACHE));
+        V_DrawPatch(4, 160, W_CacheLumpName(DEH_String("ADVISOR"), PU_CACHE));
     }
     UpdateState |= I_FULLSCRN;
 }
@@ -347,28 +349,28 @@
         case 0:
             pagetic = 210;
             gamestate = GS_DEMOSCREEN;
-            pagename = "TITLE";
+            pagename = DEH_String("TITLE");
             S_StartSong(mus_titl, false);
             break;
         case 1:
             pagetic = 140;
             gamestate = GS_DEMOSCREEN;
-            pagename = "TITLE";
+            pagename = DEH_String("TITLE");
             break;
         case 2:
             BorderNeedRefresh = true;
             UpdateState |= I_FULLSCRN;
-            G_DeferedPlayDemo("demo1");
+            G_DeferedPlayDemo(DEH_String("demo1"));
             break;
         case 3:
             pagetic = 200;
             gamestate = GS_DEMOSCREEN;
-            pagename = "CREDIT";
+            pagename = DEH_String("CREDIT");
             break;
         case 4:
             BorderNeedRefresh = true;
             UpdateState |= I_FULLSCRN;
-            G_DeferedPlayDemo("demo2");
+            G_DeferedPlayDemo(DEH_String("demo2"));
             break;
         case 5:
             pagetic = 200;
@@ -375,17 +377,17 @@
             gamestate = GS_DEMOSCREEN;
             if (gamemode == shareware)
             {
-                pagename = "ORDER";
+                pagename = DEH_String("ORDER");
             }
             else
             {
-                pagename = "CREDIT";
+                pagename = DEH_String("CREDIT");
             }
             break;
         case 6:
             BorderNeedRefresh = true;
             UpdateState |= I_FULLSCRN;
-            G_DeferedPlayDemo("demo3");
+            G_DeferedPlayDemo(DEH_String("demo3"));
             break;
     }
 }
@@ -638,7 +640,7 @@
 
     // Blit main screen
     textScreen = TXT_GetScreenData();
-    loading = W_CacheLumpName("LOADING", PU_CACHE);
+    loading = W_CacheLumpName(DEH_String("LOADING"), PU_CACHE);
     memcpy(textScreen, loading, 4000);
 
     // Print version string
@@ -698,7 +700,7 @@
 // haleyjd: moved up, removed WATCOMC code
 void CleanExit(void)
 {
-    printf("Exited from HERETIC.\n");
+    DEH_printf("Exited from HERETIC.\n");
     exit(1);
 }
 
@@ -746,6 +748,7 @@
     M_BindBaseControls();
     M_BindHereticControls();
     M_BindWeaponControls();
+    M_BindChatControls(MAXPLAYERS);
 
     M_BindMenuControls();
     M_BindMapControls();
@@ -782,7 +785,7 @@
         return;
     }
 
-    endoom_data = W_CacheLumpName("ENDTEXT", PU_STATIC);
+    endoom_data = W_CacheLumpName(DEH_String("ENDTEXT"), PU_STATIC);
 
     I_Endoom(endoom_data);
 }
@@ -847,7 +850,7 @@
 //
 // init subsystems
 //
-    printf("V_Init: allocate screens.\n");
+    DEH_printf("V_Init: allocate screens.\n");
     V_Init();
 
     // Check for -CDROM
@@ -872,7 +875,7 @@
 
     if (cdrom)
     {
-        M_SetConfigDir("c:\\heretic.cd\\");
+        M_SetConfigDir(DEH_String("c:\\heretic.cd"));
     }
     else
     {
@@ -880,7 +883,7 @@
     }
 
     // Load defaults before initing other systems
-    printf("M_LoadDefaults: Load system defaults.\n");
+    DEH_printf("M_LoadDefaults: Load system defaults.\n");
     D_BindVariables();
     M_SetConfigFilenames("heretic.cfg", PROGRAM_PREFIX "heretic.cfg");
     M_LoadDefaults();
@@ -887,11 +890,16 @@
 
     I_AtExit(M_SaveDefaults, false);
 
-    printf("Z_Init: Init zone memory allocation daemon.\n");
+    DEH_printf("Z_Init: Init zone memory allocation daemon.\n");
     Z_Init();
 
-    printf("W_Init: Init WADfiles.\n");
+#ifdef FEATURE_DEHACKED
+    printf("DEH_Init: Init Dehacked support.\n");
+    DEH_Init();
+#endif
 
+    DEH_printf("W_Init: Init WADfiles.\n");
+
     iwadfile = D_FindIWAD(IWAD_MASK_HERETIC, &gamemission);
 
     if (iwadfile == NULL)
@@ -901,25 +909,8 @@
     }
 
     D_AddFile(iwadfile);
+    W_ParseCommandLine();
 
-    // -FILE [filename] [filename] ...
-    // Add files to the wad list.
-    p = M_CheckParm("-file");
-
-    if (p)
-    {
-        char *filename;
-
-        // the parms after p are wadfile/lump names, until end of parms
-        // or another - preceded parm
-
-        while (++p != myargc && myargv[p][0] != '-')
-        {
-            filename = D_FindWADByName(myargv[p]);
-            D_AddFile(filename);
-        }
-    }
-
     p = M_CheckParm("-playdemo");
     if (!p)
     {
@@ -927,12 +918,12 @@
     }
     if (p && p < myargc - 1)
     {
-        sprintf(file, "%s.lmp", myargv[p + 1]);
+        DEH_snprintf(file, sizeof(file), "%s.lmp", myargv[p + 1]);
         D_AddFile(file);
-        printf("Playing demo %s.lmp.\n", myargv[p + 1]);
+        DEH_printf("Playing demo %s.lmp.\n", myargv[p + 1]);
     }
 
-    if (W_CheckNumForName("E2M1") == -1)
+    if (W_CheckNumForName(DEH_String("E2M1")) == -1)
     {
         gamemode = shareware;
         gamedescription = "Heretic (shareware)";
@@ -960,54 +951,55 @@
     //
     smsg[0] = 0;
     if (deathmatch)
-        status("DeathMatch...");
+        status(DEH_String("DeathMatch..."));
     if (nomonsters)
-        status("No Monsters...");
+        status(DEH_String("No Monsters..."));
     if (respawnparm)
-        status("Respawning...");
+        status(DEH_String("Respawning..."));
     if (autostart)
     {
         char temp[64];
-        sprintf(temp, "Warp to Episode %d, Map %d, Skill %d ",
-                startepisode, startmap, startskill + 1);
+        DEH_snprintf(temp, sizeof(temp),
+                     "Warp to Episode %d, Map %d, Skill %d ",
+                     startepisode, startmap, startskill + 1);
         status(temp);
     }
     wadprintf();                // print the added wadfiles
 
-    tprintf("MN_Init: Init menu system.\n", 1);
+    tprintf(DEH_String("MN_Init: Init menu system.\n"), 1);
     MN_Init();
 
     CT_Init();
 
-    tprintf("R_Init: Init Heretic refresh daemon.", 1);
-    hprintf("Loading graphics");
+    tprintf(DEH_String("R_Init: Init Heretic refresh daemon."), 1);
+    hprintf(DEH_String("Loading graphics"));
     R_Init();
     tprintf("\n", 0);
 
-    tprintf("P_Init: Init Playloop state.\n", 1);
-    hprintf("Init game engine.");
+    tprintf(DEH_String("P_Init: Init Playloop state.\n"), 1);
+    hprintf(DEH_String("Init game engine."));
     P_Init();
     IncThermo();
 
-    tprintf("I_Init: Setting up machine state.\n", 1);
+    tprintf(DEH_String("I_Init: Setting up machine state.\n"), 1);
     I_CheckIsScreensaver();
     I_InitTimer();
     I_InitJoystick();
     IncThermo();
 
-    tprintf("S_Init: Setting up sound.\n", 1);
+    tprintf(DEH_String("S_Init: Setting up sound.\n"), 1);
     S_Init();
     //IO_StartupTimer();
     S_Start();
 
-    tprintf("D_CheckNetGame: Checking network game status.\n", 1);
-    hprintf("Checking network game status.");
+    tprintf(DEH_String("D_CheckNetGame: Checking network game status.\n"), 1);
+    hprintf(DEH_String("Checking network game status."));
     D_CheckNetGame();
     IncThermo();
 
     // haleyjd: removed WATCOMC
 
-    tprintf("SB_Init: Loading patches.\n", 1);
+    tprintf(DEH_String("SB_Init: Loading patches.\n"), 1);
     SB_Init();
     IncThermo();
 
--- /dev/null
+++ b/src/heretic/deh_ammo.c
@@ -1,0 +1,122 @@
+// Emacs style mode select   -*- C++ -*- 
+//-----------------------------------------------------------------------------
+//
+// Copyright(C) 2005 Simon Howard
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+// 02111-1307, USA.
+//
+//-----------------------------------------------------------------------------
+//
+// Parses "Ammo" sections in dehacked files
+//
+//-----------------------------------------------------------------------------
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "doomdef.h"
+#include "doomtype.h"
+#include "deh_defs.h"
+#include "deh_io.h"
+#include "deh_main.h"
+#include "p_local.h"
+
+static void *DEH_AmmoStart(deh_context_t *context, char *line)
+{
+    int ammo_number = 0;
+
+    if (sscanf(line, "Ammo %i", &ammo_number) != 1)
+    {
+        DEH_Warning(context, "Parse error on section start");
+        return NULL;
+    }
+
+    if (ammo_number < 0 || ammo_number >= NUMAMMO)
+    {
+        DEH_Warning(context, "Invalid ammo number: %i", ammo_number);
+        return NULL;
+    }
+
+    return &maxammo[ammo_number];
+}
+
+static void DEH_AmmoParseLine(deh_context_t *context, char *line, void *tag)
+{
+    char *variable_name, *value;
+    int ivalue;
+    int ammo_number;
+
+    if (tag == NULL)
+        return;
+
+    ammo_number = ((int *) tag) - maxammo;
+
+    // Parse the assignment
+
+    if (!DEH_ParseAssignment(line, &variable_name, &value))
+    {
+        // Failed to parse
+
+        DEH_Warning(context, "Failed to parse assignment");
+        return;
+    }
+
+    ivalue = atoi(value);
+
+    if (!strcasecmp(variable_name, "Per ammo"))
+    {
+        // Heretic doesn't have a "per clip" ammo array, instead
+        // it is per weapon.  However, the weapon number lines
+        // up with the ammo number if we add one.
+
+        GetWeaponAmmo[ammo_number + 1] = ivalue;
+    }
+    else if (!strcasecmp(variable_name, "Max ammo"))
+    {
+        maxammo[ammo_number] = ivalue;
+    }
+    else
+    {
+        DEH_Warning(context, "Field named '%s' not found", variable_name);
+    }
+}
+
+static void DEH_AmmoMD5Hash(md5_context_t *context)
+{
+    int i;
+
+    for (i=0; i<NUMAMMO; ++i)
+    {
+        MD5_UpdateInt32(context, maxammo[i]);
+    }
+
+    for (i=0; i<NUMWEAPONS; ++i)
+    {
+        MD5_UpdateInt32(context, GetWeaponAmmo[i]);
+    }
+}
+
+deh_section_t deh_section_ammo =
+{
+    "Ammo",
+    NULL,
+    DEH_AmmoStart,
+    DEH_AmmoParseLine,
+    NULL,
+    DEH_AmmoMD5Hash,
+};
+
--- /dev/null
+++ b/src/heretic/deh_frame.c
@@ -1,0 +1,344 @@
+// Emacs style mode select   -*- C++ -*- 
+//-----------------------------------------------------------------------------
+//
+// Copyright(C) 2005 Simon Howard
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+// 02111-1307, USA.
+//
+//-----------------------------------------------------------------------------
+//
+// Parses "Frame" sections in dehacked files
+//
+//-----------------------------------------------------------------------------
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "doomtype.h"
+#include "info.h"
+
+#include "deh_defs.h"
+#include "deh_io.h"
+#include "deh_main.h"
+#include "deh_mapping.h"
+#include "deh_htic.h"
+
+#include "p_action.h"
+
+typedef struct
+{
+    int offsets[deh_hhe_num_versions];
+    void (*func)();
+} hhe_action_pointer_t;
+
+// Offsets of action pointers within the Heretic executables.
+// Different versions have different offsets.
+// (Seriously Greg, was this really necessary?  What was wrong with the
+// "copying action pointer from another frame" technique used in dehacked?)
+
+//    Offset                      Action function
+//        v1.0    v1.2    v1.3
+
+static const hhe_action_pointer_t action_pointers[] =
+{
+    { {  77680,  80144,  80208 }, A_AccTeleGlitter },
+    { {  78608,  81104,  81168 }, A_AddPlayerCorpse },
+    { { 115808, 118000, 118240 }, A_AddPlayerRain },
+    { { 112272, 114480, 114720 }, A_BeakAttackPL1 },
+    { { 112448, 114656, 114896 }, A_BeakAttackPL2 },
+    { { 111856, 114176, 114416 }, A_BeakRaise },
+    { { 111568, 113888, 114128 }, A_BeakReady },
+    { {  74640,  77120,  77184 }, A_BeastAttack },
+    { {  70480,  72992,  73056 }, A_BeastPuff },
+    { {  73120,  75600,  75664 }, A_BlueSpark },
+    { { 115456, 117648, 117888 }, A_BoltSpark },
+    { {  77344,  79808,  79872 }, A_BossDeath },
+    { {  69328,  71856,  71920 }, A_Chase },
+    { {      0,  80976,  81040 }, A_CheckBurnGone },
+    { {  78480,  80944,  81008 }, A_CheckSkullDone },
+    { {  78448,  80912,  80976 }, A_CheckSkullFloor },
+    { {  71376,  73888,  73952 }, A_ChicAttack },
+    { {  71488,  74000,  74064 }, A_ChicChase },
+    { {  71456,  73968,  74032 }, A_ChicLook },
+    { {  71520,  74032,  74096 }, A_ChicPain },
+    { {  75792,  78208,  78272 }, A_ClinkAttack },
+    { { 108432, 110816, 111056 }, A_ContMobjSound },
+    { { 114752, 116944, 117184 }, A_DeathBallImpact },
+    { {  70016,  72528,  72592 }, A_DripBlood },
+    { {  77472,  79936,  80000 }, A_ESound },
+    { {  76784,  79248,  79312 }, A_Explode },
+    { {  69872,  72400,  72464 }, A_FaceTarget },
+    { {  71568,  74080,  74144 }, A_Feathers },
+    { { 112928, 115136, 115376 }, A_FireBlasterPL1 },
+    { { 113072, 115280, 115520 }, A_FireBlasterPL2 },
+    { { 115232, 117424, 117664 }, A_FireCrossbowPL1 },
+    { { 115312, 117504, 117744 }, A_FireCrossbowPL2 },
+    { { 113152, 115360, 115600 }, A_FireGoldWandPL1 },
+    { { 113296, 115504, 115744 }, A_FireGoldWandPL2 },
+    { { 113760, 115968, 116208 }, A_FireMacePL1 },
+    { { 114624, 116816, 117056 }, A_FireMacePL2 },
+    { { 116368, 118544, 118784 }, A_FirePhoenixPL1 },
+    { { 116736, 118896, 119136 }, A_FirePhoenixPL2 },
+    { { 115568, 117760, 118000 }, A_FireSkullRodPL1 },
+    { { 115648, 117840, 118080 }, A_FireSkullRodPL2 },
+    { { 117120, 119280, 119520 }, A_FlameEnd },
+    { {  78704,  81200,  81264 }, A_FlameSnd },
+    { { 117152, 119312, 119552 }, A_FloatPuff },
+    { {  78512,  81008,  81072 }, A_FreeTargMobj },
+    { { 117184, 119344, 119584 }, A_GauntletAttack },
+    { {  73232,  75712,  75776 }, A_GenWizard },
+    { {  75872,  78304,  78368 }, A_GhostOff },
+    { {  74752,  77232,  77296 }, A_HeadAttack },
+    { {  75488,  77984,  78048 }, A_HeadFireGrow },
+    { {  75328,  77824,  77888 }, A_HeadIceImpact },
+    { { 116336, 118512, 118752 }, A_HideInCeiling },
+    { {  78736,  81232,  81296 }, A_HideThing },
+    { {  70976,  73488,  73552 }, A_ImpDeath },
+    { {  70304,  72816,  72880 }, A_ImpExplode },
+    { {  70592,  73104,  73168 }, A_ImpMeAttack },
+    { {  70672,  73184,  73248 }, A_ImpMsAttack },
+    { {  70880,  73392,  73456 }, A_ImpMsAttack2 },
+    { {  71024,  73536,  73600 }, A_ImpXDeath1 },
+    { {  71072,  73584,  73648 }, A_ImpXDeath2 },
+    { {  77728,  80192,  80256 }, A_InitKeyGizmo },
+    { { 116720, 118880, 119120 }, A_InitPhoenixPL2 },
+    { {  70160,  72672,  72736 }, A_KnightAttack },
+    { { 117648, 119824, 120064 }, A_Light0 },
+    { {  69200,  71728,  71792 }, A_Look },
+    { { 111760, 114080, 114320 }, A_Lower },
+    { { 114032, 116224, 116464 }, A_MaceBallImpact },
+    { { 114192, 116384, 116624 }, A_MaceBallImpact2 },
+    { { 113904, 116112, 116352 }, A_MacePL1Check },
+    { {  77104,  79568,  79632 }, A_MakePod },
+    { {  73648,  76128,  76192 }, A_MinotaurAtk1 },
+    { {  74112,  76592,  76656 }, A_MinotaurAtk2 },
+    { {  74352,  76832,  76896 }, A_MinotaurAtk3 },
+    { {  74032,  76512,  76576 }, A_MinotaurCharge },
+    { {  73760,  76240,  76304 }, A_MinotaurDecide },
+    { {  74528,  77008,  77072 }, A_MntrFloorFire },
+    { {  71808,  74288,  74352 }, A_MummyAttack },
+    { {  71920,  74400,  74464 }, A_MummyAttack2 },
+    { {  72016,  74496,  74560 }, A_MummyFX1Seek },
+    { {  72048,  74528,  74592 }, A_MummySoul },
+    { {  76400,  78832,  78896 }, A_NoBlocking },
+    { {  69984,  72496,  72560 }, A_Pain },
+    { { 116496, 118656, 118896 }, A_PhoenixPuff },
+    { {  76896,  79360,  79424 }, A_PodPain },
+    { { 116272, 118448, 118688 }, A_RainImpact },
+    { { 111920, 114240, 114480 }, A_Raise },
+    { { 111696, 114016, 114256 }, A_ReFire },
+    { {  77056,  79520,  79584 }, A_RemovePod },
+    { { 116480,      0,      0 }, A_RemovedPhoenixFunc },
+    { {  81952,  84464,  84528 }, A_RestoreArtifact },
+    { {  82048,  84544,  84608 }, A_RestoreSpecialThing1 },
+    { {  82128,  84592,  84656 }, A_RestoreSpecialThing2 },
+    { {  76144,  78576,  78640 }, A_Scream },
+    { { 117104, 119264, 119504 }, A_ShutdownPhoenixPL2 },
+    { {  78288,  80752,  80816 }, A_SkullPop },
+    { { 115776, 117968, 118208 }, A_SkullRodPL2Seek },
+    { { 115984, 118176, 118416 }, A_SkullRodStorm },
+    { {  75632,  78048,  78112 }, A_SnakeAttack },
+    { {  75712,  78128,  78192 }, A_SnakeAttack2 },
+    { {  72144,  74624,  74688 }, A_Sor1Chase },
+    { {  72096,  74576,  74640 }, A_Sor1Pain },
+    { {  73392,  75872,  75936 }, A_Sor2DthInit },
+    { {  73424,  75904,  75968 }, A_Sor2DthLoop },
+    { {  73584,  76064,  76128 }, A_SorDBon },
+    { {  73552,  76032,  76096 }, A_SorDExp },
+    { {  73520,  76000,  76064 }, A_SorDSph },
+    { {  73488,  75968,  76032 }, A_SorRise },
+    { {  73616,  76096,  76160 }, A_SorSightSnd },
+    { {  73456,  75936,  76000 }, A_SorZap },
+    { {  72480,  74960,  75024 }, A_SorcererRise },
+    { { 115088, 117280, 117520 }, A_SpawnRippers },
+    { {  77520,  79984,  80048 }, A_SpawnTeleGlitter },
+    { {  77600,  80064,  80128 }, A_SpawnTeleGlitter2 },
+    { {  72192,  74672,  74736 }, A_Srcr1Attack },
+    { {  72896,  75376,  75440 }, A_Srcr2Attack },
+    { {  72816,  75296,  75360 }, A_Srcr2Decide },
+    { { 112640, 114848, 115088 }, A_StaffAttackPL1 },
+    { { 112784, 114992, 115232 }, A_StaffAttackPL2 },
+    { {  78752,  81248,  81312 }, A_UnHideThing },
+    { {  78080,  80544,  80608 }, A_VolcBallImpact },
+    { {  77856,  80320,  80384 }, A_VolcanoBlast },
+    { {  77824,  80288,  80352 }, A_VolcanoSet },
+    { { 111168, 113488, 113728 }, A_WeaponReady },
+    { {  75168,  77664,  77728 }, A_WhirlwindSeek },
+    { {  75888,  78320,  78384 }, A_WizAtk1 },
+    { {  75920,  78352,  78416 }, A_WizAtk2 },
+    { {  75952,  78384,  78448 }, A_WizAtk3 },
+};
+
+DEH_BEGIN_MAPPING(state_mapping, state_t)
+  DEH_MAPPING("Sprite number",    sprite)
+  DEH_MAPPING("Sprite subnumber", frame)
+  DEH_MAPPING("Duration",         tics)
+  DEH_MAPPING("Next frame",       nextstate)
+  DEH_MAPPING("Unknown 1",        misc1)
+  DEH_MAPPING("Unknown 2",        misc2)
+DEH_END_MAPPING
+
+static void DEH_FrameInit(void)
+{
+    // Bit of a hack here:
+    DEH_HereticInit();
+}
+
+static void *DEH_FrameStart(deh_context_t *context, char *line)
+{
+    int frame_number = 0;
+    int mapped_frame_number;
+    state_t *state;
+
+    if (sscanf(line, "Frame %i", &frame_number) != 1)
+    {
+        DEH_Warning(context, "Parse error on section start");
+        return NULL;
+    }
+
+    // Map the HHE frame number (which assumes a Heretic 1.0 state table)
+    // to the internal frame number (which is is the Heretic 1.3 state table):
+
+    mapped_frame_number = DEH_MapHereticFrameNumber(frame_number);
+
+    if (mapped_frame_number < 0 || mapped_frame_number >= DEH_HERETIC_NUMSTATES)
+    {
+        DEH_Warning(context, "Invalid frame number: %i", frame_number);
+        return NULL;
+    }
+
+    state = &states[mapped_frame_number];
+
+    return state;
+}
+
+static boolean GetActionPointerForOffset(int offset, void **result)
+{
+    int i;
+
+    // Special case.
+
+    if (offset == 0)
+    {
+        *result = NULL;
+        return true;
+    }
+
+    for (i=0; i<arrlen(action_pointers); ++i)
+    {
+        if (action_pointers[i].offsets[deh_hhe_version] == offset)
+        {
+            *result = action_pointers[i].func;
+            return true;
+        }
+    }
+
+    return false;
+}
+
+// If an invalid action pointer is specified, the patch may be for a
+// different version from the version we are currently set to.  Try to
+// suggest a different version to use.
+
+static void SuggestOtherVersions(unsigned int offset)
+{
+    unsigned int i, v;
+
+    for (i=0; i<arrlen(action_pointers); ++i)
+    {
+        for (v=0; v<deh_hhe_num_versions; ++v)
+        {
+            if (action_pointers[i].offsets[v] == offset)
+            {
+                DEH_SuggestHereticVersion(v);
+            }
+        }
+    }
+}
+
+static void DEH_FrameParseLine(deh_context_t *context, char *line, void *tag)
+{
+    state_t *state;
+    char *variable_name, *value;
+    int ivalue;
+
+    if (tag == NULL)
+       return;
+
+    state = (state_t *) tag;
+
+    // Parse the assignment
+
+    if (!DEH_ParseAssignment(line, &variable_name, &value))
+    {
+        // Failed to parse
+
+        DEH_Warning(context, "Failed to parse assignment");
+        return;
+    }
+
+    // all values are integers
+
+    ivalue = atoi(value);
+
+    // Action pointer field is a special case:
+
+    if (!strcasecmp(variable_name, "Action pointer"))
+    {
+        void *func;
+
+        if (!GetActionPointerForOffset(ivalue, &func))
+        {
+            SuggestOtherVersions(ivalue);
+            DEH_Error(context, "Unknown action pointer: %i", ivalue);
+            return;
+        }
+
+        state->action = func;
+    }
+    else
+    {
+        // "Next frame" numbers need to undergo mapping.
+
+        if (!strcasecmp(variable_name, "Next frame"))
+        {
+            ivalue = DEH_MapHereticFrameNumber(ivalue);
+        }
+
+        DEH_SetMapping(context, &state_mapping, state, variable_name, ivalue);
+    }
+}
+
+static void DEH_FrameMD5Sum(md5_context_t *context)
+{
+    int i;
+
+    for (i=0; i<NUMSTATES; ++i)
+    {
+        DEH_StructMD5Sum(context, &state_mapping, &states[i]);
+    }
+}
+
+deh_section_t deh_section_frame =
+{
+    "Frame",
+    DEH_FrameInit,
+    DEH_FrameStart,
+    DEH_FrameParseLine,
+    NULL,
+    DEH_FrameMD5Sum,
+};
+
--- /dev/null
+++ b/src/heretic/deh_htext.c
@@ -1,0 +1,856 @@
+// Emacs style mode select   -*- C++ -*- 
+//-----------------------------------------------------------------------------
+//
+// Copyright(C) 2005-2010 Simon Howard
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+// 02111-1307, USA.
+//
+//-----------------------------------------------------------------------------
+//
+// Parses Text substitution sections in dehacked files
+//
+//-----------------------------------------------------------------------------
+
+#include <stdio.h>
+#include <string.h>
+
+#include "doomtype.h"
+#include "dstrings.h"
+
+#include "z_zone.h"
+
+#include "deh_defs.h"
+#include "deh_io.h"
+#include "deh_htic.h"
+#include "deh_main.h"
+
+//
+// Ok, Greg, the action pointers thing was bad enough, but this really
+// takes the biscuit.  Why does HHE's text replacement address strings
+// by offset??!!  The dehacked way was much nicer, why change it?
+//
+
+typedef struct
+{
+    unsigned int offsets[deh_hhe_num_versions];
+    char *string;
+} hhe_string_t;
+
+//     Offsets                 String
+//       v1.0   v1.2   v1.3
+
+static const hhe_string_t strings[] =
+{
+    { {   228,   228,   228 }, "PLAYPAL" },
+    { {  1240,  1252,  1252 }, "E1M1:  THE DOCKS" },
+    { {  1260,  1272,  1272 }, "E1M2:  THE DUNGEONS" },
+    { {  1280,  1292,  1292 }, "E1M3:  THE GATEHOUSE" },
+    { {  1304,  1316,  1316 }, "E1M4:  THE GUARD TOWER" },
+    { {  1328,  1340,  1340 }, "E1M5:  THE CITADEL" },
+    { {  1348,  1360,  1360 }, "E1M6:  THE CATHEDRAL" },
+    { {  1372,  1384,  1384 }, "E1M7:  THE CRYPTS" },
+    { {  1392,  1404,  1404 }, "E1M8:  HELL'S MAW" },
+    { {  1412,  1424,  1424 }, "E1M9:  THE GRAVEYARD" },
+    { {  1436,  1448,  1448 }, "E2M1:  THE CRATER" },
+    { {  1456,  1468,  1468 }, "E2M2:  THE LAVA PITS" },
+    { {  1480,  1492,  1492 }, "E2M3:  THE RIVER OF FIRE" },
+    { {  1508,  1520,  1520 }, "E2M4:  THE ICE GROTTO" },
+    { {  1532,  1544,  1544 }, "E2M5:  THE CATACOMBS" },
+    { {  1556,  1568,  1568 }, "E2M6:  THE LABYRINTH" },
+    { {  1580,  1592,  1592 }, "E2M7:  THE GREAT HALL" },
+    { {  1604,  1616,  1616 }, "E2M8:  THE PORTALS OF CHAOS" },
+    { {  1632,  1644,  1644 }, "E2M9:  THE GLACIER" },
+    { {  1652,  1664,  1664 }, "E3M1:  THE STOREHOUSE" },
+    { {  1676,  1688,  1688 }, "E3M2:  THE CESSPOOL" },
+    { {  1696,  1708,  1708 }, "E3M3:  THE CONFLUENCE" },
+    { {  1720,  1732,  1732 }, "E3M4:  THE AZURE FORTRESS" },
+    { {  1748,  1760,  1760 }, "E3M5:  THE OPHIDIAN LAIR" },
+    { {  1776,  1788,  1788 }, "E3M6:  THE HALLS OF FEAR" },
+    { {  1804,  1816,  1816 }, "E3M7:  THE CHASM" },
+    { {  1824,  1836,  1836 }, "E3M8:  D'SPARIL'S KEEP" },
+    { {  1848,  1860,  1860 }, "E3M9:  THE AQUIFER" },
+    { {     0,  1880,  1880 }, "E4M1:  CATAFALQUE" },
+    { {     0,  1900,  1900 }, "E4M2:  BLOCKHOUSE" },
+    { {     0,  1920,  1920 }, "E4M3:  AMBULATORY" },
+    { {     0,  1940,  1940 }, "E4M4:  SEPULCHER" },
+    { {     0,  1960,  1960 }, "E4M5:  GREAT STAIR" },
+    { {     0,  1980,  1980 }, "E4M6:  HALLS OF THE APOSTATE" },
+    { {     0,  2012,  2012 }, "E4M7:  RAMPARTS OF PERDITION" },
+    { {     0,  2044,  2044 }, "E4M8:  SHATTERED BRIDGE" },
+    { {     0,  2068,  2068 }, "E4M9:  MAUSOLEUM" },
+    { {     0,  2088,  2088 }, "E5M1:  OCHRE CLIFFS" },
+    { {     0,  2108,  2108 }, "E5M2:  RAPIDS" },
+    { {     0,  2124,  2124 }, "E5M3:  QUAY" },
+    { {     0,  2136,  2136 }, "E5M4:  COURTYARD" },
+    { {     0,  2156,  2156 }, "E5M5:  HYDRATYR" },
+    { {     0,  2172,  2172 }, "E5M6:  COLONNADE" },
+    { {     0,  2192,  2192 }, "E5M7:  FOETID MANSE" },
+    { {     0,  2212,  2212 }, "E5M8:  FIELD OF JUDGEMENT" },
+    { {     0,  2240,  2240 }, "E5M9:  SKEIN OF D'SPARIL" },
+    { {  1868,  2268,  2268 }, "AUTOPAGE" },
+    { {  1880,  2280,  2280 }, "FOLLOW MODE ON" },
+    { {  1896,  2296,  2296 }, "FOLLOW MODE OFF" },
+    { {  1924,  2324,  2324 }, "GREEN:  " },
+    { {  1936,  2336,  2336 }, "YELLOW:  " },
+    { {  1948,  2348,  2348 }, "RED:  " },
+    { {  1956,  2356,  2356 }, "BLUE:  " },
+    { {  1964,  2364,  2364 }, "FONTA_S" },
+    { {  1972,  2372,  2372 }, "-MESSAGE SENT-" },
+    { {  1988,  2388,  2388 }, "THERE ARE NO OTHER PLAYERS IN THE GAME!" },
+    { {  2028,  2428,  2428 }, "FONTA59" },
+    { {  2036,  2504,  2504 }, "PAUSED" },
+    { {  2072,  2540,  2540 }, "ADVISOR" },
+    { {  2080,  2548,  2548 }, "TITLE" },
+    { {  2088,  2556,  2556 }, "demo1" },
+    { {  2096,  2564,  2564 }, "CREDIT" },
+    { {  2104,  2572,  2572 }, "demo2" },
+    { {  2112,  2580,  2580 }, "ORDER" },
+    { {  2120,  2588,  2588 }, "demo3" },
+    { {  2304,  2696,  2696 }, "Exited from HERETIC.\n" },
+    { {  2412,  2800,  2800 }, "c:\\heretic.cd" },
+    { {  2528,  2916,  2916 }, "Playing demo %s.lmp.\n" },
+    { {  2592,  2980,  2980 }, "V_Init: allocate screens.\n" },
+    { {  2620,  3008,  3008 }, "M_LoadDefaults: Load system defaults.\n" },
+    { {  2660,  3048,  3048 }, "Z_Init: Init zone memory allocation daemon.\n" },
+    { {  2708,  3096,  3096 }, "W_Init: Init WADfiles.\n" },
+    { {  2732,  3120,  3120 }, "E2M1" },
+    { {     0,  3128,  3128 }, "EXTENDED" },
+    { {  2740,  3140,  3140 }, "LOADING" },
+    { {  2748,  3148,  3148 }, "DeathMatch..." },
+    { {  2764,  3164,  3164 }, "No Monsters..." },
+    { {  2780,  3180,  3180 }, "Respawning..." },
+    { {  2796,  3196,  3196 }, "Warp to Episode %d, Map %d, Skill %d " },
+    { {  2836,  3236,  3236 }, "MN_Init: Init menu system.\n" },
+    { {  2864,  3264,  3264 }, "R_Init: Init Heretic refresh daemon." },
+    { {  2904,  3304,  3304 }, "Loading graphics" },
+    { {  2924,  3324,  3324 }, "P_Init: Init Playloop state." },
+    { {  2956,  3356,  3356 }, "Init game engine." },
+    { {  2976,  3376,  3376 }, "I_Init: Setting up machine state.\n" },
+    { {  3012,  3412,  3412 }, "D_CheckNetGame: Checking network game status.\n" },
+    { {  3060,  3460,  3460 }, "Checking network game status." },
+    { {  3092,  3492,  3492 }, "SB_Init: Loading patches.\n" },
+    { {     0,  3752,  3752 }, "PLAYER 1 LEFT THE GAME" },
+    { {  3508,  3932,  3932 }, "Network game synchronization aborted." },
+    { {     0,  3972,  3972 }, "Different DOOM versions cannot play a net game!" },
+    { {  3908,  4132,  4132 }, "SKY1" },
+    { {  3916,  4140,  4140 }, "SKY2" },
+    { {  3924,  4148,  4148 }, "SKY3" },
+    { {  3736,  4196,  4196 }, "NET GAME" },
+    { {  3748,  4208,  4208 }, "SAVE GAME" },
+    { {  3760,  4220,  4220 }, "Only %i deathmatch spots, 4 required" },
+    { {  3800,  4260,  4260 }, "version %i" },
+    { {  3828,  4372,  4372 }, "c:\\heretic.cd\\hticsav%d.hsg" },
+    { {  3856,  4400,  4400 }, "hticsav%d.hsg" },
+    { {  3896,  4416,  4416 }, "GAME SAVED" },
+    { {  4016,  4456,  4456 }, E1TEXT },
+    { {  4536,  4976,  4976 }, E2TEXT },
+    { {  5068,  5508,  5508 }, E3TEXT },
+    { {     0,  6072,  6072 }, E4TEXT },
+    { {     0,  6780,  6780 }, E5TEXT },
+    { {  5632,  7468,  7468 }, "FLOOR25" },
+    { {  5640,  7476,  7476 }, "FLATHUH1" },
+    { {  5652,  7488,  7488 }, "FLTWAWA2" },
+    { {     0,  7500,  7500 }, "FLOOR28" },
+    { {     0,  7508,  7508 }, "FLOOR08" },
+    { {  5664,  7516,  7516 }, "FONTA_S" },
+    { {  5704,  7524,  7524 }, "PLAYPAL" },
+    { {  5672,  7532,  7532 }, "FINAL1" },
+    { {  5680,  7540,  7540 }, "FINAL2" },
+    { {  5688,  7548,  7548 }, "E2PAL" },
+    { {  5696,  7556,  7556 }, "E2END" },
+    { {  7884,  7564,  7564 }, "TITLE" },
+    { {  5712,  7572,  7572 }, "ORDER" },
+    { {     0,  7580,  7580 }, "CREDIT" },
+    { {  5720,  7588,  7588 }, "IMPX" },
+    { {  5728,  7596,  7596 }, "ACLO" },
+    { {  5736,  7604,  7604 }, "PTN1" },
+    { {  5744,  7612,  7612 }, "SHLD" },
+    { {  5752,  7620,  7620 }, "SHD2" },
+    { {  5760,  7628,  7628 }, "BAGH" },
+    { {  5768,  7636,  7636 }, "SPMP" },
+    { {  5776,  7644,  7644 }, "INVS" },
+    { {  5784,  7652,  7652 }, "PTN2" },
+    { {  5792,  7660,  7660 }, "SOAR" },
+    { {  5800,  7668,  7668 }, "INVU" },
+    { {  5808,  7676,  7676 }, "PWBK" },
+    { {  5816,  7684,  7684 }, "EGGC" },
+    { {  5824,  7692,  7692 }, "EGGM" },
+    { {  5832,  7700,  7700 }, "FX01" },
+    { {  5840,  7708,  7708 }, "SPHL" },
+    { {  5848,  7716,  7716 }, "TRCH" },
+    { {  5856,  7724,  7724 }, "FBMB" },
+    { {  5864,  7732,  7732 }, "XPL1" },
+    { {  5872,  7740,  7740 }, "ATLP" },
+    { {  5880,  7748,  7748 }, "PPOD" },
+    { {  5888,  7756,  7756 }, "AMG1" },
+    { {  5896,  7764,  7764 }, "SPSH" },
+    { {  5904,  7772,  7772 }, "LVAS" },
+    { {  5912,  7780,  7780 }, "SLDG" },
+    { {  5920,  7788,  7788 }, "SKH1" },
+    { {  5928,  7796,  7796 }, "SKH2" },
+    { {  5936,  7804,  7804 }, "SKH3" },
+    { {  5944,  7812,  7812 }, "SKH4" },
+    { {  5952,  7820,  7820 }, "CHDL" },
+    { {  5960,  7828,  7828 }, "SRTC" },
+    { {  5968,  7836,  7836 }, "SMPL" },
+    { {  5976,  7844,  7844 }, "STGS" },
+    { {  5984,  7852,  7852 }, "STGL" },
+    { {  5992,  7860,  7860 }, "STCS" },
+    { {  6000,  7868,  7868 }, "STCL" },
+    { {  6008,  7876,  7876 }, "KFR1" },
+    { {  6016,  7884,  7884 }, "BARL" },
+    { {  6024,  7892,  7892 }, "BRPL" },
+    { {  6032,  7900,  7900 }, "MOS1" },
+    { {  6040,  7908,  7908 }, "MOS2" },
+    { {  6048,  7916,  7916 }, "WTRH" },
+    { {  6056,  7924,  7924 }, "HCOR" },
+    { {  6064,  7932,  7932 }, "KGZ1" },
+    { {  6072,  7940,  7940 }, "KGZB" },
+    { {  6080,  7948,  7948 }, "KGZG" },
+    { {  6088,  7956,  7956 }, "KGZY" },
+    { {  6096,  7964,  7964 }, "VLCO" },
+    { {  6104,  7972,  7972 }, "VFBL" },
+    { {  6112,  7980,  7980 }, "VTFB" },
+    { {  6120,  7988,  7988 }, "SFFI" },
+    { {  6128,  7996,  7996 }, "TGLT" },
+    { {  6136,  8004,  8004 }, "TELE" },
+    { {  6144,  8012,  8012 }, "STFF" },
+    { {  6152,  8020,  8020 }, "PUF3" },
+    { {  6160,  8028,  8028 }, "PUF4" },
+    { {  6168,  8036,  8036 }, "BEAK" },
+    { {  6176,  8044,  8044 }, "WGNT" },
+    { {  6184,  8052,  8052 }, "GAUN" },
+    { {  6192,  8060,  8060 }, "PUF1" },
+    { {  6200,  8068,  8068 }, "WBLS" },
+    { {  6208,  8076,  8076 }, "BLSR" },
+    { {  6216,  8084,  8084 }, "FX18" },
+    { {  6224,  8092,  8092 }, "FX17" },
+    { {  6232,  8100,  8100 }, "WMCE" },
+    { {  6240,  8108,  8108 }, "MACE" },
+    { {  6248,  8116,  8116 }, "FX02" },
+    { {  6256,  8124,  8124 }, "WSKL" },
+    { {  6264,  8132,  8132 }, "HROD" },
+    { {  6272,  8140,  8140 }, "FX00" },
+    { {  6280,  8148,  8148 }, "FX20" },
+    { {  6288,  8156,  8156 }, "FX21" },
+    { {  6296,  8164,  8164 }, "FX22" },
+    { {  6304,  8172,  8172 }, "FX23" },
+    { {  6312,  8180,  8180 }, "GWND" },
+    { {  6320,  8188,  8188 }, "PUF2" },
+    { {  6328,  8196,  8196 }, "WPHX" },
+    { {  6336,  8204,  8204 }, "PHNX" },
+    { {  6344,  8212,  8212 }, "FX04" },
+    { {  6352,  8220,  8220 }, "FX08" },
+    { {  6360,  8228,  8228 }, "FX09" },
+    { {  6368,  8236,  8236 }, "WBOW" },
+    { {  6376,  8244,  8244 }, "CRBW" },
+    { {  6384,  8252,  8252 }, "FX03" },
+    { {  6392,  8260,  8260 }, "BLOD" },
+    { {  6400,  8268,  8268 }, "PLAY" },
+    { {  6408,  8276,  8276 }, "FDTH" },
+    { {  6416,  8284,  8284 }, "BSKL" },
+    { {  6424,  8292,  8292 }, "CHKN" },
+    { {  6432,  8300,  8300 }, "MUMM" },
+    { {  6440,  8308,  8308 }, "FX15" },
+    { {  6448,  8316,  8316 }, "BEAS" },
+    { {  6456,  8324,  8324 }, "FRB1" },
+    { {  6464,  8332,  8332 }, "SNKE" },
+    { {  6472,  8340,  8340 }, "SNFX" },
+    { {  6480,  8348,  8348 }, "HEAD" },
+    { {  6488,  8356,  8356 }, "FX05" },
+    { {  6496,  8364,  8364 }, "FX06" },
+    { {  6504,  8372,  8372 }, "FX07" },
+    { {  6512,  8380,  8380 }, "CLNK" },
+    { {  6520,  8388,  8388 }, "WZRD" },
+    { {  6528,  8396,  8396 }, "FX11" },
+    { {  6536,  8404,  8404 }, "FX10" },
+    { {  6544,  8412,  8412 }, "KNIG" },
+    { {  6552,  8420,  8420 }, "SPAX" },
+    { {  6560,  8428,  8428 }, "RAXE" },
+    { {  6568,  8436,  8436 }, "SRCR" },
+    { {  6576,  8444,  8444 }, "FX14" },
+    { {  6584,  8452,  8452 }, "SOR2" },
+    { {  6592,  8460,  8460 }, "SDTH" },
+    { {  6600,  8468,  8468 }, "FX16" },
+    { {  6608,  8476,  8476 }, "MNTR" },
+    { {  6616,  8484,  8484 }, "FX12" },
+    { {  6624,  8492,  8492 }, "FX13" },
+    { {  6632,  8500,  8500 }, "AKYY" },
+    { {  6640,  8508,  8508 }, "BKYY" },
+    { {  6648,  8516,  8516 }, "CKYY" },
+    { {  6656,  8524,  8524 }, "AMG2" },
+    { {  6664,  8532,  8532 }, "AMM1" },
+    { {  6672,  8540,  8540 }, "AMM2" },
+    { {  6680,  8548,  8548 }, "AMC1" },
+    { {  6688,  8556,  8556 }, "AMC2" },
+    { {  6696,  8564,  8564 }, "AMS1" },
+    { {  6704,  8572,  8572 }, "AMS2" },
+    { {  6712,  8580,  8580 }, "AMP1" },
+    { {  6720,  8588,  8588 }, "AMP2" },
+    { {  6728,  8596,  8596 }, "AMB1" },
+    { {  6736,  8604,  8604 }, "AMB2" },
+    { {  6744,  8612,  8612 }, "K" },
+    { {  6748,  8616,  8616 }, "I" },
+    { {  6752,  8620,  8620 }, "L" },
+    { {  6756,  8624,  8624 }, "E" },
+    { {  6760,  8628,  8628 }, "R" },
+    { {  6764,  8632,  8632 }, "S" },
+    { {  6768,  8636,  8636 }, "PLAYPAL" },
+    { {  6776,  8644,  8644 }, "MAPE1" },
+    { {  6784,  8652,  8652 }, "MAPE2" },
+    { {  6792,  8660,  8660 }, "MAPE3" },
+    { {  6800,  8668,  8668 }, "IN_X" },
+    { {  6808,  8676,  8676 }, "IN_YAH" },
+    { {  6816,  8684,  8684 }, "FONTB16" },
+    { {  6824,  8692,  8692 }, "FONTB_S" },
+    { {  6832,  8700,  8700 }, "FONTB13" },
+    { {  6840,  8708,  8708 }, "FONTB15" },
+    { {  6848,  8716,  8716 }, "FONTB05" },
+    { {  6856,  8724,  8724 }, "FACEA0" },
+    { {  6864,  8732,  8732 }, "FACEB0" },
+    { {  6940,  8808,  8808 }, "FLOOR16" },
+    { {  6948,  8816,  8816 }, "FINISHED" },
+    { {  6960,  8828,  8828 }, "NOW ENTERING:" },
+    { {  6976,  8844,  8844 }, "KILLS" },
+    { {  6984,  8852,  8852 }, "ITEMS" },
+    { {  6992,  8860,  8860 }, "SECRETS" },
+    { {  7000,  8868,  8868 }, "TIME" },
+    { {  7008,  8876,  8876 }, "BONUS" },
+    { {  7016,  8884,  8884 }, "SECRET" },
+    { {  7024,  8892,  8892 }, "TOTAL" },
+    { {  7032,  8900,  8900 }, "VICTIMS" },
+    { {  7040,  8908,  8908 }, ":" },
+    { {  7044,  8912,  8912 }, "NEW GAME" },
+    { {  7056,  8924,  8924 }, "OPTIONS" },
+    { {  7064,  8932,  8932 }, "GAME FILES" },
+    { {  7076,  8944,  8944 }, "INFO" },
+    { {  7084,  8952,  8952 }, "QUIT GAME" },
+    { {  7096,  8964,  8964 }, "CITY OF THE DAMNED" },
+    { {  7116,  8984,  8984 }, "HELL'S MAW" },
+    { {  7128,  8996,  8996 }, "THE DOME OF D'SPARIL" },
+    { {     0,  9020,  9020 }, "THE OSSUARY" },
+    { {     0,  9032,  9032 }, "THE STAGNANT DEMESNE" },
+    { {  7152,  9056,  9056 }, "LOAD GAME" },
+    { {  7164,  9068,  9068 }, "SAVE GAME" },
+    { {  7176,  9080,  9080 }, "THOU NEEDETH A WET-NURSE" },
+    { {  7204,  9108,  9108 }, "YELLOWBELLIES-R-US" },
+    { {  7224,  9128,  9128 }, "BRINGEST THEM ONETH" },
+    { {  7244,  9148,  9148 }, "THOU ART A SMITE-MEISTER" },
+    { {  7272,  9176,  9176 }, "BLACK PLAGUE POSSESSES THEE" },
+    { {  7300,  9204,  9204 }, "END GAME" },
+    { {  7312,  9216,  9216 }, "MESSAGES : " },
+    { {  7324,  9228,  9228 }, "MOUSE SENSITIVITY" },
+    { {  7344,  9248,  9248 }, "MORE..." },
+    { {  7352,  9256,  9256 }, "SCREEN SIZE" },
+    { {  7364,  9268,  9268 }, "SFX VOLUME" },
+    { {  7376,  9280,  9280 }, "MUSIC VOLUME" },
+    { {  7416,  9296,  9296 }, "ARE YOU SURE YOU WANT TO QUIT?" },
+    { {  7448,  9328,  9328 }, "ARE YOU SURE YOU WANT TO END THE GAME?" },
+    { {  7488,  9368,  9368 }, "DO YOU WANT TO QUICKSAVE THE GAME NAMED" },
+    { {  7528,  9408,  9408 }, "DO YOU WANT TO QUICKLOAD THE GAME NAMED" },
+    { {  7392,  9448,  9448 }, "M_SKL00" },
+    { {  7400,  9456,  9456 }, "FONTA_S" },
+    { {  7408,  9464,  9464 }, "FONTB_S" },
+    { {  7568,  9472,  9472 }, "?" },
+    { {  7572,  9476,  9476 }, "M_SLCTR1" },
+    { {  7584,  9488,  9488 }, "M_SLCTR2" },
+    { {  7596,  9500,  9500 }, "M_HTIC" },
+    { {  7604,  9508,  9508 }, "c:\\heretic.cd\\hticsav%d.hsg" },
+    { {  7632,  9536,  9536 }, "hticsav%d.hsg" },
+    { {  7652,  9556,  9556 }, "M_FSLOT" },
+    { {  7660,  9564,  9564 }, "ON" },
+    { {  7664,  9568,  9568 }, "OFF" },
+    { {     0,  9572,  9572 }, "YOU CAN'T START A NEW GAME IN NETPLAY!" },
+    { {     0,  9612,  9612 }, "YOU CAN'T LOAD A GAME IN NETPLAY!" },
+    { {  7668,  9648,  9648 }, "MESSAGES ON" },
+    { {  7680,  9660,  9660 }, "MESSAGES OFF" },
+    { {  7748,  9676,  9676 }, "ONLY AVAILABLE IN THE REGISTERED VERSION" },
+    { {  7792,  9720,  9720 }, "PLAYPAL" },
+    { {  7800,  9728,  9728 }, "QUICKSAVING...." },
+    { {  7816,  9744,  9744 }, "QUICKLOADING...." },
+    { {  7836,  9764,  9764 }, "CHOOSE A QUICKSAVE SLOT" },
+    { {  7860,  9788,  9788 }, "CHOOSE A QUICKLOAD SLOT" },
+    { {     0,  9812,  9812 }, "TITLE" },
+    { {  7892,  9820,  9820 }, "M_SLDLT" },
+    { {  7900,  9828,  9828 }, "M_SLDMD1" },
+    { {  7912,  9840,  9840 }, "M_SLDMD2" },
+    { {  7924,  9852,  9852 }, "M_SLDRT" },
+    { {  7932,  9860,  9860 }, "M_SLDKB" },
+    { {  9016, 10944, 10944 }, "SCREEN SHOT" },
+    { {  9028, 10956, 10956 }, "YOU NEED A BLUE KEY TO OPEN THIS DOOR" },
+    { {  9068, 10996, 10996 }, "YOU NEED A YELLOW KEY TO OPEN THIS DOOR" },
+    { {  9108, 11036, 11036 }, "YOU NEED A GREEN KEY TO OPEN THIS DOOR" },
+    { {  9244, 11172, 11172 }, "CRYSTAL VIAL" },
+    { {  9260, 11188, 11188 }, "SILVER SHIELD" },
+    { {  9276, 11204, 11204 }, "ENCHANTED SHIELD" },
+    { {  9296, 11224, 11224 }, "BAG OF HOLDING" },
+    { {  9312, 11240, 11240 }, "MAP SCROLL" },
+    { {  9324, 11252, 11252 }, "BLUE KEY" },
+    { {  9336, 11264, 11264 }, "YELLOW KEY" },
+    { {  9348, 11276, 11276 }, "GREEN KEY" },
+    { {  9360, 11288, 11288 }, "QUARTZ FLASK" },
+    { {  9376, 11304, 11304 }, "WINGS OF WRATH" },
+    { {  9392, 11320, 11320 }, "RING OF INVINCIBILITY" },
+    { {  9416, 11344, 11344 }, "TOME OF POWER" },
+    { {  9432, 11360, 11360 }, "SHADOWSPHERE" },
+    { {  9448, 11376, 11376 }, "MORPH OVUM" },
+    { {  9460, 11388, 11388 }, "MYSTIC URN" },
+    { {  9472, 11400, 11400 }, "TORCH" },
+    { {  9480, 11408, 11408 }, "TIME BOMB OF THE ANCIENTS" },
+    { {  9508, 11436, 11436 }, "CHAOS DEVICE" },
+    { {  9524, 11452, 11452 }, "WAND CRYSTAL" },
+    { {  9540, 11468, 11468 }, "CRYSTAL GEODE" },
+    { {  9556, 11484, 11484 }, "MACE SPHERES" },
+    { {  9572, 11500, 11500 }, "PILE OF MACE SPHERES" },
+    { {  9596, 11524, 11524 }, "ETHEREAL ARROWS" },
+    { {  9612, 11540, 11540 }, "QUIVER OF ETHEREAL ARROWS" },
+    { {  9640, 11568, 11568 }, "CLAW ORB" },
+    { {  9652, 11580, 11580 }, "ENERGY ORB" },
+    { {  9664, 11592, 11592 }, "LESSER RUNES" },
+    { {  9680, 11608, 11608 }, "GREATER RUNES" },
+    { {  9696, 11624, 11624 }, "FLAME ORB" },
+    { {  9708, 11636, 11636 }, "INFERNO ORB" },
+    { {  9720, 11648, 11648 }, "FIREMACE" },
+    { {  9732, 11660, 11660 }, "ETHEREAL CROSSBOW" },
+    { {  9752, 11680, 11680 }, "DRAGON CLAW" },
+    { {  9764, 11692, 11692 }, "HELLSTAFF" },
+    { {  9776, 11704, 11704 }, "PHOENIX ROD" },
+    { {  9788, 11716, 11716 }, "GAUNTLETS OF THE NECROMANCER" },
+    { { 10088, 12016, 12016 }, "FLTWAWA1" },
+    { { 10100, 12028, 12028 }, "FLTFLWW1" },
+    { { 10112, 12040, 12040 }, "FLTLAVA1" },
+    { { 10124, 12052, 12052 }, "FLATHUH1" },
+    { { 10136, 12064, 12064 }, "FLTSLUD1" },
+    { { 10148, 12076, 12076 }, "END" },
+    { { 10236, 12164, 12164 }, "texture2" },
+    { { 10444, 12372, 12372 }, "PLAYPAL" },
+    { { 10596, 12488, 12488 }, "PNAMES" },
+    { { 10604, 12496, 12496 }, "TEXTURE1" },
+    { { 10616, 12508, 12508 }, "TEXTURE2" },
+    { { 10628, 12520, 12520 }, "S_END" },
+    { { 10636, 12528, 12528 }, "S_START" },
+    { { 10728, 12620, 12620 }, "F_START" },
+    { { 10736, 12628, 12628 }, "F_END" },
+    { { 10744, 12636, 12636 }, "COLORMAP" },
+    { { 10756, 12648, 12648 }, "\nR_InitTextures " },
+    { { 10776, 12668, 12668 }, "R_InitFlats\n" },
+    { { 10792, 12684, 12684 }, "R_InitSpriteLumps " },
+    { { 10948, 12772, 12772 }, "TINTTAB" },
+    { { 10984, 12780, 12780 }, "FLOOR04" },
+    { { 10992, 12788, 12788 }, "FLAT513" },
+    { { 11000, 12796, 12796 }, "bordt" },
+    { { 11008, 12804, 12804 }, "bordb" },
+    { { 11016, 12812, 12812 }, "bordl" },
+    { { 11024, 12820, 12820 }, "bordr" },
+    { { 11032, 12828, 12828 }, "bordtl" },
+    { { 11040, 12836, 12836 }, "bordtr" },
+    { { 11048, 12844, 12844 }, "bordbr" },
+    { { 11056, 12852, 12852 }, "bordbl" },
+    { { 11064, 12860, 12860 }, "R_InitData " },
+    { { 11076, 12872, 12872 }, "R_InitPointToAngle\n" },
+    { { 11096, 12892, 12892 }, "R_InitTables " },
+    { { 11112, 12908, 12908 }, "R_InitPlanes\n" },
+    { { 11128, 12924, 12924 }, "R_InitLightTables " },
+    { { 11148, 12944, 12944 }, "R_InitSkyMap\n" },
+    { { 11164, 12960, 12960 }, "F_SKY1" },
+    { { 12120, 13484, 13484 }, "LTFACE" },
+    { { 12128, 13492, 13492 }, "RTFACE" },
+    { { 12136, 13500, 13500 }, "BARBACK" },
+    { { 12144, 13508, 13508 }, "INVBAR" },
+    { { 12152, 13516, 13516 }, "CHAIN" },
+    { { 12160, 13524, 13524 }, "STATBAR" },
+    { { 12168, 13532, 13532 }, "LIFEBAR" },
+    { { 12176, 13540, 13540 }, "LIFEGEM2" },
+    { { 12188, 13552, 13552 }, "LIFEGEM0" },
+    { { 12200, 13564, 13564 }, "LTFCTOP" },
+    { { 12208, 13572, 13572 }, "RTFCTOP" },
+    { { 12224, 13580, 13580 }, "SELECTBOX" },
+    { { 12236, 13592, 13592 }, "INVGEML1" },
+    { { 12248, 13604, 13604 }, "INVGEML2" },
+    { { 12260, 13616, 13616 }, "INVGEMR1" },
+    { { 12272, 13628, 13628 }, "INVGEMR2" },
+    { { 12284, 13640, 13640 }, "BLACKSQ" },
+    { { 12292, 13648, 13648 }, "ARMCLEAR" },
+    { { 12304, 13660, 13660 }, "CHAINBACK" },
+    { { 12316, 13672, 13672 }, "IN0" },
+    { { 12320, 13676, 13676 }, "NEGNUM" },
+    { { 12328, 13684, 13684 }, "FONTB16" },
+    { { 12336, 13692, 13692 }, "SMALLIN0" },
+    { { 12348, 13704, 13704 }, "PLAYPAL" },
+    { { 12356, 13712, 13712 }, "SPINBK0" },
+    { { 12364, 13720, 13720 }, "SPFLY0" },
+    { { 12372, 13728, 13728 }, "LAME" },
+    { { 12380, 13736, 13736 }, "*** SOUND DEBUG INFO ***" },
+    { { 12408, 13764, 13764 }, "NAME" },
+    { { 12416, 13772, 13772 }, "MO.T" },
+    { { 12424, 13780, 13780 }, "MO.X" },
+    { { 12432, 13788, 13788 }, "MO.Y" },
+    { { 12440, 13796, 13796 }, "ID" },
+    { { 12444, 13800, 13800 }, "PRI" },
+    { { 12448, 13804, 13804 }, "DIST" },
+    { { 12456, 13812, 13812 }, "------" },
+    { { 12464, 13820, 13820 }, "%s" },
+    { { 12468, 13824, 13824 }, "%d" },
+    { { 12472, 13828, 13828 }, "GOD1" },
+    { { 12480, 13836, 13836 }, "GOD2" },
+    { { 12488, 13844, 13844 }, "useartia" },
+    { { 12500, 13856, 13856 }, "ykeyicon" },
+    { { 12512, 13868, 13868 }, "gkeyicon" },
+    { { 12524, 13880, 13880 }, "bkeyicon" },
+    { { 12216, 13892, 13892 }, "ARTIBOX" },
+    { { 12536, 13900, 13900 }, "GOD MODE ON" },
+    { { 12548, 13912, 13912 }, "GOD MODE OFF" },
+    { { 12564, 13928, 13928 }, "NO CLIPPING ON" },
+    { { 12580, 13944, 13944 }, "NO CLIPPING OFF" },
+    { { 12596, 13960, 13960 }, "ALL WEAPONS" },
+    { { 12608, 13972, 13972 }, "POWER OFF" },
+    { { 12620, 13984, 13984 }, "POWER ON" },
+    { { 12632, 13996, 13996 }, "FULL HEALTH" },
+    { { 12644, 14008, 14008 }, "ALL KEYS" },
+    { { 12656, 14020, 14020 }, "SOUND DEBUG ON" },
+    { { 12672, 14036, 14036 }, "SOUND DEBUG OFF" },
+    { { 12688, 14052, 14052 }, "TICKER ON" },
+    { { 12700, 14064, 14064 }, "TICKER OFF" },
+    { { 12712, 14076, 14076 }, "CHOOSE AN ARTIFACT ( A - J )" },
+    { { 12744, 14108, 14108 }, "HOW MANY ( 1 - 9 )" },
+    { { 12764, 14128, 14128 }, "YOU GOT IT" },
+    { { 12776, 14140, 14140 }, "BAD INPUT" },
+    { { 12788, 14152, 14152 }, "LEVEL WARP" },
+    { { 12800, 14164, 14164 }, "CHICKEN OFF" },
+    { { 12812, 14176, 14176 }, "CHICKEN ON" },
+    { { 12824, 14188, 14188 }, "MASSACRE" },
+    { { 12836, 14200, 14200 }, "CHEATER - YOU DON'T DESERVE WEAPONS" },
+    { { 12872, 14236, 14236 }, "TRYING TO CHEAT, EH?  NOW YOU DIE!" },
+};
+
+// String offsets that are valid but we don't support.
+
+static const int unsupported_strings_1_0[] =
+{
+        0,     4,    64,   104,   160,   200,   220,   236,
+      244,   252,   272,   288,   296,   316,   332,   372,
+      436,   500,   504,   536,   544,   560,   576,   584,
+      592,   612,   640,   664,   708,   712,   744,   764,
+      808,   820,   828,   840,   876,   884,   908,   952,
+      992,  1028,  1036,  1048,  1088,  1128,  1160,  1192,
+     1212,  1912,  2044,  2056,  2068,  2128,  2140,  2168,
+     2184,  2196,  2212,  2228,  2240,  2252,  2260,  2264,
+     2284,  2292,  2296,  2300,  2328,  2340,  2352,  2364,
+     2372,  2384,  2388,  2404,  2428,  2436,  2444,  2464,
+     2496,  2508,  2520,  2552,  2564,  2572,  2584,  3120,
+     3128,  3140,  3184,  3220,  3248,  3252,  3256,  3280,
+     3304,  3320,  3352,  3380,  3400,  3432,  3464,  3548,
+     3600,  3624,  3664,  3696,  3812,  3872,  3932,  3940,
+     3976,  3996,  6872,  6896,  7648,  7696,  7940,  7964,
+     7968,  7992,  8020,  8028,  8052,  8056,  8076,  8088,
+     8104,  8116,  8128,  8136,  8148,  8164,  8180,  8192,
+     8204,  8220,  8232,  8248,  8264,  8276,  8292,  8308,
+     8320,  8328,  8340,  8352,  8364,  8376,  8392,  8408,
+     8424,  8436,  8448,  8460,  8472,  8488,  8504,  8520,
+     8536,  8548,  8560,  8572,  8584,  8596,  8608,  8612,
+     8624,  8648,  8660,  8668,  8680,  8708,  8720,  8728,
+     8740,  8752,  8764,  8788,  8800,  8812,  8824,  8848,
+     8860,  8864,  8868,  8876,  8888,  8896,  8916,  8944,
+     8948,  8960,  8964,  8968,  8980,  9148,  9172,  9212,
+     9216,  9220,  9820,  9860,  9892,  9940,  9972, 10012,
+    10036, 10040, 10052, 10080, 10152, 10192, 10248, 10284,
+    10320, 10360, 10392, 10452, 10488, 10508, 10556, 10644,
+    10684, 10812, 10844, 10880, 10912, 10956, 11172, 11200,
+    11232, 11272, 11312, 11348, 11380, 11404, 11436, 11492,
+    11548, 11616, 11684, 11748, 11792, 11840, 11896, 11936,
+    11980, 12028, 12072, 12908, 12924, 12956, 12960, 12968,
+    12976, 13020, 13048, 13076, 13104, 13136, 13168, 13196,
+    13240, 13272, 13292, 13296, 13308, 13312, 13320, 13324,
+    13364, 13408, 13460, 13492, 13516, 13560, 13612, 13664,
+    13700, 13744, 13796, 13848, 13884, 13940, 13996, 14040,
+    14084, 14140, 14148, 14156, 14164, 14184, 14192, 14204,
+    14208, 14212, 14256, 14272, 14284, 14296, 14300, 14312,
+    14320, 14324, 14348, 14356, 14360, 14372, 14380, 14392,
+    14432, 14440, 14444, 14472, 14496, 14516, 14536, 14548,
+    14560, 14572, 14580, 14588, 14596, 14604, 14612, 14620,
+    14636, 14660, 14704, 14740, 14748, 14756, 14760, 14768,
+       -1,
+};
+
+static const int unsupported_strings_1_2[] =
+{
+        0,     4,    64,   104,   160,   200,   220,   236,
+      244,   252,   272,   288,   296,   316,   332,   372,
+      436,   500,   504,   536,   544,   560,   576,   584,
+      592,   612,   640,   664,   708,   712,   744,   756,
+      776,   820,   832,   840,   852,   888,   896,   920,
+      964,  1004,  1040,  1048,  1060,  1100,  1140,  1172,
+     1204,  1224,  2312,  2436,  2448,  2464,  2480,  2492,
+     2512,  2524,  2536,  2596,  2608,  2636,  2652,  2656,
+     2676,  2684,  2688,  2720,  2732,  2744,  2752,  2764,
+     2772,  2776,  2792,  2816,  2824,  2832,  2852,  2884,
+     2896,  2908,  2940,  2952,  2960,  2972,  3520,  3528,
+     3540,  3584,  3620,  3648,  3652,  3656,  3680,  3704,
+     3720,  3776,  3804,  3824,  3856,  3888,  4020,  4044,
+     4084,  4116,  4156,  4272,  4288,  4296,  4332,  4352,
+     4428,  4432,  8740,  8764,  9552,  9868,  9888,  9900,
+     9916,  9928,  9940,  9948,  9960,  9976,  9992, 10004,
+    10016, 10032, 10044, 10060, 10076, 10088, 10104, 10120,
+    10132, 10140, 10152, 10164, 10176, 10188, 10204, 10220,
+    10236, 10248, 10260, 10272, 10284, 10300, 10316, 10332,
+    10348, 10360, 10372, 10384, 10396, 10408, 10420, 10424,
+    10436, 10460, 10472, 10480, 10492, 10520, 10532, 10540,
+    10552, 10564, 10576, 10600, 10612, 10624, 10636, 10660,
+    10672, 10676, 10700, 10704, 10728, 10756, 10764, 10788,
+    10792, 10796, 10804, 10816, 10824, 10844, 10872, 10876,
+    10888, 10892, 10896, 10908, 11076, 11100, 11140, 11144,
+    11148, 11748, 11788, 11820, 11868, 11900, 11940, 11964,
+    11968, 11980, 12008, 12080, 12120, 12176, 12212, 12248,
+    12288, 12320, 12380, 12400, 12448, 12536, 12576, 12704,
+    12736, 12968, 13000, 13024, 13080, 13136, 13204, 13272,
+    13336, 13380, 13428, 14272, 14288, 14320, 14324, 14332,
+    14340, 14384, 14412, 14440, 14468, 14500, 14532, 14560,
+    14604, 14636, 14656, 14696, 14740, 14792, 14824, 14848,
+    14892, 14944, 14996, 15032, 15076, 15128, 15180, 15216,
+    15272, 15328, 15372, 15416, 15472, 15480, 15488, 15496,
+    15516, 15524, 15536, 15540, 15544, 15588, 15604, 15616,
+    15628, 15632, 15644, 15652, 15656, 15680, 15688, 15692,
+    15704, 15712, 15724, 15764, 15772, 15776, 15804, 15828,
+    15848, 15868, 15880, 15892, 15904, 15912, 15920, 15928,
+    15936,    -1,
+};
+
+static const int unsupported_strings_1_3[] =
+{
+        0,     4,    64,   104,   160,   200,   220,   236,
+      244,   252,   272,   288,   296,   316,   332,   372,
+      436,   500,   504,   536,   544,   560,   576,   584,
+      592,   612,   640,   664,   708,   712,   744,   756,
+      776,   820,   832,   840,   852,   888,   896,   920,
+      964,  1004,  1040,  1048,  1060,  1100,  1140,  1172,
+     1204,  1224,  2312,  2436,  2448,  2464,  2480,  2492,
+     2512,  2524,  2536,  2596,  2608,  2636,  2652,  2656,
+     2676,  2684,  2688,  2720,  2732,  2744,  2752,  2764,
+     2772,  2776,  2792,  2816,  2824,  2832,  2852,  2884,
+     2896,  2908,  2940,  2952,  2960,  2972,  3520,  3528,
+     3540,  3584,  3620,  3648,  3652,  3656,  3680,  3704,
+     3720,  3776,  3804,  3824,  3856,  3888,  4020,  4044,
+     4084,  4116,  4156,  4272,  4288,  4296,  4332,  4352,
+     4428,  4432,  8740,  8764,  9552,  9868,  9888,  9900,
+     9916,  9928,  9940,  9948,  9960,  9976,  9992, 10004,
+    10016, 10032, 10044, 10060, 10076, 10088, 10104, 10120,
+    10132, 10140, 10152, 10164, 10176, 10188, 10204, 10220,
+    10236, 10248, 10260, 10272, 10284, 10300, 10316, 10332,
+    10348, 10360, 10372, 10384, 10396, 10408, 10420, 10424,
+    10436, 10460, 10472, 10480, 10492, 10520, 10532, 10540,
+    10552, 10564, 10576, 10600, 10612, 10624, 10636, 10660,
+    10672, 10676, 10700, 10704, 10728, 10756, 10764, 10788,
+    10792, 10796, 10804, 10816, 10824, 10844, 10872, 10876,
+    10888, 10892, 10896, 10908, 11076, 11100, 11140, 11144,
+    11148, 11748, 11788, 11820, 11868, 11900, 11940, 11964,
+    11968, 11980, 12008, 12080, 12120, 12176, 12212, 12248,
+    12288, 12320, 12380, 12400, 12448, 12536, 12576, 12704,
+    12736, 12968, 13000, 13024, 13080, 13136, 13204, 13272,
+    13336, 13380, 13428, 14272, 14288, 14320, 14324, 14332,
+    14340, 14384, 14412, 14440, 14468, 14500, 14532, 14560,
+    14604, 14636, 14656, 14696, 14740, 14792, 14824, 14848,
+    14892, 14944, 14996, 15032, 15076, 15128, 15180, 15216,
+    15272, 15328, 15372, 15416, 15472, 15480, 15488, 15496,
+    15516, 15524, 15536, 15540, 15544, 15588, 15604, 15616,
+    15628, 15632, 15644, 15652, 15656, 15680, 15688, 15692,
+    15704, 15712, 15724, 15764, 15772, 15776, 15804, 15828,
+    15848, 15868, 15880, 15892, 15904, 15912, 15920, 15928,
+    15936,    -1,
+};
+
+static const int *unsupported_strings[] =
+{
+    unsupported_strings_1_0,
+    unsupported_strings_1_2,
+    unsupported_strings_1_3,
+};
+
+static boolean StringIsUnsupported(unsigned int offset)
+{
+    const int *string_list;
+    int i;
+
+    string_list = unsupported_strings[deh_hhe_version];
+
+    for (i=0; string_list[i] >= 0; ++i)
+    {
+        if ((unsigned int) string_list[i] == offset)
+        {
+            return true;
+        }
+    }
+
+    return false;
+}
+
+static boolean GetStringByOffset(unsigned int offset, char **result)
+{
+    int i;
+
+    for (i=0; i<arrlen(strings); ++i)
+    {
+        if (strings[i].offsets[deh_hhe_version] == offset)
+        {
+            *result = strings[i].string;
+            return true;
+        }
+    }
+
+    return false;
+}
+
+// Given a string length, find the maximum length of a 
+// string that can replace it.
+
+static int MaxStringLength(int len)
+{
+    // Enough bytes for the string and the NUL terminator
+
+    len += 1;
+
+    // All strings in doom.exe are on 4-byte boundaries, so we may be able
+    // to support a slightly longer string.
+    // Extend up to the next 4-byte boundary
+
+    len += (4 - (len % 4)) % 4;
+
+    // Less one for the NUL terminator.
+
+    return len - 1;
+}
+
+// If a string offset does not match any string, it may be because
+// we are running in the wrong version mode, and the patch was generated
+// for a different Heretic version.  Search the lookup tables to find
+// versiosn that match.
+
+static void SuggestOtherVersions(unsigned int offset)
+{
+    const int *string_list;
+    unsigned int i;
+    unsigned int v;
+
+    // Check main string table.
+
+    for (i=0; i<arrlen(strings); ++i)
+    {
+        for (v=0; v<deh_hhe_num_versions; ++v)
+        {
+            if (strings[i].offsets[v] == offset)
+            {
+                DEH_SuggestHereticVersion(v);
+            }
+        }
+    }
+
+    // Check unsupported string tables.
+
+    for (v=0; v<deh_hhe_num_versions; ++v)
+    {
+        string_list = unsupported_strings[v];
+
+        for (i=0; string_list[i] >= 0; ++i)
+        {
+            if (string_list[i] == offset)
+            {
+                DEH_SuggestHereticVersion(v);
+            }
+        }
+    }
+}
+
+static void *DEH_TextStart(deh_context_t *context, char *line)
+{
+    char *repl_text;
+    char *orig_text;
+    int orig_offset, repl_len;
+    int i;
+
+    if (sscanf(line, "Text %i %i", &orig_offset, &repl_len) != 2)
+    {
+        DEH_Warning(context, "Parse error on section start");
+        return NULL;
+    }
+
+    repl_text = Z_Malloc(repl_len + 1, PU_STATIC, NULL);
+
+    // read in the "to" text
+
+    for (i=0; i<repl_len; ++i)
+    {
+        int c;
+
+        c = DEH_GetChar(context);
+
+        repl_text[i] = c;
+    }
+    repl_text[repl_len] = '\0';
+
+    // We don't support all strings, but at least recognise them:
+
+    if (StringIsUnsupported(orig_offset))
+    {
+        DEH_Warning(context, "Unsupported string replacement: %i", orig_offset);
+    }
+
+    // Find the string to replace:
+
+    else if (!GetStringByOffset(orig_offset, &orig_text))
+    {
+        SuggestOtherVersions(orig_offset);
+        DEH_Error(context, "Unknown string offset: %i", orig_offset);
+    }
+
+    // Only allow string replacements that are possible in Vanilla Doom.  
+    // Chocolate Doom is unforgiving!
+
+    else if (!deh_allow_long_strings
+          && repl_len > MaxStringLength(strlen(orig_text)))
+    {
+        DEH_Error(context, "Replacement string is longer than the maximum "
+                           "possible in heretic.exe");
+    }
+    else
+    {
+        // Success.
+
+        DEH_AddStringReplacement(orig_text, repl_text);
+
+        return NULL;
+    }
+
+    // Failure.
+
+    Z_Free(repl_text);
+
+    return NULL;
+}
+
+static void DEH_TextParseLine(deh_context_t *context, char *line, void *tag)
+{
+    // not used
+}
+
+deh_section_t deh_section_heretic_text =
+{
+    "Text",
+    NULL,
+    DEH_TextStart,
+    DEH_TextParseLine,
+    NULL,
+    NULL,
+};
+
--- /dev/null
+++ b/src/heretic/deh_htic.c
@@ -1,0 +1,186 @@
+// Emacs style mode select   -*- C++ -*-
+//-----------------------------------------------------------------------------
+//
+// Copyright(C) 2005 Simon Howard
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+// 02111-1307, USA.
+//
+//-----------------------------------------------------------------------------
+//
+// Top-level dehacked definitions for Heretic dehacked (HHE).
+//
+//-----------------------------------------------------------------------------
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "deh_defs.h"
+#include "deh_main.h"
+#include "deh_htic.h"
+#include "info.h"
+#include "m_argv.h"
+
+char *deh_signatures[] =
+{
+    "Patch File for HHE v1.0",
+    "Patch File for HHE v1.1",
+    NULL
+};
+
+static char *hhe_versions[] =
+{
+    "1.0", "1.2", "1.3"
+};
+
+// Version number for patches.
+
+deh_hhe_version_t deh_hhe_version = deh_hhe_1_0;
+
+// deh_ammo.c:
+extern deh_section_t deh_section_ammo;
+// deh_frame.c:
+extern deh_section_t deh_section_frame;
+// deh_ptr.c:
+extern deh_section_t deh_section_pointer;
+// deh_sound.c
+extern deh_section_t deh_section_sound;
+// deh_htext.c:
+extern deh_section_t deh_section_heretic_text;
+// deh_thing.c:
+extern deh_section_t deh_section_thing;
+// deh_weapon.c:
+extern deh_section_t deh_section_weapon;
+
+//
+// List of section types:
+//
+
+deh_section_t *deh_section_types[] =
+{
+    &deh_section_ammo,
+    &deh_section_frame,
+//    &deh_section_pointer, TODO
+    &deh_section_sound,
+    &deh_section_heretic_text,
+    &deh_section_thing,
+    &deh_section_weapon,
+    NULL
+};
+
+static void SetHHEVersionByName(char *name)
+{
+    int i;
+
+    for (i=0; i<arrlen(hhe_versions); ++i)
+    {
+        if (!strcmp(hhe_versions[i], name))
+        {
+            deh_hhe_version = i;
+            return;
+        }
+    }
+
+    fprintf(stderr, "Unknown Heretic version: %s\n", name);
+    fprintf(stderr, "Valid versions:\n");
+
+    for (i=0; i<arrlen(hhe_versions); ++i)
+    {
+        fprintf(stderr, "\t%s\n", hhe_versions[i]);
+    }
+}
+
+// Initialize Heretic(HHE)-specific dehacked bits.
+
+void DEH_HereticInit(void)
+{
+    int i;
+
+    //!
+    // @arg <version>
+    //
+    // Select the Heretic version number that was used to generate the
+    // HHE patch to be loaded.  Patches for each of the Vanilla
+    // Heretic versions (1.0, 1.2, 1.3) can be loaded, but the correct
+    // version number must be specified.
+
+    i = M_CheckParm("-hhever");
+
+    if (i > 0)
+    {
+        SetHHEVersionByName(myargv[i + 1]);
+    }
+
+    // For v1.0 patches, we must apply a slight change to the states[]
+    // table.  The table was changed between 1.0 and 1.3 to add two extra
+    // frames to the player "burning death" animation.
+    //
+    // If we are using a v1.0 patch, we must change the table to cut
+    // these out again.
+
+    if (deh_hhe_version < deh_hhe_1_2)
+    {
+        states[S_PLAY_FDTH18].nextstate = S_NULL;
+    }
+}
+
+int DEH_MapHereticFrameNumber(int frame)
+{
+    if (deh_hhe_version < deh_hhe_1_2)
+    {
+        // Between Heretic 1.0 and 1.2, two new frames
+        // were added to the "states" table, to extend the "flame death"
+        // animation displayed when the player is killed by fire.  Therefore,
+        // we must map Heretic 1.0 frame numbers to corresponding indexes
+        // for our state table.
+
+        if (frame >= S_PLAY_FDTH19)
+        {
+            frame = (frame - S_PLAY_FDTH19) + S_BLOODYSKULL1;
+        }
+    }
+    else
+    {
+        // After Heretic 1.2, three unused frames were removed from the
+        // states table, unused phoenix rod frames.  Our state table includes
+        // these missing states for backwards compatibility.  We must therefore
+        // adjust frame numbers for v1.2/v1.3 to corresponding indexes for
+        // our state table.
+
+        if (frame >= S_PHOENIXFXIX_1)
+        {
+            frame = (frame - S_PHOENIXFXIX_1) + S_PHOENIXPUFF1;
+        }
+    }
+
+    return frame;
+}
+
+void DEH_SuggestHereticVersion(deh_hhe_version_t version)
+{
+    fprintf(stderr,
+    "\n"
+    "This patch may be for version %s. You are currently running in\n"
+    "Heretic %s mode. For %s mode, this mode, add this to your command line:\n"
+    "\n"
+    "\t-hhever %s\n"
+    "\n",
+    hhe_versions[version],
+    hhe_versions[deh_hhe_version],
+    hhe_versions[version],
+    hhe_versions[version]);
+}
+
--- /dev/null
+++ b/src/heretic/deh_htic.h
@@ -1,0 +1,60 @@
+// Emacs style mode select   -*- C++ -*- 
+//-----------------------------------------------------------------------------
+//
+// Copyright(C) 2010 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.
+//
+//-----------------------------------------------------------------------------
+//
+// Common header for Heretic dehacked (HHE) support.
+//
+//-----------------------------------------------------------------------------
+
+#ifndef DEH_HTIC_H
+#define DEH_HTIC_H
+
+#include "info.h"
+
+// HHE executable version.  Loading HHE patches is (unfortunately)
+// dependent on the version of the Heretic executable used to make them.
+
+typedef enum
+{
+    deh_hhe_1_0,
+    deh_hhe_1_2,
+    deh_hhe_1_3,
+    deh_hhe_num_versions
+} deh_hhe_version_t;
+
+// HHE doesn't know about the last two states in the state table, so
+// these are considered invalid.
+
+#define DEH_HERETIC_NUMSTATES (NUMSTATES - 2)
+
+// It also doesn't know about the last two things in the mobjinfo table
+// (which correspond to the states above)
+
+#define DEH_HERETIC_NUMMOBJTYPES (NUMMOBJTYPES - 2)
+
+void DEH_HereticInit(void);
+int DEH_MapHereticFrameNumber(int frame);
+void DEH_SuggestHereticVersion(deh_hhe_version_t version);
+
+extern deh_hhe_version_t deh_hhe_version;
+
+#endif /* #ifndef DEH_HTIC_H */
+
--- /dev/null
+++ b/src/heretic/deh_sound.c
@@ -1,0 +1,118 @@
+// Emacs style mode select   -*- C++ -*- 
+//-----------------------------------------------------------------------------
+//
+// Copyright(C) 2005 Simon Howard
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+// 02111-1307, USA.
+//
+//-----------------------------------------------------------------------------
+//
+// Parses "Sound" sections in dehacked files
+//
+//-----------------------------------------------------------------------------
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "doomfeatures.h"
+#include "doomtype.h"
+#include "deh_defs.h"
+#include "deh_main.h"
+#include "deh_mapping.h"
+
+#include "doomdef.h"
+#include "i_sound.h"
+
+#include "sounds.h"
+
+DEH_BEGIN_MAPPING(sound_mapping, sfxinfo_t)
+    DEH_MAPPING_STRING("Name", name)
+    DEH_UNSUPPORTED_MAPPING("Special")
+    DEH_MAPPING("Value", priority)
+    DEH_MAPPING("Unknown 1", usefulness)
+    DEH_UNSUPPORTED_MAPPING("Unknown 2")
+    DEH_UNSUPPORTED_MAPPING("Unknown 3")
+    DEH_MAPPING("One/Two", numchannels)
+DEH_END_MAPPING
+
+static void *DEH_SoundStart(deh_context_t *context, char *line)
+{
+    int sound_number = 0;
+    
+    if (sscanf(line, "Sound %i", &sound_number) != 1)
+    {
+        DEH_Warning(context, "Parse error on section start");
+        return NULL;
+    }
+
+    if (sound_number < 0 || sound_number >= NUMSFX)
+    {
+        DEH_Warning(context, "Invalid sound number: %i", sound_number);
+        return NULL;
+    }
+
+    if (sound_number >= DEH_VANILLA_NUMSFX)
+    {
+        DEH_Warning(context, "Attempt to modify SFX %i.  This will cause "
+                             "problems in Vanilla dehacked.", sound_number); 
+    }
+
+    return &S_sfx[sound_number];
+}
+
+static void DEH_SoundParseLine(deh_context_t *context, char *line, void *tag)
+{
+    sfxinfo_t *sfx;
+    char *variable_name, *value;
+
+    if (tag == NULL)
+       return;
+
+    sfx = (sfxinfo_t *) tag;
+
+    // Parse the assignment
+
+    if (!DEH_ParseAssignment(line, &variable_name, &value))
+    {
+        // Failed to parse
+        DEH_Warning(context, "Failed to parse assignment");
+        return;
+    }
+
+    // Set the field value:
+
+    if (!strcasecmp(variable_name, "Name"))
+    {
+        DEH_SetStringMapping(context, &sound_mapping, sfx,
+                             variable_name, value);
+    }
+    else
+    {
+        DEH_SetMapping(context, &sound_mapping, sfx,
+                       variable_name, atoi(value));
+    }
+}
+
+deh_section_t deh_section_sound =
+{
+    "Sound",
+    NULL,
+    DEH_SoundStart,
+    DEH_SoundParseLine,
+    NULL,
+    NULL,
+};
+
--- /dev/null
+++ b/src/heretic/deh_thing.c
@@ -1,0 +1,150 @@
+// Emacs style mode select   -*- C++ -*- 
+//-----------------------------------------------------------------------------
+//
+// Copyright(C) 2005 Simon Howard
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+// 02111-1307, USA.
+//
+//-----------------------------------------------------------------------------
+//
+// Parses "Thing" sections in dehacked files
+//
+//-----------------------------------------------------------------------------
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "doomtype.h"
+#include "m_misc.h"
+
+#include "deh_defs.h"
+#include "deh_main.h"
+#include "deh_mapping.h"
+#include "deh_htic.h"
+
+#include "info.h"
+
+DEH_BEGIN_MAPPING(thing_mapping, mobjinfo_t)
+  DEH_MAPPING("ID #",                doomednum)
+  DEH_MAPPING("Initial frame",       spawnstate)
+  DEH_MAPPING("Hit points",          spawnhealth)
+  DEH_MAPPING("First moving frame",  seestate)
+  DEH_MAPPING("Alert sound",         seesound)
+  DEH_MAPPING("Reaction time",       reactiontime)
+  DEH_MAPPING("Attack sound",        attacksound)
+  DEH_MAPPING("Injury frame",        painstate)
+  DEH_MAPPING("Pain chance",         painchance)
+  DEH_MAPPING("Pain sound",          painsound)
+  DEH_MAPPING("Close attack frame",  meleestate)
+  DEH_MAPPING("Far attack frame",    missilestate)
+  DEH_MAPPING("Burning frame",       crashstate)
+  DEH_MAPPING("Death frame",         deathstate)
+  DEH_MAPPING("Exploding frame",     xdeathstate)
+  DEH_MAPPING("Death sound",         deathsound)
+  DEH_MAPPING("Speed",               speed)
+  DEH_MAPPING("Width",               radius)
+  DEH_MAPPING("Height",              height)
+  DEH_MAPPING("Mass",                mass)
+  DEH_MAPPING("Missile damage",      damage)
+  DEH_MAPPING("Action sound",        activesound)
+  DEH_MAPPING("Bits 1",              flags)
+  DEH_MAPPING("Bits 2",              flags2)
+DEH_END_MAPPING
+
+static void *DEH_ThingStart(deh_context_t *context, char *line)
+{
+    int thing_number = 0;
+    mobjinfo_t *mobj;
+
+    if (sscanf(line, "Thing %i", &thing_number) != 1)
+    {
+        DEH_Warning(context, "Parse error on section start");
+        return NULL;
+    }
+
+    // HHE thing numbers are indexed from 1
+    --thing_number;
+
+    if (thing_number < 0 || thing_number >= DEH_HERETIC_NUMMOBJTYPES)
+    {
+        DEH_Warning(context, "Invalid thing number: %i", thing_number);
+        return NULL;
+    }
+
+    mobj = &mobjinfo[thing_number];
+
+    return mobj;
+}
+
+static void DEH_ThingParseLine(deh_context_t *context, char *line, void *tag)
+{
+    mobjinfo_t *mobj;
+    char *variable_name, *value;
+    int ivalue;
+
+    if (tag == NULL)
+       return;
+
+    mobj = (mobjinfo_t *) tag;
+
+    // Parse the assignment
+
+    if (!DEH_ParseAssignment(line, &variable_name, &value))
+    {
+        // Failed to parse
+
+        DEH_Warning(context, "Failed to parse assignment");
+        return;
+    }
+
+    // all values are integers
+
+    ivalue = atoi(value);
+
+    // If the value to be set is a frame, the frame number must
+    // undergo transformation from a Heretic 1.0 index to a
+    // Heretic 1.3 index.
+
+    if (M_StrCaseStr(variable_name, "frame") != NULL)
+    {
+        ivalue = DEH_MapHereticFrameNumber(ivalue);
+    }
+
+    // Set the field value
+
+    DEH_SetMapping(context, &thing_mapping, mobj, variable_name, ivalue);
+}
+
+static void DEH_ThingMD5Sum(md5_context_t *context)
+{
+    int i;
+
+    for (i=0; i<NUMMOBJTYPES; ++i)
+    {
+        DEH_StructMD5Sum(context, &thing_mapping, &mobjinfo[i]);
+    }
+}
+
+deh_section_t deh_section_thing =
+{
+    "Thing",
+    NULL,
+    DEH_ThingStart,
+    DEH_ThingParseLine,
+    NULL,
+    DEH_ThingMD5Sum,
+};
+
--- /dev/null
+++ b/src/heretic/deh_weapon.c
@@ -1,0 +1,131 @@
+// Emacs style mode select   -*- C++ -*- 
+//-----------------------------------------------------------------------------
+//
+// Copyright(C) 2005 Simon Howard
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+// 02111-1307, USA.
+//
+//-----------------------------------------------------------------------------
+//
+// Parses "Weapon" sections in dehacked files
+//
+//-----------------------------------------------------------------------------
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "doomtype.h"
+#include "m_misc.h"
+
+#include "doomdef.h"
+
+#include "deh_defs.h"
+#include "deh_main.h"
+#include "deh_mapping.h"
+#include "deh_htic.h"
+
+DEH_BEGIN_MAPPING(weapon_mapping, weaponinfo_t)
+  DEH_MAPPING("Ammo type",        ammo)
+  DEH_MAPPING("Deselect frame",   upstate)
+  DEH_MAPPING("Select frame",     downstate)
+  DEH_MAPPING("Bobbing frame",    readystate)
+  DEH_MAPPING("Shooting frame",   atkstate)
+  DEH_MAPPING("Firing frame",     holdatkstate)
+  DEH_MAPPING("Unknown frame",    flashstate)
+DEH_END_MAPPING
+
+static void *DEH_WeaponStart(deh_context_t *context, char *line)
+{
+    int weapon_number = 0;
+
+    if (sscanf(line, "Weapon %i", &weapon_number) != 1)
+    {
+        DEH_Warning(context, "Parse error on section start");
+        return NULL;
+    }
+
+    if (weapon_number < 0 || weapon_number >= NUMWEAPONS * 2)
+    {
+        DEH_Warning(context, "Invalid weapon number: %i", weapon_number);
+        return NULL;
+    }
+
+    // Because of the tome of power, we have two levels of weapons:
+
+    if (weapon_number < NUMWEAPONS)
+    {
+        return &wpnlev1info[weapon_number];
+    }
+    else
+    {
+        return &wpnlev2info[weapon_number - NUMWEAPONS];
+    }
+}
+
+static void DEH_WeaponParseLine(deh_context_t *context, char *line, void *tag)
+{
+    char *variable_name, *value;
+    weaponinfo_t *weapon;
+    int ivalue;
+
+    if (tag == NULL)
+        return;
+
+    weapon = (weaponinfo_t *) tag;
+
+    if (!DEH_ParseAssignment(line, &variable_name, &value))
+    {
+        // Failed to parse
+
+        DEH_Warning(context, "Failed to parse assignment");
+        return;
+    }
+
+    ivalue = atoi(value);
+
+    // If this is a frame field, we need to map from Heretic 1.0 frame
+    // numbers to Heretic 1.3 frame numbers.
+
+    if (M_StrCaseStr(variable_name, "frame") != NULL)
+    {
+        ivalue = DEH_MapHereticFrameNumber(ivalue);
+    }
+
+    DEH_SetMapping(context, &weapon_mapping, weapon, variable_name, ivalue);
+}
+
+static void DEH_WeaponMD5Sum(md5_context_t *context)
+{
+    int i;
+
+    for (i=0; i<NUMWEAPONS ;++i)
+    {
+        DEH_StructMD5Sum(context, &weapon_mapping, &wpnlev1info[i]);
+        DEH_StructMD5Sum(context, &weapon_mapping, &wpnlev2info[i]);
+    }
+}
+
+deh_section_t deh_section_weapon =
+{
+    "Weapon",
+    NULL,
+    DEH_WeaponStart,
+    DEH_WeaponParseLine,
+    NULL,
+    DEH_WeaponMD5Sum,
+};
+
--- a/src/heretic/doomdef.h
+++ b/src/heretic/doomdef.h
@@ -581,6 +581,7 @@
 extern boolean DebugSound;      // debug flag for displaying sound info
 
 extern int maxammo[NUMAMMO];
+extern int GetWeaponAmmo[NUMWEAPONS];
 
 extern boolean demoplayback;
 extern int skytexture;
--- a/src/heretic/dstrings.h
+++ b/src/heretic/dstrings.h
@@ -26,42 +26,6 @@
 
 //---------------------------------------------------------------------------
 //
-// M_menu.c
-//
-//---------------------------------------------------------------------------
-#define PRESSKEY 	"press a key."
-#define PRESSYN 	"press y or n."
-#define TXT_PAUSED "PAUSED"
-#define QUITMSG		"are you sure you want to\nquit this great game?"
-#define LOADNET 	"you can't do load while in a net game!\n\n"PRESSKEY
-#define QLOADNET	"you can't quickload during a netgame!\n\n"PRESSKEY
-#define QSAVESPOT	"you haven't picked a quicksave slot yet!\n\n"PRESSKEY
-#define SAVEDEAD 	"you can't save if you aren't playing!\n\n"PRESSKEY
-#define QSPROMPT 	"quicksave over your game named\n\n'%s'?\n\n"PRESSYN
-#define QLPROMPT	"do you want to quickload the game named"\
-					"\n\n'%s'?\n\n"PRESSYN
-#define NEWGAME		"you can't start a new game\n"\
-					"while in a network game.\n\n"PRESSKEY
-#define NIGHTMARE	"are you sure? this skill level\n"\
-					"isn't even remotely fair.\n\n"PRESSYN
-#define SWSTRING	"this is the shareware version of doom.\n\n"\
-					"you need to order the entire trilogy.\n\n"PRESSKEY
-#define MSGOFF		"Messages OFF"
-#define MSGON		"Messages ON"
-#define NETEND		"you can't end a netgame!\n\n"PRESSKEY
-#define ENDGAME		"are you sure you want to end the game?\n\n"PRESSYN
-#define DOSY		"(press y to quit to dos.)"
-#define DETAILHI	"High detail"
-#define DETAILLO	"Low detail"
-#define GAMMALVL0	"Gamma correction OFF"
-#define GAMMALVL1	"Gamma correction level 1"
-#define GAMMALVL2	"Gamma correction level 2"
-#define GAMMALVL3	"Gamma correction level 3"
-#define GAMMALVL4	"Gamma correction level 4"
-#define	EMPTYSTRING	"empty slot"
-
-//---------------------------------------------------------------------------
-//
 // P_inter.c
 //
 //---------------------------------------------------------------------------
@@ -170,74 +134,6 @@
 
 //---------------------------------------------------------------------------
 //
-// HU_stuff.c
-//
-//---------------------------------------------------------------------------
-
-#define HUSTR_E1M1	"E1M1: Hangar"
-#define HUSTR_E1M2	"E1M2: Nuclear Plant"
-#define HUSTR_E1M3	"E1M3: Toxin Refinery"
-#define HUSTR_E1M4	"E1M4: Command Control"
-#define HUSTR_E1M5	"E1M5: Phobos Lab"
-#define HUSTR_E1M6	"E1M6: Central Processing"
-#define HUSTR_E1M7	"E1M7: Computer Station"
-#define HUSTR_E1M8	"E1M8: Phobos Anomaly"
-#define HUSTR_E1M9	"E1M9: Military Base"
-
-#define HUSTR_E2M1	"E2M1: Deimos Anomaly"
-#define HUSTR_E2M2	"E2M2: Containment Area"
-#define HUSTR_E2M3	"E2M3: Refinery"
-#define HUSTR_E2M4	"E2M4: Deimos Lab"
-#define HUSTR_E2M5	"E2M5: Command Center"
-#define HUSTR_E2M6	"E2M6: Halls of the Damned"
-#define HUSTR_E2M7	"E2M7: Spawning Vats"
-#define HUSTR_E2M8	"E2M8: Tower of Babel"
-#define HUSTR_E2M9	"E2M9: Fortress of Mystery"
-
-#define HUSTR_E3M1	"E3M1: Hell Keep"
-#define HUSTR_E3M2	"E3M2: Slough of Despair"
-#define HUSTR_E3M3	"E3M3: Pandemonium"
-#define HUSTR_E3M4	"E3M4: House of Pain"
-#define HUSTR_E3M5	"E3M5: Unholy Cathedral"
-#define HUSTR_E3M6	"E3M6: Mt. Erebus"
-#define HUSTR_E3M7	"E3M7: Limbo"
-#define HUSTR_E3M8	"E3M8: Dis"
-#define HUSTR_E3M9	"E3M9: Warrens"
-
-#define HUSTR_CHATMACRO1	"I'm ready to kick butt!"
-#define HUSTR_CHATMACRO2	"I'm OK."
-#define HUSTR_CHATMACRO3	"I'm not looking too good!"
-#define HUSTR_CHATMACRO4	"Help!"
-#define HUSTR_CHATMACRO5	"You suck!"
-#define HUSTR_CHATMACRO6	"Next time, scumbag..."
-#define HUSTR_CHATMACRO7	"Come here!"
-#define HUSTR_CHATMACRO8	"I'll take care of it."
-#define HUSTR_CHATMACRO9	"Yes"
-#define HUSTR_CHATMACRO0	"No"
-
-#define HUSTR_TALKTOSELF1	"You mumble to yourself"
-#define HUSTR_TALKTOSELF2	"Who's there?"
-#define HUSTR_TALKTOSELF3	"You scare yourself"
-#define HUSTR_TALKTOSELF4	"You start to rave"
-#define HUSTR_TALKTOSELF5	"You've lost it..."
-
-#define HUSTR_MESSAGESENT	"[Message Sent]"
-
-// The following should NOT be changed unless it seems
-// just AWFULLY necessary
-
-#define HUSTR_PLRGREEN	"Green: "
-#define HUSTR_PLRINDIGO	"Indigo: "
-#define HUSTR_PLRBROWN	"Brown: "
-#define HUSTR_PLRRED		"Red: "
-
-#define HUSTR_KEYGREEN	'g'
-#define HUSTR_KEYINDIGO	'i'
-#define HUSTR_KEYBROWN	'b'
-#define HUSTR_KEYRED		'r'
-
-//---------------------------------------------------------------------------
-//
 // AM_map.c
 //
 //---------------------------------------------------------------------------
@@ -253,26 +149,6 @@
 
 //---------------------------------------------------------------------------
 //
-// ST_stuff.c
-//
-//---------------------------------------------------------------------------
-
-#define STSTR_DQDON			"Degreelessness Mode On"
-#define STSTR_DQDOFF		"Degreelessness Mode Off"
-
-#define STSTR_KFAADDED		"Very Happy Ammo Added"
-
-#define STSTR_NCON			"No Clipping Mode ON"
-#define STSTR_NCOFF			"No Clipping Mode OFF"
-
-#define STSTR_BEHOLD		"inVuln, Str, Inviso, Rad, Allmap, or Lite-amp"
-#define STSTR_BEHOLDX		"Power-up Toggled"
-
-#define STSTR_CHOPPERS		"... doesn't suck - GM"
-#define STSTR_CLEV			"Changing Level..."
-
-//---------------------------------------------------------------------------
-//
 // F_finale.c
 //
 //---------------------------------------------------------------------------
@@ -374,56 +250,3 @@
 					"surrender without a fight. eyes\n"\
 					"wide, you go to meet your fate."
 
-/*
-#define E1TEXT	"Once you beat the big badasses and\n"\
-				"clean out the moon base you're supposed\n"\
-				"to win, aren't you? Aren't you? Where's\n"\
-				"your fat reward and ticket home? What\n"\
-				"the hell is this? It's not supposed to\n"\
-				"end this way!\n"\
-				"\n" \
-				"It stinks like rotten meat, but looks\n"\
-				"like the lost Deimos base.  Looks like\n"\
-				"you're stuck on The Shores of Hell.\n"\
-				"The only way out is through.\n"\
-				"\n"\
-				"To continue the DOOM experience, play\n"\
-				"The Shores of Hell and its amazing\n"\
-				"sequel, Inferno!\n"
-
-#define E2TEXT	"You've done it! The hideous cyber-\n"\
-				"demon lord that ruled the lost Deimos\n"\
-				"moon base has been slain and you\n"\
-				"are triumphant! But ... where are\n"\
-				"you? You clamber to the edge of the\n"\
-				"moon and look down to see the awful\n"\
-				"truth.\n" \
-				"\n"\
-				"Deimos floats above Hell itself!\n"\
-				"You've never heard of anyone escaping\n"\
-				"from Hell, but you'll make the bastards\n"\
-				"sorry they ever heard of you! Quickly,\n"\
-				"you rappel down to  the surface of\n"\
-				"Hell.\n"\
-				"\n" \
-				"Now, it's on to the final chapter of\n"\
-				"DOOM! -- Inferno."
-
-#define E3TEXT	"The loathsome spiderdemon that\n"\
-				"masterminded the invasion of the moon\n"\
-				"bases and caused so much death has had\n"\
-				"its ass kicked for all time.\n"\
-				"\n"\
-				"A hidden doorway opens and you enter.\n"\
-				"You've proven too tough for Hell to\n"\
-				"contain, and now Hell at last plays\n"\
-				"fair -- for you emerge from the door\n"\
-				"to see the green fields of Earth!\n"\
-				"Home at last.\n" \
-				"\n"\
-				"You wonder what's been happening on\n"\
-				"Earth while you were battling evil\n"\
-				"unleashed. It's good that no Hell-\n"\
-				"spawn could have come through that\n"\
-				"door with you ..."
-*/
--- a/src/heretic/f_finale.c
+++ b/src/heretic/f_finale.c
@@ -26,6 +26,7 @@
 #include <ctype.h>
 
 #include "doomdef.h"
+#include "deh_str.h"
 #include "i_swap.h"
 #include "i_video.h"
 #include "s_sound.h"
@@ -37,11 +38,6 @@
 #define TEXTSPEED       3
 #define TEXTWAIT        250
 
-char *e1text = E1TEXT;
-char *e2text = E2TEXT;
-char *e3text = E3TEXT;
-char *e4text = E4TEXT;
-char *e5text = E5TEXT;
 char *finaletext;
 char *finaleflat;
 
@@ -72,30 +68,30 @@
     switch (gameepisode)
     {
         case 1:
-            finaleflat = "FLOOR25";
-            finaletext = e1text;
+            finaleflat = DEH_String("FLOOR25");
+            finaletext = DEH_String(E1TEXT);
             break;
         case 2:
-            finaleflat = "FLATHUH1";
-            finaletext = e2text;
+            finaleflat = DEH_String("FLATHUH1");
+            finaletext = DEH_String(E2TEXT);
             break;
         case 3:
-            finaleflat = "FLTWAWA2";
-            finaletext = e3text;
+            finaleflat = DEH_String("FLTWAWA2");
+            finaletext = DEH_String(E3TEXT);
             break;
         case 4:
-            finaleflat = "FLOOR28";
-            finaletext = e4text;
+            finaleflat = DEH_String("FLOOR28");
+            finaletext = DEH_String(E4TEXT);
             break;
         case 5:
-            finaleflat = "FLOOR08";
-            finaletext = e5text;
+            finaleflat = DEH_String("FLOOR08");
+            finaletext = DEH_String(E5TEXT);
             break;
     }
 
     finalestage = 0;
     finalecount = 0;
-    FontABaseLump = W_GetNumForName("FONTA_S") + 1;
+    FontABaseLump = W_GetNumForName(DEH_String("FONTA_S")) + 1;
 
 //      S_ChangeMusic(mus_victor, true);
     S_StartSong(mus_cptd, true);
@@ -277,8 +273,8 @@
     {
         return;
     }
-    p1 = W_CacheLumpName("FINAL1", PU_LEVEL);
-    p2 = W_CacheLumpName("FINAL2", PU_LEVEL);
+    p1 = W_CacheLumpName(DEH_String("FINAL1"), PU_LEVEL);
+    p2 = W_CacheLumpName(DEH_String("FINAL2"), PU_LEVEL);
     if (finalecount < 70)
     {
         memcpy(I_VideoBuffer, p1, SCREENHEIGHT * SCREENWIDTH);
@@ -319,8 +315,8 @@
             {
                 underwawa = true;
                 memset((byte *) 0xa0000, 0, SCREENWIDTH * SCREENHEIGHT);
-                I_SetPalette(W_CacheLumpName("E2PAL", PU_CACHE));
-                V_DrawRawScreen(W_CacheLumpName("E2END", PU_CACHE));
+                I_SetPalette(W_CacheLumpName(DEH_String("E2PAL"), PU_CACHE));
+                V_DrawRawScreen(W_CacheLumpName(DEH_String("E2END"), PU_CACHE));
             }
             paused = false;
             MenuActive = false;
@@ -328,7 +324,7 @@
 
             break;
         case 2:
-            V_DrawRawScreen(W_CacheLumpName("TITLE", PU_CACHE));
+            V_DrawRawScreen(W_CacheLumpName(DEH_String("TITLE"), PU_CACHE));
             //D_StartTitle(); // go to intro/demo mode.
     }
 }
--- a/src/heretic/g_game.c
+++ b/src/heretic/g_game.c
@@ -28,6 +28,7 @@
 #include <string.h>
 #include "doomdef.h"
 #include "doomkeys.h"
+#include "deh_str.h"
 #include "i_timer.h"
 #include "i_system.h"
 #include "m_controls.h"
@@ -862,12 +863,16 @@
                         {
                             if (netgame)
                             {
-                                strcpy(savedescription, "NET GAME");
+                                strncpy(savedescription, DEH_String("NET GAME"),
+                                        sizeof(savedescription));
                             }
                             else
                             {
-                                strcpy(savedescription, "SAVE GAME");
+                                strncpy(savedescription, DEH_String("SAVE GAME"),
+                                        sizeof(savedescription));
                             }
+
+                            savedescription[sizeof(savedescription) - 1] = '\0';
                         }
                         savegameslot =
                             (players[i].cmd.
@@ -1320,7 +1325,9 @@
     save_p = savebuffer + SAVESTRINGSIZE;
     // Skip the description field
     memset(vcheck, 0, sizeof(vcheck));
-    sprintf(vcheck, "version %i", HERETIC_VERSION);
+
+    DEH_snprintf(vcheck, VERSIONSIZE, "version %i", HERETIC_VERSION);
+
     if (strcmp((char *) save_p, vcheck) != 0)
     {                           // Bad version
         return;
@@ -1449,11 +1456,11 @@
     // Set the sky map
     if (episode > 5)
     {
-        skytexture = R_TextureNumForName("SKY1");
+        skytexture = R_TextureNumForName(DEH_String("SKY1"));
     }
     else
     {
-        skytexture = R_TextureNumForName(skyLumpNames[episode - 1]);
+        skytexture = R_TextureNumForName(DEH_String(skyLumpNames[episode - 1]));
     }
 
 //
@@ -1694,7 +1701,7 @@
     SV_Open(name);
     SV_Write(description, SAVESTRINGSIZE);
     memset(verString, 0, sizeof(verString));
-    sprintf(verString, "version %i", HERETIC_VERSION);
+    DEH_snprintf(verString, VERSIONSIZE, "version %i", HERETIC_VERSION);
     SV_Write(verString, VERSIONSIZE);
     SV_WriteByte(gameskill);
     SV_WriteByte(gameepisode);
@@ -1714,7 +1721,7 @@
 
     gameaction = ga_nothing;
     savedescription[0] = 0;
-    P_SetMessage(&players[consoleplayer], TXT_GAMESAVED, true);
+    P_SetMessage(&players[consoleplayer], DEH_String(TXT_GAMESAVED), true);
 }
 
 //==========================================================================
--- a/src/heretic/in_lude.c
+++ b/src/heretic/in_lude.c
@@ -30,6 +30,7 @@
 */
 
 #include "doomdef.h"
+#include "deh_str.h"
 #include "s_sound.h"
 #include "i_system.h"
 #include "i_video.h"
@@ -161,7 +162,7 @@
 
 void IN_Start(void)
 {
-    I_SetPalette(W_CacheLumpName("PLAYPAL", PU_CACHE));
+    I_SetPalette(W_CacheLumpName(DEH_String("PLAYPAL"), PU_CACHE));
     IN_LoadPics();
     IN_InitStats();
     intermission = true;
@@ -308,26 +309,26 @@
     switch (gameepisode)
     {
         case 1:
-            callback("MAPE1", 0, &patchINTERPIC);
+            callback(DEH_String("MAPE1"), 0, &patchINTERPIC);
             break;
         case 2:
-            callback("MAPE2", 0, &patchINTERPIC);
+            callback(DEH_String("MAPE2"), 0, &patchINTERPIC);
             break;
         case 3:
-            callback("MAPE3", 0, &patchINTERPIC);
+            callback(DEH_String("MAPE3"), 0, &patchINTERPIC);
             break;
         default:
             break;
     }
 
-    callback("IN_X", 0, &patchBEENTHERE);
-    callback("IN_YAH", 0, &patchGOINGTHERE);
-    callback("FONTB13", 0, &FontBNegative);
+    callback(DEH_String("IN_X"), 0, &patchBEENTHERE);
+    callback(DEH_String("IN_YAH"), 0, &patchGOINGTHERE);
+    callback(DEH_String("FONTB13"), 0, &FontBNegative);
 
-    callback("FONTB15", 0, &FontBSlash);
-    callback("FONTB05", 0, &FontBPercent);
+    callback(DEH_String("FONTB15"), 0, &FontBSlash);
+    callback(DEH_String("FONTB05"), 0, &FontBPercent);
 
-    FontBLumpBase = W_GetNumForName("FONTB16");
+    FontBLumpBase = W_GetNumForName(DEH_String("FONTB16"));
 
     for (i = 0; i < 10; i++)
     {
@@ -355,9 +356,9 @@
 
 void IN_LoadPics(void)
 {
-    FontBLump = W_GetNumForName("FONTB_S") + 1;
-    patchFaceOkayBase = W_GetNumForName("FACEA0");
-    patchFaceDeadBase = W_GetNumForName("FACEB0");
+    FontBLump = W_GetNumForName(DEH_String("FONTB_S")) + 1;
+    patchFaceOkayBase = W_GetNumForName(DEH_String("FACEA0"));
+    patchFaceDeadBase = W_GetNumForName(DEH_String("FACEB0"));
 
     IN_LoadUnloadPics(LoadLumpCallback);
 }
@@ -580,7 +581,7 @@
     byte *src;
     byte *dest;
 
-    src = W_CacheLumpName("FLOOR16", PU_CACHE);
+    src = W_CacheLumpName(DEH_String("FLOOR16"), PU_CACHE);
     dest = I_VideoBuffer;
 
     for (y = 0; y < SCREENHEIGHT; y++)
@@ -612,8 +613,8 @@
     x = 160 - MN_TextBWidth(LevelNames[(gameepisode - 1) * 9 + prevmap - 1] +
                             7) / 2;
     IN_DrTextB(LevelNames[(gameepisode - 1) * 9 + prevmap - 1] + 7, x, 3);
-    x = 160 - MN_TextAWidth("FINISHED") / 2;
-    MN_DrTextA("FINISHED", x, 25);
+    x = 160 - MN_TextAWidth(DEH_String("FINISHED")) / 2;
+    MN_DrTextA(DEH_String("FINISHED"), x, 25);
 
     if (prevmap == 9)
     {
@@ -660,8 +661,8 @@
     int i;
     int x;
 
-    x = 160 - MN_TextAWidth("NOW ENTERING:") / 2;
-    MN_DrTextA("NOW ENTERING:", x, 10);
+    x = 160 - MN_TextAWidth(DEH_String("NOW ENTERING:")) / 2;
+    MN_DrTextA(DEH_String("NOW ENTERING:"), x, 10);
     x = 160 - MN_TextBWidth(LevelNames[(gameepisode - 1) * 9 + gamemap - 1] +
                             7) / 2;
     IN_DrTextB(LevelNames[(gameepisode - 1) * 9 + gamemap - 1] + 7, x, 20);
@@ -698,15 +699,15 @@
     int x;
     static int sounds;
 
-    IN_DrTextB("KILLS", 50, 65);
-    IN_DrTextB("ITEMS", 50, 90);
-    IN_DrTextB("SECRETS", 50, 115);
+    IN_DrTextB(DEH_String("KILLS"), 50, 65);
+    IN_DrTextB(DEH_String("ITEMS"), 50, 90);
+    IN_DrTextB(DEH_String("SECRETS"), 50, 115);
 
     x = 160 - MN_TextBWidth(LevelNames[(gameepisode - 1) * 9 + prevmap - 1] +
                             7) / 2;
     IN_DrTextB(LevelNames[(gameepisode - 1) * 9 + prevmap - 1] + 7, x, 3);
-    x = 160 - MN_TextAWidth("FINISHED") / 2;
-    MN_DrTextA("FINISHED", x, 25);
+    x = 160 - MN_TextAWidth(DEH_String("FINISHED")) / 2;
+    MN_DrTextA(DEH_String("FINISHED"), x, 25);
 
     if (intertime < 30)
     {
@@ -757,13 +758,13 @@
 
     if (gamemode != retail || gameepisode <= 3)
     {
-        IN_DrTextB("TIME", 85, 160);
+        IN_DrTextB(DEH_String("TIME"), 85, 160);
         IN_DrawTime(155, 160, hours, minutes, seconds);
     }
     else
     {
-        x = 160 - MN_TextAWidth("NOW ENTERING:") / 2;
-        MN_DrTextA("NOW ENTERING:", x, 160);
+        x = 160 - MN_TextAWidth(DEH_String("NOW ENTERING:")) / 2;
+        MN_DrTextA(DEH_String("NOW ENTERING:"), x, 160);
         x = 160 -
             MN_TextBWidth(LevelNames[(gameepisode - 1) * 9 + gamemap - 1] +
                           7) / 2;
@@ -787,14 +788,14 @@
 
     static int sounds;
 
-    IN_DrTextB("KILLS", 95, 35);
-    IN_DrTextB("BONUS", 155, 35);
-    IN_DrTextB("SECRET", 232, 35);
+    IN_DrTextB(DEH_String("KILLS"), 95, 35);
+    IN_DrTextB(DEH_String("BONUS"), 155, 35);
+    IN_DrTextB(DEH_String("SECRET"), 232, 35);
     x = 160 - MN_TextBWidth(LevelNames[(gameepisode - 1) * 9 + prevmap - 1] +
                             7) / 2;
     IN_DrTextB(LevelNames[(gameepisode - 1) * 9 + prevmap - 1] + 7, x, 3);
-    x = 160 - MN_TextAWidth("FINISHED") / 2;
-    MN_DrTextA("FINISHED", x, 25);
+    x = 160 - MN_TextAWidth(DEH_String("FINISHED")) / 2;
+    MN_DrTextA(DEH_String("FINISHED"), x, 25);
 
     ypos = 50;
     for (i = 0; i < MAXPLAYERS; i++)
@@ -845,11 +846,11 @@
     xpos = 90;
     ypos = 55;
 
-    IN_DrTextB("TOTAL", 265, 30);
-    MN_DrTextA("VICTIMS", 140, 8);
+    IN_DrTextB(DEH_String("TOTAL"), 265, 30);
+    MN_DrTextA(DEH_String("VICTIMS"), 140, 8);
     for (i = 0; i < 7; i++)
     {
-        MN_DrTextA(KillersText[i], 10, 80 + 9 * i);
+        MN_DrTextA(DEH_String(KillersText[i]), 10, 80 + 9 * i);
     }
     if (intertime < 20)
     {
@@ -940,7 +941,7 @@
     if (h)
     {
         IN_DrawNumber(h, x, y, 2);
-        IN_DrTextB(":", x + 26, y);
+        IN_DrTextB(DEH_String(":"), x + 26, y);
     }
     x += 34;
     if (m || h)
@@ -950,7 +951,7 @@
     x += 34;
     if (s)
     {
-        IN_DrTextB(":", x - 8, y);
+        IN_DrTextB(DEH_String(":"), x - 8, y);
         IN_DrawNumber(s, x, y, 2);
     }
 }
--- a/src/heretic/info.c
+++ b/src/heretic/info.c
@@ -22,7 +22,7 @@
 //
 //-----------------------------------------------------------------------------
 #include "doomdef.h"
-// generated by multigen
+#include "p_action.h"
 
 char *sprnames[] = {
     "IMPX","ACLO","PTN1","SHLD","SHD2","BAGH","SPMP","INVS","PTN2","SOAR",
@@ -41,132 +41,6 @@
     NULL
 };
 
-void A_FreeTargMobj();
-void A_RestoreSpecialThing1();
-void A_RestoreSpecialThing2();
-void A_HideThing();
-void A_UnHideThing();
-void A_RestoreArtifact();
-void A_Scream();
-void A_Explode();
-void A_PodPain();
-void A_RemovePod();
-void A_MakePod();
-void A_InitKeyGizmo();
-void A_VolcanoSet();
-void A_VolcanoBlast();
-void A_BeastPuff();
-void A_VolcBallImpact();
-void A_SpawnTeleGlitter();
-void A_SpawnTeleGlitter2();
-void A_AccTeleGlitter();
-void A_Light0();
-void A_WeaponReady();
-void A_Lower();
-void A_Raise();
-void A_StaffAttackPL1();
-void A_ReFire();
-void A_StaffAttackPL2();
-void A_BeakReady();
-void A_BeakRaise();
-void A_BeakAttackPL1();
-void A_BeakAttackPL2();
-void A_GauntletAttack();
-void A_FireBlasterPL1();
-void A_FireBlasterPL2();
-void A_SpawnRippers();
-void A_FireMacePL1();
-void A_FireMacePL2();
-void A_MacePL1Check();
-void A_MaceBallImpact();
-void A_MaceBallImpact2();
-void A_DeathBallImpact();
-void A_FireSkullRodPL1();
-void A_FireSkullRodPL2();
-void A_SkullRodPL2Seek();
-void A_AddPlayerRain();
-void A_HideInCeiling();
-void A_SkullRodStorm();
-void A_RainImpact();
-void A_FireGoldWandPL1();
-void A_FireGoldWandPL2();
-void A_FirePhoenixPL1();
-void A_InitPhoenixPL2();
-void A_FirePhoenixPL2();
-void A_ShutdownPhoenixPL2();
-void A_PhoenixPuff();
-void A_FlameEnd();
-void A_FloatPuff();
-void A_FireCrossbowPL1();
-void A_FireCrossbowPL2();
-void A_BoltSpark();
-void A_Pain();
-void A_NoBlocking();
-void A_AddPlayerCorpse();
-void A_SkullPop();
-void A_FlameSnd();
-void A_CheckBurnGone();
-void A_CheckSkullFloor();
-void A_CheckSkullDone();
-void A_Feathers();
-void A_ChicLook();
-void A_ChicChase();
-void A_ChicPain();
-void A_FaceTarget();
-void A_ChicAttack();
-void A_Look();
-void A_Chase();
-void A_MummyAttack();
-void A_MummyAttack2();
-void A_MummySoul();
-void A_ContMobjSound();
-void A_MummyFX1Seek();
-void A_BeastAttack();
-void A_SnakeAttack();
-void A_SnakeAttack2();
-void A_HeadAttack();
-void A_BossDeath();
-void A_HeadIceImpact();
-void A_HeadFireGrow();
-void A_WhirlwindSeek();
-void A_ClinkAttack();
-void A_WizAtk1();
-void A_WizAtk2();
-void A_WizAtk3();
-void A_GhostOff();
-void A_ImpMeAttack();
-void A_ImpMsAttack();
-void A_ImpMsAttack2();
-void A_ImpDeath();
-void A_ImpXDeath1();
-void A_ImpXDeath2();
-void A_ImpExplode();
-void A_KnightAttack();
-void A_DripBlood();
-void A_Sor1Chase();
-void A_Sor1Pain();
-void A_Srcr1Attack();
-void A_SorZap();
-void A_SorcererRise();
-void A_SorRise();
-void A_SorSightSnd();
-void A_Srcr2Decide();
-void A_Srcr2Attack();
-void A_Sor2DthInit();
-void A_SorDSph();
-void A_Sor2DthLoop();
-void A_SorDExp();
-void A_SorDBon();
-void A_BlueSpark();
-void A_GenWizard();
-void A_MinotaurAtk1();
-void A_MinotaurDecide();
-void A_MinotaurAtk2();
-void A_MinotaurAtk3();
-void A_MinotaurCharge();
-void A_MntrFloorFire();
-void A_ESound();
-
 state_t states[NUMSTATES] = {
     {SPR_IMPX, 0, -1, NULL, S_NULL, 0, 0},      // S_NULL
     {SPR_ACLO, 4, 1050, A_FreeTargMobj, S_NULL, 0, 0},  // S_FREETARGMOBJ
@@ -663,6 +537,9 @@
     {SPR_FX08, 32773, 4, NULL, S_PHOENIXFXI1_7, 0, 0},  // S_PHOENIXFXI1_6
     {SPR_FX08, 32774, 4, NULL, S_PHOENIXFXI1_8, 0, 0},  // S_PHOENIXFXI1_7
     {SPR_FX08, 32775, 4, NULL, S_NULL, 0, 0},   // S_PHOENIXFXI1_8
+    {SPR_FX08, 32776, 8, NULL, S_PHOENIXFXIX_1, 0, 0 }, // S_PHOENIXFXIX_1
+    {SPR_FX08, 32777, 8, A_RemovedPhoenixFunc, S_PHOENIXFXIX_2, 0, 0 },  // S_PHOENIXFXIX_2
+    {SPR_FX08, 32778, 8, NULL, S_NULL, 0, 0 },          // S_PHOENIXFXIX_3
     {SPR_FX04, 1, 4, NULL, S_PHOENIXPUFF2, 0, 0},       // S_PHOENIXPUFF1
     {SPR_FX04, 2, 4, NULL, S_PHOENIXPUFF3, 0, 0},       // S_PHOENIXPUFF2
     {SPR_FX04, 3, 4, NULL, S_PHOENIXPUFF4, 0, 0},       // S_PHOENIXPUFF3
@@ -3725,6 +3602,37 @@
      sfx_None,                  // activesound
      MF_NOBLOCKMAP | MF_MISSILE | MF_DROPOFF | MF_NOGRAVITY,    // flags
      MF2_THRUGHOST | MF2_NOTELEPORT     // flags2
+     },
+
+    // The following thing is present in the mobjinfo table from Heretic 1.0,
+    // but not in Heretic 1.3 (ie. it was removed).  It has been re-inserted
+    // here to support HHE patches.
+
+    {                           // MT_PHOENIXFX_REMOVED
+     -1,                        // doomednum
+     S_PHOENIXFXIX_1,           // spawnstate
+     1000,                      // spawnhealth
+     S_NULL,                    // seestate
+     sfx_None,                // seesound
+     8,                         // reactiontime
+     sfx_None,                  // attacksound
+     S_NULL,                    // painstate
+     0,                         // painchance
+     sfx_None,                  // painsound
+     S_NULL,                    // meleestate
+     S_NULL,                    // missilestate
+     S_NULL,                    // crashstate
+     S_PHOENIXFXIX_3,           // deathstate
+     S_NULL,                    // xdeathstate
+     sfx_None,                  // deathsound
+     0,                         // speed
+     2 * FRACUNIT,              // radius
+     4 * FRACUNIT,              // height
+     100,                       // mass
+     0,                         // damage
+     sfx_None,                  // activesound
+     MF_NOBLOCKMAP | MF_MISSILE | MF_DROPOFF | MF_NOGRAVITY,    // flags
+     MF2_NOTELEPORT     // flags2
      },
 
     {                           // MT_PHOENIXPUFF
--- a/src/heretic/info.h
+++ b/src/heretic/info.h
@@ -21,8 +21,10 @@
 // 02111-1307, USA.
 //
 //-----------------------------------------------------------------------------
-// generated by multigen
 
+#ifndef HERETIC_INFO_H
+#define HERETIC_INFO_H
+
 typedef enum
 {
     SPR_IMPX,
@@ -653,6 +655,9 @@
     S_PHOENIXFXI1_6,
     S_PHOENIXFXI1_7,
     S_PHOENIXFXI1_8,
+    S_PHOENIXFXIX_1,      // [ States in Heretic 1.0 that were removed
+    S_PHOENIXFXIX_2,
+    S_PHOENIXFXIX_3,      // ]
     S_PHOENIXPUFF1,
     S_PHOENIXPUFF2,
     S_PHOENIXPUFF3,
@@ -773,8 +778,8 @@
     S_PLAY_FDTH16,
     S_PLAY_FDTH17,
     S_PLAY_FDTH18,
-    S_PLAY_FDTH19,
-    S_PLAY_FDTH20,
+    S_PLAY_FDTH19,    // < These two frames were not present in the Heretic
+    S_PLAY_FDTH20,    // < 1.0 executable (fire death animation was extended)
     S_BLOODYSKULL1,
     S_BLOODYSKULL2,
     S_BLOODYSKULL3,
@@ -1470,6 +1475,7 @@
     MT_GOLDWANDPUFF2,
     MT_WPHOENIXROD,
     MT_PHOENIXFX1,
+    MT_PHOENIXFX_REMOVED,  // In Heretic 1.0, but removed.
     MT_PHOENIXPUFF,
     MT_PHOENIXFX2,
     MT_MISC15,
@@ -1575,3 +1581,6 @@
 } mobjinfo_t;
 
 extern mobjinfo_t mobjinfo[NUMMOBJTYPES];
+
+#endif /* #ifndef HERETIC_INFO_H */
+
--- a/src/heretic/mn_menu.c
+++ b/src/heretic/mn_menu.c
@@ -25,6 +25,8 @@
 // MN_menu.c
 
 #include <ctype.h>
+
+#include "deh_str.h"
 #include "doomdef.h"
 #include "doomkeys.h"
 #include "i_system.h"
@@ -73,7 +75,7 @@
 {
     ItemType_t type;
     char *text;
-      boolean(*func) (int option);
+    boolean(*func) (int option);
     int option;
     MenuType_t menu;
 } MenuItem_t;
@@ -305,7 +307,7 @@
     InitFonts();
     MenuActive = false;
     messageson = true;
-    SkullBaseLump = W_GetNumForName("M_SKL00");
+    SkullBaseLump = W_GetNumForName(DEH_String("M_SKL00"));
 
     if (gamemode == retail)
     {                           // Add episodes 4 and 5 to the menu
@@ -322,8 +324,8 @@
 
 static void InitFonts(void)
 {
-    FontABaseLump = W_GetNumForName("FONTA_S") + 1;
-    FontBBaseLump = W_GetNumForName("FONTB_S") + 1;
+    FontABaseLump = W_GetNumForName(DEH_String("FONTA_S")) + 1;
+    FontBBaseLump = W_GetNumForName(DEH_String("FONTB_S")) + 1;
 }
 
 //---------------------------------------------------------------------------
@@ -476,6 +478,7 @@
     int x;
     int y;
     MenuItem_t *item;
+    char *message;
     char *selName;
 
     if (MenuActive == false)
@@ -482,13 +485,14 @@
     {
         if (askforquit)
         {
-            MN_DrTextA(QuitEndMsg[typeofask - 1], 160 -
-                       MN_TextAWidth(QuitEndMsg[typeofask - 1]) / 2, 80);
+            message = DEH_String(QuitEndMsg[typeofask - 1]);
+
+            MN_DrTextA(message, 160 - MN_TextAWidth(message) / 2, 80);
             if (typeofask == 3)
             {
                 MN_DrTextA(SlotText[quicksave - 1], 160 -
                            MN_TextAWidth(SlotText[quicksave - 1]) / 2, 90);
-                MN_DrTextA("?", 160 +
+                MN_DrTextA(DEH_String("?"), 160 +
                            MN_TextAWidth(SlotText[quicksave - 1]) / 2, 90);
             }
             if (typeofask == 4)
@@ -495,7 +499,7 @@
             {
                 MN_DrTextA(SlotText[quickload - 1], 160 -
                            MN_TextAWidth(SlotText[quickload - 1]) / 2, 90);
-                MN_DrTextA("?", 160 +
+                MN_DrTextA(DEH_String("?"), 160 +
                            MN_TextAWidth(SlotText[quickload - 1]) / 2, 90);
             }
             UpdateState |= I_FULLSCRN;
@@ -525,13 +529,13 @@
         {
             if (item->type != ITT_EMPTY && item->text)
             {
-                MN_DrTextB(item->text, x, y);
+                MN_DrTextB(DEH_String(item->text), x, y);
             }
             y += ITEM_HEIGHT;
             item++;
         }
         y = CurrentMenu->y + (CurrentItPos * ITEM_HEIGHT) + SELECTOR_YOFFSET;
-        selName = MenuTime & 16 ? "M_SLCTR1" : "M_SLCTR2";
+        selName = DEH_String(MenuTime & 16 ? "M_SLCTR1" : "M_SLCTR2");
         V_DrawPatch(x + SELECTOR_XOFFSET, y,
                     W_CacheLumpName(selName, PU_CACHE));
     }
@@ -548,7 +552,7 @@
     int frame;
 
     frame = (MenuTime / 3) % 18;
-    V_DrawPatch(88, 0, W_CacheLumpName("M_HTIC", PU_CACHE));
+    V_DrawPatch(88, 0, W_CacheLumpName(DEH_String("M_HTIC"), PU_CACHE));
     V_DrawPatch(40, 10, W_CacheLumpNum(SkullBaseLump + (17 - frame),
                                        PU_CACHE));
     V_DrawPatch(232, 10, W_CacheLumpNum(SkullBaseLump + frame, PU_CACHE));
@@ -597,7 +601,11 @@
 
 static void DrawLoadMenu(void)
 {
-    MN_DrTextB("LOAD GAME", 160 - MN_TextBWidth("LOAD GAME") / 2, 10);
+    char *title;
+
+    title = DEH_String("LOAD GAME");
+
+    MN_DrTextB(title, 160 - MN_TextBWidth(title) / 2, 10);
     if (!slottextloaded)
     {
         MN_LoadSlotText();
@@ -613,7 +621,11 @@
 
 static void DrawSaveMenu(void)
 {
-    MN_DrTextB("SAVE GAME", 160 - MN_TextBWidth("SAVE GAME") / 2, 10);
+    char *title;
+
+    title = DEH_String("SAVE GAME");
+
+    MN_DrTextB(title, 160 - MN_TextBWidth(title) / 2, 10);
     if (!slottextloaded)
     {
         MN_LoadSlotText();
@@ -675,7 +687,7 @@
     y = menu->y;
     for (i = 0; i < 6; i++)
     {
-        V_DrawPatch(x, y, W_CacheLumpName("M_FSLOT", PU_CACHE));
+        V_DrawPatch(x, y, W_CacheLumpName(DEH_String("M_FSLOT"), PU_CACHE));
         if (SlotStatus[i])
         {
             MN_DrTextA(SlotText[i], x + 5, y + 5);
@@ -694,11 +706,11 @@
 {
     if (messageson)
     {
-        MN_DrTextB("ON", 196, 50);
+        MN_DrTextB(DEH_String("ON"), 196, 50);
     }
     else
     {
-        MN_DrTextB("OFF", 196, 50);
+        MN_DrTextB(DEH_String("OFF"), 196, 50);
     }
     DrawSlider(&OptionsMenu, 3, 10, mouseSensitivity);
 }
@@ -796,11 +808,11 @@
     messageson ^= 1;
     if (messageson)
     {
-        P_SetMessage(&players[consoleplayer], "MESSAGES ON", true);
+        P_SetMessage(&players[consoleplayer], DEH_String("MESSAGES ON"), true);
     }
     else
     {
-        P_SetMessage(&players[consoleplayer], "MESSAGES OFF", true);
+        P_SetMessage(&players[consoleplayer], DEH_String("MESSAGES OFF"), true);
     }
     S_StartSound(NULL, sfx_chat);
     return true;
@@ -1460,7 +1472,7 @@
                 if (CurrentMenu->items[i].text)
                 {
                     if (toupper(charTyped)
-                        == toupper(CurrentMenu->items[i].text[0]))
+                        == toupper(DEH_String(CurrentMenu->items[i].text)[0]))
                     {
                         CurrentItPos = i;
                         return (true);
@@ -1628,13 +1640,13 @@
 
     x = menu->x + 24;
     y = menu->y + 2 + (item * ITEM_HEIGHT);
-    V_DrawPatch(x - 32, y, W_CacheLumpName("M_SLDLT", PU_CACHE));
+    V_DrawPatch(x - 32, y, W_CacheLumpName(DEH_String("M_SLDLT"), PU_CACHE));
     for (x2 = x, count = width; count--; x2 += 8)
     {
-        V_DrawPatch(x2, y, W_CacheLumpName(count & 1 ? "M_SLDMD1"
-                                           : "M_SLDMD2", PU_CACHE));
+        V_DrawPatch(x2, y, W_CacheLumpName(DEH_String(count & 1 ? "M_SLDMD1"
+                                           : "M_SLDMD2"), PU_CACHE));
     }
-    V_DrawPatch(x2, y, W_CacheLumpName("M_SLDRT", PU_CACHE));
+    V_DrawPatch(x2, y, W_CacheLumpName(DEH_String("M_SLDRT"), PU_CACHE));
     V_DrawPatch(x + 4 + slot * 8, y + 7,
-                W_CacheLumpName("M_SLDKB", PU_CACHE));
+                W_CacheLumpName(DEH_String("M_SLDKB"), PU_CACHE));
 }
--- /dev/null
+++ b/src/heretic/p_action.h
@@ -1,0 +1,160 @@
+// Emacs style mode select   -*- C++ -*- 
+//-----------------------------------------------------------------------------
+//
+// Copyright(C) 1993-1996 Id Software, Inc.
+// Copyright(C) 1993-2008 Raven Software
+// Copyright(C) 2008 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.
+//
+//-----------------------------------------------------------------------------
+//
+// External definitions for action pointer functions.
+//
+//-----------------------------------------------------------------------------
+
+#ifndef HERETIC_P_ACTION_H
+#define HERETIC_P_ACTION_H
+
+void A_FreeTargMobj();
+void A_RestoreSpecialThing1();
+void A_RestoreSpecialThing2();
+void A_HideThing();
+void A_UnHideThing();
+void A_RestoreArtifact();
+void A_Scream();
+void A_Explode();
+void A_PodPain();
+void A_RemovePod();
+void A_MakePod();
+void A_InitKeyGizmo();
+void A_VolcanoSet();
+void A_VolcanoBlast();
+void A_BeastPuff();
+void A_VolcBallImpact();
+void A_SpawnTeleGlitter();
+void A_SpawnTeleGlitter2();
+void A_AccTeleGlitter();
+void A_Light0();
+void A_WeaponReady();
+void A_Lower();
+void A_Raise();
+void A_StaffAttackPL1();
+void A_ReFire();
+void A_StaffAttackPL2();
+void A_BeakReady();
+void A_BeakRaise();
+void A_BeakAttackPL1();
+void A_BeakAttackPL2();
+void A_GauntletAttack();
+void A_FireBlasterPL1();
+void A_FireBlasterPL2();
+void A_SpawnRippers();
+void A_FireMacePL1();
+void A_FireMacePL2();
+void A_MacePL1Check();
+void A_MaceBallImpact();
+void A_MaceBallImpact2();
+void A_DeathBallImpact();
+void A_FireSkullRodPL1();
+void A_FireSkullRodPL2();
+void A_SkullRodPL2Seek();
+void A_AddPlayerRain();
+void A_HideInCeiling();
+void A_SkullRodStorm();
+void A_RainImpact();
+void A_FireGoldWandPL1();
+void A_FireGoldWandPL2();
+void A_FirePhoenixPL1();
+void A_InitPhoenixPL2();
+void A_FirePhoenixPL2();
+void A_ShutdownPhoenixPL2();
+void A_PhoenixPuff();
+void A_RemovedPhoenixFunc();
+void A_FlameEnd();
+void A_FloatPuff();
+void A_FireCrossbowPL1();
+void A_FireCrossbowPL2();
+void A_BoltSpark();
+void A_Pain();
+void A_NoBlocking();
+void A_AddPlayerCorpse();
+void A_SkullPop();
+void A_FlameSnd();
+void A_CheckBurnGone();
+void A_CheckSkullFloor();
+void A_CheckSkullDone();
+void A_Feathers();
+void A_ChicLook();
+void A_ChicChase();
+void A_ChicPain();
+void A_FaceTarget();
+void A_ChicAttack();
+void A_Look();
+void A_Chase();
+void A_MummyAttack();
+void A_MummyAttack2();
+void A_MummySoul();
+void A_ContMobjSound();
+void A_MummyFX1Seek();
+void A_BeastAttack();
+void A_SnakeAttack();
+void A_SnakeAttack2();
+void A_HeadAttack();
+void A_BossDeath();
+void A_HeadIceImpact();
+void A_HeadFireGrow();
+void A_WhirlwindSeek();
+void A_ClinkAttack();
+void A_WizAtk1();
+void A_WizAtk2();
+void A_WizAtk3();
+void A_GhostOff();
+void A_ImpMeAttack();
+void A_ImpMsAttack();
+void A_ImpMsAttack2();
+void A_ImpDeath();
+void A_ImpXDeath1();
+void A_ImpXDeath2();
+void A_ImpExplode();
+void A_KnightAttack();
+void A_DripBlood();
+void A_Sor1Chase();
+void A_Sor1Pain();
+void A_Srcr1Attack();
+void A_SorZap();
+void A_SorcererRise();
+void A_SorRise();
+void A_SorSightSnd();
+void A_Srcr2Decide();
+void A_Srcr2Attack();
+void A_Sor2DthInit();
+void A_SorDSph();
+void A_Sor2DthLoop();
+void A_SorDExp();
+void A_SorDBon();
+void A_BlueSpark();
+void A_GenWizard();
+void A_MinotaurAtk1();
+void A_MinotaurDecide();
+void A_MinotaurAtk2();
+void A_MinotaurAtk3();
+void A_MinotaurCharge();
+void A_MntrFloorFire();
+void A_ESound();
+
+#endif /* #ifndef HERETIC_P_ACTION_H */
+
--- a/src/heretic/p_doors.c
+++ b/src/heretic/p_doors.c
@@ -25,6 +25,7 @@
 // P_doors.c
 
 #include "doomdef.h"
+#include "deh_str.h"
 #include "p_local.h"
 #include "s_sound.h"
 #include "v_video.h"
@@ -232,7 +233,7 @@
             }
             if (!player->keys[key_blue])
             {
-                P_SetMessage(player, TXT_NEEDBLUEKEY, false);
+                P_SetMessage(player, DEH_String(TXT_NEEDBLUEKEY), false);
                 S_StartSound(NULL, sfx_plroof);
                 return;
             }
@@ -245,7 +246,7 @@
             }
             if (!player->keys[key_yellow])
             {
-                P_SetMessage(player, TXT_NEEDYELLOWKEY, false);
+                P_SetMessage(player, DEH_String(TXT_NEEDYELLOWKEY), false);
                 S_StartSound(NULL, sfx_plroof);
                 return;
             }
@@ -258,7 +259,7 @@
             }
             if (!player->keys[key_green])
             {
-                P_SetMessage(player, TXT_NEEDGREENKEY, false);
+                P_SetMessage(player, DEH_String(TXT_NEEDGREENKEY), false);
                 S_StartSound(NULL, sfx_plroof);
                 return;
             }
--- a/src/heretic/p_inter.c
+++ b/src/heretic/p_inter.c
@@ -25,6 +25,7 @@
 // P_inter.c
 
 #include "doomdef.h"
+#include "deh_str.h"
 #include "i_system.h"
 #include "i_timer.h"
 #include "m_random.h"
@@ -54,7 +55,7 @@
     150                         // mace
 };
 
-static int GetWeaponAmmo[NUMWEAPONS] = {
+int GetWeaponAmmo[NUMWEAPONS] = {
     0,                          // staff
     25,                         // gold wand
     10,                         // crossbow
@@ -580,7 +581,7 @@
             {
                 return;
             }
-            P_SetMessage(player, TXT_ITEMHEALTH, false);
+            P_SetMessage(player, DEH_String(TXT_ITEMHEALTH), false);
             break;
         case SPR_SHLD:         // Item_Shield1
             if (!P_GiveArmor(player, 1))
@@ -587,7 +588,7 @@
             {
                 return;
             }
-            P_SetMessage(player, TXT_ITEMSHIELD1, false);
+            P_SetMessage(player, DEH_String(TXT_ITEMSHIELD1), false);
             break;
         case SPR_SHD2:         // Item_Shield2
             if (!P_GiveArmor(player, 2))
@@ -594,7 +595,7 @@
             {
                 return;
             }
-            P_SetMessage(player, TXT_ITEMSHIELD2, false);
+            P_SetMessage(player, DEH_String(TXT_ITEMSHIELD2), false);
             break;
         case SPR_BAGH:         // Item_BagOfHolding
             if (!player->backpack)
@@ -610,7 +611,7 @@
             P_GiveAmmo(player, am_crossbow, AMMO_CBOW_WIMPY);
             P_GiveAmmo(player, am_skullrod, AMMO_SKRD_WIMPY);
             P_GiveAmmo(player, am_phoenixrod, AMMO_PHRD_WIMPY);
-            P_SetMessage(player, TXT_ITEMBAGOFHOLDING, false);
+            P_SetMessage(player, DEH_String(TXT_ITEMBAGOFHOLDING), false);
             break;
         case SPR_SPMP:         // Item_SuperMap
             if (!P_GivePower(player, pw_allmap))
@@ -617,7 +618,7 @@
             {
                 return;
             }
-            P_SetMessage(player, TXT_ITEMSUPERMAP, false);
+            P_SetMessage(player, DEH_String(TXT_ITEMSUPERMAP), false);
             break;
 
             // Keys
@@ -624,7 +625,7 @@
         case SPR_BKYY:         // Key_Blue
             if (!player->keys[key_blue])
             {
-                P_SetMessage(player, TXT_GOTBLUEKEY, false);
+                P_SetMessage(player, DEH_String(TXT_GOTBLUEKEY), false);
             }
             P_GiveKey(player, key_blue);
             sound = sfx_keyup;
@@ -636,7 +637,7 @@
         case SPR_CKYY:         // Key_Yellow
             if (!player->keys[key_yellow])
             {
-                P_SetMessage(player, TXT_GOTYELLOWKEY, false);
+                P_SetMessage(player, DEH_String(TXT_GOTYELLOWKEY), false);
             }
             sound = sfx_keyup;
             P_GiveKey(player, key_yellow);
@@ -648,7 +649,7 @@
         case SPR_AKYY:         // Key_Green
             if (!player->keys[key_green])
             {
-                P_SetMessage(player, TXT_GOTGREENKEY, false);
+                P_SetMessage(player, DEH_String(TXT_GOTGREENKEY), false);
             }
             sound = sfx_keyup;
             P_GiveKey(player, key_green);
@@ -662,7 +663,7 @@
         case SPR_PTN2:         // Arti_HealingPotion
             if (P_GiveArtifact(player, arti_health, special))
             {
-                P_SetMessage(player, TXT_ARTIHEALTH, false);
+                P_SetMessage(player, DEH_String(TXT_ARTIHEALTH), false);
                 P_SetDormantArtifact(special);
             }
             return;
@@ -669,7 +670,7 @@
         case SPR_SOAR:         // Arti_Fly
             if (P_GiveArtifact(player, arti_fly, special))
             {
-                P_SetMessage(player, TXT_ARTIFLY, false);
+                P_SetMessage(player, DEH_String(TXT_ARTIFLY), false);
                 P_SetDormantArtifact(special);
             }
             return;
@@ -676,7 +677,7 @@
         case SPR_INVU:         // Arti_Invulnerability
             if (P_GiveArtifact(player, arti_invulnerability, special))
             {
-                P_SetMessage(player, TXT_ARTIINVULNERABILITY, false);
+                P_SetMessage(player, DEH_String(TXT_ARTIINVULNERABILITY), false);
                 P_SetDormantArtifact(special);
             }
             return;
@@ -683,7 +684,7 @@
         case SPR_PWBK:         // Arti_TomeOfPower
             if (P_GiveArtifact(player, arti_tomeofpower, special))
             {
-                P_SetMessage(player, TXT_ARTITOMEOFPOWER, false);
+                P_SetMessage(player, DEH_String(TXT_ARTITOMEOFPOWER), false);
                 P_SetDormantArtifact(special);
             }
             return;
@@ -690,7 +691,7 @@
         case SPR_INVS:         // Arti_Invisibility
             if (P_GiveArtifact(player, arti_invisibility, special))
             {
-                P_SetMessage(player, TXT_ARTIINVISIBILITY, false);
+                P_SetMessage(player, DEH_String(TXT_ARTIINVISIBILITY), false);
                 P_SetDormantArtifact(special);
             }
             return;
@@ -697,7 +698,7 @@
         case SPR_EGGC:         // Arti_Egg
             if (P_GiveArtifact(player, arti_egg, special))
             {
-                P_SetMessage(player, TXT_ARTIEGG, false);
+                P_SetMessage(player, DEH_String(TXT_ARTIEGG), false);
                 P_SetDormantArtifact(special);
             }
             return;
@@ -704,7 +705,7 @@
         case SPR_SPHL:         // Arti_SuperHealth
             if (P_GiveArtifact(player, arti_superhealth, special))
             {
-                P_SetMessage(player, TXT_ARTISUPERHEALTH, false);
+                P_SetMessage(player, DEH_String(TXT_ARTISUPERHEALTH), false);
                 P_SetDormantArtifact(special);
             }
             return;
@@ -711,7 +712,7 @@
         case SPR_TRCH:         // Arti_Torch
             if (P_GiveArtifact(player, arti_torch, special))
             {
-                P_SetMessage(player, TXT_ARTITORCH, false);
+                P_SetMessage(player, DEH_String(TXT_ARTITORCH), false);
                 P_SetDormantArtifact(special);
             }
             return;
@@ -718,7 +719,7 @@
         case SPR_FBMB:         // Arti_FireBomb
             if (P_GiveArtifact(player, arti_firebomb, special))
             {
-                P_SetMessage(player, TXT_ARTIFIREBOMB, false);
+                P_SetMessage(player, DEH_String(TXT_ARTIFIREBOMB), false);
                 P_SetDormantArtifact(special);
             }
             return;
@@ -725,7 +726,7 @@
         case SPR_ATLP:         // Arti_Teleport
             if (P_GiveArtifact(player, arti_teleport, special))
             {
-                P_SetMessage(player, TXT_ARTITELEPORT, false);
+                P_SetMessage(player, DEH_String(TXT_ARTITELEPORT), false);
                 P_SetDormantArtifact(special);
             }
             return;
@@ -736,7 +737,7 @@
             {
                 return;
             }
-            P_SetMessage(player, TXT_AMMOGOLDWAND1, false);
+            P_SetMessage(player, DEH_String(TXT_AMMOGOLDWAND1), false);
             break;
         case SPR_AMG2:         // Ammo_GoldWandHefty
             if (!P_GiveAmmo(player, am_goldwand, special->health))
@@ -743,7 +744,7 @@
             {
                 return;
             }
-            P_SetMessage(player, TXT_AMMOGOLDWAND2, false);
+            P_SetMessage(player, DEH_String(TXT_AMMOGOLDWAND2), false);
             break;
         case SPR_AMM1:         // Ammo_MaceWimpy
             if (!P_GiveAmmo(player, am_mace, special->health))
@@ -750,7 +751,7 @@
             {
                 return;
             }
-            P_SetMessage(player, TXT_AMMOMACE1, false);
+            P_SetMessage(player, DEH_String(TXT_AMMOMACE1), false);
             break;
         case SPR_AMM2:         // Ammo_MaceHefty
             if (!P_GiveAmmo(player, am_mace, special->health))
@@ -757,7 +758,7 @@
             {
                 return;
             }
-            P_SetMessage(player, TXT_AMMOMACE2, false);
+            P_SetMessage(player, DEH_String(TXT_AMMOMACE2), false);
             break;
         case SPR_AMC1:         // Ammo_CrossbowWimpy
             if (!P_GiveAmmo(player, am_crossbow, special->health))
@@ -764,7 +765,7 @@
             {
                 return;
             }
-            P_SetMessage(player, TXT_AMMOCROSSBOW1, false);
+            P_SetMessage(player, DEH_String(TXT_AMMOCROSSBOW1), false);
             break;
         case SPR_AMC2:         // Ammo_CrossbowHefty
             if (!P_GiveAmmo(player, am_crossbow, special->health))
@@ -771,7 +772,7 @@
             {
                 return;
             }
-            P_SetMessage(player, TXT_AMMOCROSSBOW2, false);
+            P_SetMessage(player, DEH_String(TXT_AMMOCROSSBOW2), false);
             break;
         case SPR_AMB1:         // Ammo_BlasterWimpy
             if (!P_GiveAmmo(player, am_blaster, special->health))
@@ -778,7 +779,7 @@
             {
                 return;
             }
-            P_SetMessage(player, TXT_AMMOBLASTER1, false);
+            P_SetMessage(player, DEH_String(TXT_AMMOBLASTER1), false);
             break;
         case SPR_AMB2:         // Ammo_BlasterHefty
             if (!P_GiveAmmo(player, am_blaster, special->health))
@@ -785,7 +786,7 @@
             {
                 return;
             }
-            P_SetMessage(player, TXT_AMMOBLASTER2, false);
+            P_SetMessage(player, DEH_String(TXT_AMMOBLASTER2), false);
             break;
         case SPR_AMS1:         // Ammo_SkullRodWimpy
             if (!P_GiveAmmo(player, am_skullrod, special->health))
@@ -792,7 +793,7 @@
             {
                 return;
             }
-            P_SetMessage(player, TXT_AMMOSKULLROD1, false);
+            P_SetMessage(player, DEH_String(TXT_AMMOSKULLROD1), false);
             break;
         case SPR_AMS2:         // Ammo_SkullRodHefty
             if (!P_GiveAmmo(player, am_skullrod, special->health))
@@ -799,7 +800,7 @@
             {
                 return;
             }
-            P_SetMessage(player, TXT_AMMOSKULLROD2, false);
+            P_SetMessage(player, DEH_String(TXT_AMMOSKULLROD2), false);
             break;
         case SPR_AMP1:         // Ammo_PhoenixRodWimpy
             if (!P_GiveAmmo(player, am_phoenixrod, special->health))
@@ -806,7 +807,7 @@
             {
                 return;
             }
-            P_SetMessage(player, TXT_AMMOPHOENIXROD1, false);
+            P_SetMessage(player, DEH_String(TXT_AMMOPHOENIXROD1), false);
             break;
         case SPR_AMP2:         // Ammo_PhoenixRodHefty
             if (!P_GiveAmmo(player, am_phoenixrod, special->health))
@@ -813,7 +814,7 @@
             {
                 return;
             }
-            P_SetMessage(player, TXT_AMMOPHOENIXROD2, false);
+            P_SetMessage(player, DEH_String(TXT_AMMOPHOENIXROD2), false);
             break;
 
             // Weapons
@@ -822,7 +823,7 @@
             {
                 return;
             }
-            P_SetMessage(player, TXT_WPNMACE, false);
+            P_SetMessage(player, DEH_String(TXT_WPNMACE), false);
             sound = sfx_wpnup;
             break;
         case SPR_WBOW:         // Weapon_Crossbow
@@ -830,7 +831,7 @@
             {
                 return;
             }
-            P_SetMessage(player, TXT_WPNCROSSBOW, false);
+            P_SetMessage(player, DEH_String(TXT_WPNCROSSBOW), false);
             sound = sfx_wpnup;
             break;
         case SPR_WBLS:         // Weapon_Blaster
@@ -838,7 +839,7 @@
             {
                 return;
             }
-            P_SetMessage(player, TXT_WPNBLASTER, false);
+            P_SetMessage(player, DEH_String(TXT_WPNBLASTER), false);
             sound = sfx_wpnup;
             break;
         case SPR_WSKL:         // Weapon_SkullRod
@@ -846,7 +847,7 @@
             {
                 return;
             }
-            P_SetMessage(player, TXT_WPNSKULLROD, false);
+            P_SetMessage(player, DEH_String(TXT_WPNSKULLROD), false);
             sound = sfx_wpnup;
             break;
         case SPR_WPHX:         // Weapon_PhoenixRod
@@ -854,7 +855,7 @@
             {
                 return;
             }
-            P_SetMessage(player, TXT_WPNPHOENIXROD, false);
+            P_SetMessage(player, DEH_String(TXT_WPNPHOENIXROD), false);
             sound = sfx_wpnup;
             break;
         case SPR_WGNT:         // Weapon_Gauntlets
@@ -862,7 +863,7 @@
             {
                 return;
             }
-            P_SetMessage(player, TXT_WPNGAUNTLETS, false);
+            P_SetMessage(player, DEH_String(TXT_WPNGAUNTLETS), false);
             sound = sfx_wpnup;
             break;
         default:
--- a/src/heretic/p_pspr.c
+++ b/src/heretic/p_pspr.c
@@ -1650,6 +1650,17 @@
     puff->momz = 0;
 }
 
+//
+// This function was present in the Heretic 1.0 executable for the
+// removed "secondary phoenix flash" object (MT_PHOENIXFX_REMOVED).
+// The purpose of this object is unknown, as is this function.
+//
+
+void A_RemovedPhoenixFunc(mobj_t *actor)
+{
+    I_Error("Action function invoked for removed Phoenix action!");
+}
+
 //----------------------------------------------------------------------------
 //
 // PROC A_InitPhoenixPL2
--- a/src/heretic/p_spec.c
+++ b/src/heretic/p_spec.c
@@ -25,6 +25,7 @@
 // P_Spec.c
 
 #include "doomdef.h"
+#include "deh_str.h"
 #include "i_system.h"
 #include "i_timer.h"
 #include "m_random.h"
@@ -204,18 +205,12 @@
     int type;
 } TerrainTypeDefs[] =
 {
-    {
-    "FLTWAWA1", FLOOR_WATER},
-    {
-    "FLTFLWW1", FLOOR_WATER},
-    {
-    "FLTLAVA1", FLOOR_LAVA},
-    {
-    "FLATHUH1", FLOOR_LAVA},
-    {
-    "FLTSLUD1", FLOOR_SLUDGE},
-    {
-    "END", -1}
+    { "FLTWAWA1", FLOOR_WATER },
+    { "FLTFLWW1", FLOOR_WATER },
+    { "FLTLAVA1", FLOOR_LAVA },
+    { "FLATHUH1", FLOOR_LAVA },
+    { "FLTSLUD1", FLOOR_SLUDGE },
+    { "END", -1 }
 };
 
 mobj_t LavaInflictor;
@@ -266,28 +261,33 @@
 
 void P_InitPicAnims(void)
 {
+    char *startname;
+    char *endname;
     int i;
 
     lastanim = anims;
     for (i = 0; animdefs[i].istexture != -1; i++)
     {
+        startname = DEH_String(animdefs[i].startname);
+        endname = DEH_String(animdefs[i].endname);
+
         if (animdefs[i].istexture)
         {                       // Texture animation
-            if (R_CheckTextureNumForName(animdefs[i].startname) == -1)
+            if (R_CheckTextureNumForName(startname) == -1)
             {                   // Texture doesn't exist
                 continue;
             }
-            lastanim->picnum = R_TextureNumForName(animdefs[i].endname);
-            lastanim->basepic = R_TextureNumForName(animdefs[i].startname);
+            lastanim->picnum = R_TextureNumForName(endname);
+            lastanim->basepic = R_TextureNumForName(startname);
         }
         else
         {                       // Flat animation
-            if (W_CheckNumForName(animdefs[i].startname) == -1)
+            if (W_CheckNumForName(startname) == -1)
             {                   // Flat doesn't exist
                 continue;
             }
-            lastanim->picnum = R_FlatNumForName(animdefs[i].endname);
-            lastanim->basepic = R_FlatNumForName(animdefs[i].startname);
+            lastanim->picnum = R_FlatNumForName(endname);
+            lastanim->basepic = R_FlatNumForName(startname);
         }
         lastanim->istexture = animdefs[i].istexture;
         lastanim->numpics = lastanim->picnum - lastanim->basepic + 1;
@@ -294,7 +294,7 @@
         if (lastanim->numpics < 2)
         {
             I_Error("P_InitPicAnims: bad cycle from %s to %s",
-                    animdefs[i].startname, animdefs[i].endname);
+                    startname, endname);
         }
         lastanim->speed = animdefs[i].speed;
         lastanim++;
@@ -1132,7 +1132,7 @@
     int episode;
 
     episode = 1;
-    if (W_CheckNumForName("texture2") >= 0)
+    if (W_CheckNumForName(DEH_String("texture2")) >= 0)
         episode = 2;
 
     //
--- a/src/heretic/p_switch.c
+++ b/src/heretic/p_switch.c
@@ -23,6 +23,7 @@
 //-----------------------------------------------------------------------------
 
 #include "doomdef.h"
+#include "deh_str.h"
 #include "i_system.h"
 #include "p_local.h"
 #include "s_sound.h"
@@ -129,9 +130,9 @@
         if (alphSwitchList[i].episode <= episode)
         {
             switchlist[index++] =
-                R_TextureNumForName(alphSwitchList[i].name1);
+                R_TextureNumForName(DEH_String(alphSwitchList[i].name1));
             switchlist[index++] =
-                R_TextureNumForName(alphSwitchList[i].name2);
+                R_TextureNumForName(DEH_String(alphSwitchList[i].name2));
         }
     }
 }
--- a/src/heretic/p_user.c
+++ b/src/heretic/p_user.c
@@ -27,6 +27,7 @@
 #include <stdlib.h>
 
 #include "doomdef.h"
+#include "deh_str.h"
 #include "m_random.h"
 #include "p_local.h"
 #include "s_sound.h"
@@ -394,7 +395,7 @@
     {
         if (player == &players[consoleplayer])
         {
-            I_SetPalette((byte *) W_CacheLumpName("PLAYPAL", PU_CACHE));
+            I_SetPalette(W_CacheLumpName(DEH_String("PLAYPAL"), PU_CACHE));
             inv_ptr = 0;
             curpos = 0;
             newtorch = 0;
--- a/src/heretic/r_data.c
+++ b/src/heretic/r_data.c
@@ -25,6 +25,8 @@
 // R_data.c
 
 #include "doomdef.h"
+#include "deh_str.h"
+
 #include "i_swap.h"
 #include "i_system.h"
 #include "r_local.h"
@@ -316,12 +318,17 @@
     int offset, maxoff, maxoff2;
     int numtextures1, numtextures2;
     int *directory;
+    char *texture1, *texture2, *pnames;
 
+    texture1 = DEH_String("TEXTURE1");
+    texture2 = DEH_String("TEXTURE2");
+    pnames = DEH_String("PNAMES");
+
 //
 // load the patch names from pnames.lmp
 //
     name[8] = 0;
-    names = W_CacheLumpName("PNAMES", PU_STATIC);
+    names = W_CacheLumpName(pnames, PU_STATIC);
     nummappatches = LONG(*((int *) names));
     name_p = names + 4;
     patchlookup = Z_Malloc(nummappatches * sizeof(*patchlookup), PU_STATIC, NULL);
@@ -330,21 +337,21 @@
         strncpy(name, name_p + i * 8, 8);
         patchlookup[i] = W_CheckNumForName(name);
     }
-    W_ReleaseLumpName("PNAMES");
+    W_ReleaseLumpName(pnames);
 
 //
 // load the map texture definitions from textures.lmp
 //
-    maptex = maptex1 = W_CacheLumpName("TEXTURE1", PU_STATIC);
+    maptex = maptex1 = W_CacheLumpName(texture1, PU_STATIC);
     numtextures1 = LONG(*maptex);
-    maxoff = W_LumpLength(W_GetNumForName("TEXTURE1"));
+    maxoff = W_LumpLength(W_GetNumForName(texture1));
     directory = maptex + 1;
 
-    if (W_CheckNumForName("TEXTURE2") != -1)
+    if (W_CheckNumForName(texture2) != -1)
     {
-        maptex2 = W_CacheLumpName("TEXTURE2", PU_STATIC);
+        maptex2 = W_CacheLumpName(texture2, PU_STATIC);
         numtextures2 = LONG(*maptex2);
-        maxoff2 = W_LumpLength(W_GetNumForName("TEXTURE2"));
+        maxoff2 = W_LumpLength(W_GetNumForName(texture2));
     }
     else
     {
@@ -358,8 +365,11 @@
     //      Init the startup thermometer at this point...
     //
     {
+        int start, end;
         int spramount;
-        spramount = W_GetNumForName("S_END") - W_GetNumForName("S_START") + 1;
+        start = W_GetNumForName(DEH_String("S_START"));
+        end = W_GetNumForName(DEH_String("S_END"));
+        spramount = end - start + 1;
         InitThermo(spramount + numtextures + 6);
     }
 
@@ -427,10 +437,10 @@
 
     Z_Free(patchlookup);
 
-    W_ReleaseLumpName("TEXTURE1");
+    W_ReleaseLumpName(texture1);
     if (maptex2)
     {
-        W_ReleaseLumpName("TEXTURE2");
+        W_ReleaseLumpName(texture2);
     }
 
 //
@@ -463,8 +473,8 @@
 {
     int i;
 
-    firstflat = W_GetNumForName("F_START") + 1;
-    lastflat = W_GetNumForName("F_END") - 1;
+    firstflat = W_GetNumForName(DEH_String("F_START")) + 1;
+    lastflat = W_GetNumForName(DEH_String("F_END")) - 1;
     numflats = lastflat - firstflat + 1;
 
 // translation table for global animation
@@ -489,8 +499,8 @@
     int i;
     patch_t *patch;
 
-    firstspritelump = W_GetNumForName("S_START") + 1;
-    lastspritelump = W_GetNumForName("S_END") - 1;
+    firstspritelump = W_GetNumForName(DEH_String("S_START")) + 1;
+    lastspritelump = W_GetNumForName(DEH_String("S_END")) - 1;
     numspritelumps = lastspritelump - firstspritelump + 1;
     spritewidth = Z_Malloc(numspritelumps * sizeof(fixed_t), PU_STATIC, 0);
     spriteoffset = Z_Malloc(numspritelumps * sizeof(fixed_t), PU_STATIC, 0);
@@ -527,7 +537,7 @@
 // load in the light tables
 // 256 byte align tables
 //
-    lump = W_GetNumForName("COLORMAP");
+    lump = W_GetNumForName(DEH_String("COLORMAP"));
     length = W_LumpLength(lump);
     colormaps = Z_Malloc(length, PU_STATIC, 0);
     W_ReadLump(lump, colormaps);
--- a/src/heretic/r_draw.c
+++ b/src/heretic/r_draw.c
@@ -24,6 +24,7 @@
 // R_draw.c
 
 #include "doomdef.h"
+#include "deh_str.h"
 #include "r_local.h"
 #include "i_video.h"
 #include "v_video.h"
@@ -386,11 +387,11 @@
 
     if (gamemode == shareware)
     {
-        src = W_CacheLumpName("FLOOR04", PU_CACHE);
+        src = W_CacheLumpName(DEH_String("FLOOR04"), PU_CACHE);
     }
     else
     {
-        src = W_CacheLumpName("FLAT513", PU_CACHE);
+        src = W_CacheLumpName(DEH_String("FLAT513"), PU_CACHE);
     }
     dest = I_VideoBuffer;
 
@@ -409,24 +410,26 @@
     }
     for (x = viewwindowx; x < viewwindowx + viewwidth; x += 16)
     {
-        V_DrawPatch(x, viewwindowy - 4, W_CacheLumpName("bordt", PU_CACHE));
-        V_DrawPatch(x, viewwindowy + viewheight, W_CacheLumpName("bordb",
-                                                                 PU_CACHE));
+        V_DrawPatch(x, viewwindowy - 4,
+                    W_CacheLumpName(DEH_String("bordt"), PU_CACHE));
+        V_DrawPatch(x, viewwindowy + viewheight,
+                    W_CacheLumpName(DEH_String("bordb"), PU_CACHE));
     }
     for (y = viewwindowy; y < viewwindowy + viewheight; y += 16)
     {
-        V_DrawPatch(viewwindowx - 4, y, W_CacheLumpName("bordl", PU_CACHE));
-        V_DrawPatch(viewwindowx + viewwidth, y, W_CacheLumpName("bordr",
-                                                                PU_CACHE));
+        V_DrawPatch(viewwindowx - 4, y,
+                    W_CacheLumpName(DEH_String("bordl"), PU_CACHE));
+        V_DrawPatch(viewwindowx + viewwidth, y,
+                    W_CacheLumpName(DEH_String("bordr"), PU_CACHE));
     }
-    V_DrawPatch(viewwindowx - 4, viewwindowy - 4, W_CacheLumpName("bordtl",
-                                                                  PU_CACHE));
+    V_DrawPatch(viewwindowx - 4, viewwindowy - 4,
+                W_CacheLumpName(DEH_String("bordtl"), PU_CACHE));
     V_DrawPatch(viewwindowx + viewwidth, viewwindowy - 4,
-                W_CacheLumpName("bordtr", PU_CACHE));
+                W_CacheLumpName(DEH_String("bordtr"), PU_CACHE));
     V_DrawPatch(viewwindowx + viewwidth, viewwindowy + viewheight,
-                W_CacheLumpName("bordbr", PU_CACHE));
+                W_CacheLumpName(DEH_String("bordbr"), PU_CACHE));
     V_DrawPatch(viewwindowx - 4, viewwindowy + viewheight,
-                W_CacheLumpName("bordbl", PU_CACHE));
+                W_CacheLumpName(DEH_String("bordbl"), PU_CACHE));
 }
 
 /*
@@ -450,11 +453,11 @@
 
     if (gamemode == shareware)
     {
-        src = W_CacheLumpName("FLOOR04", PU_CACHE);
+        src = W_CacheLumpName(DEH_String("FLOOR04"), PU_CACHE);
     }
     else
     {
-        src = W_CacheLumpName("FLAT513", PU_CACHE);
+        src = W_CacheLumpName(DEH_String("FLAT513"), PU_CACHE);
     }
     dest = I_VideoBuffer;
 
@@ -476,20 +479,20 @@
         for (x = viewwindowx; x < viewwindowx + viewwidth; x += 16)
         {
             V_DrawPatch(x, viewwindowy - 4,
-                        W_CacheLumpName("bordt", PU_CACHE));
+                        W_CacheLumpName(DEH_String("bordt"), PU_CACHE));
         }
-        V_DrawPatch(viewwindowx - 4, viewwindowy, W_CacheLumpName("bordl",
-                                                                  PU_CACHE));
+        V_DrawPatch(viewwindowx - 4, viewwindowy,
+                    W_CacheLumpName(DEH_String("bordl"), PU_CACHE));
         V_DrawPatch(viewwindowx + viewwidth, viewwindowy,
-                    W_CacheLumpName("bordr", PU_CACHE));
+                    W_CacheLumpName(DEH_String("bordr"), PU_CACHE));
         V_DrawPatch(viewwindowx - 4, viewwindowy + 16,
-                    W_CacheLumpName("bordl", PU_CACHE));
+                    W_CacheLumpName(DEH_String("bordl"), PU_CACHE));
         V_DrawPatch(viewwindowx + viewwidth, viewwindowy + 16,
-                    W_CacheLumpName("bordr", PU_CACHE));
+                    W_CacheLumpName(DEH_String("bordr"), PU_CACHE));
 
         V_DrawPatch(viewwindowx - 4, viewwindowy - 4,
-                    W_CacheLumpName("bordtl", PU_CACHE));
+                    W_CacheLumpName(DEH_String("bordtl"), PU_CACHE));
         V_DrawPatch(viewwindowx + viewwidth, viewwindowy - 4,
-                    W_CacheLumpName("bordtr", PU_CACHE));
+                    W_CacheLumpName(DEH_String("bordtr"), PU_CACHE));
     }
 }
--- a/src/heretic/r_plane.c
+++ b/src/heretic/r_plane.c
@@ -25,6 +25,7 @@
 
 #include <stdlib.h>
 #include "doomdef.h"
+#include "deh_str.h"
 #include "i_system.h"
 #include "r_local.h"
 
@@ -90,7 +91,7 @@
 
 void R_InitSkyMap(void)
 {
-    skyflatnum = R_FlatNumForName("F_SKY1");
+    skyflatnum = R_FlatNumForName(DEH_String("F_SKY1"));
     skytexturemid = 200 * FRACUNIT;
     skyiscale = FRACUNIT;
 }
--- a/src/heretic/r_things.c
+++ b/src/heretic/r_things.c
@@ -25,6 +25,7 @@
 #include <stdio.h>
 #include <stdlib.h>
 #include "doomdef.h"
+#include "deh_str.h"
 #include "i_swap.h"
 #include "i_system.h"
 #include "r_local.h"
@@ -154,7 +155,7 @@
 void R_InitSpriteDefs(char **namelist)
 {
     char **check;
-    int i, l, intname, frame, rotation;
+    int i, l, frame, rotation;
     int start, end;
 
 // count the number of sprite names
@@ -176,17 +177,16 @@
 // Just compare 4 characters as ints
     for (i = 0; i < numsprites; i++)
     {
-        spritename = namelist[i];
+        spritename = DEH_String(namelist[i]);
         memset(sprtemp, -1, sizeof(sprtemp));
 
         maxframe = -1;
-        intname = *(int *) namelist[i];
 
         //
         // scan the lumps, filling in the frames for whatever is found
         //
         for (l = start + 1; l < end; l++)
-            if (*(int *) lumpinfo[l].name == intname)
+            if (!strncasecmp(lumpinfo[l].name, spritename, 4))
             {
                 frame = lumpinfo[l].name[4] - 'A';
                 rotation = lumpinfo[l].name[5] - '0';
@@ -209,7 +209,7 @@
             if (gamemode == shareware)
                 continue;
             I_Error("R_InitSprites: No lumps found for sprite %s",
-                    namelist[i]);
+                    spritename);
         }
 
         maxframe++;
@@ -219,7 +219,7 @@
             {
                 case -1:       // no rotations were found for that frame at all
                     I_Error("R_InitSprites: No patches found for %s frame %c",
-                            namelist[i], frame + 'A');
+                            spritename, frame + 'A');
                 case 0:        // only the first rotation is needed
                     break;
 
@@ -228,7 +228,7 @@
                         if (sprtemp[frame].lump[rotation] == -1)
                             I_Error
                                 ("R_InitSprites: Sprite %s frame %c is missing rotations",
-                                 namelist[i], frame + 'A');
+                                 spritename, frame + 'A');
             }
         }
 
--- a/src/heretic/s_sound.c
+++ b/src/heretic/s_sound.c
@@ -33,6 +33,8 @@
 #include "r_local.h"
 #include "p_local.h"
 
+#include "sounds.h"
+
 #include "w_wad.h"
 #include "z_zone.h"
 
@@ -55,9 +57,6 @@
 void *mus_sndptr;
 byte *soundCurve;
 
-extern sfxinfo_t S_sfx[];
-extern musicinfo_t S_music[];
-
 int snd_MaxVolume = 10;
 int snd_MusicVolume = 10;
 int snd_Channels = 16;
@@ -529,7 +528,7 @@
     {
         snd_Channels = 8;
     }
-    I_SetMusicVolume(snd_MusicVolume);
+    I_SetMusicVolume(snd_MusicVolume * 8);
     S_SetMaxVolume(true);
 
     I_AtExit(S_ShutDown, true);
@@ -550,8 +549,16 @@
         c->priority = channel[i].priority;
         c->name = S_sfx[c->id].name;
         c->mo = channel[i].mo;
-        c->distance = P_AproxDistance(c->mo->x - viewx, c->mo->y - viewy)
-            >> FRACBITS;
+
+        if (c->mo != NULL)
+        {
+            c->distance = P_AproxDistance(c->mo->x - viewx, c->mo->y - viewy)
+                >> FRACBITS;
+        }
+        else
+        {
+            c->distance = 0;
+        }
     }
 }
 
@@ -579,7 +586,7 @@
 static boolean musicPaused;
 void S_SetMusicVolume(void)
 {
-    I_SetMusicVolume(snd_MusicVolume);
+    I_SetMusicVolume(snd_MusicVolume * 8);
     if (snd_MusicVolume == 0)
     {
         I_PauseSong();
--- a/src/heretic/sb_bar.c
+++ b/src/heretic/sb_bar.c
@@ -25,6 +25,7 @@
 // SB_bar.c
 
 #include "doomdef.h"
+#include "deh_str.h"
 #include "i_video.h"
 #include "m_cheat.h"
 #include "m_misc.h"
@@ -196,53 +197,53 @@
     int i;
     int startLump;
 
-    PatchLTFACE = W_CacheLumpName("LTFACE", PU_STATIC);
-    PatchRTFACE = W_CacheLumpName("RTFACE", PU_STATIC);
-    PatchBARBACK = W_CacheLumpName("BARBACK", PU_STATIC);
-    PatchINVBAR = W_CacheLumpName("INVBAR", PU_STATIC);
-    PatchCHAIN = W_CacheLumpName("CHAIN", PU_STATIC);
+    PatchLTFACE = W_CacheLumpName(DEH_String("LTFACE"), PU_STATIC);
+    PatchRTFACE = W_CacheLumpName(DEH_String("RTFACE"), PU_STATIC);
+    PatchBARBACK = W_CacheLumpName(DEH_String("BARBACK"), PU_STATIC);
+    PatchINVBAR = W_CacheLumpName(DEH_String("INVBAR"), PU_STATIC);
+    PatchCHAIN = W_CacheLumpName(DEH_String("CHAIN"), PU_STATIC);
     if (deathmatch)
     {
-        PatchSTATBAR = W_CacheLumpName("STATBAR", PU_STATIC);
+        PatchSTATBAR = W_CacheLumpName(DEH_String("STATBAR"), PU_STATIC);
     }
     else
     {
-        PatchSTATBAR = W_CacheLumpName("LIFEBAR", PU_STATIC);
+        PatchSTATBAR = W_CacheLumpName(DEH_String("LIFEBAR"), PU_STATIC);
     }
     if (!netgame)
     {                           // single player game uses red life gem
-        PatchLIFEGEM = W_CacheLumpName("LIFEGEM2", PU_STATIC);
+        PatchLIFEGEM = W_CacheLumpName(DEH_String("LIFEGEM2"), PU_STATIC);
     }
     else
     {
-        PatchLIFEGEM = W_CacheLumpNum(W_GetNumForName("LIFEGEM0")
+        PatchLIFEGEM = W_CacheLumpNum(W_GetNumForName(DEH_String("LIFEGEM0"))
                                       + consoleplayer, PU_STATIC);
     }
-    PatchLTFCTOP = W_CacheLumpName("LTFCTOP", PU_STATIC);
-    PatchRTFCTOP = W_CacheLumpName("RTFCTOP", PU_STATIC);
-    PatchSELECTBOX = W_CacheLumpName("SELECTBOX", PU_STATIC);
-    PatchINVLFGEM1 = W_CacheLumpName("INVGEML1", PU_STATIC);
-    PatchINVLFGEM2 = W_CacheLumpName("INVGEML2", PU_STATIC);
-    PatchINVRTGEM1 = W_CacheLumpName("INVGEMR1", PU_STATIC);
-    PatchINVRTGEM2 = W_CacheLumpName("INVGEMR2", PU_STATIC);
-    PatchBLACKSQ = W_CacheLumpName("BLACKSQ", PU_STATIC);
-    PatchARMCLEAR = W_CacheLumpName("ARMCLEAR", PU_STATIC);
-    PatchCHAINBACK = W_CacheLumpName("CHAINBACK", PU_STATIC);
-    startLump = W_GetNumForName("IN0");
+    PatchLTFCTOP = W_CacheLumpName(DEH_String("LTFCTOP"), PU_STATIC);
+    PatchRTFCTOP = W_CacheLumpName(DEH_String("RTFCTOP"), PU_STATIC);
+    PatchSELECTBOX = W_CacheLumpName(DEH_String("SELECTBOX"), PU_STATIC);
+    PatchINVLFGEM1 = W_CacheLumpName(DEH_String("INVGEML1"), PU_STATIC);
+    PatchINVLFGEM2 = W_CacheLumpName(DEH_String("INVGEML2"), PU_STATIC);
+    PatchINVRTGEM1 = W_CacheLumpName(DEH_String("INVGEMR1"), PU_STATIC);
+    PatchINVRTGEM2 = W_CacheLumpName(DEH_String("INVGEMR2"), PU_STATIC);
+    PatchBLACKSQ = W_CacheLumpName(DEH_String("BLACKSQ"), PU_STATIC);
+    PatchARMCLEAR = W_CacheLumpName(DEH_String("ARMCLEAR"), PU_STATIC);
+    PatchCHAINBACK = W_CacheLumpName(DEH_String("CHAINBACK"), PU_STATIC);
+    startLump = W_GetNumForName(DEH_String("IN0"));
     for (i = 0; i < 10; i++)
     {
         PatchINumbers[i] = W_CacheLumpNum(startLump + i, PU_STATIC);
     }
-    PatchNEGATIVE = W_CacheLumpName("NEGNUM", PU_STATIC);
-    FontBNumBase = W_GetNumForName("FONTB16");
-    startLump = W_GetNumForName("SMALLIN0");
+    PatchNEGATIVE = W_CacheLumpName(DEH_String("NEGNUM"), PU_STATIC);
+    FontBNumBase = W_GetNumForName(DEH_String("FONTB16"));
+    startLump = W_GetNumForName(DEH_String("SMALLIN0"));
     for (i = 0; i < 10; i++)
     {
         PatchSmNumbers[i] = W_CacheLumpNum(startLump + i, PU_STATIC);
     }
-    playpalette = W_GetNumForName("PLAYPAL");
-    spinbooklump = W_GetNumForName("SPINBK0");
-    spinflylump = W_GetNumForName("SPFLY0");
+    playpalette = W_GetNumForName(DEH_String("PLAYPAL"));
+    spinbooklump = W_GetNumForName(DEH_String("SPINBK0"));
+    spinflylump = W_GetNumForName(DEH_String("SPFLY0"));
 }
 
 //---------------------------------------------------------------------------
@@ -311,7 +312,7 @@
     {
         if (val < -9)
         {
-            V_DrawPatch(x + 1, y + 1, W_CacheLumpName("LAME", PU_CACHE));
+            V_DrawPatch(x + 1, y + 1, W_CacheLumpName(DEH_String("LAME"), PU_CACHE));
         }
         else
         {
@@ -458,7 +459,7 @@
 
     if (leveltime & 16)
     {
-        MN_DrTextA("*** SOUND DEBUG INFO ***", xPos[0], 20);
+        MN_DrTextA(DEH_String("*** SOUND DEBUG INFO ***"), xPos[0], 20);
     }
     S_GetChannelInfo(&s);
     if (s.channelCount == 0)
@@ -466,13 +467,13 @@
         return;
     }
     x = 0;
-    MN_DrTextA("NAME", xPos[x++], 30);
-    MN_DrTextA("MO.T", xPos[x++], 30);
-    MN_DrTextA("MO.X", xPos[x++], 30);
-    MN_DrTextA("MO.Y", xPos[x++], 30);
-    MN_DrTextA("ID", xPos[x++], 30);
-    MN_DrTextA("PRI", xPos[x++], 30);
-    MN_DrTextA("DIST", xPos[x++], 30);
+    MN_DrTextA(DEH_String("NAME"), xPos[x++], 30);
+    MN_DrTextA(DEH_String("MO.T"), xPos[x++], 30);
+    MN_DrTextA(DEH_String("MO.X"), xPos[x++], 30);
+    MN_DrTextA(DEH_String("MO.Y"), xPos[x++], 30);
+    MN_DrTextA(DEH_String("ID"), xPos[x++], 30);
+    MN_DrTextA(DEH_String("PRI"), xPos[x++], 30);
+    MN_DrTextA(DEH_String("DIST"), xPos[x++], 30);
     for (i = 0; i < s.channelCount; i++)
     {
         c = &s.chan[i];
@@ -480,7 +481,7 @@
         y = 40 + i * 10;
         if (c->mo == NULL)
         {                       // Channel is unused
-            MN_DrTextA("------", xPos[0], y);
+            MN_DrTextA(DEH_String("------"), xPos[0], y);
             continue;
         }
         sprintf(text, "%s", c->name);
@@ -570,8 +571,10 @@
             V_DrawPatch(0, 158, PatchBARBACK);
             if (players[consoleplayer].cheats & CF_GODMODE)
             {
-                V_DrawPatch(16, 167, W_CacheLumpName("GOD1", PU_CACHE));
-                V_DrawPatch(287, 167, W_CacheLumpName("GOD2", PU_CACHE));
+                V_DrawPatch(16, 167,
+                            W_CacheLumpName(DEH_String("GOD1"), PU_CACHE));
+                V_DrawPatch(287, 167,
+                            W_CacheLumpName(DEH_String("GOD2"), PU_CACHE));
             }
             oldhealth = -1;
         }
@@ -776,8 +779,10 @@
     if (ArtifactFlash)
     {
         V_DrawPatch(180, 161, PatchBLACKSQ);
-        V_DrawPatch(182, 161, W_CacheLumpNum(W_GetNumForName("useartia")
-                                             + ArtifactFlash - 1, PU_CACHE));
+
+        temp = W_GetNumForName(DEH_String("useartia")) + ArtifactFlash - 1;
+
+        V_DrawPatch(182, 161, W_CacheLumpNum(temp, PU_CACHE));
         ArtifactFlash--;
         oldarti = -1;           // so that the correct artifact fills in after the flash
         UpdateState |= I_STATBAR;
@@ -789,7 +794,7 @@
         if (CPlayer->readyArtifact > 0)
         {
             V_DrawPatch(179, 160,
-                        W_CacheLumpName(patcharti[CPlayer->readyArtifact],
+                        W_CacheLumpName(DEH_String(patcharti[CPlayer->readyArtifact]),
                                         PU_CACHE));
             DrSmallNumber(CPlayer->inventory[inv_ptr].count, 201, 182);
         }
@@ -839,15 +844,15 @@
     {
         if (CPlayer->keys[key_yellow])
         {
-            V_DrawPatch(153, 164, W_CacheLumpName("ykeyicon", PU_CACHE));
+            V_DrawPatch(153, 164, W_CacheLumpName(DEH_String("ykeyicon"), PU_CACHE));
         }
         if (CPlayer->keys[key_green])
         {
-            V_DrawPatch(153, 172, W_CacheLumpName("gkeyicon", PU_CACHE));
+            V_DrawPatch(153, 172, W_CacheLumpName(DEH_String("gkeyicon"), PU_CACHE));
         }
         if (CPlayer->keys[key_blue])
         {
-            V_DrawPatch(153, 180, W_CacheLumpName("bkeyicon", PU_CACHE));
+            V_DrawPatch(153, 180, W_CacheLumpName(DEH_String("bkeyicon"), PU_CACHE));
         }
         oldkeys = playerkeys;
         UpdateState |= I_STATBAR;
@@ -861,7 +866,7 @@
         {
             DrINumber(temp, 109, 162);
             V_DrawPatch(111, 172,
-                        W_CacheLumpName(ammopic[CPlayer->readyweapon - 1],
+                        W_CacheLumpName(DEH_String(ammopic[CPlayer->readyweapon - 1]),
                                         PU_CACHE));
         }
         oldammo = temp;
@@ -887,6 +892,7 @@
 
 void DrawInventoryBar(void)
 {
+    char *patch;
     int i;
     int x;
 
@@ -899,10 +905,9 @@
         if (CPlayer->inventorySlotNum > x + i
             && CPlayer->inventory[x + i].type != arti_none)
         {
-            V_DrawPatch(50 + i * 31, 160,
-                        W_CacheLumpName(patcharti
-                                        [CPlayer->inventory[x + i].type],
-                                        PU_CACHE));
+            patch = DEH_String(patcharti[CPlayer->inventory[x + i].type]);
+
+            V_DrawPatch(50 + i * 31, 160, W_CacheLumpName(patch, PU_CACHE));
             DrSmallNumber(CPlayer->inventory[x + i].count, 69 + i * 31, 182);
         }
     }
@@ -921,6 +926,7 @@
 
 void DrawFullScreenStuff(void)
 {
+    char *patch;
     int i;
     int x;
     int temp;
@@ -950,10 +956,9 @@
     {
         if (CPlayer->readyArtifact > 0)
         {
-            V_DrawTLPatch(286, 170, W_CacheLumpName("ARTIBOX", PU_CACHE));
-            V_DrawPatch(286, 170,
-                        W_CacheLumpName(patcharti[CPlayer->readyArtifact],
-                                        PU_CACHE));
+            patch = DEH_String(patcharti[CPlayer->readyArtifact]);
+            V_DrawTLPatch(286, 170, W_CacheLumpName(DEH_String("ARTIBOX"), PU_CACHE));
+            V_DrawPatch(286, 170, W_CacheLumpName(patch, PU_CACHE));
             DrSmallNumber(CPlayer->inventory[inv_ptr].count, 307, 192);
         }
     }
@@ -962,15 +967,14 @@
         x = inv_ptr - curpos;
         for (i = 0; i < 7; i++)
         {
-            V_DrawTLPatch(50 + i * 31, 168, W_CacheLumpName("ARTIBOX",
-                                                            PU_CACHE));
+            V_DrawTLPatch(50 + i * 31, 168,
+                          W_CacheLumpName(DEH_String("ARTIBOX"), PU_CACHE));
             if (CPlayer->inventorySlotNum > x + i
                 && CPlayer->inventory[x + i].type != arti_none)
             {
+                patch = DEH_String(patcharti[CPlayer->inventory[x + i].type]);
                 V_DrawPatch(50 + i * 31, 168,
-                            W_CacheLumpName(patcharti
-                                            [CPlayer->inventory[x + i].type],
-                                            PU_CACHE));
+                            W_CacheLumpName(patch, PU_CACHE));
                 DrSmallNumber(CPlayer->inventory[x + i].count, 69 + i * 31,
                               190);
             }
@@ -1051,11 +1055,11 @@
     player->cheats ^= CF_GODMODE;
     if (player->cheats & CF_GODMODE)
     {
-        P_SetMessage(player, TXT_CHEATGODON, false);
+        P_SetMessage(player, DEH_String(TXT_CHEATGODON), false);
     }
     else
     {
-        P_SetMessage(player, TXT_CHEATGODOFF, false);
+        P_SetMessage(player, DEH_String(TXT_CHEATGODOFF), false);
     }
     SB_state = -1;
 }
@@ -1065,11 +1069,11 @@
     player->cheats ^= CF_NOCLIP;
     if (player->cheats & CF_NOCLIP)
     {
-        P_SetMessage(player, TXT_CHEATNOCLIPON, false);
+        P_SetMessage(player, DEH_String(TXT_CHEATNOCLIPON), false);
     }
     else
     {
-        P_SetMessage(player, TXT_CHEATNOCLIPOFF, false);
+        P_SetMessage(player, DEH_String(TXT_CHEATNOCLIPOFF), false);
     }
 }
 
@@ -1102,7 +1106,7 @@
     {
         player->ammo[i] = player->maxammo[i];
     }
-    P_SetMessage(player, TXT_CHEATWEAPONS, false);
+    P_SetMessage(player, DEH_String(TXT_CHEATWEAPONS), false);
 }
 
 static void CheatPowerFunc(player_t * player, Cheat_t * cheat)
@@ -1110,12 +1114,12 @@
     if (player->powers[pw_weaponlevel2])
     {
         player->powers[pw_weaponlevel2] = 0;
-        P_SetMessage(player, TXT_CHEATPOWEROFF, false);
+        P_SetMessage(player, DEH_String(TXT_CHEATPOWEROFF), false);
     }
     else
     {
         P_UseArtifact(player, arti_tomeofpower);
-        P_SetMessage(player, TXT_CHEATPOWERON, false);
+        P_SetMessage(player, DEH_String(TXT_CHEATPOWERON), false);
     }
 }
 
@@ -1129,7 +1133,7 @@
     {
         player->health = player->mo->health = MAXHEALTH;
     }
-    P_SetMessage(player, TXT_CHEATHEALTH, false);
+    P_SetMessage(player, DEH_String(TXT_CHEATHEALTH), false);
 }
 
 static void CheatKeysFunc(player_t * player, Cheat_t * cheat)
@@ -1140,7 +1144,7 @@
     player->keys[key_green] = true;
     player->keys[key_blue] = true;
     playerkeys = 7;             // Key refresh flags
-    P_SetMessage(player, TXT_CHEATKEYS, false);
+    P_SetMessage(player, DEH_String(TXT_CHEATKEYS), false);
 }
 
 static void CheatSoundFunc(player_t * player, Cheat_t * cheat)
@@ -1148,11 +1152,11 @@
     DebugSound = !DebugSound;
     if (DebugSound)
     {
-        P_SetMessage(player, TXT_CHEATSOUNDON, false);
+        P_SetMessage(player, DEH_String(TXT_CHEATSOUNDON), false);
     }
     else
     {
-        P_SetMessage(player, TXT_CHEATSOUNDOFF, false);
+        P_SetMessage(player, DEH_String(TXT_CHEATSOUNDOFF), false);
     }
 }
 
@@ -1161,11 +1165,11 @@
     DisplayTicker = !DisplayTicker;
     if (DisplayTicker)
     {
-        P_SetMessage(player, TXT_CHEATTICKERON, false);
+        P_SetMessage(player, DEH_String(TXT_CHEATTICKERON), false);
     }
     else
     {
-        P_SetMessage(player, TXT_CHEATTICKEROFF, false);
+        P_SetMessage(player, DEH_String(TXT_CHEATTICKEROFF), false);
     }
 
     I_DisplayFPSDots(DisplayTicker);
@@ -1173,12 +1177,12 @@
 
 static void CheatArtifact1Func(player_t * player, Cheat_t * cheat)
 {
-    P_SetMessage(player, TXT_CHEATARTIFACTS1, false);
+    P_SetMessage(player, DEH_String(TXT_CHEATARTIFACTS1), false);
 }
 
 static void CheatArtifact2Func(player_t * player, Cheat_t * cheat)
 {
-    P_SetMessage(player, TXT_CHEATARTIFACTS2, false);
+    P_SetMessage(player, DEH_String(TXT_CHEATARTIFACTS2), false);
 }
 
 static void CheatArtifact3Func(player_t * player, Cheat_t * cheat)
@@ -1206,7 +1210,7 @@
                 P_GiveArtifact(player, i, NULL);
             }
         }
-        P_SetMessage(player, TXT_CHEATARTIFACTS3, false);
+        P_SetMessage(player, DEH_String(TXT_CHEATARTIFACTS3), false);
     }
     else if (type > arti_none && type < NUMARTIFACTS
              && count > 0 && count < 10)
@@ -1214,7 +1218,7 @@
         if (gamemode == shareware
          && (type == arti_superhealth || type == arti_teleport))
         {
-            P_SetMessage(player, TXT_CHEATARTIFACTSFAIL, false);
+            P_SetMessage(player, DEH_String(TXT_CHEATARTIFACTSFAIL), false);
             return;
         }
         for (i = 0; i < count; i++)
@@ -1221,11 +1225,11 @@
         {
             P_GiveArtifact(player, type, NULL);
         }
-        P_SetMessage(player, TXT_CHEATARTIFACTS3, false);
+        P_SetMessage(player, DEH_String(TXT_CHEATARTIFACTS3), false);
     }
     else
     {                           // Bad input
-        P_SetMessage(player, TXT_CHEATARTIFACTSFAIL, false);
+        P_SetMessage(player, DEH_String(TXT_CHEATARTIFACTSFAIL), false);
     }
 }
 
@@ -1242,7 +1246,7 @@
     if (D_ValidEpisodeMap(gamemission, gamemode, episode, map))
     {
         G_DeferedInitNew(gameskill, episode, map);
-        P_SetMessage(player, TXT_CHEATWARP, false);
+        P_SetMessage(player, DEH_String(TXT_CHEATWARP), false);
     }
 }
 
@@ -1254,12 +1258,12 @@
     {
         if (P_UndoPlayerChicken(player))
         {
-            P_SetMessage(player, TXT_CHEATCHICKENOFF, false);
+            P_SetMessage(player, DEH_String(TXT_CHEATCHICKENOFF), false);
         }
     }
     else if (P_ChickenMorphPlayer(player))
     {
-        P_SetMessage(player, TXT_CHEATCHICKENON, false);
+        P_SetMessage(player, DEH_String(TXT_CHEATCHICKENON), false);
     }
 }
 
@@ -1266,7 +1270,7 @@
 static void CheatMassacreFunc(player_t * player, Cheat_t * cheat)
 {
     P_Massacre();
-    P_SetMessage(player, TXT_CHEATMASSACRE, false);
+    P_SetMessage(player, DEH_String(TXT_CHEATMASSACRE), false);
 }
 
 static void CheatIDKFAFunc(player_t * player, Cheat_t * cheat)
@@ -1281,11 +1285,11 @@
         player->weaponowned[i] = false;
     }
     player->pendingweapon = wp_staff;
-    P_SetMessage(player, TXT_CHEATIDKFA, true);
+    P_SetMessage(player, DEH_String(TXT_CHEATIDKFA), true);
 }
 
 static void CheatIDDQDFunc(player_t * player, Cheat_t * cheat)
 {
     P_DamageMobj(player->mo, NULL, player->mo, 10000);
-    P_SetMessage(player, TXT_CHEATIDDQD, true);
+    P_SetMessage(player, DEH_String(TXT_CHEATIDDQD), true);
 }
--- a/src/heretic/sounds.h
+++ b/src/heretic/sounds.h
@@ -27,6 +27,8 @@
 #ifndef __SOUNDSH__
 #define __SOUNDSH__
 
+#include "i_sound.h"
+
 #define MAX_SND_DIST 	1600
 #define MAX_CHANNELS	16
 
@@ -290,5 +292,8 @@
     sfx_amb11,
     NUMSFX
 } sfxenum_t;
+
+extern sfxinfo_t S_sfx[];
+extern musicinfo_t S_music[];
 
 #endif
--- a/src/hexen/.gitignore
+++ b/src/hexen/.gitignore
@@ -1,7 +1,5 @@
 Makefile
 Makefile.in
 .deps
-*.rc
-chocolate-doom
-chocolate-server
-*.exe
+tags
+TAGS
--- a/src/hexen/h2_main.c
+++ b/src/hexen/h2_main.c
@@ -46,6 +46,7 @@
 #include "m_controls.h"
 #include "p_local.h"
 #include "v_video.h"
+#include "w_main.h"
 
 // MACROS ------------------------------------------------------------------
 
@@ -88,7 +89,6 @@
 static void HandleArgs(void);
 static void CheckRecordFrom(void);
 static void DrawAndBlit(void);
-static void ExecOptionFILE(char **args, int tag);
 static void ExecOptionSCRIPTS(char **args, int tag);
 static void ExecOptionSKILL(char **args, int tag);
 static void ExecOptionPLAYDEMO(char **args, int tag);
@@ -133,7 +133,6 @@
 static char *pagename;
 
 static execOpt_t ExecOptions[] = {
-    {"-file", ExecOptionFILE, 1, 0},
     {"-scripts", ExecOptionSCRIPTS, 1, 0},
     {"-skill", ExecOptionSKILL, 1, 0},
     {"-playdemo", ExecOptionPLAYDEMO, 1, 0},
@@ -157,6 +156,7 @@
     M_BindMapControls();
     M_BindMenuControls();
     M_BindWeaponControls();
+    M_BindChatControls(MAXPLAYERS);
     M_BindHereticControls();
     M_BindHexenControls();
 
@@ -422,6 +422,9 @@
 
     cmdfrag = M_ParmExists("-cmdfrag");
 
+    // Check WAD file command line options
+    W_ParseCommandLine();
+
     // Process command line options
     for (opt = ExecOptions; opt->name != NULL; opt++)
     {
@@ -482,27 +485,6 @@
     startskill = args[1][0] - '1';
     autostart = true;
 }
-
-//==========================================================================
-//
-// ExecOptionFILE
-//
-//==========================================================================
-
-static void ExecOptionFILE(char **args, int tag)
-{
-    char *filename;
-    int p;
-
-    p = M_CheckParm("-file");
-    while (++p != myargc && myargv[p][0] != '-')
-    {
-        filename = D_TryFindWADByName(myargv[p]);
-
-        D_AddFile(filename);
-    }
-}
-
 
 //==========================================================================
 //
--- a/src/hexen/mn_menu.c
+++ b/src/hexen/mn_menu.c
@@ -679,6 +679,32 @@
     DrawFileSlots(&SaveMenu);
 }
 
+static boolean ReadDescriptionForSlot(int slot, char *description)
+{
+    FILE *fp;
+    boolean found;
+    char name[100];
+    char versionText[HXS_VERSION_TEXT_LENGTH];
+
+    sprintf(name, "%shex%d.hxs", SavePath, slot);
+
+    fp = fopen(name, "rb");
+
+    if (fp == NULL)
+    {
+        return false;
+    }
+
+    found = fread(description, HXS_DESCRIPTION_LENGTH, 1, fp) == 1
+         && fread(versionText, HXS_VERSION_TEXT_LENGTH, 1, fp) == 1;
+
+    found = found && strcmp(versionText, HXS_VERSION_TEXT) == 0;
+
+    fclose(fp);
+
+    return found;
+}
+
 //===========================================================================
 //
 // MN_LoadSlotText
@@ -689,29 +715,12 @@
 
 void MN_LoadSlotText(void)
 {
-    int slot;
-    FILE *fp;
-    char name[100];
-    char versionText[HXS_VERSION_TEXT_LENGTH];
     char description[HXS_DESCRIPTION_LENGTH];
-    boolean found;
+    int slot;
 
     for (slot = 0; slot < 6; slot++)
     {
-        found = false;
-        sprintf(name, "%shex%d.hxs", SavePath, slot);
-        fp = fopen(name, "rb");
-        if (fp)
-        {
-            fread(description, HXS_DESCRIPTION_LENGTH, 1, fp);
-            fread(versionText, HXS_VERSION_TEXT_LENGTH, 1, fp);
-            fclose(fp);
-            if (!strcmp(versionText, HXS_VERSION_TEXT))
-            {
-                found = true;
-            }
-        }
-        if (found)
+        if (ReadDescriptionForSlot(slot, description))
         {
             memcpy(SlotText[slot], description, SLOTTEXTLEN);
             SlotStatus[slot] = 1;
--- a/src/hexen/p_acs.c
+++ b/src/hexen/p_acs.c
@@ -509,7 +509,7 @@
     }
     ACSStore[index].map = map;
     ACSStore[index].script = number;
-    *((int *) ACSStore[index].args) = *((int *) args);
+    memcpy(ACSStore[index].args, args, sizeof(int));
     return true;
 }
 
--- a/src/hexen/p_spec.c
+++ b/src/hexen/p_spec.c
@@ -245,6 +245,8 @@
     fixed_t height = currentheight;
     fixed_t heightlist[20];     // 20 adjoining sectors max!
 
+    heightlist[0] = 0;
+
     for (i = 0, h = 0; i < sec->linecount; i++)
     {
         check = sec->lines[i];
--- a/src/hexen/r_things.c
+++ b/src/hexen/r_things.c
@@ -158,7 +158,7 @@
 void R_InitSpriteDefs(char **namelist)
 {
     char **check;
-    int i, l, intname, frame, rotation;
+    int i, l, frame, rotation;
     int start, end;
 
 // count the number of sprite names
@@ -184,13 +184,12 @@
         memset(sprtemp, -1, sizeof(sprtemp));
 
         maxframe = -1;
-        intname = *(int *) namelist[i];
 
         //
         // scan the lumps, filling in the frames for whatever is found
         //
         for (l = start + 1; l < end; l++)
-            if (*(int *) lumpinfo[l].name == intname)
+            if (!strncmp(lumpinfo[l].name, namelist[i], 4))
             {
                 frame = lumpinfo[l].name[4] - 'A';
                 rotation = lumpinfo[l].name[5] - '0';
--- a/src/hexen/s_sound.c
+++ b/src/hexen/s_sound.c
@@ -736,7 +736,7 @@
     {
         snd_Channels = 8;
     }
-    I_SetMusicVolume(snd_MusicVolume);
+    I_SetMusicVolume(snd_MusicVolume * 8);
 
     I_AtExit(S_ShutDown, true);
 
@@ -787,8 +787,16 @@
         c->priority = Channel[i].priority;
         c->name = S_sfx[c->id].name;
         c->mo = Channel[i].mo;
-        c->distance = P_AproxDistance(c->mo->x - viewx, c->mo->y - viewy)
-            >> FRACBITS;
+
+        if (c->mo != NULL)
+        {
+            c->distance = P_AproxDistance(c->mo->x - viewx, c->mo->y - viewy)
+                >> FRACBITS;
+        }
+        else
+        {
+            c->distance = 0;
+        }
     }
 }
 
@@ -829,7 +837,7 @@
     }
     else
     {
-        I_SetMusicVolume(snd_MusicVolume);
+        I_SetMusicVolume(snd_MusicVolume * 8);
     }
     if (snd_MusicVolume == 0)
     {
--- /dev/null
+++ b/src/i_oplmusic.c
@@ -1,0 +1,1464 @@
+// Emacs style mode select   -*- C++ -*- 
+//-----------------------------------------------------------------------------
+//
+// Copyright(C) 1993-1996 Id Software, Inc.
+// Copyright(C) 2005 Simon Howard
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+// 02111-1307, USA.
+//
+// DESCRIPTION:
+//	System interface for music.
+//
+//-----------------------------------------------------------------------------
+
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "memio.h"
+#include "mus2mid.h"
+
+#include "deh_main.h"
+#include "i_sound.h"
+#include "i_swap.h"
+#include "m_misc.h"
+#include "w_wad.h"
+#include "z_zone.h"
+
+#include "opl.h"
+#include "midifile.h"
+
+// #define OPL_MIDI_DEBUG
+
+#define MAXMIDLENGTH (96 * 1024)
+#define GENMIDI_NUM_INSTRS  128
+
+#define GENMIDI_HEADER          "#OPL_II#"
+#define GENMIDI_FLAG_FIXED      0x0001         /* fixed pitch */
+#define GENMIDI_FLAG_2VOICE     0x0004         /* double voice (OPL3) */
+
+typedef struct
+{
+    byte tremolo;
+    byte attack;
+    byte sustain;
+    byte waveform;
+    byte scale;
+    byte level;
+} PACKEDATTR genmidi_op_t;
+
+typedef struct
+{
+    genmidi_op_t modulator;
+    byte feedback;
+    genmidi_op_t carrier;
+    byte unused;
+    short base_note_offset;
+} PACKEDATTR genmidi_voice_t;
+
+typedef struct
+{
+    unsigned short flags;
+    byte fine_tuning;
+    byte fixed_note;
+
+    genmidi_voice_t voices[2];
+} PACKEDATTR genmidi_instr_t;
+
+// Data associated with a channel of a track that is currently playing.
+
+typedef struct
+{
+    // The instrument currently used for this track.
+
+    genmidi_instr_t *instrument;
+
+    // Volume level
+
+    int volume;
+
+    // Pitch bend value:
+
+    int bend;
+
+} opl_channel_data_t;
+
+// Data associated with a track that is currently playing.
+
+typedef struct
+{
+    // Data for each channel.
+
+    opl_channel_data_t channels[MIDI_CHANNELS_PER_TRACK];
+
+    // Track iterator used to read new events.
+
+    midi_track_iter_t *iter;
+
+    // Tempo control variables
+
+    unsigned int ticks_per_beat;
+    unsigned int ms_per_beat;
+} opl_track_data_t;
+
+typedef struct opl_voice_s opl_voice_t;
+
+struct opl_voice_s
+{
+    // Index of this voice:
+    int index;
+
+    // The operators used by this voice:
+    int op1, op2;
+
+    // Currently-loaded instrument data
+    genmidi_instr_t *current_instr;
+
+    // The voice number in the instrument to use.
+    // This is normally set to zero; if this is a double voice
+    // instrument, it may be one.
+    unsigned int current_instr_voice;
+
+    // The channel currently using this voice.
+    opl_channel_data_t *channel;
+
+    // The midi key that this voice is playing.
+    unsigned int key;
+
+    // The note being played.  This is normally the same as
+    // the key, but if the instrument is a fixed pitch
+    // instrument, it is different.
+    unsigned int note;
+
+    // The frequency value being used.
+    unsigned int freq;
+
+    // The volume of the note being played on this channel.
+    unsigned int note_volume;
+
+    // The current volume (register value) that has been set for this channel.
+    unsigned int reg_volume;
+
+    // Next in linked list; a voice is always either in the
+    // free list or the allocated list.
+    opl_voice_t *next;
+};
+
+// Operators used by the different voices.
+
+static const int voice_operators[2][OPL_NUM_VOICES] = {
+    { 0x00, 0x01, 0x02, 0x08, 0x09, 0x0a, 0x10, 0x11, 0x12 },
+    { 0x03, 0x04, 0x05, 0x0b, 0x0c, 0x0d, 0x13, 0x14, 0x15 }
+};
+
+// Frequency values to use for each note.
+
+static const unsigned short frequency_curve[] = {
+
+    0x133, 0x133, 0x134, 0x134, 0x135, 0x136, 0x136, 0x137,   // -1
+    0x137, 0x138, 0x138, 0x139, 0x139, 0x13a, 0x13b, 0x13b,
+    0x13c, 0x13c, 0x13d, 0x13d, 0x13e, 0x13f, 0x13f, 0x140,
+    0x140, 0x141, 0x142, 0x142, 0x143, 0x143, 0x144, 0x144,
+
+    0x145, 0x146, 0x146, 0x147, 0x147, 0x148, 0x149, 0x149,   // -2
+    0x14a, 0x14a, 0x14b, 0x14c, 0x14c, 0x14d, 0x14d, 0x14e,
+    0x14f, 0x14f, 0x150, 0x150, 0x151, 0x152, 0x152, 0x153,
+    0x153, 0x154, 0x155, 0x155, 0x156, 0x157, 0x157, 0x158,
+
+    // These are used for the first seven MIDI note values:
+
+    0x158, 0x159, 0x15a, 0x15a, 0x15b, 0x15b, 0x15c, 0x15d,   // 0
+    0x15d, 0x15e, 0x15f, 0x15f, 0x160, 0x161, 0x161, 0x162,
+    0x162, 0x163, 0x164, 0x164, 0x165, 0x166, 0x166, 0x167,
+    0x168, 0x168, 0x169, 0x16a, 0x16a, 0x16b, 0x16c, 0x16c,
+
+    0x16d, 0x16e, 0x16e, 0x16f, 0x170, 0x170, 0x171, 0x172,   // 1
+    0x172, 0x173, 0x174, 0x174, 0x175, 0x176, 0x176, 0x177,
+    0x178, 0x178, 0x179, 0x17a, 0x17a, 0x17b, 0x17c, 0x17c,
+    0x17d, 0x17e, 0x17e, 0x17f, 0x180, 0x181, 0x181, 0x182,
+
+    0x183, 0x183, 0x184, 0x185, 0x185, 0x186, 0x187, 0x188,   // 2
+    0x188, 0x189, 0x18a, 0x18a, 0x18b, 0x18c, 0x18d, 0x18d,
+    0x18e, 0x18f, 0x18f, 0x190, 0x191, 0x192, 0x192, 0x193,
+    0x194, 0x194, 0x195, 0x196, 0x197, 0x197, 0x198, 0x199,
+
+    0x19a, 0x19a, 0x19b, 0x19c, 0x19d, 0x19d, 0x19e, 0x19f,   // 3
+    0x1a0, 0x1a0, 0x1a1, 0x1a2, 0x1a3, 0x1a3, 0x1a4, 0x1a5,
+    0x1a6, 0x1a6, 0x1a7, 0x1a8, 0x1a9, 0x1a9, 0x1aa, 0x1ab,
+    0x1ac, 0x1ad, 0x1ad, 0x1ae, 0x1af, 0x1b0, 0x1b0, 0x1b1,
+
+    0x1b2, 0x1b3, 0x1b4, 0x1b4, 0x1b5, 0x1b6, 0x1b7, 0x1b8,   // 4
+    0x1b8, 0x1b9, 0x1ba, 0x1bb, 0x1bc, 0x1bc, 0x1bd, 0x1be,
+    0x1bf, 0x1c0, 0x1c0, 0x1c1, 0x1c2, 0x1c3, 0x1c4, 0x1c4,
+    0x1c5, 0x1c6, 0x1c7, 0x1c8, 0x1c9, 0x1c9, 0x1ca, 0x1cb,
+
+    0x1cc, 0x1cd, 0x1ce, 0x1ce, 0x1cf, 0x1d0, 0x1d1, 0x1d2,   // 5
+    0x1d3, 0x1d3, 0x1d4, 0x1d5, 0x1d6, 0x1d7, 0x1d8, 0x1d8,
+    0x1d9, 0x1da, 0x1db, 0x1dc, 0x1dd, 0x1de, 0x1de, 0x1df,
+    0x1e0, 0x1e1, 0x1e2, 0x1e3, 0x1e4, 0x1e5, 0x1e5, 0x1e6,
+
+    0x1e7, 0x1e8, 0x1e9, 0x1ea, 0x1eb, 0x1ec, 0x1ed, 0x1ed,   // 6
+    0x1ee, 0x1ef, 0x1f0, 0x1f1, 0x1f2, 0x1f3, 0x1f4, 0x1f5,
+    0x1f6, 0x1f6, 0x1f7, 0x1f8, 0x1f9, 0x1fa, 0x1fb, 0x1fc,
+    0x1fd, 0x1fe, 0x1ff, 0x200, 0x201, 0x201, 0x202, 0x203,
+
+    // First note of looped range used for all octaves:
+
+    0x204, 0x205, 0x206, 0x207, 0x208, 0x209, 0x20a, 0x20b,   // 7
+    0x20c, 0x20d, 0x20e, 0x20f, 0x210, 0x210, 0x211, 0x212,
+    0x213, 0x214, 0x215, 0x216, 0x217, 0x218, 0x219, 0x21a,
+    0x21b, 0x21c, 0x21d, 0x21e, 0x21f, 0x220, 0x221, 0x222,
+
+    0x223, 0x224, 0x225, 0x226, 0x227, 0x228, 0x229, 0x22a,   // 8
+    0x22b, 0x22c, 0x22d, 0x22e, 0x22f, 0x230, 0x231, 0x232,
+    0x233, 0x234, 0x235, 0x236, 0x237, 0x238, 0x239, 0x23a,
+    0x23b, 0x23c, 0x23d, 0x23e, 0x23f, 0x240, 0x241, 0x242,
+
+    0x244, 0x245, 0x246, 0x247, 0x248, 0x249, 0x24a, 0x24b,   // 9
+    0x24c, 0x24d, 0x24e, 0x24f, 0x250, 0x251, 0x252, 0x253,
+    0x254, 0x256, 0x257, 0x258, 0x259, 0x25a, 0x25b, 0x25c,
+    0x25d, 0x25e, 0x25f, 0x260, 0x262, 0x263, 0x264, 0x265,
+
+    0x266, 0x267, 0x268, 0x269, 0x26a, 0x26c, 0x26d, 0x26e,   // 10
+    0x26f, 0x270, 0x271, 0x272, 0x273, 0x275, 0x276, 0x277,
+    0x278, 0x279, 0x27a, 0x27b, 0x27d, 0x27e, 0x27f, 0x280,
+    0x281, 0x282, 0x284, 0x285, 0x286, 0x287, 0x288, 0x289,
+
+    0x28b, 0x28c, 0x28d, 0x28e, 0x28f, 0x290, 0x292, 0x293,   // 11
+    0x294, 0x295, 0x296, 0x298, 0x299, 0x29a, 0x29b, 0x29c,
+    0x29e, 0x29f, 0x2a0, 0x2a1, 0x2a2, 0x2a4, 0x2a5, 0x2a6,
+    0x2a7, 0x2a9, 0x2aa, 0x2ab, 0x2ac, 0x2ae, 0x2af, 0x2b0,
+
+    0x2b1, 0x2b2, 0x2b4, 0x2b5, 0x2b6, 0x2b7, 0x2b9, 0x2ba,   // 12
+    0x2bb, 0x2bd, 0x2be, 0x2bf, 0x2c0, 0x2c2, 0x2c3, 0x2c4,
+    0x2c5, 0x2c7, 0x2c8, 0x2c9, 0x2cb, 0x2cc, 0x2cd, 0x2ce,
+    0x2d0, 0x2d1, 0x2d2, 0x2d4, 0x2d5, 0x2d6, 0x2d8, 0x2d9,
+
+    0x2da, 0x2dc, 0x2dd, 0x2de, 0x2e0, 0x2e1, 0x2e2, 0x2e4,   // 13
+    0x2e5, 0x2e6, 0x2e8, 0x2e9, 0x2ea, 0x2ec, 0x2ed, 0x2ee,
+    0x2f0, 0x2f1, 0x2f2, 0x2f4, 0x2f5, 0x2f6, 0x2f8, 0x2f9,
+    0x2fb, 0x2fc, 0x2fd, 0x2ff, 0x300, 0x302, 0x303, 0x304,
+
+    0x306, 0x307, 0x309, 0x30a, 0x30b, 0x30d, 0x30e, 0x310,   // 14
+    0x311, 0x312, 0x314, 0x315, 0x317, 0x318, 0x31a, 0x31b,
+    0x31c, 0x31e, 0x31f, 0x321, 0x322, 0x324, 0x325, 0x327,
+    0x328, 0x329, 0x32b, 0x32c, 0x32e, 0x32f, 0x331, 0x332,
+
+    0x334, 0x335, 0x337, 0x338, 0x33a, 0x33b, 0x33d, 0x33e,   // 15
+    0x340, 0x341, 0x343, 0x344, 0x346, 0x347, 0x349, 0x34a,
+    0x34c, 0x34d, 0x34f, 0x350, 0x352, 0x353, 0x355, 0x357,
+    0x358, 0x35a, 0x35b, 0x35d, 0x35e, 0x360, 0x361, 0x363,
+
+    0x365, 0x366, 0x368, 0x369, 0x36b, 0x36c, 0x36e, 0x370,   // 16
+    0x371, 0x373, 0x374, 0x376, 0x378, 0x379, 0x37b, 0x37c,
+    0x37e, 0x380, 0x381, 0x383, 0x384, 0x386, 0x388, 0x389,
+    0x38b, 0x38d, 0x38e, 0x390, 0x392, 0x393, 0x395, 0x397,
+
+    0x398, 0x39a, 0x39c, 0x39d, 0x39f, 0x3a1, 0x3a2, 0x3a4,   // 17
+    0x3a6, 0x3a7, 0x3a9, 0x3ab, 0x3ac, 0x3ae, 0x3b0, 0x3b1,
+    0x3b3, 0x3b5, 0x3b7, 0x3b8, 0x3ba, 0x3bc, 0x3bd, 0x3bf,
+    0x3c1, 0x3c3, 0x3c4, 0x3c6, 0x3c8, 0x3ca, 0x3cb, 0x3cd,
+
+    // The last note has an incomplete range, and loops round back to
+    // the start.  Note that the last value is actually a buffer overrun
+    // and does not fit with the other values.
+
+    0x3cf, 0x3d1, 0x3d2, 0x3d4, 0x3d6, 0x3d8, 0x3da, 0x3db,   // 18
+    0x3dd, 0x3df, 0x3e1, 0x3e3, 0x3e4, 0x3e6, 0x3e8, 0x3ea,
+    0x3ec, 0x3ed, 0x3ef, 0x3f1, 0x3f3, 0x3f5, 0x3f6, 0x3f8,
+    0x3fa, 0x3fc, 0x3fe, 0x36c,
+};
+
+// Mapping from MIDI volume level to OPL level value.
+
+static const unsigned int volume_mapping_table[] = {
+    0, 1, 3, 5, 6, 8, 10, 11,
+    13, 14, 16, 17, 19, 20, 22, 23,
+    25, 26, 27, 29, 30, 32, 33, 34,
+    36, 37, 39, 41, 43, 45, 47, 49,
+    50, 52, 54, 55, 57, 59, 60, 61,
+    63, 64, 66, 67, 68, 69, 71, 72,
+    73, 74, 75, 76, 77, 79, 80, 81,
+    82, 83, 84, 84, 85, 86, 87, 88,
+    89, 90, 91, 92, 92, 93, 94, 95,
+    96, 96, 97, 98, 99, 99, 100, 101,
+    101, 102, 103, 103, 104, 105, 105, 106,
+    107, 107, 108, 109, 109, 110, 110, 111,
+    112, 112, 113, 113, 114, 114, 115, 115,
+    116, 117, 117, 118, 118, 119, 119, 120,
+    120, 121, 121, 122, 122, 123, 123, 123,
+    124, 124, 125, 125, 126, 126, 127, 127
+};
+
+static boolean music_initialized = false;
+
+//static boolean musicpaused = false;
+static int current_music_volume;
+
+// GENMIDI lump instrument data:
+
+static genmidi_instr_t *main_instrs;
+static genmidi_instr_t *percussion_instrs;
+
+// Voices:
+
+static opl_voice_t voices[OPL_NUM_VOICES];
+static opl_voice_t *voice_free_list;
+static opl_voice_t *voice_alloced_list;
+
+// Track data for playing tracks:
+
+static opl_track_data_t *tracks;
+static unsigned int num_tracks = 0;
+static unsigned int running_tracks = 0;
+static boolean song_looping;
+
+// Configuration file variable, containing the port number for the
+// adlib chip.
+
+int opl_io_port = 0x388;
+
+// Load instrument table from GENMIDI lump:
+
+static boolean LoadInstrumentTable(void)
+{
+    byte *lump;
+
+    lump = W_CacheLumpName("GENMIDI", PU_STATIC);
+
+    // Check header
+
+    if (strncmp((char *) lump, GENMIDI_HEADER, strlen(GENMIDI_HEADER)) != 0)
+    {
+        W_ReleaseLumpName("GENMIDI");
+
+        return false;
+    }
+
+    main_instrs = (genmidi_instr_t *) (lump + strlen(GENMIDI_HEADER));
+    percussion_instrs = main_instrs + GENMIDI_NUM_INSTRS;
+
+    return true;
+}
+
+// Get the next available voice from the freelist.
+
+static opl_voice_t *GetFreeVoice(void)
+{
+    opl_voice_t *result;
+
+    // None available?
+
+    if (voice_free_list == NULL)
+    {
+        return NULL;
+    }
+
+    // Remove from free list
+
+    result = voice_free_list;
+    voice_free_list = voice_free_list->next;
+
+    // Add to allocated list
+
+    result->next = voice_alloced_list;
+    voice_alloced_list = result;
+
+    return result;
+}
+
+// Remove a voice from the allocated voices list.
+
+static void RemoveVoiceFromAllocedList(opl_voice_t *voice)
+{
+    opl_voice_t **rover;
+
+    rover = &voice_alloced_list;
+
+    // Search the list until we find the voice, then remove it.
+
+    while (*rover != NULL)
+    {
+        if (*rover == voice)
+        {
+            *rover = voice->next;
+            voice->next = NULL;
+            break;
+        }
+
+        rover = &(*rover)->next;
+    }
+}
+
+// Release a voice back to the freelist.
+
+static void ReleaseVoice(opl_voice_t *voice)
+{
+    opl_voice_t **rover;
+
+    voice->channel = NULL;
+    voice->note = 0;
+
+    // Remove from alloced list.
+
+    RemoveVoiceFromAllocedList(voice);
+
+    // Search to the end of the freelist (This is how Doom behaves!)
+
+    rover = &voice_free_list;
+
+    while (*rover != NULL)
+    {
+        rover = &(*rover)->next;
+    }
+
+    *rover = voice;
+    voice->next = NULL;
+}
+
+// Load data to the specified operator
+
+static void LoadOperatorData(int operator, genmidi_op_t *data,
+                             boolean max_level)
+{
+    int level;
+
+    // The scale and level fields must be combined for the level register.
+    // For the carrier wave we always set the maximum level.
+
+    level = (data->scale & 0xc0) | (data->level & 0x3f);
+
+    if (max_level)
+    {
+        level |= 0x3f;
+    }
+
+    OPL_WriteRegister(OPL_REGS_LEVEL + operator, level);
+    OPL_WriteRegister(OPL_REGS_TREMOLO + operator, data->tremolo);
+    OPL_WriteRegister(OPL_REGS_ATTACK + operator, data->attack);
+    OPL_WriteRegister(OPL_REGS_SUSTAIN + operator, data->sustain);
+    OPL_WriteRegister(OPL_REGS_WAVEFORM + operator, data->waveform);
+}
+
+// Set the instrument for a particular voice.
+
+static void SetVoiceInstrument(opl_voice_t *voice,
+                               genmidi_instr_t *instr,
+                               unsigned int instr_voice)
+{
+    genmidi_voice_t *data;
+    unsigned int modulating;
+
+    // Instrument already set for this channel?
+
+    if (voice->current_instr == instr
+     && voice->current_instr_voice == instr_voice)
+    {
+        return;
+    }
+
+    voice->current_instr = instr;
+    voice->current_instr_voice = instr_voice;
+
+    data = &instr->voices[instr_voice];
+
+    // Are we usind modulated feedback mode?
+
+    modulating = (data->feedback & 0x01) == 0;
+
+    // Doom loads the second operator first, then the first.
+    // The carrier is set to minimum volume until the voice volume
+    // is set in SetVoiceVolume (below).  If we are not using
+    // modulating mode, we must set both to minimum volume.
+
+    LoadOperatorData(voice->op2, &data->carrier, true);
+    LoadOperatorData(voice->op1, &data->modulator, !modulating);
+
+    // Set feedback register that control the connection between the
+    // two operators.  Turn on bits in the upper nybble; I think this
+    // is for OPL3, where it turns on channel A/B.
+
+    OPL_WriteRegister(OPL_REGS_FEEDBACK + voice->index,
+                      data->feedback | 0x30);
+
+    // Hack to force a volume update.
+
+    voice->reg_volume = 999;
+}
+
+static void SetVoiceVolume(opl_voice_t *voice, unsigned int volume)
+{
+    genmidi_voice_t *opl_voice;
+    unsigned int full_volume;
+    unsigned int op_volume;
+    unsigned int reg_volume;
+
+    voice->note_volume = volume;
+
+    opl_voice = &voice->current_instr->voices[voice->current_instr_voice];
+
+    // Multiply note volume and channel volume to get the actual volume.
+
+    full_volume = (volume_mapping_table[voice->note_volume]
+                   * volume_mapping_table[voice->channel->volume]
+                   * volume_mapping_table[current_music_volume]) / (127 * 127);
+
+    // The volume of each instrument can be controlled via GENMIDI:
+
+    op_volume = 0x3f - opl_voice->carrier.level;
+
+    // The volume value to use in the register:
+
+    reg_volume = (op_volume * full_volume) / 128;
+    reg_volume = (0x3f - reg_volume) | opl_voice->carrier.scale;
+
+    // Update the volume register(s) if necessary.
+
+    if (reg_volume != voice->reg_volume)
+    {
+        voice->reg_volume = reg_volume;
+
+        OPL_WriteRegister(OPL_REGS_LEVEL + voice->op2, reg_volume);
+
+        // If we are using non-modulated feedback mode, we must set the
+        // volume for both voices.
+        // Note that the same register volume value is written for
+        // both voices, always calculated from the carrier's level
+        // value.
+
+        if ((opl_voice->feedback & 0x01) != 0)
+        {
+            OPL_WriteRegister(OPL_REGS_LEVEL + voice->op1, reg_volume);
+        }
+    }
+}
+
+// Initialize the voice table and freelist
+
+static void InitVoices(void)
+{
+    int i;
+
+    // Start with an empty free list.
+
+    voice_free_list = NULL;
+
+    // Initialize each voice.
+
+    for (i=0; i<OPL_NUM_VOICES; ++i)
+    {
+        voices[i].index = i;
+        voices[i].op1 = voice_operators[0][i];
+        voices[i].op2 = voice_operators[1][i];
+        voices[i].current_instr = NULL;
+
+        // Add this voice to the freelist.
+
+        ReleaseVoice(&voices[i]);
+    }
+}
+
+// Set music volume (0 - 127)
+
+static void I_OPL_SetMusicVolume(int volume)
+{
+    unsigned int i;
+
+    // Internal state variable.
+
+    current_music_volume = volume;
+
+    // Update the volume of all voices.
+
+    for (i=0; i<OPL_NUM_VOICES; ++i)
+    {
+        if (voices[i].channel != NULL)
+        {
+            SetVoiceVolume(&voices[i], voices[i].note_volume);
+        }
+    }
+}
+
+static void VoiceKeyOff(opl_voice_t *voice)
+{
+    OPL_WriteRegister(OPL_REGS_FREQ_2 + voice->index, voice->freq >> 8);
+}
+
+// Get the frequency that we should be using for a voice.
+
+static void KeyOffEvent(opl_track_data_t *track, midi_event_t *event)
+{
+    opl_channel_data_t *channel;
+    unsigned int key;
+    unsigned int i;
+
+/*
+    printf("note off: channel %i, %i, %i\n",
+           event->data.channel.channel,
+           event->data.channel.param1,
+           event->data.channel.param2);
+*/
+
+    channel = &track->channels[event->data.channel.channel];
+    key = event->data.channel.param1;
+
+    // Turn off voices being used to play this key.
+    // If it is a double voice instrument there will be two.
+
+    for (i=0; i<OPL_NUM_VOICES; ++i)
+    {
+        if (voices[i].channel == channel && voices[i].key == key)
+        {
+            VoiceKeyOff(&voices[i]);
+
+            // Finished with this voice now.
+
+            ReleaseVoice(&voices[i]);
+        }
+    }
+}
+
+// Compare the priorities of channels, returning either -1, 0 or 1.
+
+static int CompareChannelPriorities(opl_channel_data_t *chan1,
+                                    opl_channel_data_t *chan2)
+{
+    // TODO ...
+
+    return 1;
+}
+
+// When all voices are in use, we must discard an existing voice to
+// play a new note.  Find and free an existing voice.  The channel
+// passed to the function is the channel for the new note to be
+// played.
+
+static opl_voice_t *ReplaceExistingVoice(opl_channel_data_t *channel)
+{
+    opl_voice_t *rover;
+    opl_voice_t *result;
+
+    // Check the allocated voices, if we find an instrument that is
+    // of a lower priority to the new instrument, discard it.
+    // If a voice is being used to play the second voice of an instrument,
+    // use that, as second voices are non-essential.
+    // Lower numbered MIDI channels implicitly have a higher priority
+    // than higher-numbered channels, eg. MIDI channel 1 is never
+    // discarded for MIDI channel 2.
+
+    result = NULL;
+
+    for (rover = voice_alloced_list; rover != NULL; rover = rover->next)
+    {
+        if (rover->current_instr_voice != 0
+         || (rover->channel > channel
+             && CompareChannelPriorities(channel, rover->channel) > 0))
+        {
+            result = rover;
+            break;
+        }
+    }
+
+    // If we didn't find a voice, find an existing voice being used to
+    // play a note on the same channel, and use that.
+
+    if (result == NULL)
+    {
+        for (rover = voice_alloced_list; rover != NULL; rover = rover->next)
+        {
+            if (rover->channel == channel)
+            {
+                result = rover;
+                break;
+            }
+        }
+    }
+
+    // Still nothing found?  Give up and just use the first voice in
+    // the list.
+
+    if (result == NULL)
+    {
+        result = voice_alloced_list;
+    }
+
+    // Stop playing this voice playing and release it back to the free
+    // list.
+
+    VoiceKeyOff(result);
+    ReleaseVoice(result);
+
+    // Re-allocate the voice again and return it.
+
+    return GetFreeVoice();
+}
+
+
+static unsigned int FrequencyForVoice(opl_voice_t *voice)
+{
+    genmidi_voice_t *gm_voice;
+    unsigned int freq_index;
+    unsigned int octave;
+    unsigned int sub_index;
+    unsigned int note;
+
+    note = voice->note;
+
+    // Apply note offset.
+    // Don't apply offset if the instrument is a fixed note instrument.
+
+    gm_voice = &voice->current_instr->voices[voice->current_instr_voice];
+
+    if ((voice->current_instr->flags & GENMIDI_FLAG_FIXED) == 0)
+    {
+        note += (signed short) SHORT(gm_voice->base_note_offset);
+    }
+
+    // Avoid possible overflow due to base note offset:
+
+    if (note > 0x7f)
+    {
+        note = voice->note;
+    }
+
+    freq_index = 64 + 32 * note + voice->channel->bend;
+
+    // If this is the second voice of a double voice instrument, the
+    // frequency index can be adjusted by the fine tuning field.
+
+    if (voice->current_instr_voice != 0)
+    {
+        freq_index += (voice->current_instr->fine_tuning / 2) - 64;
+    }
+
+    // The first 7 notes use the start of the table, while
+    // consecutive notes loop around the latter part.
+
+    if (freq_index < 284)
+    {
+        return frequency_curve[freq_index];
+    }
+
+    sub_index = (freq_index - 284) % (12 * 32);
+    octave = (freq_index - 284) / (12 * 32);
+
+    // Once the seventh octave is reached, things break down.
+    // We can only go up to octave 7 as a maximum anyway (the OPL
+    // register only has three bits for octave number), but for the
+    // notes in octave 7, the first five bits have octave=7, the
+    // following notes have octave=6.  This 7/6 pattern repeats in
+    // following octaves (which are technically impossible to
+    // represent anyway).
+
+    if (octave >= 7)
+    {
+        if (sub_index < 5)
+        {
+            octave = 7;
+        }
+        else
+        {
+            octave = 6;
+        }
+    }
+
+    // Calculate the resulting register value to use for the frequency.
+
+    return frequency_curve[sub_index + 284] | (octave << 10);
+}
+
+// Update the frequency that a voice is programmed to use.
+
+static void UpdateVoiceFrequency(opl_voice_t *voice)
+{
+    unsigned int freq;
+
+    // Calculate the frequency to use for this voice and update it
+    // if neccessary.
+
+    freq = FrequencyForVoice(voice);
+
+    if (voice->freq != freq)
+    {
+        OPL_WriteRegister(OPL_REGS_FREQ_1 + voice->index, freq & 0xff);
+        OPL_WriteRegister(OPL_REGS_FREQ_2 + voice->index, (freq >> 8) | 0x20);
+
+        voice->freq = freq;
+    }
+}
+
+// Program a single voice for an instrument.  For a double voice 
+// instrument (GENMIDI_FLAG_2VOICE), this is called twice for each
+// key on event.
+
+static void VoiceKeyOn(opl_channel_data_t *channel,
+                       genmidi_instr_t *instrument,
+                       unsigned int instrument_voice,
+                       unsigned int key,
+                       unsigned int volume)
+{
+    opl_voice_t *voice;
+
+    // Find a voice to use for this new note.
+
+    voice = GetFreeVoice();
+
+    // If there are no more voices left, we must decide what to do.
+    // If this is the first voice of the instrument, free an existing
+    // voice and use that.  Otherwise, if this is the second voice,
+    // it isn't as important; just discard it.
+
+    if (voice == NULL)
+    {
+        if (instrument_voice == 0)
+        {
+            voice = ReplaceExistingVoice(channel);
+        }
+        else
+        {
+            return;
+        }
+    }
+
+    voice->channel = channel;
+    voice->key = key;
+
+    // Work out the note to use.  This is normally the same as
+    // the key, unless it is a fixed pitch instrument.
+
+    if ((instrument->flags & GENMIDI_FLAG_FIXED) != 0)
+    {
+        voice->note = instrument->fixed_note;
+    }
+    else
+    {
+        voice->note = key;
+    }
+
+    // Program the voice with the instrument data:
+
+    SetVoiceInstrument(voice, instrument, instrument_voice);
+
+    // Set the volume level.
+
+    SetVoiceVolume(voice, volume);
+
+    // Write the frequency value to turn the note on.
+
+    voice->freq = 0;
+    UpdateVoiceFrequency(voice);
+}
+
+static void KeyOnEvent(opl_track_data_t *track, midi_event_t *event)
+{
+    genmidi_instr_t *instrument;
+    opl_channel_data_t *channel;
+    unsigned int key;
+    unsigned int volume;
+
+/*
+    printf("note on: channel %i, %i, %i\n",
+           event->data.channel.channel,
+           event->data.channel.param1,
+           event->data.channel.param2);
+*/
+
+    // The channel.
+
+    channel = &track->channels[event->data.channel.channel];
+    key = event->data.channel.param1;
+    volume = event->data.channel.param2;
+
+    // Percussion channel (10) is treated differently.
+
+    if (event->data.channel.channel == 9)
+    {
+        if (key < 35 || key > 81)
+        {
+            return;
+        }
+
+        instrument = &percussion_instrs[key - 35];
+    }
+    else
+    {
+        instrument = channel->instrument;
+    }
+
+    // Find and program a voice for this instrument.  If this
+    // is a double voice instrument, we must do this twice.
+
+    VoiceKeyOn(channel, instrument, 0, key, volume);
+
+    if ((instrument->flags & GENMIDI_FLAG_2VOICE) != 0)
+    {
+        VoiceKeyOn(channel, instrument, 1, key, volume);
+    }
+}
+
+static void ProgramChangeEvent(opl_track_data_t *track, midi_event_t *event)
+{
+    int channel;
+    int instrument;
+
+    // Set the instrument used on this channel.
+
+    channel = event->data.channel.channel;
+    instrument = event->data.channel.param1;
+    track->channels[channel].instrument = &main_instrs[instrument];
+
+    // TODO: Look through existing voices that are turned on on this
+    // channel, and change the instrument.
+}
+
+static void SetChannelVolume(opl_channel_data_t *channel, unsigned int volume)
+{
+    unsigned int i;
+
+    channel->volume = volume;
+
+    // Update all voices that this channel is using.
+
+    for (i=0; i<OPL_NUM_VOICES; ++i)
+    {
+        if (voices[i].channel == channel)
+        {
+            SetVoiceVolume(&voices[i], voices[i].note_volume);
+        }
+    }
+}
+
+static void ControllerEvent(opl_track_data_t *track, midi_event_t *event)
+{
+    unsigned int controller;
+    unsigned int param;
+    opl_channel_data_t *channel;
+
+/*
+    printf("change controller: channel %i, %i, %i\n",
+           event->data.channel.channel,
+           event->data.channel.param1,
+           event->data.channel.param2);
+*/
+
+    channel = &track->channels[event->data.channel.channel];
+    controller = event->data.channel.param1;
+    param = event->data.channel.param2;
+
+    switch (controller)
+    {
+        case MIDI_CONTROLLER_MAIN_VOLUME:
+            SetChannelVolume(channel, param);
+            break;
+
+        default:
+#ifdef OPL_MIDI_DEBUG
+            fprintf(stderr, "Unknown MIDI controller type: %i\n", controller);
+#endif
+            break;
+    }
+}
+
+// Process a pitch bend event.
+
+static void PitchBendEvent(opl_track_data_t *track, midi_event_t *event)
+{
+    opl_channel_data_t *channel;
+    unsigned int i;
+
+    // Update the channel bend value.  Only the MSB of the pitch bend
+    // value is considered: this is what Doom does.
+
+    channel = &track->channels[event->data.channel.channel];
+    channel->bend = event->data.channel.param2 - 64;
+
+    // Update all voices for this channel.
+
+    for (i=0; i<OPL_NUM_VOICES; ++i)
+    {
+        if (voices[i].channel == channel)
+        {
+            UpdateVoiceFrequency(&voices[i]);
+        }
+    }
+}
+
+// Process a meta event.
+
+static void MetaEvent(opl_track_data_t *track, midi_event_t *event)
+{
+    switch (event->data.meta.type)
+    {
+        // Things we can just ignore.
+
+        case MIDI_META_SEQUENCE_NUMBER:
+        case MIDI_META_TEXT:
+        case MIDI_META_COPYRIGHT:
+        case MIDI_META_TRACK_NAME:
+        case MIDI_META_INSTR_NAME:
+        case MIDI_META_LYRICS:
+        case MIDI_META_MARKER:
+        case MIDI_META_CUE_POINT:
+        case MIDI_META_SEQUENCER_SPECIFIC:
+            break;
+
+        // End of track - actually handled when we run out of events
+        // in the track, see below.
+
+        case MIDI_META_END_OF_TRACK:
+            break;
+
+        default:
+#ifdef OPL_MIDI_DEBUG
+            fprintf(stderr, "Unknown MIDI meta event type: %i\n",
+                            event->data.meta.type);
+#endif
+            break;
+    }
+}
+
+// Process a MIDI event from a track.
+
+static void ProcessEvent(opl_track_data_t *track, midi_event_t *event)
+{
+    switch (event->event_type)
+    {
+        case MIDI_EVENT_NOTE_OFF:
+            KeyOffEvent(track, event);
+            break;
+
+        case MIDI_EVENT_NOTE_ON:
+            KeyOnEvent(track, event);
+            break;
+
+        case MIDI_EVENT_CONTROLLER:
+            ControllerEvent(track, event);
+            break;
+
+        case MIDI_EVENT_PROGRAM_CHANGE:
+            ProgramChangeEvent(track, event);
+            break;
+
+        case MIDI_EVENT_PITCH_BEND:
+            PitchBendEvent(track, event);
+            break;
+
+        case MIDI_EVENT_META:
+            MetaEvent(track, event);
+            break;
+
+        // SysEx events can be ignored.
+
+        case MIDI_EVENT_SYSEX:
+        case MIDI_EVENT_SYSEX_SPLIT:
+            break;
+
+        default:
+#ifdef OPL_MIDI_DEBUG
+            fprintf(stderr, "Unknown MIDI event type %i\n", event->event_type);
+#endif
+            break;
+    }
+}
+
+static void ScheduleTrack(opl_track_data_t *track);
+
+// Restart a song from the beginning.
+
+static void RestartSong(void)
+{
+    unsigned int i;
+
+    running_tracks = num_tracks;
+
+    for (i=0; i<num_tracks; ++i)
+    {
+        MIDI_RestartIterator(tracks[i].iter);
+        ScheduleTrack(&tracks[i]);
+    }
+}
+
+// Callback function invoked when another event needs to be read from
+// a track.
+
+static void TrackTimerCallback(void *arg)
+{
+    opl_track_data_t *track = arg;
+    midi_event_t *event;
+
+    // Get the next event and process it.
+
+    if (!MIDI_GetNextEvent(track->iter, &event))
+    {
+        return;
+    }
+
+    ProcessEvent(track, event);
+
+    // End of track?
+
+    if (event->event_type == MIDI_EVENT_META
+     && event->data.meta.type == MIDI_META_END_OF_TRACK)
+    {
+        --running_tracks;
+
+        // When all tracks have finished, restart the song.
+
+        if (running_tracks <= 0 && song_looping)
+        {
+            RestartSong();
+        }
+
+        return;
+    }
+
+    // Reschedule the callback for the next event in the track.
+
+    ScheduleTrack(track);
+}
+
+static void ScheduleTrack(opl_track_data_t *track)
+{
+    unsigned int nticks;
+    unsigned int ms;
+    static int total = 0;
+
+    // Get the number of milliseconds until the next event.
+
+    nticks = MIDI_GetDeltaTime(track->iter);
+    ms = (nticks * track->ms_per_beat) / track->ticks_per_beat;
+    total += ms;
+
+    // Set a timer to be invoked when the next event is
+    // ready to play.
+
+    OPL_SetCallback(ms, TrackTimerCallback, track);
+}
+
+// Initialize a channel.
+
+static void InitChannel(opl_track_data_t *track, opl_channel_data_t *channel)
+{
+    // TODO: Work out sensible defaults?
+
+    channel->instrument = &main_instrs[0];
+    channel->volume = 127;
+    channel->bend = 0;
+}
+
+// Start a MIDI track playing:
+
+static void StartTrack(midi_file_t *file, unsigned int track_num)
+{
+    opl_track_data_t *track;
+    unsigned int i;
+
+    track = &tracks[track_num];
+    track->iter = MIDI_IterateTrack(file, track_num);
+    track->ticks_per_beat = MIDI_GetFileTimeDivision(file);
+
+    // Default is 120 bpm.
+    // TODO: this is wrong
+
+    track->ms_per_beat = 500 * 260;
+
+    for (i=0; i<MIDI_CHANNELS_PER_TRACK; ++i)
+    {
+        InitChannel(track, &track->channels[i]);
+    }
+
+    // Schedule the first event.
+
+    ScheduleTrack(track);
+}
+
+// Start playing a mid
+
+static void I_OPL_PlaySong(void *handle, boolean looping)
+{
+    midi_file_t *file;
+    unsigned int i;
+
+    if (!music_initialized || handle == NULL)
+    {
+        return;
+    }
+
+    file = handle;
+
+    // Allocate track data.
+
+    tracks = malloc(MIDI_NumTracks(file) * sizeof(opl_track_data_t));
+
+    num_tracks = MIDI_NumTracks(file);
+    running_tracks = num_tracks;
+    song_looping = looping;
+
+    for (i=0; i<num_tracks; ++i)
+    {
+        StartTrack(file, i);
+    }
+}
+
+static void I_OPL_PauseSong(void)
+{
+    unsigned int i;
+
+    if (!music_initialized)
+    {
+        return;
+    }
+
+    // Pause OPL callbacks.
+
+    OPL_SetPaused(1);
+
+    // Turn off all main instrument voices (not percussion).
+    // This is what Vanilla does.
+
+    for (i=0; i<OPL_NUM_VOICES; ++i)
+    {
+        if (voices[i].channel != NULL
+         && voices[i].current_instr < percussion_instrs)
+        {
+            VoiceKeyOff(&voices[i]);
+        }
+    }
+}
+
+static void I_OPL_ResumeSong(void)
+{
+    if (!music_initialized)
+    {
+        return;
+    }
+
+    OPL_SetPaused(0);
+}
+
+static void I_OPL_StopSong(void)
+{
+    unsigned int i;
+
+    if (!music_initialized)
+    {
+        return;
+    }
+
+    OPL_Lock();
+
+    // Stop all playback.
+
+    OPL_ClearCallbacks();
+
+    // Free all voices.
+
+    for (i=0; i<OPL_NUM_VOICES; ++i)
+    {
+        if (voices[i].channel != NULL)
+        {
+            VoiceKeyOff(&voices[i]);
+            ReleaseVoice(&voices[i]);
+        }
+    }
+
+    // Free all track data.
+
+    for (i=0; i<num_tracks; ++i)
+    {
+        MIDI_FreeIterator(tracks[i].iter);
+    }
+
+    free(tracks);
+
+    tracks = NULL;
+    num_tracks = 0;
+
+    OPL_Unlock();
+}
+
+static void I_OPL_UnRegisterSong(void *handle)
+{
+    if (!music_initialized)
+    {
+        return;
+    }
+
+    if (handle != NULL)
+    {
+        MIDI_FreeFile(handle);
+    }
+}
+
+// Determine whether memory block is a .mid file 
+
+static boolean IsMid(byte *mem, int len)
+{
+    return len > 4 && !memcmp(mem, "MThd", 4);
+}
+
+static boolean ConvertMus(byte *musdata, int len, char *filename)
+{
+    MEMFILE *instream;
+    MEMFILE *outstream;
+    void *outbuf;
+    size_t outbuf_len;
+    int result;
+
+    instream = mem_fopen_read(musdata, len);
+    outstream = mem_fopen_write();
+
+    result = mus2mid(instream, outstream);
+
+    if (result == 0)
+    {
+        mem_get_buf(outstream, &outbuf, &outbuf_len);
+
+        M_WriteFile(filename, outbuf, outbuf_len);
+    }
+
+    mem_fclose(instream);
+    mem_fclose(outstream);
+
+    return result;
+}
+
+static void *I_OPL_RegisterSong(void *data, int len)
+{
+    midi_file_t *result;
+    char *filename;
+
+    if (!music_initialized)
+    {
+        return NULL;
+    }
+
+    // MUS files begin with "MUS"
+    // Reject anything which doesnt have this signature
+
+    filename = M_TempFile("doom.mid");
+
+    if (IsMid(data, len) && len < MAXMIDLENGTH)
+    {
+        M_WriteFile(filename, data, len);
+    }
+    else 
+    {
+	// Assume a MUS file and try to convert
+
+        ConvertMus(data, len, filename);
+    }
+
+    result = MIDI_LoadFile(filename);
+
+    if (result == NULL)
+    {
+        fprintf(stderr, "I_OPL_RegisterSong: Failed to load MID.\n");
+    }
+
+    // remove file now
+
+    remove(filename);
+
+    Z_Free(filename);
+
+    return result;
+}
+
+// Is the song playing?
+
+static boolean I_OPL_MusicIsPlaying(void)
+{
+    if (!music_initialized)
+    {
+        return false;
+    }
+
+    return num_tracks > 0;
+}
+
+// Shutdown music
+
+static void I_OPL_ShutdownMusic(void)
+{
+    if (music_initialized)
+    {
+        // Stop currently-playing track, if there is one:
+
+        I_OPL_StopSong();
+
+        OPL_Shutdown();
+
+        // Release GENMIDI lump
+
+        W_ReleaseLumpName("GENMIDI");
+
+        music_initialized = false;
+    }
+}
+
+// Initialize music subsystem
+
+static boolean I_OPL_InitMusic(void)
+{
+    OPL_SetSampleRate(snd_samplerate);
+
+    if (!OPL_Init(opl_io_port))
+    {
+        printf("Dude.  The Adlib isn't responding.\n");
+        return false;
+    }
+
+    // Load instruments from GENMIDI lump:
+
+    if (!LoadInstrumentTable())
+    {
+        OPL_Shutdown();
+        return false;
+    }
+
+    InitVoices();
+
+    tracks = NULL;
+    num_tracks = 0;
+    music_initialized = true;
+
+    return true;
+}
+
+static snddevice_t music_opl_devices[] =
+{
+    SNDDEVICE_ADLIB,
+    SNDDEVICE_SB,
+};
+
+music_module_t music_opl_module =
+{
+    music_opl_devices,
+    arrlen(music_opl_devices),
+    I_OPL_InitMusic,
+    I_OPL_ShutdownMusic,
+    I_OPL_SetMusicVolume,
+    I_OPL_PauseSong,
+    I_OPL_ResumeSong,
+    I_OPL_RegisterSong,
+    I_OPL_UnRegisterSong,
+    I_OPL_PlaySong,
+    I_OPL_StopSong,
+    I_OPL_MusicIsPlaying,
+};
+
--- a/src/i_scale.c
+++ b/src/i_scale.c
@@ -56,11 +56,11 @@
 // stretch_tables[1] : 40% / 60%
 // All other combinations can be reached from these two tables.
 
-static byte *stretch_tables[2];
+static byte *stretch_tables[2] = { NULL, NULL };
 
 // 50%/50% stretch table, for 800x600 squash mode
 
-static byte *half_stretch_table;
+static byte *half_stretch_table = NULL;
 
 // Called to set the source and destination buffers before doing the
 // scale.
@@ -367,6 +367,11 @@
 
 static void I_InitStretchTables(byte *palette)
 {
+    if (stretch_tables[0] != NULL)
+    {
+        return;
+    }
+
     // We only actually need two lookup tables:
     //
     // mix 0%   =  just write line 1
@@ -388,6 +393,11 @@
 
 static void I_InitSquashTable(byte *palette)
 {
+    if (half_stretch_table != NULL)
+    {
+        return;
+    }
+
     printf("I_InitSquashTable: Generating lookup table..");
     fflush(stdout);
     half_stretch_table = GenerateStretchTable(palette, 50);
--- a/src/i_sdlmusic.c
+++ b/src/i_sdlmusic.c
@@ -335,8 +335,6 @@
 
 static snddevice_t music_sdl_devices[] =
 {
-    SNDDEVICE_ADLIB,
-    SNDDEVICE_SB,
     SNDDEVICE_PAS,
     SNDDEVICE_GUS,
     SNDDEVICE_WAVEBLASTER,
--- a/src/i_sdlsound.c
+++ b/src/i_sdlsound.c
@@ -25,7 +25,6 @@
 //
 //-----------------------------------------------------------------------------
 
-
 #include "config.h"
 
 #include <stdio.h>
@@ -42,6 +41,7 @@
 #include "deh_str.h"
 #include "i_sound.h"
 #include "i_system.h"
+#include "i_swap.h"
 #include "m_argv.h"
 #include "w_wad.h"
 #include "z_zone.h"
@@ -49,6 +49,7 @@
 #include "doomtype.h"
 
 #define LOW_PASS_FILTER
+//#define DEBUG_DUMP_WAVS
 #define MAX_SOUND_SLICE_TIME 70 /* ms */
 #define NUM_CHANNELS 16
 
@@ -288,6 +289,56 @@
     }
 }
 
+#ifdef DEBUG_DUMP_WAVS
+
+// Debug code to dump resampled sound effects to WAV files for analysis.
+
+static void WriteWAV(char *filename, byte *data,
+                     uint32_t length, int samplerate)
+{
+    FILE *wav;
+    unsigned int i;
+    unsigned short s;
+
+    wav = fopen(filename, "wb");
+
+    // Header
+
+    fwrite("RIFF", 1, 4, wav);
+    i = LONG(36 + samplerate);
+    fwrite(&i, 4, 1, wav);
+    fwrite("WAVE", 1, 4, wav);
+
+    // Subchunk 1
+
+    fwrite("fmt ", 1, 4, wav);
+    i = LONG(16);
+    fwrite(&i, 4, 1, wav);           // Length
+    s = SHORT(1);
+    fwrite(&s, 2, 1, wav);           // Format (PCM)
+    s = SHORT(2);
+    fwrite(&s, 2, 1, wav);           // Channels (2=stereo)
+    i = LONG(samplerate);
+    fwrite(&i, 4, 1, wav);           // Sample rate
+    i = LONG(samplerate * 2 * 2);
+    fwrite(&i, 4, 1, wav);           // Byte rate (samplerate * stereo * 16 bit)
+    s = SHORT(2 * 2);
+    fwrite(&s, 2, 1, wav);           // Block align (stereo * 16 bit)
+    s = SHORT(16);
+    fwrite(&s, 2, 1, wav);           // Bits per sample (16 bit)
+
+    // Data subchunk
+
+    fwrite("data", 1, 4, wav);
+    i = LONG(length);
+    fwrite(&i, 4, 1, wav);           // Data length
+    fwrite(data, 1, length, wav);    // Data
+
+    fclose(wav);
+}
+
+#endif
+
 // Generic sound expansion function for any sample rate.
 // Returns number of clipped samples (always 0).
 
@@ -313,7 +364,7 @@
     chunk = AllocateChunk(sfxinfo, expanded_length);
 
     // If we can, use the standard / optimized SDL conversion routines.
-    
+
     if (samplerate <= mixer_freq
      && ConvertibleRatio(samplerate, mixer_freq)
      && SDL_BuildAudioCVT(&convertor,
@@ -379,9 +430,12 @@
             rc = 1.0f / (3.14f * samplerate);
             alpha = dt / (rc + dt);
 
-            for (i=1; i<expanded_length; ++i) 
+            // Both channels are processed in parallel, hence [i-2]:
+
+            for (i=2; i<expanded_length * 2; ++i)
             {
-                expanded[i] = (Sint16) (alpha * expanded[i] + (1 - alpha) * expanded[i-1]);
+                expanded[i] = (Sint16) (alpha * expanded[i]
+                                      + (1 - alpha) * expanded[i-2]);
             }
         }
 #endif /* #ifdef LOW_PASS_FILTER */
@@ -431,6 +485,16 @@
     // Sample rate conversion
 
     ExpandSoundData(sfxinfo, data + 8, samplerate, length);
+
+#ifdef DEBUG_DUMP_WAVS
+    {
+        char filename[16];
+
+        sprintf(filename, "%s.wav", DEH_String(S_sfx[sound].name));
+        WriteWAV(filename, sound_chunks[sound].abuf,
+                 sound_chunks[sound].alen, mixer_freq);
+    }
+#endif
 
     // don't need the original lump any more
   
--- a/src/i_sound.c
+++ b/src/i_sound.c
@@ -46,7 +46,7 @@
 static sound_module_t *sound_module;
 static music_module_t *music_module;
 
-int snd_musicdevice = SNDDEVICE_SB;
+int snd_musicdevice = SNDDEVICE_GENMIDI;
 int snd_sfxdevice = SNDDEVICE_SB;
 
 // Sound modules
@@ -54,7 +54,12 @@
 extern sound_module_t sound_sdl_module;
 extern sound_module_t sound_pcsound_module;
 extern music_module_t music_sdl_module;
+extern music_module_t music_opl_module;
 
+// For OPL module:
+
+extern int opl_io_port;
+
 // DOS-specific options: These are unused but should be maintained
 // so that the config file can be shared between chocolate
 // doom and doom.exe
@@ -81,6 +86,7 @@
 {
 #ifdef FEATURE_SOUND
     &music_sdl_module,
+    &music_opl_module,
 #endif
     NULL,
 };
@@ -245,7 +251,6 @@
 
 static void CheckVolumeSeparation(int *sep, int *vol)
 {
-return;
     if (*sep < 0)
     {
         *sep = 0;
@@ -407,6 +412,7 @@
     M_BindVariable("snd_sbdma",         &snd_sbdma);
     M_BindVariable("snd_mport",         &snd_mport);
     M_BindVariable("snd_samplerate",    &snd_samplerate);
+    M_BindVariable("opl_io_port",       &opl_io_port);
 #ifdef FEATURE_SOUND
     M_BindVariable("use_libsamplerate", &use_libsamplerate);
 #endif
--- a/src/i_video.c
+++ b/src/i_video.c
@@ -144,8 +144,6 @@
 static SDL_Color palette[256];
 static boolean palette_to_set;
 
-static int windowwidth, windowheight;
-
 // display has been set up?
 
 static boolean initialized = false;
@@ -155,6 +153,10 @@
 static boolean nomouse = false;
 int usemouse = 1;
 
+// Bit mask of mouse button state.
+
+static unsigned int mouse_button_state = 0;
+
 // Disallow mouse and joystick movement to cause forward/backward
 // motion.  Specified with the '-novert' command line parameter.
 // This is an int to allow saving to config file
@@ -235,6 +237,12 @@
 
 static screen_mode_t *screen_mode;
 
+// Window resize state.
+
+static boolean need_resize = false;
+static unsigned int resize_w, resize_h;
+static unsigned int last_resize_time;
+
 // If true, keyboard mapping is ignored, like in Vanilla Doom.
 // The sensible thing to do is to disable this if you have a non-US
 // keyboard.
@@ -261,6 +269,8 @@
 
 int usegamma = 0;
 
+static void ApplyWindowResize(unsigned int w, unsigned int h);
+
 static boolean MouseShouldBeGrabbed()
 {
     // never grab the mouse when in screensaver mode
@@ -524,29 +534,56 @@
 
 }
 
-static int MouseButtonState(void)
+static void UpdateMouseButtonState(unsigned int button, boolean on)
 {
-    Uint8 state;
-    int result = 0;
+    event_t event;
 
-#if SDL_VERSION_ATLEAST(1, 3, 0)
-    state = SDL_GetMouseState(0, NULL, NULL);
-#else
-    state = SDL_GetMouseState(NULL, NULL);
-#endif
+    if (button < SDL_BUTTON_LEFT || button > MAX_MOUSE_BUTTONS)
+    {
+        return;
+    }
 
     // Note: button "0" is left, button "1" is right,
     // button "2" is middle for Doom.  This is different
     // to how SDL sees things.
 
-    if (state & SDL_BUTTON(1))
-        result |= 1;
-    if (state & SDL_BUTTON(3))
-        result |= 2;
-    if (state & SDL_BUTTON(2))
-        result |= 4;
+    switch (button)
+    {
+        case SDL_BUTTON_LEFT:
+            button = 0;
+            break;
 
-    return result;
+        case SDL_BUTTON_RIGHT:
+            button = 1;
+            break;
+
+        case SDL_BUTTON_MIDDLE:
+            button = 2;
+            break;
+
+        default:
+            // SDL buttons are indexed from 1.
+            --button;
+            break;
+    }
+
+    // Turn bit representing this button on or off.
+
+    if (on)
+    {
+        mouse_button_state |= (1 << button);
+    }
+    else
+    {
+        mouse_button_state &= ~(1 << button);
+    }
+
+    // Post an event with the new button state.
+
+    event.type = ev_mouse;
+    event.data1 = mouse_button_state;
+    event.data2 = event.data3 = 0;
+    D_PostEvent(&event);
 }
 
 static int AccelerateMouse(int val)
@@ -692,7 +729,7 @@
                 /*
             case SDL_MOUSEMOTION:
                 event.type = ev_mouse;
-                event.data1 = MouseButtonState();
+                event.data1 = mouse_button_state;
                 event.data2 = AccelerateMouse(sdlevent.motion.xrel);
                 event.data3 = -AccelerateMouse(sdlevent.motion.yrel);
                 D_PostEvent(&event);
@@ -702,10 +739,7 @@
             case SDL_MOUSEBUTTONDOWN:
 		if (usemouse && !nomouse)
 		{
-                    event.type = ev_mouse;
-                    event.data1 = MouseButtonState();
-                    event.data2 = event.data3 = 0;
-                    D_PostEvent(&event);
+                    UpdateMouseButtonState(sdlevent.button.button, true);
 		}
                 break;
 
@@ -712,10 +746,7 @@
             case SDL_MOUSEBUTTONUP:
 		if (usemouse && !nomouse)
 		{
-                    event.type = ev_mouse;
-                    event.data1 = MouseButtonState();
-                    event.data2 = event.data3 = 0;
-                    D_PostEvent(&event);
+                    UpdateMouseButtonState(sdlevent.button.button, false);
 		}
                 break;
 
@@ -733,6 +764,13 @@
                 palette_to_set = true;
                 break;
 
+            case SDL_RESIZABLE:
+                need_resize = true;
+                resize_w = sdlevent.resize.w;
+                resize_h = sdlevent.resize.h;
+                last_resize_time = SDL_GetTicks();
+                break;
+
             default:
                 break;
         }
@@ -777,7 +815,7 @@
     if (x != 0 || y != 0) 
     {
         ev.type = ev_mouse;
-        ev.data1 = MouseButtonState();
+        ev.data1 = mouse_button_state;
         ev.data2 = AccelerateMouse(x);
 
         if (!novert)
@@ -976,7 +1014,6 @@
     static int	lasttic;
     int		tics;
     int		i;
-    // UNUSED static unsigned char *bigscreen=0;
 
     if (!initialized)
         return;
@@ -983,7 +1020,14 @@
 
     if (noblit)
         return;
-    
+
+    if (need_resize && SDL_GetTicks() > last_resize_time + 500)
+    {
+        ApplyWindowResize(resize_w, resize_h);
+        need_resize = false;
+        palette_to_set = true;
+    }
+
     UpdateGrab();
 
     // Don't update the screen if the window isn't visible.
@@ -1692,11 +1736,96 @@
     }
 }
 
+static void SetVideoMode(screen_mode_t *mode, int w, int h)
+{
+    byte *doompal;
+    int flags = 0;
+
+    doompal = W_CacheLumpName(DEH_String("PLAYPAL"), PU_CACHE);
+
+    // Generate lookup tables before setting the video mode.
+
+    if (mode != NULL && mode->InitMode != NULL)
+    {
+        mode->InitMode(doompal);
+    }
+
+    // Set the video mode.
+
+    flags |= SDL_SWSURFACE | SDL_HWPALETTE | SDL_DOUBLEBUF;
+
+    if (fullscreen)
+    {
+        flags |= SDL_FULLSCREEN;
+    }
+    else
+    {
+        flags |= SDL_RESIZABLE;
+    }
+
+    screen = SDL_SetVideoMode(w, h, 8, flags);
+
+    if (screen == NULL)
+    {
+        I_Error("Error setting video mode: %s\n", SDL_GetError());
+    }
+
+    // If mode was not set, it must be set now that we know the
+    // screen size.
+
+    if (mode == NULL)
+    {
+        mode = I_FindScreenMode(screen->w, screen->h);
+
+        if (mode == NULL)
+        {
+            I_Error("I_InitGraphics: Unable to find a screen mode small "
+                    "enough for %ix%i", screen->w, screen->h);
+        }
+
+        // Generate lookup tables before setting the video mode.
+
+        if (mode->InitMode != NULL)
+        {
+            mode->InitMode(doompal);
+        }
+    }
+
+    // Save screen mode.
+
+    screen_mode = mode;
+}
+
+static void ApplyWindowResize(unsigned int w, unsigned int h)
+{
+    screen_mode_t *mode;
+
+    // Find the biggest screen mode that will fall within these
+    // dimensions, falling back to the smallest mode possible if
+    // none is found.
+
+    mode = I_FindScreenMode(w, h);
+
+    if (mode == NULL)
+    {
+        mode = I_FindScreenMode(SCREENWIDTH, SCREENHEIGHT);
+    }
+
+    // Reset mode to resize window.
+
+    printf("Resize to %ix%i\n", mode->width, mode->height);
+    SetVideoMode(mode, mode->width, mode->height);
+
+    // Save settings.
+
+    screen_width = mode->width;
+    screen_height = mode->height;
+}
+
 void I_InitGraphics(void)
 {
     SDL_Event dummy;
     byte *doompal;
-    int flags = 0;
     char *env;
 
     // Pass through the XSCREENSAVER_WINDOW environment variable to 
@@ -1727,72 +1856,55 @@
 
     CheckCommandLine();
 
-    doompal = W_CacheLumpName (DEH_String("PLAYPAL"),PU_CACHE);
+    // Set up title and icon.  Windows cares about the ordering; this
+    // has to be done before the call to SDL_SetVideoMode.
 
+    I_InitWindowTitle();
+#if !SDL_VERSION_ATLEAST(1, 3, 0)
+    I_InitWindowIcon();
+#endif
+
+    //
+    // Enter into graphics mode.
+    //
+    // When in screensaver mode, run full screen and auto detect
+    // screen dimensions (don't change video mode)
+    //
+
     if (screensaver_mode)
     {
-        windowwidth = 0;
-        windowheight = 0;
+        SetVideoMode(NULL, 0, 0);
     }
     else
     {
+        int w, h;
+
         if (autoadjust_video_settings)
         {
             I_AutoAdjustSettings();
         }
 
-        windowwidth = screen_width;
-        windowheight = screen_height;
+        w = screen_width;
+        h = screen_height;
 
-        screen_mode = I_FindScreenMode(windowwidth, windowheight);
+        screen_mode = I_FindScreenMode(w, h);
 
         if (screen_mode == NULL)
         {
             I_Error("I_InitGraphics: Unable to find a screen mode small "
-                    "enough for %ix%i", windowwidth, windowheight);
+                    "enough for %ix%i", w, h);
         }
 
-        if (windowwidth != screen_mode->width
-         || windowheight != screen_mode->height)
+        if (w != screen_mode->width || h != screen_mode->height)
         {
             printf("I_InitGraphics: %s (%ix%i within %ix%i)\n",
-                   WindowBoxType(screen_mode, windowwidth, windowheight),
-                   screen_mode->width, screen_mode->height,
-                   windowwidth, windowheight);
+                   WindowBoxType(screen_mode, w, h),
+                   screen_mode->width, screen_mode->height, w, h);
         }
 
-        // Generate lookup tables before setting the video mode.
-
-        if (screen_mode->InitMode != NULL)
-        {
-            screen_mode->InitMode(doompal);
-        }
+        SetVideoMode(screen_mode, w, h);
     }
 
-    // Set up title and icon.  Windows cares about the ordering; this
-    // has to be done before the call to SDL_SetVideoMode.
-
-    I_InitWindowTitle();
-#if !SDL_VERSION_ATLEAST(1, 3, 0)
-    I_InitWindowIcon();
-#endif
-
-    // Set the video mode.
-
-    flags |= SDL_SWSURFACE | SDL_HWPALETTE | SDL_DOUBLEBUF;
-
-    if (fullscreen)
-    {
-        flags |= SDL_FULLSCREEN;
-    }
-
-    screen = SDL_SetVideoMode(windowwidth, windowheight, 8, flags);
-
-    if (screen == NULL)
-    {
-        I_Error("Error setting video mode: %s\n", SDL_GetError());
-    }
-
     // Start with a clear black screen
     // (screen will be flipped after we set the palette)
 
@@ -1811,6 +1923,7 @@
 
     // Set the palette
 
+    doompal = W_CacheLumpName(DEH_String("PLAYPAL"), PU_CACHE);
     I_SetPalette(doompal);
     SDL_SetColors(screen, palette, 0, 256);
 
@@ -1819,26 +1932,6 @@
     UpdateFocus();
     UpdateGrab();
 
-    // In screensaver mode, now find a screen_mode to use.
-
-    if (screensaver_mode)
-    {
-        screen_mode = I_FindScreenMode(screen->w, screen->h);
-
-        if (screen_mode == NULL)
-        {
-            I_Error("I_InitGraphics: Unable to find a screen mode small "
-                    "enough for %ix%i", screen->w, screen->h);
-        }
-
-        // Generate lookup tables before setting the video mode.
-
-        if (screen_mode->InitMode != NULL)
-        {
-            screen_mode->InitMode(doompal);
-        }
-    }
-    
     // On some systems, it takes a second or so for the screen to settle
     // after changing modes.  We include the option to add a delay when
     // setting the screen mode, so that the game doesn't start immediately
@@ -1854,12 +1947,12 @@
     // Likewise if the screen pitch is not the same as the width
     // If we have to multiply, drawing is done to a separate 320x200 buf
 
-    native_surface = !SDL_MUSTLOCK(screen) 
+    native_surface = !SDL_MUSTLOCK(screen)
                   && screen_mode == &mode_scale_1x
                   && screen->pitch == SCREENWIDTH
                   && aspect_ratio_correct;
 
-    // If not, allocate a buffer and copy from that buffer to the 
+    // If not, allocate a buffer and copy from that buffer to the
     // screen when we do an update
 
     if (native_surface)
--- a/src/i_video.h
+++ b/src/i_video.h
@@ -43,7 +43,9 @@
 
 #define SCREENHEIGHT_4_3 240
 
-typedef struct 
+#define MAX_MOUSE_BUTTONS 8
+
+typedef struct
 {
     // Screen width and height
 
--- a/src/m_argv.c
+++ b/src/m_argv.c
@@ -101,7 +101,7 @@
     size = M_FileLength(handle);
 
     // Read in the entire file
-    // Allocate one byte extra - this is incase there is an argument
+    // Allocate one byte extra - this is in case there is an argument
     // at the end of the response file, in which case a '\0' will be
     // needed.
 
--- a/src/m_config.c
+++ b/src/m_config.c
@@ -60,6 +60,7 @@
 typedef enum 
 {
     DEFAULT_INT,
+    DEFAULT_INT_HEX,
     DEFAULT_STRING,
     DEFAULT_FLOAT,
     DEFAULT_KEY,
@@ -99,14 +100,19 @@
     char *filename;
 } default_collection_t;
 
+#define CONFIG_VARIABLE_GENERIC(name, type) \
+    { #name, NULL, type, 0, 0, false }
+
 #define CONFIG_VARIABLE_KEY(name) \
-    { #name, NULL, DEFAULT_KEY, 0, 0, false }
+    CONFIG_VARIABLE_GENERIC(name, DEFAULT_KEY)
 #define CONFIG_VARIABLE_INT(name) \
-    { #name, NULL, DEFAULT_INT, 0, 0, false }
+    CONFIG_VARIABLE_GENERIC(name, DEFAULT_INT)
+#define CONFIG_VARIABLE_INT_HEX(name) \
+    CONFIG_VARIABLE_GENERIC(name, DEFAULT_INT_HEX)
 #define CONFIG_VARIABLE_FLOAT(name) \
-    { #name, NULL, DEFAULT_FLOAT, 0, 0, false }
+    CONFIG_VARIABLE_GENERIC(name, DEFAULT_FLOAT)
 #define CONFIG_VARIABLE_STRING(name) \
-    { #name, NULL, DEFAULT_STRING, 0, 0, false }
+    CONFIG_VARIABLE_GENERIC(name, DEFAULT_STRING)
 
 //! @begin_config_file default.cfg
 
@@ -664,7 +670,7 @@
 
     //!
     // Mouse acceleration threshold.  When the speed of mouse movement
-    // exceeds this threshold value, the speed is multiplied by an 
+    // exceeds this threshold value, the speed is multiplied by an
     // acceleration factor (mouse_acceleration).
     //
 
@@ -671,7 +677,7 @@
     CONFIG_VARIABLE_INT(mouse_threshold),
 
     //!
-    // Sound output sample rate, in Hz.  Typical values to use are 
+    // Sound output sample rate, in Hz.  Typical values to use are
     // 11025, 22050, 44100 and 48000.
     //
 
@@ -678,6 +684,13 @@
     CONFIG_VARIABLE_INT(snd_samplerate),
 
     //!
+    // The I/O port to use to access the OPL chip.  Only relevant when
+    // using native OPL music playback.
+    //
+
+    CONFIG_VARIABLE_INT_HEX(opl_io_port),
+
+    //!
     // If non-zero, the ENDOOM screen is displayed when exiting the
     // game.  If zero, the ENDOOM screen is not displayed.
     //
@@ -772,6 +785,18 @@
     CONFIG_VARIABLE_INT(joyb_straferight),
 
     //!
+    // Joystick button to cycle to the previous weapon.
+    //
+
+    CONFIG_VARIABLE_INT(joyb_prevweapon),
+
+    //!
+    // Joystick button to cycle to the next weapon.
+    //
+
+    CONFIG_VARIABLE_INT(joyb_nextweapon),
+
+    //!
     // Mouse button to strafe left.
     //
 
@@ -796,6 +821,18 @@
     CONFIG_VARIABLE_INT(mouseb_backward),
 
     //!
+    // Mouse button to cycle to the previous weapon.
+    //
+
+    CONFIG_VARIABLE_INT(mouseb_prevweapon),
+
+    //!
+    // Mouse button to cycle to the next weapon.
+    //
+
+    CONFIG_VARIABLE_INT(mouseb_nextweapon),
+
+    //!
     // If non-zero, double-clicking a mouse button acts like pressing
     // the "use" key to use an object in-game, eg. a door or switch.
     //
@@ -948,6 +985,12 @@
     CONFIG_VARIABLE_KEY(key_menu_gamma),
 
     //!
+    // Keyboard shortcut to switch view in multiplayer.
+    //
+
+    CONFIG_VARIABLE_KEY(key_spy),
+
+    //!
     // Keyboard shortcut to increase the screen size.
     //
 
@@ -1080,10 +1123,82 @@
     CONFIG_VARIABLE_KEY(key_weapon8),
 
     //!
+    // Key to cycle to the previous weapon.
+    //
+
+    CONFIG_VARIABLE_KEY(key_prevweapon),
+
+    //!
+    // Key to cycle to the next weapon.
+    //
+
+    CONFIG_VARIABLE_KEY(key_nextweapon),
+
+    //!
     // Key to re-display last message.
     //
 
     CONFIG_VARIABLE_KEY(key_message_refresh),
+
+    //!
+    // Key to quit the game when recording a demo.
+    //
+
+    CONFIG_VARIABLE_KEY(key_demo_quit),
+
+    //!
+    // Key to send a message during multiplayer games.
+    //
+
+    CONFIG_VARIABLE_KEY(key_multi_msg),
+
+    //!
+    // Key to send a message to player 1 during multiplayer games.
+    //
+
+    CONFIG_VARIABLE_KEY(key_multi_msgplayer1),
+
+    //!
+    // Key to send a message to player 2 during multiplayer games.
+    //
+
+    CONFIG_VARIABLE_KEY(key_multi_msgplayer2),
+
+    //!
+    // Key to send a message to player 3 during multiplayer games.
+    //
+
+    CONFIG_VARIABLE_KEY(key_multi_msgplayer3),
+
+    //!
+    // Key to send a message to player 4 during multiplayer games.
+    //
+
+    CONFIG_VARIABLE_KEY(key_multi_msgplayer4),
+
+    //!
+    // Key to send a message to player 5 during multiplayer games.
+    //
+
+    CONFIG_VARIABLE_KEY(key_multi_msgplayer5),
+
+    //!
+    // Key to send a message to player 6 during multiplayer games.
+    //
+
+    CONFIG_VARIABLE_KEY(key_multi_msgplayer6),
+
+    //!
+    // Key to send a message to player 7 during multiplayer games.
+    //
+
+    CONFIG_VARIABLE_KEY(key_multi_msgplayer7),
+
+    //!
+    // Key to send a message to player 8 during multiplayer games.
+    //
+
+    CONFIG_VARIABLE_KEY(key_multi_msgplayer8)
 };
 
 static default_collection_t extra_defaults =
@@ -1205,6 +1320,10 @@
 	        fprintf(f, "%i", * (int *) defaults[i].location);
                 break;
 
+            case DEFAULT_INT_HEX:
+	        fprintf(f, "0x%x", * (int *) defaults[i].location);
+                break;
+
             case DEFAULT_FLOAT:
                 fprintf(f, "%f", * (float *) defaults[i].location);
                 break;
@@ -1294,6 +1413,7 @@
                 break;
 
             case DEFAULT_INT:
+            case DEFAULT_INT_HEX:
                 * (int *) def->location = ParseIntParameter(strparm);
                 break;
 
--- a/src/m_controls.c
+++ b/src/m_controls.c
@@ -22,6 +22,8 @@
 //
 //-----------------------------------------------------------------------------
 
+#include <stdio.h>
+
 #include "doomtype.h"
 #include "doomkeys.h"
 
@@ -100,9 +102,20 @@
 int mousebbackward = -1;
 int mousebuse = -1;
 
+int mousebprevweapon = -1;
+int mousebnextweapon = -1;
+
+
 int key_message_refresh = KEY_ENTER;
 int key_pause = KEY_PAUSE;
+int key_demo_quit = 'q';
+int key_spy = KEY_F12;
 
+// Multiplayer chat keys:
+
+int key_multi_msg = 't';
+int key_multi_msgplayer[8];
+
 // Weapon selection keys:
 
 int key_weapon1 = '1';
@@ -113,8 +126,10 @@
 int key_weapon6 = '6';
 int key_weapon7 = '7';
 int key_weapon8 = '8';
+int key_prevweapon = 0;
+int key_nextweapon = 0;
 
-// Map controls keys:
+// Map control keys:
 
 int key_map_north     = KEY_UPARROW;
 int key_map_south     = KEY_DOWNARROW;
@@ -170,6 +185,9 @@
 
 int joybjump = -1;
 
+int joybprevweapon = -1;
+int joybnextweapon = -1;
+
 // Control whether if a mouse button is double clicked, it acts like 
 // "use" has been pressed
 
@@ -202,7 +220,7 @@
     M_BindVariable("joyb_speed",         &joybspeed),
 
     // Extra controls that are not in the Vanilla versions:
-  
+
     M_BindVariable("joyb_strafeleft",    &joybstrafeleft);
     M_BindVariable("joyb_straferight",   &joybstraferight);
     M_BindVariable("mouseb_strafeleft",  &mousebstrafeleft);
@@ -278,6 +296,15 @@
     M_BindVariable("key_weapon6",        &key_weapon6);
     M_BindVariable("key_weapon7",        &key_weapon7);
     M_BindVariable("key_weapon8",        &key_weapon8);
+
+    M_BindVariable("key_prevweapon",     &key_prevweapon);
+    M_BindVariable("key_nextweapon",     &key_nextweapon);
+
+    M_BindVariable("joyb_prevweapon",    &joybprevweapon);
+    M_BindVariable("joyb_nextweapon",    &joybnextweapon);
+
+    M_BindVariable("mouseb_prevweapon",  &mousebprevweapon);
+    M_BindVariable("mouseb_nextweapon",  &mousebnextweapon);
 }
 
 void M_BindMapControls(void)
@@ -322,6 +349,22 @@
 
     M_BindVariable("key_menu_incscreen", &key_menu_incscreen);
     M_BindVariable("key_menu_decscreen", &key_menu_decscreen);
+    M_BindVariable("key_demo_quit",      &key_demo_quit);
+    M_BindVariable("key_spy",            &key_spy);
+}
+
+void M_BindChatControls(unsigned int num_players)
+{
+    char name[20];
+    int i;
+
+    M_BindVariable("key_multi_msg",     &key_multi_msg);
+
+    for (i=0; i<num_players; ++i)
+    {
+        sprintf(name, "key_multi_msgplayer%i", i + 1);
+        M_BindVariable(name, &key_multi_msgplayer[i]);
+    }
 }
 
 #ifdef _WIN32_WCE
--- a/src/m_controls.h
+++ b/src/m_controls.h
@@ -63,6 +63,9 @@
 extern int key_message_refresh;
 extern int key_pause;
 
+extern int key_multi_msg;
+extern int key_multi_msgplayer[8];
+
 extern int key_weapon1;
 extern int key_weapon2;
 extern int key_weapon3;
@@ -72,6 +75,11 @@
 extern int key_weapon7;
 extern int key_weapon8;
 
+extern int key_demo_quit;
+extern int key_spy;
+extern int key_prevweapon;
+extern int key_nextweapon;
+
 extern int key_map_north;
 extern int key_map_south;
 extern int key_map_east;
@@ -136,6 +144,9 @@
 extern int mousebbackward;
 extern int mousebuse;
 
+extern int mousebprevweapon;
+extern int mousebnextweapon;
+
 extern int joybfire;
 extern int joybstrafe;
 extern int joybuse;
@@ -146,8 +157,11 @@
 extern int joybstrafeleft;
 extern int joybstraferight;
 
+extern int joybprevweapon;
+extern int joybnextweapon;
+
 extern int dclick_use;
- 
+
 void M_BindBaseControls(void);
 void M_BindHereticControls(void);
 void M_BindHexenControls(void);
@@ -155,6 +169,7 @@
 void M_BindWeaponControls(void);
 void M_BindMapControls(void);
 void M_BindMenuControls(void);
+void M_BindChatControls(unsigned int num_players);
 
 void M_ApplyPlatformDefaults(void);
 
--- a/src/m_misc.c
+++ b/src/m_misc.c
@@ -255,3 +255,37 @@
     }
 }
 
+//
+// M_StrCaseStr
+//
+// Case-insensitive version of strstr()
+//
+
+char *M_StrCaseStr(char *haystack, char *needle)
+{
+    unsigned int haystack_len;
+    unsigned int needle_len;
+    unsigned int len;
+    unsigned int i;
+
+    haystack_len = strlen(haystack);
+    needle_len = strlen(needle);
+
+    if (haystack_len < needle_len)
+    {
+        return NULL;
+    }
+
+    len = haystack_len - needle_len;
+
+    for (i = 0; i <= len; ++i)
+    {
+        if (!strncasecmp(haystack + i, needle, needle_len))
+        {
+            return haystack + i;
+        }
+    }
+
+    return NULL;
+}
+
--- a/src/m_misc.h
+++ b/src/m_misc.h
@@ -42,7 +42,7 @@
 boolean M_StrToInt(const char *str, int *result);
 void M_ExtractFileBase(char *path, char *dest);
 void M_ForceUppercase(char *text);
-
+char *M_StrCaseStr(char *haystack, char *needle);
 
 #endif
 
--- /dev/null
+++ b/src/midifile.c
@@ -1,0 +1,814 @@
+// Emacs style mode select   -*- C++ -*- 
+//-----------------------------------------------------------------------------
+//
+// 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.
+//
+// DESCRIPTION:
+//    Reading of MIDI files.
+//
+//-----------------------------------------------------------------------------
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+
+#include "doomtype.h"
+#include "i_swap.h"
+#include "midifile.h"
+
+#define HEADER_CHUNK_ID "MThd"
+#define TRACK_CHUNK_ID  "MTrk"
+#define MAX_BUFFER_SIZE 0x10000
+
+typedef struct
+{
+    byte chunk_id[4];
+    unsigned int chunk_size;
+} PACKEDATTR chunk_header_t;
+
+typedef struct
+{
+    chunk_header_t chunk_header;
+    unsigned short format_type;
+    unsigned short num_tracks;
+    unsigned short time_division;
+} PACKEDATTR midi_header_t;
+
+typedef struct
+{
+    // Length in bytes:
+
+    unsigned int data_len;
+
+    // Events in this track:
+
+    midi_event_t *events;
+    int num_events;
+} midi_track_t;
+
+struct midi_track_iter_s
+{
+    midi_track_t *track;
+    unsigned int position;
+};
+
+struct midi_file_s
+{
+    midi_header_t header;
+
+    // All tracks in this file:
+    midi_track_t *tracks;
+    unsigned int num_tracks;
+
+    // Data buffer used to store data read for SysEx or meta events:
+    byte *buffer;
+    unsigned int buffer_size;
+};
+
+// Check the header of a chunk:
+
+static boolean CheckChunkHeader(chunk_header_t *chunk,
+                                char *expected_id)
+{
+    boolean result;
+    
+    result = (memcmp((char *) chunk->chunk_id, expected_id, 4) == 0);
+
+    if (!result)
+    {
+        fprintf(stderr, "CheckChunkHeader: Expected '%s' chunk header, "
+                        "got '%c%c%c%c'\n",
+                        expected_id,
+                        chunk->chunk_id[0], chunk->chunk_id[1],
+                        chunk->chunk_id[2], chunk->chunk_id[3]);
+    }
+
+    return result;
+}
+
+// Read a single byte.  Returns false on error.
+
+static boolean ReadByte(byte *result, FILE *stream)
+{
+    int c;
+
+    c = fgetc(stream);
+
+    if (c == EOF)
+    {
+        fprintf(stderr, "ReadByte: Unexpected end of file\n");
+        return false;
+    }
+    else
+    {
+        *result = (byte) c;
+
+        return true;
+    }
+}
+
+// Read a variable-length value.
+
+static boolean ReadVariableLength(unsigned int *result, FILE *stream)
+{
+    int i;
+    byte b;
+
+    *result = 0;
+
+    for (i=0; i<4; ++i)
+    {
+        if (!ReadByte(&b, stream))
+        {
+            fprintf(stderr, "ReadVariableLength: Error while reading "
+                            "variable-length value\n");
+            return false;
+        }
+
+        // Insert the bottom seven bits from this byte.
+
+        *result <<= 7;
+        *result |= b & 0x7f;
+
+        // If the top bit is not set, this is the end.
+
+        if ((b & 0x80) == 0)
+        {
+            return true;
+        }
+    }
+
+    fprintf(stderr, "ReadVariableLength: Variable-length value too "
+                    "long: maximum of four bytes\n");
+    return false;
+}
+
+// Read a byte sequence into the data buffer.
+
+static void *ReadByteSequence(unsigned int num_bytes, FILE *stream)
+{
+    unsigned int i;
+    byte *result;
+
+    // Allocate a buffer:
+
+    result = malloc(num_bytes);
+
+    if (result == NULL)
+    {
+        fprintf(stderr, "ReadByteSequence: Failed to allocate buffer\n");
+        return NULL;
+    }
+
+    // Read the data:
+
+    for (i=0; i<num_bytes; ++i)
+    {
+        if (!ReadByte(&result[i], stream))
+        {
+            fprintf(stderr, "ReadByteSequence: Error while reading byte %u\n",
+                            i);
+            free(result);
+            return NULL;
+        }
+    }
+
+    return result;
+}
+
+// Read a MIDI channel event.
+// two_param indicates that the event type takes two parameters
+// (three byte) otherwise it is single parameter (two byte)
+
+static boolean ReadChannelEvent(midi_event_t *event,
+                                byte event_type, boolean two_param,
+                                FILE *stream)
+{
+    byte b;
+
+    // Set basics:
+
+    event->event_type = event_type & 0xf0;
+    event->data.channel.channel = event_type & 0x0f;
+
+    // Read parameters:
+
+    if (!ReadByte(&b, stream))
+    {
+        fprintf(stderr, "ReadChannelEvent: Error while reading channel "
+                        "event parameters\n");
+        return false;
+    }
+
+    event->data.channel.param1 = b;
+
+    // Second parameter:
+
+    if (two_param)
+    {
+        if (!ReadByte(&b, stream))
+        {
+            fprintf(stderr, "ReadChannelEvent: Error while reading channel "
+                            "event parameters\n");
+            return false;
+        }
+
+        event->data.channel.param2 = b;
+    }
+
+    return true;
+}
+
+// Read sysex event:
+
+static boolean ReadSysExEvent(midi_event_t *event, int event_type,
+                              FILE *stream)
+{
+    event->event_type = event_type;
+
+    if (!ReadVariableLength(&event->data.sysex.length, stream))
+    {
+        fprintf(stderr, "ReadSysExEvent: Failed to read length of "
+                                        "SysEx block\n");
+        return false;
+    }
+
+    // Read the byte sequence:
+
+    event->data.sysex.data = ReadByteSequence(event->data.sysex.length, stream);
+
+    if (event->data.sysex.data == NULL)
+    {
+        fprintf(stderr, "ReadSysExEvent: Failed while reading SysEx event\n");
+        return false;
+    }
+
+    return true;
+}
+
+// Read meta event:
+
+static boolean ReadMetaEvent(midi_event_t *event, FILE *stream)
+{
+    byte b;
+
+    event->event_type = MIDI_EVENT_META;
+
+    // Read meta event type:
+
+    if (!ReadByte(&b, stream))
+    {
+        fprintf(stderr, "ReadMetaEvent: Failed to read meta event type\n");
+        return false;
+    }
+
+    event->data.meta.type = b;
+
+    // Read length of meta event data:
+
+    if (!ReadVariableLength(&event->data.meta.length, stream))
+    {
+        fprintf(stderr, "ReadSysExEvent: Failed to read length of "
+                                        "SysEx block\n");
+        return false;
+    }
+
+    // Read the byte sequence:
+
+    event->data.meta.data = ReadByteSequence(event->data.meta.length, stream);
+
+    if (event->data.meta.data == NULL)
+    {
+        fprintf(stderr, "ReadSysExEvent: Failed while reading SysEx event\n");
+        return false;
+    }
+
+    return true;
+}
+
+static boolean ReadEvent(midi_event_t *event, unsigned int *last_event_type,
+                         FILE *stream)
+{
+    byte event_type;
+
+    if (!ReadVariableLength(&event->delta_time, stream))
+    {
+        fprintf(stderr, "ReadEvent: Failed to read event timestamp\n");
+        return false;
+    }
+
+    if (!ReadByte(&event_type, stream))
+    {
+        fprintf(stderr, "ReadEvent: Failed to read event type\n");
+        return false;
+    }
+
+    // All event types have their top bit set.  Therefore, if 
+    // the top bit is not set, it is because we are using the "same
+    // as previous event type" shortcut to save a byte.  Skip back
+    // a byte so that we read this byte again.
+
+    if ((event_type & 0x80) == 0)
+    {
+        event_type = *last_event_type;
+
+        if (fseek(stream, -1, SEEK_CUR) < 0)
+        {
+            fprintf(stderr, "ReadEvent: Unable to seek in stream\n");
+            return false;
+        }
+    }
+    else
+    {
+        *last_event_type = event_type;
+    }
+
+    // Check event type:
+
+    switch (event_type & 0xf0)
+    {
+        // Two parameter channel events:
+
+        case MIDI_EVENT_NOTE_OFF:
+        case MIDI_EVENT_NOTE_ON:
+        case MIDI_EVENT_AFTERTOUCH:
+        case MIDI_EVENT_CONTROLLER:
+        case MIDI_EVENT_PITCH_BEND:
+            return ReadChannelEvent(event, event_type, true, stream);
+
+        // Single parameter channel events:
+
+        case MIDI_EVENT_PROGRAM_CHANGE:
+        case MIDI_EVENT_CHAN_AFTERTOUCH:
+            return ReadChannelEvent(event, event_type, false, stream);
+
+        default:
+            break;
+    }
+
+    // Specific value?
+
+    switch (event_type)
+    {
+        case MIDI_EVENT_SYSEX:
+        case MIDI_EVENT_SYSEX_SPLIT:
+            return ReadSysExEvent(event, event_type, stream);
+
+        case MIDI_EVENT_META:
+            return ReadMetaEvent(event, stream);
+
+        default:
+            break;
+    }
+
+    fprintf(stderr, "ReadEvent: Unknown MIDI event type: 0x%x\n", event_type);
+    return false;
+}
+
+// Free an event:
+
+static void FreeEvent(midi_event_t *event)
+{
+    // Some event types have dynamically allocated buffers assigned
+    // to them that must be freed.
+
+    switch (event->event_type)
+    {
+        case MIDI_EVENT_SYSEX:
+        case MIDI_EVENT_SYSEX_SPLIT:
+            free(event->data.sysex.data);
+            break;
+
+        case MIDI_EVENT_META:
+            free(event->data.meta.data);
+            break;
+
+        default:
+            // Nothing to do.
+            break;
+    }
+}
+
+// Read and check the track chunk header
+
+static boolean ReadTrackHeader(midi_track_t *track, FILE *stream)
+{
+    size_t records_read;
+    chunk_header_t chunk_header;
+
+    records_read = fread(&chunk_header, sizeof(chunk_header_t), 1, stream);
+
+    if (records_read < 1)
+    {
+        return false;
+    }
+
+    if (!CheckChunkHeader(&chunk_header, TRACK_CHUNK_ID))
+    {
+        return false;
+    }
+
+    track->data_len = SDL_SwapBE32(chunk_header.chunk_size);
+
+    return true;
+}
+
+static boolean ReadTrack(midi_track_t *track, FILE *stream)
+{
+    midi_event_t *new_events;
+    midi_event_t *event;
+    unsigned int last_event_type;
+
+    track->num_events = 0;
+    track->events = NULL;
+
+    // Read the header:
+
+    if (!ReadTrackHeader(track, stream))
+    {
+        return false;
+    }
+
+    // Then the events:
+
+    last_event_type = 0;
+
+    for (;;)
+    {
+        // Resize the track slightly larger to hold another event:
+
+        new_events = realloc(track->events, 
+                             sizeof(midi_event_t) * (track->num_events + 1));
+
+        if (new_events == NULL)
+        {
+            return false;
+        }
+
+        track->events = new_events;
+
+        // Read the next event:
+
+        event = &track->events[track->num_events];
+        if (!ReadEvent(event, &last_event_type, stream))
+        {
+            return false;
+        }
+
+        ++track->num_events;
+
+        // End of track?
+
+        if (event->event_type == MIDI_EVENT_META
+         && event->data.meta.type == MIDI_META_END_OF_TRACK)
+        {
+            break;
+        }
+    }
+
+    return true;
+}
+
+// Free a track:
+
+static void FreeTrack(midi_track_t *track)
+{
+    unsigned int i;
+
+    for (i=0; i<track->num_events; ++i)
+    {
+        FreeEvent(&track->events[i]);
+    }
+
+    free(track->events);
+}
+
+static boolean ReadAllTracks(midi_file_t *file, FILE *stream)
+{
+    unsigned int i;
+
+    // Allocate list of tracks and read each track:
+
+    file->tracks = malloc(sizeof(midi_track_t) * file->num_tracks);
+
+    if (file->tracks == NULL)
+    {
+        return false;
+    }
+
+    memset(file->tracks, 0, sizeof(midi_track_t) * file->num_tracks);
+
+    // Read each track:
+
+    for (i=0; i<file->num_tracks; ++i)
+    {
+        if (!ReadTrack(&file->tracks[i], stream))
+        {
+            return false;
+        }
+    }
+
+    return true;
+}
+
+// Read and check the header chunk.
+
+static boolean ReadFileHeader(midi_file_t *file, FILE *stream)
+{
+    size_t records_read;
+    unsigned int format_type;
+
+    records_read = fread(&file->header, sizeof(midi_header_t), 1, stream);
+
+    if (records_read < 1)
+    {
+        return false;
+    }
+
+    if (!CheckChunkHeader(&file->header.chunk_header, HEADER_CHUNK_ID)
+     || SDL_SwapBE32(file->header.chunk_header.chunk_size) != 6)
+    {
+        fprintf(stderr, "ReadFileHeader: Invalid MIDI chunk header! "
+                        "chunk_size=%i\n",
+                        SDL_SwapBE32(file->header.chunk_header.chunk_size));
+        return false;
+    }
+
+    format_type = SDL_SwapBE16(file->header.format_type);
+    file->num_tracks = SDL_SwapBE16(file->header.num_tracks);
+
+    if ((format_type != 0 && format_type != 1)
+     || file->num_tracks < 1)
+    {
+        fprintf(stderr, "ReadFileHeader: Only type 0/1 "
+                                         "MIDI files supported!\n");
+        return false;
+    }
+
+    return true;
+}
+
+void MIDI_FreeFile(midi_file_t *file)
+{
+    int i;
+
+    if (file->tracks != NULL)
+    {
+        for (i=0; i<file->num_tracks; ++i)
+        {
+            FreeTrack(&file->tracks[i]);
+        }
+
+        free(file->tracks);
+    }
+
+    free(file);
+}
+
+midi_file_t *MIDI_LoadFile(char *filename)
+{
+    midi_file_t *file;
+    FILE *stream;
+
+    file = malloc(sizeof(midi_file_t));
+
+    if (file == NULL)
+    {
+        return NULL;
+    }
+
+    file->tracks = NULL;
+    file->num_tracks = 0;
+    file->buffer = NULL;
+    file->buffer_size = 0;
+
+    // Open file
+
+    stream = fopen(filename, "rb");
+
+    if (stream == NULL)
+    {
+        fprintf(stderr, "MIDI_LoadFile: Failed to open '%s'\n", filename);
+        MIDI_FreeFile(file);
+        return NULL;
+    }
+
+    // Read MIDI file header
+
+    if (!ReadFileHeader(file, stream))
+    {
+        fclose(stream);
+        MIDI_FreeFile(file);
+        return NULL;
+    }
+
+    // Read all tracks:
+
+    if (!ReadAllTracks(file, stream))
+    {
+        fclose(stream);
+        MIDI_FreeFile(file);
+        return NULL;
+    }
+
+    fclose(stream);
+
+    return file;
+}
+
+// Get the number of tracks in a MIDI file.
+
+unsigned int MIDI_NumTracks(midi_file_t *file)
+{
+    return file->num_tracks;
+}
+
+// Start iterating over the events in a track.
+
+midi_track_iter_t *MIDI_IterateTrack(midi_file_t *file, unsigned int track)
+{
+    midi_track_iter_t *iter;
+
+    assert(track < file->num_tracks);
+
+    iter = malloc(sizeof(*iter));
+    iter->track = &file->tracks[track];
+    iter->position = 0;
+
+    return iter;
+}
+
+void MIDI_FreeIterator(midi_track_iter_t *iter)
+{
+    free(iter);
+}
+
+// Get the time until the next MIDI event in a track.
+
+unsigned int MIDI_GetDeltaTime(midi_track_iter_t *iter)
+{
+    if (iter->position < iter->track->num_events)
+    {
+        midi_event_t *next_event;
+
+        next_event = &iter->track->events[iter->position];
+
+        return next_event->delta_time;
+    }
+    else
+    {
+        return 0;
+    }
+}
+
+// Get a pointer to the next MIDI event.
+
+int MIDI_GetNextEvent(midi_track_iter_t *iter, midi_event_t **event)
+{
+    if (iter->position < iter->track->num_events)
+    {
+        *event = &iter->track->events[iter->position];
+        ++iter->position;
+
+        return 1;
+    }
+    else
+    {
+        return 0;
+    }
+}
+
+unsigned int MIDI_GetFileTimeDivision(midi_file_t *file)
+{
+    return file->header.time_division;
+}
+
+void MIDI_RestartIterator(midi_track_iter_t *iter)
+{
+    iter->position = 0;
+}
+
+#ifdef TEST
+
+static char *MIDI_EventTypeToString(midi_event_type_t event_type)
+{
+    switch (event_type)
+    {
+        case MIDI_EVENT_NOTE_OFF:
+            return "MIDI_EVENT_NOTE_OFF";
+        case MIDI_EVENT_NOTE_ON:
+            return "MIDI_EVENT_NOTE_ON";
+        case MIDI_EVENT_AFTERTOUCH:
+            return "MIDI_EVENT_AFTERTOUCH";
+        case MIDI_EVENT_CONTROLLER:
+            return "MIDI_EVENT_CONTROLLER";
+        case MIDI_EVENT_PROGRAM_CHANGE:
+            return "MIDI_EVENT_PROGRAM_CHANGE";
+        case MIDI_EVENT_CHAN_AFTERTOUCH:
+            return "MIDI_EVENT_CHAN_AFTERTOUCH";
+        case MIDI_EVENT_PITCH_BEND:
+            return "MIDI_EVENT_PITCH_BEND";
+        case MIDI_EVENT_SYSEX:
+            return "MIDI_EVENT_SYSEX";
+        case MIDI_EVENT_SYSEX_SPLIT:
+            return "MIDI_EVENT_SYSEX_SPLIT";
+        case MIDI_EVENT_META:
+            return "MIDI_EVENT_META";
+
+        default:
+            return "(unknown)";
+    }
+}
+
+void PrintTrack(midi_track_t *track)
+{
+    midi_event_t *event;
+    unsigned int i;
+
+    for (i=0; i<track->num_events; ++i)
+    {
+        event = &track->events[i];
+
+        if (event->delta_time > 0)
+        {
+            printf("Delay: %i ticks\n", event->delta_time);
+        }
+
+        printf("Event type: %s (%i)\n",
+               MIDI_EventTypeToString(event->event_type),
+               event->event_type);
+
+        switch(event->event_type)
+        {
+            case MIDI_EVENT_NOTE_OFF:
+            case MIDI_EVENT_NOTE_ON:
+            case MIDI_EVENT_AFTERTOUCH:
+            case MIDI_EVENT_CONTROLLER:
+            case MIDI_EVENT_PROGRAM_CHANGE:
+            case MIDI_EVENT_CHAN_AFTERTOUCH:
+            case MIDI_EVENT_PITCH_BEND:
+                printf("\tChannel: %i\n", event->data.channel.channel);
+                printf("\tParameter 1: %i\n", event->data.channel.param1);
+                printf("\tParameter 2: %i\n", event->data.channel.param2);
+                break;
+
+            case MIDI_EVENT_SYSEX:
+            case MIDI_EVENT_SYSEX_SPLIT:
+                printf("\tLength: %i\n", event->data.sysex.length);
+                break;
+
+            case MIDI_EVENT_META:
+                printf("\tMeta type: %i\n", event->data.meta.type);
+                printf("\tLength: %i\n", event->data.meta.length);
+                break;
+        }
+    }
+}
+
+int main(int argc, char *argv[])
+{
+    midi_file_t *file;
+    unsigned int i;
+
+    if (argc < 2)
+    {
+        printf("Usage: %s <filename>\n", argv[0]);
+        exit(1);
+    }
+
+    file = MIDI_LoadFile(argv[1]);
+
+    if (file == NULL)
+    {
+        fprintf(stderr, "Failed to open %s\n", argv[1]);
+        exit(1);
+    }
+
+    for (i=0; i<file->num_tracks; ++i)
+    {
+        printf("\n== Track %i ==\n\n", i);
+
+        PrintTrack(&file->tracks[i]);
+    }
+
+    return 0;
+}
+
+#endif
+
--- /dev/null
+++ b/src/midifile.h
@@ -1,0 +1,175 @@
+// Emacs style mode select   -*- C++ -*- 
+//-----------------------------------------------------------------------------
+//
+// 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.
+//
+// DESCRIPTION:
+//     MIDI file parsing.
+//
+//-----------------------------------------------------------------------------
+
+#ifndef MIDIFILE_H
+#define MIDIFILE_H
+
+typedef struct midi_file_s midi_file_t;
+typedef struct midi_track_iter_s midi_track_iter_t;
+
+#define MIDI_CHANNELS_PER_TRACK 16
+
+typedef enum
+{
+    MIDI_EVENT_NOTE_OFF        = 0x80,
+    MIDI_EVENT_NOTE_ON         = 0x90,
+    MIDI_EVENT_AFTERTOUCH      = 0xa0,
+    MIDI_EVENT_CONTROLLER      = 0xb0,
+    MIDI_EVENT_PROGRAM_CHANGE  = 0xc0,
+    MIDI_EVENT_CHAN_AFTERTOUCH = 0xd0,
+    MIDI_EVENT_PITCH_BEND      = 0xe0,
+
+    MIDI_EVENT_SYSEX           = 0xf0,
+    MIDI_EVENT_SYSEX_SPLIT     = 0xf7,
+    MIDI_EVENT_META            = 0xff,
+} midi_event_type_t;
+
+typedef enum
+{
+    MIDI_CONTROLLER_BANK_SELECT     = 0x0,
+    MIDI_CONTROLLER_MODULATION      = 0x1,
+    MIDI_CONTROLLER_BREATH_CONTROL  = 0x2,
+    MIDI_CONTROLLER_FOOT_CONTROL    = 0x3,
+    MIDI_CONTROLLER_PORTAMENTO      = 0x4,
+    MIDI_CONTROLLER_DATA_ENTRY      = 0x5,
+
+    MIDI_CONTROLLER_MAIN_VOLUME     = 0x7,
+    MIDI_CONTROLLER_PAN             = 0xa
+} midi_controller_t;
+
+typedef enum
+{
+    MIDI_META_SEQUENCE_NUMBER       = 0x0,
+
+    MIDI_META_TEXT                  = 0x1,
+    MIDI_META_COPYRIGHT             = 0x2,
+    MIDI_META_TRACK_NAME            = 0x3,
+    MIDI_META_INSTR_NAME            = 0x4,
+    MIDI_META_LYRICS                = 0x5,
+    MIDI_META_MARKER                = 0x6,
+    MIDI_META_CUE_POINT             = 0x7,
+
+    MIDI_META_CHANNEL_PREFIX        = 0x20,
+    MIDI_META_END_OF_TRACK          = 0x2f,
+
+    MIDI_META_SET_TEMPO             = 0x51,
+    MIDI_META_SMPTE_OFFSET          = 0x54,
+    MIDI_META_TIME_SIGNATURE        = 0x58,
+    MIDI_META_KEY_SIGNATURE         = 0x59,
+    MIDI_META_SEQUENCER_SPECIFIC    = 0x7f,
+} midi_meta_event_type_t;
+
+typedef struct
+{
+    // Meta event type:
+
+    unsigned int type;
+
+    // Length:
+
+    unsigned int length;
+
+    // Meta event data:
+
+    byte *data;
+} midi_meta_event_data_t;
+
+typedef struct
+{
+    // Length:
+
+    unsigned int length;
+
+    // Event data:
+
+    byte *data;
+} midi_sysex_event_data_t;
+
+typedef struct
+{
+    // The channel number to which this applies:
+
+    unsigned int channel;
+
+    // Extra parameters:
+
+    unsigned int param1;
+    unsigned int param2;
+} midi_channel_event_data_t;
+
+typedef struct
+{
+    // Time between the previous event and this event.
+    unsigned int delta_time;
+
+    // Type of event:
+    midi_event_type_t event_type;
+
+    union
+    {
+        midi_channel_event_data_t channel;
+        midi_meta_event_data_t meta;
+        midi_sysex_event_data_t sysex;
+    } data;
+} midi_event_t;
+
+// Load a MIDI file.
+
+midi_file_t *MIDI_LoadFile(char *filename);
+
+// Free a MIDI file.
+
+void MIDI_FreeFile(midi_file_t *file);
+
+// Get the time division value from the MIDI header.
+
+unsigned int MIDI_GetFileTimeDivision(midi_file_t *file);
+
+// Get the number of tracks in a MIDI file.
+
+unsigned int MIDI_NumTracks(midi_file_t *file);
+
+// Start iterating over the events in a track.
+
+midi_track_iter_t *MIDI_IterateTrack(midi_file_t *file, unsigned int track_num);
+
+// Free an iterator.
+
+void MIDI_FreeIterator(midi_track_iter_t *iter);
+
+// Get the time until the next MIDI event in a track.
+
+unsigned int MIDI_GetDeltaTime(midi_track_iter_t *iter);
+
+// Get a pointer to the next MIDI event.
+
+int MIDI_GetNextEvent(midi_track_iter_t *iter, midi_event_t **event);
+
+// Reset an iterator to the beginning of a track.
+
+void MIDI_RestartIterator(midi_track_iter_t *iter);
+
+#endif /* #ifndef MIDIFILE_H */
+
--- a/src/net_client.c
+++ b/src/net_client.c
@@ -196,7 +196,8 @@
     // Do this the same way as Vanilla Doom does, to allow dehacked
     // replacements of this message
 
-    strcpy(exitmsg, DEH_String("Player 1 left the game"));
+    strncpy(exitmsg, DEH_String("Player 1 left the game"), sizeof(exitmsg));
+    exitmsg[sizeof(exitmsg) - 1] = '\0';
 
     exitmsg[7] += player - players;
 
--- a/src/setup/.gitignore
+++ b/src/setup/.gitignore
@@ -1,8 +1,7 @@
 Makefile.in
 Makefile
 .deps
-chocolate-setup
+setup-manifest.xml
 *.rc
-*.exe
 tags
 TAGS
--- a/src/setup/joystick.c
+++ b/src/setup/joystick.c
@@ -425,6 +425,8 @@
 
     AddJoystickControl(button_table, "Strafe Left", &joybstrafeleft);
     AddJoystickControl(button_table, "Strafe Right", &joybstraferight);
+    AddJoystickControl(button_table, "Previous weapon", &joybprevweapon);
+    AddJoystickControl(button_table, "Next weapon", &joybnextweapon);
 
     if (gamemission == hexen)
     {
--- a/src/setup/keyboard.c
+++ b/src/setup/keyboard.c
@@ -49,7 +49,7 @@
                            &key_weapon1, &key_weapon2, &key_weapon3,
                            &key_weapon4, &key_weapon5, &key_weapon6,
                            &key_weapon7, &key_weapon8,
-                           NULL };
+                           &key_prevweapon, &key_nextweapon, NULL };
 
 static int *menu_nav[] = { &key_menu_activate, &key_menu_up, &key_menu_down,
                            &key_menu_left, &key_menu_right, &key_menu_back,
@@ -57,10 +57,12 @@
 
 static int *shortcuts[] = { &key_menu_help, &key_menu_save, &key_menu_load,
                             &key_menu_volume, &key_menu_detail, &key_menu_qsave,
-                            &key_menu_endgame, &key_menu_messages,
+                            &key_menu_endgame, &key_menu_messages, &key_spy,
                             &key_menu_qload, &key_menu_quit, &key_menu_gamma,
                             &key_menu_incscreen, &key_menu_decscreen, 
-                            &key_message_refresh, NULL };
+                            &key_message_refresh, &key_multi_msg,
+                            &key_multi_msgplayer[0], &key_multi_msgplayer[1],
+                            &key_multi_msgplayer[2], &key_multi_msgplayer[3] };
 
 static int *map_keys[] = { &key_map_north, &key_map_south, &key_map_east,
                            &key_map_west, &key_map_zoomin, &key_map_zoomout,
@@ -240,6 +242,8 @@
     AddKeyControl(table, "Weapon 6", &key_weapon6);
     AddKeyControl(table, "Weapon 7", &key_weapon7);
     AddKeyControl(table, "Weapon 8", &key_weapon8);
+    AddKeyControl(table, "Previous weapon",       &key_prevweapon);
+    AddKeyControl(table, "Next weapon",           &key_nextweapon);
 }
 
 static void OtherKeysDialog(TXT_UNCAST_ARG(widget), TXT_UNCAST_ARG(unused))
@@ -280,14 +284,15 @@
     AddKeyControl(table, "Quick load",            &key_menu_qload);
     AddKeyControl(table, "Quit game",             &key_menu_quit);
     AddKeyControl(table, "Toggle gamma",          &key_menu_gamma);
+    AddKeyControl(table, "Multiplayer spy",       &key_spy);
 
     AddKeyControl(table, "Increase screen size",  &key_menu_incscreen);
     AddKeyControl(table, "Decrease screen size",  &key_menu_decscreen);
 
     AddKeyControl(table, "Display last message",  &key_message_refresh);
+    AddKeyControl(table, "Finish recording demo", &key_demo_quit);
 
     AddSectionLabel(table, "Map", true);
-
     AddKeyControl(table, "Toggle map",            &key_map_toggle);
     AddKeyControl(table, "Zoom in",               &key_map_zoomin);
     AddKeyControl(table, "Zoom out",              &key_map_zoomout);
@@ -300,6 +305,20 @@
     AddKeyControl(table, "Toggle grid",           &key_map_grid);
     AddKeyControl(table, "Mark location",         &key_map_mark);
     AddKeyControl(table, "Clear all marks",       &key_map_clearmark);
+
+    AddSectionLabel(table, "Multiplayer", true);
+
+    AddKeyControl(table, "Send message",          &key_multi_msg);
+    AddKeyControl(table, "- to green",            &key_multi_msgplayer[0]);
+    AddKeyControl(table, "- to indigo",           &key_multi_msgplayer[1]);
+    AddKeyControl(table, "- to brown",            &key_multi_msgplayer[2]);
+    AddKeyControl(table, "- to red",              &key_multi_msgplayer[3]);
+
+    TXT_AddWidgets(table, TXT_NewStrut(0, 1),
+                          TXT_NewStrut(0, 1),
+                          TXT_NewLabel(" - Map - "),
+                          TXT_NewStrut(0, 0),
+                          NULL);
 
     scrollpane = TXT_NewScrollPane(0, 13, table);
 
--- a/src/setup/keyboard.h
+++ b/src/setup/keyboard.h
@@ -22,59 +22,6 @@
 #ifndef SETUP_KEYBOARD_H 
 #define SETUP_KEYBOARD_H 
 
-// Menu keys:
-
-extern int key_menu_activate;
-extern int key_menu_up;
-extern int key_menu_down;
-extern int key_menu_left;
-extern int key_menu_right;
-extern int key_menu_back;
-extern int key_menu_forward;
-extern int key_menu_confirm;
-extern int key_menu_abort;
-
-extern int key_menu_help;
-extern int key_menu_save;
-extern int key_menu_load;
-extern int key_menu_volume;
-extern int key_menu_detail;
-extern int key_menu_qsave;
-extern int key_menu_endgame;
-extern int key_menu_messages;
-extern int key_menu_qload;
-extern int key_menu_quit;
-extern int key_menu_gamma;
-
-extern int key_menu_incscreen;
-extern int key_menu_decscreen;
-
-// Automap keys:
-
-extern int key_map_north;
-extern int key_map_south;
-extern int key_map_east;
-extern int key_map_west;
-extern int key_map_zoomin;
-extern int key_map_zoomout;
-extern int key_map_toggle;
-extern int key_map_maxzoom;
-extern int key_map_follow;
-extern int key_map_grid;
-extern int key_map_mark;
-extern int key_map_clearmark;
-
-// Weapon keys:
-
-extern int key_weapon1;
-extern int key_weapon2;
-extern int key_weapon3;
-extern int key_weapon4;
-extern int key_weapon5;
-extern int key_weapon6;
-extern int key_weapon7;
-extern int key_weapon8;
-
 void ConfigKeyboard(void);
 void BindKeyboardVariables(void);
 
--- a/src/setup/mainmenu.c
+++ b/src/setup/mainmenu.c
@@ -46,6 +46,64 @@
 #include "multiplayer.h"
 #include "sound.h"
 
+static const int cheat_sequence[] =
+{
+    KEY_UPARROW, KEY_UPARROW, KEY_DOWNARROW, KEY_DOWNARROW,
+    KEY_LEFTARROW, KEY_RIGHTARROW, KEY_LEFTARROW, KEY_RIGHTARROW,
+    'b', 'a', KEY_ENTER, 0
+};
+
+static unsigned int cheat_sequence_index = 0;
+
+// I think these are good "sensible" defaults:
+
+static void SensibleDefaults(void)
+{
+#if 0
+    // TODO for raven-branch
+    key_up = 'w';
+    key_down = 's';
+    key_strafeleft = 'a';
+    key_straferight = 'd';
+    mousebprevweapon = 4;
+    mousebnextweapon = 3;
+    snd_musicdevice = 3;
+    joybspeed = 29;
+    vanilla_savegame_limit = 0;
+    vanilla_keyboard_mapping = 0;
+    vanilla_demo_limit = 0;
+    show_endoom = 0;
+    dclick_use = 0;
+    novert = 1;
+#endif
+}
+
+static int MainMenuKeyPress(txt_window_t *window, int key, void *user_data)
+{
+    if (key == cheat_sequence[cheat_sequence_index])
+    {
+        ++cheat_sequence_index;
+
+        if (cheat_sequence[cheat_sequence_index] == 0)
+        {
+            SensibleDefaults();
+            cheat_sequence_index = 0;
+
+            window = TXT_NewWindow(NULL);
+            TXT_AddWidget(window, TXT_NewLabel("    \x01    "));
+            TXT_SetWindowAction(window, TXT_HORIZ_RIGHT, NULL);
+
+            return 1;
+        }
+    }
+    else
+    {
+        cheat_sequence_index = 0;
+    }
+
+    return 0;
+}
+
 static void DoQuit(void *widget, void *dosave)
 {
     if (dosave != NULL)
@@ -174,6 +232,8 @@
     quit_action = TXT_NewWindowAction(KEY_ESCAPE, "Quit");
     TXT_SignalConnect(quit_action, "pressed", QuitConfirm, NULL);
     TXT_SetWindowAction(window, TXT_HORIZ_LEFT, quit_action);
+
+    TXT_SetKeyListener(window, MainMenuKeyPress, NULL);
 }
 
 //
--- a/src/setup/mouse.c
+++ b/src/setup/mouse.c
@@ -36,7 +36,7 @@
 
 static int novert = 0;
 static int mouseSensitivity = 5;
-static float mouse_acceleration = 1.0;
+static float mouse_acceleration = 2.0;
 static int mouse_threshold = 10;
 static int grabmouse = 1;
 
@@ -48,7 +48,9 @@
     &mousebstraferight,
     &mousebbackward,
     &mousebuse,
-    &mousebjump
+    &mousebjump,
+    &mousebprevweapon,
+    &mousebnextweapon
 };
 
 static void MouseSetCallback(TXT_UNCAST_ARG(widget), TXT_UNCAST_ARG(variable))
@@ -103,6 +105,9 @@
     {
         AddMouseControl(buttons_table, "Jump", &mousebjump);
     }
+
+    AddMouseControl(buttons_table, "Previous weapon", &mousebprevweapon);
+    AddMouseControl(buttons_table, "Next weapon", &mousebnextweapon);
 }
 
 void ConfigMouse(void)
--- a/src/setup/multiplayer.c
+++ b/src/setup/multiplayer.c
@@ -29,6 +29,7 @@
 #include "d_iwad.h"
 #include "m_config.h"
 #include "doom/d_englsh.h"
+#include "m_controls.h"
 
 #include "multiplayer.h"
 #include "mode.h"
@@ -862,6 +863,40 @@
     {
         sprintf(buf, "chatmacro%i", i);
         M_BindVariable(buf, &chat_macros[i]);
+    }
+
+    switch (gamemission)
+    {
+        case doom:
+            M_BindChatControls(4);
+            key_multi_msgplayer[0] = 'g';
+            key_multi_msgplayer[1] = 'i';
+            key_multi_msgplayer[2] = 'b';
+            key_multi_msgplayer[3] = 'r';
+            break;
+
+        case heretic:
+            M_BindChatControls(4);
+            key_multi_msgplayer[0] = 'g';
+            key_multi_msgplayer[1] = 'y';
+            key_multi_msgplayer[2] = 'r';
+            key_multi_msgplayer[3] = 'b';
+            break;
+
+        case hexen:
+            M_BindChatControls(8);
+            key_multi_msgplayer[0] = 'b';
+            key_multi_msgplayer[1] = 'r';
+            key_multi_msgplayer[2] = 'y';
+            key_multi_msgplayer[3] = 'g';
+            key_multi_msgplayer[4] = 'j';
+            key_multi_msgplayer[5] = 'w';
+            key_multi_msgplayer[6] = 'h';
+            key_multi_msgplayer[7] = 'p';
+            break;
+
+        default:
+            break;
     }
 }
 
--- a/src/setup/sound.c
+++ b/src/setup/sound.c
@@ -40,7 +40,7 @@
     NUM_SFXMODES
 } sfxmode_t;
 
-static char *sfxmode_strings[] = 
+static char *sfxmode_strings[] =
 {
     "Disabled",
     "Digital",
@@ -47,10 +47,11 @@
     "PC speaker"
 };
 
-typedef enum 
+typedef enum
 {
     MUSICMODE_DISABLED,
-    MUSICMODE_MIDI,
+    MUSICMODE_OPL,
+    MUSICMODE_NATIVE,
     MUSICMODE_CD,
     NUM_MUSICMODES
 } musicmode_t;
@@ -58,7 +59,8 @@
 static char *musicmode_strings[] =
 {
     "Disabled",
-    "MIDI",
+    "OPL (Adlib/SB)",
+    "Native MIDI",
     "CD audio"
 };
 
@@ -65,8 +67,9 @@
 // Config file variables:
 
 int snd_sfxdevice = SNDDEVICE_SB;
-int snd_musicdevice = SNDDEVICE_SB;
+int snd_musicdevice = SNDDEVICE_GENMIDI;
 int snd_samplerate = 22050;
+int opl_io_port = 0x388;
 
 static int numChannels = 8;
 static int sfxVolume = 15;
@@ -108,7 +111,10 @@
         case MUSICMODE_DISABLED:
             snd_musicdevice = SNDDEVICE_NONE;
             break;
-        case MUSICMODE_MIDI:
+        case MUSICMODE_NATIVE:
+            snd_musicdevice = SNDDEVICE_GENMIDI;
+            break;
+        case MUSICMODE_OPL:
             snd_musicdevice = SNDDEVICE_SB;
             break;
         case MUSICMODE_CD:
@@ -139,20 +145,26 @@
     {
         snd_sfxmode = SFXMODE_DISABLED;
     }
-    
+
     // Is music enabled?
 
-    if (snd_musicdevice == SNDDEVICE_NONE)
+    if (snd_musicdevice == SNDDEVICE_GENMIDI)
     {
-        snd_musicmode = MUSICMODE_DISABLED;
+        snd_musicmode = MUSICMODE_NATIVE;
     }
     else if (snd_musicmode == SNDDEVICE_CD)
     {
         snd_musicmode = MUSICMODE_CD;
     }
+    else if (snd_musicdevice == SNDDEVICE_SB
+          || snd_musicdevice == SNDDEVICE_ADLIB
+          || snd_musicdevice == SNDDEVICE_AWE32)
+    {
+        snd_musicmode = MUSICMODE_OPL;
+    }
     else
     {
-        snd_musicmode = MUSICMODE_MIDI;
+        snd_musicmode = MUSICMODE_DISABLED;
     }
 
     // Doom has PC speaker sound effects, but others do not:
@@ -188,7 +200,7 @@
                music_table = TXT_NewTable(2),
                NULL);
 
-    TXT_SetColumnWidths(sfx_table, 20, 5);
+    TXT_SetColumnWidths(sfx_table, 20, 14);
 
     TXT_AddWidgets(sfx_table, 
                    TXT_NewLabel("Sound effects"),
@@ -209,7 +221,7 @@
                        NULL);
     }
 
-    TXT_SetColumnWidths(music_table, 20, 5);
+    TXT_SetColumnWidths(music_table, 20, 14);
 
     TXT_AddWidgets(music_table,
                    TXT_NewLabel("Music"),
@@ -222,7 +234,6 @@
 
     TXT_SignalConnect(sfx_mode_control, "changed", UpdateSndDevices, NULL);
     TXT_SignalConnect(music_mode_control, "changed", UpdateSndDevices, NULL);
-
 }
 
 void BindSoundVariables(void)
--- a/src/setup/txt_keyinput.c
+++ b/src/setup/txt_keyinput.c
@@ -111,7 +111,7 @@
 
     if (*key_input->variable == 0)
     {
-        strcpy(buf, "");
+        strcpy(buf, "(none)");
     }
     else
     {
--- a/src/setup/txt_mouseinput.c
+++ b/src/setup/txt_mouseinput.c
@@ -91,7 +91,7 @@
             strcpy(buf, "MID");
             break;
         default:
-            sprintf(buf, "BUTTON #%i", button);
+            sprintf(buf, "BUTTON #%i", button + 1);
             break;
     }
 }
@@ -153,7 +153,7 @@
 static void TXT_MouseInputMousePress(TXT_UNCAST_ARG(widget), int x, int y, int b)
 {
     TXT_CAST_ARG(txt_mouse_input_t, widget);
-            
+
     // Clicking is like pressing enter
 
     if (b == TXT_MOUSE_LEFT)
--- a/src/strife/.gitignore
+++ b/src/strife/.gitignore
@@ -1,9 +1,5 @@
 Makefile
 Makefile.in
 .deps
-*.rc
-chocolate-doom
-chocolate-server
-*.exe
 tags
 TAGS
--- a/src/strife/am_map.c
+++ b/src/strife/am_map.c
@@ -502,7 +502,7 @@
   
     for (i=0;i<10;i++)
     {
-        sprintf(namebuf, DEH_String("PLMNUM%d"), i); 
+        DEH_snprintf(namebuf, 9, "PLMNUM%d", i);
         marknums[i] = W_CacheLumpName(namebuf, PU_STATIC);
     }
 
@@ -515,7 +515,7 @@
   
     for (i=0;i<10;i++)
     {
-        sprintf(namebuf, DEH_String("PLMNUM%d"), i); // haleyjd: Choco change.
+        DEH_snprintf(namebuf, 9, "PLMNUM%d", i);
         W_ReleaseLumpName(namebuf);
     }
 }
@@ -1022,7 +1022,7 @@
 	   || fl->b.x < 0 || fl->b.x >= f_w
 	   || fl->b.y < 0 || fl->b.y >= f_h)
     {
-	fprintf(stderr, DEH_String("fuck %d \r"), fuck++);
+        DEH_fprintf(stderr, "fuck %d \r", fuck++);
 	return;
     }
 
--- a/src/strife/d_main.c
+++ b/src/strife/d_main.c
@@ -45,8 +45,8 @@
 #include "d_iwad.h"
 
 #include "z_zone.h"
+#include "w_main.h"
 #include "w_wad.h"
-#include "w_merge.h"
 #include "s_sound.h"
 #include "v_video.h"
 
@@ -384,7 +384,13 @@
     M_BindMapControls();
     M_BindMenuControls();
     M_BindStrifeControls(); // haleyjd 09/01/10: [STRIFE]
+    M_BindChatControls(MAXPLAYERS);
 
+    key_multi_msgplayer[0] = HUSTR_KEYGREEN;
+    key_multi_msgplayer[1] = HUSTR_KEYINDIGO;
+    key_multi_msgplayer[2] = HUSTR_KEYBROWN;
+    key_multi_msgplayer[3] = HUSTR_KEYRED;
+
 #ifdef FEATURE_MULTIPLAYER
     NET_BindVariables();
 #endif
@@ -849,7 +855,6 @@
 //      print title for every printed line
 char            title[128];
 
-
 static boolean D_AddFile(char *filename)
 {
     wad_file_t *handle;
@@ -1126,7 +1131,7 @@
 
     I_PrintBanner(PACKAGE_STRING);
 
-    printf (DEH_String("Z_Init: Init zone memory allocation daemon. \n"));
+    DEH_printf("Z_Init: Init zone memory allocation daemon. \n");
     Z_Init ();
 
 #ifdef FEATURE_MULTIPLAYER
@@ -1245,7 +1250,7 @@
 	deathmatch = 2;
 
     if (devparm)
-	printf(DEH_String(D_DEVSTR));
+	DEH_printf(D_DEVSTR);
     
     // find which dir to use for config files
 
@@ -1294,7 +1299,7 @@
 	    scale = 10;
 	if (scale > 400)
 	    scale = 400;
-	printf (DEH_String("turbo scale: %i%%\n"),scale);
+        DEH_printf("turbo scale: %i%%\n", scale);
 	forwardmove[0] = forwardmove[0]*scale/100;
 	forwardmove[1] = forwardmove[1]*scale/100;
 	sidemove[0] = sidemove[0]*scale/100;
@@ -1302,12 +1307,12 @@
     }
     
     // init subsystems
-    printf(DEH_String("V_Init: allocate screens.\n"));
-    V_Init();
+    DEH_printf("V_Init: allocate screens.\n");
+    V_Init ();
 
     // Load configuration files before initialising other subsystems.
     // haleyjd 08/22/2010: [STRIFE] - use strife.cfg
-    printf(DEH_String("M_LoadDefaults: Load system defaults.\n"));
+    DEH_printf("M_LoadDefaults: Load system defaults.\n");
     M_SetConfigFilenames("strife.cfg", PROGRAM_PREFIX "strife.cfg");
     D_BindVariables();
     M_LoadDefaults();
@@ -1315,161 +1320,10 @@
     // Save configuration at exit.
     I_AtExit(M_SaveDefaults, false);
 
-    printf (DEH_String("W_Init: Init WADfiles.\n"));
+    DEH_printf("W_Init: Init WADfiles.\n");
     D_AddFile(iwadfile);
+    modifiedgame = W_ParseCommandLine();
 
-#ifdef FEATURE_WAD_MERGE
-
-    // Merged PWADs are loaded first, because they are supposed to be 
-    // modified IWADs.
-
-    //!
-    // @arg <files>
-    // @category mod
-    //
-    // Simulates the behavior of deutex's -merge option, merging a PWAD
-    // into the main IWAD.  Multiple files may be specified.
-    //
-
-    p = M_CheckParm("-merge");
-
-    if (p > 0)
-    {
-        for (p = p + 1; p<myargc && myargv[p][0] != '-'; ++p)
-        {
-            char *filename;
-
-            filename = D_TryFindWADByName(myargv[p]);
-
-            printf(" merging %s\n", filename);
-            W_MergeFile(filename);
-        }
-    }
-
-    // NWT-style merging:
-
-    // NWT's -merge option:
-
-    //!
-    // @arg <files>
-    // @category mod
-    //
-    // Simulates the behavior of NWT's -merge option.  Multiple files
-    // may be specified.
-
-    p = M_CheckParm("-nwtmerge");
-
-    if (p > 0)
-    {
-        for (p = p + 1; p<myargc && myargv[p][0] != '-'; ++p)
-        {
-            char *filename;
-
-            filename = D_TryFindWADByName(myargv[p]);
-
-            printf(" performing NWT-style merge of %s\n", filename);
-            W_NWTDashMerge(filename);
-        }
-    }
-    
-    // Add flats
-
-    //!
-    // @arg <files>
-    // @category mod
-    //
-    // Simulates the behavior of NWT's -af option, merging flats into
-    // the main IWAD directory.  Multiple files may be specified.
-    //
-
-    p = M_CheckParm("-af");
-
-    if (p > 0)
-    {
-        for (p = p + 1; p<myargc && myargv[p][0] != '-'; ++p)
-        {
-            char *filename;
-
-            filename = D_TryFindWADByName(myargv[p]);
-
-            printf(" merging flats from %s\n", filename);
-            W_NWTMergeFile(filename, W_NWT_MERGE_FLATS);
-        }
-    }
-
-    //!
-    // @arg <files>
-    // @category mod
-    //
-    // Simulates the behavior of NWT's -as option, merging sprites
-    // into the main IWAD directory.  Multiple files may be specified.
-    //
-
-    p = M_CheckParm("-as");
-
-    if (p > 0)
-    {
-        for (p = p + 1; p<myargc && myargv[p][0] != '-'; ++p)
-        {
-            char *filename;
-
-            filename = D_TryFindWADByName(myargv[p]);
-
-            printf(" merging sprites from %s\n", filename);
-            W_NWTMergeFile(filename, W_NWT_MERGE_SPRITES);
-        }
-    }
-
-    //!
-    // @arg <files>
-    // @category mod
-    //
-    // Equivalent to "-af <files> -as <files>".
-    //
-
-    p = M_CheckParm("-aa");
-
-    if (p > 0)
-    {
-        for (p = p + 1; p<myargc && myargv[p][0] != '-'; ++p)
-        {
-            char *filename;
-
-            filename = D_TryFindWADByName(myargv[p]);
-
-            printf(" merging sprites and flats from %s\n", filename);
-            W_NWTMergeFile(filename, W_NWT_MERGE_SPRITES | W_NWT_MERGE_FLATS);
-        }
-    }
-
-#endif
-
-    //!
-    // @arg <files>
-    // @vanilla
-    //
-    // Load the specified PWAD files.
-    //
-
-    p = M_CheckParm ("-file");
-    if (p)
-    {
-	// the parms after p are wadfile/lump names,
-	// until end of parms or another - preceded parm
-	modifiedgame = true;            // homebrew levels
-	while (++p != myargc && myargv[p][0] != '-')
-        {
-            char *filename;
-
-            filename = D_TryFindWADByName(myargv[p]);
-
-	    D_AddFile(filename);
-        }
-    }
-
-    // Debug:
-//    W_PrintDirectory();
-
     // add any files specified on the command line with -file wadfile
     // to the wad list
     //
@@ -1664,8 +1518,8 @@
 
     if (p && p < myargc-1 && deathmatch)
     {
-	printf(DEH_String("Austin Virtual Gaming: Levels will end "
-			  "after 20 minutes\n"));
+        DEH_printf("Austin Virtual Gaming: Levels will end "
+                       "after 20 minutes\n");
 	timelimit = 20;
     }
 
@@ -1750,17 +1604,17 @@
     // haleyjd 08/28/10: Init Choco Strife stuff.
     D_InitChocoStrife();
 
-    printf (DEH_String("M_Init: Init miscellaneous info.\n"));
+    DEH_printf("M_Init: Init miscellaneous info.\n");
     M_Init ();
 
     // haleyjd 08/22/2010: [STRIFE] Modified string to match binary
-    printf (DEH_String("R_Init: Loading Graphics - "));
+    DEH_printf("R_Init: Loading Graphics - ");
     R_Init ();
 
-    printf (DEH_String("\nP_Init: Init Playloop state.\n"));
+    DEH_printf("\nP_Init: Init Playloop state.\n");
     P_Init ();
 
-    printf (DEH_String("I_Init: Setting up machine state.\n"));
+    DEH_printf("I_Init: Setting up machine state.\n");
     I_CheckIsScreensaver();
     I_InitTimer();
     I_InitJoystick();
@@ -1770,18 +1624,18 @@
     NET_Init ();
 #endif
 
-    printf (DEH_String("S_Init: Setting up sound.\n"));
+    DEH_printf("S_Init: Setting up sound.\n");
     S_Init (sfxVolume * 8, musicVolume * 8);
 
-    printf (DEH_String("D_CheckNetGame: Checking network game status.\n"));
+    DEH_printf("D_CheckNetGame: Checking network game status.\n");
     D_CheckNetGame ();
 
     PrintGameVersion();
 
-    printf (DEH_String("HU_Init: Setting up heads up display.\n"));
+    DEH_printf("HU_Init: Setting up heads up display.\n");
     HU_Init ();
 
-    printf (DEH_String("ST_Init: Init status bar.\n"));
+    DEH_printf("ST_Init: Init status bar.\n");
     ST_Init ();
 
     // If Doom II without a MAP01 lump, this is a store demo.  
--- a/src/strife/d_net.c
+++ b/src/strife/d_net.c
@@ -374,17 +374,17 @@
             ++num_players;
     }
 
-    printf (DEH_String("startskill %i  deathmatch: %i  startmap: %i  startepisode: %i\n"),
-	    startskill, deathmatch, startmap, startepisode);
+    DEH_printf("startskill %i  deathmatch: %i  startmap: %i  startepisode: %i\n",
+               startskill, deathmatch, startmap, startepisode);
 	
-    printf(DEH_String("player %i of %i (%i nodes)\n"),
-	    consoleplayer+1, num_players, num_players);
+    DEH_printf("player %i of %i (%i nodes)\n",
+               consoleplayer+1, num_players, num_players);
 
     // Show players here; the server might have specified a time limit
 
     if (timelimit > 0)
     {
-	printf(DEH_String("Levels will end after %d minute"),timelimit);
+	DEH_printf("Levels will end after %d minute", timelimit);
 	if (timelimit > 1)
 	    printf("s");
 	printf(".\n");
--- a/src/strife/f_finale.c
+++ b/src/strife/f_finale.c
@@ -665,7 +665,7 @@
 	laststage = stage;
     }
 	
-    sprintf (name, DEH_String("END%i"), stage);
+    DEH_snprintf(name, 10, "END%i", stage);
     V_DrawPatch((SCREENWIDTH - 13 * 8) / 2, 
                 (SCREENHEIGHT - 8 * 8) / 2, 
                 W_CacheLumpName (name,PU_CACHE));
--- a/src/strife/g_game.c
+++ b/src/strife/g_game.c
@@ -183,14 +183,40 @@
     &key_weapon8
 };
 
+// Set to -1 or +1 to switch to the previous or next weapon.
+
+static int next_weapon = 0;
+
+// Used for prev/next weapon keys.
+// STRIFE-TODO: Check this table makes sense.
+
+static const struct
+{
+    weapontype_t weapon;
+    weapontype_t weapon_num;
+} weapon_order_table[] = {
+    { wp_fist,                  wp_fist },
+    { wp_elecbow,               wp_elecbow },
+    { wp_poisonbow,             wp_elecbow },
+    { wp_rifle,                 wp_rifle },
+    { wp_missile,               wp_missile },
+    { wp_hegrenade,             wp_hegrenade },
+    { wp_wpgrenade,             wp_hegrenade },
+    { wp_flame,                 wp_flame },
+    { wp_mauler,                wp_mauler },
+    { wp_torpedo,               wp_mauler },
+    { wp_sigil,                 wp_sigil },
+};
+
 #define SLOWTURNTICS	6 
  
 #define NUMKEYS		256 
+#define MAX_JOY_BUTTONS 20
 
 static boolean  gamekeydown[NUMKEYS]; 
 static int      turnheld;		// for accelerative turning 
  
-static boolean  mousearray[4]; 
+static boolean  mousearray[MAX_MOUSE_BUTTONS + 1];
 static boolean *mousebuttons = &mousearray[1];  // allow [-1]
 
 // mouse values are used once 
@@ -204,8 +230,6 @@
 static boolean  dclickstate2;
 static int      dclicks2;
 
-#define MAX_JOY_BUTTONS 20
-
 // joystick values are repeated 
 static int      joyxmove;
 static int      joyymove;
@@ -344,8 +368,56 @@
 		 
     return sum; 
 } 
- 
 
+static boolean WeaponSelectable(weapontype_t weapon)
+{
+    // Can't select a weapon if we don't own it.
+
+    if (!players[consoleplayer].weaponowned[weapon])
+    {
+        return false;
+    }
+
+    // STRIFE-TODO: Special weapon cycling rules?
+
+    return true;
+}
+
+static int G_NextWeapon(int direction)
+{
+    weapontype_t weapon;
+    int i;
+
+    // Find index in the table.
+
+    if (players[consoleplayer].pendingweapon == wp_nochange)
+    {
+        weapon = players[consoleplayer].readyweapon;
+    }
+    else
+    {
+        weapon = players[consoleplayer].pendingweapon;
+    }
+
+    for (i=0; i<arrlen(weapon_order_table); ++i)
+    {
+        if (weapon_order_table[i].weapon == weapon)
+        {
+            break;
+        }
+    }
+
+    // Switch weapon.
+
+    do
+    {
+        i += direction;
+        i = (i + arrlen(weapon_order_table)) % arrlen(weapon_order_table);
+    } while (!WeaponSelectable(weapon_order_table[i].weapon));
+
+    return weapon_order_table[i].weapon_num;
+}
+
 //
 // G_BuildTiccmd
 // Builds a ticcmd from all of the available inputs
@@ -503,20 +575,34 @@
 	dclicks = 0;                   
     } 
 
-    // chainsaw overrides 
+    // If the previous or next weapon button is pressed, the
+    // next_weapon variable is set to change weapons when
+    // we generate a ticcmd.  Choose a new weapon.
 
-    for (i=0; i<arrlen(weapon_keys); ++i)
+    if (next_weapon != 0)
     {
-        int key = *weapon_keys[i];
+        i = G_NextWeapon(next_weapon);
+        cmd->buttons |= BT_CHANGE;
+        cmd->buttons |= i << BT_WEAPONSHIFT;
+        next_weapon = 0;
+    }
+    else
+    {
+        // Check weapon keys.
 
-        if (gamekeydown[key])
+        for (i=0; i<arrlen(weapon_keys); ++i)
         {
-	    cmd->buttons |= BT_CHANGE; 
-	    cmd->buttons |= i<<BT_WEAPONSHIFT; 
-	    break; 
+            int key = *weapon_keys[i];
+
+            if (gamekeydown[key])
+            {
+                cmd->buttons |= BT_CHANGE;
+                cmd->buttons |= i<<BT_WEAPONSHIFT;
+                break;
+            }
         }
     }
-    
+
     // mouse
     if (mousebuttons[mousebforward]) 
     {
@@ -697,7 +783,6 @@
 
     P_DialogLoad(); // villsa [STRIFE]
 } 
- 
 
 static void SetJoyButtons(unsigned int buttons_mask)
 {
@@ -705,10 +790,54 @@
 
     for (i=0; i<MAX_JOY_BUTTONS; ++i)
     {
-        joybuttons[i] = (buttons_mask & (1 << i)) != 0;
+        int button_on = (buttons_mask & (1 << i)) != 0;
+
+        // Detect button press:
+
+        if (!joybuttons[i] && button_on)
+        {
+            // Weapon cycling:
+
+            if (i == joybprevweapon)
+            {
+                next_weapon = -1;
+            }
+            else if (i == joybnextweapon)
+            {
+                next_weapon = 1;
+            }
+        }
+
+        joybuttons[i] = button_on;
     }
 }
- 
+
+static void SetMouseButtons(unsigned int buttons_mask)
+{
+    int i;
+
+    for (i=0; i<MAX_MOUSE_BUTTONS; ++i)
+    {
+        unsigned int button_on = (buttons_mask & (1 << i)) != 0;
+
+        // Detect button press:
+
+        if (!mousebuttons[i] && button_on)
+        {
+            if (i == mousebprevweapon)
+            {
+                next_weapon = -1;
+            }
+            else if (i == mousebnextweapon)
+            {
+                next_weapon = 1;
+            }
+        }
+
+	mousebuttons[i] = button_on;
+    }
+}
+
 //
 // G_Responder  
 // Get info needed to make ticcmd_ts for the players.
@@ -717,7 +846,7 @@
 { 
     // allow spy mode changes even during the demo
     if (gamestate == GS_LEVEL && ev->type == ev_keydown 
-     && ev->data1 == KEY_F12 && (singledemo || !deathmatch) )
+     && ev->data1 == key_spy && (singledemo || !deathmatch) )
     {
 	// spy mode 
 	do 
@@ -777,6 +906,18 @@
         testcontrols_mousespeed = abs(ev->data2);
     }
 
+    // If the next/previous weapon keys are pressed, set the next_weapon
+    // variable to change weapons when the next ticcmd is generated.
+
+    if (ev->type == ev_keydown && ev->data1 == key_prevweapon)
+    {
+        next_weapon = -1;
+    }
+    else if (ev->type == ev_keydown && ev->data1 == key_nextweapon)
+    {
+        next_weapon = 1;
+    }
+
     switch (ev->type) 
     { 
       case ev_keydown: 
@@ -797,9 +938,7 @@
 	return false;   // always let key up events filter down 
 		 
       case ev_mouse: 
-	mousebuttons[0] = ev->data1 & 1; 
-	mousebuttons[1] = ev->data1 & 2; 
-	mousebuttons[2] = ev->data1 & 4; 
+        SetMouseButtons(ev->data1);
 	mousex = ev->data2*(mouseSensitivity+5)/10; 
 	mousey = ev->data3*(mouseSensitivity+5)/10; 
 	return true;    // eat events 
@@ -1416,6 +1555,8 @@
         return;
     }
 
+    savegame_error = false;
+
     if (!P_ReadSaveGameHeader())
     {
         fclose(save_stream);
@@ -1483,6 +1624,8 @@
         return;
     }
 
+    savegame_error = false;
+
     P_WriteSaveGameHeader(savedescription);
  
     P_ArchivePlayers (); 
@@ -1778,7 +1921,7 @@
 { 
     byte *demo_start;
 
-    if (gamekeydown['q'])           // press q to end demo recording 
+    if (gamekeydown[key_demo_quit])           // press q to end demo recording 
 	G_CheckDemoStatus (); 
 
     demo_start = demo_p;
--- a/src/strife/hu_stuff.c
+++ b/src/strife/hu_stuff.c
@@ -178,7 +178,7 @@
     j = HU_FONTSTART;
     for (i=0;i<HU_FONTSIZE;i++)
     {
-	sprintf(buffer, DEH_String("STCFN%.3d"), j++);
+	DEH_snprintf(buffer, 9, "STCFN%.3d", j++);
 	hu_font[i] = (patch_t *) W_CacheLumpName(buffer, PU_STATIC);
     }
 
@@ -383,14 +383,6 @@
     int			i;
     int			numplayers;
     
-    static char		destination_keys[MAXPLAYERS] =
-    {
-	HUSTR_KEYGREEN,
-	HUSTR_KEYINDIGO,
-	HUSTR_KEYBROWN,
-	HUSTR_KEYRED
-    };
-    
     static int		num_nobrainers = 0;
 
     numplayers = 0;
@@ -419,7 +411,7 @@
 	    message_counter = HU_MSGTIMEOUT;
 	    eatkey = true;
 	}
-	else if (netgame && ev->data2 == HU_INPUTTOGGLE)
+	else if (netgame && ev->data2 == key_multi_msg)
 	{
 	    eatkey = chat_on = true;
 	    HUlib_resetIText(&w_chat);
@@ -429,7 +421,7 @@
 	{
 	    for (i=0; i<MAXPLAYERS ; i++)
 	    {
-		if (ev->data2 == destination_keys[i])
+		if (ev->data2 == key_multi_msgplayer[i])
 		{
 		    if (playeringame[i] && i!=consoleplayer)
 		    {
--- a/src/strife/m_menu.c
+++ b/src/strife/m_menu.c
@@ -715,7 +715,7 @@
 	quickSaveSlot = -2;	// means to pick a slot now
 	return;
     }
-    sprintf(tempstring,DEH_String(QSPROMPT),savegamestrings[quickSaveSlot]);
+    DEH_snprintf(tempstring, 80, QSPROMPT, savegamestrings[quickSaveSlot]);
     M_StartMessage(tempstring,M_QuickSaveResponse,true);
 }
 
@@ -747,7 +747,7 @@
 	M_StartMessage(DEH_String(QSAVESPOT),NULL,false);
 	return;
     }
-    sprintf(tempstring,DEH_String(QLPROMPT),savegamestrings[quickSaveSlot]);
+    DEH_snprintf(tempstring, 80, QLPROMPT, savegamestrings[quickSaveSlot]);
     M_StartMessage(tempstring,M_QuickLoadResponse,true);
 }
 
--- a/src/strife/p_saveg.c
+++ b/src/strife/p_saveg.c
@@ -44,6 +44,7 @@
 
 FILE *save_stream;
 int savegamelength;
+boolean savegame_error;
 
 // Get the filename of a temporary file to write the savegame to.  After
 // the file has been successfully saved, it will be renamed to the 
@@ -75,7 +76,7 @@
         filename = malloc(strlen(savegamedir) + 32);
     }
 
-    sprintf(basename, DEH_String(SAVEGAMENAME "%d.dsg"), slot);
+    DEH_snprintf(basename, 32, SAVEGAMENAME "%d.dsg", slot);
 
     sprintf(filename, "%s%s", savegamedir, basename);
 
@@ -88,14 +89,31 @@
 {
     byte result;
 
-    fread(&result, 1, 1, save_stream);
+    if (fread(&result, 1, 1, save_stream) < 1)
+    {
+        if (!savegame_error)
+        {
+            fprintf(stderr, "saveg_read8: Unexpected end of file while "
+                            "reading save game\n");
 
+            savegame_error = true;
+        }
+    }
+
     return result;
 }
 
 static void saveg_write8(byte value)
 {
-    fwrite(&value, 1, 1, save_stream);
+    if (fwrite(&value, 1, 1, save_stream) < 1)
+    {
+        if (!savegame_error)
+        {
+            fprintf(stderr, "saveg_write8: Error while writing save game\n");
+
+            savegame_error = true;
+        }
+    }
 }
 
 static short saveg_read16(void)
--- a/src/strife/p_saveg.h
+++ b/src/strife/p_saveg.h
@@ -64,6 +64,7 @@
 void P_UnArchiveSpecials (void);
 
 extern FILE *save_stream;
+extern boolean savegame_error;
 
 
 #endif
--- a/src/strife/p_setup.c
+++ b/src/strife/p_setup.c
@@ -33,6 +33,7 @@
 
 #include "deh_main.h"
 #include "i_swap.h"
+#include "m_argv.h"
 #include "m_bbox.h"
 
 #include "g_game.h"
@@ -76,6 +77,7 @@
 int		numsides;
 side_t*		sides;
 
+static int      totallines;
 
 // BLOCKMAP
 // Created from axis aligned bounding box
@@ -554,7 +556,6 @@
     line_t**		linebuffer;
     int			i;
     int			j;
-    int			total;
     line_t*		li;
     sector_t*		sector;
     subsector_t*	ss;
@@ -572,21 +573,21 @@
 
     // count number of lines in each sector
     li = lines;
-    total = 0;
+    totallines = 0;
     for (i=0 ; i<numlines ; i++, li++)
     {
-	total++;
+	totallines++;
 	li->frontsector->linecount++;
 
 	if (li->backsector && li->backsector != li->frontsector)
 	{
 	    li->backsector->linecount++;
-	    total++;
+	    totallines++;
 	}
     }
 
     // build line tables for each sector	
-    linebuffer = Z_Malloc (total*sizeof(line_t *), PU_LEVEL, 0);
+    linebuffer = Z_Malloc (totallines*sizeof(line_t *), PU_LEVEL, 0);
 
     for (i=0; i<numsectors; ++i)
     {
@@ -663,7 +664,88 @@
 	
 }
 
+// Pad the REJECT lump with extra data when the lump is too small,
+// to simulate a REJECT buffer overflow in Vanilla Doom.
 
+static void PadRejectArray(byte *array, unsigned int len)
+{
+    unsigned int i;
+    unsigned int byte_num;
+    byte *dest;
+    unsigned int padvalue;
+
+    // Values to pad the REJECT array with:
+
+    unsigned int rejectpad[4] =
+    {
+        ((totallines * 4 + 3) & ~3) + 24,     // Size
+        0,                                    // Part of z_zone block header
+        50,                                   // PU_LEVEL
+        0x1d4a11                              // DOOM_CONST_ZONEID
+    };
+
+    // Copy values from rejectpad into the destination array.
+
+    dest = array;
+
+    for (i=0; i<len && i<sizeof(rejectpad); ++i)
+    {
+        byte_num = i % 4;
+        *dest = (rejectpad[i / 4] >> (byte_num * 8)) & 0xff;
+        ++dest;
+    }
+
+    // We only have a limited pad size.  Print a warning if the
+    // REJECT lump is too small.
+
+    if (len > sizeof(rejectpad))
+    {
+        fprintf(stderr, "PadRejectArray: REJECT lump too short to pad! (%i > %i)\n",
+                        len, sizeof(rejectpad));
+
+        // Pad remaining space with 0 (or 0xff, if specified on command line).
+
+        if (M_CheckParm("-reject_pad_with_ff"))
+        {
+            padvalue = 0xff;
+        }
+        else
+        {
+            padvalue = 0xf00;
+        }
+
+        memset(array + sizeof(rejectpad), padvalue, len - sizeof(rejectpad));
+    }
+}
+
+static void P_LoadReject(int lumpnum)
+{
+    int minlength;
+    int lumplen;
+
+    // Calculate the size that the REJECT lump *should* be.
+
+    minlength = (numsectors * numsectors + 7) / 8;
+
+    // If the lump meets the minimum length, it can be loaded directly.
+    // Otherwise, we need to allocate a buffer of the correct size
+    // and pad it with appropriate data.
+
+    lumplen = W_LumpLength(lumpnum);
+
+    if (lumplen >= minlength)
+    {
+        rejectmatrix = W_CacheLumpNum(lumpnum, PU_LEVEL);
+    }
+    else
+    {
+        rejectmatrix = Z_Malloc(minlength, PU_LEVEL, &rejectmatrix);
+        W_ReadLump(lumpnum, rejectmatrix);
+
+        PadRejectArray(rejectmatrix + lumplen, minlength - lumplen);
+    }
+}
+
 //
 // P_SetupLevel
 //
@@ -712,9 +794,9 @@
     if ( gamemode == commercial)
     {
 	if (map<10)
-	    sprintf (lumpname, DEH_String("map0%i"), map);
+	    DEH_snprintf(lumpname, 9, "map0%i", map);
 	else
-	    sprintf (lumpname, DEH_String("map%i"), map);
+	    DEH_snprintf(lumpname, 9, "map%i", map);
     }
     else
     {
@@ -739,9 +821,9 @@
     P_LoadSubsectors (lumpnum+ML_SSECTORS);
     P_LoadNodes (lumpnum+ML_NODES);
     P_LoadSegs (lumpnum+ML_SEGS);
-	
-    rejectmatrix = W_CacheLumpNum (lumpnum+ML_REJECT,PU_LEVEL);
+
     P_GroupLines ();
+    P_LoadReject (lumpnum+ML_REJECT);
 
     bodyqueslot = 0;
     deathmatch_p = deathmatchstarts;
--- a/src/strife/r_draw.c
+++ b/src/strife/r_draw.c
@@ -600,51 +600,57 @@
 // Draws the actual span.
 void R_DrawSpan (void) 
 { 
-    fixed_t		xfrac;
-    fixed_t		yfrac; 
-    byte*		dest; 
-    int			count;
-    int			spot; 
-	 
-#ifdef RANGECHECK 
+    unsigned int position, step;
+    byte *dest;
+    int count;
+    int spot;
+    unsigned int xtemp, ytemp;
+
+#ifdef RANGECHECK
     if (ds_x2 < ds_x1
 	|| ds_x1<0
-	|| ds_x2>=SCREENWIDTH  
+	|| ds_x2>=SCREENWIDTH
 	|| (unsigned)ds_y>SCREENHEIGHT)
     {
 	I_Error( "R_DrawSpan: %i to %i at %i",
 		 ds_x1,ds_x2,ds_y);
     }
-//	dscount++; 
-#endif 
+//	dscount++;
+#endif
 
-    
-    xfrac = ds_xfrac; 
-    yfrac = ds_yfrac; 
-	 
+    // Pack position and step variables into a single 32-bit integer,
+    // with x in the top 16 bits and y in the bottom 16 bits.  For
+    // each 16-bit part, the top 6 bits are the integer part and the
+    // bottom 10 bits are the fractional part of the pixel position.
+
+    position = ((ds_xfrac << 10) & 0xffff0000)
+             | ((ds_yfrac >> 6)  & 0x0000ffff);
+    step = ((ds_xstep << 10) & 0xffff0000)
+         | ((ds_ystep >> 6)  & 0x0000ffff);
+
     dest = ylookup[ds_y] + columnofs[ds_x1];
 
     // We do not check for zero spans here?
-    count = ds_x2 - ds_x1; 
+    count = ds_x2 - ds_x1;
 
-    do 
+    do
     {
-	// Current texture index in u,v.
-	spot = ((yfrac>>(16-6))&(63*64)) + ((xfrac>>16)&63);
+	// Calculate current texture index in u,v.
+        ytemp = (position >> 4) & 0x0fc0;
+        xtemp = (position >> 26);
+        spot = xtemp | ytemp;
 
 	// Lookup pixel from flat texture tile,
 	//  re-index using light/colormap.
 	*dest++ = ds_colormap[ds_source[spot]];
 
-	// Next step in u,v.
-	xfrac += ds_xstep; 
-	yfrac += ds_ystep;
-	
-    } while (count--); 
-} 
+        position += step;
 
+    } while (count--);
+}
 
 
+
 // UNUSED.
 // Loop unrolled by 4.
 #if 0
@@ -721,18 +727,18 @@
 //
 // Again..
 //
-void R_DrawSpanLow (void) 
-{ 
-    fixed_t		xfrac;
-    fixed_t		yfrac; 
-    byte*		dest; 
-    int			count;
-    int			spot; 
-	 
-#ifdef RANGECHECK 
+void R_DrawSpanLow (void)
+{
+    unsigned int position, step;
+    unsigned int xtemp, ytemp;
+    byte *dest;
+    int count;
+    int spot;
+
+#ifdef RANGECHECK
     if (ds_x2 < ds_x1
 	|| ds_x1<0
-	|| ds_x2>=SCREENWIDTH  
+	|| ds_x2>=SCREENWIDTH
 	|| (unsigned)ds_y>SCREENHEIGHT)
     {
 	I_Error( "R_DrawSpan: %i to %i at %i",
@@ -739,31 +745,36 @@
 		 ds_x1,ds_x2,ds_y);
     }
 //	dscount++; 
-#endif 
-	 
-    xfrac = ds_xfrac; 
-    yfrac = ds_yfrac; 
-    
-    count = (ds_x2 - ds_x1); 
+#endif
 
+    position = ((ds_xfrac << 10) & 0xffff0000)
+             | ((ds_yfrac >> 6)  & 0x0000ffff);
+    step = ((ds_xstep << 10) & 0xffff0000)
+         | ((ds_ystep >> 6)  & 0x0000ffff);
+
+    count = (ds_x2 - ds_x1);
+
     // Blocky mode, need to multiply by 2.
     ds_x1 <<= 1;
     ds_x2 <<= 1;
-    
+
     dest = ylookup[ds_y] + columnofs[ds_x1];
-  
-    do 
-    { 
-	spot = ((yfrac>>(16-6))&(63*64)) + ((xfrac>>16)&63);
+
+    do
+    {
+	// Calculate current texture index in u,v.
+        ytemp = (position >> 4) & 0x0fc0;
+        xtemp = (position >> 26);
+        spot = xtemp | ytemp;
+
 	// Lowres/blocky mode does it twice,
 	//  while scale is adjusted appropriately.
-	*dest++ = ds_colormap[ds_source[spot]]; 
 	*dest++ = ds_colormap[ds_source[spot]];
-	
-	xfrac += ds_xstep; 
-	yfrac += ds_ystep; 
+	*dest++ = ds_colormap[ds_source[spot]];
 
-    } while (count--); 
+	position += step;
+
+    } while (count--);
 }
 
 //
--- a/src/strife/st_stuff.c
+++ b/src/strife/st_stuff.c
@@ -767,10 +767,10 @@
     // Load the numbers, green and yellow
     for (i=0;i<10;i++)
     {
-	sprintf(namebuf, DEH_String("INVFONG%d"), i);
+        DEH_snprintf(namebuf, 9, "INVFONG%d", i);
         callback(namebuf, &invfontg[i]);
 
-	sprintf(namebuf, DEH_String("INVFONY%d"), i);
+        DEH_snprintf(namebuf, 9, "INVFONY%d", i);
         callback(namebuf, &invfonty[i]);
     }
 
--- a/src/strife/wi_stuff.c
+++ b/src/strife/wi_stuff.c
@@ -1572,16 +1572,16 @@
     if (gamemode == commercial)
     {
 	for (i=0 ; i<NUMCMAPS ; i++)
-	{								
-	    sprintf(name, DEH_String("CWILV%2.2d"), i);
+	{
+	    DEH_snprintf(name, 9, "CWILV%2.2d", i);
             callback(name, &lnames[i]);
-	}					
+	}
     }
     else
     {
 	for (i=0 ; i<NUMMAPS ; i++)
 	{
-	    sprintf(name, DEH_String("WILV%d%d"), wbs->epsd, i);
+	    DEH_snprintf(name, 9, "WILV%d%d", wbs->epsd, i);
             callback(name, &lnames[i]);
 	}
 
@@ -1593,7 +1593,7 @@
 
 	// splat
         callback(DEH_String("WISPLAT"), &splat[0]);
-	
+
 	if (wbs->epsd < 3)
 	{
 	    for (j=0;j<NUMANIMS[wbs->epsd];j++)
@@ -1602,17 +1602,16 @@
 		for (i=0;i<a->nanims;i++)
 		{
 		    // MONDO HACK!
-		    if (wbs->epsd != 1 || j != 8) 
+		    if (wbs->epsd != 1 || j != 8)
 		    {
 			// animations
-			sprintf(name, DEH_String("WIA%d%.2d%.2d"), 
-				      wbs->epsd, j, i);  
+			DEH_snprintf(name, 9, "WIA%d%.2d%.2d", wbs->epsd, j, i);
                         callback(name, &a->p[i]);
 		    }
 		    else
 		    {
 			// HACK ALERT!
-			a->p[i] = anims[1][4].p[i]; 
+			a->p[i] = anims[1][4].p[i];
 		    }
 		}
 	    }
@@ -1625,7 +1624,7 @@
     for (i=0;i<10;i++)
     {
 	 // numbers 0-9
-	sprintf(name, DEH_String("WINUM%d"), i);     
+	DEH_snprintf(name, 9, "WINUM%d", i);
         callback(name, &num[i]);
     }
 
@@ -1666,13 +1665,13 @@
     callback(DEH_String("WICOLON"), &colon);
 
     // "time"
-    callback(DEH_String("WITIME"), &timepatch);   
+    callback(DEH_String("WITIME"), &timepatch);
 
     // "sucks"
-    callback(DEH_String("WISUCKS"), &sucks);  
+    callback(DEH_String("WISUCKS"), &sucks);
 
     // "par"
-    callback(DEH_String("WIPAR"), &par);   
+    callback(DEH_String("WIPAR"), &par);
 
     // "killers" (vertical)
     callback(DEH_String("WIKILRS"), &killers);
@@ -1681,16 +1680,16 @@
     callback(DEH_String("WIVCTMS"), &victims);
 
     // "total"
-    callback(DEH_String("WIMSTT"), &total);   
+    callback(DEH_String("WIMSTT"), &total);
 
     for (i=0 ; i<MAXPLAYERS ; i++)
     {
 	// "1,2,3,4"
-	sprintf(name, DEH_String("STPB%d"), i);      
+	DEH_snprintf(name, 9, "STPB%d", i);
         callback(name, &p[i]);
 
 	// "1,2,3,4"
-	sprintf(name, DEH_String("WIBP%d"), i+1);     
+	DEH_snprintf(name, 9, "WIBP%d", i+1);
         callback(name, &bp[i]);
     }
 
@@ -1698,19 +1697,21 @@
 
     if (gamemode == commercial)
     {
-	strcpy(name, DEH_String("INTERPIC"));
+	strncpy(name, DEH_String("INTERPIC"), 9);
+        name[8] = '\0';
     }
     else if (gamemode == retail && wbs->epsd == 3)
     {
-	strcpy(name, DEH_String("INTERPIC"));
+	strncpy(name, DEH_String("INTERPIC"), 9);
+        name[8] = '\0';
     }
-    else 
+    else
     {
-	sprintf(name, DEH_String("WIMAP%d"), wbs->epsd);
+	DEH_snprintf(name, 9, "WIMAP%d", wbs->epsd);
     }
-    
+
     // Draw backdrop and save to a temporary buffer
-  
+
     callback(name, &background);
 }
 
@@ -1723,7 +1724,7 @@
 {
     if (gamemode == commercial)
     {
-	NUMCMAPS = 32;								
+	NUMCMAPS = 32;
 	lnames = (patch_t **) Z_Malloc(sizeof(patch_t*) * NUMCMAPS,
 				       PU_STATIC, NULL);
     }
--- /dev/null
+++ b/src/w_main.c
@@ -1,0 +1,206 @@
+// Emacs style mode select   -*- C++ -*- 
+//-----------------------------------------------------------------------------
+//
+// Copyright(C) 1993-1996 Id Software, Inc.
+// Copyright(C) 2005-2010 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.
+//
+// DESCRIPTION:
+//     Common code to parse command line, identifying WAD files to load.
+//
+//-----------------------------------------------------------------------------
+
+#include "doomfeatures.h"
+#include "d_iwad.h"
+#include "m_argv.h"
+#include "w_main.h"
+#include "w_merge.h"
+#include "w_wad.h"
+#include "z_zone.h"
+
+// Parse the command line, merging WAD files that are sppecified.
+// Returns true if at least one file was added.
+
+boolean W_ParseCommandLine(void)
+{
+    boolean modifiedgame = false;
+    int p;
+
+#ifdef FEATURE_WAD_MERGE
+
+    // Merged PWADs are loaded first, because they are supposed to be 
+    // modified IWADs.
+
+    //!
+    // @arg <files>
+    // @category mod
+    //
+    // Simulates the behavior of deutex's -merge option, merging a PWAD
+    // into the main IWAD.  Multiple files may be specified.
+    //
+
+    p = M_CheckParm("-merge");
+
+    if (p > 0)
+    {
+        for (p = p + 1; p<myargc && myargv[p][0] != '-'; ++p)
+        {
+            char *filename;
+
+            modifiedgame = true;
+
+            filename = D_TryFindWADByName(myargv[p]);
+
+            printf(" merging %s\n", filename);
+            W_MergeFile(filename);
+        }
+    }
+
+    // NWT-style merging:
+
+    // NWT's -merge option:
+
+    //!
+    // @arg <files>
+    // @category mod
+    //
+    // Simulates the behavior of NWT's -merge option.  Multiple files
+    // may be specified.
+
+    p = M_CheckParm("-nwtmerge");
+
+    if (p > 0)
+    {
+        for (p = p + 1; p<myargc && myargv[p][0] != '-'; ++p)
+        {
+            char *filename;
+
+            modifiedgame = true;
+
+            filename = D_TryFindWADByName(myargv[p]);
+
+            printf(" performing NWT-style merge of %s\n", filename);
+            W_NWTDashMerge(filename);
+        }
+    }
+    
+    // Add flats
+
+    //!
+    // @arg <files>
+    // @category mod
+    //
+    // Simulates the behavior of NWT's -af option, merging flats into
+    // the main IWAD directory.  Multiple files may be specified.
+    //
+
+    p = M_CheckParm("-af");
+
+    if (p > 0)
+    {
+        for (p = p + 1; p<myargc && myargv[p][0] != '-'; ++p)
+        {
+            char *filename;
+
+            modifiedgame = true;
+
+            filename = D_TryFindWADByName(myargv[p]);
+
+            printf(" merging flats from %s\n", filename);
+            W_NWTMergeFile(filename, W_NWT_MERGE_FLATS);
+        }
+    }
+
+    //!
+    // @arg <files>
+    // @category mod
+    //
+    // Simulates the behavior of NWT's -as option, merging sprites
+    // into the main IWAD directory.  Multiple files may be specified.
+    //
+
+    p = M_CheckParm("-as");
+
+    if (p > 0)
+    {
+        for (p = p + 1; p<myargc && myargv[p][0] != '-'; ++p)
+        {
+            char *filename;
+
+            modifiedgame = true;
+            filename = D_TryFindWADByName(myargv[p]);
+
+            printf(" merging sprites from %s\n", filename);
+            W_NWTMergeFile(filename, W_NWT_MERGE_SPRITES);
+        }
+    }
+
+    //!
+    // @arg <files>
+    // @category mod
+    //
+    // Equivalent to "-af <files> -as <files>".
+    //
+
+    p = M_CheckParm("-aa");
+
+    if (p > 0)
+    {
+        for (p = p + 1; p<myargc && myargv[p][0] != '-'; ++p)
+        {
+            char *filename;
+
+            modifiedgame = true;
+
+            filename = D_TryFindWADByName(myargv[p]);
+
+            printf(" merging sprites and flats from %s\n", filename);
+            W_NWTMergeFile(filename, W_NWT_MERGE_SPRITES | W_NWT_MERGE_FLATS);
+        }
+    }
+
+#endif
+
+    //!
+    // @arg <files>
+    // @vanilla
+    //
+    // Load the specified PWAD files.
+    //
+
+    p = M_CheckParm ("-file");
+    if (p)
+    {
+	// the parms after p are wadfile/lump names,
+	// until end of parms or another - preceded parm
+	modifiedgame = true;            // homebrew levels
+	while (++p != myargc && myargv[p][0] != '-')
+        {
+            char *filename;
+
+            filename = D_TryFindWADByName(myargv[p]);
+
+            printf(" adding %s\n", filename);
+	    W_AddFile(filename);
+        }
+    }
+
+//    W_PrintDirectory();
+
+    return modifiedgame;
+}
+
--- /dev/null
+++ b/src/w_main.h
@@ -1,0 +1,32 @@
+// Emacs style mode select   -*- C++ -*- 
+//-----------------------------------------------------------------------------
+//
+// Copyright(C) 2005 Simon Howard
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+// 02111-1307, USA.
+//
+// DESCRIPTION:
+//     Common code to parse command line, identifying WAD files to load.
+//
+//-----------------------------------------------------------------------------
+
+#ifndef W_MAIN_H
+#define W_MAIN_H
+
+boolean W_ParseCommandLine(void);
+
+#endif /* #ifndef W_MAIN_H */
+
--- a/src/z_zone.c
+++ b/src/z_zone.c
@@ -41,6 +41,7 @@
 //  because it will get overwritten automatically if needed.
 // 
  
+#define MEM_ALIGN sizeof(void *)
 #define ZONEID	0x1d4a11
 
 typedef struct memblock_s
@@ -201,7 +202,7 @@
     memblock_t*	base;
     void *result;
 
-    size = (size + 3) & ~3;
+    size = (size + MEM_ALIGN - 1) & ~(MEM_ALIGN - 1);
     
     // scan through the block list,
     // looking for the first free block
--- a/textscreen/Makefile.am
+++ b/textscreen/Makefile.am
@@ -9,6 +9,8 @@
 
 noinst_LIBRARIES=libtextscreen.a
 
+EXTRA_DIST=Doxyfile
+
 libtextscreen_a_SOURCES =                                 \
 	textscreen.h                                      \
 	txt_checkbox.c           txt_checkbox.h           \
@@ -21,11 +23,11 @@
 	txt_button.c             txt_button.h             \
 	txt_label.c              txt_label.h              \
 	txt_radiobutton.c        txt_radiobutton.h        \
-        txt_scrollpane.c         txt_scrollpane.h         \
+	txt_scrollpane.c         txt_scrollpane.h         \
 	txt_separator.c          txt_separator.h          \
 	txt_spinctrl.c           txt_spinctrl.h           \
 	txt_sdl.c                txt_sdl.h                \
-                                 txt_smallfont.h          \
+	                         txt_smallfont.h          \
 	txt_strut.c              txt_strut.h              \
 	txt_table.c              txt_table.h              \
 	txt_widget.c             txt_widget.h             \