shithub: choc

Download patch

ref: fbc9b30c1033e63e0af5b31cef5a7eab95e428dc
parent: 0083adaa40f4af7d44a3744ca315edb8d87c6bd8
parent: 37d108a5abb9e81fde6f1f47169d1130294d08e0
author: Thomas A. Birkel <[email protected]>
date: Wed Nov 30 20:12:13 EST 2016

Merge branch 'master' of https://github.com/chocolate-doom/chocolate-doom into commandline-savedir

--- a/NEWS.md
+++ b/NEWS.md
@@ -1,79 +1,91 @@
 ## HEAD
 
 ### 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).
 
 ### 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)
+  * 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/configure.ac
+++ b/configure.ac
@@ -1,4 +1,5 @@
-AC_INIT(Chocolate Doom, 2.2.1, [email protected], chocolate-doom)
+AC_INIT(Chocolate Doom, 2.2.1, [email protected],
+        chocolate-doom)
 
 PACKAGE_SHORTNAME=${PACKAGE_NAME% Doom}
 PACKAGE_SHORTDESC="Conservative source port"
--- a/src/d_iwad.c
+++ b/src/d_iwad.c
@@ -459,13 +459,15 @@
 static char *CheckDirectoryHasIWAD(char *dir, char *iwadname)
 {
     char *filename; 
+    char *probe;
 
     // As a special case, the "directory" may refer directly to an
     // IWAD file if the path comes from DOOMWADDIR or DOOMWADPATH.
 
-    if (DirIsFile(dir, iwadname) && M_FileExists(dir))
+    probe = M_FileCaseExists(dir);
+    if (DirIsFile(dir, iwadname) && probe != NULL)
     {
-        return M_StringDuplicate(dir);
+        return probe;
     }
 
     // Construct the full path to the IWAD if it is located in
@@ -480,9 +482,10 @@
         filename = M_StringJoin(dir, DIR_SEPARATOR_S, iwadname, NULL);
     }
 
-    if (M_FileExists(filename))
+    probe = M_FileCaseExists(filename);
+    if (probe != NULL)
     {
-        return filename;
+        return probe;
     }
 
     free(filename);
@@ -709,13 +712,15 @@
 char *D_FindWADByName(char *name)
 {
     char *path;
+    char *probe;
     int i;
     
     // Absolute path?
 
-    if (M_FileExists(name))
+    probe = M_FileCaseExists(name);
+    if (probe != NULL)
     {
-        return name;
+        return probe;
     }
 
     BuildIWADDirList();
@@ -728,9 +733,10 @@
         // the "directory" may actually refer directly to an IWAD
         // file.
 
-        if (DirIsFile(iwad_dirs[i], name) && M_FileExists(iwad_dirs[i]))
+        probe = M_FileCaseExists(iwad_dirs[i]);
+        if (DirIsFile(iwad_dirs[i], name) && probe != NULL)
         {
-            return M_StringDuplicate(iwad_dirs[i]);
+            return probe;
         }
 
         // Construct a string for the full path
@@ -737,9 +743,10 @@
 
         path = M_StringJoin(iwad_dirs[i], DIR_SEPARATOR_S, name, NULL);
 
-        if (M_FileExists(path))
+        probe = M_FileCaseExists(path);
+        if (probe != NULL)
         {
-            return path;
+            return probe;
         }
 
         free(path);
--- 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
@@ -1438,19 +1438,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)
@@ -1467,6 +1454,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
@@ -2056,6 +2056,8 @@
 { 
     int             i; 
 
+    demo_p = demobuffer;
+
     //!
     // @category demo
     //
@@ -2062,16 +2064,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;
@@ -2127,6 +2125,8 @@
             return "v1.8";
         case 109:
             return "v1.9";
+        case 111:
+            return "v1.91 hack demo?";
         default:
             break;
     }
@@ -2146,27 +2146,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"
@@ -2180,7 +2182,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
@@ -1790,7 +1790,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
 
@@ -1836,7 +1837,7 @@
     //   0x02 = -nomonsters
 
     *demo_p = 1; // assume player one exists
-    if (respawnparm)
+    if (D_NonVanillaRecord(respawnparm, "vvHeretic -respawn header flag"))
     {
         *demo_p |= DEMOHEADER_RESPAWN;
     }
@@ -1844,7 +1845,7 @@
     {
         *demo_p |= DEMOHEADER_LONGTICS;
     }
-    if (nomonsters)
+    if (D_NonVanillaRecord(nomonsters, "vvHeretic -nomonsters header flag"))
     {
         *demo_p |= DEMOHEADER_NOMONSTERS;
     }
@@ -1876,21 +1877,34 @@
 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;
+    // 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;
+    }
 
-    // don't overwrite arguments from the command line
-    respawnparm |= (*demo_p & DEMOHEADER_RESPAWN) != 0;
-    nomonsters  |= (*demo_p & DEMOHEADER_NOMONSTERS) != 0;
-
     for (i = 0; i < MAXPLAYERS; i++)
         playeringame[i] = (*demo_p++) != 0;
 
@@ -1921,9 +1935,11 @@
     map = *demo_p++;
 
     // Read special parameter bits: see G_RecordDemo() for details.
-    respawnparm = (*demo_p & DEMOHEADER_RESPAWN) != 0;
-    longtics    = (*demo_p & DEMOHEADER_LONGTICS) != 0;
-    nomonsters  = (*demo_p & DEMOHEADER_NOMONSTERS) != 0;
+    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;
 
     for (i = 0; i < MAXPLAYERS; i++)
     {
--- a/src/hexen/g_game.c
+++ b/src/hexen/g_game.c
@@ -312,13 +312,13 @@
     {
         forward -= forwardmove[pClass][speed];
     }
-    if (gamekeydown[key_straferight] || joystrafemove > 0
-     || joybuttons[joybstraferight])
+    if (gamekeydown[key_straferight] || mousebuttons[mousebstraferight]
+     || joystrafemove > 0 || joybuttons[joybstraferight])
     {
         side += sidemove[pClass][speed];
     }
-    if (gamekeydown[key_strafeleft] || joystrafemove < 0
-     || joybuttons[joybstrafeleft])
+    if (gamekeydown[key_strafeleft] || mousebuttons[mousebstrafeleft]
+     || joystrafemove < 0 || joybuttons[joybstrafeleft])
     {
         side -= sidemove[pClass][speed];
     }
@@ -504,57 +504,66 @@
     {
         forward += forwardmove[pClass][speed];
     }
+    if (mousebuttons[mousebbackward])
+    {
+        forward -= forwardmove[pClass][speed];
+    }
 
-//
-// forward double click
-//
-    if (mousebuttons[mousebforward] != dclickstate && dclicktime > 1)
+    // Double click to use can be disabled
+
+    if (dclick_use)
     {
-        dclickstate = mousebuttons[mousebforward];
-        if (dclickstate)
-            dclicks++;
-        if (dclicks == 2)
+        //
+        // forward double click
+        //
+        if (mousebuttons[mousebforward] != dclickstate && dclicktime > 1)
         {
-            cmd->buttons |= BT_USE;
-            dclicks = 0;
+            dclickstate = mousebuttons[mousebforward];
+            if (dclickstate)
+                dclicks++;
+            if (dclicks == 2)
+            {
+                cmd->buttons |= BT_USE;
+                dclicks = 0;
+            }
+            else
+                dclicktime = 0;
         }
         else
-            dclicktime = 0;
-    }
-    else
-    {
-        dclicktime += ticdup;
-        if (dclicktime > 20)
         {
-            dclicks = 0;
-            dclickstate = 0;
+            dclicktime += ticdup;
+            if (dclicktime > 20)
+            {
+                dclicks = 0;
+                dclickstate = 0;
+            }
         }
-    }
 
-//
-// strafe double click
-//
-    bstrafe = mousebuttons[mousebstrafe] || joybuttons[joybstrafe];
-    if (bstrafe != dclickstate2 && dclicktime2 > 1)
-    {
-        dclickstate2 = bstrafe;
-        if (dclickstate2)
-            dclicks2++;
-        if (dclicks2 == 2)
+        //
+        // strafe double click
+        //
+        bstrafe = mousebuttons[mousebstrafe] || joybuttons[joybstrafe];
+        if (bstrafe != dclickstate2 && dclicktime2 > 1)
         {
-            cmd->buttons |= BT_USE;
-            dclicks2 = 0;
+            dclickstate2 = bstrafe;
+            if (dclickstate2)
+                dclicks2++;
+            if (dclicks2 == 2)
+            {
+                cmd->buttons |= BT_USE;
+                dclicks2 = 0;
+            }
+            else
+                dclicktime2 = 0;
         }
         else
-            dclicktime2 = 0;
-    }
-    else
-    {
-        dclicktime2 += ticdup;
-        if (dclicktime2 > 20)
         {
-            dclicks2 = 0;
-            dclickstate2 = 0;
+            dclicktime2 += ticdup;
+            if (dclicktime2 > 20)
+            {
+                dclicks2 = 0;
+                dclickstate2 = 0;
+            }
         }
     }
 
@@ -1949,7 +1958,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
 
@@ -1996,7 +2006,7 @@
     //   0x02 = -nomonsters
 
     *demo_p = 1; // assume player one exists
-    if (respawnparm)
+    if (D_NonVanillaRecord(respawnparm, "vvHeretic -respawn header flag"))
     {
         *demo_p |= DEMOHEADER_RESPAWN;
     }
@@ -2004,7 +2014,7 @@
     {
         *demo_p |= DEMOHEADER_LONGTICS;
     }
-    if (nomonsters)
+    if (D_NonVanillaRecord(nomonsters, "vvHeretic -nomonsters header flag"))
     {
         *demo_p |= DEMOHEADER_NOMONSTERS;
     }
@@ -2040,21 +2050,37 @@
 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;
+    // 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;
+    }
 
-    // don't overwrite arguments from the command line
-    respawnparm |= (*demo_p & DEMOHEADER_RESPAWN) != 0;
-    nomonsters  |= (*demo_p & DEMOHEADER_NOMONSTERS) != 0;
-
     for (i = 0; i < maxplayers; i++)
     {
         playeringame[i] = (*demo_p++) != 0;
@@ -2091,9 +2117,11 @@
     map = *demo_p++;
 
     // Read special parameter bits: see G_RecordDemo() for details.
-    respawnparm = (*demo_p & DEMOHEADER_RESPAWN) != 0;
-    longtics    = (*demo_p & DEMOHEADER_LONGTICS) != 0;
-    nomonsters  = (*demo_p & DEMOHEADER_NOMONSTERS) != 0;
+    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;
 
     for (i = 0; i < maxplayers; i++)
     {
--- a/src/hexen/p_acs.c
+++ b/src/hexen/p_acs.c
@@ -56,8 +56,6 @@
 
 // PUBLIC FUNCTION PROTOTYPES ----------------------------------------------
 
-void CheckACSPresent(int number);
-
 // PRIVATE FUNCTION PROTOTYPES ---------------------------------------------
 
 static void StartOpenACS(int number, int infoIndex, int *address);
--- a/src/hexen/sv_save.c
+++ b/src/hexen/sv_save.c
@@ -3299,10 +3299,11 @@
         {
             I_Error ("Couldn't read file %s", source_name);
         }
+
         write_count = fwrite(buffer, 1, buf_count, write_handle);
-        if (read_count < buf_count)
+        if (write_count < buf_count)
         {
-            I_Error ("Couldn't read file %s", dest_name);
+            I_Error ("Couldn't write to file %s", dest_name);
         }
 
         file_remaining -= buf_count;
--- a/src/m_misc.c
+++ b/src/m_misc.c
@@ -83,6 +83,75 @@
     }
 }
 
+// Check if a file exists by probing for common case variation of its filename.
+// Returns a newly allocated string that the caller is responsible for freeing.
+
+char *M_FileCaseExists(char *path)
+{
+    char *path_dup, *filename, *ext;
+
+    path_dup = M_StringDuplicate(path);
+
+    // 0: actual path
+    if (M_FileExists(path_dup))
+    {
+        return path_dup;
+    }
+
+    filename = strrchr(path_dup, DIR_SEPARATOR);
+    if (filename != NULL)
+    {
+        filename++;
+    }
+    else
+    {
+        filename = path_dup;
+    }
+
+    // 1: lowercase filename, e.g. doom2.wad
+    M_ForceLowercase(filename);
+
+    if (M_FileExists(path_dup))
+    {
+        return path_dup;
+    }
+
+    // 2: uppercase filename, e.g. DOOM2.WAD
+    M_ForceUppercase(filename);
+
+    if (M_FileExists(path_dup))
+    {
+        return path_dup;
+    }
+
+    // 3. uppercase basename with lowercase extension, e.g. DOOM2.wad
+    ext = strrchr(path_dup, '.');
+    if (ext != NULL && ext > filename)
+    {
+        M_ForceLowercase(ext + 1);
+
+        if (M_FileExists(path_dup))
+        {
+            return path_dup;
+        }
+    }
+
+    // 4. lowercase filename with uppercase first letter, e.g. Doom2.wad
+    if (strlen(filename) > 1)
+    {
+        M_ForceLowercase(filename + 1);
+
+        if (M_FileExists(path_dup))
+        {
+            return path_dup;
+        }
+    }
+
+    // 5. no luck
+    free(path_dup);
+    return NULL;
+}
+
 //
 // Determine the length of an open file.
 //
@@ -247,6 +316,24 @@
     for (p = text; *p != '\0'; ++p)
     {
         *p = toupper(*p);
+    }
+}
+
+//---------------------------------------------------------------------------
+//
+// PROC M_ForceLowercase
+//
+// Change string to lowercase.
+//
+//---------------------------------------------------------------------------
+
+void M_ForceLowercase(char *text)
+{
+    char *p;
+
+    for (p = text; *p != '\0'; ++p)
+    {
+        *p = tolower(*p);
     }
 }
 
--- a/src/m_misc.h
+++ b/src/m_misc.h
@@ -30,10 +30,12 @@
 void M_MakeDirectory(char *dir);
 char *M_TempFile(char *s);
 boolean M_FileExists(char *file);
+char *M_FileCaseExists(char *file);
 long M_FileLength(FILE *handle);
 boolean M_StrToInt(const char *str, int *result);
 void M_ExtractFileBase(char *path, char *dest);
 void M_ForceUppercase(char *text);
+void M_ForceLowercase(char *text);
 char *M_StrCaseStr(char *haystack, char *needle);
 char *M_StringDuplicate(const char *orig);
 boolean M_StringCopy(char *dest, const char *src, size_t dest_size);
--- 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: