shithub: choc

Download patch

ref: 6f48bab893205636faf89ade2625b55c90d8c9bd
parent: 30f853b65820280931e06e07fd5f624313670305
parent: 939cbfeb115338cf7868e516019a4ae832b1ebde
author: Simon Howard <[email protected]>
date: Mon Jan 2 09:42:49 EST 2017

Merge remote-tracking branch 'origin/master' into sdl2-branch

--- a/NEWS.md
+++ b/NEWS.md
@@ -1,82 +1,94 @@
-## HEAD
+## 2.3.0 (2016-12-29)
 
 ### General
-  * Bash completion scripts are included. (thanks Fabian)
-  * Support the *.lmp file format in the OS X launcher (thanks Jon)
-  * Added emulation for pitch-shifting as in early versions of Doom,
-    Heretic, and Hexen. (thanks Jon)
-  * Write out aspect-correct 1600×1200 PNGs. (thanks Jon)
-  * OPL emulation is more accurate. (thanks Nuke.YKT)
-  * Futher emulation of DMX bugs with GUS cards. (thanks Nuke.YKT)
-  * Emulation of the disk icon has returned. (thanks Fabian, Jon)
-  * Checksum calculations were fixed on big endian systems, allowing
+  * Bash completion scripts are included (thanks Fabian)
+  * The OS X launcher now supports the .lmp file format (thanks Jon)
+  * Pitch-shifting from early versions of Doom, Heretic, and Hexen.
+    is now supported (thanks Jon)
+  * Aspect ratio-corrected 1600×1200 PNGs are now written (thanks Jon)
+  * OPL emulation is more accurate (thanks Nuke.YKT)
+  * DMX bugs with GUS cards are now better emulated (thanks Nuke.YKT)
+  * The disk activity floppy disk icon is now shown (thanks Fabian, Jon)
+  * Checksum calculations are fixed on big endian systems, allowing
     multiplayer games to be played in mixed little/big-endian
-    environments. (thanks GhostlyDeath, njankowski)
+    environments (thanks GhostlyDeath, njankowski)
   * The NES30, SNES30, and SFC30 gamepads are detected and configured
-    automatically by the Setup tool.  The automap can also be
-    configured to a joystick button. (thanks Jon)
-  * The vanilla limit of 4046 lumps per WAD is now enforced. (thanks
+    automatically by the Setup tool. The automap can also be configured
+    to a joystick button (thanks Jon)
+  * The vanilla limit of 4046 lumps per WAD is now enforced (thanks
     Jon, Quasar, Edward-san)
-  * Solidsegs overflow is emulated like in vanilla. (thanks Quasar)
-  * Heretic/Hexen demo support has expanded. "-demoextend" allows
-    demos to last longer than a single level; "-shortticfix" adjusts
-    low-resolution turning to match Doom's handling; "-maxdemo" and
-    "-longtics" support. (thanks CapnClever)
+  * Solidsegs overflow is emulated like in vanilla (thanks Quasar)
+  * Multiple capitalizations are now tried when searching for WAD files,
+    for convenience when running on case sensitive filesystems (thanks
+    Fabian).
+  * A new command line argument, `-strictdemos`, was added, to allow
+    more careful control over demo format extensions. Such extensions
+    are now forbidden in WAD files and warning messages are shown.
 
 ### Build systems
-  * Improved compatibility with BSD Make. (thanks R.Rebello)
+  * There is better compatibility with BSD Make (thanks R.Rebello)
   * “./configure --with-PACKAGE” checks were repaired to behave
-    logically, rather than disabling the feature. (thanks R.Rebello)
-  * Default to installing the games to ${bindir}, such as
-    /usr/local/bin, rather than /usr/local/games. (thanks chungy)
-  * Support Visual Studio 2015. (thanks Azarien)
-  * Allow SDL headers and libraries to exist in the Microsoft Visual
-    Studio project directory. (thanks Quasar)
-  * Repaired the CodeBlocks projects by removing non-existent files
+    logically, rather than disabling the feature (thanks R.Rebello)
+  * Games are now installed to ${bindir} by default, eg.
+    /usr/local/bin, rather than /usr/local/games (thanks chungy)
+  * Visual Studio 2015 is now supported (thanks Azarien)
+  * SDL headers and libraries can now exist in the Microsoft Visual
+    Studio project directory (thanks Quasar)
+  * CodeBlocks projects were repaired by removing non-existent files
     from the project files (thanks krystalgamer)
 
 ### Doom
-  * Chex Quest’s level warp cheat (LEESNYDER##) was changed to behave
-    like the original EXE. (thanks Nuke.YKT)
-  * Allow starting multiplayer Chex Quest games.
-  * Allow Freedoom: Phase 1 ≤ 0.10.1 to be loaded with mods, with
-    -gameversion older than ultimate. (thanks Fabian, chungy)
+  * Chex Quest’s level warp cheat (LEESNYDER##) now behaves more like
+    like the original EXE (thanks Nuke.YKT)
+  * It's now possible to start multiplayer Chex Quest games.
+  * Freedoom: Phase 1 <= 0.10.1 can now be loaded with mods, with
+    -gameversion older than ultimate (thanks Fabian, chungy)
   * The IWAD order preference for GOG.com installs matches vanilla
-    Final Doom: doom2, plutonia, tnt, doom. (thanks chungy)
-  * Added safety checks against write failures when saving a game,
-    such as when the directory is read-only.  Try falling back to a
-    temporary directory and reporting an error instead.  (thanks
+    Final Doom: doom2, plutonia, tnt, doom (thanks chungy)
+  * There are better safety checks against write failures when saving
+    a game, such as when the directory is read-only (thanks
     terrorcide)
-  * Versions 1.666, 1.7, and 1.8 are emulated. (thanks Nuke.YKT)
+  * Versions 1.666, 1.7, and 1.8 are emulated (thanks Nuke.YKT)
+  * Crashes are now handled more gracefully when a linedef references
+    nonexistent sidedefs (thanks Fabian)
 
 ### Heretic
-  * Added map names for Episode 6, fixing a crash after completing a
-    level in this episode. (thanks J.Benaim)
-  * Added unlimited demo/savegame support. (thanks CapnClever)
+  * Map names were added for Episode 6, fixing a crash after completing
+    a level in this episode (thanks J.Benaim)
+  * Support for unlimited demo/savegames was added (thanks CapnClever)
+  * Demo support is expanded: "-demoextend" allows demos to last longer
+    than a single level; "-shortticfix" adjusts low-resolution turning
+    to match Doom's handling, and there is now "-maxdemo" and "-longtics"
+    support (thanks CapnClever)
 
 ### Hexen
-  * The MRJONES cheat code returns an identical string as vanilla, and
-    enables fully reproducable builds. (thanks Fabian)
-  * Fixed an issue where the game crashed while killing the
-    Wraithverge in 64-bit builds. (thanks J.Benaim)
-  * Added unlimited demo/savegame support. (thanks CapnClever)
-  * Mouse buttons for strafe left/right and move backward, as well as
-    'Double click acts as "use"' mouse option, now function properly.
-    (thanks CapnClever)
+  * The MRJONES cheat code returns an identical string to vanilla, and
+    enables fully reproducible builds (thanks Fabian)
+  * An issue was fixed where the game crashed while killing the
+    Wraithverge in 64-bit builds (thanks J.Benaim)
+  * Support for unlimited demo/savegames was added (thanks CapnClever)
+  * Mouse buttons for strafe left/right and move backward were added,
+    as well as a "Double click acts as use" mouse option (thanks
+    CapnClever)
+  * Demo support is expanded: "-demoextend" allows demos to last longer
+    than a single level; "-shortticfix" adjusts low-resolution turning
+    to match Doom's handling, and there is now "-maxdemo" and "-longtics"
+    support (thanks CapnClever)
 
 ### Strife
-  * Support added for automatic loading of the IWAD from the GOG.com
-    release of Strife: Veteran Edition on Windows. (thanks chungy)
-  * Jumping can be bound to a mouse button. (thanks Gez)
-  * Gibbing logic was changed to match vanilla behavior. (thanks Quasar)
-  * Several constants differences from vanilla were fixed. (thanks
+  * Support was added for automatic loading of the IWAD from the GOG.com
+    release of Strife: Veteran Edition on Windows (thanks chungy)
+  * Jumping can now be bound to a mouse button (thanks Gez)
+  * Gibbing logic was changed to match vanilla behavior (thanks Quasar)
+  * Several constants differences from vanilla were fixed (thanks
     Nuke.YKT, Quasar)
   * When using -iwad, voices.wad from the IWAD’s directory is prefered
-    over auto-detected DOS/Steam/GOG.com installs. (thanks Quasar)
+    over auto-detected DOS/Steam/GOG.com installs (thanks Quasar)
 
 ### libtextscreen
-  * Simplified the API for creating and managing tables and columns.
-  * Allow cycling through tables with tab key.
+  * The API for creating and managing tables and columns was simplified.
+  * It's now possible to cycle through tables with the tab key.
+  * Windows can now have multiple columns.
 
 ## 2.2.1 (2015-09-10)
 
--- a/PHILOSOPHY.md
+++ b/PHILOSOPHY.md
@@ -28,9 +28,12 @@
  * DOS Heretic 1.3.
  * DOS Hexen 1.1.
  * DOS Strife 1.31.
+ * DOS Chex Quest.
 
-“Vanilla” does not include ports (either official or unofficial), such
-as console ports, Doom 95 or Doom 3: BFG Edition.
+Compatibility with older versions of the DOS binaries is also a
+secondary goal (though not pre-release versions). Other ports (either
+official or unofficial) are out of scope: this includes console ports,
+non-DOS ports, Doom 95 and Doom 3: BFG Edition.
 
 # Compatibility
 
@@ -46,7 +49,7 @@
    Chocolate Doom as a faithful substitute.
  * Demo compatibility: Doom was one of the first games to develop a
    ‘speedrunning’ community, and thousands of recordings of Doom
-   gameplay (.lmp demo files) exist in the Compet-N archive. Chocolate
+   gameplay (`.lmp` demo files) exist in the Compet-N archive. Chocolate
    Doom aims for the highest standard of demo compatibility with
    Vanilla Doom, a goal that is often complicated by obscure behavior
    that can be difficult to reproduce.
@@ -67,8 +70,8 @@
 treated as such. Some examples are:
 
  * The novert setting, which reproduces the functionality of
-   novert.exe.
- * The -deh parameter, which loads Dehacked patches like dehacked.exe
+   `novert.exe`.
+ * The `-deh` parameter, which loads Dehacked patches like dehacked.exe
    does under DOS. Chocolate Doom imposes the same limitations that
    Vanilla Dehacked does.
 
@@ -97,8 +100,8 @@
  3. Supporting obsolete technology is not a goal: it’s considered
     acceptable that Chocolate Doom does not support every type of
     hardware from 1993. However, Chocolate Doom should aim to recreate
-    the functionality in a modern way. Examples of technology that
-    isn’t supported are:
+    the functionality in a modern way. Examples of technologies that
+    aren’t supported are:
 
     - No support for IPX networks, but TCP/IP is supported instead.
     - No support for dial-up/serial connections; modern operating
@@ -111,9 +114,9 @@
     - There are new key bindings for actions that can’t be rebound with
       Vanilla Doom, because it’s useful for portability to machines
       that don’t have a full keyboard.
-    - There are additional mouse and joystick key bindings that let you
-      perform actions with them that can only be done with the keyboard
-      in Vanilla Doom.
+    - There are additional mouse and joystick button bindings that let
+      you perform actions with them that can only be done with the
+      keyboard in Vanilla Doom.
     - Chocolate Doom includes some hacks to support the Doom 3: BFG
       Edition IWAD files. The assumption is that being able to at least
       play is better than nothing, even if it’s not Vanilla behavior.
@@ -145,9 +148,20 @@
 
     - The startup messages in Chocolate Doom are not identical to
       Vanilla Doom and are not necessarily in the same order.
-    - Vanilla Doom has command line options named -comdev, -shdev and
-      -regdev used by id internally for development; these have been
-      removed.
+    - Vanilla Doom has command line options named `-comdev`, `-shdev`
+      and `-regdev` used by id internally for development; these have
+      been removed.
+
+ 8. Expansions to the vanilla demo formats are allowed, to make
+    recording and playback of vanilla gameplay more convenient, with
+    the following restrictions:
+
+    - Such expansions are not supported in WAD files (they are not
+      an editing feature for WAD authors to use).
+    - Support for these features can be completely disabled using the
+      `-strictdemos` command line argument.
+    - A warning is shown to the user on the console (stdout) when a
+      demo using one of these features is recorded or played back.
 
 A good litmus test of when it’s acceptable to break from Vanilla
 behavior is to ask the question: “Although this is Vanilla behavior,
--- a/README.md
+++ b/README.md
@@ -101,4 +101,4 @@
    file for more information.
 
  * Please send any feedback, questions or suggestions to
-   [email protected]. Thanks!
+   [email protected]. Thanks!
--- a/codeblocks/config.h
+++ b/codeblocks/config.h
@@ -9,13 +9,13 @@
 #define PACKAGE_NAME "Chocolate Doom"
 
 /* Define to the full name and version of this package. */
-#define PACKAGE_STRING "Chocolate Doom 2.2.1"
+#define PACKAGE_STRING "Chocolate Doom 2.3.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 "2.2.1"
+#define PACKAGE_VERSION "2.3.0"
 
 /* Change this when you create your awesome forked version */
 #define PROGRAM_PREFIX "chocolate-"
@@ -24,7 +24,7 @@
 #define STDC_HEADERS 1
 
 /* Version number of package */
-#define VERSION "2.2.1"
+#define VERSION "2.3.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 2,2,1,0
-FILEVERSION 2,2,1,0
+PRODUCTVERSION 2,3,0,0
+FILEVERSION 2,3,0,0
 FILETYPE 1
 {
  BLOCK "StringFileInfo"
@@ -9,13 +9,13 @@
  {
   BLOCK "040904E4"
   {
-   VALUE "FileVersion", "2.2.1"
-   VALUE "FileDescription", "2.2.1"
+   VALUE "FileVersion", "2.3.0"
+   VALUE "FileDescription", "2.3.0"
    VALUE "InternalName", "Chocolate Doom"
    VALUE "CompanyName", "Chocolate Doom"
    VALUE "LegalCopyright", "GNU General Public License"
    VALUE "ProductName", "Chocolate Doom"
-   VALUE "ProductVersion", "2.2.1"
+   VALUE "ProductVersion", "2.3.0"
   }
  }
  BLOCK "VarFileInfo"
--- a/codeblocks/setup-res.rc
+++ b/codeblocks/setup-res.rc
@@ -1,8 +1,8 @@
 1 ICON "../data/setup.ico"
 
 1 VERSIONINFO
-PRODUCTVERSION 2,2,1,0
-FILEVERSION 2,2,1,0
+PRODUCTVERSION 2,3,0,0
+FILEVERSION 2,3,0,0
 FILETYPE 1
 {
  BLOCK "StringFileInfo"
@@ -9,13 +9,13 @@
  {
   BLOCK "040904E4"
   {
-   VALUE "FileVersion", "2.2.1"
+   VALUE "FileVersion", "2.3.0"
    VALUE "FileDescription", "Chocolate Doom Setup"
    VALUE "InternalName", "chocolate-setup"
    VALUE "CompanyName", "Chocolate Doom"
    VALUE "LegalCopyright", "GNU General Public License"
    VALUE "ProductName", "Chocolate Doom Setup"
-   VALUE "ProductVersion", "2.2.1"
+   VALUE "ProductVersion", "2.3.0"
   }
  }
  BLOCK "VarFileInfo"
--- a/configure.ac
+++ b/configure.ac
@@ -1,4 +1,5 @@
-AC_INIT(Chocolate Doom, 2.999.0, [email protected], chocolate-doom)
+AC_INIT(Chocolate Doom, 2.999.0, [email protected],
+        chocolate-doom)
 
 PACKAGE_SHORTNAME=${PACKAGE_NAME% Doom}
 PACKAGE_SHORTDESC="Conservative source port"
--- a/msvc/config.h
+++ b/msvc/config.h
@@ -11,19 +11,19 @@
 #define PACKAGE_NAME "Chocolate Doom"
 
 /* Define to the full name and version of this package. */
-#define PACKAGE_STRING "Chocolate Doom 2.2.1"
+#define PACKAGE_STRING "Chocolate Doom 2.3.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 "2.2.1"
+#define PACKAGE_VERSION "2.3.0"
 
 /* Change this when you create your awesome forked version */
 #define PROGRAM_PREFIX "chocolate-"
 
 /* Version number of package */
-#define VERSION "2.2.1"
+#define VERSION "2.3.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
@@ -25,8 +25,8 @@
 #endif
 
 1 VERSIONINFO
-PRODUCTVERSION 2,2,1,0
-FILEVERSION 2,2,1,0
+PRODUCTVERSION 2,3,0,0
+FILEVERSION 2,3,0,0
 FILETYPE 1
 BEGIN
 	BLOCK "StringFileInfo"
@@ -34,12 +34,12 @@
 		BLOCK "040904E4"
 		BEGIN
 			VALUE "FileVersion", "1.0.0"
-			VALUE "FileDescription", "Chocolate Doom 2.2.1"
+			VALUE "FileDescription", "Chocolate Doom 2.3.0"
 			VALUE "InternalName", "chocolate-doom"
 			VALUE "CompanyName", "[email protected]"
 			VALUE "LegalCopyright", "GNU General Public License"
 			VALUE "ProductName", "Chocolate Doom"
-			VALUE "ProductVersion", "2.2.1"
+			VALUE "ProductVersion", "2.3.0"
 		END
 	END
 	BLOCK "VarFileInfo"
--- a/src/d_loop.c
+++ b/src/d_loop.c
@@ -816,3 +816,85 @@
 {
     loop_interface = i;
 }
+
+// TODO: Move nonvanilla demo functions into a dedicated file.
+#include "m_misc.h"
+#include "w_wad.h"
+
+static boolean StrictDemos(void)
+{
+    //!
+    // @category demo
+    //
+    // When recording or playing back demos, disable any extensions
+    // of the vanilla demo format - record demos as vanilla would do,
+    // and play back demos as vanilla would do.
+    //
+    return M_ParmExists("-strictdemos");
+}
+
+// If the provided conditional value is true, we're trying to record
+// a demo file that will include a non-vanilla extension. The function
+// will return true if the conditional is true and it's allowed to use
+// this extension (no extensions are allowed if -strictdemos is given
+// on the command line). A warning is shown on the console using the
+// provided string describing the non-vanilla expansion.
+boolean D_NonVanillaRecord(boolean conditional, char *feature)
+{
+    if (!conditional || StrictDemos())
+    {
+        return false;
+    }
+
+    printf("Warning: Recording a demo file with a non-vanilla extension "
+           "(%s). Use -strictdemos to disable this extension.\n",
+           feature);
+
+    return true;
+}
+
+// Returns true if the given lump number corresponds to data from a .lmp
+// file, as opposed to a WAD.
+static boolean IsDemoFile(int lumpnum)
+{
+    char *lower;
+    boolean result;
+
+    lower = M_StringDuplicate(lumpinfo[lumpnum]->wad_file->path);
+    M_ForceLowercase(lower);
+    result = M_StringEndsWith(lower, ".lmp");
+    free(lower);
+
+    return result;
+}
+
+// If the provided conditional value is true, we're trying to play back
+// a demo that includes a non-vanilla extension. We return true if the
+// conditional is true and it's allowed to use this extension, checking
+// that:
+//  - The -strictdemos command line argument is not provided.
+//  - The given lumpnum identifying the demo to play back identifies a
+//    demo that comes from a .lmp file, not a .wad file.
+//  - Before proceeding, a warning is shown to the user on the console.
+boolean D_NonVanillaPlayback(boolean conditional, int lumpnum,
+                             char *feature)
+{
+    if (!conditional || StrictDemos())
+    {
+        return false;
+    }
+
+    if (!IsDemoFile(lumpnum))
+    {
+        printf("Warning: WAD contains demo with a non-vanilla extension "
+               "(%s)\n", feature);
+        return false;
+    }
+
+    printf("Warning: Playing back a demo file with a non-vanilla extension "
+           "(%s). Use -strictdemos to disable this extension.\n",
+           feature);
+
+    return true;
+}
+
--- a/src/d_loop.h
+++ b/src/d_loop.h
@@ -77,5 +77,12 @@
 extern boolean singletics;
 extern int gametic, ticdup;
 
+// Check if it is permitted to record a demo with a non-vanilla feature.
+boolean D_NonVanillaRecord(boolean conditional, char *feature);
+
+// Check if it is permitted to play back a demo with a non-vanilla feature.
+boolean D_NonVanillaPlayback(boolean conditional, int lumpnum,
+                             char *feature);
+
 #endif
 
--- a/src/doom/d_main.c
+++ b/src/doom/d_main.c
@@ -1440,19 +1440,6 @@
     D_IdentifyVersion();
     InitGameVersion();
 
-    //!
-    // @category mod
-    //
-    // Disable automatic loading of Dehacked patches for certain
-    // IWAD files.
-    //
-    if (!M_ParmExists("-nodeh"))
-    {
-        // Some IWADs have dehacked patches that need to be loaded for
-        // them to be played properly.
-        LoadIwadDeh();
-    }
-
     // Check which IWAD variant we are using.
 
     if (W_CheckNumForName("FREEDOOM") >= 0)
@@ -1469,6 +1456,19 @@
     else if (W_CheckNumForName("DMENUPIC") >= 0)
     {
         gamevariant = bfgedition;
+    }
+
+    //!
+    // @category mod
+    //
+    // Disable automatic loading of Dehacked patches for certain
+    // IWAD files.
+    //
+    if (!M_ParmExists("-nodeh"))
+    {
+        // Some IWADs have dehacked patches that need to be loaded for
+        // them to be played properly.
+        LoadIwadDeh();
     }
 
     // Doom 3: BFG Edition includes modified versions of the classic
--- a/src/doom/g_game.c
+++ b/src/doom/g_game.c
@@ -2057,6 +2057,8 @@
 { 
     int             i; 
 
+    demo_p = demobuffer;
+
     //!
     // @category demo
     //
@@ -2063,16 +2065,12 @@
     // Record a high resolution "Doom 1.91" demo.
     //
 
-    longtics = M_CheckParm("-longtics") != 0;
+    longtics = D_NonVanillaRecord(M_ParmExists("-longtics"),
+                                  "Doom 1.91 demo format");
 
     // If not recording a longtics demo, record in low res
-
     lowres_turn = !longtics;
-    
-    demo_p = demobuffer;
-	
-    // Save the right version code for this demo
- 
+
     if (longtics)
     {
         *demo_p++ = DOOM_191_VERSION;
@@ -2128,6 +2126,8 @@
             return "v1.8";
         case 109:
             return "v1.9";
+        case 111:
+            return "v1.91 hack demo?";
         default:
             break;
     }
@@ -2147,27 +2147,29 @@
     }
 }
 
-void G_DoPlayDemo (void) 
-{ 
-    skill_t skill; 
-    int             i, episode, map; 
+void G_DoPlayDemo (void)
+{
+    skill_t skill;
+    int i, lumpnum, episode, map;
     int demoversion;
-	 
-    gameaction = ga_nothing; 
-    demobuffer = demo_p = W_CacheLumpName (defdemoname, PU_STATIC); 
 
+    lumpnum = W_GetNumForName(defdemoname);
+    gameaction = ga_nothing;
+    demobuffer = W_CacheLumpNum(lumpnum, PU_STATIC);
+    demo_p = demobuffer;
+
     demoversion = *demo_p++;
 
-    if (demoversion == G_VanillaVersionCode())
+    longtics = false;
+
+    // Longtics demos use the modified format that is generated by cph's
+    // hacked "v1.91" doom exe. This is a non-vanilla extension.
+    if (D_NonVanillaPlayback(demoversion == DOOM_191_VERSION, lumpnum,
+                             "Doom 1.91 demo format"))
     {
-        longtics = false;
-    }
-    else if (demoversion == DOOM_191_VERSION)
-    {
-        // demo recorded with cph's modified "v1.91" doom exe
         longtics = true;
     }
-    else
+    else if (demoversion != G_VanillaVersionCode())
     {
         char *message = "Demo is from a different game version!\n"
                         "(read %i, should be %i)\n"
@@ -2181,7 +2183,7 @@
         I_Error(message, demoversion, G_VanillaVersionCode(),
                          DemoVersionDescription(demoversion));
     }
-    
+
     skill = *demo_p++; 
     episode = *demo_p++; 
     map = *demo_p++; 
--- a/src/heretic/g_game.c
+++ b/src/heretic/g_game.c
@@ -1791,7 +1791,8 @@
     // Record or playback a demo with high resolution turning.
     //
 
-    longtics = M_ParmExists("-longtics");
+    longtics = D_NonVanillaRecord(M_ParmExists("-longtics"),
+                                  "vvHeretic longtics demo");
 
     // If not recording a longtics demo, record in low res
 
@@ -1837,7 +1838,7 @@
     //   0x02 = -nomonsters
 
     *demo_p = 1; // assume player one exists
-    if (respawnparm)
+    if (D_NonVanillaRecord(respawnparm, "vvHeretic -respawn header flag"))
     {
         *demo_p |= DEMOHEADER_RESPAWN;
     }
@@ -1845,7 +1846,7 @@
     {
         *demo_p |= DEMOHEADER_LONGTICS;
     }
-    if (nomonsters)
+    if (D_NonVanillaRecord(nomonsters, "vvHeretic -nomonsters header flag"))
     {
         *demo_p |= DEMOHEADER_NOMONSTERS;
     }
@@ -1877,20 +1878,33 @@
 void G_DoPlayDemo(void)
 {
     skill_t skill;
-    int i, episode, map;
+    int i, lumpnum, episode, map;
 
     gameaction = ga_nothing;
-    demobuffer = demo_p = W_CacheLumpName(defdemoname, PU_STATIC);
+    lumpnum = W_GetNumForName(defdemoname);
+    demobuffer = W_CacheLumpNum(lumpnum, PU_STATIC);
+    demo_p = demobuffer;
     skill = *demo_p++;
     episode = *demo_p++;
     map = *demo_p++;
 
-    // Read special parameter bits: see G_RecordDemo() for details.
-    longtics = (*demo_p & DEMOHEADER_LONGTICS) != 0;
-
-    // don't overwrite arguments from the command line
-    respawnparm |= (*demo_p & DEMOHEADER_RESPAWN) != 0;
-    nomonsters  |= (*demo_p & DEMOHEADER_NOMONSTERS) != 0;
+    // vvHeretic allows extra options to be stored in the upper bits of
+    // the player 1 present byte. However, this is a non-vanilla extension.
+    if (D_NonVanillaPlayback((*demo_p & DEMOHEADER_LONGTICS) != 0,
+                             lumpnum, "vvHeretic longtics demo"))
+    {
+        longtics = true;
+    }
+    if (D_NonVanillaPlayback((*demo_p & DEMOHEADER_RESPAWN) != 0,
+                             lumpnum, "vvHeretic -respawn header flag"))
+    {
+        respawnparm = true;
+    }
+    if (D_NonVanillaPlayback((*demo_p & DEMOHEADER_NOMONSTERS) != 0,
+                             lumpnum, "vvHeretic -nomonsters header flag"))
+    {
+        nomonsters = true;
+    }
 
     for (i = 0; i < MAXPLAYERS; i++)
         playeringame[i] = (*demo_p++) != 0;
--- a/src/heretic/p_enemy.c
+++ b/src/heretic/p_enemy.c
@@ -845,8 +845,8 @@
     r1 = P_SubRandom();
     r2 = P_SubRandom();
 
-    mo = P_SpawnMobj(actor->x + (r1 << 11),
-                     actor->y + (r2 << 11), actor->z,
+    mo = P_SpawnMobj(actor->x + (r2 << 11),
+                     actor->y + (r1 << 11), actor->z,
                      MT_BLOOD);
     mo->momx = P_SubRandom() << 10;
     mo->momy = P_SubRandom() << 10;
@@ -920,9 +920,9 @@
         r1 = P_SubRandom();
         r2 = P_SubRandom();
         r3 = P_SubRandom();
-        P_SpawnMobj(actor->x + (r1 << 10),
+        P_SpawnMobj(actor->x + (r3 << 10),
                     actor->y + (r2 << 10),
-                    actor->z + (r3 << 10), MT_PUFFY);
+                    actor->z + (r1 << 10), MT_PUFFY);
     }
 }
 
@@ -1769,8 +1769,8 @@
     r2 = P_SubRandom();
 
     actor->z = actor->floorz;
-    mo = P_SpawnMobj(actor->x + (r1 << 10),
-                     actor->y + (r2 << 10), ONFLOORZ,
+    mo = P_SpawnMobj(actor->x + (r2 << 10),
+                     actor->y + (r1 << 10), ONFLOORZ,
                      MT_MNTRFX3);
     mo->target = actor->target;
     mo->momx = 1;               // Force block checking
@@ -2416,11 +2416,12 @@
 void A_SpawnTeleGlitter(mobj_t * actor)
 {
     mobj_t *mo;
-    int r;
+    int r1, r2;
 
-    r = P_Random();
-    mo = P_SpawnMobj(actor->x + ((r & 31) - 16) * FRACUNIT,
-                     actor->y + ((P_Random() & 31) - 16) * FRACUNIT,
+    r1 = P_Random();
+    r2 = P_Random();
+    mo = P_SpawnMobj(actor->x + ((r2 & 31) - 16) * FRACUNIT,
+                     actor->y + ((r1 & 31) - 16) * FRACUNIT,
                      actor->subsector->sector->floorheight, MT_TELEGLITTER);
     mo->momz = FRACUNIT / 4;
 }
@@ -2434,11 +2435,12 @@
 void A_SpawnTeleGlitter2(mobj_t * actor)
 {
     mobj_t *mo;
-    int r;
+    int r1, r2;
 
-    r = P_Random();
-    mo = P_SpawnMobj(actor->x + ((r & 31) - 16) * FRACUNIT,
-                     actor->y + ((P_Random() & 31) - 16) * FRACUNIT,
+    r1 = P_Random();
+    r2 = P_Random();
+    mo = P_SpawnMobj(actor->x + ((r2 & 31) - 16) * FRACUNIT,
+                     actor->y + ((r1 & 31) - 16) * FRACUNIT,
                      actor->subsector->sector->floorheight, MT_TELEGLITTER2);
     mo->momz = FRACUNIT / 4;
 }
--- a/src/hexen/a_action.c
+++ b/src/hexen/a_action.c
@@ -1175,9 +1175,9 @@
         r1 = P_Random();
         r2 = P_Random();
         r3 = P_Random();
-        mo = P_SpawnMobj(actor->x + ((r1 - 128) << 12),
+        mo = P_SpawnMobj(actor->x + ((r3 - 128) << 12),
                          actor->y + ((r2 - 128) << 12),
-                         actor->z + (r3 * actor->height / 256),
+                         actor->z + (r1 * actor->height / 256),
                          MT_ZARMORCHUNK);
         P_SetMobjState(mo, mo->info->spawnstate + i);
         if (mo)
--- a/src/hexen/g_game.c
+++ b/src/hexen/g_game.c
@@ -1959,7 +1959,8 @@
     // Record or playback a demo with high resolution turning.
     //
 
-    longtics = M_ParmExists("-longtics");
+    longtics = D_NonVanillaRecord(M_ParmExists("-longtics"),
+                                  "vvHeretic longtics demo");
 
     // If not recording a longtics demo, record in low res
 
@@ -2006,7 +2007,7 @@
     //   0x02 = -nomonsters
 
     *demo_p = 1; // assume player one exists
-    if (respawnparm)
+    if (D_NonVanillaRecord(respawnparm, "vvHeretic -respawn header flag"))
     {
         *demo_p |= DEMOHEADER_RESPAWN;
     }
@@ -2014,7 +2015,7 @@
     {
         *demo_p |= DEMOHEADER_LONGTICS;
     }
-    if (nomonsters)
+    if (D_NonVanillaRecord(nomonsters, "vvHeretic -nomonsters header flag"))
     {
         *demo_p |= DEMOHEADER_NOMONSTERS;
     }
@@ -2050,20 +2051,36 @@
 void G_DoPlayDemo(void)
 {
     skill_t skill;
-    int i, episode, map;
+    int i, lumpnum, episode, map;
 
     gameaction = ga_nothing;
-    demobuffer = demo_p = W_CacheLumpName(defdemoname, PU_STATIC);
+    lumpnum = W_GetNumForName(defdemoname);
+    demobuffer = W_CacheLumpNum(lumpnum, PU_STATIC);
+    demo_p = demobuffer;
     skill = *demo_p++;
     episode = *demo_p++;
     map = *demo_p++;
 
-    // Read special parameter bits: see G_RecordDemo() for details.
-    longtics = (*demo_p & DEMOHEADER_LONGTICS) != 0;
-
-    // don't overwrite arguments from the command line
-    respawnparm |= (*demo_p & DEMOHEADER_RESPAWN) != 0;
-    nomonsters  |= (*demo_p & DEMOHEADER_NOMONSTERS) != 0;
+    // When recording we store some extra options inside the upper bits
+    // of the player 1 present byte. However, this is a non-vanilla extension.
+    // Note references to vvHeretic here; these are the extensions used by
+    // vvHeretic, which we're just reusing for Hexen demos too. There is no
+    // vvHexen.
+    if (D_NonVanillaPlayback((*demo_p & DEMOHEADER_LONGTICS) != 0,
+                             lumpnum, "vvHeretic longtics demo"))
+    {
+        longtics = true;
+    }
+    if (D_NonVanillaPlayback((*demo_p & DEMOHEADER_RESPAWN) != 0,
+                             lumpnum, "vvHeretic -respawn header flag"))
+    {
+        respawnparm = true;
+    }
+    if (D_NonVanillaPlayback((*demo_p & DEMOHEADER_NOMONSTERS) != 0,
+                             lumpnum, "vvHeretic -nomonsters header flag"))
+    {
+        nomonsters = true;
+    }
 
     for (i = 0; i < maxplayers; i++)
     {
--- a/src/hexen/p_enemy.c
+++ b/src/hexen/p_enemy.c
@@ -1481,13 +1481,14 @@
 void A_MntrFloorFire(mobj_t * actor)
 {
     mobj_t *mo;
-    int r;
+    int r1, r2;
 
-    r = P_SubRandom();
+    r1 = P_SubRandom();
+    r2 = P_SubRandom();
 
     actor->z = actor->floorz;
-    mo = P_SpawnMobj(actor->x + (r << 10),
-                     actor->y + (P_SubRandom() << 10), ONFLOORZ,
+    mo = P_SpawnMobj(actor->x + (r2 << 10),
+                     actor->y + (r1 << 10), ONFLOORZ,
                      MT_MNTRFX3);
     mo->target = actor->target;
     mo->momx = 1;               // Force block checking
@@ -2411,10 +2412,12 @@
 void A_SerpentSpawnGibs(mobj_t * actor)
 {
     mobj_t *mo;
-    int r = P_Random();
+    int r1, r2;
 
-    mo = P_SpawnMobj(actor->x + ((r - 128) << 12),
-                     actor->y + ((P_Random() - 128) << 12),
+    r1 = P_Random();
+    r2 = P_Random();
+    mo = P_SpawnMobj(actor->x + ((r2 - 128) << 12),
+                     actor->y + ((r1 - 128) << 12),
                      actor->floorz + FRACUNIT, MT_SERPENT_GIB1);
     if (mo)
     {
@@ -2422,9 +2425,10 @@
         mo->momy = (P_Random() - 128) << 6;
         mo->floorclip = 6 * FRACUNIT;
     }
-    r = P_Random();
-    mo = P_SpawnMobj(actor->x + ((r - 128) << 12),
-                     actor->y + ((P_Random() - 128) << 12),
+    r1 = P_Random();
+    r2 = P_Random();
+    mo = P_SpawnMobj(actor->x + ((r2 - 128) << 12),
+                     actor->y + ((r1 - 128) << 12),
                      actor->floorz + FRACUNIT, MT_SERPENT_GIB2);
     if (mo)
     {
@@ -2432,9 +2436,10 @@
         mo->momy = (P_Random() - 128) << 6;
         mo->floorclip = 6 * FRACUNIT;
     }
-    r = P_Random();
-    mo = P_SpawnMobj(actor->x + ((r - 128) << 12),
-                     actor->y + ((P_Random() - 128) << 12),
+    r1 = P_Random();
+    r2 = P_Random();
+    mo = P_SpawnMobj(actor->x + ((r2 - 128) << 12),
+                     actor->y + ((r1 - 128) << 12),
                      actor->floorz + FRACUNIT, MT_SERPENT_GIB3);
     if (mo)
     {
@@ -2797,9 +2802,9 @@
     r2 = P_SubRandom();
     r3 = P_SubRandom();
 
-    mo = P_SpawnMobj(actor->x + (r1 << 12), actor->y
+    mo = P_SpawnMobj(actor->x + (r3 << 12), actor->y
                      + (r2 << 12),
-                     actor->z + (r3 << 11),
+                     actor->z + (r1 << 11),
                      MT_BISHOPPAINBLUR);
     if (mo)
     {
@@ -3048,9 +3053,9 @@
         r1 = P_Random();
         r2 = P_Random();
         r3 = P_Random();
-        mo = P_SpawnMobj(actor->x + ((r1 - 128) << 14),
+        mo = P_SpawnMobj(actor->x + ((r3 - 128) << 14),
                          actor->y + ((r2 - 128) << 14),
-                         actor->z + ((r3 - 128) << 12),
+                         actor->z + ((r1 - 128) << 12),
                          MT_DRAGON_FX2);
         if (mo)
         {
@@ -4865,10 +4870,10 @@
         r2 = P_Random();
         r3 = P_Random();
         mo = P_SpawnMobj(actor->x +
-                         (((r1 - 128) * actor->radius) >> 7),
+                         (((r3 - 128) * actor->radius) >> 7),
                          actor->y +
                          (((r2 - 128) * actor->radius) >> 7),
-                         actor->z + (r3 * actor->height / 255),
+                         actor->z + (r1 * actor->height / 255),
                          MT_ICECHUNK);
         P_SetMobjState(mo, mo->info->spawnstate + (P_Random() % 3));
         if (mo)
@@ -4885,10 +4890,10 @@
         r2 = P_Random();
         r3 = P_Random();
         mo = P_SpawnMobj(actor->x +
-                         (((r1 - 128) * actor->radius) >> 7),
+                         (((r3 - 128) * actor->radius) >> 7),
                          actor->y +
                          (((r2 - 128) * actor->radius) >> 7),
-                         actor->z + (r3 * actor->height / 255),
+                         actor->z + (r1 * actor->height / 255),
                          MT_ICECHUNK);
         P_SetMobjState(mo, mo->info->spawnstate + (P_Random() % 3));
         if (mo)
--- a/src/hexen/p_mobj.c
+++ b/src/hexen/p_mobj.c
@@ -1830,9 +1830,12 @@
 void P_BloodSplatter2(fixed_t x, fixed_t y, fixed_t z, mobj_t * originator)
 {
     mobj_t *mo;
+    int r1, r2;
 
-    mo = P_SpawnMobj(x + ((P_Random() - 128) << 11),
-                     y + ((P_Random() - 128) << 11), z, MT_AXEBLOOD);
+    r1 = P_Random();
+    r2 = P_Random();
+    mo = P_SpawnMobj(x + ((r2 - 128) << 11),
+                     y + ((r1 - 128) << 11), z, MT_AXEBLOOD);
     mo->target = originator;
 }
 
--- a/src/hexen/p_pspr.c
+++ b/src/hexen/p_pspr.c
@@ -868,9 +868,9 @@
         r1 = P_Random();
         r2 = P_Random();
         r3 = P_Random();
-        P_SpawnMobj(actor->x + ((r1 - 128) << 12), actor->y
+        P_SpawnMobj(actor->x + ((r3 - 128) << 12), actor->y
                     + ((r2 - 128) << 12),
-                    actor->z + ((r3 - 128) << 11), MT_FSWORD_FLAME);
+                    actor->z + ((r1 - 128) << 11), MT_FSWORD_FLAME);
     }
 }
 
@@ -1003,8 +1003,8 @@
     }
     r1 = P_Random();
     r2 = P_Random();
-    mo = P_SpawnMobj(actor->x + ((r1 - 128) * actor->radius / 256),
-                     actor->y + ((r2 - 128) * actor->radius / 256),
+    mo = P_SpawnMobj(actor->x + ((r2 - 128) * actor->radius / 256),
+                     actor->y + ((r1 - 128) * actor->radius / 256),
                      actor->z + deltaZ, MT_LIGHTNING_ZAP);
     if (mo)
     {
--- a/src/i_oplmusic.c
+++ b/src/i_oplmusic.c
@@ -101,10 +101,6 @@
 
 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;
@@ -149,7 +145,8 @@
     unsigned int note_volume;
 
     // The current volume (register value) that has been set for this channel.
-    unsigned int reg_volume;
+    unsigned int car_volume;
+    unsigned int mod_volume;
 
     // Pan.
     unsigned int reg_pan;
@@ -156,10 +153,6 @@
 
     // Priority.
     unsigned int priority;
-
-    // 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.
@@ -325,12 +318,17 @@
 // Voices:
 
 static opl_voice_t voices[OPL_NUM_VOICES * 2];
-static opl_voice_t *voice_free_list;
-static opl_voice_t *voice_alloced_list;
+static opl_voice_t *voice_free_list[OPL_NUM_VOICES * 2];
+static opl_voice_t *voice_alloced_list[OPL_NUM_VOICES * 2];
+static int voice_free_num;
 static int voice_alloced_num;
 static int opl_opl3mode;
 static int num_opl_voices;
 
+// Data for each channel.
+
+static opl_channel_data_t channels[MIDI_CHANNELS_PER_TRACK];
+
 // Track data for playing tracks:
 
 static opl_track_data_t *tracks;
@@ -366,17 +364,10 @@
 {
     byte *lump;
 
-    lump = W_CacheLumpName("GENMIDI", PU_STATIC);
+    lump = W_CacheLumpName(DEH_String("genmidi"), PU_STATIC);
 
-    // Check header
+    // DMX does not 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;
     main_instr_names =
@@ -391,11 +382,11 @@
 static opl_voice_t *GetFreeVoice(void)
 {
     opl_voice_t *result;
-    opl_voice_t **rover;
+    int i;
 
     // None available?
 
-    if (voice_free_list == NULL)
+    if (voice_free_num == 0)
     {
         return NULL;
     }
@@ -402,86 +393,66 @@
 
     // Remove from free list
 
-    result = voice_free_list;
-    voice_free_list = voice_free_list->next;
+    result = voice_free_list[0];
 
-    // Add to allocated list
+    voice_free_num--;
 
-    rover = &voice_alloced_list;
-
-    while (*rover != NULL)
+    for (i = 0; i < voice_free_num; i++)
     {
-        rover = &(*rover)->next;
+        voice_free_list[i] = voice_free_list[i + 1];
     }
 
-    *rover = result;
-    result->next = NULL;
+    // Add to allocated list
 
-    voice_alloced_num++;
+    voice_alloced_list[voice_alloced_num++] = result;
 
     return result;
 }
 
-// Remove a voice from the allocated voices list.
+// Release a voice back to the freelist.
 
-static void RemoveVoiceFromAllocedList(opl_voice_t *voice)
+static void VoiceKeyOff(opl_voice_t *voice);
+
+static void ReleaseVoice(int index)
 {
-    opl_voice_t **rover;
+    opl_voice_t *voice;
+    boolean double_voice;
+    int i;
 
-    rover = &voice_alloced_list;
-
-    // Search the list until we find the voice, then remove it.
-
-    while (*rover != NULL)
+    // Doom 2 1.666 OPL crash emulation.
+    if (index >= voice_alloced_num)
     {
-        if (*rover == voice)
-        {
-            *rover = voice->next;
-            voice->next = NULL;
-            voice_alloced_num--;
-            break;
-        }
 
-        rover = &(*rover)->next;
+        voice_alloced_num = 0;
+        voice_free_num = 0;
+        return;
     }
-}
 
-// Release a voice back to the freelist.
+    voice = voice_alloced_list[index];
 
-static void VoiceKeyOff(opl_voice_t *voice);
+    VoiceKeyOff(voice);
 
-static void ReleaseVoice(opl_voice_t *voice)
-{
-    opl_voice_t **rover;
-    opl_voice_t *next;
-    boolean double_voice;
-
     voice->channel = NULL;
     voice->note = 0;
 
     double_voice = voice->current_instr_voice != 0;
-    next = voice->next;
-
+    
     // Remove from alloced list.
 
-    RemoveVoiceFromAllocedList(voice);
+    voice_alloced_num--;
 
-    // Search to the end of the freelist (This is how Doom behaves!)
-
-    rover = &voice_free_list;
-
-    while (*rover != NULL)
+    for (i = index; i < voice_alloced_num; i++)
     {
-        rover = &(*rover)->next;
+        voice_alloced_list[i] = voice_alloced_list[i + 1];
     }
 
-    *rover = voice;
-    voice->next = NULL;
+    // Search to the end of the freelist (This is how Doom behaves!)
 
-    if (next != NULL && double_voice && opl_drv_ver != opl_doom_1_9)
+    voice_free_list[voice_free_num++] = voice;
+
+    if (double_voice && opl_drv_ver < opl_doom_1_9)
     {
-        VoiceKeyOff(next);
-        ReleaseVoice(next);
+        ReleaseVoice(index);
     }
 }
 
@@ -488,7 +459,7 @@
 // Load data to the specified operator
 
 static void LoadOperatorData(int operator, genmidi_op_t *data,
-                             boolean max_level)
+                             boolean max_level, unsigned int *volume)
 {
     int level;
 
@@ -495,13 +466,19 @@
     // 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);
+    level = data->scale;
 
     if (max_level)
     {
         level |= 0x3f;
     }
+    else
+    {
+        level |= data->level;
+    }
 
+    *volume = level;
+
     OPL_WriteRegister(OPL_REGS_LEVEL + operator, level);
     OPL_WriteRegister(OPL_REGS_TREMOLO + operator, data->tremolo);
     OPL_WriteRegister(OPL_REGS_ATTACK + operator, data->attack);
@@ -540,8 +517,10 @@
     // is set in SetVoiceVolume (below).  If we are not using
     // modulating mode, we must set both to minimum volume.
 
-    LoadOperatorData(voice->op2 | voice->array, &data->carrier, true);
-    LoadOperatorData(voice->op1 | voice->array, &data->modulator, !modulating);
+    LoadOperatorData(voice->op2 | voice->array, &data->carrier, true,
+                     &voice->car_volume);
+    LoadOperatorData(voice->op1 | voice->array, &data->modulator, !modulating,
+                     &voice->mod_volume);
 
     // Set feedback register that control the connection between the
     // two operators.  Turn on bits in the upper nybble; I think this
@@ -550,10 +529,6 @@
     OPL_WriteRegister((OPL_REGS_FEEDBACK + voice->index) | voice->array,
                       data->feedback | voice->reg_pan);
 
-    // Hack to force a volume update.
-
-    voice->reg_volume = 999;
-
     // Calculate voice priority.
 
     voice->priority = 0x0f - (data->carrier.attack >> 4)
@@ -584,12 +559,12 @@
 
     // Update the volume register(s) if necessary.
 
-    if (car_volume != voice->reg_volume)
+    if (car_volume != (voice->car_volume & 0x3f))
     {
-        voice->reg_volume = car_volume | (opl_voice->carrier.scale & 0xc0);
+        voice->car_volume = car_volume | (voice->car_volume & 0xc0);
 
         OPL_WriteRegister((OPL_REGS_LEVEL + voice->op2) | voice->array,
-                          voice->reg_volume);
+                          voice->car_volume);
 
         // If we are using non-modulated feedback mode, we must set the
         // volume for both voices.
@@ -597,14 +572,21 @@
         if ((opl_voice->feedback & 0x01) != 0
          && opl_voice->modulator.level != 0x3f)
         {
-            mod_volume = 0x3f - opl_voice->modulator.level;
-            if (mod_volume >= car_volume)
+            mod_volume = opl_voice->modulator.level;
+            if (mod_volume < car_volume)
             {
                 mod_volume = car_volume;
             }
-            OPL_WriteRegister((OPL_REGS_LEVEL + voice->op1) | voice->array,
-                              mod_volume |
-                              (opl_voice->modulator.scale & 0xc0));
+
+            mod_volume |= voice->mod_volume & 0xc0;
+
+            if(mod_volume != voice->mod_volume)
+            {
+                voice->mod_volume = mod_volume;
+                OPL_WriteRegister((OPL_REGS_LEVEL + voice->op1) | voice->array,
+                                  mod_volume |
+                                  (opl_voice->modulator.scale & 0xc0));
+            }
         }
     }
 }
@@ -627,9 +609,10 @@
     int i;
 
     // Start with an empty free list.
+    
+    voice_free_num = num_opl_voices;
+    voice_alloced_num = 0;
 
-    voice_free_list = NULL;
-
     // Initialize each voice.
 
     for (i = 0; i < num_opl_voices; ++i)
@@ -642,7 +625,7 @@
 
         // Add this voice to the freelist.
 
-        ReleaseVoice(&voices[i]);
+        voice_free_list[i] = &voices[i];
     }
 }
 
@@ -653,7 +636,7 @@
 
 static void I_OPL_SetMusicVolume(int volume)
 {
-    unsigned int i, j;
+    unsigned int i;
 
     if (current_music_volume == volume)
     {
@@ -666,20 +649,16 @@
 
     // Update the volume of all voices.
 
-    for (i = 0; i < num_tracks; ++i)
+    for (i = 0; i < MIDI_CHANNELS_PER_TRACK; ++i)
     {
-        for (j = 0; j < MIDI_CHANNELS_PER_TRACK; ++j)
+        if (i == 15)
         {
-            if (j == 15)
-            {
-                SetChannelVolume(&tracks[i].channels[j], volume, false);
-            }
-            else
-            {
-                SetChannelVolume(&tracks[i].channels[j],
-                                 tracks[i].channels[j].volume_base, false);
-            }
+            SetChannelVolume(&channels[i], volume, false);
         }
+        else
+        {
+            SetChannelVolume(&channels[i], channels[i].volume_base, false);
+        }
     }
 }
 
@@ -706,7 +685,7 @@
         channel_num = 9;
     }
 
-    return &track->channels[channel_num];
+    return &channels[channel_num];
 }
 
 // Get the frequency that we should be using for a voice.
@@ -714,8 +693,7 @@
 static void KeyOffEvent(opl_track_data_t *track, midi_event_t *event)
 {
     opl_channel_data_t *channel;
-    opl_voice_t *rover;
-    opl_voice_t *prev;
+    int i;
     unsigned int key;
 
 /*
@@ -731,32 +709,17 @@
     // Turn off voices being used to play this key.
     // If it is a double voice instrument there will be two.
 
-    rover = voice_alloced_list;
-    prev = NULL;
-
-    while (rover != NULL)
+    for (i = 0; i < voice_alloced_num; i++)
     {
-        if (rover->channel == channel && rover->key == key)
+        if (voice_alloced_list[i]->channel == channel
+         && voice_alloced_list[i]->key == key)
         {
-            VoiceKeyOff(rover);
-
             // Finished with this voice now.
 
-            ReleaseVoice(rover);
-            if (prev == NULL)
-            {
-                rover = voice_alloced_list;
-            }
-            else
-            {
-                rover = prev->next;
-            }
+            ReleaseVoice(i);
+
+            i--;
         }
-        else
-        {
-            prev = rover;
-            rover = rover->next;
-        }
     }
 }
 
@@ -767,8 +730,8 @@
 
 static void ReplaceExistingVoice(void)
 {
-    opl_voice_t *rover;
-    opl_voice_t *result;
+    int i;
+    int result;
 
     // Check the allocated voices, if we find an instrument that is
     // of a lower priority to the new instrument, discard it.
@@ -778,18 +741,18 @@
     // than higher-numbered channels, eg. MIDI channel 1 is never
     // discarded for MIDI channel 2.
 
-    result = voice_alloced_list;
+    result = 0;
 
-    for (rover = voice_alloced_list; rover != NULL; rover = rover->next)
+    for (i = 0; i < voice_alloced_num; i++)
     {
-        if (rover->current_instr_voice != 0
-         || rover->channel >= result->channel)
+        if (voice_alloced_list[i]->current_instr_voice != 0
+         || voice_alloced_list[i]->channel
+         >= voice_alloced_list[result]->channel)
         {
-            result = rover;
+            result = i;
         }
     }
 
-    VoiceKeyOff(result);
     ReleaseVoice(result);
 }
 
@@ -798,53 +761,43 @@
 
 static void ReplaceExistingVoiceDoom1(void)
 {
-    opl_voice_t *rover;
-    opl_voice_t *result;
+    int i;
+    int result;
 
-    result = voice_alloced_list;
+    result = 0;
 
-    for (rover = voice_alloced_list; rover != NULL; rover = rover->next)
+    for (i = 0; i < voice_alloced_num; i++)
     {
-        if (rover->channel > result->channel)
+        if (voice_alloced_list[i]->channel
+          > voice_alloced_list[result]->channel)
         {
-            result = rover;
+            result = i;
         }
     }
 
-    VoiceKeyOff(result);
     ReleaseVoice(result);
 }
 
 static void ReplaceExistingVoiceDoom2(opl_channel_data_t *channel)
 {
-    opl_voice_t *rover;
-    opl_voice_t *result;
-    opl_voice_t *roverend;
     int i;
+    int result;
     int priority;
 
-    result = voice_alloced_list;
+    result = 0;
 
-    roverend = voice_alloced_list;
+    priority = 0x8000;
 
     for (i = 0; i < voice_alloced_num - 3; i++)
     {
-        roverend = roverend->next;
-    }
-
-    priority = 0x8000;
-
-    for (rover = voice_alloced_list; rover != roverend; rover = rover->next)
-    {
-        if (rover->priority < priority
-         && rover->channel >= channel)
+        if (voice_alloced_list[i]->priority < priority
+         && voice_alloced_list[i]->channel >= channel)
         {
-            priority = rover->priority;
-            result = rover;
+            priority = voice_alloced_list[i]->priority;
+            result = i;
         }
     }
 
-    VoiceKeyOff(result);
     ReleaseVoice(result);
 }
 
@@ -1101,7 +1054,7 @@
             break;
         default:
         case opl_doom_1_9:
-            if (voice_free_list == NULL)
+            if (voice_free_num == 0)
             {
                 ReplaceExistingVoice();
             }
@@ -1210,35 +1163,18 @@
 // Handler for the MIDI_CONTROLLER_ALL_NOTES_OFF channel event.
 static void AllNotesOff(opl_channel_data_t *channel, unsigned int param)
 {
-    opl_voice_t *rover;
-    opl_voice_t *prev;
+    int i;
 
-    rover = voice_alloced_list;
-    prev = NULL;
-
-    while (rover!=NULL)
+    for (i = 0; i < voice_alloced_num; i++)
     {
-        if (rover->channel == channel)
+        if (voice_alloced_list[i]->channel == channel)
         {
-            VoiceKeyOff(rover);
-
             // Finished with this voice now.
 
-            ReleaseVoice(rover);
-            if (prev == NULL)
-            {
-                rover = voice_alloced_list;
-            }
-            else
-            {
-                rover = prev->next;
-            }
+            ReleaseVoice(i);
+            
+            i--;
         }
-        else
-        {
-            prev = rover;
-            rover = rover->next;
-        }
     }
 }
 
@@ -1286,7 +1222,11 @@
 static void PitchBendEvent(opl_track_data_t *track, midi_event_t *event)
 {
     opl_channel_data_t *channel;
-    unsigned int i;
+    int i;
+    opl_voice_t *voice_updated_list[OPL_NUM_VOICES * 2];
+    unsigned int voice_updated_num = 0;
+    opl_voice_t *voice_not_updated_list[OPL_NUM_VOICES * 2];
+    unsigned int voice_not_updated_num = 0;
 
     // Update the channel bend value.  Only the MSB of the pitch bend
     // value is considered: this is what Doom does.
@@ -1296,13 +1236,29 @@
 
     // Update all voices for this channel.
 
-    for (i = 0; i < num_opl_voices; ++i)
+	for (i = 0; i < voice_alloced_num; ++i)
     {
-        if (voices[i].channel == channel)
+        if (voice_alloced_list[i]->channel == channel)
         {
-            UpdateVoiceFrequency(&voices[i]);
+            UpdateVoiceFrequency(voice_alloced_list[i]);
+            voice_updated_list[voice_updated_num++] = voice_alloced_list[i];
         }
+        else
+        {
+            voice_not_updated_list[voice_not_updated_num++] =
+            voice_alloced_list[i];
+        }
     }
+
+    for (i = 0; i < voice_not_updated_num; i++)
+    {
+        voice_alloced_list[i] = voice_not_updated_list[i];
+    }
+
+    for (i = 0; i < voice_updated_num; i++)
+    {
+        voice_alloced_list[i + voice_not_updated_num] = voice_updated_list[i];
+    }
 }
 
 static void MetaSetTempo(unsigned int tempo)
@@ -1400,13 +1356,13 @@
 }
 
 static void ScheduleTrack(opl_track_data_t *track);
-static void InitChannel(opl_track_data_t *track, opl_channel_data_t *channel);
+static void InitChannel(opl_channel_data_t *channel);
 
 // Restart a song from the beginning.
 
 static void RestartSong(void *unused)
 {
-    unsigned int i, j;
+    unsigned int i;
 
     running_tracks = num_tracks;
 
@@ -1416,11 +1372,12 @@
     {
         MIDI_RestartIterator(tracks[i].iter);
         ScheduleTrack(&tracks[i]);
-        for (j = 0; j < MIDI_CHANNELS_PER_TRACK; ++j)
-        {
-            InitChannel(&tracks[i], &tracks[i].channels[j]);
-        }
     }
+
+    for (i = 0; i < MIDI_CHANNELS_PER_TRACK; ++i)
+    {
+        InitChannel(&channels[i]);
+    }
 }
 
 // Callback function invoked when another event needs to be read from
@@ -1485,7 +1442,7 @@
 
 // Initialize a channel.
 
-static void InitChannel(opl_track_data_t *track, opl_channel_data_t *channel)
+static void InitChannel(opl_channel_data_t *channel)
 {
     // TODO: Work out sensible defaults?
 
@@ -1505,16 +1462,10 @@
 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);
 
-    for (i = 0; i < MIDI_CHANNELS_PER_TRACK; ++i)
-    {
-        InitChannel(track, &track->channels[i]);
-    }
-
     // Schedule the first event.
 
     ScheduleTrack(track);
@@ -1555,6 +1506,11 @@
     {
         StartTrack(file, i);
     }
+
+    for (i = 0; i < MIDI_CHANNELS_PER_TRACK; ++i)
+    {
+        InitChannel(&channels[i]);
+    }
 }
 
 static void I_OPL_PauseSong(void)
@@ -1610,13 +1566,9 @@
 
     // Free all voices.
 
-    for (i = 0; i < num_opl_voices; ++i)
+    for (i = 0; i < MIDI_CHANNELS_PER_TRACK; ++i)
     {
-        if (voices[i].channel != NULL)
-        {
-            VoiceKeyOff(&voices[i]);
-            ReleaseVoice(&voices[i]);
-        }
+        AllNotesOff(&channels[i], 0);
     }
 
     // Free all track data.
@@ -1747,7 +1699,7 @@
 
         // Release GENMIDI lump
 
-        W_ReleaseLumpName("GENMIDI");
+        W_ReleaseLumpName(DEH_String("genmidi"));
 
         music_initialized = false;
     }
@@ -1854,7 +1806,7 @@
 
     for (i = MIDI_CHANNELS_PER_TRACK - 1; i >= 0; --i)
     {
-        if (tracks[0].channels[i].instrument != &main_instrs[0])
+        if (channels[i].instrument != &main_instrs[0])
         {
             return i + 1;
         }
@@ -1865,11 +1817,11 @@
 
 static int ChannelInUse(opl_channel_data_t *channel)
 {
-    opl_voice_t *voice;
+    int i;
 
-    for (voice = voice_alloced_list; voice != NULL; voice = voice->next)
+    for (i = 0; i < voice_alloced_num; i++)
     {
-        if (voice->channel == channel)
+        if (voice_alloced_list[i]->channel == channel)
         {
             return 1;
         }
@@ -1896,17 +1848,17 @@
 
     for (i = 0; i < NumActiveChannels(); ++i)
     {
-        if (tracks[0].channels[i].instrument == NULL)
+        if (channels[i].instrument == NULL)
         {
             continue;
         }
 
-        instr_num = tracks[0].channels[i].instrument - main_instrs;
+        instr_num = channels[i].instrument - main_instrs;
 
         M_snprintf(tmp, sizeof(tmp),
                    "chan %i: %c i#%i (%s)\n",
                    i,
-                   ChannelInUse(&tracks[0].channels[i]) ? '\'' : ' ',
+                   ChannelInUse(&channels[i]) ? '\'' : ' ',
                    instr_num + 1,
                    main_instr_names[instr_num]);
         M_StringConcat(result, tmp, result_len);
--- a/src/w_file.h
+++ b/src/w_file.h
@@ -28,35 +28,31 @@
 typedef struct
 {
     // Open a file for reading.
-
     wad_file_t *(*OpenFile)(char *path);
 
     // Close the specified file.
-
     void (*CloseFile)(wad_file_t *file);
 
     // Read data from the specified position in the file into the 
     // provided buffer.  Returns the number of bytes read.
-
     size_t (*Read)(wad_file_t *file, unsigned int offset,
                    void *buffer, size_t buffer_len);
-
 } wad_file_class_t;
 
 struct _wad_file_s
 {
     // Class of this file.
-
     wad_file_class_t *file_class;
 
     // If this is NULL, the file cannot be mapped into memory.  If this
     // is non-NULL, it is a pointer to the mapped file.
-
     byte *mapped;
 
     // Length of the file, in bytes.
-
     unsigned int length;
+
+    // File's location on disk.
+    const char *path;
 };
 
 // Open the specified file. Returns a pointer to a new wad_file_t 
--- a/src/w_file_posix.c
+++ b/src/w_file_posix.c
@@ -26,6 +26,7 @@
 #include <sys/mman.h>
 #include <string.h>
 
+#include "m_misc.h"
 #include "w_file.h"
 #include "z_zone.h"
 
@@ -90,6 +91,7 @@
     result = Z_Malloc(sizeof(posix_wad_file_t), PU_STATIC, 0);
     result->wad.file_class = &posix_wad_file;
     result->wad.length = GetFileLength(handle);
+    result->wad.path = M_StringDuplicate(path);
     result->handle = handle;
 
     // Try to map the file into memory with mmap:
--- a/src/w_file_stdc.c
+++ b/src/w_file_stdc.c
@@ -48,6 +48,7 @@
     result->wad.file_class = &stdc_wad_file;
     result->wad.mapped = NULL;
     result->wad.length = M_FileLength(fstream);
+    result->wad.path = M_StringDuplicate(path);
     result->fstream = fstream;
 
     return &result->wad;
--- a/src/w_file_win32.c
+++ b/src/w_file_win32.c
@@ -26,6 +26,7 @@
 #include <windows.h>
 
 #include "i_system.h"
+#include "m_misc.h"
 #include "w_file.h"
 #include "z_zone.h"
 
@@ -115,6 +116,7 @@
     result = Z_Malloc(sizeof(win32_wad_file_t), PU_STATIC, 0);
     result->wad.file_class = &win32_wad_file;
     result->wad.length = GetFileLength(handle);
+    result->wad.path = M_StringDuplicate(path);
     result->handle = handle;
 
     // Try to map the file into memory with mmap: