[chocolate-doom] 01/08: New upstream version 2.3.0

Fabian Greffrath fabian at moszumanska.debian.org
Fri Dec 30 14:20:51 UTC 2016


This is an automated email from the git hooks/post-receive script.

fabian pushed a commit to branch master
in repository chocolate-doom.

commit 9aff3e9fdb580a29a9aeeb66419846cd57fefc78
Author: Fabian Greffrath <fabian at debian.org>
Date:   Thu Dec 29 21:25:32 2016 +0100

    New upstream version 2.3.0
---
 .github/CONTRIBUTING.md               |   98 ++
 .gitignore                            |    1 +
 .travis.yml                           |   30 +
 AUTHORS                               |    3 +
 CODE_OF_CONDUCT.md                    |   74 +
 HACKING => HACKING.md                 |  124 +-
 Makefile.am                           |   26 +-
 NEWS                                  | 1115 -------------
 NEWS.md                               | 1193 ++++++++++++++
 NOT-BUGS => NOT-BUGS.md               |   94 +-
 PHILOSOPHY => PHILOSOPHY.md           |  128 +-
 README.Music => README.Music.md       |   97 +-
 README.Strife => README.Strife.md     |   73 +-
 README => README.md                   |   42 +-
 TODO                                  |   56 -
 TODO.md                               |   36 +
 codeblocks/config.h                   |    6 +-
 codeblocks/game-res.rc                |   10 +-
 codeblocks/libopl.cbp                 |    4 -
 codeblocks/setup-res.rc               |    8 +-
 configure.ac                          |   44 +-
 man/INSTALL.template                  |    5 +-
 man/Makefile.am                       |    4 +-
 man/bash-completion/.gitignore        |    4 +
 man/bash-completion/Makefile.am       |   44 +
 man/bash-completion/doom.template     |   51 +
 man/bash-completion/heretic.template  |   48 +
 man/bash-completion/hexen.template    |   42 +
 man/bash-completion/strife.template   |   48 +
 man/docgen                            |   34 +-
 msvc/.gitignore                       |    4 +
 msvc/config.h                         |    6 +-
 msvc/doom.vcproj                      |    8 +
 msvc/heretic.vcproj                   |    8 +
 msvc/hexen.vcproj                     |    9 +
 msvc/libopl.vcproj                    |   12 +-
 msvc/strife.vcproj                    |    8 +
 msvc/win32.rc                         |    8 +-
 opl/Makefile.am                       |    2 +-
 opl/dbopl.c                           | 1638 -------------------
 opl/dbopl.h                           |  203 ---
 opl/opl.c                             |    4 +-
 opl/opl3.c                            | 1415 +++++++++++++++++
 opl/opl3.h                            |  135 ++
 opl/opl_linux.c                       |    4 +-
 opl/opl_queue.c                       |    2 +-
 opl/opl_sdl.c                         |   37 +-
 pkg/config.make.in                    |    9 +-
 pkg/osx/AppController.m               |    5 +
 pkg/osx/Execute.m                     |    4 +
 pkg/osx/GNUmakefile                   |    7 +-
 pkg/osx/Info.plist.in                 |   12 +
 pkg/win32/GNUmakefile                 |    8 +-
 rpm.spec.in                           |   10 +-
 src/.gitignore                        |    2 +
 src/Makefile.am                       |   15 +-
 src/d_iwad.c                          |   60 +-
 src/d_loop.c                          |   82 +
 src/d_loop.h                          |    7 +
 src/d_mode.c                          |    5 +-
 src/d_mode.h                          |   10 +
 src/deh_io.c                          |    2 +-
 src/doom.appdata.xml.in               |    8 +-
 src/doom/am_map.c                     |   41 +-
 src/doom/d_main.c                     |  176 +-
 src/doom/doomstat.c                   |    1 +
 src/doom/doomstat.h                   |    4 +-
 src/doom/f_finale.c                   |   18 +-
 src/doom/f_wipe.c                     |   12 +-
 src/doom/g_game.c                     |  143 +-
 src/doom/hu_stuff.c                   |   61 +-
 src/doom/m_menu.c                     |  149 +-
 src/doom/m_random.c                   |   20 +-
 src/doom/m_random.h                   |    3 +-
 src/doom/p_doors.c                    |    7 +
 src/doom/p_enemy.c                    |   12 +-
 src/doom/p_map.c                      |    8 +-
 src/doom/p_mobj.c                     |   18 +-
 src/doom/p_pspr.c                     |   10 +-
 src/doom/p_saveg.c                    |    5 +-
 src/doom/p_saveg.h                    |    3 +
 src/doom/p_setup.c                    |   12 +-
 src/doom/p_spec.c                     |    2 +-
 src/doom/p_user.c                     |    2 +-
 src/doom/r_bsp.c                      |   14 +-
 src/doom/r_data.c                     |    6 +-
 src/doom/r_draw.c                     |    4 +-
 src/doom/r_main.c                     |    4 +-
 src/doom/r_segs.c                     |    4 +-
 src/doom/r_sky.c                      |    2 +-
 src/doom/r_things.c                   |   18 +-
 src/doom/s_sound.c                    |   94 +-
 src/doom/st_stuff.c                   |   84 +-
 src/doom/statdump.c                   |  712 ++++-----
 src/doom/statdump.h                   |   46 +-
 src/doom/wi_stuff.c                   |    6 +-
 src/doomtype.h                        |    9 +-
 src/gusconf.c                         |   39 +-
 src/heretic.appdata.xml.in            |    8 +-
 src/heretic/am_map.c                  |   26 +-
 src/heretic/d_main.c                  |   17 +-
 src/heretic/d_net.c                   |   16 +-
 src/heretic/doomdef.h                 |    8 +
 src/heretic/g_game.c                  |  235 ++-
 src/heretic/m_random.c                |    6 +
 src/heretic/m_random.h                |    3 +
 src/heretic/p_enemy.c                 |   74 +-
 src/heretic/p_inter.c                 |    6 +-
 src/heretic/p_map.c                   |    4 +-
 src/heretic/p_mobj.c                  |   24 +-
 src/heretic/p_pspr.c                  |   20 +-
 src/heretic/p_saveg.c                 |   50 +-
 src/heretic/p_user.c                  |    2 +-
 src/heretic/r_bsp.c                   |   14 +-
 src/heretic/r_data.c                  |    6 +-
 src/heretic/r_things.c                |   12 +-
 src/heretic/s_sound.c                 |   19 +-
 src/hexen.appdata.xml.in              |    8 +-
 src/hexen/a_action.c                  |   45 +-
 src/hexen/am_map.c                    |   25 +
 src/hexen/d_net.c                     |   17 +-
 src/hexen/g_game.c                    |  349 +++-
 src/hexen/h2_main.c                   |   19 +-
 src/hexen/h2def.h                     |   29 +-
 src/hexen/m_random.c                  |    6 +
 src/hexen/m_random.h                  |    3 +
 src/hexen/mn_menu.c                   |   12 +
 src/hexen/p_acs.c                     |   17 +
 src/hexen/p_enemy.c                   |  100 +-
 src/hexen/p_map.c                     |    4 +-
 src/hexen/p_mobj.c                    |   35 +-
 src/hexen/p_pspr.c                    |   27 +-
 src/hexen/p_spec.h                    |    1 +
 src/hexen/r_bsp.c                     |   13 +-
 src/hexen/r_data.c                    |    6 +-
 src/hexen/r_things.c                  |   12 +-
 src/hexen/s_sound.c                   |   24 +-
 src/hexen/sv_save.c                   | 1243 ++++++++-------
 src/i_joystick.c                      |    6 +-
 src/i_joystick.h                      |    2 +-
 src/i_oplmusic.c                      |  519 +++---
 src/i_pcsound.c                       |    3 +-
 src/i_scale.c                         |    4 +-
 src/i_sdlmusic.c                      |    4 +-
 src/i_sdlsound.c                      |  180 ++-
 src/i_sound.c                         |   12 +-
 src/i_sound.h                         |   22 +-
 src/i_swap.h                          |    4 +-
 src/i_video.c                         |  149 +-
 src/i_video.h                         |    3 +-
 src/m_config.c                        |   30 +
 src/m_controls.c                      |    2 +
 src/m_controls.h                      |    1 +
 src/m_fixed.c                         |    2 +-
 src/m_misc.c                          |   87 +
 src/m_misc.h                          |    2 +
 src/mus2mid.c                         |    2 +-
 src/net_server.c                      |    5 +-
 src/setup/Makefile.am                 |    4 +-
 src/setup/compatibility.c             |    9 +-
 src/setup/display.c                   |    8 +-
 src/setup/display.h                   |    1 +
 src/setup/joystick.c                  |  203 ++-
 src/setup/keyboard.c                  |  100 +-
 src/setup/mainmenu.c                  |   19 +-
 src/setup/mode.c                      |    3 +
 src/setup/mouse.c                     |   43 +-
 src/setup/multiplayer.c               |  137 +-
 src/setup/sound.c                     |  113 +-
 src/setup/sound.h                     |    2 +
 src/setup/txt_joyaxis.c               |    4 +-
 src/setup/txt_joybinput.c             |   14 +-
 src/strife.appdata.xml.in             |    8 +-
 src/strife/am_map.c                   |   28 +-
 src/strife/d_main.c                   |   75 +-
 src/strife/g_game.c                   |    6 +-
 src/strife/m_saves.c                  | 1056 ++++++------
 src/strife/m_saves.h                  |  112 +-
 src/strife/p_dialog.c                 | 2826 ++++++++++++++++-----------------
 src/strife/p_dialog.h                 |  204 +--
 src/strife/p_enemy.c                  |    9 +-
 src/strife/p_inter.c                  |   13 +-
 src/strife/p_plats.c                  |    8 +-
 src/strife/p_pspr.c                   |    2 +-
 src/strife/p_spec.c                   |    2 +-
 src/strife/r_bsp.c                    |   14 +-
 src/strife/r_data.c                   |    6 +-
 src/strife/r_things.c                 |   14 +-
 src/strife/s_sound.c                  |   19 +-
 src/tables.c                          |    4 +-
 src/v_diskicon.c                      |  145 ++
 src/{doom/m_random.h => v_diskicon.h} |   27 +-
 src/v_video.c                         |  114 +-
 src/w_checksum.c                      |    8 +-
 src/w_file.h                          |   10 +-
 src/w_file_posix.c                    |    2 +
 src/w_file_stdc.c                     |    1 +
 src/w_file_win32.c                    |    2 +
 src/w_main.c                          |   44 +-
 src/w_main.h                          |    3 +
 src/w_merge.c                         |   65 +-
 src/w_wad.c                           |  252 ++-
 src/w_wad.h                           |   32 +-
 src/z_zone.c                          |    4 +-
 textscreen/examples/guitest.c         |   15 +-
 textscreen/txt_checkbox.c             |    2 +-
 textscreen/txt_scrollpane.c           |    3 +-
 textscreen/txt_table.c                |  376 ++++-
 textscreen/txt_table.h                |   52 +-
 209 files changed, 10564 insertions(+), 8283 deletions(-)

diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md
new file mode 100644
index 0000000..52ef3f5
--- /dev/null
+++ b/.github/CONTRIBUTING.md
@@ -0,0 +1,98 @@
+Thanks for contributing to Chocolate Doom! Whatever your contribution,
+whether it's code or just a bug report, it's greatly appreciated.
+
+The project is governed by the
+[Contributor Covenant](http://contributor-covenant.org/version/1/4/)
+version 1.4. By contributing to the project you agree to abide by its
+terms. To report violations, please send an email to fraggle at gmail.com.
+
+### Reporting bugs
+
+Before reporting a bug, it's worth checking if this really is a bug.
+Chocolate Doom's mission is to reproduce the Vanilla (DOS) versions of
+the Doom engine games, bugs and all. Check out the
+[NOT-BUGS](../NOT-BUGS.md) file for a list of common issues which aren't
+really bugs at all. You might also try searching [the GitHub issues
+list](https://github.com/chocolate-doom/chocolate-doom/issues) to see
+if your bug has already been reported.
+
+If you're confident that you've found a real bug (or even if you're
+not sure!) please go ahead and [file an issue on
+GitHub](https://github.com/chocolate-doom/chocolate-doom/issues/new).
+You'll need a GitHub account, but it's pretty easy to sign up.
+
+Please try to give as much information as possible:
+
+* What version of Chocolate Doom are you using? Check the title bar of
+  the window for the version number.
+
+* Chocolate Doom runs on many different operating systems (not just
+  Windows!). Please say which operating system and what version of it
+  you're using.
+
+* Please say which game you're playing (Doom 1, Doom 2, Heretic,
+  Hexen, Strife, etc.) and list any add-on WADs you're using. Please
+  mention if you have any special configuration you think may be
+  relevant, too.
+
+### Feature requests
+
+Chocolate Doom is always open to new feature requests; however, please
+be aware that the project is designed around a deliberately limited
+[philosophy](../PHILOSOPHY.md), and many features common in other source
+ports will not be accepted. Here are a few common requests which are
+often rejected:
+
+* "High resolution" rendering (greater than 320x200 display).
+
+* An option to disable Vanilla limits, such as the visplane rendering
+  limit.
+
+* Ability to play "No Rest For The Living", the expansion pack which
+  comes with the XBLA / BFG Edition of Doom.
+
+If you're not sure whether your feature is in line with the project
+philosophy, don't worry - just ask anyway!
+To make a feature request, [file an issue on
+GitHub](https://github.com/chocolate-doom/chocolate-doom/issues/new).
+
+### Bug fixes / code submission
+
+Thank you for contributing code to Chocolate Doom! Please check the
+following guidelines before opening a pull request:
+
+* All code must be licensed under [the GNU General Public License,
+  version 2](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html).
+  Please don't reuse code that isn't GPL, or that is GPLv3 licensed.
+  Be aware that by submitting your code to the project, you're agreeing
+  to license it under the GPL.
+
+* Please follow the coding style guidelines described in the
+  [HACKING](../HACKING.md) file.
+
+* Please don't make unnecessary changes which just change formatting
+  without any actual change to program logic. While being consistent
+  is nice, such changes destroy the ability to use the `git blame`
+  command to see when code was last changed.
+
+* The guidelines given above in the "feature requests" section also
+  apply here. New features which aren't in line with the project
+  philosophy are likely to be rejected. If you're not sure, open a
+  feature request first and ask before you start implementing your
+  feature.
+
+* Follow the guidelines for [how to write a Git commit
+  message](http://chris.beams.io/posts/git-commit/). In short: the
+  first line should be a short summary; keep to an 80 column limit;
+  use the imperative mood ("fix bug X", rather than "fixed bug X" or
+  "fixing bug X"). If your change fixes a particular subsystem,
+  prefix the summary with that subsystem: eg. "doom: Fix bug X" or
+  "textscreen: Change size of X".
+
+* If you're making a change related to a bug, reference the GitHub
+  issue number in the commit message, eg. "This is a partial fix
+  for #646". This will link your commit into the issue comments. If
+  your change is a fix for the bug, put the word "fixes" before the
+  issue number to automatically close the issue once your change
+  is merged.
+
diff --git a/.gitignore b/.gitignore
index 57ab998..9de6a07 100644
--- a/.gitignore
+++ b/.gitignore
@@ -19,6 +19,7 @@ stamp-h
 stamp-h.in
 stamp-h1
 tags
+\#*\#
 
 # These are the default patterns globally ignored by Subversion:
 *.o
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..dd3524a
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,30 @@
+language: c
+
+compiler: gcc
+
+# Travis uses Ubuntu 12.04 (Precise) for builds by default, which is too
+# old and missing the SDL2 packages, so use Trusty instead.
+sudo: required
+dist: trusty
+
+# TODO: Remove old SDL library dependencies once sdl2-branch is merged.
+addons:
+    apt:
+        packages:
+        - libsdl2-dev
+        - libsdl2-mixer-dev
+        - libsdl2-net-dev
+        - libsdl2-image-dev
+        - libsamplerate0-dev
+        - libsdl1.2-dev
+        - libsdl-mixer1.2-dev
+        - libsdl-net1.2-dev
+        - libpng-dev
+
+script: ./autogen.sh && make && make install DESTDIR=/tmp/whatever && make dist
+
+branches:
+    only:
+    - master
+    - sdl2-branch
+
diff --git a/AUTHORS b/AUTHORS
index 21238bf..684ece0 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -1,3 +1,6 @@
 Simon Howard <fraggle at gmail.com>
 James Haley <haleyjd at hotmail.com>
 Samuel Villarreal <svkaiser at gmail.com>
+Fabian Greffrath <fabian at greffrath.com>
+Jonathan Dowland <jon at dow.land>
+Alexey Khokholov <alexeytf2 at gmail.com>
diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
new file mode 100644
index 0000000..895dcfd
--- /dev/null
+++ b/CODE_OF_CONDUCT.md
@@ -0,0 +1,74 @@
+# Contributor Covenant Code of Conduct
+
+## Our Pledge
+
+In the interest of fostering an open and welcoming environment, we as
+contributors and maintainers pledge to making participation in our project and
+our community a harassment-free experience for everyone, regardless of age, body
+size, disability, ethnicity, gender identity and expression, level of experience,
+nationality, personal appearance, race, religion, or sexual identity and
+orientation.
+
+## Our Standards
+
+Examples of behavior that contributes to creating a positive environment
+include:
+
+* Using welcoming and inclusive language
+* Being respectful of differing viewpoints and experiences
+* Gracefully accepting constructive criticism
+* Focusing on what is best for the community
+* Showing empathy towards other community members
+
+Examples of unacceptable behavior by participants include:
+
+* The use of sexualized language or imagery and unwelcome sexual attention or
+advances
+* Trolling, insulting/derogatory comments, and personal or political attacks
+* Public or private harassment
+* Publishing others' private information, such as a physical or electronic
+  address, without explicit permission
+* Other conduct which could reasonably be considered inappropriate in a
+  professional setting
+
+## Our Responsibilities
+
+Project maintainers are responsible for clarifying the standards of acceptable
+behavior and are expected to take appropriate and fair corrective action in
+response to any instances of unacceptable behavior.
+
+Project maintainers have the right and responsibility to remove, edit, or
+reject comments, commits, code, wiki edits, issues, and other contributions
+that are not aligned to this Code of Conduct, or to ban temporarily or
+permanently any contributor for other behaviors that they deem inappropriate,
+threatening, offensive, or harmful.
+
+## Scope
+
+This Code of Conduct applies both within project spaces and in public spaces
+when an individual is representing the project or its community. Examples of
+representing a project or community include using an official project e-mail
+address, posting via an official social media account, or acting as an appointed
+representative at an online or offline event. Representation of a project may be
+further defined and clarified by project maintainers.
+
+## Enforcement
+
+Instances of abusive, harassing, or otherwise unacceptable behavior may be
+reported by contacting the project team at fraggle at gmail.com. All
+complaints will be reviewed and investigated and will result in a response that
+is deemed necessary and appropriate to the circumstances. The project team is
+obligated to maintain confidentiality with regard to the reporter of an incident.
+Further details of specific enforcement policies may be posted separately.
+
+Project maintainers who do not follow or enforce the Code of Conduct in good
+faith may face temporary or permanent repercussions as determined by other
+members of the project's leadership.
+
+## Attribution
+
+This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
+available at [http://contributor-covenant.org/version/1/4][version]
+
+[homepage]: http://contributor-covenant.org
+[version]: http://contributor-covenant.org/version/1/4/
diff --git a/HACKING b/HACKING.md
similarity index 61%
rename from HACKING
rename to HACKING.md
index db20f96..0356d2e 100644
--- a/HACKING
+++ b/HACKING.md
@@ -1,6 +1,4 @@
-
-Coding style guidelines
-=======================
+# Coding style guidelines
 
 The coding style guidelines for Chocolate Doom are designed to keep the
 style of the original source code.  This maintains consistency throughout
@@ -9,52 +7,57 @@ of these guidelines are stricter than what was done in the original
 source; follow these when writing new code only: there is no need to
 change existing code to fit them.
 
-You should set tabs to _display_ as eight spaces, not four.  However,
-_indentation_ should be four spaces.  If possible, do not use tab
-characters at all.  There is a utility called "expand" which will remove
+You should set tabs to *display* as eight spaces, not four.  However,
+*indentation* should be four spaces.  If possible, do not use tab
+characters at all.  There is a utility called “expand” which will remove
 tab characters.  For the reasoning behind this, see:
 http://www.jwz.org/doc/tabs-vs-spaces.html
 
 Please write code to an 80 column limit so that it fits within a standard
 80 column terminal. Do not leave trailing whitespace at the end of lines.
 
-Functions should be named like this: 'AB_FunctionName'.  The 'AB' prefix
-denotes the subsystem (AM_ for automap, G_ for game, etc).  If a
+Functions should be named like this: `AB_FunctionName`.  The `AB` prefix
+denotes the subsystem (`AM_` for automap, `G_` for game, etc).  If a
 function is static, you can omit the prefix and just name it like
-'FunctionName'.  Functions and global variables should always be made
+`FunctionName`.  Functions and global variables should always be made
 static if possible.
 
-Put '_t' on the end of types created with typedef.  Type names like this
+Put `_t` on the end of types created with typedef.  Type names like this
 should be all lowercase and have the subsystem name at the start. An
-example of this is 'txt_window_t'.  When creating structures, always
+example of this is `txt_window_t`.  When creating structures, always
 typedef them.
 
 Do not use Hungarian notation.
 
 Do not use the goto statement.
 
-Use C++-style comments, ie. '//' comments, not '/* ... */' comments.
-I don't care that this isn't standard ANSI C.
+Use C++-style comments, ie. `//` comments, not `/* ... */` comments.
+I don’t care that this isn’t standard ANSI C.
 
-Variables should be named like this: 'my_variable_name', not like this:
-'MyVariableName'.  In pointer variable declarations, place the '*' next
+Variables should be named like this: `my_variable_name`, not like this:
+`MyVariableName`.  In pointer variable declarations, place the `*` next
 to the variable name, not the type.
 
 When using an if, do, while, or for statement, always use the { } braces
 even when they are not necessary.  For example, do this:
 
-    if (condition)
-    {
-        body;
-    }
+```c
+if (condition)
+{
+    body;
+}
+```
 
 Not this:
 
-    if (condition)   // NO
-        body;
+```c
+if (condition)   // NO
+    body;
+```
 
 Write code like this:
 
+```c
 typedef struct
 {
     int member1;
@@ -117,81 +120,82 @@ void FunctionName(int argument, int arg2, int arg3, int arg4, int arg5,
 
     } while (condition);
 }
+```
 
-Security
-========
+## Security
 
 The C standard library has a number of unsafe functions that should be
 avoided when writing code for Chocolate Doom. These are:
 
-    Unsafe function       Safer alternative
-    ---------------------------------------------
-    gets()                fgets(.., stdin)
-    sprintf               M_snprintf()
-    snprintf              M_snprintf()
-    vsprintf              M_vsnprintf()
-    vsnprintf             M_vsnprintf()
-    strcpy()              M_StringCopy()
-    strncpy()             M_StringCopy()
-    strcat()              M_StringConcat()
-    strncat()             M_StringConcat()
-    strdup()              M_StringDuplicate()
+Unsafe function   |   Safer alternative
+------------------|------------------------
+`gets()`          |  `fgets(.., stdin)`
+`sprintf`         |  `M_snprintf()`
+`snprintf`        |  `M_snprintf()`
+`vsprintf`        |  `M_vsnprintf()`
+`vsnprintf`       |  `M_vsnprintf()`
+`strcpy()`        |  `M_StringCopy()`
+`strncpy()`       |  `M_StringCopy()`
+`strcat()`        |  `M_StringConcat()`
+`strncat()`       |  `M_StringConcat()`
+`strdup()`        |  `M_StringDuplicate()`
 
 Lots of the code includes calls to DEH_String() to simulate string
 replacement by the Dehacked tool. Be careful when using Dehacked
 replacements of printf format strings. For example, do not do this:
 
-    printf(DEH_String("foo %s"), s);
-    sprintf(mybuf, DEH_String("bar %s"), t);
+```c
+printf(DEH_String("foo %s"), s);
+sprintf(mybuf, DEH_String("bar %s"), t);
+```
 
 Instead do this:
 
-    DEH_printf("foo %s", s);
-    DEH_snprintf(mybuf, sizeof(mybuf), "bar %s", t);
+```c
+DEH_printf("foo %s", s);
+DEH_snprintf(mybuf, sizeof(mybuf), "bar %s", t);
+```
 
 This does the format string replacement safely in a way that checks
 the arguments securely.
 
-
-Portability
-===========
+## Portability
 
 Chocolate Doom is designed to be cross-platform and work on different
 Operating Systems and processors.  Bear this in mind when writing code.
 
-Do not use the long type (its size differs across platforms; use
-int or int64_t depending on which you want).
+Do not use the `long` type (its size differs across platforms; use
+`int` or `int64_t` depending on which you want).
 
-Use Doom's byte data type for byte data. 'int' is assumed to be a
-32-bit integer, and 'short' is a 16-bit integer. You can also use the
-ISO C99 data types: intN_t and uintN_t where N is 8,16,32,64.
+Use Doom’s byte data type for byte data. `int` is assumed to be a
+32-bit integer, and `short` is a 16-bit integer. You can also use the
+ISO C99 data types: `intN_t` and `uintN_t` where N is 8, 16, 32, 64.
 
 Be careful with platform dependencies: do not use Windows API
 functions, for example.  Use SDL where possible.
 
-Preprocessor #defines are set that can be used to identify the OS
-if necessary: _WIN32 for Windows and __MACOSX__ for MacOS X. Others
+Preprocessor `#defines` are set that can be used to identify the OS
+if necessary: `_WIN32` for Windows and `__MACOSX__` for Mac OS X. Others
 are set through SDL.  Try to avoid this if possible.
 
-Be careful of endianness!  Doom has SHORT() and LONG() macros that
+Be careful of endianness!  Doom has `SHORT()` and `LONG()` macros that
 do endianness conversion.  Never assume that integer types have a
 particular byte ordering.  Similarly, never assume that fields
 inside a structure are aligned in a particular way.  This is most
 relevant when reading or writing data to a file or a network pipe.
 
-For signed integers, you shouldn't assume that (i >> n) is the same as
-(i / (1 << n)).  However, most processors handle bitshifts of signed
-integers properly, so it's not a huge problem.
-
+For signed integers, you shouldn’t assume that `(i >> n)` is the same as
+`(i / (1 << n))`.  However, most processors handle bitshifts of signed
+integers properly, so it’s not a huge problem.
 
-GNU GPL and licensing
-=====================
+## GNU GPL and licensing
 
-All code submitted to the project must be licensed under the GNU GPL or a
-compatible license.  If you use code that you haven't 100% written
+All code submitted to the project must be licensed under the GNU GPLv2 or a
+compatible license.  If you use code that you haven’t 100% written
 yourself, say so. Add a copyright header to the start of every file.  Use
 this template:
 
+```
 //
 // Copyright(C) YEAR Author's name
 //
@@ -208,6 +212,4 @@ this template:
 //
 // *File description goes here*
 //
-
-# vim: tw=70
-
+```
diff --git a/Makefile.am b/Makefile.am
index abc4d99..74ee60c 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -8,13 +8,17 @@ MSVC_FILES=                             \
         msvc/doom.vcproj                \
         msvc/heretic.vcproj             \
         msvc/hexen.vcproj               \
+        msvc/strife.vcproj              \
         msvc/inttypes.h                 \
+        msvc/libopl.vcproj              \
         msvc/libpcsound.vcproj          \
         msvc/libtextscreen.vcproj       \
         msvc/README                     \
         msvc/server.vcproj              \
         msvc/setup.vcproj               \
         msvc/stdint.h                   \
+        msvc/win_opendir.c              \
+        msvc/win_opendir.h              \
         msvc/win32.rc
 
 CODEBLOCKS_FILES=                              \
@@ -24,6 +28,8 @@ CODEBLOCKS_FILES=                              \
         codeblocks/game-res.rc                 \
         codeblocks/heretic.cbp                 \
         codeblocks/hexen.cbp                   \
+        codeblocks/strife.cbp                  \
+        codeblocks/libopl.cbp                  \
         codeblocks/libpcsound.cbp              \
         codeblocks/libtextscreen.cbp           \
         codeblocks/README                      \
@@ -32,10 +38,10 @@ CODEBLOCKS_FILES=                              \
         codeblocks/setup-res.rc
 
 DOC_FILES=                              \
-        README                          \
-        README.Music                    \
-        NEWS                            \
-        PHILOSOPHY                      \
+        README.md                       \
+        README.Music.md                 \
+        NEWS.md                         \
+        PHILOSOPHY.md                   \
         ChangeLog
 
 EXTRA_DIST=                             \
@@ -43,15 +49,15 @@ EXTRA_DIST=                             \
         $(MSVC_FILES)                   \
         $(CODEBLOCKS_FILES)             \
         $(DOC_FILES)                    \
-        NOT-BUGS                        \
-        README.Strife                   \
+        NOT-BUGS.md                     \
+        README.Strife.md                \
         .lvimrc                         \
-        HACKING                         \
-        TODO                            \
+        HACKING.md                      \
+        TODO.md                         \
         rpm.spec
 
 doomdocsdir = ${docdir}/../${PROGRAM_PREFIX}doom
-doomdocs_DATA = $(DOC_FILES) NOT-BUGS
+doomdocs_DATA = $(DOC_FILES) NOT-BUGS.md
 
 hereticdocsdir = ${docdir}/../${PROGRAM_PREFIX}heretic
 hereticdocs_DATA = $(DOC_FILES)
@@ -60,7 +66,7 @@ hexendocsdir = ${docdir}/../${PROGRAM_PREFIX}hexen
 hexendocs_DATA = $(DOC_FILES)
 
 strifedocsdir = ${docdir}/../${PROGRAM_PREFIX}strife
-strifedocs_DATA = $(DOC_FILES) README.Strife
+strifedocs_DATA = $(DOC_FILES) README.Strife.md
 
 MAINTAINERCLEANFILES =  $(AUX_DIST_GEN)
 
diff --git a/NEWS b/NEWS
deleted file mode 100644
index 4f8d629..0000000
--- a/NEWS
+++ /dev/null
@@ -1,1115 +0,0 @@
-2.2.1 (2015-09-10):
-
-    Chocolate Doom has not seen a great deal of "stable" patch
-    releases in its history. While the development tree sees major new
-    features and changes, the purpose of this release, and hopefully
-    others to follow like it, is to repair some deficiencies that
-    existed in 2.2.0.
-
-    General:
-    * Preferences for the OS X launcher are now stored with a unique
-      name to not conflict with other applications. (thanks Xeriphas1994)
-    * Unix desktop entry files are now brought up to full desktop
-      entry specification compliance. (thanks chungy, Fabian)
-    * Unix AppData entries are now included, allowing software centers
-      to display detailed information about the engines. (thanks chungy)
-    * Partial XDG base directory specification compliance on Unix
-      systems now exist to search for IWAD paths.  One benefit is that
-      $HOME/.local/share/games/doom is now a valid location to store
-      and automatically find IWADs. (thanks chungy)
-
-    Build systems:
-    * The Microsoft Visual Studio build system was not fully
-      functional in 2.2.0 and has been fixed. (thanks Linguica)
-    * The autoconf build system checks for windres only for Windows
-      toolchains.  Some Linux distributions mistakingly include the
-      program in their native toolchains. (thanks Fabian)
-    * A compiler hint for packed structs has been added, which
-      otherwise broke the games when built under recent GCC releases
-      for Windows. (thanks Fabian)
-
-    Doom:
-    * The GOG.com releases of The Ultimate Doom, Doom II, and Final
-      Doom are now detected and supported on Windows. (thanks chungy)
-    * An integer overflow was used in spawn angle calculation,
-      undefined C behavior which broke with Clang optimization.
-      (thanks David Majnemer for insight)
-
-    Setup tool:
-    * The help URL for the level warp menu now points to the proper
-      wiki page, rather than the multiplayer page.
-    * The manifest has been updated for Windows 10 compatibility.
-      (thanks chungy)
-
-2.2.0 (2015-06-09):
-
-     * The Hexen four level demo IWAD is now supported. Thanks to
-       Fabian Greffrath for his careful investigation and emulation of
-       the demo game's behavior in developing this.
-     * OPL music playback has been improved in a number of ways to
-       match the behavior of Vanilla Doom's DMX library much more
-       closely. OPL3 playback is also now supported. Thanks go to
-       Alexey Khokholov for his excellent research into the Vanilla
-       DMX library that enabled these improvements.
-     * New gamepad configurations:
-        - PS4 DualShock 4 (thanks Matt '3nT' Davis).
-        - Xbox One controller on Linux (thanks chungy).
-        - "Super Joy Box 7" USB/PC gameport adapter.
-     * The Doom reload hack has been added back. See the wiki for more
-       context on this: http://doomwiki.org/wiki/Reload_hack
-     * The IWAD file from Strife: Veteran Edition is now detected
-       automatically (thanks chungy).
-     * It's now possible to build outside of the source directory
-       (thanks Dave Murphy).
-     * MSVC project files were brought up to date (thanks dbrackett16).
-     * M_StringDuplicate() has been added as a safer replacement for
-       strdup() (thanks Quasar). M_StringCopy() now handles short
-       buffers more gracefully.
-     * The netgame discrepancy window is now dismissed by pressing
-       enter to proceed, not escape (thanks Alexandre-Xavier).
-     * A couple of source files that were in the previous release and
-       were GPL3 have been replaced by GPL2 equivalents. Previous
-       releases that included these files should be retroactively
-       considered GPL3.
-
-    Bug fixes:
-     * A long-standing bug that could cause every display frame to be
-       rendered twice was fixed (thanks Linguica, Harha, Alexandre-
-       Xavier).
-     * Lots of endianness fixes were integrated that were found by
-       Ronald Lasmanowicz during development of his Wii port of
-       Chocolate Doom, including a fix for a bug that could cause
-       monsters to become partially invisible.
-     * DeHackEd files without a newline character at the EOF are now
-       correctly parsed (thanks Fabian).
-     * An infinite loop that could occur in the weapon cycling code
-       was fixed (thanks raithe, Fabian).
-     * Mouse input triggered by cursor warp was fixed (thanks Super6-4).
-     * Loop tags in substitute music files are ignored if both of the
-       loop tags are equal to zero. This makes us consistent with
-       other source ports that support the tags.
-     * It's now possible to more conveniently play back demo .lmp
-       files with names that end in the all-caps '.LMP' (thanks Ioan
-       Chera).
-     * Some code that accessed memory after freeing it was fixed. Two
-       new parameters, -zonezero and -zonescan, were added to try to
-       help detect these cases.
-     * Mistaken assumptions about representations of booleans that
-       affected some ARM systems were fixed (thanks floppes).
-     * memcpy() uses on overlapping memory were changed to use
-       memmove(), fixing abort traps on OpenBSD (thanks ryan-sg).
-     * Hyphens in manpages were fixed (thanks chungy, Fabian).
-     * Lots of compiler build warnings were fixed (thanks Fabian).
-
-    Setup tool:
-     * The setup tool now has help buttons for its various different
-       screens, which link to articles on the wiki that give more
-       information (thanks to chungy for helping to put the wiki pages
-       together).
-     * A fix was applied for a buffer overrun that could occur if the
-       user had lots of IWAD files installed (thanks Fabian).
-     * A crash related to username lookup was fixed.
-     * It's now possible to connect via the setup tool to multiplayer
-       servers that are not listening on the default port (thanks
-       Alexandre-Xavier).
-
-    Doom:
-     * Sky transitions when emulating the id anthology version of the
-       Final Doom executable were fixed (thanks Alexandre-Xavier,
-       Fabian, chungy).
-     * Structure fields in the stair-building functions were fixed to
-       be deterministic, fixing a desync in mm09-512.lmp (thanks
-       Fabian).
-
-    Hexen:
-     * A bug with texture names that had long names was fixed (thanks
-       ETTiNGRiNDER).
-     * Minotaur spawn time is now stored in little endian format,
-       fixing a bug that affected compatibility with Vanilla savegames
-       on big endian systems.
-     * Code that starts ACS scripts is no longer compiler-dependent.
-
-    Strife (all these are thanks to Quasar):
-     * Sound priority was changed, so that the ticking sound that
-       Stalker enemies make while active matches Vanilla behavior
-       (thanks GeoffLedak).
-     * Minor fixes to game behavior to match Vanilla, discovered
-       during development of Strife: Veteran edition.
-     * Behavior of descending stairs was fixed to match Vanilla.
-     * Inventory items beyond the 8-bit range are now allowed in
-       netgames.
-     * Automap behavior better matches Vanilla now.
-     * Multiplayer name changes were fixed.
-     * Sound origin behavior for switches was fixed.
-     * Teleport beacon behavior was fixed.
-     * Default Strife skill level and screen size were changed to
-       match Vanilla.
-     * Bug was fixed where Rowan would not always take Beldin's ring.
-     * Totally-invisible objects are now displayed correctly, and a
-       Vanilla glitch with Shadow Acolytes is correctly emulated.
-     * The level name for MAP29 (Entity's Lair) was fixed (thanks
-       chungy).
-
-    libtextscreen:
-     * The main loop now exits immediately once all windows are closed
-       (thanks Alexander-Xavier).
-     * The large font is no longer selected based entirely on screen
-       size.
-
-2.1.0 (2014-10-22):
-
-    Chocolate Doom now supports high-quality substitute music packs
-    that are used in place of the original MIDI music tracks. I'm
-    hoping to put together high-quality recordings of the music for
-    all supported games using the Roland SC-55 synthesizer
-    originally used to compose Doom's music (thanks twipley and
-    MusicallyInspired).
-
-    Support for joysticks and gamepads has been significantly
-    improved in this version. Most gamepads should now work; if they
-    don't, please report a bug. A number of gamepads are now
-    automatically detected and configured automatically; if yours is
-    not, you can help by sending in details. See the following page:
-
-      http://www.chocolate-doom.org/wiki/index.php/Adding_your_gamepad
-
-    OPL MIDI playback has been significantly improved, and problems
-    with most tracks should now be resolved. Multi-track MIDI files now
-    play back properly, MIDI tempo meta events are now supported and
-    problems with stuttering when playing certain tracks have been
-    fixed. If you still have problems with OPL playback, let me know.
-
-    Also of note is that Chocolate Doom now has a document that
-    describes the philosophy of the project and the reasoning behind
-    its design (see PHILOSOPHY distributed with the source).
-
-    Other new features:
-     * There is now a -dehlump command line parameter to load Dehacked
-       files contained inside WAD files (thanks Fabian Greffrath).
-     * PNG format screenshots are now supported, and there is a
-       dedicated key binding for taking screenshots without needing to
-       always use -devparm (thanks Fabian Greffrath). The PrintScreen
-       key can be used as a key binding (thanks Alexandre-Xavier).
-     * There is now a config file variable (snd_maxslicetime_ms) to
-       control the sound buffer size, and the default is more precise
-       to reduce sound latency (thanks Holering).
-     * You can now use an external command for music playback (thanks
-       Holering).
-     * All games now detect if you're tring to play using the wrong
-       type of IWAD (doom.wad with Hexen, etc.) and exit with a
-       helpful error message. A couple of users made this mistake
-       after the 2.0 release introduced support for the new games.
-     * The OS X app now associates with .hhe and .seh files.
-     * There is now a -nodes parameter that automatically starts a
-       netgame when a desired number of players have joined the game.
-     * There is now more extensive documentation about music
-       configuration (README.Music).
-     * On Linux, a GUI pop-up is used when the game quits with an
-       error to show the error message (thanks Willy Barro).
-     * There are now Linux .desktop files for all supported games
-       (thanks Mike Swanson).
-     * The -geometry command line parameter can now be used to specify
-       fullscreen or windowed modes, eg. -geometry 640x480w or
-       -geometry 1024x768f. (thanks Mike Swanson)
-
-    Doom:
-     * Minor workarounds were added to allow the BFG Edition IWADs to
-       be used without crashing the game (thanks Fabian Greffrath).
-     * GUS patch files included with the BFG Edition are now
-       automatically detected.
-     * The 'no fog on spawn west' Vanilla bug is now correctly
-       emulated (thanks xttl).
-     * Behavior of older versions of Doom back to v1.666 can now be
-       emulated.
-     * The new Freedoom IWAD names are now recognized and supported.
-     * Freedoom's DEHACKED lump can now be parsed and is automatically
-       loaded when a Freedoom IWAD file is used (thanks Fabian
-       Greffrath). A new command line parameter, -nodeh, can be used
-       to prevent this from being loaded.
-     * Behavior of the M_EPI4 menu item is now correctly emulated
-       based on game version (thanks Alexandre-Xavier).
-     * IDCLEV up to MAP40 is now supported, to match Vanilla (thanks
-       Alexandre-Xavier).
-     * Level warping on the command line (-warp) to episodes higher
-       than 4 is possible, matching Vanilla behavior (thanks plumsinus).
-     * The -cdrom command line parameter writes savegames to the
-       correct directory now, matching Vanilla Doom behavior (thanks
-       Alexandre-Xavier).
-     * The Doom II mission pack to use can now be specified manually on
-       the command line with the -pack parameter (thanks chungy)
-
-    Heretic:
-     * Weapon cycling keys for mouse and joystick were fixed (thanks
-       Sander van Dijk).
-     * The -timedemo parameter has been fixed, and -playdemo now
-       handles full paths correctly.
-     * A bug when panning the map was fixed (thanks Chris Fielder).
-     * A savegame bug where plat_t structures were not restored
-       correctly was fixed (thanks romeroyakovlev).
-     * Rebinding of the pause key was fixed (thanks Fabian Greffrath).
-
-    Hexen:
-     * Music workarounds have been added so that it is possible to
-       play using the Mac version of the Hexen IWAD file.
-     * Weapon cycling keys for mouse and joystick were fixed (thanks
-       Sander van Dijk).
-     * The -timedemo parameter has been fixed, and -playdemo now
-       handles full paths correctly.
-     * There are now key bindings to allow the artifact keys to be
-       rebound (thanks Fabian Greffrath).
-     * Rebinding of the pause key was fixed (thanks Fabian Greffrath).
-     * Maximum level number was extended to MAP60, allowing
-       multiplayer games using the Deathkings add-on.
-     * The startup screen can now be aborted by pressing escape, like
-       in Vanilla.
-     * Desync when playing back DEMO1 was fixed (thanks alexey.lysiuk).
-
-    Strife:
-     * 'Show mission' key is configured properly in setup (thanks
-       Sander van Dijk).
-     * Default music volume level now matches Vanilla (thanks
-       Alexandre-Xavier).
-     * Teleport beacon allegiance was fixed to match Vanilla (thanks
-       Quasar).
-     * The stair building code now more closely matches Vanilla
-       (thanks Quasar).
-     * Torpedo weapon changing behavior now matches Vanilla (thanks
-       Quasar).
-
-   Cleanups:
-     * The copyright headers at the top of all source files have been
-       vastly simplified.
-     * Unsafe string functions have been eliminated from the codebase.
-       Thanks to Theo de Raadt for calling out Chocolate Doom by name
-       (alongside many other packages) for still using unsafe functions
-       like strcpy: http://marc.info/?l=openbsd-tech&m=138733933417096
-     * vldoor_e enum values are now namespaced to avoid potential
-       conflicts with POSIX standard functions.
-
-   Bug fixes:
-     * WAD and Dehacked checksums are now sent to clients and checked
-       correctly when setting up netgames.
-     * A bug was fixed that caused sound not to work in multiplayer
-       games (thanks to everyone who reported this, and for
-       Alexandre-Xavier and Quasar for help in fixing it).
-     * The "D_DDTBLU disease" bug affecting certain MIDI files has
-       been fixed (thanks plumsinus, Brad Harding and Quasar).
-     * Calculation of the -devparm 'ticker' dots was fixed to match
-       Vanilla behavior (thanks _bruce_ and Alexandre-Xavier).
-     * The PC speaker code now supports the full range of sound
-       frequencies (thanks Gez).
-     * Annoying "jumping" behavior when grabbing the mouse cursor was
-       fixed.
-     * The screen is now initialized at the native bit depth by
-       default, to avoid problems with systems that don't handle 8-bit
-       screenbuffers very well any more.
-     * The --docdir argument to the configure script is now honored
-       (thanks Jan Engelhardt).
-     * Various issues with the build were fixed (thanks Jan
-       Engelhardt and Fabian Greffrath).
-     * Backwards parameters were fixed in the sound code (thanks
-       proteal).
-     * A crash was fixed when running fullscreen with the -2 parameter
-       (thanks Fabian Greffrath).
-     * A crash when using large values of snd_channels was fixed
-       (thanks Alexandre-Xavier).
-     * A resource leak in the BSD PC speaker code was fixed (thanks
-       Edward-san).
-     * Windows resource files were fixed for Windows 7 (thanks Brad
-       Harding).
-     * A hard to trigger crash caused by a realloc() in the WAD code
-       was fixed (thanks Fabian Greffrath for debugging).
-     * A bug has been fixed where Chocolate Doom would stay running
-       in the background on Windows after quitting. SDL_Quit() is
-       called now (thanks johnsirett, Brad Harding, Quasar).
-     * String replacements in dehacked lumps can now be overridden
-       if a subsequent dehacked patch replaces the same string.
-
-    libtextscreen:
-     * Clicking on scrollbars now jumps to the correct position
-       (thanks Alexandre-Xavier).
-     * A use-after-free bug has been fixed where a click in a window
-       that causes the window to close could lead to a crash (thanks
-       DuClare).
-     * Characters that are unprintable in the Extended ASCII chart
-       are just ignored when they're typed, rather than appearing as
-       an upside-down question mark (thanks Alexandre-Xavier).
-
-2.0.0 (2013-12-09):
-
-    This is version 2.0 of Chocolate Doom! This new major version is
-    released to celeberate the 20th anniversary of the first release
-    of Doom in 1993. Happy Birthday Doom!
-
-    This new version has some major changes compared to the 1.0 series:
-
-     * The codebase now includes Chocolate Heretic and Chocolate
-       Hexen. These are based on the GPL source code released by
-       Raven Software.
-     * Also included is Chocolate Strife. This was developed through a
-       mammoth four year reverse engineering project conducted by
-       James "Quasar" Haley and Samuel "Kaiser" Villareal. The result
-       is the most accurate reproduction of Strife to date, including
-       full demo and savegame compatibility. See README.Strife for
-       more information.
-
-    Minor features that are nonetheless worth mentioning:
-     * Chocolate Doom now includes a -statdump command line option,
-       which emulates the output of the statdump.exe tool. This is
-       used to implement a form of regression testing (statcheck) that
-       directly compares against the Vanilla behavior.
-     * Chocolate Heretic includes HHE patch file support, and I
-       believe is the first Heretic port to include this feature.
-     * GUS "pseudo-emulation" is now supported. This does not fully
-       emulate a GUS, but Doom's DMXGUS lump can be used to generate
-       a Timidity configuration file that plays music using the GUS
-       patch set.
-     * The setup tool now includes a built-in server browser, for use
-       when selecting a server to join.
-
-    Version 2.0 of Chocolate Doom has been in development for a long
-    time, and there have been many bugs fixed over this time, too many
-    to list here. Thanks to all the people who have tested it and
-    diligently reported bugs over this time, and to all the people who
-    have tested the beta releases over the past couple of months.
-    Your contributions have been essential and invaluable.
-
-1.7.0 (2012-06-09):
-
-     * Fixed gnome-screensaver desktop file (thanks Rahul Sundaram).
-     * Updated COPYING to current version of GPL2 (thanks Rahul
-       Sundaram).
-     * Running servers now re-resolve the address of the master server
-       occasionally, to adapt to DNS address changes.
-     * Error dialog is no longer shown on OS X when running from the
-       console.
-     * The Makefiles no longer use GNU make extensions, so the package
-       builds on OpenBSD.
-     * There is now an OPL MIDI debug option (-opldev), useful for
-       when developing GENMIDI lumps.
-     * A workaround for SDL mouse lag is now only used on Windows
-       (where it is needed), and not on other systems. This fixes
-       Chocolate Doom on AmigaOS (thanks Timo Sievänen).
-     * UTF-8 usernames are supported, and Windows usernames with
-       non-ASCII characters are now supported (thanks Alexandre
-       Xavier).
-
-    Compatibility:
-     * Palette accuracy is reduced to 6 bits per channel, to more
-       accurately emulate the PC VGA hardware (thanks GhostlyDeath).
-     * Fixed teleport behavior when emulating the alternate Final Doom
-       executable (-gameversion final2) (thanks xttl).
-
-    Bugs fixed:
-     * Fixed weapon cycling keys when playing in Shareware Doom and using
-       the IDKFA cheat (thanks Alexandre Xavier).
-     * Fixed the default mouse buttons in the setup tool (thanks
-       Alexandre Xavier).
-     * Chat macros now work when vanilla_keyboard_mapping is turned
-       off.
-     * Default chat macros were fixed in the setup tool.
-     * Ping time calculation was fixed for LAN search, and made more
-       accurate for all searches.
-     * Fixed bug with detection of IWAD type by filename (thanks mether).
-
-    libtextscreen:
-     * There is now limited UTF-8 text support in the textscreen
-       library, used in the label and input box widgets.
-     * Scroll bar behavior was fixed (thanks Alexandre Xavier).
-     * Input boxes stop editing and save when they lose their focus,
-       correcting a previous counterintuitive behavior (thanks
-       Twelve).
-     * The numeric keypad now works properly when entering text values
-       (thanks Twelve).
-
-1.6.0 (2011-05-17):
-
-     * The instructions in the INSTALL file are now customized for
-       different platforms, and each binary package contains a version
-       with instructions specific to the platform that it is
-       targetting.  This should help to avoid confusion that some
-       users have reported experiencing.
-     * The display settings window in the setup tool has been
-       reorganised to a better arrangement.
-     * It is now possible to load .lmp files (and play back demos)
-       with long filenames (thanks blzut3).
-     * In the setup tool, it is now possible to hold down shift when
-       changing key/mouse/joystick bindings to prevent other bindings
-       to the same key from being cleared (thanks myk).
-     * The joystick menu in the setup tool now has a test button
-       (thanks Alexandre Xavier).
-     * Specifying the -privateserver option implies -server (thanks
-       Porsche Monty).
-     * The Mac OS X .dmg package now has a background and looks generally
-       more polished.
-     * In Mac OS X, it is now possible to simply double click an IWAD
-       file in the Finder to configure its location within the launcher.
-     * Freedesktop.org desktop files are now installed for Doom and
-       the setup tool, which will appear in the main menu on desktop
-       environments such as Gnome and KDE (thanks Adrián Chaves
-       Fernández).
-     * The Chex Quest dehacked patch (chex.deh) will now be detected
-       if it is in the same directory as the IWAD file.
-
-    Compatibility:
-     * Added support for the alternate version of the Final Doom
-       executable included in some later versions of the Id Anthology.
-       This version fixed the demo loop crash that occurred with the
-       "original" Final Doom executable.
-
-       This executable can be selected on the command line with
-       -gameversion final2. It has been made the default when playing
-       with the Final Doom IWADs (the original behavior can be
-       selected with -gameversion final).  (thanks Porsche Monty,
-       Enjay).
-     * Very short sound effects are not played, to better emulate the
-       behavior of DMX in Vanilla Doom (thanks to Quasar for help in
-       investigating this).
-     * The null sector dereference emulation code has been imported
-       from Prboom+ - this fixes a desync with CLNJ-506.LMP (thanks
-       entryway).
-     * The IDMUS cheat doesn't work when emulating the v1.9 executable
-       (thanks Alexandre Xavier).
-
-    Bugs fixed:
-     * Menu navigation when using joystick/joypad (thanks Alexandre
-       Xavier).
-     * For configuration file value for shift keys, use scan code for
-       right shift, not left shift (thanks Alexandre Xavier).
-     * Default joystick buttons for the setup tool now match Vanilla
-       (thanks twipley).
-     * Visual Studio project files work again (thanks GhostlyDeath).
-     * The default sfx/music volume set by the setup tool is now 8
-       instead of 15, matching the game itself. (thanks Alexandre
-       Xavier).
-     * Weapon cycling from the shotgun to the chaingun in Doom 1 now
-       works properly (thanks Alexandre Xavier).
-     * MIDI playback that locked up when using an empty MUS / MIDI
-       file (thanks Alexandre Xavier).
-     * Default sampling rate used by setup tool changed to 44100Hz, to
-       match the game default (thanks Alexandre Xavier).
-     * Cheat codes and menu hot keys now work when shift is held down
-       or capslock turned on (thanks Alexandre Xavier).
-
-    libtextscreen:
-     * The background on GUI controls now lights up when hovering over
-       them, so that it is more obvious what you are selecting.
-     * It is now possible to type a '+' in input boxes (thanks
-       Alexandre Xavier).
-     * It is possible to use the mouse wheel to scroll through scroll
-       panes.
-     * Clicking on scroll bars now moves the scroll handle to a
-       matching location.
-     * Clicking outside a dropdown list popup window now dismisses the
-       window.
-     * Window hotkeys that are an alphabetical letter now work when
-       shift is held down or capslock turned on (thanks Alexandre
-       Xavier).
-
-1.5.0 (2011-01-02):
-
-    Big changes in this version:
-     * The DOSbox OPL emulator (DBOPL) has been imported to replace
-       the older FMOPL code.  The quality of OPL emulation is now
-       therefore much better.
-     * The game can now run in screen modes at any color depth (not
-       just 8-bit modes).  This is mainly to work around problems with
-       Windows Vista/7, where 8-bit color modes don't always work
-       properly.
-     * Multiplayer servers now register themselves with an Internet
-       master server.  Use the -search command line parameter to
-       find servers on the Internet to play on.  You can also use
-       DoomSeeker (http://skulltag.net/doomseeker/) which supports
-       this functionality.
-     * When running in windowed mode, it is now possible to
-       dynamically resize the window by dragging the window borders.
-     * Names can be specified for servers with the -servername command
-       line parameter.
-     * There are now keyboard, mouse and joystick bindings to cycle
-       through available weapons, making play with joypads or mobile
-       devices (ie. without a proper keyboard) much more practical.
-     * There is now a key binding to change the multiplayer spy key
-       (usually F12).
-     * The setup tool now has a "warp" button on the main menu, like
-       Vanilla setup.exe (thanks Proteh).
-     * Up to 8 mouse buttons are now supported (including the
-       mousewheel).
-     * A new command line parameter has been added (-solo-net) which
-       can be used to simulate being in a single player netgame.
-     * There is now a configuration file parameter to set the OPL I/O
-       port, for cards that don't use port 0x388.
-     * The Python scripts used for building Chocolate Doom now work
-       with Python 3 (but also continue to work with Python 2)
-       (thanks arin).
-     * There is now a NOT-BUGS file included that lists some common
-       Vanilla Doom bugs/limitations that you might encounter
-       (thanks to Sander van Dijk for feedback).
-
-    Compatibility:
-     * The -timer and -avg options now work the same as Vanilla when
-       playing back demos (thanks xttl)
-     * A texture lookup bug was fixed that caused the wrong sky to be
-       displayed in Spooky01.wad (thanks Porsche Monty).
-     * The HacX v1.2 IWAD file is now supported, and can be used
-       standalone without the need for the Doom II IWAD (thanks
-       atyth).
-     * The I_Error function doesn't display "Error:" before the error
-       message, matching the Vanilla behavior.  "Error" has also been
-       removed from the title of the dialog box that appears on
-       Windows when this happens.  This is desirable as not all such
-       messages are actually errors (thanks Proteh).
-     * The setup tool now passes through all command line arguments
-       when launching the game (thanks AlexXav).
-     * Demo loop behavior (ie. whether to play DEMO4) now depends on
-       the version being emulated.  When playing Final Doom the game
-       will exit unexpectedly as it tries to play the fourth demo -
-       this is Vanilla behaviour (thanks AlexXav).
-
-    Bugs fixed:
-     * A workaround has been a bug in old versions of SDL_mixer
-       (v1.2.8 and earlier) that could cause the game to lock up.
-       Please upgrade to a newer version if you haven't already.
-     * It is now possible to use OPL emulation at 11025Hz sound
-       sampling rate, due to the new OPL emulator (thanks Porsche
-       Monty).
-     * The span renderer function (used for drawing floors and
-       ceilings) now behaves the same as Vanilla Doom, so screenshots
-       are pixel-perfect identical to Vanilla Doom (thanks Porsche
-       Monty).
-     * The zone memory system now aligns allocated memory to 8-byte
-       boundaries on 64-bit systems, which may fix crashes on systems
-       such as sparc64 (thanks Ryan Freeman and Edd Barrett).
-     * The configure script now checks for libm, fixing compile
-       problems on Fedora Linux (thanks Sander van Dijk).
-     * Sound distortion with certain music files when played back
-       using OPL (eg. Heretic title screen).
-     * Error in Windows when reading response files (thanks Porsche
-       Monty, xttl, Janizdreg).
-     * Windows Vista/7 8-bit color mode issues (the default is now to
-       run in 32-bit color depth on these versions) (thanks to
-       everybody who reported this and helped test the fix).
-     * Screen borders no longer flash when running on widescreen
-       monitors, if you choose a true-color screen mode (thanks
-       exp(x)).
-     * The controller player in a netgame is the first player to join,
-       instead of just being someone who gets lucky.
-     * Command line arguments that take an option now check that an
-       option is provided (thanks Sander van Dijk).
-     * Skill level names in the setup tool are now written the same as
-       they are on the in-game "new game" menu (thanks AlexXav).
-     * There is no longer a limit on the lengths of filenames provided
-       to the -record command line parameter (thanks AlexXav).
-     * Window title is not lost in setup tool when changing video
-       driver (thanks AlexXav).
-
-    libtextscreen:
-     * The font used for the textscreen library can be forced by
-       setting the TEXTSCREEN_FONT environment variable to "small" or
-       "normal".
-     * Tables or scroll panes that don't contain any selectable widgets
-       are now themselves not selectable (thanks Proteh).
-     * The actions displayed at the bottom of windows are now laid out
-       in a more aesthetically pleasing way.
-
-1.4.0 (2010-07-10):
-
-     The biggest change in this version is the addition of OPL
-     emulation.  This emulates Vanilla Doom's MIDI playback when
-     using a Yamaha OPL synthesizer chip, as was found on
-     SoundBlaster compatible cards.
-
-     A software OPL emulator is included as most modern computers do
-     not have a hardware OPL chip any more.  If you do have one, you
-     can configure Chocolate Doom to use it; see README.OPL.
-
-     The OPL playback feature is not yet perfect or 100% complete,
-     but is judged to be good enough for general use.  If you find
-     music that does not play back properly, please report it as a
-     bug.
-
-     Other changes:
-     * The REJECT overflow emulation code from PrBoom+ has been
-       imported.  This fixes demo desync on some demos, although
-       others will still desync.
-     * Warnings are now generated for invalid dehacked replacements of
-       printf format strings.  Some potential buffer overflows are
-       also checked.
-     * The installation instructions (INSTALL file) have been
-       clarified and made more platform-agnostic.
-     * The mouse is no longer warped to the center of the screen when
-       the demo sequence advances.
-     * Key bindings can now be changed for the demo recording quit key
-       (normally 'q') and the multiplayer messaging keys (normally
-       't', 'g', 'i', 'b' and 'r').
-
-1.3.0 (2010-02-10):
-
-     * Chocolate Doom now runs on Windows Mobile/Windows CE!
-     * It is possible to rebind most/all of the keys that control the
-       menu, shortcuts, automap and weapon switching.  The main
-       reason for this is to support the Windows CE port and other
-       platforms where a full keyboard may not be present.
-     * Chocolate Doom now includes a proper Mac OS X package; it is
-       no longer necessary to compile binaries for this system by
-       hand.  The package includes a simple graphical launcher
-       program and can be installed simply by dragging the "Chocolate
-       Doom" icon to the Applications folder. (thanks to Rikard Lang
-       for extensive testing and feedback)
-     * The video mode auto-adjust code will automatically choose
-       windowed mode if no fullscreen video modes are available.
-     * The zone memory size is automatically reduced on systems with
-       a small amount of memory.
-     * The "join game" window in the setup tool now has an option to
-       automatically join a game on the local network.
-     * Chocolate Doom includes some initial hacks for compiling under
-       SDL 1.3.
-     * Recent versions of SDL_mixer include rewritten MIDI code on Mac
-       OS X.  If you are using a version of SDL_mixer with the new
-       code, music will now be enabled by default.
-     * Windows Vista and Windows 7 no longer prompt for elevated
-       privileges when running the setup tool (thanks hobbs and
-       MikeRS).
-     * The Windows binaries now have better looking icons (thanks
-       MikeRS).
-     * Magic values specified using the -spechit command line
-       parameter can now be hexadecimal.
-     * DOOMWADDIR/DOOMWADPATH can now specify the complete path to
-       IWAD files, rather than the path to the directory that contains
-       them.
-     * When recording shorttics demos, errors caused by the reduced
-       turning resolution are carried forward, possibly making turning
-       smoother.
-     * The source tarball can now be used to build an RPM package:
-         rpmbuild -tb chocolate-doom-VER.tar.gz
-
-    Compatibility:
-     * The A_BossDeath behavior in v1.9 emulation mode was fixed
-       (thanks entryway)
-     * The "loading" disk icon is drawn more like how it is drawn in
-       Vanilla Doom, also fixing a bug with chook3.wad.
-     * Desync on 64-bit systems with ep1-0500.lmp has (at long last)
-       been fixed (thanks exp(x)).
-     * Donut overrun emulation code imported from Prboom+ (thanks
-       entryway).
-     * The correct level name should now be shown in the automap for
-       pl2.wad MAP33 (thanks Janizdreg).
-     * In Chex Quest, the green radiation suit colormap is now used
-       instead of the red colormaps normally used when taking damage
-       or using the berserk pack.  This matches Vanilla chex.exe
-       behavior (thanks Fuzztooth).
-     * Impassible glass now displays and works the same as in Vanilla,
-       fixing wads such as OTTAWAU.WAD (thanks Never_Again).
-
-    Bugs fixed:
-     * Memory-mapped WAD I/O is disabled by default, as it caused
-       various issues, including a slowdown/crash with Plutonia 2
-       MAP23.  It can be explicitly re-enabled using the '-mmap'
-       command line parameter.
-     * Crash when saving games due to the ~/.chocolate-doom/savegames
-       directory not being created (thanks to everyone who reported
-       this).
-     * Chocolate Doom will now run under Win95/98, as the
-       SetProcessAffinityMask function is looked up dynamically.
-     * Compilation under Linux with older versions of libc will now
-       work (the semantics for sched_setaffinity were different in
-       older versions)
-     * Sound clipping when using libsamplerate was improved (thanks
-       David Flater)
-     * The audio buffer size is now calculated based on the sample
-       rate, so there is not a noticeable delay when using a lower
-       sample rate.
-     * The manpage documentation for the DOOMWADPATH variable was
-       fixed (thanks MikeRS).
-     * Compilation with FEATURE_MULTIPLAYER and FEATURE_SOUND
-       disabled was fixed.
-     * Fixed crash when using the donut special type and the joining
-       linedef is one sided (thanks Alexander Waldmann).
-     * Key settings in a configuration file that are out of range
-       do not cause a crash (thanks entryway).
-     * Fix ear-piercing whistle when playing the MAP05 MIDI music
-       using timidity with EAWPATS (thanks entryway / HackNeyed).
-
-    libtextscreen:
-     * There is now a second, small textscreen font, so that the
-       ENDOOM screen and setup tool can be used on low resolution
-       devices (eg. PDAs/embedded devices)
-     * The textscreen library now has a scrollable pane widget. Thanks
-       to LionsPhil for contributing code to scroll up and down using
-       the keyboard.
-     * Doxygen documentation was added for the textscreen library.
-
-1.2.1 (2008-12-10):
-
-    This version just fixes a crash at the intermission screen when
-    playing Doom 1 levels.
-
-1.2.0 (2008-12-10):
-
-    Happy 15th Birthday, Doom!
-
-     * Chocolate Doom now has an icon that is not based on the proprietary
-       Doom artwork.
-     * There is now memory-mapped WAD I/O support, which should be useful
-       on some embedded systems.
-     * Chex quest emulation support is now included, although an
-       auxiliary dehacked patch is needed (chexdeh.zip in the idgames
-       archive).
-
-    Compatibility:
-     * The armor class is always set to 2 when picking up a megasphere
-       (thanks entryway).
-     * The quit screen prompts to quit "to dos" instead of just to quit
-       (thanks MikeRS)
-     * The "dimensional shambler" quit message was fixed.
-     * Fix crash related to A_BFGSpray with NULL target when using
-       dehacked patches - discovered with insaned2.deh
-       (thanks CSonicGo)
-     * NUL characters are stripped from dehacked files, to ensure correct
-       behavior with some dehacked patches (eg. the one with portal.wad).
-
-    Bugs fixed:
-     * "Python Image Library" should have been "Python Imaging Library"
-       (thanks exp(x)).
-     * The setup tool should no longer ask for elevated permissions
-       on Windows Vista (this fix possibly may not work).
-     * The application icon is set properly when running under Windows
-       XP with the "Luna" theme.
-     * Fix compilation under Cygwin to detect libraries and headers from
-       the correct environment.
-     * The video code does not try to read SDL events before SDL has
-       been properly initialised - this was causing problems with some
-       older versions of SDL.
-
-1.1.1 (2008-04-20):
-
-    The previous release (v1.1.0) included a bug that broke compilation
-    when libsamplerate support was enabled.  The only change in this 
-    version is to fix this bug.
-
-1.1.0 (2008-04-19):
-
-     * The video mode code has been radically restructured.  The video mode is
-       now chosen by directly specifying the mode to use; the scale factor is
-       then chosen to fit the screen.  This is helpful when using widescreen
-       monitors (thanks Linguica)
-     * MSVC build project files (thanks GhostlyDeath and entryway).
-     * Unix manpage improvements; the manpage now lists the environment
-       variables that Chocolate Doom uses.  Manpages have been added for
-       chocolate-setup and chocolate-server, from the versions for the Debian
-       Chocolate Doom package (thanks Jon Dowland).
-     * INSTALL file with installation instructions for installing Chocolate
-       Doom on Unix systems.
-     * Support for high quality resampling of sound effects using 
-       libsamplerate (thanks David Flater).
-     * A low pass filter is applied when doing sound resampling in an
-       attempt to filter out high frequency noise from the resampling
-       process.
-     * R_Main progress box is not displayed if stdout is a file (produces
-       cleaner output).
-     * Client/server version checking can be disabled to allow different
-       versions of Chocolate Doom to play together, or Chocolate Doom
-       clients to play with Strawberry Doom clients.
-     * Unix manpages are now generated for the Chocolate Doom 
-       configuration files.
-     * The BSD PC speaker driver now works on FreeBSD.
-
-    Compatibility:
-     * Use the same spechits compatibility value as PrBoom+, for consistency
-       (thanks Lemonzest).
-     * The intercepts overrun code has been refactored to work on big
-       endian machines.
-     * The default startup delay has been set to one second, to allow 
-       time for the screen to settle before starting the game (some 
-       monitors have a delay before they come back on after changing modes).
-     * If a savegame buffer overrun occurs, the savegame does not get saved
-       and existing savegames are not overwritten (same behaviour as 
-       Vanilla).
-
-    Bugs fixed:
-     * Desync with STRAIN demos and dehacked Misc values not being set
-       properly (thanks Lemonzest)
-     * Don't grab the mouse if the mouse is disabled via -nomouse or use_mouse
-       in the configuration file (thanks MikeRS).
-     * Don't center the mouse on startup if the mouse is disabled (thanks
-       Siggi).
-     * Reset the palette when the window is restored to clear any screen
-       corruption (thanks Catoptromancy).
-     * mus2mid.c should use MEM_SEEK_SET, not SEEK_SET (thanks Russell)
-     * Fast/Respawn options were not being exchanged when starting netgames
-       (thanks GhostlyDeath).
-     * Letterbox mode is more accurately described as "pillarboxed" or 
-       "windowboxed" where appropriate (thanks MikeRS)
-     * Process affinity mask is set to 1 on Windows, to work around a 
-       bug in SDL_mixer that can cause crashes on multi-core machines
-       (thanks entryway).
-     * Bugs in the joystick configuration dialog in the setup tool have
-       been fixed.
-
-1.0.0 (2007-12-10):
-
-    This release is dedicated to Dylan 'Toke' McIntosh, who was
-    tragically killed in a car crash in 2006.  I knew Dylan
-    from IRC and the Doomworld forums for several years, and he had
-    a deep passion for this game.  He was also a huge help for me while
-    developing Chocolate Doom, as he helped point out a lot of small
-    quirks in Vanilla Doom that I didn't know about. His death is a 
-    great loss.  RIP Toke.
-
-    This is the first release to reach full feature parity with 
-    Vanilla Doom.  As a result, I have made this version 1.0.0, so
-    Chocolate Doom is no longer beta!
-
-    Big new features:
-     * Multiplayer!  This version includes an entirely new multiplayer
-       engine, based on a packet server architecture.  I'd like to thank
-       joe, pritch, Meph and myk, and everyone else who has helped test
-       the new code for their support, feedback and help in testing this.  
-       The new code still needs more testing, and I'm eager to hear any 
-       feedback on this.
-     * A working setup tool.  This has the same look and feel as the 
-       original setup.exe.  I hope people like it!  Note that it has 
-       some advantages over the original setup.exe - for example, 
-       you can use the mouse.
-
-    Other new features:
-     * New mus conversion code thanks to Ben Ryves.  This converts the
-       Doom .mus format to .mid a lot better.  As one example, tnt.wad
-       Map02 is now a lot closer to how Vanilla says.  Also, the music 
-       on the deca.wad titlescreen now plays!
-     * x3, x4 and x5 display scale (thanks to MikeRS for x5 scale).
-     * Fullscreen "letterbox" mode allows Chocolate Doom to run on machines
-       where 1.6:1 aspect ratio modes are unavailable (320x200/640x400).
-       The game runs in 320x240/640x480 instead, with black borders.
-       The system automatically adjusts to this if closer modes are 
-       unavailable.
-     * Aspect ratio correction: you can (also) run at 640x480 without black 
-       borders at the top and bottom of the screen.
-     * PC speaker sound effect support.  Chocolate Doom can output real
-       PC speaker sounds on Linux, or emulate a PC speaker through the
-       sound card.
-     * Working three-screen mode, as seen in early versions of Doom!
-       To test this out, put three computers on a LAN and type:
-         chocolate-doom -server
-	 chocolate-doom -autojoin -left
-	 chocolate-doom -autojoin -right
-     * Allow a delay to be specified on startup, to allow the display to
-       settle after changing modes before starting the game.
-     * Allow the full path and filename to be specified when loading demos:
-       It is now possible to type 'chocolate-doom -playdemo /tmp/foo.lmp'
-       for example.
-     * Savegames are now stored in separate directories depending on
-       the IWAD: eg. the savegames for Doom II are stored in a different
-       place to those for Doom I, Final Doom, etc. (this does not affect
-       Windows).
-     * New mouse acceleration code works based on a threshold and 
-       acceleration.  Hopefully this should be closer to what the DOS
-       drivers do.  There is a 'test' feature in the setup tool to help 
-       in configuring this.
-     * New '-nwtmerge' command line option that emulates NWT's '-merge'
-       option.  This allows TiC's Obituary TC to be played.
-     * The ENDOOM screen no longer closes automatically, you have to click
-       the window to make it go away.
-     * Spechit overrun fixes and improvements.  Thanks to entryway for
-       his continued research on this topic (and because I stole your
-       improvements :-).  Thanks to Quasar for reporting a bug as well.
-     * Multiple dehacked patches can be specified on the command line,
-       in the same way as with WADs - eg. -deh foo.deh bar.deh baz.deh.
-     * Default zone memory size increased to 16MB; this can be controlled
-       using the -mb command-line option.
-     * It is now possible to record demos of unlimited length (by default, 
-       the Vanilla limit still applies, but it can now be disabled).
-     * Autoadjusting the screen mode can now be disabled.
-     * On Windows, the registry is queried to detect installed versions of
-       Doom and automatically locate IWAD files.  IWADs installed through
-       Steam are also autodetected.
-     * Added DOOMWADPATH that can be used like PATH to specify multiple 
-       locations in which to search for IWAD files.  Also, '-iwad' is 
-       now enhanced, so that eg. '-iwad doom.wad' will now search all 
-       IWAD search paths for 'doom.wad'.
-     * Improved mouse tracking that should no longer lag.  Thanks to 
-       entryway for research into this.
-     * The SDL driver can now be specified in the configuration file.  
-       The setup tool has an option on Windows to select between
-       DirectX and windib.
-     * Joystick support.
-     * Configuration file option to change the sound sample rate.
-     * More than three mouse buttons are now supported.
-     
-    Portability improvements: 
-     * Chocolate Doom now compiles and runs cleanly on MacOS X.  Huge
-       thanks go to Insomniak who kindly gave me an account on his machine
-       so that I could debug this remotely.  Big thanks also go to 
-       athanatos on the Doomworld forums for his patience in testing 
-       various ideas as I tried to get Chocolate Doom up and running
-       on MacOS.
-     * Chocolate Doom now compiles and runs natively on AMD64.
-     * Chocolate Doom now compiles and runs on Solaris/SPARC, including
-       the Sun compiler.  Thanks to Mike Spooner for some portability 
-       fixes.
-     * Improved audio rate conversion, so that sound should play properly
-       on machines that don't support low bitrate output.
-
-    Compatibility fixes:
-     * Check for IWADs in the same order as Vanilla Doom.
-     * Dehacked code will now not allow string replacements to be longer than
-       those possible through DOS dehacked.
-     * Fix sound effects playing too loud on level 8 (thanks to myk
-       for his continued persistence in getting me to fix this)
-     * Save demos when quitting normally - it is no longer necessary to
-       press 'q' to quit and save a demo.
-     * Fix spacing of -devparm mode dots.
-     * Fix sky behavior to be the same as Vanilla Doom - when playing in
-       Doom II, the skies never change from the sky on the first level
-       unless the player loads from a savegame. 
-     * Make -nomouse and config file use_mouse work again.
-     * Fix the -nomusic command-line parameter.  Make the snd_sfxdevice
-       snd_musicdevice values in the configuration file work, so that it
-       is possible to disable sound, as with Vanilla.
-     * Repeat key presses when the key is held down (this is the Vanilla 
-       behavior)  - thanks to Mad_Mac for pointing this out.
-     * Don't print a list of all arguments read from response files - Vanilla
-       doesn't do this.
-     * Autorun only when joyb_speed >= 10, not >= 4.  Thanks to Janizdreg 
-       for this.
-     * Emulate a bug in DOS dehacked that can overflow the dehacked
-       frame table and corrupt the weaponinfo table.  Note that this means
-       Batman Doom will no longer play properly (identical behavior
-       to Vanilla); vbatman.deh needs to also be applied to fix it.
-       (Thanks grazza)
-     * Allow dehacked 2.3 patches to be loaded.
-     * Add more dehacked string replacements.
-     * Compatibility option to enable or disable native key mappings.  This
-       means that people with non-US keyboards can decide whether to use
-       their correct native mapping or behave like Vanilla mapping (which
-       assumes all keyboards are US).
-     * Emulate overflow bug in P_FindNextHighestFloor.  Thanks to
-       entryway for the fix for this.
-     * Add -netdemo command line parameter, for playing back netgame
-       demos recorded with a single player.
-     * The numeric keypad now behaves like Vanilla Doom does.
-     * Fix some crashes when loading from savegames.
-     * Add intercepts overrun emulation from PrBoom-plus.  Thanks again
-       to entryway for his research on this subject.
-     * Add playeringame overrun emulation.
-
-    Bugs fixed:
-     * Fix crash when starting new levels due to the intermission screen
-       being drawn after the WI_ subsystem is shut down (thanks 
-       pritch and joe)
-     * Catch failures to initialise sound properly, and fail gracefully.
-     * Fix crasher in 1427uv01.lmp (thanks ultdoomer)
-     * Fix crash in udm1.wad.
-     * Fix crash when loading a savegame with revenant tracer missiles.
-     * Fix crash when loading a savegame when a mancubus was in the middle
-       of firing.
-     * Fix Doom 1 E1-3 intermission screen animations.
-     * Fix loading of dehacked "sound" sections.
-     * Make sure that modified copyright banners always end in a newline
-       - this fixes a bug with av.wad (thanks myk)
-     * Added missing quit message ("are you sure you want to quit this
-       great game?").
-     * Fix when playing long sound effects - the death sound in marina.wad
-       now plays properly, for example.
-     * Fix buffer overrun on the quicksave prompt screen that caused a
-       mysterious cycling character to appear.
-     * IDCLEV should not work in net games (thanks Janizdreg)
-     * Stop music playing at the ENDOOM screen.
-     * Fix sound sample rate conversion crash.
-     * Fix 'pop' heard at the end of sound effects.
-     * Fix crash when playing long sounds.
-     * Fix bug with -timedemo accuracy over multi-level demos.
-     * Fix bug with the automap always following player 1 in multiplayer
-       mode (thanks Janizdreg).
-
-0.1.4 (2006-02-13):
-
-    NWT-style merging command line options (allows Mordeth to be played)
-    Unix manpage (thanks Jon Dowland)
-    Dehacked improvements/fixes:
-     * Allow changing the names of graphic lumps used in menu, status bar
-       intermission screen, etc.
-     * Allow changing skies, animated flats + textures
-     * Allow changing more startup strings.
-     * Allow text replacements on music + sfx lump names
-    Fix for plutonia map12 crash.
-    Fix bug with playing long sfx at odd sample rates.
-    Big Endian fixes (for MacOS X).  Thanks to athanatos for helping
-        find some of these.
-    Install into /usr/games, rather than /usr/bin (thanks Jon Dowland)
-
-0.1.3 (2006-01-20):
-
-    Imported the spechit overrun emulation code from prboom-plus.  Thanks to
-         Andrey Budko for this.
-    New show_endoom option in the chocolate-doom.cfg config file allows
-         the ENDOOM screen to be disabled.
-    Chocolate Doom is now savegame-compatible with Vanilla Doom.
-
-    Fixes for big endian machines (thanks locust)
-    Fixed the behavior of the dehacked maximum health setting.
-    Fix the "-skill 0" hack to play without any items (thanks to Janizdreg
-        for pointing out that this was nonfunctional)
-    Fix playing of sounds at odd sample rates (again).  Sound effects at
-        any sample rate now play, but only sounds with valid headers.
-        This is the *real* way Vanilla Doom behaves.  Thanks to myk for
-        pointing out the incorrect behavior.
-
-0.1.2 (2005-10-29):
-
-    Silence sounds at odd sample rates (rather than bombing out); this
-        is the way Vanilla Doom behaves.
-    Handle multiple replacements of the same sprite in a PWAD.
-    Support specifying a specific version to emulate via the command line
-        (-gameversion)
-    Fix help screen orderings and skull positions.  Behave exactly as
-        the original executables do.
-
-0.1.1 (2005-10-18):
-    Display startup "banners" if they have been modified through 
-        dehacked.
-    Dehacked "Misc" section support.
-
-    Bugs fixed:
-     * Doom 1 skies always using Episode 1 sky
-     * Crash when switching applications while running fullscreen
-     * Lost soul bounce logic (do not bounce in Registered/Shareware)
-     * Mouse buttons mapped incorrectly (button 1 is right, 2 is middle)
-     * Music not pausing when game is paused, when using SDL_mixer's 
-       native MIDI playback.
-     * Pink icon on startup (palette should be fully set before anything is
-       loaded)
-
-0.1.0 (2005-10-09):
-    Dehacked support
-    WAD merging for TCs
-    ENDOOM display
-    Fix bug with invalid MUS files causing crashes
-    Final Doom fixes
-
-0.0.4 (2005-09-27):
-    Application icon and version info included in Windows .exe files
-    Fixes for non-x86 architectures
-    Fix uac_dead.wad (platform drop on e1m8 should occur when all
-        bosses die, not just barons)
-    Fix "loading" icon to work for all graphics modes
-
-0.0.3 (2005-09-17):
-    Mouse acceleration code to emulate the behaviour of old
-        DOS mouse drivers (thanks to Toke for information about 
-        this and suggestions)
-    Lock surfaces properly when we have to (fixes crash under
-        Windows 98)
-
-0.0.2 (2005-09-13):
-    Remove temporary MIDI files generated by sound code.
-    Fix sound not playing at the right volume
-    Allow alt-tab away while running in fullscreen under Windows
-    Add second configuration file (chocolate-doom.cfg) to allow 
-        chocolate-doom specific settings.
-    Fix switches not changing in Ultimate Doom
-
-0.0.1 (2005-09-07):
-    First beta release
-
-# vim: tw=70
-
diff --git a/NEWS.md b/NEWS.md
new file mode 100644
index 0000000..837a808
--- /dev/null
+++ b/NEWS.md
@@ -0,0 +1,1193 @@
+## 2.3.0 (2016-12-29)
+
+### General
+  * 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)
+  * 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
+    Jon, Quasar, Edward-san)
+  * 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
+  * 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)
+  * 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##) 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)
+  * 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)
+  * Crashes are now handled more gracefully when a linedef references
+    nonexistent sidedefs (thanks Fabian)
+
+### Heretic
+  * 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 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 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)
+
+### libtextscreen
+  * 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)
+
+  Chocolate Doom has not seen a great deal of “stable” patch releases
+  in its history. While the development tree sees major new features
+  and changes, the purpose of this release, and hopefully others to
+  follow like it, is to repair some deficiencies that existed
+  in 2.2.0.
+
+### General
+  * Preferences for the OS X launcher are now stored with a unique
+    name to not conflict with other applications. (thanks
+    Xeriphas1994)
+  * Unix desktop entry files are now brought up to full desktop entry
+    specification compliance. (thanks chungy, Fabian)
+  * Unix AppData entries are now included, allowing software centers
+    to display detailed information about the engines. (thanks chungy)
+  * Partial XDG base directory specification compliance on Unix
+    systems now exist to search for IWAD paths.  One benefit is that
+    $HOME/.local/share/games/doom is now a valid location to store and
+    automatically find IWADs. (thanks chungy)
+
+### Build systems
+  * The Microsoft Visual Studio build system was not fully functional
+    in 2.2.0 and has been fixed. (thanks Linguica)
+  * The autoconf build system checks for windres only for Windows
+    toolchains.  Some Linux distributions mistakingly include the
+    program in their native toolchains. (thanks Fabian)
+  * A compiler hint for packed structs has been added, which otherwise
+    broke the games when built under recent GCC releases for
+    Windows. (thanks Fabian)
+
+### Doom
+  * The GOG.com releases of The Ultimate Doom, Doom II, and Final Doom
+    are now detected and supported on Windows. (thanks chungy)
+  * An integer overflow was used in spawn angle calculation, undefined
+    C behavior which broke with Clang optimization.  (thanks David
+    Majnemer for insight)
+
+### Setup tool
+  * The help URL for the level warp menu now points to the proper wiki
+    page, rather than the multiplayer page.
+  * The manifest has been updated for Windows 10 compatibility.
+    (thanks chungy)
+
+## 2.2.0 (2015-06-09)
+
+  * The Hexen four level demo IWAD is now supported. Thanks to Fabian
+    Greffrath for his careful investigation and emulation of the demo
+    game’s behavior in developing this.
+  * OPL music playback has been improved in a number of ways to match
+    the behavior of Vanilla Doom’s DMX library much more closely. OPL3
+    playback is also now supported. Thanks go to Alexey Khokholov for
+    his excellent research into the Vanilla DMX library that enabled
+    these improvements.
+  * New gamepad configurations:
+      - PS4 DualShock 4 (thanks Matt “3nT” Davis).
+      - Xbox One controller on Linux (thanks chungy).
+      - “Super Joy Box 7” USB/PC gameport adapter.
+  * The Doom reload hack has been added back. See the wiki for more
+    context on this: http://doomwiki.org/wiki/Reload_hack
+  * The IWAD file from Strife: Veteran Edition is now detected
+    automatically (thanks chungy).
+  * It’s now possible to build outside of the source directory (thanks
+    Dave Murphy).
+  * MSVC project files were brought up to date (thanks dbrackett16).
+  * M_StringDuplicate() has been added as a safer replacement for
+    strdup() (thanks Quasar). M_StringCopy() now handles short buffers
+    more gracefully.
+  * The netgame discrepancy window is now dismissed by pressing enter
+    to proceed, not escape (thanks Alexandre-Xavier).
+  * A couple of source files that were in the previous release and
+    were GPL3 have been replaced by GPL2 equivalents. Previous
+    releases that included these files should be retroactively
+    considered GPL3.
+
+### Bug fixes
+  * A long-standing bug that could cause every display frame to be
+    rendered twice was fixed (thanks Linguica, Harha, Alexandre-
+    Xavier).
+  * Lots of endianness fixes were integrated that were found by Ronald
+    Lasmanowicz during development of his Wii port of Chocolate Doom,
+    including a fix for a bug that could cause monsters to become
+    partially invisible.
+  * DeHackEd files without a newline character at the EOF are now
+    correctly parsed (thanks Fabian).
+  * An infinite loop that could occur in the weapon cycling code was
+    fixed (thanks raithe, Fabian).
+  * Mouse input triggered by cursor warp was fixed (thanks Super6-4).
+  * Loop tags in substitute music files are ignored if both of the
+    loop tags are equal to zero. This makes us consistent with other
+    source ports that support the tags.
+  * It’s now possible to more conveniently play back demo .lmp files
+    with names that end in the all-caps “.LMP” (thanks Ioan Chera).
+  * Some code that accessed memory after freeing it was fixed. Two new
+    parameters, -zonezero and -zonescan, were added to try to help
+    detect these cases.
+  * Mistaken assumptions about representations of booleans that
+    affected some ARM systems were fixed (thanks floppes).
+  * memcpy() uses on overlapping memory were changed to use memmove(),
+    fixing abort traps on OpenBSD (thanks ryan-sg).
+  * Hyphens in manpages were fixed (thanks chungy, Fabian).
+  * Lots of compiler build warnings were fixed (thanks Fabian).
+
+### Setup tool
+  * The setup tool now has help buttons for its various different
+    screens, which link to articles on the wiki that give more
+    information (thanks to chungy for helping to put the wiki pages
+    together).
+  * A fix was applied for a buffer overrun that could occur if the
+    user had lots of IWAD files installed (thanks Fabian).
+  * A crash related to username lookup was fixed.
+  * It’s now possible to connect via the setup tool to multiplayer
+    servers that are not listening on the default port (thanks
+    Alexandre-Xavier).
+
+### Doom
+  * Sky transitions when emulating the id anthology version of the
+    Final Doom executable were fixed (thanks Alexandre-Xavier, Fabian,
+    chungy).
+  * Structure fields in the stair-building functions were fixed to be
+    deterministic, fixing a desync in mm09-512.lmp (thanks Fabian).
+
+### Hexen
+  * A bug with texture names that had long names was fixed (thanks
+    ETTiNGRiNDER).
+  * Minotaur spawn time is now stored in little endian format, fixing
+    a bug that affected compatibility with Vanilla savegames on big
+    endian systems.
+  * Code that starts ACS scripts is no longer compiler-dependent.
+
+### Strife (all these are thanks to Quasar)
+  * Sound priority was changed, so that the ticking sound that Stalker
+    enemies make while active matches Vanilla behavior (thanks
+    GeoffLedak).
+  * Minor fixes to game behavior to match Vanilla, discovered during
+    development of Strife: Veteran edition.
+  * Behavior of descending stairs was fixed to match Vanilla.
+  * Inventory items beyond the 8-bit range are now allowed in
+    netgames.
+  * Automap behavior better matches Vanilla now.
+  * Multiplayer name changes were fixed.
+  * Sound origin behavior for switches was fixed.
+  * Teleport beacon behavior was fixed.
+  * Default Strife skill level and screen size were changed to match
+    Vanilla.
+  * Bug was fixed where Rowan would not always take Beldin’s ring.
+  * Totally-invisible objects are now displayed correctly, and a
+    Vanilla glitch with Shadow Acolytes is correctly emulated.
+  * The level name for MAP29 (Entity’s Lair) was fixed (thanks
+    chungy).
+
+### libtextscreen
+  * The main loop now exits immediately once all windows are closed
+    (thanks Alexander-Xavier).
+  * The large font is no longer selected based entirely on screen
+    size.
+
+## 2.1.0 (2014-10-22)
+
+  Chocolate Doom now supports high-quality substitute music packs that
+  are used in place of the original MIDI music tracks. I’m hoping to
+  put together high-quality recordings of the music for all supported
+  games using the Roland SC-55 synthesizer originally used to compose
+  Doom’s music (thanks twipley and MusicallyInspired).
+
+  Support for joysticks and gamepads has been significantly improved
+  in this version. Most gamepads should now work; if they don’t,
+  please report a bug. A number of gamepads are now automatically
+  detected and configured automatically; if yours is not, you can help
+  by sending in details. See the following page:
+
+  http://www.chocolate-doom.org/wiki/index.php/Adding_your_gamepad
+
+  OPL MIDI playback has been significantly improved, and problems with
+  most tracks should now be resolved. Multi-track MIDI files now play
+  back properly, MIDI tempo meta events are now supported and problems
+  with stuttering when playing certain tracks have been fixed. If you
+  still have problems with OPL playback, let me know.
+
+  Also of note is that Chocolate Doom now has a document that
+  describes the philosophy of the project and the reasoning behind its
+  design (see PHILOSOPHY distributed with the source).
+
+### Other new features
+  * There is now a -dehlump command line parameter to load Dehacked
+    files contained inside WAD files (thanks Fabian Greffrath).
+  * PNG format screenshots are now supported, and there is a dedicated
+    key binding for taking screenshots without needing to always use
+    -devparm (thanks Fabian Greffrath). The PrintScreen key can be
+    used as a key binding (thanks Alexandre-Xavier).
+  * There is now a config file variable (snd_maxslicetime_ms) to
+    control the sound buffer size, and the default is more precise to
+    reduce sound latency (thanks Holering).
+  * You can now use an external command for music playback (thanks
+    Holering).
+  * All games now detect if you’re tring to play using the wrong type
+    of IWAD (doom.wad with Hexen, etc.) and exit with a helpful error
+    message. A couple of users made this mistake after the 2.0 release
+    introduced support for the new games.
+  * The OS X app now associates with .hhe and .seh files.
+  * There is now a -nodes parameter that automatically starts a
+    netgame when a desired number of players have joined the game.
+  * There is now more extensive documentation about music
+    configuration (README.Music).
+  * On Linux, a GUI pop-up is used when the game quits with an error
+    to show the error message (thanks Willy Barro).
+  * There are now Linux .desktop files for all supported games (thanks
+    Mike Swanson).
+  * The -geometry command line parameter can now be used to specify
+    fullscreen or windowed modes, eg. -geometry 640x480w or -geometry
+    1024x768f. (thanks Mike Swanson)
+
+### Doom
+  * Minor workarounds were added to allow the BFG Edition IWADs to be
+    used without crashing the game (thanks Fabian Greffrath).
+  * GUS patch files included with the BFG Edition are now
+    automatically detected.
+  * The “no fog on spawn west” Vanilla bug is now correctly emulated
+    (thanks xttl).
+  * Behavior of older versions of Doom back to v1.666 can now be
+    emulated.
+  * The new Freedoom IWAD names are now recognized and supported.
+  * Freedoom’s DEHACKED lump can now be parsed and is automatically
+    loaded when a Freedoom IWAD file is used (thanks Fabian
+    Greffrath). A new command line parameter, -nodeh, can be used to
+    prevent this from being loaded.
+  * Behavior of the M_EPI4 menu item is now correctly emulated based
+    on game version (thanks Alexandre-Xavier).
+  * IDCLEV up to MAP40 is now supported, to match Vanilla (thanks
+    Alexandre-Xavier).
+  * Level warping on the command line (-warp) to episodes higher than
+    4 is possible, matching Vanilla behavior (thanks plumsinus).
+  * The -cdrom command line parameter writes savegames to the correct
+    directory now, matching Vanilla Doom behavior (thanks
+    Alexandre-Xavier).
+  * The Doom II mission pack to use can now be specified manually on
+    the command line with the -pack parameter (thanks chungy)
+
+### Heretic
+  * Weapon cycling keys for mouse and joystick were fixed (thanks
+    Sander van Dijk).
+  * The -timedemo parameter has been fixed, and -playdemo now handles
+    full paths correctly.
+  * A bug when panning the map was fixed (thanks Chris Fielder).
+  * A savegame bug where plat_t structures were not restored correctly
+    was fixed (thanks romeroyakovlev).
+  * Rebinding of the pause key was fixed (thanks Fabian Greffrath).
+
+### Hexen
+  * Music workarounds have been added so that it is possible to play
+    using the Mac version of the Hexen IWAD file.
+  * Weapon cycling keys for mouse and joystick were fixed (thanks
+    Sander van Dijk).
+  * The -timedemo parameter has been fixed, and -playdemo now handles
+    full paths correctly.
+  * There are now key bindings to allow the artifact keys to be
+    rebound (thanks Fabian Greffrath).
+  * Rebinding of the pause key was fixed (thanks Fabian Greffrath).
+  * Maximum level number was extended to MAP60, allowing multiplayer
+    games using the Deathkings add-on.
+  * The startup screen can now be aborted by pressing escape, like in
+    Vanilla.
+  * Desync when playing back DEMO1 was fixed (thanks alexey.lysiuk).
+
+### Strife
+  * “Show mission” key is configured properly in setup (thanks Sander
+    van Dijk).
+  * Default music volume level now matches Vanilla (thanks
+    Alexandre-Xavier).
+  * Teleport beacon allegiance was fixed to match Vanilla (thanks
+    Quasar).
+  * The stair building code now more closely matches Vanilla (thanks
+    Quasar).
+  * Torpedo weapon changing behavior now matches Vanilla (thanks
+    Quasar).
+
+### Cleanups
+  * The copyright headers at the top of all source files have been
+    vastly simplified.
+  * Unsafe string functions have been eliminated from the codebase.
+    Thanks to Theo de Raadt for calling out Chocolate Doom by name
+    (alongside many other packages) for still using unsafe functions
+    like strcpy: http://marc.info/?l=openbsd-tech&m=138733933417096
+  * vldoor_e enum values are now namespaced to avoid potential
+    conflicts with POSIX standard functions.
+
+### Bug fixes
+  * WAD and Dehacked checksums are now sent to clients and checked
+    correctly when setting up netgames.
+  * A bug was fixed that caused sound not to work in multiplayer games
+    (thanks to everyone who reported this, and for Alexandre-Xavier
+    and Quasar for help in fixing it).
+  * The “D_DDTBLU disease” bug affecting certain MIDI files has been
+    fixed (thanks plumsinus, Brad Harding and Quasar).
+  * Calculation of the -devparm “ticker” dots was fixed to match
+    Vanilla behavior (thanks _bruce_ and Alexandre-Xavier).
+  * The PC speaker code now supports the full range of sound
+    frequencies (thanks Gez).
+  * Annoying “jumping” behavior when grabbing the mouse cursor was
+    fixed.
+  * The screen is now initialized at the native bit depth by default,
+    to avoid problems with systems that don’t handle 8-bit
+    screenbuffers very well any more.
+  * The --docdir argument to the configure script is now honored
+    (thanks Jan Engelhardt).
+  * Various issues with the build were fixed (thanks Jan Engelhardt
+    and Fabian Greffrath).
+  * Backwards parameters were fixed in the sound code (thanks
+    proteal).
+  * A crash was fixed when running fullscreen with the -2 parameter
+    (thanks Fabian Greffrath).
+  * A crash when using large values of snd_channels was fixed (thanks
+    Alexandre-Xavier).
+  * A resource leak in the BSD PC speaker code was fixed (thanks
+    Edward-san).
+  * Windows resource files were fixed for Windows 7 (thanks Brad
+    Harding).
+  * A hard to trigger crash caused by a realloc() in the WAD code was
+    fixed (thanks Fabian Greffrath for debugging).
+  * A bug has been fixed where Chocolate Doom would stay running in
+    the background on Windows after quitting. SDL_Quit() is called now
+    (thanks johnsirett, Brad Harding, Quasar).
+  * String replacements in dehacked lumps can now be overridden if a
+    subsequent dehacked patch replaces the same string.
+
+### libtextscreen
+  * Clicking on scrollbars now jumps to the correct position (thanks
+    Alexandre-Xavier).
+  * A use-after-free bug has been fixed where a click in a window that
+    causes the window to close could lead to a crash (thanks DuClare).
+  * Characters that are unprintable in the Extended ASCII chart are
+    just ignored when they’re typed, rather than appearing as an
+    upside-down question mark (thanks Alexandre-Xavier).
+
+## 2.0.0 (2013-12-09)
+
+  This is version 2.0 of Chocolate Doom! This new major version is
+  released to celeberate the 20th anniversary of the first release of
+  Doom in 1993. Happy Birthday Doom!
+
+  This new version has some major changes compared to the 1.0 series:
+
+  * The codebase now includes Chocolate Heretic and Chocolate
+    Hexen. These are based on the GPL source code released by Raven
+    Software.
+  * Also included is Chocolate Strife. This was developed through a
+    mammoth four year reverse engineering project conducted by James
+    “Quasar” Haley and Samuel “Kaiser” Villareal. The result is the
+    most accurate reproduction of Strife to date, including full demo
+    and savegame compatibility. See README.Strife for more
+    information.
+
+### Minor features that are nonetheless worth mentioning
+  * Chocolate Doom now includes a -statdump command line option, which
+    emulates the output of the statdump.exe tool. This is used to
+    implement a form of regression testing (statcheck) that directly
+    compares against the Vanilla behavior.
+  * Chocolate Heretic includes HHE patch file support, and I believe
+    is the first Heretic port to include this feature.
+  * GUS “pseudo-emulation” is now supported. This does not fully
+    emulate a GUS, but Doom’s DMXGUS lump can be used to generate a
+    Timidity configuration file that plays music using the GUS patch
+    set.
+  * The setup tool now includes a built-in server browser, for use
+    when selecting a server to join.
+
+  Version 2.0 of Chocolate Doom has been in development for a long
+  time, and there have been many bugs fixed over this time, too many
+  to list here. Thanks to all the people who have tested it and
+  diligently reported bugs over this time, and to all the people who
+  have tested the beta releases over the past couple of months.  Your
+  contributions have been essential and invaluable.
+
+## 1.7.0 (2012-06-09)
+
+  * Fixed gnome-screensaver desktop file (thanks Rahul Sundaram).
+  * Updated COPYING to current version of GPL2 (thanks Rahul
+    Sundaram).
+  * Running servers now re-resolve the address of the master server
+    occasionally, to adapt to DNS address changes.
+  * Error dialog is no longer shown on OS X when running from the
+    console.
+  * The Makefiles no longer use GNU make extensions, so the package
+    builds on OpenBSD.
+  * There is now an OPL MIDI debug option (-opldev), useful for when
+    developing GENMIDI lumps.
+  * A workaround for SDL mouse lag is now only used on Windows (where
+    it is needed), and not on other systems. This fixes Chocolate Doom
+    on AmigaOS (thanks Timo Sievänen).
+  * UTF-8 usernames are supported, and Windows usernames with
+    non-ASCII characters are now supported (thanks Alexandre Xavier).
+
+### Compatibility
+  * Palette accuracy is reduced to 6 bits per channel, to more
+    accurately emulate the PC VGA hardware (thanks GhostlyDeath).
+  * Fixed teleport behavior when emulating the alternate Final Doom
+    executable (-gameversion final2) (thanks xttl).
+
+### Bugs fixed
+  * Fixed weapon cycling keys when playing in Shareware Doom and using
+    the IDKFA cheat (thanks Alexandre Xavier).
+  * Fixed the default mouse buttons in the setup tool (thanks
+    Alexandre Xavier).
+  * Chat macros now work when vanilla_keyboard_mapping is turned off.
+  * Default chat macros were fixed in the setup tool.
+  * Ping time calculation was fixed for LAN search, and made more
+    accurate for all searches.
+  * Fixed bug with detection of IWAD type by filename (thanks mether).
+
+### libtextscreen
+  * There is now limited UTF-8 text support in the textscreen library,
+    used in the label and input box widgets.
+  * Scroll bar behavior was fixed (thanks Alexandre Xavier).
+  * Input boxes stop editing and save when they lose their focus,
+    correcting a previous counterintuitive behavior (thanks Twelve).
+  * The numeric keypad now works properly when entering text values
+    (thanks Twelve).
+
+## 1.6.0 (2011-05-17)
+
+  * The instructions in the INSTALL file are now customized for
+    different platforms, and each binary package contains a version
+    with instructions specific to the platform that it is targetting.
+    This should help to avoid confusion that some users have reported
+    experiencing.
+  * The display settings window in the setup tool has been reorganised
+    to a better arrangement.
+  * It is now possible to load .lmp files (and play back demos) with
+    long filenames (thanks blzut3).
+  * In the setup tool, it is now possible to hold down shift when
+    changing key/mouse/joystick bindings to prevent other bindings to
+    the same key from being cleared (thanks myk).
+  * The joystick menu in the setup tool now has a test button (thanks
+    Alexandre Xavier).
+  * Specifying the -privateserver option implies -server (thanks
+    Porsche Monty).
+  * The Mac OS X .dmg package now has a background and looks generally
+    more polished.
+  * In Mac OS X, it is now possible to simply double click an IWAD
+    file in the Finder to configure its location within the launcher.
+  * Freedesktop.org desktop files are now installed for Doom and the
+    setup tool, which will appear in the main menu on desktop
+    environments such as Gnome and KDE (thanks Adrián Chaves
+    Fernández).
+  * The Chex Quest dehacked patch (chex.deh) will now be detected if
+    it is in the same directory as the IWAD file.
+
+### Compatibility
+  * Added support for the alternate version of the Final Doom
+    executable included in some later versions of the Id Anthology.
+    This version fixed the demo loop crash that occurred with the
+    “original” Final Doom executable.
+
+    This executable can be selected on the command line with
+    -gameversion final2. It has been made the default when playing
+    with the Final Doom IWADs (the original behavior can be selected
+    with -gameversion final).  (thanks Porsche Monty, Enjay).
+  * Very short sound effects are not played, to better emulate the
+    behavior of DMX in Vanilla Doom (thanks to Quasar for help in
+    investigating this).
+  * The null sector dereference emulation code has been imported from
+    Prboom+ - this fixes a desync with CLNJ-506.LMP (thanks entryway).
+  * The IDMUS cheat doesn’t work when emulating the v1.9 executable
+    (thanks Alexandre Xavier).
+
+### Bugs fixed
+  * Menu navigation when using joystick/joypad (thanks Alexandre
+    Xavier).
+  * For configuration file value for shift keys, use scan code for
+    right shift, not left shift (thanks Alexandre Xavier).
+  * Default joystick buttons for the setup tool now match Vanilla
+    (thanks twipley).
+  * Visual Studio project files work again (thanks GhostlyDeath).
+  * The default sfx/music volume set by the setup tool is now 8
+    instead of 15, matching the game itself. (thanks Alexandre
+    Xavier).
+  * Weapon cycling from the shotgun to the chaingun in Doom 1 now
+    works properly (thanks Alexandre Xavier).
+  * MIDI playback that locked up when using an empty MUS / MIDI file
+    (thanks Alexandre Xavier).
+  * Default sampling rate used by setup tool changed to 44100Hz, to
+    match the game default (thanks Alexandre Xavier).
+  * Cheat codes and menu hot keys now work when shift is held down or
+    capslock turned on (thanks Alexandre Xavier).
+
+### libtextscreen
+  * The background on GUI controls now lights up when hovering over
+    them, so that it is more obvious what you are selecting.
+  * It is now possible to type a “+” in input boxes (thanks Alexandre
+    Xavier).
+  * It is possible to use the mouse wheel to scroll through scroll
+    panes.
+  * Clicking on scroll bars now moves the scroll handle to a matching
+    location.
+  * Clicking outside a dropdown list popup window now dismisses the
+    window.
+  * Window hotkeys that are an alphabetical letter now work when shift
+    is held down or capslock turned on (thanks Alexandre Xavier).
+
+## 1.5.0 (2011-01-02)
+
+  Big changes in this version:
+
+  * The DOSbox OPL emulator (DBOPL) has been imported to replace the
+    older FMOPL code.  The quality of OPL emulation is now therefore
+    much better.
+  * The game can now run in screen modes at any color depth (not just
+    8-bit modes).  This is mainly to work around problems with Windows
+    Vista/7, where 8-bit color modes don’t always work properly.
+  * Multiplayer servers now register themselves with an Internet
+    master server.  Use the -search command line parameter to find
+    servers on the Internet to play on.  You can also use DoomSeeker
+    (http://skulltag.net/doomseeker/) which supports this
+    functionality.
+  * When running in windowed mode, it is now possible to dynamically
+    resize the window by dragging the window borders.
+  * Names can be specified for servers with the -servername command
+    line parameter.
+  * There are now keyboard, mouse and joystick bindings to cycle
+    through available weapons, making play with joypads or mobile
+    devices (ie. without a proper keyboard) much more practical.
+  * There is now a key binding to change the multiplayer spy key
+    (usually F12).
+  * The setup tool now has a “warp” button on the main menu, like
+    Vanilla setup.exe (thanks Proteh).
+  * Up to 8 mouse buttons are now supported (including the
+    mousewheel).
+  * A new command line parameter has been added (-solo-net) which can
+    be used to simulate being in a single player netgame.
+  * There is now a configuration file parameter to set the OPL I/O
+    port, for cards that don’t use port 0x388.
+  * The Python scripts used for building Chocolate Doom now work with
+    Python 3 (but also continue to work with Python 2) (thanks arin).
+  * There is now a NOT-BUGS file included that lists some common
+    Vanilla Doom bugs/limitations that you might encounter (thanks to
+    Sander van Dijk for feedback).
+
+### Compatibility
+  * The -timer and -avg options now work the same as Vanilla when
+    playing back demos (thanks xttl)
+  * A texture lookup bug was fixed that caused the wrong sky to be
+    displayed in Spooky01.wad (thanks Porsche Monty).
+  * The HacX v1.2 IWAD file is now supported, and can be used
+    standalone without the need for the Doom II IWAD (thanks atyth).
+  * The I_Error function doesn’t display “Error:” before the error
+    message, matching the Vanilla behavior.  “Error” has also been
+    removed from the title of the dialog box that appears on Windows
+    when this happens.  This is desirable as not all such messages are
+    actually errors (thanks Proteh).
+  * The setup tool now passes through all command line arguments when
+    launching the game (thanks AlexXav).
+  * Demo loop behavior (ie. whether to play DEMO4) now depends on the
+    version being emulated.  When playing Final Doom the game will
+    exit unexpectedly as it tries to play the fourth demo - this is
+    Vanilla behaviour (thanks AlexXav).
+
+### Bugs fixed
+  * A workaround has been a bug in old versions of SDL_mixer (v1.2.8
+    and earlier) that could cause the game to lock up.  Please upgrade
+    to a newer version if you haven’t already.
+  * It is now possible to use OPL emulation at 11025Hz sound sampling
+    rate, due to the new OPL emulator (thanks Porsche Monty).
+  * The span renderer function (used for drawing floors and ceilings)
+    now behaves the same as Vanilla Doom, so screenshots are
+    pixel-perfect identical to Vanilla Doom (thanks Porsche Monty).
+  * The zone memory system now aligns allocated memory to 8-byte
+    boundaries on 64-bit systems, which may fix crashes on systems
+    such as sparc64 (thanks Ryan Freeman and Edd Barrett).
+  * The configure script now checks for libm, fixing compile problems
+    on Fedora Linux (thanks Sander van Dijk).
+  * Sound distortion with certain music files when played back using
+    OPL (eg. Heretic title screen).
+  * Error in Windows when reading response files (thanks Porsche
+    Monty, xttl, Janizdreg).
+  * Windows Vista/7 8-bit color mode issues (the default is now to run
+    in 32-bit color depth on these versions) (thanks to everybody who
+    reported this and helped test the fix).
+  * Screen borders no longer flash when running on widescreen
+    monitors, if you choose a true-color screen mode (thanks exp(x)).
+  * The controller player in a netgame is the first player to join,
+    instead of just being someone who gets lucky.
+  * Command line arguments that take an option now check that an
+    option is provided (thanks Sander van Dijk).
+  * Skill level names in the setup tool are now written the same as
+    they are on the in-game “new game” menu (thanks AlexXav).
+  * There is no longer a limit on the lengths of filenames provided to
+    the -record command line parameter (thanks AlexXav).
+  * Window title is not lost in setup tool when changing video driver
+    (thanks AlexXav).
+
+### libtextscreen
+  * The font used for the textscreen library can be forced by setting
+    the TEXTSCREEN_FONT environment variable to “small” or “normal”.
+  * Tables or scroll panes that don’t contain any selectable widgets
+    are now themselves not selectable (thanks Proteh).
+  * The actions displayed at the bottom of windows are now laid out in
+    a more aesthetically pleasing way.
+
+## 1.4.0 (2010-07-10)
+
+  The biggest change in this version is the addition of OPL emulation.
+  This emulates Vanilla Doom’s MIDI playback when using a Yamaha OPL
+  synthesizer chip, as was found on SoundBlaster compatible cards.
+
+  A software OPL emulator is included as most modern computers do not
+  have a hardware OPL chip any more.  If you do have one, you can
+  configure Chocolate Doom to use it; see README.OPL.
+
+  The OPL playback feature is not yet perfect or 100% complete, but is
+  judged to be good enough for general use.  If you find music that
+  does not play back properly, please report it as a bug.
+
+### Other changes
+  * The REJECT overflow emulation code from PrBoom+ has been
+    imported.  This fixes demo desync on some demos, although
+    others will still desync.
+  * Warnings are now generated for invalid dehacked replacements of
+    printf format strings.  Some potential buffer overflows are also
+    checked.
+  * The installation instructions (INSTALL file) have been clarified
+    and made more platform-agnostic.
+  * The mouse is no longer warped to the center of the screen when the
+    demo sequence advances.
+  * Key bindings can now be changed for the demo recording quit key
+    (normally ‘q’) and the multiplayer messaging keys (normally ‘t’,
+    ‘g’, ‘i’, ‘b’ and ‘r’).
+
+## 1.3.0 (2010-02-10)
+
+  * Chocolate Doom now runs on Windows Mobile/Windows CE!
+  * It is possible to rebind most/all of the keys that control the
+    menu, shortcuts, automap and weapon switching.  The main reason
+    for this is to support the Windows CE port and other platforms
+    where a full keyboard may not be present.
+  * Chocolate Doom now includes a proper Mac OS X package; it is no
+    longer necessary to compile binaries for this system by hand.  The
+    package includes a simple graphical launcher program and can be
+    installed simply by dragging the “Chocolate Doom” icon to the
+    Applications folder. (thanks to Rikard Lang for extensive testing
+    and feedback)
+  * The video mode auto-adjust code will automatically choose windowed
+    mode if no fullscreen video modes are available.
+  * The zone memory size is automatically reduced on systems with a
+    small amount of memory.
+  * The “join game” window in the setup tool now has an option to
+    automatically join a game on the local network.
+  * Chocolate Doom includes some initial hacks for compiling under
+    SDL 1.3.
+  * Recent versions of SDL_mixer include rewritten MIDI code on Mac OS
+    X.  If you are using a version of SDL_mixer with the new code,
+    music will now be enabled by default.
+  * Windows Vista and Windows 7 no longer prompt for elevated
+    privileges when running the setup tool (thanks hobbs and MikeRS).
+  * The Windows binaries now have better looking icons (thanks
+    MikeRS).
+  * Magic values specified using the -spechit command line parameter
+    can now be hexadecimal.
+  * DOOMWADDIR/DOOMWADPATH can now specify the complete path to IWAD
+    files, rather than the path to the directory that contains them.
+  * When recording shorttics demos, errors caused by the reduced
+    turning resolution are carried forward, possibly making turning
+    smoother.
+  * The source tarball can now be used to build an RPM package:
+    rpmbuild -tb chocolate-doom-VER.tar.gz
+
+### Compatibility
+  * The A_BossDeath behavior in v1.9 emulation mode was fixed (thanks
+    entryway)
+  * The “loading” disk icon is drawn more like how it is drawn in
+    Vanilla Doom, also fixing a bug with chook3.wad.
+  * Desync on 64-bit systems with ep1-0500.lmp has (at long last) been
+    fixed (thanks exp(x)).
+  * Donut overrun emulation code imported from Prboom+ (thanks
+    entryway).
+  * The correct level name should now be shown in the automap for
+    pl2.wad MAP33 (thanks Janizdreg).
+  * In Chex Quest, the green radiation suit colormap is now used
+    instead of the red colormaps normally used when taking damage or
+    using the berserk pack.  This matches Vanilla chex.exe behavior
+    (thanks Fuzztooth).
+  * Impassible glass now displays and works the same as in Vanilla,
+    fixing wads such as OTTAWAU.WAD (thanks Never_Again).
+
+### Bugs fixed
+  * Memory-mapped WAD I/O is disabled by default, as it caused various
+    issues, including a slowdown/crash with Plutonia 2 MAP23.  It can
+    be explicitly re-enabled using the “-mmap” command line parameter.
+  * Crash when saving games due to the ~/.chocolate-doom/savegames
+    directory not being created (thanks to everyone who reported
+    this).
+  * Chocolate Doom will now run under Win95/98, as the
+    SetProcessAffinityMask function is looked up dynamically.
+  * Compilation under Linux with older versions of libc will now work
+    (the semantics for sched_setaffinity were different in older
+    versions)
+  * Sound clipping when using libsamplerate was improved (thanks David
+    Flater)
+  * The audio buffer size is now calculated based on the sample rate,
+    so there is not a noticeable delay when using a lower sample rate.
+  * The manpage documentation for the DOOMWADPATH variable was fixed
+    (thanks MikeRS).
+  * Compilation with FEATURE_MULTIPLAYER and FEATURE_SOUND disabled
+    was fixed.
+  * Fixed crash when using the donut special type and the joining
+    linedef is one sided (thanks Alexander Waldmann).
+  * Key settings in a configuration file that are out of range do not
+    cause a crash (thanks entryway).
+  * Fix ear-piercing whistle when playing the MAP05 MIDI music using
+    timidity with EAWPATS (thanks entryway / HackNeyed).
+
+### libtextscreen
+  * There is now a second, small textscreen font, so that the ENDOOM
+    screen and setup tool can be used on low resolution devices
+    (eg. PDAs/embedded devices)
+  * The textscreen library now has a scrollable pane widget. Thanks to
+    LionsPhil for contributing code to scroll up and down using the
+    keyboard.
+  * Doxygen documentation was added for the textscreen library.
+
+## 1.2.1 (2008-12-10)
+
+  This version just fixes a crash at the intermission screen when
+  playing Doom 1 levels.
+
+## 1.2.0 (2008-12-10)
+
+  Happy 15th Birthday, Doom!
+
+  * Chocolate Doom now has an icon that is not based on the
+    proprietary Doom artwork.
+  * There is now memory-mapped WAD I/O support, which should be useful
+    on some embedded systems.
+  * Chex quest emulation support is now included, although an
+    auxiliary dehacked patch is needed (chexdeh.zip in the idgames
+    archive).
+
+### Compatibility
+  * The armor class is always set to 2 when picking up a megasphere
+    (thanks entryway).
+  * The quit screen prompts to quit “to dos” instead of just to quit
+    (thanks MikeRS)
+  * The “dimensional shambler” quit message was fixed.
+  * Fix crash related to A_BFGSpray with NULL target when using
+    dehacked patches - discovered with insaned2.deh (thanks CSonicGo)
+  * NUL characters are stripped from dehacked files, to ensure correct
+    behavior with some dehacked patches (eg. the one with portal.wad).
+
+### Bugs fixed
+  * “Python Image Library” should have been “Python Imaging Library”
+    (thanks exp(x)).
+  * The setup tool should no longer ask for elevated permissions on
+    Windows Vista (this fix possibly may not work).
+  * The application icon is set properly when running under Windows XP
+    with the “Luna” theme.
+  * Fix compilation under Cygwin to detect libraries and headers from
+    the correct environment.
+  * The video code does not try to read SDL events before SDL has been
+    properly initialised - this was causing problems with some older
+    versions of SDL.
+
+## 1.1.1 (2008-04-20)
+
+  The previous release (v1.1.0) included a bug that broke compilation
+  when libsamplerate support was enabled.  The only change in this
+  version is to fix this bug.
+
+## 1.1.0 (2008-04-19)
+
+  * The video mode code has been radically restructured.  The video
+    mode is now chosen by directly specifying the mode to use; the
+    scale factor is then chosen to fit the screen.  This is helpful
+    when using widescreen monitors (thanks Linguica)
+  * MSVC build project files (thanks GhostlyDeath and entryway).
+  * Unix manpage improvements; the manpage now lists the environment
+    variables that Chocolate Doom uses.  Manpages have been added for
+    chocolate-setup and chocolate-server, from the versions for the
+    Debian Chocolate Doom package (thanks Jon Dowland).
+  * INSTALL file with installation instructions for installing
+    Chocolate Doom on Unix systems.
+  * Support for high quality resampling of sound effects using
+    libsamplerate (thanks David Flater).
+  * A low pass filter is applied when doing sound resampling in an
+    attempt to filter out high frequency noise from the resampling
+    process.
+  * R_Main progress box is not displayed if stdout is a file (produces
+    cleaner output).
+  * Client/server version checking can be disabled to allow different
+    versions of Chocolate Doom to play together, or Chocolate Doom
+    clients to play with Strawberry Doom clients.
+  * Unix manpages are now generated for the Chocolate Doom
+    configuration files.
+  * The BSD PC speaker driver now works on FreeBSD.
+
+### Compatibility
+  * Use the same spechits compatibility value as PrBoom+, for
+    consistency (thanks Lemonzest).
+  * The intercepts overrun code has been refactored to work on big
+    endian machines.
+  * The default startup delay has been set to one second, to allow
+    time for the screen to settle before starting the game (some
+    monitors have a delay before they come back on after changing
+    modes).
+  * If a savegame buffer overrun occurs, the savegame does not get
+    saved and existing savegames are not overwritten (same behaviour
+    as Vanilla).
+
+### Bugs fixed
+  * Desync with STRAIN demos and dehacked Misc values not being set
+    properly (thanks Lemonzest)
+  * Don’t grab the mouse if the mouse is disabled via -nomouse or
+    use_mouse in the configuration file (thanks MikeRS).
+  * Don’t center the mouse on startup if the mouse is disabled (thanks
+    Siggi).
+  * Reset the palette when the window is restored to clear any screen
+    corruption (thanks Catoptromancy).
+  * mus2mid.c should use `MEM_SEEK_SET`, not `SEEK_SET` (thanks
+    Russell)
+  * Fast/Respawn options were not being exchanged when starting
+    netgames (thanks GhostlyDeath).
+  * Letterbox mode is more accurately described as “pillarboxed” or
+    “windowboxed” where appropriate (thanks MikeRS)
+  * Process affinity mask is set to 1 on Windows, to work around a bug
+    in SDL_mixer that can cause crashes on multi-core machines (thanks
+    entryway).
+  * Bugs in the joystick configuration dialog in the setup tool have
+    been fixed.
+
+## 1.0.0 (2007-12-10)
+
+  This release is dedicated to Dylan “Toke” McIntosh, who was
+  tragically killed in a car crash in 2006.  I knew Dylan from IRC and
+  the Doomworld forums for several years, and he had a deep passion
+  for this game.  He was also a huge help for me while developing
+  Chocolate Doom, as he helped point out a lot of small quirks in
+  Vanilla Doom that I didn’t know about. His death is a great loss.
+  RIP Toke.
+
+  This is the first release to reach full feature parity with Vanilla
+  Doom.  As a result, I have made this version 1.0.0, so Chocolate
+  Doom is no longer beta!
+
+### Big new features
+  * Multiplayer!  This version includes an entirely new multiplayer
+    engine, based on a packet server architecture.  I’d like to thank
+    joe, pritch, Meph and myk, and everyone else who has helped test
+    the new code for their support, feedback and help in testing this.
+    The new code still needs more testing, and I’m eager to hear any
+    feedback on this.
+  * A working setup tool.  This has the same look and feel as the
+    original setup.exe.  I hope people like it!  Note that it has some
+    advantages over the original setup.exe - for example, you can use
+    the mouse.
+
+### Other new features
+  * New mus conversion code thanks to Ben Ryves.  This converts the
+    Doom .mus format to .mid a lot better.  As one example, tnt.wad
+    Map02 is now a lot closer to how Vanilla says.  Also, the music on
+    the deca.wad titlescreen now plays!
+  * x3, x4 and x5 display scale (thanks to MikeRS for x5 scale).
+  * Fullscreen “letterbox” mode allows Chocolate Doom to run on
+    machines where 1.6:1 aspect ratio modes are unavailable
+    (320x200/640x400).  The game runs in 320x240/640x480 instead, with
+    black borders.  The system automatically adjusts to this if closer
+    modes are unavailable.
+  * Aspect ratio correction: you can (also) run at 640x480 without
+    black borders at the top and bottom of the screen.
+  * PC speaker sound effect support.  Chocolate Doom can output real
+    PC speaker sounds on Linux, or emulate a PC speaker through the
+    sound card.
+  * Working three-screen mode, as seen in early versions of Doom!  To
+    test this out, put three computers on a LAN and type:
+
+        chocolate-doom -server
+        chocolate-doom -autojoin -left
+        chocolate-doom -autojoin -right
+
+  * Allow a delay to be specified on startup, to allow the display to
+    settle after changing modes before starting the game.
+  * Allow the full path and filename to be specified when loading
+    demos: It is now possible to type “chocolate-doom -playdemo
+    /tmp/foo.lmp” for example.
+  * Savegames are now stored in separate directories depending on the
+    IWAD: eg. the savegames for Doom II are stored in a different
+    place to those for Doom I, Final Doom, etc. (this does not affect
+    Windows).
+  * New mouse acceleration code works based on a threshold and
+    acceleration.  Hopefully this should be closer to what the DOS
+    drivers do.  There is a ‘test’ feature in the setup tool to help
+    in configuring this.
+  * New “-nwtmerge” command line option that emulates NWT’s “-merge”
+    option.  This allows TiC’s Obituary TC to be played.
+  * The ENDOOM screen no longer closes automatically, you have to
+    click the window to make it go away.
+  * Spechit overrun fixes and improvements.  Thanks to entryway for
+    his continued research on this topic (and because I stole your
+    improvements :-).  Thanks to Quasar for reporting a bug as well.
+  * Multiple dehacked patches can be specified on the command line, in
+    the same way as with WADs - eg. -deh foo.deh bar.deh baz.deh.
+  * Default zone memory size increased to 16MB; this can be controlled
+    using the -mb command-line option.
+  * It is now possible to record demos of unlimited length (by
+    default, the Vanilla limit still applies, but it can now be
+    disabled).
+  * Autoadjusting the screen mode can now be disabled.
+  * On Windows, the registry is queried to detect installed versions
+    of Doom and automatically locate IWAD files.  IWADs installed
+    through Steam are also autodetected.
+  * Added DOOMWADPATH that can be used like PATH to specify multiple
+    locations in which to search for IWAD files.  Also, “-iwad” is now
+    enhanced, so that eg. “-iwad doom.wad” will now search all IWAD
+    search paths for “doom.wad”.
+  * Improved mouse tracking that should no longer lag.  Thanks to
+    entryway for research into this.
+  * The SDL driver can now be specified in the configuration file.
+    The setup tool has an option on Windows to select between DirectX
+    and windib.
+  * Joystick support.
+  * Configuration file option to change the sound sample rate.
+  * More than three mouse buttons are now supported.
+
+### Portability improvements
+  * Chocolate Doom now compiles and runs cleanly on MacOS X.  Huge
+    thanks go to Insomniak who kindly gave me an account on his
+    machine so that I could debug this remotely.  Big thanks also go
+    to athanatos on the Doomworld forums for his patience in testing
+    various ideas as I tried to get Chocolate Doom up and running on
+    MacOS.
+  * Chocolate Doom now compiles and runs natively on AMD64.
+  * Chocolate Doom now compiles and runs on Solaris/SPARC, including
+    the Sun compiler.  Thanks to Mike Spooner for some portability
+    fixes.
+  * Improved audio rate conversion, so that sound should play properly
+    on machines that don’t support low bitrate output.
+
+### Compatibility fixes
+  * Check for IWADs in the same order as Vanilla Doom.
+  * Dehacked code will now not allow string replacements to be longer
+    than those possible through DOS dehacked.
+  * Fix sound effects playing too loud on level 8 (thanks to myk for
+    his continued persistence in getting me to fix this)
+  * Save demos when quitting normally - it is no longer necessary to
+    press ‘q’ to quit and save a demo.
+  * Fix spacing of -devparm mode dots.
+  * Fix sky behavior to be the same as Vanilla Doom - when playing in
+    Doom II, the skies never change from the sky on the first level
+    unless the player loads from a savegame.
+  * Make -nomouse and config file use_mouse work again.
+  * Fix the -nomusic command-line parameter.  Make the snd_sfxdevice
+    snd_musicdevice values in the configuration file work, so that it
+    is possible to disable sound, as with Vanilla.
+  * Repeat key presses when the key is held down (this is the Vanilla
+    behavior) - thanks to Mad_Mac for pointing this out.
+  * Don’t print a list of all arguments read from response files -
+    Vanilla doesn’t do this.
+  * Autorun only when joyb_speed >= 10, not >= 4.  Thanks to Janizdreg
+    for this.
+  * Emulate a bug in DOS dehacked that can overflow the dehacked frame
+    table and corrupt the weaponinfo table.  Note that this means
+    Batman Doom will no longer play properly (identical behavior to
+    Vanilla); vbatman.deh needs to also be applied to fix it.  (Thanks
+    grazza)
+  * Allow dehacked 2.3 patches to be loaded.
+  * Add more dehacked string replacements.
+  * Compatibility option to enable or disable native key mappings.
+    This means that people with non-US keyboards can decide whether to
+    use their correct native mapping or behave like Vanilla mapping
+    (which assumes all keyboards are US).
+  * Emulate overflow bug in P_FindNextHighestFloor.  Thanks to
+    entryway for the fix for this.
+  * Add -netdemo command line parameter, for playing back netgame
+    demos recorded with a single player.
+  * The numeric keypad now behaves like Vanilla Doom does.
+  * Fix some crashes when loading from savegames.
+  * Add intercepts overrun emulation from PrBoom-plus.  Thanks again
+    to entryway for his research on this subject.
+  * Add playeringame overrun emulation.
+
+### Bugs fixed
+  * Fix crash when starting new levels due to the intermission screen
+    being drawn after the WI_ subsystem is shut down (thanks pritch
+    and joe)
+  * Catch failures to initialise sound properly, and fail gracefully.
+  * Fix crasher in 1427uv01.lmp (thanks ultdoomer)
+  * Fix crash in udm1.wad.
+  * Fix crash when loading a savegame with revenant tracer missiles.
+  * Fix crash when loading a savegame when a mancubus was in the
+    middle of firing.
+  * Fix Doom 1 E1-3 intermission screen animations.
+  * Fix loading of dehacked “sound” sections.
+  * Make sure that modified copyright banners always end in a newline
+    - this fixes a bug with av.wad (thanks myk)
+  * Added missing quit message (“are you sure you want to quit this
+    great game?”).
+  * Fix when playing long sound effects - the death sound in
+    marina.wad now plays properly, for example.
+  * Fix buffer overrun on the quicksave prompt screen that caused a
+    mysterious cycling character to appear.
+  * IDCLEV should not work in net games (thanks Janizdreg)
+  * Stop music playing at the ENDOOM screen.
+  * Fix sound sample rate conversion crash.
+  * Fix “pop” heard at the end of sound effects.
+  * Fix crash when playing long sounds.
+  * Fix bug with -timedemo accuracy over multi-level demos.
+  * Fix bug with the automap always following player 1 in multiplayer
+    mode (thanks Janizdreg).
+
+## 0.1.4 (2006-02-13)
+
+  * NWT-style merging command line options (allows Mordeth to be played)
+  * Unix manpage (thanks Jon Dowland)
+  * Dehacked improvements/fixes:
+     * Allow changing the names of graphic lumps used in menu, status bar
+       intermission screen, etc.
+     * Allow changing skies, animated flats + textures
+     * Allow changing more startup strings.
+     * Allow text replacements on music + sfx lump names
+  * Fix for plutonia map12 crash.
+  * Fix bug with playing long sfx at odd sample rates.
+  * Big Endian fixes (for MacOS X).  Thanks to athanatos for helping
+    find some of these.
+  * Install into /usr/games, rather than /usr/bin (thanks Jon Dowland)
+
+## 0.1.3 (2006-01-20)
+
+  * Imported the spechit overrun emulation code from prboom-plus.  Thanks to
+    Andrey Budko for this.
+  * New show_endoom option in the chocolate-doom.cfg config file allows
+    the ENDOOM screen to be disabled.
+  * Chocolate Doom is now savegame-compatible with Vanilla Doom.
+
+  * Fixes for big endian machines (thanks locust)
+  * Fixed the behavior of the dehacked maximum health setting.
+  * Fix the “-skill 0” hack to play without any items (thanks to Janizdreg
+    for pointing out that this was nonfunctional)
+  * Fix playing of sounds at odd sample rates (again).  Sound effects
+    at any sample rate now play, but only sounds with valid headers.
+    This is the *real* way Vanilla Doom behaves.  Thanks to myk for
+    pointing out the incorrect behavior.
+
+## 0.1.2 (2005-10-29)
+
+  * Silence sounds at odd sample rates (rather than bombing out); this
+    is the way Vanilla Doom behaves.
+  * Handle multiple replacements of the same sprite in a PWAD.
+  * Support specifying a specific version to emulate via the command line
+    (-gameversion)
+  * Fix help screen orderings and skull positions.  Behave exactly as
+    the original executables do.
+
+## 0.1.1 (2005-10-18)
+
+  * Display startup “banners” if they have been modified through
+    dehacked.
+  * Dehacked “Misc” section support.
+
+### Bugs fixed
+  * Doom 1 skies always using Episode 1 sky
+  * Crash when switching applications while running fullscreen
+  * Lost soul bounce logic (do not bounce in Registered/Shareware)
+  * Mouse buttons mapped incorrectly (button 1 is right, 2 is middle)
+  * Music not pausing when game is paused, when using SDL_mixer’s
+    native MIDI playback.
+  * Pink icon on startup (palette should be fully set before anything is
+    loaded)
+
+## 0.1.0 (2005-10-09)
+
+  * Dehacked support
+  * WAD merging for TCs
+  * ENDOOM display
+  * Fix bug with invalid MUS files causing crashes
+  * Final Doom fixes
+
+## 0.0.4 (2005-09-27)
+
+  * Application icon and version info included in Windows .exe files
+  * Fixes for non-x86 architectures
+  * Fix uac_dead.wad (platform drop on e1m8 should occur when all
+    bosses die, not just barons)
+  * Fix “loading” icon to work for all graphics modes
+
+## 0.0.3 (2005-09-17)
+
+  * Mouse acceleration code to emulate the behaviour of old DOS mouse
+    drivers (thanks to Toke for information about this and
+    suggestions)
+  * Lock surfaces properly when we have to (fixes crash under
+    Windows 98)
+
+## 0.0.2 (2005-09-13)
+
+  * Remove temporary MIDI files generated by sound code.
+  * Fix sound not playing at the right volume
+  * Allow alt-tab away while running in fullscreen under Windows
+  * Add second configuration file (chocolate-doom.cfg) to allow
+    chocolate-doom specific settings.
+  * Fix switches not changing in Ultimate Doom
+
+## 0.0.1 (2005-09-07)
+
+  First beta release
diff --git a/NOT-BUGS b/NOT-BUGS.md
similarity index 65%
rename from NOT-BUGS
rename to NOT-BUGS.md
index 5d91c5b..1ef97e3 100644
--- a/NOT-BUGS
+++ b/NOT-BUGS.md
@@ -1,37 +1,32 @@
-
 The aim of Chocolate Doom is to behave as closely to Vanilla Doom as
 possible.  As a result, you may experience problems that you would
-also experience when using Vanilla Doom.  These are not "bugs" as
+also experience when using Vanilla Doom.  These are not “bugs” as
 Chocolate Doom is behaving as intended.
 
 This is not intended to be a comprehensive list of Vanilla Doom bugs.
-For more information, consult the "engine bugs" page of the Doom Wiki.
-
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+For more information, consult the “engine bugs” page of the Doom Wiki.
 
-== Game exits after title screen with message about game version ==
+## Game exits after title screen with message about game version
 
 The game may exit after the title screen is shown, with a message like
 the following:
 
     Demo is from a different game version!
-    (read 106, should be 109)
+    (read 2, should be 109)
 
     *** You may need to upgrade your version of Doom to v1.9. ***
-        See: http://doomworld.com/files/patches.shtml
-        This appears to be v1.6/v1.666.
+        See: https://www.doomworld.com/classicdoom/info/patches.php
+        This appears to be v1.0/v1.1/v1.2.
 
 This usually indicates that your IWAD file that you are using to play
 the game (usually named doom.wad or doom2.wad) is out of date.
-Chocolate Doom only supports the v1.9 IWAD file.
-
-To fix the problem, you must upgrade to the v1.9 IWAD file.  The URL
-in the message has downloadable upgrade patches that you can use to
-upgrade.
+Chocolate Doom only supports versions 1.666 through 1.9.
 
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+To fix the problem, you must upgrade your IWAD file, preferably
+to 1.9.  The URL in the message has downloadable upgrade patches that
+you can use to upgrade.
 
-== Game exits in demo loop when playing Final Doom ==
+## Game exits in demo loop when playing Final Doom
 
 When playing with the Final Doom IWAD files (tnt.wad, plutonia.wad),
 if you leave the game at the title screen to play through the demo
@@ -53,11 +48,9 @@ the alternate version, run with:
 
     chocolate-doom -gameversion final2
 
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+## Game exits when accessing the options menu
 
-== Game exits when accessing the options menu ==
-
-The game may exit with the message "Bad V_DrawPatch" when accessing
+The game may exit with the message “Bad V_DrawPatch” when accessing
 the options menu, if you have your mouse sensitivity set high.
 
 The Doom options menu has a slider that allows the mouse sensitivity
@@ -73,37 +66,35 @@ same thing.
 
 One solution to the problem is to set a lower mouse sensitivity.
 Alternatively, all of the settings in the options menu can be
-controlled through Doom's key bindings anyway:
-
-    End game: F7
-    Messages on/off: F8
-    Graphic detail high/low: F5
-    Screen size smaller/larger: -/+
-    Sound volume menu: F4
+controlled through Doom’s key bindings anyway:
 
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+Option                     | Key
+---------------------------|-----
+End game                   | F7
+Messages on/off            | F8
+Graphic detail high/low    | F5
+Screen size smaller/larger | -/+
+Sound volume menu          | F4
 
-== Game exits with "Savegame buffer overrun" when saving the game ==
+## Game exits with “Savegame buffer overrun” when saving the game
 
 If you are playing on a particularly large level, it is possible that
-when you save the game, the game will quit with the message "Savegame
-buffer overrun".
+when you save the game, the game will quit with the message “Savegame
+buffer overrun”.
 
 Vanilla Doom has a limited size memory buffer that it uses for saving
 games.  If you are playing on a large level, the buffer may be too
 small for the entire savegame to fit.  Chocolate Doom allows the limit
-to be disabled: in the setup tool, go to the "compatibility" menu and
-disable the "Vanilla savegame limit" option.
+to be disabled: in the setup tool, go to the “compatibility” menu and
+disable the “Vanilla savegame limit” option.
 
 If this error happens to you, your game has not been lost!  A file
 named temp.dsg is saved; rename this to doomsav0.dsg to make it appear
-in the first slot in the "load game" menu.  (On Unix systems, you will
+in the first slot in the “load game” menu.  (On Unix systems, you will
 need to look in the .chocolate-doom/savegames directory in your home
 directory)
 
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-== Game ends suddenly when recording a demo ==
+## Game ends suddenly when recording a demo
 
 If you are recording a very long demo, the game may exit suddenly.
 Vanilla Doom has a limited size memory buffer that it uses to save the
@@ -113,33 +104,27 @@ this happens, as the demo file will be around 131,072 bytes in size.
 You can work around this by using the -maxdemo command line parameter
 to specify a larger buffer size.  Alternatively, the limit can be
 disabled: in the setup tool, go to the compatibility menu and disable
-the "Vanilla demo limit" option.
+the “Vanilla demo limit” option.
 
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-== Game exits with a message about "visplanes" ==
+## Game exits with a message about “visplanes”
 
 The game may exit with one of these messages:
 
     R_FindPlane: no more visplanes
     R_DrawPlanes: visplane overflow (129)
 
-This is known as the "visplane overflow" limit and is one of the most
+This is known as the “visplane overflow” limit and is one of the most
 well-known Vanilla Doom engine limits.  You should only ever experience
 this when trying to play an add-on level.  The level you are trying to
 play is too complex; it was most likely designed to work with a limit
 removing source port.
 
-More information can be found here:
-
-    http://rome.ro/lee_killough/editing/visplane.shtml
-
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+More information can be found here (archived link): https://archive.is/s6h7V
 
-== IDMUS## cheat doesn't work with shareware/registered Doom IWADs ==
+## IDMUS## cheat doesn’t work with shareware/registered Doom IWADs
 
 The IDMUS cheat allows the in-game music to be changed.  However, in
-the original v1.9 this cheat didn't work properly when playing with
+the original v1.9 this cheat didn’t work properly when playing with
 the Doom 1 (shareware and registered) IWADs.  This bug was fixed in
 the Ultimate Doom and Final Doom executables.
 
@@ -149,9 +134,7 @@ properly.  If you are playing with the Ultimate Doom IWAD, the
 Ultimate Doom executable is emulated by default, so the cheat works
 properly.
 
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-== Some graphics are wrong when playing with BFG Edition IWADs ==
+## Some graphics are wrong when playing with BFG Edition IWADs
 
 If you are playing using the IWAD files from Doom 3: BFG Edition, you
 may notice that certain graphics appear strange or wrong. This
@@ -161,9 +144,6 @@ The IWAD files in the new BFG Edition have had some significant
 changes from the IWAD files in older releases. Some of the graphics
 lumps have been removed or changed, and the Doom 2 secret levels are
 also censored. Chocolate Doom includes some small workarounds that
-allow the game to run, but for the best experience, it's best to get a
+allow the game to run, but for the best experience, it’s best to get a
 copy of the classic versions of the IWADs. These are still available
-to buy from Steam or Id's online store.
-
-# vim: tw=70
-
+to buy from Steam or GOG.com.
diff --git a/PHILOSOPHY b/PHILOSOPHY.md
similarity index 60%
rename from PHILOSOPHY
rename to PHILOSOPHY.md
index e84e558..5f309b0 100644
--- a/PHILOSOPHY
+++ b/PHILOSOPHY.md
@@ -1,39 +1,41 @@
-
 Chocolate Doom has been designed around a careful and deliberate
-philosophy that attempts to recreate the original ("Vanilla") DOS
+philosophy that attempts to recreate the original (“Vanilla”) DOS
 executables for Doom, Heretic, Hexen and Strife. This document
 describes some of that philosophy and the reasoning behind it.
 
 This document is descriptive, not proscriptive.
 
-== Vanilla behavior ==
+# Vanilla behavior
 
 Ideally Chocolate Doom aims to recreate the behavior of the Vanilla
 binaries, but different aspects of Vanilla behavior are held to
-varying degrees of importance. It can be imagined as different "tiers"
+varying degrees of importance. It can be imagined as different “tiers”
 of compatibility:
 
  * The game and gameplay itself is of central importance. Here, the
    Vanilla behavior ought to be maintained as accurately as possible.
    This includes the look, feel and sound, and things like demo
    compatibility.
- * The surrounding aspects of the game that aren't part of the central
-   gameplay experience can be extended as long as there's a good
+ * The surrounding aspects of the game that aren’t part of the central
+   gameplay experience can be extended as long as there’s a good
    reason and Vanilla behavior is respected.
  * The setup tool is not required to reproduce the behavior of the
    Vanilla setup tool, even though it reproduces its look and feel.
 
-"Vanilla" is defined as:
+“Vanilla” is defined as:
 
- * DOS Doom 1.9 (although there are actually multiple "1.9"s).
+ * DOS Doom 1.9 (although there are actually multiple “1.9”s).
  * 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 ==
+# Compatibility
 
 Chocolate Doom aims to be compatible with Vanilla Doom in several
 different ways. Examples are:
@@ -41,13 +43,13 @@ different ways. Examples are:
  * Bug compatibility: the aim is to emulate compatibility of the
    original game down to bugs that were present in the DOS
    executables. This includes maintaining the limitations of the
-   original engine: for example, the infamous "visplane overflow" bug
+   original engine: for example, the infamous “visplane overflow” bug
    is intentionally still present, where other source ports have
    removed it; this allows mappers targeting Vanilla Doom to use
    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
+   ‘speedrunning’ community, and thousands of recordings of Doom
+   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.
@@ -60,20 +62,20 @@ different ways. Examples are:
    file format as Vanilla, such that it should be possible to import
    and use existing savegame files.
 
-== DOS tools ==
+# DOS tools
 
-Chocolate Doom includes some features that aren't part of Vanilla Doom
+Chocolate Doom includes some features that aren’t part of Vanilla Doom
 but exist for compatibility with DOS tools that interact with it.
 These are considered part of the Vanilla experience and ought to be
 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.
 
-== Exceptions ==
+# Exceptions
 
 Chocolate Doom differs from Vanilla Doom in a number of ways. In most
 cases these are subtle, minor differences. Nonetheless they deserve
@@ -81,25 +83,25 @@ some explanation and justification. Here are some examples of
 situations where changes are considered acceptable:
 
  1. Vanilla behavior can be broken that is harmful, eg. can damage the
-    player's computer, or is just outright misleading. For example:
+    player’s computer, or is just outright misleading. For example:
 
     - Vanilla uses unbounded sprintf and strcpy (security issue).
     - Vanilla has crashes that force the user to reboot the machine.
     - Vanilla has an out of memory message that recommends tweaking
-      CONFIG.SYS. As Chocolate Doom doesn't run under DOS, reproducing
+      CONFIG.SYS. As Chocolate Doom doesn’t run under DOS, reproducing
       this message would not be helpful.
 
- 2. Subtly extending behavior is okay where it's not clear what the
+ 2. Subtly extending behavior is okay where it’s not clear what the
     Vanilla behavior is anyway. For example:
 
     - Opening the menu releases mouse grab in Chocolate Doom.
-    - Chocolate Hexen's graphical startup screen runs in a window.
+    - Chocolate Hexen’s graphical startup screen runs in a window.
 
- 3. Supporting obsolete technology is not a goal: it's considered
+ 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
@@ -109,59 +111,74 @@ situations where changes are considered acceptable:
  4. Changes are acceptable that allow the player to be able play the
     game. For example:
 
-    - 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 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 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.
+      play is better than nothing, even if it’s not Vanilla behavior.
+    - Chocolate Strife does not emulate the save bug from
+      Vanilla 1.31, which could corrupt saves when overwriting a slot,
+      if the old slot was not part of the current game’s progression.
+      Vanilla behavior is unexpected and potentially devastating.
 
  5. Adding extra options to Vanilla functionality is acceptable as long
-    as the defaults match Vanilla, it doesn't change gameplay and
-    there's a good reason for it. For example:
+    as the defaults match Vanilla, it doesn’t change gameplay and
+    there’s a good reason for it. For example:
 
     - PNG screenshots are supported because PCX is an obsolete format.
     - Chocolate Doom has the vanilla_keyboard_mapping option that
       allows the user to use the native keyboard mapping for their
       computer, rather than always assuming a US layout.
 
- 6. Changing configuration file defaults is acceptable where there's a
+ 6. Changing configuration file defaults is acceptable where there’s a
     very good reason. For example:
 
     - Vanilla Doom defaults to no sound or music if a configuration
       file is not found. Chocolate Doom defaults to having sound
       effects and music turned on by default, because modern computers
-      support these; there's no need to configure hardware IRQ settings
+      support these; there’s no need to configure hardware IRQ settings
       to get sound working.
 
- 7. Things can be changed if they're really just inconsequential. For
+ 7. Things can be changed if they’re really just inconsequential. For
     example:
 
     - 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,
-is there anyone who would want this?".
+A good litmus test of when it’s acceptable to break from Vanilla
+behavior is to ask the question: “Although this is Vanilla behavior,
+is there anyone who would want this?”
 
 For example, emulating Vanilla bugs like the visplane overflow bug is
 something that is useful for people making Vanilla format maps. On the
 other hand, painstakingly emulating Vanilla Doom by starting with no
 sound or music by default is not helpful to anyone.
 
-== Minimalism ==
+# Minimalism
 
 Chocolate Doom aims to be minimalist and straightforward to configure;
 in particular, the setup tool should have a sane interface. Part of
-the inspiration for Chocolate Doom came from Boom's complicated maze
+the inspiration for Chocolate Doom came from Boom’s complicated maze
 of options menus (and a desire to avoid them). Too many preferences
-lead to a bad user interface; see Havoc Pennington's essay on Free
+lead to a bad user interface; see Havoc Pennington’s essay on Free
 Software UI:
 
   http://ometer.com/free-software-ui.html
@@ -173,21 +190,21 @@ the setup tool. The assumption is that if you care enough about those
 obscure features, editing a configuration file by hand should not be a
 huge problem for you.
 
-Also inspirational was the README file from Havoc's Metacity window
+Also inspirational was the README file from Havoc’s Metacity window
 manager, where the list of features begins:
 
-  Boring window manager for the adult in you. Many window managers
-  are like Marshmallow Froot Loops; Metacity is like Cheerios.
+  > Boring window manager for the adult in you. Many window managers
+  > are like Marshmallow Froot Loops; Metacity is like Cheerios.
 
-I'd like to think that Chocolate Doom's philosophy towards features is
+I’d like to think that Chocolate Doom’s philosophy towards features is
 similar. The idea is for a source port that is boring. I find the best
 software - both proprietary and open source - is software that is
-"egoless": it does a job well without pretentions about its importance
+“egoless”: it does a job well without pretentions about its importance
 or delusions of grandeur. A couple of other notable examples of
 software that I feel embody this spirit of design in a beautiful way
-are Marco Pesenti Gritti's Epiphany web browser and Evince PDF viewer.
+are Marco Pesenti Gritti’s Epiphany web browser and Evince PDF viewer.
 
-== Other philosophical aspects ==
+# Other philosophical aspects
 
 Chocolate Doom aims for maximal portability. That includes running on
 many different CPUs, different operating systems and different devices
@@ -195,6 +212,3 @@ many different CPUs, different operating systems and different devices
 
 Chocolate Doom is and will always remain Free Software. It will never
 include code that is not compatible with the GNU GPL.
-
-# vim: tw=70
-
diff --git a/README.Music b/README.Music.md
similarity index 62%
rename from README.Music
rename to README.Music.md
index bd76566..3c3e0da 100644
--- a/README.Music
+++ b/README.Music.md
@@ -1,16 +1,15 @@
-
 Doom has a memorable and atmospheric soundtrack. Like many games of
 the era, it is MIDI-based. Chocolate Doom includes a number of
 different options for music playback, detailed below.
 
-== Native MIDI playback ==
+# Native MIDI playback
 
 Most modern operating systems have some kind of built-in support for
 MIDI playback; some have very good quality MIDI playback (Mac OS X for
-example). To use this, choose "Native MIDI" in the sound configuration
+example). To use this, choose “Native MIDI” in the sound configuration
 dialog in the setup tool.
 
-== Timidity ==
+# Timidity
 
 Timidity is a software-based MIDI synthesizer, and a version of it is
 included in the SDL_mixer library used by Chocolate Doom. To use
@@ -18,19 +17,19 @@ Timidity for MIDI playback, first download a sound font. An example of
 a good quality sound font is the eawpats font, which can be downloaded
 from the idgames archive as sounds/eawpats.zip:
 
-  http://www.doomworld.com/idgames/index.php?file=sounds/eawpats.zip
+  https://www.doomworld.com/idgames/sounds/eawpats
 
-Having installed a sound font, select "Native MIDI" in the sound
-configuration dialog in the setup tool, and use the "Timidity
-configuration file" widget below to enter the path to the Timidity
+Having installed a sound font, select “Native MIDI” in the sound
+configuration dialog in the setup tool, and use the “Timidity
+configuration file” widget below to enter the path to the Timidity
 configuration file (normally named timidity.cfg).
 
-== Gravis Ultrasound (GUS) ==
+# Gravis Ultrasound (GUS)
 
-The Gravis Ultrasound (GUS) was a PC sound card popular in the '90s,
+The Gravis Ultrasound (GUS) was a PC sound card popular in the ’90s,
 notable for having wavetable synthesis that provided MIDI playback
 that was superior to most other cards of the era. Chocolate Doom
-includes a "pseudo-GUS emulation" feature that simulates the GUS
+includes a “pseudo-GUS emulation” feature that simulates the GUS
 (using Timidity, under the hood).
 
 To use this feature you need a copy of the GUS patch files that were
@@ -39,39 +38,39 @@ Edition, these patches are included with its version of classic Doom,
 and are automatically detected. Otherwise, they can be downloaded
 from the idgames archive as music/dgguspat.zip:
 
-  http://www.doomworld.com/idgames/index.php?file=music/dgguspat.zip
+  https://www.doomworld.com/idgames/music/dgguspat
 
-Having downloaded the patches, select "GUS (emulated)" in the sound
-configuration dialog in the setup tool, and use the "GUS patch path"
+Having downloaded the patches, select “GUS (emulated)” in the sound
+configuration dialog in the setup tool, and use the “GUS patch path”
 widget to enter the path to the directory containing the patch files.
 
 By default a GUS card with 1024KB is simulated; to simulate a 256KB,
 512KB or 768KB card instead, change the gus_ram_kb option in
 chocolate-doom.cfg.
 
-== OPL (Soundblaster / Adlib) ==
+# OPL (Soundblaster / Adlib)
 
-Most people playing Doom in the '90s had Soundblaster-compatible sound
+Most people playing Doom in the ’90s had Soundblaster-compatible sound
 cards, which used the Yamaha OPL series of chips for FM-based MIDI
 synthesis. Chocolate Doom includes the ability to emulate these chips
 for a retro experience. OPL emulation is the default MIDI playback,
-but can be selected in the setup tool as "OPL (Adlib/SB)".
+but can be selected in the setup tool as “OPL (Adlib/SB)”.
 
 Most modern computers do not include an OPL chip any more, as CPUs are
 fast enough to do decent software MIDI synthesis. However, no software
 emulator sounds exactly like a real (hardware) OPL chip, and a few
-cards do have real hardware OPL. If you have such a card, here's how
+cards do have real hardware OPL. If you have such a card, here’s how
 to configure Chocolate Doom to use it.
 
-=== Sound cards with OPL chips ===
+## Sound cards with OPL chips
 
 If you have an ISA sound card, it almost certainly includes an OPL
-chip. Modern computers don't have slots for ISA cards though, so you
+chip. Modern computers don’t have slots for ISA cards though, so you
 must be running a pretty old machine.
 
-If you have a PCI sound card, you probably don't have an OPL chip.
+If you have a PCI sound card, you probably don’t have an OPL chip.
 However, there are some exceptions to this. The following cards are
-known to include "legacy" OPL support:
+known to include “legacy” OPL support:
 
   * C-Media CMI8738 (*)
   * Forte Media FM801
@@ -90,34 +89,36 @@ Other cards that apparently have OPL support but have not been tested:
 If you desperately want hardware OPL music, you may be able to find
 one of these cards for sale cheap on eBay.
 
-For the cards listed above with (*) next to them, OPL support is
-disabled by default and must be explictly enabled in software.
+For the cards listed above with (\*) next to them, OPL support is
+disabled by default and must be explictly enabled in software. See
+sections below for operating system-specific instructions on how you
+may be able to do this.
 
-If your machine is not a PC, you don't have an OPL chip, and you will
+If your machine is not a PC, you don’t have an OPL chip, and you will
 have to use the software OPL.
 
-=== Operating System support ===
+## Operating System support
 
-If you're certain that you have a sound card with hardware OPL, you
+If you’re certain that you have a sound card with hardware OPL, you
 may need to take extra steps to configure your operating system to
 allow access to it. To do hardware OPL, Chocolate Doom must access
 the chip directly, which is usually not possible in modern operating
 systems unless you are running as the superuser (root/Administrator).
 
-=== Windows 9x ===
+### Windows 9x
 
-If you're running Windows 95, 98 or Me, there is no need to configure
+If you’re running Windows 95, 98 or Me, there is no need to configure
 anything. Windows allows direct access to the OPL chip. You can
 confirm that hardware OPL is working by checking for this message in
 stdout.txt:
 
-  OPL_Init: Using driver 'Win32'.
+    OPL_Init: Using driver 'Win32'.
 
-=== Windows NT (including 2000, XP and later) ===
+### Windows NT (including 2000, XP and later)
 
-If you're running an NT-based system, it is not possible to directly
+If you’re running an NT-based system, it is not possible to directly
 access the OPL chip, even when running as Administrator. Fortunately,
-it is possible to use the "ioperm.sys" driver developed for Cygwin:
+it is possible to use the “ioperm.sys” driver developed for Cygwin:
 
   http://openwince.sourceforge.net/ioperm/
 
@@ -128,44 +129,46 @@ executable and it should be automatically loaded.
 You can confirm that hardware OPL is working by checking for this
 message in stdout.txt:
 
-  OPL_Init: Using driver 'Win32'.
+    OPL_Init: Using driver 'Win32'.
+
+If you have a C-Media CMI8738, you may need to enable the `FM_EN`
+flag in Windows Device Manager to access hardware OPL output. See
+[this](http://www.vogons.org/viewtopic.php?f=46&t=36445) thread on
+vogons.org for some more information.
 
-=== Linux ===
+### Linux
 
 If you are using a system based on the Linux kernel, you can access
 the OPL chip directly, but you must be running as root. You can
 confirm that hardware OPL is working, by checking for this message on
 startup:
 
-  OPL_Init: Using driver 'Linux'.
+    OPL_Init: Using driver 'Linux'.
 
 If you are using one of the PCI cards in the list above with a (*)
 next to it, you may need to manually enable FM legacy support. Add
 the following to your /etc/modprobe.conf file to do this:
 
-  options snd-ymfpci fm_port=0x388
-  options snd-cmipci fm_port=0x388
+    options snd-ymfpci fm_port=0x388
+    options snd-cmipci fm_port=0x388
 
-=== OpenBSD/NetBSD ===
+### OpenBSD/NetBSD
 
 You must be running as root to access the hardware OPL directly. You
 can confirm that hardware OPL is working by checking for this message
 on startup:
 
-  OPL_Init: Using driver 'OpenBSD'.
+    OPL_Init: Using driver 'OpenBSD'.
 
 There is no native OPL backend for FreeBSD yet. Sorry!
 
-== Other options ==
+# Other options
 
-If you have some other favorite MIDI playback option that isn't
+If you have some other favorite MIDI playback option that isn’t
 listed above, you can set a hook to invoke an external command for
-MIDI playback using the 'snd_musiccmd' configuration file option. For
+MIDI playback using the ‘snd_musiccmd’ configuration file option. For
 example, set:
 
-  snd_musiccmd    "aplaymidi -p 128:0"
+    snd_musiccmd    "aplaymidi -p 128:0"
 
 in your chocolate-doom.cfg file.
-
-# vim: set tw=70:
-
diff --git a/README.Strife b/README.Strife.md
similarity index 68%
rename from README.Strife
rename to README.Strife.md
index 0bd8e77..c770449 100644
--- a/README.Strife
+++ b/README.Strife.md
@@ -1,6 +1,6 @@
-===============================================================================
+````````````````````````````````````````````````````````````````````````
 
-          Samuel 'Kaiser' Villarreal and James 'Quasar' Haley Present
+          Samuel ‘Kaiser’ Villarreal and James ‘Quasar’ Haley Present
 
            C      H      O      C      O      L      A      T      E
             ______________________________._________________________
@@ -10,17 +10,17 @@
           /_______  /  |____|    |____|_  /___|\___  /   /_______  /
                   \/                    \/         \/            \/
 
-===============================================================================
+````````````````````````````````````````````````````````````````````````
 
-* What is it? *
+## What is it?
 
 Chocolate Strife is the most accurate and complete recreation of Rogue
-Entertainment's "Strife: Quest for the Sigil." It was created through more than
-four years of reverse engineering effort with the blessings of the original
-programmers of the game.
+Entertainment’s “Strife: Quest for the Sigil.”  It was created through more
+than four years of reverse engineering effort with the blessings of the
+original programmers of the game.
 
 
-* Why? *
+## Why?
 
 The source code for Strife was lost, which means, unlike the code for all the
 other commercial DOOM-engine games, it cannot be released. The only access we
@@ -32,7 +32,7 @@ resulting Chocolate Doom-based executable is as close as possible to the
 original.
 
 
-* Is it Legal? *
+## Is it Legal?
 
 Chocolate Strife was originally reverse-engineered from the DOS Strife
 binaries. Although reverse engineering is legally a protected activity, this
@@ -45,9 +45,9 @@ Edition, along with its GPL-licensed source code, constitutes tacit approval
 for the legal status of Chocolate Strife by its current copyright holder.
 
 
-* Is it Perfect? *
+## Is it Perfect?
 
-Almost, but not entirely! That's where you come in. Help us by reporting any
+Almost, but not entirely! That’s where you come in. Help us by reporting any
 discrepancies you may notice between this executable and the vanilla DOS
 program!
 
@@ -60,16 +60,20 @@ for example by initializing pointers to NULL rather than using them without
 setting a value first.
 
 
-* What are some known problems? *
+## What are some known problems?
 
-- The demo version is *not* supported, and there are not any current plans
-  to support it in the future, due to the vast number of differences (the
-  demo version of Strife is based on a much earlier beta version of Rogue's
-  codebase). You should use a commercial Strife IWAD file, preferably of
-  version 1.2 or later.
+The demo version is *not* supported, and there are not any current plans to
+support it in the future, due to the vast number of differences (the demo
+version of Strife is based on an earlier version of Rogue’s
+codebase).
 
+The commercial Strife IWAD version 1.1 may run, but also exhibit issues.  Like
+the demo version, there are no current plans to fully support it.  Make sure
+your copy is updated to at least 1.2.  Strife: Veteran Edition already
+includes the required version.
 
-* How do I use it? *
+
+## How do I use it?
 
 From the Run box or a command line, issue a command to Chocolate Strife just
 like you would run Chocolate Doom. Most of the same command line parameters
@@ -82,27 +86,27 @@ redundant and unnecessary.
 
 Some new command-line parameters in Chocolate Strife include the following:
 
--nograph
-  Disables the graphical introduction sequence. -devparm implies this.
+  - -nograph
+    - Disables the graphical introduction sequence. -devparm implies this.
 
--novoice
-  Disables voices even if voices.wad can be found.
+  - -novoice
+    - Disables voices even if voices.wad can be found.
 
--work
-  Enables Rogue's playtesting mode. Automatic godmode, and pressing the
-  inventory drop key will toggle noclipping.
+  - -work
+    - Enables Rogue’s playtesting mode. Automatic godmode, and pressing the
+      inventory drop key will toggle noclipping.
 
--flip
-  Flips player gun graphics. This is buggy, however, because it does not
-  reverse the graphics' x offsets (this is an accurate emulation of the
-  vanilla engine's behavior).
+  - -flip
+    - Flips player gun graphics. This is buggy, however, because it does not
+      reverse the graphics’ x offsets (this is an accurate emulation of the
+      vanilla engine’s behavior).
 
--random
-  Randomizes the timing and location of item respawns in deathmatch, when
-  item respawning is enabled.
+  - -random
+    - Randomizes the timing and location of item respawns in deathmatch, when
+      item respawning is enabled.
 
 
-* Copyright *
+## Copyright
 
 This program is free software; you can redistribute it and/or modify it under
 the terms of the GNU General Public License as published by the Free Software
@@ -113,9 +117,8 @@ This program is distributed in the hope that it will be useful,but WITHOUT ANY
 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
 A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
 
-See the "COPYING" file for the full license text. The source code for this
+See the “COPYING” file for the full license text. The source code for this
 program is available from the same location where you downloaded this package.
 
 Aside from Chocolate Doom, portions of the code are derived from the Eternity
 Engine, Copyright 2011 Team Eternity, as published under the GNU GPL.
-
diff --git a/README b/README.md
similarity index 79%
rename from README
rename to README.md
index 9ee5610..1bc51c4 100644
--- a/README
+++ b/README.md
@@ -1,3 +1,4 @@
+# Chocolate Doom
 
 Chocolate Doom aims to accurately reproduce the original DOS version of
 Doom and other games based on the Doom engine in a form that can be
@@ -6,35 +7,35 @@ run on modern computers.
 Originally, Chocolate Doom was only a Doom source port. The project
 now includes ports of Heretic and Hexen, and Strife.
 
-Chocolate Doom's aims are:
+Chocolate Doom’s aims are:
 
  * To always be 100% Free and Open Source software.
  * Portability to as many different operating systems as possible.
  * Accurate reproduction of the original DOS versions of the games,
    including bugs.
  * Compatibility with the DOS demo, configuration and savegame files.
- * To provide an accurate retro "feel" (display and input should
+ * To provide an accurate retro “feel” (display and input should
    behave the same).
 
 More information about the philosophy and design behind Chocolate Doom
 can be found in the PHILOSOPHY file distributed with the source code.
 
-== Setting up gameplay ==
+## Setting up gameplay
 
 For instructions on how to set up Chocolate Doom for play, see the
 INSTALL file.
 
-== Configuration File ==
+## Configuration File
 
 Chocolate Doom is compatible with the DOS Doom configuration file
-(normally named 'default.cfg'). Existing configuration files for DOS
+(normally named `default.cfg`). Existing configuration files for DOS
 Doom should therefore simply work out of the box. However, Chocolate
 Doom also provides some extra settings. These are stored in a
-separate file named 'chocolate-doom.cfg'.
+separate file named `chocolate-doom.cfg`.
 
 The configuration can be edited using the chocolate-setup tool.
 
-== Command line options ==
+## Command line options
 
 Chocolate Doom supports a number of command line parameters, including
 some extras that were not originally suported by the DOS versions. For
@@ -42,30 +43,34 @@ binary distributions, see the CMDLINE file included with your
 download; more information is also available on the Chocolate Doom
 website.
 
-== Playing TCs ==
+## Playing TCs
 
 With Vanilla Doom there is no way to include sprites in PWAD files.
-Chocolate Doom's '-file' command line option behaves exactly the same
+Chocolate Doom’s ‘-file’ command line option behaves exactly the same
 as Vanilla Doom, and trying to play TCs by adding the WAD files using
-'-file' will not work.
+‘-file’ will not work.
 
 Many Total Conversions (TCs) are distributed as a PWAD file which must
 be merged into the main IWAD. Typically a copy of DEUSF.EXE is
 included which performs this merge. Chocolate Doom includes a new
-option, '-merge', which will simulate this merge. Essentially, the
+option, ‘-merge’, which will simulate this merge. Essentially, the
 WAD directory is merged in memory, removing the need to modify the
 IWAD on disk.
 
 To play TCs using Chocolate Doom, run like this:
 
-  chocolate-doom -merge thetc.wad
+```
+chocolate-doom -merge thetc.wad
+```
 
 Here are some examples:
 
-  chocolate-doom -merge batman.wad -deh batman.deh vbatman.deh  (Batman Doom)
-  chocolate-doom -merge aoddoom1.wad -deh aoddoom1.deh  (Army of Darkness Doom)
+```
+chocolate-doom -merge batman.wad -deh batman.deh vbatman.deh  (Batman Doom)
+chocolate-doom -merge aoddoom1.wad -deh aoddoom1.deh  (Army of Darkness Doom)
+```
 
-== Other information ==
+## Other information
 
  * Chocolate Doom includes a number of different options for music
    playback. See the README.Music file for more details.
@@ -73,7 +78,7 @@ Here are some examples:
  * More information, including information about how to play various
    classic TCs, is available on the Chocolate Doom website:
 
-     http://www.chocolate-doom.org/
+     https://www.chocolate-doom.org/
 
    You are encouraged to sign up and contribute any useful information
    you may have regarding the port!
@@ -96,7 +101,4 @@ Here are some examples:
    file for more information.
 
  * Please send any feedback, questions or suggestions to
-   fraggle at gmail.com. Thanks!
-
-# vim: tw=70
-
+   chocolate-doom-dev-list at chocolate-doom.org. Thanks!
diff --git a/TODO b/TODO
deleted file mode 100644
index b6d995e..0000000
--- a/TODO
+++ /dev/null
@@ -1,56 +0,0 @@
-To do:
-
-* Multiplayer:
-  - Use UPnP to automatically configure port forwarding for NATted
-    networks.
-  - Multiplayer options and configuration file (server name, etc)
-* Improve multiplayer startup:
-  - Select an IWAD automatically from the server's game type rather than
-    all players having to specify -iwad.
-  - Send list of WADs to load instead of all clients having to specify -file.
-  - Same applies to dehacked patches and wad merging parameters.
-* Portability improvements:
-  - Test on and fix for architectures where ((-2) >> 1) != -1
-  - Use size-specific types (eg. int32_t instead of int)
-  - Don't make structure packing assumptions when loading levels.
-  - Port to every OS and architecture under the sun
-
-Heretic/Hexen:
- * Frequency shifted sounds.
- * Check for endianness assumptions - mostly done now
- * Structure packing macros for structures read from disk
- * Merge r_draw.c to common version and delete duplicate
- * Heretic v1.2 emulation (if possible)
- * Hexen v1.0 emulation (if possible/necessary)
- * Screensaver mode
-
-Crazy pie in the sky ideas:
-
-* Automatic WAD installer - download and run TCs from a list automatically
-  (automating the current "instructions on wiki" system).
-* Textscreen interface to the Compet-N database: menu driven system to
-  automatically download and play speedruns.
-* DWANGO-like interface for finding players and setting up games.
-* Video capture mode?
-
-== OPL TODO list ==
-
-Needs research:
-
- * Strategy when no more voices are available is still wrong
- * Scale levels don't exactly match Vanilla (off-by-one?)
-
-Bad MIDIs:
-
- * doom2.wad MAP01
- * gothicdm MAP05
- * tnt.wad MAP30
- * Alien Vendetta (title screen, MAP01, etc)
-
-Other tasks:
-
- * Get a better software OPL emulator
- * DMXOPTIONS opl3/phase option support.
-
-# vim: tw=70
-
diff --git a/TODO.md b/TODO.md
new file mode 100644
index 0000000..3f40d47
--- /dev/null
+++ b/TODO.md
@@ -0,0 +1,36 @@
+This is Chocolate Doom’s “to do” list. Note that this is kind of an arbitrary
+and unstructured wish list of features and improvements. The bug tracker
+(http://chocolate-doom.org/bugs) has more feature requests.
+
+* Multiplayer:
+  - Use UPnP to automatically configure port forwarding for NATed
+    networks.
+  - Multiplayer options and configuration file (server name, etc)
+* Improve multiplayer startup:
+  - Select an IWAD automatically from the server’s game type rather than
+    all players having to specify -iwad.
+  - Send list of WADs to load instead of all clients having to specify -file.
+  - Same applies to dehacked patches and wad merging parameters.
+* Portability improvements:
+  - Test on and fix for architectures where `((-2) >> 1) != -1`
+  - Use size-specific types (eg. `int32_t` instead of int)
+  - Don’t make structure packing assumptions when loading levels.
+  - Port to every OS and architecture under the sun
+  - Port to Emscripten and release a web-based version.
+* Video capture mode
+  - Real-time recording of gameplay
+  - Batch conversion of demos into videos
+* Heretic/Hexen/Strife:
+  - Merge r_draw.c to common version and delete duplicates
+  - Heretic v1.2 emulation (if possible)
+  - Hexen v1.0 emulation (if possible/necessary)
+  - Strife v1.1 emulation (for demo IWAD support)
+  - Screensaver mode
+
+Crazy pie in the sky ideas:
+
+* Automatic WAD installer - download and run TCs from a list automatically
+  (automating the current “instructions on wiki” system).
+* Textscreen interface to the Compet-N database: menu driven system to
+  automatically download and play speedruns.
+* DWANGO-like interface for finding players and setting up games.
diff --git a/codeblocks/config.h b/codeblocks/config.h
index 26af9dd..79f83c6 100644
--- 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). */
diff --git a/codeblocks/game-res.rc b/codeblocks/game-res.rc
index 7392f1e..7e0ac02 100644
--- a/codeblocks/game-res.rc
+++ b/codeblocks/game-res.rc
@@ -1,21 +1,21 @@
 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"
  {
   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"
diff --git a/codeblocks/libopl.cbp b/codeblocks/libopl.cbp
index a38460d..df3368b 100644
--- a/codeblocks/libopl.cbp
+++ b/codeblocks/libopl.cbp
@@ -41,10 +41,6 @@
 		<Linker>
 			<Add option="-lmingw32 -lSDLmain -lSDL -lSDL_mixer -mwindows" />
 		</Linker>
-		<Unit filename="..\opl\dbopl.c">
-			<Option compilerVar="CC" />
-		</Unit>
-		<Unit filename="..\opl\dbopl.h" />
 		<Unit filename="..\opl\ioperm_sys.c">
 			<Option compilerVar="CC" />
 		</Unit>
diff --git a/codeblocks/setup-res.rc b/codeblocks/setup-res.rc
index 26d5cfa..36e4eef 100644
--- a/codeblocks/setup-res.rc
+++ b/codeblocks/setup-res.rc
@@ -1,21 +1,21 @@
 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"
  {
   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"
diff --git a/configure.ac b/configure.ac
index ee97fe2..8bfe377 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1,14 +1,16 @@
-AC_INIT(Chocolate Doom, 2.2.1, fraggle at gmail.com, chocolate-doom)
+AC_INIT(Chocolate Doom, 2.3.0, chocolate-doom-dev-list at chocolate-doom.org,
+        chocolate-doom)
 
 PACKAGE_SHORTNAME=${PACKAGE_NAME% Doom}
 PACKAGE_SHORTDESC="Conservative source port"
 PACKAGE_COPYRIGHT="Copyright (C) 1993-2015"
 PACKAGE_LICENSE="GNU General Public License, version 2"
 PACKAGE_MAINTAINER="Simon Howard"
-PACKAGE_URL="http://www.chocolate-doom.org/"
+PACKAGE_URL="https://www.chocolate-doom.org/"
 PACKAGE_ISSUES="https://github.com/chocolate-doom/chocolate-doom/issues"
 
 AC_CONFIG_AUX_DIR(autotools)
+AC_CANONICAL_HOST
 
 orig_CFLAGS="$CFLAGS"
 
@@ -18,12 +20,6 @@ AC_CHECK_PROG(HAVE_PYTHON, python, true, false)
 
 OPT_LEVEL=2
 
-# Engine room, we need more speed!
-
-AC_ARG_ENABLE(penis-extension, 
-[  --enable-penis-extension   Enable counterproductive compiler optimisations ],
-[ OPT_LEVEL=3 ])
-
 # If this is gcc, we have some options we'd like to turn on.  Turn on 
 # optimisation and debugging symbols.
 
@@ -80,7 +76,14 @@ AC_SDL_MAIN_WORKAROUND([
         [Build without libsamplerate @<:@default=check@:>@]),
     [],
     [
-        AC_CHECK_LIB(samplerate, src_new)
+        [with_libsamplerate=check]
+    ])
+    AS_IF([test "x$with_libsamplerate" != xno], [
+        AC_CHECK_LIB(samplerate, src_new, [], [
+            AS_IF([test "x$with_libsamplerate" != xcheck], [AC_MSG_FAILURE(
+                [--with-libsamplerate was given, but test for libsamplerate failed])
+            ])
+        ])
     ])
     # Check for libpng.
     AC_ARG_WITH([libpng],
@@ -88,8 +91,15 @@ AC_SDL_MAIN_WORKAROUND([
         [Build without libpng @<:@default=check@:>@]),
     [],
     [
+        [with_libpng=check]
+    ])
+    AS_IF([test "x$with_libpng" != xno], [
         AC_CHECK_LIB(z, zlibVersion)
-        AC_CHECK_LIB(png, png_get_io_ptr)
+        AC_CHECK_LIB(png, png_get_io_ptr, [], [
+            AS_IF([test "x$with_libpng" != xcheck], [AC_MSG_FAILURE(
+                [--with-libpng was given, but test for libpng failed])
+            ])
+        ])
     ])
     AC_CHECK_LIB(m, log)
 
@@ -103,8 +113,15 @@ AC_SDL_MAIN_WORKAROUND([
     AC_CHECK_LIB(amd64, amd64_iopl)
 ])
 
-case $host in
-  *cygwin* | *mingw* )
+AC_ARG_WITH([bashcompletiondir],
+    AS_HELP_STRING([--with-bashcompletiondir=DIR], [Bash completion directory]),
+    [],
+    [AS_IF([$($PKG_CONFIG --exists bash-completion 2> /dev/null)],
+        [bashcompletiondir=$($PKG_CONFIG --variable=completionsdir bash-completion)],
+	[bashcompletiondir=${datadir}/bash-completion/completions])])
+
+case "$host" in
+  *-*-mingw* | *-*-cygwin* | *-*-msvc* )
     AC_CHECK_TOOL(WINDRES, windres, )
     ;;
   *)
@@ -153,12 +170,15 @@ AC_SUBST(PACKAGE_MAINTAINER)
 AC_SUBST(PACKAGE_URL)
 AC_SUBST(PACKAGE_ISSUES)
 
+AC_SUBST(bashcompletiondir)
+
 dnl Shut up the datarootdir warnings.
 AC_DEFUN([AC_DATAROOTDIR_CHECKED])
 
 AC_OUTPUT([
 Makefile
 man/Makefile
+man/bash-completion/Makefile
 opl/Makefile
 opl/examples/Makefile
 pcsound/Makefile
diff --git a/man/INSTALL.template b/man/INSTALL.template
index 9c85099..69f4cca 100644
--- a/man/INSTALL.template
+++ b/man/INSTALL.template
@@ -65,6 +65,7 @@ following names:
    tnt.wad                     (Final Doom: TNT: Evilution)
    plutonia.wad                (Final Doom: Plutonia Experiment)
    chex.wad                    (Chex Quest)
+   freedm.wad                  (FreeDM)
 #endif
 #if HERETIC
    heretic1.wad                (Shareware Heretic)
@@ -81,7 +82,7 @@ following names:
 If you don't have a copy of a commercial version, you can download
 the shareware version of Doom (extract the file named doom1.wad):
 
- * http://www.doomworld.com/idgames/index.php?id=7053
+ * https://www.doomworld.com/idgames/idstuff/doom/win95/doom95
    (idstuff/doom/win95/doom95.zip in your nearest /idgames mirror)
 
 #endif
@@ -195,7 +196,7 @@ are needed:
  * The IWAD file 'chex.wad', from the Chex Quest CD.
 
  * The dehacked patch 'chex.deh', which can be found here:
-   http://www.doomworld.com/idgames/?id=15420
+   https://www.doomworld.com/idgames/utils/exe_edit/patches/chexdeh
    (utils/exe_edit/patches/chexdeh.zip in your nearest /idgames mirror)
 
 Copy these files into a directory together and use the '-iwad' command
diff --git a/man/Makefile.am b/man/Makefile.am
index f93e8ed..b92cf97 100644
--- a/man/Makefile.am
+++ b/man/Makefile.am
@@ -1,3 +1,5 @@
+SUBDIRS = bash-completion
+
 MANPAGE_GEN_FILES = environ.man           \
                     iwad_paths.man        \
                     doom.template         \
@@ -52,7 +54,7 @@ MANDIR = $(top_srcdir)/man
 DOCGEN = $(MANDIR)/docgen
 
 $(SETUP_MAN_PAGES): chocolate-setup.6
-	cp $< $@
+	cp chocolate-setup.6 $@
 
 @PROGRAM_PREFIX at doom.6: ../src $(MANPAGE_GEN_FILES)
 	$(DOCGEN) -g doom -m doom.template $(top_srcdir)/src $(top_srcdir)/src/doom > $@
diff --git a/man/bash-completion/.gitignore b/man/bash-completion/.gitignore
new file mode 100644
index 0000000..74c4d54
--- /dev/null
+++ b/man/bash-completion/.gitignore
@@ -0,0 +1,4 @@
+*doom
+*heretic
+*hexen
+*strife
diff --git a/man/bash-completion/Makefile.am b/man/bash-completion/Makefile.am
new file mode 100644
index 0000000..7001aff
--- /dev/null
+++ b/man/bash-completion/Makefile.am
@@ -0,0 +1,44 @@
+bashcompletiondir=@bashcompletiondir@
+
+BASH_COMPLETION_TEMPLATES = \
+    doom.template \
+    heretic.template \
+    hexen.template \
+    strife.template
+
+if HAVE_PYTHON
+
+BASH_COMPLETION_SCRIPTLETS = \
+    @PROGRAM_PREFIX at doom \
+    @PROGRAM_PREFIX at heretic \
+    @PROGRAM_PREFIX at hexen \
+    @PROGRAM_PREFIX at strife
+
+bashcompletion_DATA = $(BASH_COMPLETION_SCRIPTLETS)
+CLEANFILES = $(BASH_COMPLETION_SCRIPTLETS)
+
+MANDIR = $(top_srcdir)/man
+DOCGEN = $(MANDIR)/docgen
+
+ at PROGRAM_PREFIX@doom: $(top_srcdir)/src $(DOCGEN) $(BASH_COMPLETION_TEMPLATES)
+	$(DOCGEN) -g doom -b doom.template $(top_srcdir)/src $(top_srcdir)/src/doom > $@
+
+ at PROGRAM_PREFIX@heretic: $(top_srcdir)/src $(DOCGEN) $(BASH_COMPLETION_TEMPLATES)
+	$(DOCGEN) -g heretic -b heretic.template $(top_srcdir)/src $(top_srcdir)/src/heretic > $@
+
+ at PROGRAM_PREFIX@hexen: $(top_srcdir)/src $(DOCGEN) $(BASH_COMPLETION_TEMPLATES)
+	$(DOCGEN) -g hexen -b hexen.template $(top_srcdir)/src $(top_srcdir)/src/hexen > $@
+
+ at PROGRAM_PREFIX@strife: $(top_srcdir)/src $(DOCGEN) $(BASH_COMPLETION_TEMPLATES)
+	$(DOCGEN) -g strife -b strife.template $(top_srcdir)/src $(top_srcdir)/src/strife > $@
+
+EXTRA_DIST = \
+    $(BASH_COMPLETION_TEMPLATES) \
+    $(BASH_COMPLETION_SCRIPTLETS)
+
+else
+
+EXTRA_DIST = \
+    $(BASH_COMPLETION_TEMPLATES)
+
+endif
diff --git a/man/bash-completion/doom.template b/man/bash-completion/doom.template
new file mode 100644
index 0000000..535ad2a
--- /dev/null
+++ b/man/bash-completion/doom.template
@@ -0,0 +1,51 @@
+# bash completion for Chocolate Doom                       -*- shell-script -*-
+
+_chocolate_doom()
+{
+    local cur prev words cword
+    _init_completion || return
+
+    # Save the previous switch on the command line in the prevsw variable
+    local i prevsw=""
+    for (( i=1; $cword > 1 && i <= $cword; i++ )); do
+        if [[ ${words[i]} == -* ]]; then
+            prevsw=${words[i]}
+        fi
+    done
+
+    # Allow adding more than one file with the same extension to the same switch
+    case $prevsw in
+        -config|-extraconfig)
+            _filedir cfg
+            ;;
+        -file|-iwad|-aa|-af|-as|-merge|-nwtmerge)
+            _filedir wad
+            ;;
+        -playdemo|-timedemo)
+            _filedir lmp
+            ;;
+        -deh)
+            _filedir '@(bex|deh)'
+            ;;
+    esac
+
+    case $prev in
+        -pack)
+            COMPREPLY=(doom2 tnt plutonia)
+            ;;
+        -gameversion)
+            COMPREPLY=(1.9 ultimate final final2 hacx chex)
+            ;;
+        -setmem)
+            COMPREPLY=(dos622 dos71 dosbox)
+            ;;
+    esac
+
+    if [[ $cur == -* ]]; then
+        COMPREPLY=( $( compgen -W '@content' -- "$cur" ) )
+    fi
+} &&
+
+complete -F _chocolate_doom chocolate-doom
+
+# ex: ts=4 sw=4 et filetype=sh
diff --git a/man/bash-completion/heretic.template b/man/bash-completion/heretic.template
new file mode 100644
index 0000000..5d658d4
--- /dev/null
+++ b/man/bash-completion/heretic.template
@@ -0,0 +1,48 @@
+# bash completion for Chocolate Heretic                    -*- shell-script -*-
+
+_chocolate_heretic()
+{
+    local cur prev words cword
+    _init_completion || return
+
+    # Save the previous switch on the command line in the prevsw variable
+    local i prevsw=""
+    for (( i=1; $cword > 1 && i <= $cword; i++ )); do
+        if [[ ${words[i]} == -* ]]; then
+            prevsw=${words[i]}
+        fi
+    done
+
+    # Allow adding more than one file with the same extension to the same switch
+    case $prevsw in
+        -config|-extraconfig)
+            _filedir cfg
+            ;;
+        -file|-iwad|-aa|-af|-as|-merge|-nwtmerge)
+            _filedir wad
+            ;;
+        -playdemo|-timedemo)
+            _filedir lmp
+            ;;
+        -deh)
+            _filedir hhe
+            ;;
+    esac
+
+    case $prev in
+        -hhever)
+            COMPREPLY=(1.0 1.2 1.3)
+            ;;
+        -setmem)
+            COMPREPLY=(dos622 dos71 dosbox)
+            ;;
+    esac
+
+    if [[ $cur == -* ]]; then
+        COMPREPLY=( $( compgen -W '@content' -- "$cur" ) )
+    fi
+} &&
+
+complete -F _chocolate_heretic chocolate-heretic
+
+# ex: ts=4 sw=4 et filetype=sh
diff --git a/man/bash-completion/hexen.template b/man/bash-completion/hexen.template
new file mode 100644
index 0000000..b7e75f7
--- /dev/null
+++ b/man/bash-completion/hexen.template
@@ -0,0 +1,42 @@
+# bash completion for Chocolate Hexen                     -*- shell-script -*-
+
+_chocolate_hexen()
+{
+    local cur prev words cword
+    _init_completion || return
+
+    # Save the previous switch on the command line in the prevsw variable
+    local i prevsw=""
+    for (( i=1; $cword > 1 && i <= $cword; i++ )); do
+        if [[ ${words[i]} == -* ]]; then
+            prevsw=${words[i]}
+        fi
+    done
+
+    # Allow adding more than one file with the same extension to the same switch
+    case $prevsw in
+        -config|-extraconfig)
+            _filedir cfg
+            ;;
+        -file|-iwad|-aa|-af|-as|-merge|-nwtmerge)
+            _filedir wad
+            ;;
+        -playdemo|-timedemo)
+            _filedir lmp
+            ;;
+    esac
+
+    case $prev in
+        -setmem)
+            COMPREPLY=(dos622 dos71 dosbox)
+            ;;
+    esac
+
+    if [[ $cur == -* ]]; then
+        COMPREPLY=( $( compgen -W '@content' -- "$cur" ) )
+    fi
+} &&
+
+complete -F _chocolate_hexen chocolate-hexen
+
+# ex: ts=4 sw=4 et filetype=sh
diff --git a/man/bash-completion/strife.template b/man/bash-completion/strife.template
new file mode 100644
index 0000000..13c8a8c
--- /dev/null
+++ b/man/bash-completion/strife.template
@@ -0,0 +1,48 @@
+# bash completion for Chocolate Strife                     -*- shell-script -*-
+
+_chocolate_strife()
+{
+    local cur prev words cword
+    _init_completion || return
+
+    # Save the previous switch on the command line in the prevsw variable
+    local i prevsw=""
+    for (( i=1; $cword > 1 && i <= $cword; i++ )); do
+        if [[ ${words[i]} == -* ]]; then
+            prevsw=${words[i]}
+        fi
+    done
+
+    # Allow adding more than one file with the same extension to the same switch
+    case $prevsw in
+        -config|-extraconfig)
+            _filedir cfg
+            ;;
+        -file|-iwad|-aa|-af|-as|-merge|-nwtmerge)
+            _filedir wad
+            ;;
+        -playdemo|-timedemo)
+            _filedir lmp
+            ;;
+        -deh)
+            _filedir seh
+            ;;
+    esac
+
+    case $prev in
+        -gameversion)
+            COMPREPLY=(1.2 1.31)
+            ;;
+        -setmem)
+            COMPREPLY=(dos622 dos71 dosbox)
+            ;;
+    esac
+
+    if [[ $cur == -* ]]; then
+        COMPREPLY=( $( compgen -W '@content' -- "$cur" ) )
+    fi
+} &&
+
+complete -F _chocolate_strife chocolate-strife
+
+# ex: ts=4 sw=4 et filetype=sh
diff --git a/man/docgen b/man/docgen
index 3b11ad8..510c2ba 100755
--- a/man/docgen
+++ b/man/docgen
@@ -106,6 +106,19 @@ class Category:
 
         return result
 
+    def completion_output(self):
+        result = ""
+
+        self.params.sort()
+
+        for p in self.params:
+            if p.should_show():
+                result += p.completion_output(0)
+
+        result = result.rstrip()
+
+        return result
+
     def manpage_output(self):
         result = ".SH " + self.description.upper() + "\n"
 
@@ -278,6 +291,12 @@ class Parameter:
 
         return result
 
+    def completion_output(self, w):
+
+        result = self.name + " "
+
+        return result
+
 # Read list of wiki pages
 
 def read_wikipages():
@@ -444,6 +463,15 @@ def plaintext_output(targets, template_file):
 
     print_template(template_file, content)
 
+def completion_output(targets, template_file):
+
+    content = ""
+
+    for t in targets:
+        content += t.completion_output() + "\n"
+
+    print_template(template_file, content)
+
 def usage():
     print("Usage: %s [-V] [-c tag] [-g game] ( -m | -w | -p ) <dir>..." \
             % sys.argv[0])
@@ -452,13 +480,14 @@ def usage():
     print("   -m :  Manpage output")
     print("   -w :  Wikitext output")
     print("   -p :  Plaintext output")
+    print("   -b :  Bash-Completion output")
     print("   -V :  Don't show Vanilla Doom options")
     print("   -g :  Only document options for specified game.")
     sys.exit(0)
 
 # Parse command line
 
-opts, args = getopt.getopt(sys.argv[1:], "m:wp:c:g:V")
+opts, args = getopt.getopt(sys.argv[1:], "m:wp:b:c:g:V")
 
 output_function = None
 template = None
@@ -474,6 +503,9 @@ for opt in opts:
     elif opt[0] == "-p":
         output_function = plaintext_output
         template = opt[1]
+    elif opt[0] == "-b":
+        output_function = completion_output
+        template = opt[1]
     elif opt[0] == "-V":
         show_vanilla_options = False
     elif opt[0] == "-c":
diff --git a/msvc/.gitignore b/msvc/.gitignore
index 47e8226..08f3498 100644
--- a/msvc/.gitignore
+++ b/msvc/.gitignore
@@ -2,6 +2,10 @@
 *.ncb
 *.suo
 *.user
+hexndata
 savegames
 strfsav*
 *.pcx
+SDL*
+begin_code.h
+close_code.h
\ No newline at end of file
diff --git a/msvc/config.h b/msvc/config.h
index e376ac1..f6ae5bc 100644
--- 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). */
diff --git a/msvc/doom.vcproj b/msvc/doom.vcproj
index 7446ede..b41fca4 100644
--- a/msvc/doom.vcproj
+++ b/msvc/doom.vcproj
@@ -380,6 +380,10 @@
 				>
 			</File>
 			<File
+				RelativePath="..\src\v_diskicon.h"
+				>
+			</File>
+			<File
 				RelativePath="..\src\v_patch.h"
 				>
 			</File>
@@ -796,6 +800,10 @@
 				>
 			</File>
 			<File
+				RelativePath="..\src\v_diskicon.c"
+				>
+			</File>
+			<File
 				RelativePath="..\src\v_video.c"
 				>
 			</File>
diff --git a/msvc/heretic.vcproj b/msvc/heretic.vcproj
index b3e150c..d9955ac 100644
--- a/msvc/heretic.vcproj
+++ b/msvc/heretic.vcproj
@@ -374,6 +374,10 @@
 				>
 			</File>
 			<File
+				RelativePath="..\src\v_diskicon.c"
+				>
+			</File>
+			<File
 				RelativePath="..\src\v_video.c"
 				>
 			</File>
@@ -792,6 +796,10 @@
 				>
 			</File>
 			<File
+				RelativePath="..\src\v_diskicon.h"
+				>
+			</File>
+			<File
 				RelativePath="..\src\v_patch.h"
 				>
 			</File>
diff --git a/msvc/hexen.vcproj b/msvc/hexen.vcproj
index 5c2d201..6a3f769 100644
--- a/msvc/hexen.vcproj
+++ b/msvc/hexen.vcproj
@@ -65,6 +65,7 @@
 				IgnoreAllDefaultLibraries="false"
 				IgnoreDefaultLibraryNames="msvcrt"
 				GenerateDebugInformation="true"
+				GenerateMapFile="true"
 				SubSystem="1"
 				TargetMachine="1"
 			/>
@@ -355,6 +356,10 @@
 				>
 			</File>
 			<File
+				RelativePath="..\src\v_diskicon.c"
+				>
+			</File>
+			<File
 				RelativePath="..\src\v_video.c"
 				>
 			</File>
@@ -753,6 +758,10 @@
 				>
 			</File>
 			<File
+				RelativePath="..\src\v_diskicon.h"
+				>
+			</File>
+			<File
 				RelativePath="..\src\v_patch.h"
 				>
 			</File>
diff --git a/msvc/libopl.vcproj b/msvc/libopl.vcproj
index b0480b6..316847c 100644
--- a/msvc/libopl.vcproj
+++ b/msvc/libopl.vcproj
@@ -159,10 +159,6 @@
 			UniqueIdentifier="{4FC737F1-C7A5-4376-A066-2A32D752A2FF}"
 			>
 			<File
-				RelativePath="..\opl\dbopl.c"
-				>
-			</File>
-			<File
 				RelativePath="..\opl\ioperm_sys.c"
 				>
 			</File>
@@ -194,6 +190,10 @@
 				RelativePath="..\opl\opl_win32.c"
 				>
 			</File>
+			<File
+				RelativePath="..\opl\opl3.c"
+				>
+			</File>
 		</Filter>
 		<Filter
 			Name="Header Files"
@@ -224,6 +224,10 @@
 				RelativePath="..\opl\opl_timer.h"
 				>
 			</File>
+			<File
+				RelativePath="..\opl\opl3.h"
+				>
+			</File>
 		</Filter>
 		<Filter
 			Name="Resource Files"
diff --git a/msvc/strife.vcproj b/msvc/strife.vcproj
index ebb595f..a569f7b 100644
--- a/msvc/strife.vcproj
+++ b/msvc/strife.vcproj
@@ -380,6 +380,10 @@
 				>
 			</File>
 			<File
+				RelativePath="..\src\v_diskicon.h"
+				>
+			</File>
+			<File
 				RelativePath="..\src\v_patch.h"
 				>
 			</File>
@@ -820,6 +824,10 @@
 				>
 			</File>
 			<File
+				RelativePath="..\src\v_diskicon.c"
+				>
+			</File>
+			<File
 				RelativePath="..\src\v_video.c"
 				>
 			</File>
diff --git a/msvc/win32.rc b/msvc/win32.rc
index 171ddab..030d63a 100644
--- 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 @@ BEGIN
 		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", "fraggle at gmail.com"
 			VALUE "LegalCopyright", "GNU General Public License"
 			VALUE "ProductName", "Chocolate Doom"
-			VALUE "ProductVersion", "2.2.1"
+			VALUE "ProductVersion", "2.3.0"
 		END
 	END
 	BLOCK "VarFileInfo"
diff --git a/opl/Makefile.am b/opl/Makefile.am
index be1619d..b0ed22b 100644
--- a/opl/Makefile.am
+++ b/opl/Makefile.am
@@ -15,5 +15,5 @@ libopl_a_SOURCES =                                \
         opl_timer.c         opl_timer.h           \
         opl_win32.c                               \
         ioperm_sys.c        ioperm_sys.h          \
-        dbopl.c             dbopl.h
+        opl3.c              opl3.h
 
diff --git a/opl/dbopl.c b/opl/dbopl.c
deleted file mode 100644
index bb7424f..0000000
--- a/opl/dbopl.c
+++ /dev/null
@@ -1,1638 +0,0 @@
-/*
- *  Copyright (C) 2002-2010  The DOSBox Team
- *
- *  This program is free software; you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation; either version 2 of the License, or
- *  (at your option) any later version.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- */
-
-//
-// Chocolate Doom-related discussion:
-//
-// This is the DosBox OPL emulator code (src/hardware/dbopl.cpp) r3635,
-// converted to C.  The bulk of the work was done using the minus-minus
-// script in the Chocolate Doom SVN repository, then the result tweaked
-// by hand until working.
-//
-
-
-/*
-	DOSBox implementation of a combined Yamaha YMF262 and Yamaha YM3812 emulator.
-	Enabling the opl3 bit will switch the emulator to stereo opl3 output instead of regular mono opl2
-	Except for the table generation it's all integer math
-	Can choose different types of generators, using muls and bigger tables, try different ones for slower platforms
-	The generation was based on the MAME implementation but tried to have it use less memory and be faster in general
-	MAME uses much bigger envelope tables and this will be the biggest cause of it sounding different at times
-
-	//TODO Don't delay first operator 1 sample in opl3 mode
-	//TODO Maybe not use class method pointers but a regular function pointers with operator as first parameter
-	//TODO Fix panning for the Percussion channels, would any opl3 player use it and actually really change it though?
-	//TODO Check if having the same accuracy in all frequency multipliers sounds better or not
-
-	//DUNNO Keyon in 4op, switch to 2op without keyoff.
-*/
-
-/* $Id: dbopl.cpp,v 1.10 2009-06-10 19:54:51 harekiet Exp $ */
-
-
-#include <math.h>
-#include <stdlib.h>
-#include <string.h>
-//#include "dosbox.h"
-#include "dbopl.h"
-
-
-#define GCC_UNLIKELY(x) x
-
-#define TRUE 1
-#define FALSE 0
-
-#ifndef PI
-#define PI 3.14159265358979323846
-#endif
-
-#define OPLRATE		((double)(14318180.0 / 288.0))
-#define TREMOLO_TABLE 52
-
-//Try to use most precision for frequencies
-//Else try to keep different waves in synch
-//#define WAVE_PRECISION	1
-#ifndef WAVE_PRECISION
-//Wave bits available in the top of the 32bit range
-//Original adlib uses 10.10, we use 10.22
-#define WAVE_BITS	10
-#else
-//Need some extra bits at the top to have room for octaves and frequency multiplier
-//We support to 8 times lower rate
-//128 * 15 * 8 = 15350, 2^13.9, so need 14 bits
-#define WAVE_BITS	14
-#endif
-#define WAVE_SH		( 32 - WAVE_BITS )
-#define WAVE_MASK	( ( 1 << WAVE_SH ) - 1 )
-
-//Use the same accuracy as the waves
-#define LFO_SH ( WAVE_SH - 10 )
-//LFO is controlled by our tremolo 256 sample limit
-#define LFO_MAX ( 256 << ( LFO_SH ) )
-
-
-//Maximum amount of attenuation bits
-//Envelope goes to 511, 9 bits
-#if (DBOPL_WAVE == WAVE_TABLEMUL )
-//Uses the value directly
-#define ENV_BITS	( 9 )
-#else
-//Add 3 bits here for more accuracy and would have to be shifted up either way
-#define ENV_BITS	( 9 )
-#endif
-//Limits of the envelope with those bits and when the envelope goes silent
-#define ENV_MIN		0
-#define ENV_EXTRA	( ENV_BITS - 9 )
-#define ENV_MAX		( 511 << ENV_EXTRA )
-#define ENV_LIMIT	( ( 12 * 256) >> ( 3 - ENV_EXTRA ) )
-#define ENV_SILENT( _X_ ) ( (_X_) >= ENV_LIMIT )
-
-//Attack/decay/release rate counter shift
-#define RATE_SH		24
-#define RATE_MASK	( ( 1 << RATE_SH ) - 1 )
-//Has to fit within 16bit lookuptable
-#define MUL_SH		16
-
-//Check some ranges
-#if ENV_EXTRA > 3
-#error Too many envelope bits
-#endif
-
-static inline void Operator__SetState(Operator *self, Bit8u s );
-static inline Bit32u Chip__ForwardNoise(Chip *self);
-
-// C++'s template<> sure is useful sometimes.
-
-static Channel* Channel__BlockTemplate(Channel *self, Chip* chip,
-                                Bit32u samples, Bit32s* output,
-                                SynthMode mode );
-#define BLOCK_TEMPLATE(mode) \
-    static Channel* Channel__BlockTemplate_ ## mode(Channel *self, Chip* chip, \
-                                             Bit32u samples, Bit32s* output) \
-    { \
-       return Channel__BlockTemplate(self, chip, samples, output, mode); \
-    }
-
-BLOCK_TEMPLATE(sm2AM)
-BLOCK_TEMPLATE(sm2FM)
-BLOCK_TEMPLATE(sm3AM)
-BLOCK_TEMPLATE(sm3FM)
-BLOCK_TEMPLATE(sm3FMFM)
-BLOCK_TEMPLATE(sm3AMFM)
-BLOCK_TEMPLATE(sm3FMAM)
-BLOCK_TEMPLATE(sm3AMAM)
-BLOCK_TEMPLATE(sm2Percussion)
-BLOCK_TEMPLATE(sm3Percussion)
-
-//How much to substract from the base value for the final attenuation
-static const Bit8u KslCreateTable[16] = {
-	//0 will always be be lower than 7 * 8
-	64, 32, 24, 19, 
-	16, 12, 11, 10, 
-	 8,  6,  5,  4,
-	 3,  2,  1,  0,
-};
-
-#define M(_X_) ((Bit8u)( (_X_) * 2))
-static const Bit8u FreqCreateTable[16] = {
-	M(0.5), M(1 ), M(2 ), M(3 ), M(4 ), M(5 ), M(6 ), M(7 ),
-	M(8  ), M(9 ), M(10), M(10), M(12), M(12), M(15), M(15)
-};
-#undef M
-
-//We're not including the highest attack rate, that gets a special value
-static const Bit8u AttackSamplesTable[13] = {
-	69, 55, 46, 40,
-	35, 29, 23, 20,
-	19, 15, 11, 10,
-	9
-};
-//On a real opl these values take 8 samples to reach and are based upon larger tables
-static const Bit8u EnvelopeIncreaseTable[13] = {
-	4,  5,  6,  7,
-	8, 10, 12, 14,
-	16, 20, 24, 28,
-	32, 
-};
-
-#if ( DBOPL_WAVE == WAVE_HANDLER ) || ( DBOPL_WAVE == WAVE_TABLELOG )
-static Bit16u ExpTable[ 256 ];
-#endif
-
-#if ( DBOPL_WAVE == WAVE_HANDLER )
-//PI table used by WAVEHANDLER
-static Bit16u SinTable[ 512 ];
-#endif
-
-#if ( DBOPL_WAVE > WAVE_HANDLER )
-//Layout of the waveform table in 512 entry intervals
-//With overlapping waves we reduce the table to half it's size
-
-//	|    |//\\|____|WAV7|//__|/\  |____|/\/\|
-//	|\\//|    |    |WAV7|    |  \/|    |    |
-//	|06  |0126|17  |7   |3   |4   |4 5 |5   |
-
-//6 is just 0 shifted and masked
-
-static Bit16s WaveTable[ 8 * 512 ];
-//Distance into WaveTable the wave starts
-static const Bit16u WaveBaseTable[8] = {
-	0x000, 0x200, 0x200, 0x800,
-	0xa00, 0xc00, 0x100, 0x400,
-
-};
-//Mask the counter with this
-static const Bit16u WaveMaskTable[8] = {
-	1023, 1023, 511, 511,
-	1023, 1023, 512, 1023,
-};
-
-//Where to start the counter on at keyon
-static const Bit16u WaveStartTable[8] = {
-	512, 0, 0, 0,
-	0, 512, 512, 256,
-};
-#endif
-
-#if ( DBOPL_WAVE == WAVE_TABLEMUL )
-static Bit16u MulTable[ 384 ];
-#endif
-
-static Bit8u KslTable[ 8 * 16 ];
-static Bit8u TremoloTable[ TREMOLO_TABLE ];
-//Start of a channel behind the chip struct start
-static Bit16u ChanOffsetTable[32];
-//Start of an operator behind the chip struct start
-static Bit16u OpOffsetTable[64];
-
-//The lower bits are the shift of the operator vibrato value
-//The highest bit is right shifted to generate -1 or 0 for negation
-//So taking the highest input value of 7 this gives 3, 7, 3, 0, -3, -7, -3, 0
-static const Bit8s VibratoTable[ 8 ] = {	
-	1 - 0x00, 0 - 0x00, 1 - 0x00, 30 - 0x00, 
-	1 - 0x80, 0 - 0x80, 1 - 0x80, 30 - 0x80 
-};
-
-//Shift strength for the ksl value determined by ksl strength
-static const Bit8u KslShiftTable[4] = {
-	31,1,2,0
-};
-
-//Generate a table index and table shift value using input value from a selected rate
-static void EnvelopeSelect( Bit8u val, Bit8u *index, Bit8u *shift ) {
-	if ( val < 13 * 4 ) {				//Rate 0 - 12
-		*shift = 12 - ( val >> 2 );
-		*index = val & 3;
-	} else if ( val < 15 * 4 ) {		//rate 13 - 14
-		*shift = 0;
-		*index = val - 12 * 4;
-	} else {							//rate 15 and up
-		*shift = 0;
-		*index = 12;
-	}
-}
-
-#if ( DBOPL_WAVE == WAVE_HANDLER )
-/*
-	Generate the different waveforms out of the sine/exponetial table using handlers
-*/
-static inline Bits MakeVolume( Bitu wave, Bitu volume ) {
-	Bitu total = wave + volume;
-	Bitu index = total & 0xff;
-	Bitu sig = ExpTable[ index ];
-	Bitu exp = total >> 8;
-#if 0
-	//Check if we overflow the 31 shift limit
-	if ( exp >= 32 ) {
-		LOG_MSG( "WTF %d %d", total, exp );
-	}
-#endif
-	return (sig >> exp);
-};
-
-static Bits DB_FASTCALL WaveForm0( Bitu i, Bitu volume ) {
-	Bits neg = 0 - (( i >> 9) & 1);//Create ~0 or 0
-	Bitu wave = SinTable[i & 511];
-	return (MakeVolume( wave, volume ) ^ neg) - neg;
-}
-static Bits DB_FASTCALL WaveForm1( Bitu i, Bitu volume ) {
-	Bit32u wave = SinTable[i & 511];
-	wave |= ( ( (i ^ 512 ) & 512) - 1) >> ( 32 - 12 );
-	return MakeVolume( wave, volume );
-}
-static Bits DB_FASTCALL WaveForm2( Bitu i, Bitu volume ) {
-	Bitu wave = SinTable[i & 511];
-	return MakeVolume( wave, volume );
-}
-static Bits DB_FASTCALL WaveForm3( Bitu i, Bitu volume ) {
-	Bitu wave = SinTable[i & 255];
-	wave |= ( ( (i ^ 256 ) & 256) - 1) >> ( 32 - 12 );
-	return MakeVolume( wave, volume );
-}
-static Bits DB_FASTCALL WaveForm4( Bitu i, Bitu volume ) {
-	//Twice as fast
-	i <<= 1;
-	Bits neg = 0 - (( i >> 9) & 1);//Create ~0 or 0
-	Bitu wave = SinTable[i & 511];
-	wave |= ( ( (i ^ 512 ) & 512) - 1) >> ( 32 - 12 );
-	return (MakeVolume( wave, volume ) ^ neg) - neg;
-}
-static Bits DB_FASTCALL WaveForm5( Bitu i, Bitu volume ) {
-	//Twice as fast
-	i <<= 1;
-	Bitu wave = SinTable[i & 511];
-	wave |= ( ( (i ^ 512 ) & 512) - 1) >> ( 32 - 12 );
-	return MakeVolume( wave, volume );
-}
-static Bits DB_FASTCALL WaveForm6( Bitu i, Bitu volume ) {
-	Bits neg = 0 - (( i >> 9) & 1);//Create ~0 or 0
-	return (MakeVolume( 0, volume ) ^ neg) - neg;
-}
-static Bits DB_FASTCALL WaveForm7( Bitu i, Bitu volume ) {
-	//Negative is reversed here
-	Bits neg = (( i >> 9) & 1) - 1;
-	Bitu wave = (i << 3);
-	//When negative the volume also runs backwards
-	wave = ((wave ^ neg) - neg) & 4095;
-	return (MakeVolume( wave, volume ) ^ neg) - neg;
-}
-
-static const WaveHandler WaveHandlerTable[8] = {
-	WaveForm0, WaveForm1, WaveForm2, WaveForm3,
-	WaveForm4, WaveForm5, WaveForm6, WaveForm7
-};
-
-#endif
-
-/*
-	Operator
-*/
-
-//We zero out when rate == 0
-static inline void Operator__UpdateAttack(Operator *self, const Chip* chip ) {
-	Bit8u rate = self->reg60 >> 4;
-	if ( rate ) {
-		Bit8u val = (rate << 2) + self->ksr;
-		self->attackAdd = chip->attackRates[ val ];
-		self->rateZero &= ~(1 << ATTACK);
-	} else {
-		self->attackAdd = 0;
-		self->rateZero |= (1 << ATTACK);
-	}
-}
-static inline void Operator__UpdateDecay(Operator *self, const Chip* chip ) {
-	Bit8u rate = self->reg60 & 0xf;
-	if ( rate ) {
-		Bit8u val = (rate << 2) + self->ksr;
-		self->decayAdd = chip->linearRates[ val ];
-		self->rateZero &= ~(1 << DECAY);
-	} else {
-		self->decayAdd = 0;
-		self->rateZero |= (1 << DECAY);
-	}
-}
-static inline void Operator__UpdateRelease(Operator *self, const Chip* chip ) {
-	Bit8u rate = self->reg80 & 0xf;
-	if ( rate ) {
-		Bit8u val = (rate << 2) + self->ksr;
-		self->releaseAdd = chip->linearRates[ val ];
-		self->rateZero &= ~(1 << RELEASE);
-		if ( !(self->reg20 & MASK_SUSTAIN ) ) {
-			self->rateZero &= ~( 1 << SUSTAIN );
-		}	
-	} else {
-		self->rateZero |= (1 << RELEASE);
-		self->releaseAdd = 0;
-		if ( !(self->reg20 & MASK_SUSTAIN ) ) {
-			self->rateZero |= ( 1 << SUSTAIN );
-		}	
-	}
-}
-
-static inline void Operator__UpdateAttenuation(Operator *self) {
-	Bit8u kslBase = (Bit8u)((self->chanData >> SHIFT_KSLBASE) & 0xff);
-	Bit32u tl = self->reg40 & 0x3f;
-	Bit8u kslShift = KslShiftTable[ self->reg40 >> 6 ];
-	//Make sure the attenuation goes to the right bits
-	self->totalLevel = tl << ( ENV_BITS - 7 );	//Total level goes 2 bits below max
-	self->totalLevel += ( kslBase << ENV_EXTRA ) >> kslShift;
-}
-
-static void Operator__UpdateFrequency(Operator *self) {
-	Bit32u freq = self->chanData & (( 1 << 10 ) - 1);
-	Bit32u block = (self->chanData >> 10) & 0xff;
-#ifdef WAVE_PRECISION
-	block = 7 - block;
-	self->waveAdd = ( freq * self->freqMul ) >> block;
-#else
-	self->waveAdd = ( freq << block ) * self->freqMul;
-#endif
-	if ( self->reg20 & MASK_VIBRATO ) {
-		self->vibStrength = (Bit8u)(freq >> 7);
-
-#ifdef WAVE_PRECISION
-		self->vibrato = ( self->vibStrength * self->freqMul ) >> block;
-#else
-		self->vibrato = ( self->vibStrength << block ) * self->freqMul;
-#endif
-	} else {
-		self->vibStrength = 0;
-		self->vibrato = 0;
-	}
-}
-
-static void Operator__UpdateRates(Operator *self, const Chip* chip ) {
-	//Mame seems to reverse this where enabling ksr actually lowers
-	//the rate, but pdf manuals says otherwise?
-	Bit8u newKsr = (Bit8u)((self->chanData >> SHIFT_KEYCODE) & 0xff);
-	if ( !( self->reg20 & MASK_KSR ) ) {
-		newKsr >>= 2;
-	}
-	if ( self->ksr == newKsr )
-		return;
-	self->ksr = newKsr;
-	Operator__UpdateAttack( self, chip );
-	Operator__UpdateDecay( self, chip );
-	Operator__UpdateRelease( self, chip );
-}
-
-static inline Bit32s Operator__RateForward(Operator *self, Bit32u add ) {
-	Bit32s ret; // haleyjd: GNUisms out!
-	self->rateIndex += add;
-	ret = self->rateIndex >> RATE_SH;
-	self->rateIndex = self->rateIndex & RATE_MASK;
-	return ret;
-}
-
-static Bits Operator__TemplateVolume(Operator *self, OperatorState yes) {
-	Bit32s vol = self->volume;
-	Bit32s change;
-	switch ( yes ) {
-	case OFF:
-		return ENV_MAX;
-	case ATTACK:
-		change = Operator__RateForward( self, self->attackAdd );
-		if ( !change )
-			return vol;
-		vol += ( (~vol) * change ) >> 3;
-		if ( vol < ENV_MIN ) {
-			self->volume = ENV_MIN;
-			self->rateIndex = 0;
-			Operator__SetState( self, DECAY );
-			return ENV_MIN;
-		}
-		break;
-	case DECAY:
-		vol += Operator__RateForward( self, self->decayAdd );
-		if ( GCC_UNLIKELY(vol >= self->sustainLevel) ) {
-			//Check if we didn't overshoot max attenuation, then just go off
-			if ( GCC_UNLIKELY(vol >= ENV_MAX) ) {
-				self->volume = ENV_MAX;
-				Operator__SetState( self, OFF );
-				return ENV_MAX;
-			}
-			//Continue as sustain
-			self->rateIndex = 0;
-			Operator__SetState( self, SUSTAIN );
-		}
-		break;
-	case SUSTAIN:
-		if ( self->reg20 & MASK_SUSTAIN ) {
-			return vol;
-		}
-		//In sustain phase, but not sustaining, do regular release
-	case RELEASE: 
-		vol += Operator__RateForward( self, self->releaseAdd );;
-		if ( GCC_UNLIKELY(vol >= ENV_MAX) ) {
-			self->volume = ENV_MAX;
-			Operator__SetState( self, OFF );
-			return ENV_MAX;
-		}
-		break;
-	}
-	self->volume = vol;
-	return vol;
-}
-
-#define TEMPLATE_VOLUME(mode) \
-    static Bits Operator__TemplateVolume ## mode(Operator *self) \
-    { \
-        return Operator__TemplateVolume(self, mode); \
-    }
-
-TEMPLATE_VOLUME(OFF)
-TEMPLATE_VOLUME(RELEASE)
-TEMPLATE_VOLUME(SUSTAIN)
-TEMPLATE_VOLUME(ATTACK)
-TEMPLATE_VOLUME(DECAY)
-
-static const VolumeHandler VolumeHandlerTable[5] = {
-        &Operator__TemplateVolumeOFF,
-        &Operator__TemplateVolumeRELEASE,
-        &Operator__TemplateVolumeSUSTAIN,
-        &Operator__TemplateVolumeDECAY,
-        &Operator__TemplateVolumeATTACK,
-};
-
-static inline Bitu Operator__ForwardVolume(Operator *self) {
-	return self->currentLevel + (self->volHandler)(self);
-}
-
-
-static inline Bitu Operator__ForwardWave(Operator *self) {
-	self->waveIndex += self->waveCurrent;	
-	return self->waveIndex >> WAVE_SH;
-}
-
-static void Operator__Write20(Operator *self, const Chip* chip, Bit8u val ) {
-	Bit8u change = (self->reg20 ^ val );
-	if ( !change ) 
-		return;
-	self->reg20 = val;
-	//Shift the tremolo bit over the entire register, saved a branch, YES!
-	self->tremoloMask = (Bit8s)(val) >> 7;
-	self->tremoloMask &= ~(( 1 << ENV_EXTRA ) -1);
-	//Update specific features based on changes
-	if ( change & MASK_KSR ) {
-		Operator__UpdateRates( self, chip );
-	}
-	//With sustain enable the volume doesn't change
-	if ( self->reg20 & MASK_SUSTAIN || ( !self->releaseAdd ) ) {
-		self->rateZero |= ( 1 << SUSTAIN );
-	} else {
-		self->rateZero &= ~( 1 << SUSTAIN );
-	}
-	//Frequency multiplier or vibrato changed
-	if ( change & (0xf | MASK_VIBRATO) ) {
-		self->freqMul = chip->freqMul[ val & 0xf ];
-		Operator__UpdateFrequency(self);
-	}
-}
-
-static void Operator__Write40(Operator *self, const Chip *chip, Bit8u val ) {
-	if (!(self->reg40 ^ val )) 
-		return;
-	self->reg40 = val;
-	Operator__UpdateAttenuation( self );
-}
-
-static void Operator__Write60(Operator *self, const Chip* chip, Bit8u val ) {
-	Bit8u change = self->reg60 ^ val;
-	self->reg60 = val;
-	if ( change & 0x0f ) {
-		Operator__UpdateDecay( self, chip );
-	}
-	if ( change & 0xf0 ) {
-		Operator__UpdateAttack( self, chip );
-	}
-}
-
-static void Operator__Write80(Operator *self, const Chip* chip, Bit8u val ) {
-	Bit8u change = (self->reg80 ^ val );
-	Bit8u sustain; // haleyjd 09/09/10: GNUisms out!
-	if ( !change ) 
-		return;
-	self->reg80 = val;
-	sustain = val >> 4;
-	//Turn 0xf into 0x1f
-	sustain |= ( sustain + 1) & 0x10;
-	self->sustainLevel = sustain << ( ENV_BITS - 5 );
-	if ( change & 0x0f ) {
-		Operator__UpdateRelease( self, chip );
-	}
-}
-
-static void Operator__WriteE0(Operator *self, const Chip* chip, Bit8u val ) {
-	Bit8u waveForm; // haleyjd 09/09/10: GNUisms out!
-	if ( !(self->regE0 ^ val) ) 
-		return;
-	//in opl3 mode you can always selet 7 waveforms regardless of waveformselect
-	waveForm = val & ( ( 0x3 & chip->waveFormMask ) | (0x7 & chip->opl3Active ) );
-	self->regE0 = val;
-#if( DBOPL_WAVE == WAVE_HANDLER )
-	self->waveHandler = WaveHandlerTable[ waveForm ];
-#else
-	self->waveBase = WaveTable + WaveBaseTable[ waveForm ];
-	self->waveStart = WaveStartTable[ waveForm ] << WAVE_SH;
-	self->waveMask = WaveMaskTable[ waveForm ];
-#endif
-}
-
-static inline void Operator__SetState(Operator *self, Bit8u s ) {
-	self->state = s;
-	self->volHandler = VolumeHandlerTable[ s ];
-}
-
-static inline int Operator__Silent(Operator *self) {
-	if ( !ENV_SILENT( self->totalLevel + self->volume ) )
-		return FALSE;
-	if ( !(self->rateZero & ( 1 << self->state ) ) )
-		return FALSE;
-	return TRUE;
-}
-
-static inline void Operator__Prepare(Operator *self, const Chip* chip )  {
-	self->currentLevel = self->totalLevel + (chip->tremoloValue & self->tremoloMask);
-	self->waveCurrent = self->waveAdd;
-	if ( self->vibStrength >> chip->vibratoShift ) {
-		Bit32s add = self->vibrato >> chip->vibratoShift;
-		//Sign extend over the shift value
-		Bit32s neg = chip->vibratoSign;
-		//Negate the add with -1 or 0
-		add = ( add ^ neg ) - neg; 
-		self->waveCurrent += add;
-	}
-}
-
-static void Operator__KeyOn(Operator *self, Bit8u mask ) {
-	if ( !self->keyOn ) {
-		//Restart the frequency generator
-#if( DBOPL_WAVE > WAVE_HANDLER )
-		self->waveIndex = self->waveStart;
-#else
-		self->waveIndex = 0;
-#endif
-		self->rateIndex = 0;
-		Operator__SetState( self, ATTACK );
-	}
-	self->keyOn |= mask;
-}
-
-static void Operator__KeyOff(Operator *self, Bit8u mask ) {
-	self->keyOn &= ~mask;
-	if ( !self->keyOn ) {
-		if ( self->state != OFF ) {
-			Operator__SetState( self, RELEASE );
-		}
-	}
-}
-
-static inline Bits Operator__GetWave(Operator *self, Bitu index, Bitu vol ) {
-#if( DBOPL_WAVE == WAVE_HANDLER )
-	return self->waveHandler( index, vol << ( 3 - ENV_EXTRA ) );
-#elif( DBOPL_WAVE == WAVE_TABLEMUL )
-	return(self->waveBase[ index & self->waveMask ] * MulTable[ vol >> ENV_EXTRA ]) >> MUL_SH;
-#elif( DBOPL_WAVE == WAVE_TABLELOG )
-	Bit32s wave = self->waveBase[ index & self->waveMask ];
-	Bit32u total = ( wave & 0x7fff ) + vol << ( 3 - ENV_EXTRA );
-	Bit32s sig = ExpTable[ total & 0xff ];
-	Bit32u exp = total >> 8;
-	Bit32s neg = wave >> 16;
-	return((sig ^ neg) - neg) >> exp;
-#else
-#error "No valid wave routine"
-#endif
-}
-
-static inline Bits Operator__GetSample(Operator *self, Bits modulation ) {
-	Bitu vol = Operator__ForwardVolume(self);
-	if ( ENV_SILENT( vol ) ) {
-		//Simply forward the wave
-		self->waveIndex += self->waveCurrent;
-		return 0;
-	} else {
-		Bitu index = Operator__ForwardWave(self);
-		index += modulation;
-		return Operator__GetWave( self, index, vol );
-	}
-}
-
-static void Operator__Operator(Operator *self) {
-	self->chanData = 0;
-	self->freqMul = 0;
-	self->waveIndex = 0;
-	self->waveAdd = 0;
-	self->waveCurrent = 0;
-	self->keyOn = 0;
-	self->ksr = 0;
-	self->reg20 = 0;
-	self->reg40 = 0;
-	self->reg60 = 0;
-	self->reg80 = 0;
-	self->regE0 = 0;
-	Operator__SetState( self, OFF );
-	self->rateZero = (1 << OFF);
-	self->sustainLevel = ENV_MAX;
-	self->currentLevel = ENV_MAX;
-	self->totalLevel = ENV_MAX;
-	self->volume = ENV_MAX;
-	self->releaseAdd = 0;
-}
-
-/*
-	Channel
-*/
-
-static void Channel__Channel(Channel *self) {
-        Operator__Operator(&self->op[0]);
-        Operator__Operator(&self->op[1]);
-	self->old[0] = self->old[1] = 0;
-	self->chanData = 0;
-	self->regB0 = 0;
-	self->regC0 = 0;
-	self->maskLeft = -1;
-	self->maskRight = -1;
-	self->feedback = 31;
-	self->fourMask = 0;
-	self->synthHandler = Channel__BlockTemplate_sm2FM;
-};
-
-static inline Operator* Channel__Op( Channel *self, Bitu index ) {
-        return &( ( self + (index >> 1) )->op[ index & 1 ]);
-}
-
-static void Channel__SetChanData(Channel *self, const Chip* chip, Bit32u data ) {
-	Bit32u change = self->chanData ^ data;
-	self->chanData = data;
-	Channel__Op( self, 0 )->chanData = data;
-	Channel__Op( self, 1 )->chanData = data;
-	//Since a frequency update triggered this, always update frequency
-        Operator__UpdateFrequency(Channel__Op( self, 0 ));
-        Operator__UpdateFrequency(Channel__Op( self, 1 ));
-	if ( change & ( 0xff << SHIFT_KSLBASE ) ) {
-                Operator__UpdateAttenuation(Channel__Op( self, 0 ));
-                Operator__UpdateAttenuation(Channel__Op( self, 1 ));
-	}
-	if ( change & ( 0xff << SHIFT_KEYCODE ) ) {
-                Operator__UpdateRates(Channel__Op( self, 0 ), chip);
-                Operator__UpdateRates(Channel__Op( self, 1 ), chip);
-	}
-}
-
-static void Channel__UpdateFrequency(Channel *self, const Chip* chip, Bit8u fourOp ) {
-	//Extrace the frequency bits
-	Bit32u data = self->chanData & 0xffff;
-	Bit32u kslBase = KslTable[ data >> 6 ];
-	Bit32u keyCode = ( data & 0x1c00) >> 9;
-	if ( chip->reg08 & 0x40 ) {
-		keyCode |= ( data & 0x100)>>8;	/* notesel == 1 */
-	} else {
-		keyCode |= ( data & 0x200)>>9;	/* notesel == 0 */
-	}
-	//Add the keycode and ksl into the highest bits of chanData
-	data |= (keyCode << SHIFT_KEYCODE) | ( kslBase << SHIFT_KSLBASE );
-        Channel__SetChanData( self + 0, chip, data );
-	if ( fourOp & 0x3f ) {
-                Channel__SetChanData( self + 1, chip, data );
-	}
-}
-
-static void Channel__WriteA0(Channel *self, const Chip* chip, Bit8u val ) {
-	Bit8u fourOp = chip->reg104 & chip->opl3Active & self->fourMask;
-	Bit32u change; // haleyjd 09/09/10: GNUisms out!
-	//Don't handle writes to silent fourop channels
-	if ( fourOp > 0x80 )
-		return;
-	change = (self->chanData ^ val ) & 0xff;
-	if ( change ) {
-		self->chanData ^= change;
-		Channel__UpdateFrequency( self, chip, fourOp );
-	}
-}
-
-static void Channel__WriteB0(Channel *self, const Chip* chip, Bit8u val ) {
-	Bit8u fourOp = chip->reg104 & chip->opl3Active & self->fourMask;
-	Bitu change; // haleyjd 09/09/10: GNUisms out!
-	//Don't handle writes to silent fourop channels
-	if ( fourOp > 0x80 )
-		return;
-	change = (self->chanData ^ ( val << 8 ) ) & 0x1f00;
-	if ( change ) {
-		self->chanData ^= change;
-		Channel__UpdateFrequency( self, chip, fourOp );
-	}
-	//Check for a change in the keyon/off state
-	if ( !(( val ^ self->regB0) & 0x20))
-		return;
-	self->regB0 = val;
-	if ( val & 0x20 ) {
-                Operator__KeyOn( Channel__Op(self, 0), 0x1 );
-                Operator__KeyOn( Channel__Op(self, 1), 0x1 );
-		if ( fourOp & 0x3f ) {
-                        Operator__KeyOn( Channel__Op(self + 1, 0), 1 );
-                        Operator__KeyOn( Channel__Op(self + 1, 1), 1 );
-		}
-	} else {
-                Operator__KeyOff( Channel__Op(self, 0), 0x1 );
-                Operator__KeyOff( Channel__Op(self, 1), 0x1 );
-		if ( fourOp & 0x3f ) {
-                        Operator__KeyOff( Channel__Op(self + 1, 0), 1 );
-                        Operator__KeyOff( Channel__Op(self + 1, 1), 1 );
-		}
-	}
-}
-
-static void Channel__WriteC0(Channel *self, const Chip* chip, Bit8u val ) {
-	Bit8u change = val ^ self->regC0;
-	if ( !change )
-		return;
-	self->regC0 = val;
-	self->feedback = ( val >> 1 ) & 7;
-	if ( self->feedback ) {
-		//We shift the input to the right 10 bit wave index value
-		self->feedback = 9 - self->feedback;
-	} else {
-		self->feedback = 31;
-	}
-	//Select the new synth mode
-	if ( chip->opl3Active ) {
-		//4-op mode enabled for this channel
-		if ( (chip->reg104 & self->fourMask) & 0x3f ) {
-			Channel* chan0, *chan1;
-			Bit8u synth; // haleyjd 09/09/10: GNUisms out!
-			//Check if it's the 2nd channel in a 4-op
-			if ( !(self->fourMask & 0x80 ) ) {
-				chan0 = self;
-				chan1 = self + 1;
-			} else {
-				chan0 = self - 1;
-				chan1 = self;
-			}
-
-			synth = ( (chan0->regC0 & 1) << 0 )| (( chan1->regC0 & 1) << 1 );
-			switch ( synth ) {
-			case 0:
-				chan0->synthHandler = Channel__BlockTemplate_sm3FMFM;
-				break;
-			case 1:
-				chan0->synthHandler = Channel__BlockTemplate_sm3AMFM;
-				break;
-			case 2:
-				chan0->synthHandler = Channel__BlockTemplate_sm3FMAM ;
-				break;
-			case 3:
-				chan0->synthHandler = Channel__BlockTemplate_sm3AMAM ;
-				break;
-			}
-		//Disable updating percussion channels
-		} else if ((self->fourMask & 0x40) && ( chip->regBD & 0x20) ) {
-
-		//Regular dual op, am or fm
-		} else if ( val & 1 ) {
-			self->synthHandler = Channel__BlockTemplate_sm3AM;
-		} else {
-			self->synthHandler = Channel__BlockTemplate_sm3FM;
-		}
-		self->maskLeft = ( val & 0x10 ) ? -1 : 0;
-		self->maskRight = ( val & 0x20 ) ? -1 : 0;
-	//opl2 active
-	} else { 
-		//Disable updating percussion channels
-		if ( (self->fourMask & 0x40) && ( chip->regBD & 0x20 ) ) {
-
-		//Regular dual op, am or fm
-		} else if ( val & 1 ) {
-			self->synthHandler = Channel__BlockTemplate_sm2AM;
-		} else {
-			self->synthHandler = Channel__BlockTemplate_sm2FM;
-		}
-	}
-}
-
-static void Channel__ResetC0(Channel *self, const Chip* chip ) {
-	Bit8u val = self->regC0;
-	self->regC0 ^= 0xff;
-	Channel__WriteC0( self, chip, val );
-};
-
-static inline void Channel__GeneratePercussion(Channel *self, Chip* chip,
-                                               Bit32s* output, int opl3Mode ) {
-	Channel* chan = self;
-
-	//BassDrum
-	Bit32s mod = (Bit32u)((self->old[0] + self->old[1])) >> self->feedback;
-	Bit32s sample; // haleyjd 09/09/10
-	Bit32u noiseBit;
-	Bit32u c2;
-	Bit32u c5;
-	Bit32u phaseBit;
-	Bit32u hhVol;
-	Bit32u sdVol;
-	Bit32u tcVol;
-
-	self->old[0] = self->old[1];
-	self->old[1] = Operator__GetSample( Channel__Op(self, 0), mod ); 
-
-	//When bassdrum is in AM mode first operator is ignoed
-	if ( chan->regC0 & 1 ) {
-		mod = 0;
-	} else {
-		mod = self->old[0];
-	}
-	sample = Operator__GetSample( Channel__Op(self, 1), mod ); 
-
-	//Precalculate stuff used by other outputs
-	noiseBit = Chip__ForwardNoise(chip) & 0x1;
-	c2 = Operator__ForwardWave(Channel__Op(self, 2));
-	c5 = Operator__ForwardWave(Channel__Op(self, 5));
-	phaseBit = (((c2 & 0x88) ^ ((c2<<5) & 0x80)) | ((c5 ^ (c5<<2)) & 0x20)) ? 0x02 : 0x00;
-
-	//Hi-Hat
-	hhVol = Operator__ForwardVolume(Channel__Op(self, 2));
-	if ( !ENV_SILENT( hhVol ) ) {
-		Bit32u hhIndex = (phaseBit<<8) | (0x34 << ( phaseBit ^ (noiseBit << 1 )));
-		sample += Operator__GetWave( Channel__Op(self, 2), hhIndex, hhVol );
-	}
-	//Snare Drum
-	sdVol = Operator__ForwardVolume( Channel__Op(self, 3) );
-	if ( !ENV_SILENT( sdVol ) ) {
-		Bit32u sdIndex = ( 0x100 + (c2 & 0x100) ) ^ ( noiseBit << 8 );
-		sample += Operator__GetWave( Channel__Op(self, 3), sdIndex, sdVol );
-	}
-	//Tom-tom
-	sample += Operator__GetSample( Channel__Op(self, 4), 0 );
-
-	//Top-Cymbal
-	tcVol = Operator__ForwardVolume(Channel__Op(self, 5));
-	if ( !ENV_SILENT( tcVol ) ) {
-		Bit32u tcIndex = (1 + phaseBit) << 8;
-		sample += Operator__GetWave( Channel__Op(self, 5), tcIndex, tcVol );
-	}
-	sample <<= 1;
-	if ( opl3Mode ) {
-		output[0] += sample;
-		output[1] += sample;
-	} else {
-		output[0] += sample;
-	}
-}
-
-Channel* Channel__BlockTemplate(Channel *self, Chip* chip,
-                                Bit32u samples, Bit32s* output,
-                                SynthMode mode ) {
-        Bitu i;
-
-	switch( mode ) {
-	case sm2AM:
-	case sm3AM:
-		if ( Operator__Silent(Channel__Op(self, 0))
-                 && Operator__Silent(Channel__Op(self, 1))) {
-			self->old[0] = self->old[1] = 0;
-			return(self + 1);
-		}
-		break;
-	case sm2FM:
-	case sm3FM:
-		if ( Operator__Silent(Channel__Op(self, 1))) {
-			self->old[0] = self->old[1] = 0;
-			return (self + 1);
-		}
-		break;
-	case sm3FMFM:
-		if ( Operator__Silent(Channel__Op(self, 3))) {
-			self->old[0] = self->old[1] = 0;
-			return (self + 2);
-		}
-		break;
-	case sm3AMFM:
-		if ( Operator__Silent( Channel__Op(self, 0) )
-                 && Operator__Silent( Channel__Op(self, 3) )) {
-			self->old[0] = self->old[1] = 0;
-			return (self + 2);
-		}
-		break;
-	case sm3FMAM:
-		if ( Operator__Silent( Channel__Op(self, 1))
-                 && Operator__Silent( Channel__Op(self, 3))) {
-			self->old[0] = self->old[1] = 0;
-			return (self + 2);
-		}
-		break;
-	case sm3AMAM:
-		if ( Operator__Silent( Channel__Op(self, 0) )
-                 && Operator__Silent( Channel__Op(self, 2) )
-                 && Operator__Silent( Channel__Op(self, 3) )) {
-			self->old[0] = self->old[1] = 0;
-			return (self + 2);
-		}
-		break;
-
-        default:
-                abort();
-	}
-	//Init the operators with the the current vibrato and tremolo values
-        Operator__Prepare( Channel__Op( self, 0 ), chip );
-        Operator__Prepare( Channel__Op( self, 1 ), chip );
-	if ( mode > sm4Start ) {
-                Operator__Prepare( Channel__Op( self, 2 ), chip );
-                Operator__Prepare( Channel__Op( self, 3 ), chip );
-	}
-	if ( mode > sm6Start ) {
-                Operator__Prepare( Channel__Op( self, 4 ), chip );
-                Operator__Prepare( Channel__Op( self, 5 ), chip );
-	}
-	for ( i = 0; i < samples; i++ ) {
-		Bit32s mod; // haleyjd 09/09/10: GNUisms out!
-		Bit32s sample;
-		Bit32s out0;
-
-		//Early out for percussion handlers
-		if ( mode == sm2Percussion ) {
-			Channel__GeneratePercussion( self, chip, output + i, FALSE );
-			continue;	//Prevent some unitialized value bitching
-		} else if ( mode == sm3Percussion ) {
-			Channel__GeneratePercussion( self, chip, output + i * 2, TRUE );
-			continue;	//Prevent some unitialized value bitching
-		}
-
-		//Do unsigned shift so we can shift out all bits but still stay in 10 bit range otherwise
-		mod = (Bit32u)((self->old[0] + self->old[1])) >> self->feedback;
-		self->old[0] = self->old[1];
-		self->old[1] = Operator__GetSample( Channel__Op(self, 0), mod );
-		sample = 0;
-		out0 = self->old[0];
-		if ( mode == sm2AM || mode == sm3AM ) {
-			sample = out0 + Operator__GetSample( Channel__Op(self, 1), 0 );
-		} else if ( mode == sm2FM || mode == sm3FM ) {
-			sample = Operator__GetSample( Channel__Op(self, 1), out0 );
-		} else if ( mode == sm3FMFM ) {
-			Bits next = Operator__GetSample( Channel__Op(self, 1), out0 );
-			next = Operator__GetSample( Channel__Op(self, 2), next );
-			sample = Operator__GetSample( Channel__Op(self, 3), next );
-		} else if ( mode == sm3AMFM ) {
-			Bits next; // haleyjd 09/09/10: GNUisms out!
-			sample = out0;
-			next = Operator__GetSample( Channel__Op(self, 1), 0 );
-			next = Operator__GetSample( Channel__Op(self, 2), next );
-			sample += Operator__GetSample( Channel__Op(self, 3), next );
-		} else if ( mode == sm3FMAM ) {
-			Bits next; // haleyjd 09/09/10: GNUisms out!
-			sample = Operator__GetSample( Channel__Op(self, 1), out0 );
-			next = Operator__GetSample( Channel__Op(self, 2), 0 );
-			sample += Operator__GetSample( Channel__Op(self, 3), next );
-		} else if ( mode == sm3AMAM ) {
-			Bits next; // haleyjd 09/09/10: GNUisms out!
-			sample = out0;
-			next = Operator__GetSample( Channel__Op(self, 1), 0 );
-			sample += Operator__GetSample( Channel__Op(self, 2), next );
-			sample += Operator__GetSample( Channel__Op(self, 3), 0 );
-		}
-		switch( mode ) {
-		case sm2AM:
-		case sm2FM:
-			output[ i ] += sample;
-			break;
-		case sm3AM:
-		case sm3FM:
-		case sm3FMFM:
-		case sm3AMFM:
-		case sm3FMAM:
-		case sm3AMAM:
-			output[ i * 2 + 0 ] += sample & self->maskLeft;
-			output[ i * 2 + 1 ] += sample & self->maskRight;
-			break;
-                default:
-                        abort();
-		}
-	}
-	switch( mode ) {
-	case sm2AM:
-	case sm2FM:
-	case sm3AM:
-	case sm3FM:
-		return ( self + 1 );
-	case sm3FMFM:
-	case sm3AMFM:
-	case sm3FMAM:
-	case sm3AMAM:
-		return ( self + 2 );
-	case sm2Percussion:
-	case sm3Percussion:
-		return( self + 3 );
-        default:
-                abort();
-	}
-	return 0;
-}
-
-/*
-	Chip
-*/
-
-void Chip__Chip(Chip *self) {
-        int i;
-
-        for (i=0; i<18; ++i) {
-                Channel__Channel(&self->chan[i]);
-        }
-
-	self->reg08 = 0;
-	self->reg04 = 0;
-	self->regBD = 0;
-	self->reg104 = 0;
-	self->opl3Active = 0;
-}
-
-static inline Bit32u Chip__ForwardNoise(Chip *self) {
-	Bitu count;
-	self->noiseCounter += self->noiseAdd;
-	count = self->noiseCounter >> LFO_SH;
-	self->noiseCounter &= WAVE_MASK;
-	for ( ; count > 0; --count ) {
-		//Noise calculation from mame
-		self->noiseValue ^= ( 0x800302 ) & ( 0 - (self->noiseValue & 1 ) );
-		self->noiseValue >>= 1;
-	}
-	return self->noiseValue;
-}
-
-static inline Bit32u Chip__ForwardLFO(Chip *self, Bit32u samples ) {
-	Bit32u todo; // haleyjd 09/09/10: GNUisms out!!!!!!
-	Bit32u count;
-
-	//Current vibrato value, runs 4x slower than tremolo
-	self->vibratoSign = ( VibratoTable[ self->vibratoIndex >> 2] ) >> 7;
-	self->vibratoShift = ( VibratoTable[ self->vibratoIndex >> 2] & 7) + self->vibratoStrength; 
-	self->tremoloValue = TremoloTable[ self->tremoloIndex ] >> self->tremoloStrength;
-
-	//Check hom many samples there can be done before the value changes
-	todo = LFO_MAX - self->lfoCounter;
-	count = (todo + self->lfoAdd - 1) / self->lfoAdd;
-	if ( count > samples ) {
-		count = samples;
-		self->lfoCounter += count * self->lfoAdd;
-	} else {
-		self->lfoCounter += count * self->lfoAdd;
-		self->lfoCounter &= (LFO_MAX - 1);
-		//Maximum of 7 vibrato value * 4
-		self->vibratoIndex = ( self->vibratoIndex + 1 ) & 31;
-		//Clip tremolo to the the table size
-		if ( self->tremoloIndex + 1 < TREMOLO_TABLE  )
-			++self->tremoloIndex;
-		else
-			self->tremoloIndex = 0;
-	}
-	return count;
-}
-
-
-static void Chip__WriteBD(Chip *self, Bit8u val ) {
-	Bit8u change = self->regBD ^ val;
-	if ( !change )
-		return;
-	self->regBD = val;
-	//TODO could do this with shift and xor?
-	self->vibratoStrength = (val & 0x40) ? 0x00 : 0x01;
-	self->tremoloStrength = (val & 0x80) ? 0x00 : 0x02;
-	if ( val & 0x20 ) {
-		//Drum was just enabled, make sure channel 6 has the right synth
-		if ( change & 0x20 ) {
-			if ( self->opl3Active ) {
-				self->chan[6].synthHandler
-                                    = Channel__BlockTemplate_sm3Percussion; 
-			} else {
-				self->chan[6].synthHandler
-                                    = Channel__BlockTemplate_sm2Percussion;
-			}
-		}
-		//Bass Drum
-		if ( val & 0x10 ) {
-                        Operator__KeyOn( &self->chan[6].op[0], 0x2 );
-                        Operator__KeyOn( &self->chan[6].op[1], 0x2 );
-		} else {
-                        Operator__KeyOff( &self->chan[6].op[0], 0x2 );
-                        Operator__KeyOff( &self->chan[6].op[1], 0x2 );
-		}
-		//Hi-Hat
-		if ( val & 0x1 ) {
-                        Operator__KeyOn( &self->chan[7].op[0], 0x2 );
-		} else {
-                        Operator__KeyOff( &self->chan[7].op[0], 0x2 );
-		}
-		//Snare
-		if ( val & 0x8 ) {
-                        Operator__KeyOn( &self->chan[7].op[1], 0x2 );
-		} else {
-                        Operator__KeyOff( &self->chan[7].op[1], 0x2 );
-		}
-		//Tom-Tom
-		if ( val & 0x4 ) {
-                        Operator__KeyOn( &self->chan[8].op[0], 0x2 );
-		} else {
-                        Operator__KeyOff( &self->chan[8].op[0], 0x2 );
-		}
-		//Top Cymbal
-		if ( val & 0x2 ) {
-                        Operator__KeyOn( &self->chan[8].op[1], 0x2 );
-		} else {
-                        Operator__KeyOff( &self->chan[8].op[1], 0x2 );
-		}
-	//Toggle keyoffs when we turn off the percussion
-	} else if ( change & 0x20 ) {
-		//Trigger a reset to setup the original synth handler
-                Channel__ResetC0( &self->chan[6], self );
-                Operator__KeyOff( &self->chan[6].op[0], 0x2 );
-                Operator__KeyOff( &self->chan[6].op[1], 0x2 );
-                Operator__KeyOff( &self->chan[7].op[0], 0x2 );
-                Operator__KeyOff( &self->chan[7].op[1], 0x2 );
-                Operator__KeyOff( &self->chan[8].op[0], 0x2 );
-                Operator__KeyOff( &self->chan[8].op[1], 0x2 );
-	}
-}
-
-
-#define REGOP( _FUNC_ )															\
-	index = ( ( reg >> 3) & 0x20 ) | ( reg & 0x1f );								\
-	if ( OpOffsetTable[ index ] ) {													\
-		Operator* regOp = (Operator*)( ((char *)self ) + OpOffsetTable[ index ] );	\
-                Operator__ ## _FUNC_ (regOp, self, val); \
-	}
-
-#define REGCHAN( _FUNC_ )																\
-	index = ( ( reg >> 4) & 0x10 ) | ( reg & 0xf );										\
-	if ( ChanOffsetTable[ index ] ) {													\
-		Channel* regChan = (Channel*)( ((char *)self ) + ChanOffsetTable[ index ] );	\
-                Channel__ ## _FUNC_ (regChan, self, val); \
-	}
-
-void Chip__WriteReg(Chip *self, Bit32u reg, Bit8u val ) {
-	Bitu index;
-	switch ( (reg & 0xf0) >> 4 ) {
-	case 0x00 >> 4:
-		if ( reg == 0x01 ) {
-			self->waveFormMask = ( val & 0x20 ) ? 0x7 : 0x0; 
-		} else if ( reg == 0x104 ) {
-			//Only detect changes in lowest 6 bits
-			if ( !((self->reg104 ^ val) & 0x3f) )
-				return;
-			//Always keep the highest bit enabled, for checking > 0x80
-			self->reg104 = 0x80 | ( val & 0x3f );
-		} else if ( reg == 0x105 ) {
-                        int i;
-
-			//MAME says the real opl3 doesn't reset anything on opl3 disable/enable till the next write in another register
-			if ( !((self->opl3Active ^ val) & 1 ) )
-				return;
-			self->opl3Active = ( val & 1 ) ? 0xff : 0;
-			//Update the 0xc0 register for all channels to signal the switch to mono/stereo handlers
-			for ( i = 0; i < 18;i++ ) {
-                                Channel__ResetC0( &self->chan[i], self );
-			}
-		} else if ( reg == 0x08 ) {
-			self->reg08 = val;
-		}
-	case 0x10 >> 4:
-		break;
-	case 0x20 >> 4:
-	case 0x30 >> 4:
-		REGOP( Write20 );
-		break;
-	case 0x40 >> 4:
-	case 0x50 >> 4:
-		REGOP( Write40 );
-		break;
-	case 0x60 >> 4:
-	case 0x70 >> 4:
-		REGOP( Write60 );
-		break;
-	case 0x80 >> 4:
-	case 0x90 >> 4:
-		REGOP( Write80 );
-		break;
-	case 0xa0 >> 4:
-		REGCHAN( WriteA0 );
-		break;
-	case 0xb0 >> 4:
-		if ( reg == 0xbd ) {
-			Chip__WriteBD( self, val );
-		} else {
-			REGCHAN( WriteB0 );
-		}
-		break;
-	case 0xc0 >> 4:
-		REGCHAN( WriteC0 );
-	case 0xd0 >> 4:
-		break;
-	case 0xe0 >> 4:
-	case 0xf0 >> 4:
-		REGOP( WriteE0 );
-		break;
-	}
-}
-
-Bit32u Chip__WriteAddr(Chip *self, Bit32u port, Bit8u val ) {
-	switch ( port & 3 ) {
-	case 0:
-		return val;
-	case 2:
-		if ( self->opl3Active || (val == 0x05) )
-			return 0x100 | val;
-		else
-			return val;
-	}
-	return 0;
-}
-
-void Chip__GenerateBlock2(Chip *self, Bitu total, Bit32s* output ) {
-	while ( total > 0 ) {
-                Channel *ch;
-		int count;
-
-		Bit32u samples = Chip__ForwardLFO( self, total );
-		memset(output, 0, sizeof(Bit32s) * samples);
-		count = 0;
-		for ( ch = self->chan; ch < self->chan + 9; ) {
-			count++;
-			ch = (ch->synthHandler)( ch, self, samples, output );
-		}
-		total -= samples;
-		output += samples;
-	}
-}
-
-void Chip__GenerateBlock3(Chip *self, Bitu total, Bit32s* output  ) {
-	while ( total > 0 ) {
-                int count;
-                Channel *ch;
-
-		Bit32u samples = Chip__ForwardLFO( self, total );
-		memset(output, 0, sizeof(Bit32s) * samples *2);
-		count = 0;
-		for ( ch = self->chan; ch < self->chan + 18; ) {
-			count++;
-			ch = (ch->synthHandler)( ch, self, samples, output );
-		}
-		total -= samples;
-		output += samples * 2;
-	}
-}
-
-void Chip__Setup(Chip *self, Bit32u rate ) {
-	double original = OPLRATE;
-	Bit32u i;
-	Bit32u freqScale; // haleyjd 09/09/10: GNUisms out!
-//	double original = rate;
-	double scale = original / (double)rate;
-
-	//Noise counter is run at the same precision as general waves
-	self->noiseAdd = (Bit32u)( 0.5 + scale * ( 1 << LFO_SH ) );
-	self->noiseCounter = 0;
-	self->noiseValue = 1;	//Make sure it triggers the noise xor the first time
-	//The low frequency oscillation counter
-	//Every time his overflows vibrato and tremoloindex are increased
-	self->lfoAdd = (Bit32u)( 0.5 + scale * ( 1 << LFO_SH ) );
-	self->lfoCounter = 0;
-	self->vibratoIndex = 0;
-	self->tremoloIndex = 0;
-
-	//With higher octave this gets shifted up
-	//-1 since the freqCreateTable = *2
-#ifdef WAVE_PRECISION
-	double freqScale = ( 1 << 7 ) * scale * ( 1 << ( WAVE_SH - 1 - 10));
-	for ( i = 0; i < 16; i++ ) {
-		self->freqMul[i] = (Bit32u)( 0.5 + freqScale * FreqCreateTable[ i ] );
-	}
-#else
-	freqScale = (Bit32u)( 0.5 + scale * ( 1 << ( WAVE_SH - 1 - 10)));
-	for ( i = 0; i < 16; i++ ) {
-		self->freqMul[i] = freqScale * FreqCreateTable[ i ];
-	}
-#endif
-
-	//-3 since the real envelope takes 8 steps to reach the single value we supply
-	for ( i = 0; i < 76; i++ ) {
-		Bit8u index, shift;
-		EnvelopeSelect( i, &index, &shift );
-		self->linearRates[i] = (Bit32u)( scale * (EnvelopeIncreaseTable[ index ] << ( RATE_SH + ENV_EXTRA - shift - 3 )));
-	}
-	//Generate the best matching attack rate
-	for ( i = 0; i < 62; i++ ) {
-		Bit8u index, shift;
-		Bit32s original; // haleyjd 09/09/10: GNUisms out!
-		Bit32s guessAdd;
-		Bit32s bestAdd;
-		Bit32u bestDiff;
-		Bit32u passes;
-		EnvelopeSelect( i, &index, &shift );
-		//Original amount of samples the attack would take
-		original = (Bit32u)( (AttackSamplesTable[ index ] << shift) / scale);
-		 
-		guessAdd = (Bit32u)( scale * (EnvelopeIncreaseTable[ index ] << ( RATE_SH - shift - 3 )));
-		bestAdd = guessAdd;
-		bestDiff = 1 << 30;
-
-		for ( passes = 0; passes < 16; passes ++ ) {
-			Bit32s volume = ENV_MAX;
-			Bit32s samples = 0;
-			Bit32u count = 0;
-			Bit32s diff;
-			Bit32u lDiff;
-			while ( volume > 0 && samples < original * 2 ) {
-				Bit32s change; // haleyjd 09/09/10
-				count += guessAdd;
-				change = count >> RATE_SH;
-				count &= RATE_MASK;
-				if ( GCC_UNLIKELY(change) ) { // less than 1 % 
-					volume += ( ~volume * change ) >> 3;
-				}
-				samples++;
-
-			}
-			diff = original - samples;
-			lDiff = labs( diff );
-			//Init last on first pass
-			if ( lDiff < bestDiff ) {
-				bestDiff = lDiff;
-				bestAdd = guessAdd;
-				if ( !bestDiff )
-					break;
-			}
-			//Below our target
-			if ( diff < 0 ) {
-				//Better than the last time
-				Bit32s mul = ((original - diff) << 12) / original;
-				guessAdd = ((guessAdd * mul) >> 12);
-				guessAdd++;
-			} else if ( diff > 0 ) {
-				Bit32s mul = ((original - diff) << 12) / original;
-				guessAdd = (guessAdd * mul) >> 12;
-				guessAdd--;
-			}
-		}
-		self->attackRates[i] = bestAdd;
-	}
-	for ( i = 62; i < 76; i++ ) {
-		//This should provide instant volume maximizing
-		self->attackRates[i] = 8 << RATE_SH;
-	}
-	//Setup the channels with the correct four op flags
-	//Channels are accessed through a table so they appear linear here
-	self->chan[ 0].fourMask = 0x00 | ( 1 << 0 );
-	self->chan[ 1].fourMask = 0x80 | ( 1 << 0 );
-	self->chan[ 2].fourMask = 0x00 | ( 1 << 1 );
-	self->chan[ 3].fourMask = 0x80 | ( 1 << 1 );
-	self->chan[ 4].fourMask = 0x00 | ( 1 << 2 );
-	self->chan[ 5].fourMask = 0x80 | ( 1 << 2 );
-
-	self->chan[ 9].fourMask = 0x00 | ( 1 << 3 );
-	self->chan[10].fourMask = 0x80 | ( 1 << 3 );
-	self->chan[11].fourMask = 0x00 | ( 1 << 4 );
-	self->chan[12].fourMask = 0x80 | ( 1 << 4 );
-	self->chan[13].fourMask = 0x00 | ( 1 << 5 );
-	self->chan[14].fourMask = 0x80 | ( 1 << 5 );
-
-	//mark the percussion channels
-	self->chan[ 6].fourMask = 0x40;
-	self->chan[ 7].fourMask = 0x40;
-	self->chan[ 8].fourMask = 0x40;
-
-	//Clear Everything in opl3 mode
-	Chip__WriteReg( self, 0x105, 0x1 );
-	for ( i = 0; i < 512; i++ ) {
-		if ( i == 0x105 )
-			continue;
-		Chip__WriteReg( self, i, 0xff );
-		Chip__WriteReg( self, i, 0x0 );
-	}
-	Chip__WriteReg( self, 0x105, 0x0 );
-	//Clear everything in opl2 mode
-	for ( i = 0; i < 255; i++ ) {
-		Chip__WriteReg( self, i, 0xff );
-		Chip__WriteReg( self, i, 0x0 );
-	}
-}
-
-static int doneTables = FALSE;
-void DBOPL_InitTables( void ) {
-        int i, oct;
-
-	if ( doneTables )
-		return;
-	doneTables = TRUE;
-#if ( DBOPL_WAVE == WAVE_HANDLER ) || ( DBOPL_WAVE == WAVE_TABLELOG )
-	//Exponential volume table, same as the real adlib
-	for ( i = 0; i < 256; i++ ) {
-		//Save them in reverse
-		ExpTable[i] = (int)( 0.5 + ( pow(2.0, ( 255 - i) * ( 1.0 /256 ) )-1) * 1024 );
-		ExpTable[i] += 1024; //or remove the -1 oh well :)
-		//Preshift to the left once so the final volume can shift to the right
-		ExpTable[i] *= 2;
-	}
-#endif
-#if ( DBOPL_WAVE == WAVE_HANDLER )
-	//Add 0.5 for the trunc rounding of the integer cast
-	//Do a PI sinetable instead of the original 0.5 PI
-	for ( i = 0; i < 512; i++ ) {
-		SinTable[i] = (Bit16s)( 0.5 - log10( sin( (i + 0.5) * (PI / 512.0) ) ) / log10(2.0)*256 );
-	}
-#endif
-#if ( DBOPL_WAVE == WAVE_TABLEMUL )
-	//Multiplication based tables
-	for ( i = 0; i < 384; i++ ) {
-		int s = i * 8;
-		//TODO maybe keep some of the precision errors of the original table?
-		double val = ( 0.5 + ( pow(2.0, -1.0 + ( 255 - s) * ( 1.0 /256 ) )) * ( 1 << MUL_SH ));
-		MulTable[i] = (Bit16u)(val);
-	}
-
-	//Sine Wave Base
-	for ( i = 0; i < 512; i++ ) {
-		WaveTable[ 0x0200 + i ] = (Bit16s)(sin( (i + 0.5) * (PI / 512.0) ) * 4084);
-		WaveTable[ 0x0000 + i ] = -WaveTable[ 0x200 + i ];
-	}
-	//Exponential wave
-	for ( i = 0; i < 256; i++ ) {
-		WaveTable[ 0x700 + i ] = (Bit16s)( 0.5 + ( pow(2.0, -1.0 + ( 255 - i * 8) * ( 1.0 /256 ) ) ) * 4085 );
-		WaveTable[ 0x6ff - i ] = -WaveTable[ 0x700 + i ];
-	}
-#endif
-#if ( DBOPL_WAVE == WAVE_TABLELOG )
-	//Sine Wave Base
-	for ( i = 0; i < 512; i++ ) {
-		WaveTable[ 0x0200 + i ] = (Bit16s)( 0.5 - log10( sin( (i + 0.5) * (PI / 512.0) ) ) / log10(2.0)*256 );
-		WaveTable[ 0x0000 + i ] = ((Bit16s)0x8000) | WaveTable[ 0x200 + i];
-	}
-	//Exponential wave
-	for ( i = 0; i < 256; i++ ) {
-		WaveTable[ 0x700 + i ] = i * 8;
-		WaveTable[ 0x6ff - i ] = ((Bit16s)0x8000) | i * 8;
-	} 
-#endif
-
-	//	|    |//\\|____|WAV7|//__|/\  |____|/\/\|
-	//	|\\//|    |    |WAV7|    |  \/|    |    |
-	//	|06  |0126|27  |7   |3   |4   |4 5 |5   |
-
-#if (( DBOPL_WAVE == WAVE_TABLELOG ) || ( DBOPL_WAVE == WAVE_TABLEMUL ))
-	for ( i = 0; i < 256; i++ ) {
-		//Fill silence gaps
-		WaveTable[ 0x400 + i ] = WaveTable[0];
-		WaveTable[ 0x500 + i ] = WaveTable[0];
-		WaveTable[ 0x900 + i ] = WaveTable[0];
-		WaveTable[ 0xc00 + i ] = WaveTable[0];
-		WaveTable[ 0xd00 + i ] = WaveTable[0];
-		//Replicate sines in other pieces
-		WaveTable[ 0x800 + i ] = WaveTable[ 0x200 + i ];
-		//double speed sines
-		WaveTable[ 0xa00 + i ] = WaveTable[ 0x200 + i * 2 ];
-		WaveTable[ 0xb00 + i ] = WaveTable[ 0x000 + i * 2 ];
-		WaveTable[ 0xe00 + i ] = WaveTable[ 0x200 + i * 2 ];
-		WaveTable[ 0xf00 + i ] = WaveTable[ 0x200 + i * 2 ];
-	} 
-#endif
-
-	//Create the ksl table
-	for ( oct = 0; oct < 8; oct++ ) {
-		int base = oct * 8;
-		for ( i = 0; i < 16; i++ ) {
-			int val = base - KslCreateTable[i];
-			if ( val < 0 )
-				val = 0;
-			//*4 for the final range to match attenuation range
-			KslTable[ oct * 16 + i ] = val * 4;
-		}
-	}
-	//Create the Tremolo table, just increase and decrease a triangle wave
-	for ( i = 0; i < TREMOLO_TABLE / 2; i++ ) {
-		Bit8u val = i << ENV_EXTRA;
-		TremoloTable[i] = val;
-		TremoloTable[TREMOLO_TABLE - 1 - i] = val;
-	}
-	//Create a table with offsets of the channels from the start of the chip
-	{ // haleyjd 09/09/10: Driving me #$%^@ insane
-		Chip *chip = NULL;
-		for ( i = 0; i < 32; i++ ) {
-			Bitu index = i & 0xf;
-			Bitu blah; // haleyjd 09/09/10
-			if ( index >= 9 ) {
-				ChanOffsetTable[i] = 0;
-				continue;
-			}
-			//Make sure the four op channels follow eachother
-			if ( index < 6 ) {
-				index = (index % 3) * 2 + ( index / 3 );
-			}
-			//Add back the bits for highest ones
-			if ( i >= 16 )
-				index += 9;
-			blah = (Bitu) ( &(chip->chan[ index ]) );
-			ChanOffsetTable[i] = blah;
-		}
-		//Same for operators
-		for ( i = 0; i < 64; i++ ) {
-			Bitu chNum; // haleyjd 09/09/10
-			Bitu opNum;
-			Bitu blah;
-			Channel* chan = NULL;
-			if ( i % 8 >= 6 || ( (i / 8) % 4 == 3 ) ) {
-				OpOffsetTable[i] = 0;
-				continue;
-			}
-			chNum = (i / 8) * 3 + (i % 8) % 3;
-			//Make sure we use 16 and up for the 2nd range to match the chanoffset gap
-			if ( chNum >= 12 )
-				chNum += 16 - 12;
-			opNum = ( i % 8 ) / 3;
-			blah = (Bitu) ( &(chan->op[opNum]) );
-			OpOffsetTable[i] = ChanOffsetTable[ chNum ] + blah;
-		}
-#if 0
-		//Stupid checks if table's are correct
-		for ( Bitu i = 0; i < 18; i++ ) {
-			Bit32u find = (Bit16u)( &(chip->chan[ i ]) );
-			for ( Bitu c = 0; c < 32; c++ ) {
-				if ( ChanOffsetTable[c] == find ) {
-					find = 0;
-					break;
-				}
-			}
-			if ( find ) {
-				find = find;
-			}
-		}
-		for ( Bitu i = 0; i < 36; i++ ) {
-			Bit32u find = (Bit16u)( &(chip->chan[ i / 2 ].op[i % 2]) );
-			for ( Bitu c = 0; c < 64; c++ ) {
-				if ( OpOffsetTable[c] == find ) {
-					find = 0;
-					break;
-				}
-			}
-			if ( find ) {
-				find = find;
-			}
-		}
-#endif
-	}
-}
-
-/*
-
-Bit32u Handler::WriteAddr( Bit32u port, Bit8u val ) {
-	return chip.WriteAddr( port, val );
-
-}
-void Handler::WriteReg( Bit32u addr, Bit8u val ) {
-	chip.WriteReg( addr, val );
-}
-
-void Handler::Generate( MixerChannel* chan, Bitu samples ) {
-	Bit32s buffer[ 512 * 2 ];
-	if ( GCC_UNLIKELY(samples > 512) )
-		samples = 512;
-	if ( !chip.opl3Active ) {
-		chip.GenerateBlock2( samples, buffer );
-		chan->AddSamples_m32( samples, buffer );
-	} else {
-		chip.GenerateBlock3( samples, buffer );
-		chan->AddSamples_s32( samples, buffer );
-	}
-}
-
-void Handler::Init( Bitu rate ) {
-	InitTables();
-	chip.Setup( rate );
-}
-*/
-
diff --git a/opl/dbopl.h b/opl/dbopl.h
deleted file mode 100644
index a8b857b..0000000
--- a/opl/dbopl.h
+++ /dev/null
@@ -1,203 +0,0 @@
-/*
- *  Copyright (C) 2002-2010  The DOSBox Team
- *
- *  This program is free software; you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation; either version 2 of the License, or
- *  (at your option) any later version.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- */
-
-#include <inttypes.h>
-
-//Use 8 handlers based on a small logatirmic wavetabe and an exponential table for volume
-#define WAVE_HANDLER	10
-//Use a logarithmic wavetable with an exponential table for volume
-#define WAVE_TABLELOG	11
-//Use a linear wavetable with a multiply table for volume
-#define WAVE_TABLEMUL	12
-
-//Select the type of wave generator routine
-#define DBOPL_WAVE WAVE_TABLEMUL
-
-typedef struct _Chip Chip;
-typedef struct _Operator Operator;
-typedef struct _Channel Channel;
-
-typedef uintptr_t       Bitu;
-typedef intptr_t        Bits;
-typedef uint32_t        Bit32u;
-typedef int32_t         Bit32s;
-typedef uint16_t        Bit16u;
-typedef int16_t         Bit16s;
-typedef uint8_t         Bit8u;
-typedef int8_t          Bit8s;
-
-#if (DBOPL_WAVE == WAVE_HANDLER)
-typedef Bits ( DB_FASTCALL *WaveHandler) ( Bitu i, Bitu volume );
-#endif
-
-#define DB_FASTCALL
-
-typedef Bits (*VolumeHandler)(Operator *self);
-typedef Channel* (*SynthHandler)(Channel *self, Chip* chip, Bit32u samples, Bit32s* output );
-
-//Different synth modes that can generate blocks of data
-typedef enum {
-	sm2AM,
-	sm2FM,
-	sm3AM,
-	sm3FM,
-	sm4Start,
-	sm3FMFM,
-	sm3AMFM,
-	sm3FMAM,
-	sm3AMAM,
-	sm6Start,
-	sm2Percussion,
-	sm3Percussion,
-} SynthMode;
-
-//Shifts for the values contained in chandata variable
-enum {
-	SHIFT_KSLBASE = 16,
-	SHIFT_KEYCODE = 24,
-};
-
-//Masks for operator 20 values
-enum {
-        MASK_KSR = 0x10,
-        MASK_SUSTAIN = 0x20,
-        MASK_VIBRATO = 0x40,
-        MASK_TREMOLO = 0x80,
-};
-
-typedef enum {
-        OFF,
-        RELEASE,
-        SUSTAIN,
-        DECAY,
-        ATTACK,
-} OperatorState;
-
-struct _Operator {
-	VolumeHandler volHandler;
-
-#if (DBOPL_WAVE == WAVE_HANDLER)
-	WaveHandler waveHandler;	//Routine that generate a wave 
-#else
-	Bit16s* waveBase;
-	Bit32u waveMask;
-	Bit32u waveStart;
-#endif
-	Bit32u waveIndex;			//WAVE_BITS shifted counter of the frequency index
-	Bit32u waveAdd;				//The base frequency without vibrato
-	Bit32u waveCurrent;			//waveAdd + vibratao
-
-	Bit32u chanData;			//Frequency/octave and derived data coming from whatever channel controls this
-	Bit32u freqMul;				//Scale channel frequency with this, TODO maybe remove?
-	Bit32u vibrato;				//Scaled up vibrato strength
-	Bit32s sustainLevel;		//When stopping at sustain level stop here
-	Bit32s totalLevel;			//totalLevel is added to every generated volume
-	Bit32u currentLevel;		//totalLevel + tremolo
-	Bit32s volume;				//The currently active volume
-	
-	Bit32u attackAdd;			//Timers for the different states of the envelope
-	Bit32u decayAdd;
-	Bit32u releaseAdd;
-	Bit32u rateIndex;			//Current position of the evenlope
-
-	Bit8u rateZero;				//Bits for the different states of the envelope having no changes
-	Bit8u keyOn;				//Bitmask of different values that can generate keyon
-	//Registers, also used to check for changes
-	Bit8u reg20, reg40, reg60, reg80, regE0;
-	//Active part of the envelope we're in
-	Bit8u state;
-	//0xff when tremolo is enabled
-	Bit8u tremoloMask;
-	//Strength of the vibrato
-	Bit8u vibStrength;
-	//Keep track of the calculated KSR so we can check for changes
-	Bit8u ksr;
-};
-
-struct _Channel {
-	Operator op[2];
-	SynthHandler synthHandler;
-	Bit32u chanData;		//Frequency/octave and derived values
-	Bit32s old[2];			//Old data for feedback
-
-	Bit8u feedback;			//Feedback shift
-	Bit8u regB0;			//Register values to check for changes
-	Bit8u regC0;
-	//This should correspond with reg104, bit 6 indicates a Percussion channel, bit 7 indicates a silent channel
-	Bit8u fourMask;
-	Bit8s maskLeft;		//Sign extended values for both channel's panning
-	Bit8s maskRight;
-
-};
-
-struct _Chip {
-	//This is used as the base counter for vibrato and tremolo
-	Bit32u lfoCounter;
-	Bit32u lfoAdd;
-	
-
-	Bit32u noiseCounter;
-	Bit32u noiseAdd;
-	Bit32u noiseValue;
-
-	//Frequency scales for the different multiplications
-	Bit32u freqMul[16];
-	//Rates for decay and release for rate of this chip
-	Bit32u linearRates[76];
-	//Best match attack rates for the rate of this chip
-	Bit32u attackRates[76];
-
-	//18 channels with 2 operators each
-	Channel chan[18];
-
-	Bit8u reg104;
-	Bit8u reg08;
-	Bit8u reg04;
-	Bit8u regBD;
-	Bit8u vibratoIndex;
-	Bit8u tremoloIndex;
-	Bit8s vibratoSign;
-	Bit8u vibratoShift;
-	Bit8u tremoloValue;
-	Bit8u vibratoStrength;
-	Bit8u tremoloStrength;
-	//Mask for allowed wave forms
-	Bit8u waveFormMask;
-	//0 or -1 when enabled
-	Bit8s opl3Active;
-
-};
-
-/*
-struct Handler : public Adlib::Handler {
-	DBOPL::Chip chip;
-	virtual Bit32u WriteAddr( Bit32u port, Bit8u val );
-	virtual void WriteReg( Bit32u addr, Bit8u val );
-	virtual void Generate( MixerChannel* chan, Bitu samples );
-	virtual void Init( Bitu rate );
-};
-*/
-
-
-void Chip__Setup(Chip *self, Bit32u rate );
-void DBOPL_InitTables( void );
-void Chip__Chip(Chip *self);
-void Chip__WriteReg(Chip *self, Bit32u reg, Bit8u val );
-void Chip__GenerateBlock2(Chip *self, Bitu total, Bit32s* output );
-void Chip__GenerateBlock3(Chip *self, Bitu total, Bit32s* output );
-
-// haleyjd 09/09/10: Not standard C.
-#ifdef _MSC_VER
-#define inline __inline
-#endif
diff --git a/opl/opl.c b/opl/opl.c
index 60f027d..0d25689 100644
--- a/opl/opl.c
+++ b/opl/opl.c
@@ -27,7 +27,7 @@
 
 //#define OPL_DEBUG_TRACE
 
-#ifdef HAVE_IOPERM
+#if (defined(__i386__) || defined(__x86_64__)) && defined(HAVE_IOPERM)
 extern opl_driver_t opl_linux_driver;
 #endif
 #if defined(HAVE_LIBI386) || defined(HAVE_LIBAMD64)
@@ -40,7 +40,7 @@ extern opl_driver_t opl_sdl_driver;
 
 static opl_driver_t *drivers[] =
 {
-#ifdef HAVE_IOPERM
+#if (defined(__i386__) || defined(__x86_64__)) && defined(HAVE_IOPERM)
     &opl_linux_driver,
 #endif
 #if defined(HAVE_LIBI386) || defined(HAVE_LIBAMD64)
diff --git a/opl/opl3.c b/opl/opl3.c
new file mode 100644
index 0000000..f98100c
--- /dev/null
+++ b/opl/opl3.c
@@ -0,0 +1,1415 @@
+//
+// Copyright (C) 2013-2016 Alexey Khokholov (Nuke.YKT)
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+//
+//  Nuked OPL3 emulator.
+//  Thanks:
+//      MAME Development Team(Jarek Burczynski, Tatsuyuki Satoh):
+//          Feedback and Rhythm part calculation information.
+//      forums.submarine.org.uk(carbon14, opl3):
+//          Tremolo and phase generator calculation information.
+//      OPLx decapsulated(Matthew Gambrell, Olli Niemitalo):
+//          OPL2 ROMs.
+//
+// version: 1.7.4
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "opl3.h"
+
+#define RSM_FRAC    10
+
+// Channel types
+
+enum {
+    ch_2op = 0,
+    ch_4op = 1,
+    ch_4op2 = 2,
+    ch_drum = 3
+};
+
+// Envelope key types
+
+enum {
+    egk_norm = 0x01,
+    egk_drum = 0x02
+};
+
+
+//
+// logsin table
+//
+
+static const Bit16u logsinrom[256] = {
+    0x859, 0x6c3, 0x607, 0x58b, 0x52e, 0x4e4, 0x4a6, 0x471,
+    0x443, 0x41a, 0x3f5, 0x3d3, 0x3b5, 0x398, 0x37e, 0x365,
+    0x34e, 0x339, 0x324, 0x311, 0x2ff, 0x2ed, 0x2dc, 0x2cd,
+    0x2bd, 0x2af, 0x2a0, 0x293, 0x286, 0x279, 0x26d, 0x261,
+    0x256, 0x24b, 0x240, 0x236, 0x22c, 0x222, 0x218, 0x20f,
+    0x206, 0x1fd, 0x1f5, 0x1ec, 0x1e4, 0x1dc, 0x1d4, 0x1cd,
+    0x1c5, 0x1be, 0x1b7, 0x1b0, 0x1a9, 0x1a2, 0x19b, 0x195,
+    0x18f, 0x188, 0x182, 0x17c, 0x177, 0x171, 0x16b, 0x166,
+    0x160, 0x15b, 0x155, 0x150, 0x14b, 0x146, 0x141, 0x13c,
+    0x137, 0x133, 0x12e, 0x129, 0x125, 0x121, 0x11c, 0x118,
+    0x114, 0x10f, 0x10b, 0x107, 0x103, 0x0ff, 0x0fb, 0x0f8,
+    0x0f4, 0x0f0, 0x0ec, 0x0e9, 0x0e5, 0x0e2, 0x0de, 0x0db,
+    0x0d7, 0x0d4, 0x0d1, 0x0cd, 0x0ca, 0x0c7, 0x0c4, 0x0c1,
+    0x0be, 0x0bb, 0x0b8, 0x0b5, 0x0b2, 0x0af, 0x0ac, 0x0a9,
+    0x0a7, 0x0a4, 0x0a1, 0x09f, 0x09c, 0x099, 0x097, 0x094,
+    0x092, 0x08f, 0x08d, 0x08a, 0x088, 0x086, 0x083, 0x081,
+    0x07f, 0x07d, 0x07a, 0x078, 0x076, 0x074, 0x072, 0x070,
+    0x06e, 0x06c, 0x06a, 0x068, 0x066, 0x064, 0x062, 0x060,
+    0x05e, 0x05c, 0x05b, 0x059, 0x057, 0x055, 0x053, 0x052,
+    0x050, 0x04e, 0x04d, 0x04b, 0x04a, 0x048, 0x046, 0x045,
+    0x043, 0x042, 0x040, 0x03f, 0x03e, 0x03c, 0x03b, 0x039,
+    0x038, 0x037, 0x035, 0x034, 0x033, 0x031, 0x030, 0x02f,
+    0x02e, 0x02d, 0x02b, 0x02a, 0x029, 0x028, 0x027, 0x026,
+    0x025, 0x024, 0x023, 0x022, 0x021, 0x020, 0x01f, 0x01e,
+    0x01d, 0x01c, 0x01b, 0x01a, 0x019, 0x018, 0x017, 0x017,
+    0x016, 0x015, 0x014, 0x014, 0x013, 0x012, 0x011, 0x011,
+    0x010, 0x00f, 0x00f, 0x00e, 0x00d, 0x00d, 0x00c, 0x00c,
+    0x00b, 0x00a, 0x00a, 0x009, 0x009, 0x008, 0x008, 0x007,
+    0x007, 0x007, 0x006, 0x006, 0x005, 0x005, 0x005, 0x004,
+    0x004, 0x004, 0x003, 0x003, 0x003, 0x002, 0x002, 0x002,
+    0x002, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001,
+    0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000
+};
+
+//
+// exp table
+//
+
+static const Bit16u exprom[256] = {
+    0x000, 0x003, 0x006, 0x008, 0x00b, 0x00e, 0x011, 0x014,
+    0x016, 0x019, 0x01c, 0x01f, 0x022, 0x025, 0x028, 0x02a,
+    0x02d, 0x030, 0x033, 0x036, 0x039, 0x03c, 0x03f, 0x042,
+    0x045, 0x048, 0x04b, 0x04e, 0x051, 0x054, 0x057, 0x05a,
+    0x05d, 0x060, 0x063, 0x066, 0x069, 0x06c, 0x06f, 0x072,
+    0x075, 0x078, 0x07b, 0x07e, 0x082, 0x085, 0x088, 0x08b,
+    0x08e, 0x091, 0x094, 0x098, 0x09b, 0x09e, 0x0a1, 0x0a4,
+    0x0a8, 0x0ab, 0x0ae, 0x0b1, 0x0b5, 0x0b8, 0x0bb, 0x0be,
+    0x0c2, 0x0c5, 0x0c8, 0x0cc, 0x0cf, 0x0d2, 0x0d6, 0x0d9,
+    0x0dc, 0x0e0, 0x0e3, 0x0e7, 0x0ea, 0x0ed, 0x0f1, 0x0f4,
+    0x0f8, 0x0fb, 0x0ff, 0x102, 0x106, 0x109, 0x10c, 0x110,
+    0x114, 0x117, 0x11b, 0x11e, 0x122, 0x125, 0x129, 0x12c,
+    0x130, 0x134, 0x137, 0x13b, 0x13e, 0x142, 0x146, 0x149,
+    0x14d, 0x151, 0x154, 0x158, 0x15c, 0x160, 0x163, 0x167,
+    0x16b, 0x16f, 0x172, 0x176, 0x17a, 0x17e, 0x181, 0x185,
+    0x189, 0x18d, 0x191, 0x195, 0x199, 0x19c, 0x1a0, 0x1a4,
+    0x1a8, 0x1ac, 0x1b0, 0x1b4, 0x1b8, 0x1bc, 0x1c0, 0x1c4,
+    0x1c8, 0x1cc, 0x1d0, 0x1d4, 0x1d8, 0x1dc, 0x1e0, 0x1e4,
+    0x1e8, 0x1ec, 0x1f0, 0x1f5, 0x1f9, 0x1fd, 0x201, 0x205,
+    0x209, 0x20e, 0x212, 0x216, 0x21a, 0x21e, 0x223, 0x227,
+    0x22b, 0x230, 0x234, 0x238, 0x23c, 0x241, 0x245, 0x249,
+    0x24e, 0x252, 0x257, 0x25b, 0x25f, 0x264, 0x268, 0x26d,
+    0x271, 0x276, 0x27a, 0x27f, 0x283, 0x288, 0x28c, 0x291,
+    0x295, 0x29a, 0x29e, 0x2a3, 0x2a8, 0x2ac, 0x2b1, 0x2b5,
+    0x2ba, 0x2bf, 0x2c4, 0x2c8, 0x2cd, 0x2d2, 0x2d6, 0x2db,
+    0x2e0, 0x2e5, 0x2e9, 0x2ee, 0x2f3, 0x2f8, 0x2fd, 0x302,
+    0x306, 0x30b, 0x310, 0x315, 0x31a, 0x31f, 0x324, 0x329,
+    0x32e, 0x333, 0x338, 0x33d, 0x342, 0x347, 0x34c, 0x351,
+    0x356, 0x35b, 0x360, 0x365, 0x36a, 0x370, 0x375, 0x37a,
+    0x37f, 0x384, 0x38a, 0x38f, 0x394, 0x399, 0x39f, 0x3a4,
+    0x3a9, 0x3ae, 0x3b4, 0x3b9, 0x3bf, 0x3c4, 0x3c9, 0x3cf,
+    0x3d4, 0x3da, 0x3df, 0x3e4, 0x3ea, 0x3ef, 0x3f5, 0x3fa
+};
+
+//
+// freq mult table multiplied by 2
+//
+// 1/2, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 10, 12, 12, 15, 15
+//
+
+static const Bit8u mt[16] = {
+    1, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 20, 24, 24, 30, 30
+};
+
+//
+// ksl table
+//
+
+static const Bit8u kslrom[16] = {
+    0, 32, 40, 45, 48, 51, 53, 55, 56, 58, 59, 60, 61, 62, 63, 64
+};
+
+static const Bit8u kslshift[4] = {
+    8, 1, 2, 0
+};
+
+//
+// envelope generator constants
+//
+
+static const Bit8u eg_incstep[3][4][8] = {
+    {
+        { 0, 0, 0, 0, 0, 0, 0, 0 },
+        { 0, 0, 0, 0, 0, 0, 0, 0 },
+        { 0, 0, 0, 0, 0, 0, 0, 0 },
+        { 0, 0, 0, 0, 0, 0, 0, 0 }
+    },
+    {
+        { 0, 1, 0, 1, 0, 1, 0, 1 },
+        { 0, 1, 0, 1, 1, 1, 0, 1 },
+        { 0, 1, 1, 1, 0, 1, 1, 1 },
+        { 0, 1, 1, 1, 1, 1, 1, 1 }
+    },
+    {
+        { 1, 1, 1, 1, 1, 1, 1, 1 },
+        { 2, 2, 1, 1, 1, 1, 1, 1 },
+        { 2, 2, 1, 1, 2, 2, 1, 1 },
+        { 2, 2, 2, 2, 2, 2, 1, 1 }
+    }
+};
+
+static const Bit8u eg_incdesc[16] = {
+    0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2
+};
+
+static const Bit8s eg_incsh[16] = {
+    0, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 0, -1, -2
+};
+
+//
+// address decoding
+//
+
+static const Bit8s ad_slot[0x20] = {
+    0, 1, 2, 3, 4, 5, -1, -1, 6, 7, 8, 9, 10, 11, -1, -1,
+    12, 13, 14, 15, 16, 17, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1
+};
+
+static const Bit8u ch_slot[18] = {
+    0, 1, 2, 6, 7, 8, 12, 13, 14, 18, 19, 20, 24, 25, 26, 30, 31, 32
+};
+
+//
+// Envelope generator
+//
+
+typedef Bit16s(*envelope_sinfunc)(Bit16u phase, Bit16u envelope);
+typedef void(*envelope_genfunc)(opl3_slot *slott);
+
+static Bit16s OPL3_EnvelopeCalcExp(Bit32u level)
+{
+    if (level > 0x1fff)
+    {
+        level = 0x1fff;
+    }
+    return ((exprom[(level & 0xff) ^ 0xff] | 0x400) << 1) >> (level >> 8);
+}
+
+static Bit16s OPL3_EnvelopeCalcSin0(Bit16u phase, Bit16u envelope)
+{
+    Bit16u out = 0;
+    Bit16u neg = 0;
+    phase &= 0x3ff;
+    if (phase & 0x200)
+    {
+        neg = ~0;
+    }
+    if (phase & 0x100)
+    {
+        out = logsinrom[(phase & 0xff) ^ 0xff];
+    }
+    else
+    {
+        out = logsinrom[phase & 0xff];
+    }
+    return OPL3_EnvelopeCalcExp(out + (envelope << 3)) ^ neg;
+}
+
+static Bit16s OPL3_EnvelopeCalcSin1(Bit16u phase, Bit16u envelope)
+{
+    Bit16u out = 0;
+    phase &= 0x3ff;
+    if (phase & 0x200)
+    {
+        out = 0x1000;
+    }
+    else if (phase & 0x100)
+    {
+        out = logsinrom[(phase & 0xff) ^ 0xff];
+    }
+    else
+    {
+        out = logsinrom[phase & 0xff];
+    }
+    return OPL3_EnvelopeCalcExp(out + (envelope << 3));
+}
+
+static Bit16s OPL3_EnvelopeCalcSin2(Bit16u phase, Bit16u envelope)
+{
+    Bit16u out = 0;
+    phase &= 0x3ff;
+    if (phase & 0x100)
+    {
+        out = logsinrom[(phase & 0xff) ^ 0xff];
+    }
+    else
+    {
+        out = logsinrom[phase & 0xff];
+    }
+    return OPL3_EnvelopeCalcExp(out + (envelope << 3));
+}
+
+static Bit16s OPL3_EnvelopeCalcSin3(Bit16u phase, Bit16u envelope)
+{
+    Bit16u out = 0;
+    phase &= 0x3ff;
+    if (phase & 0x100)
+    {
+        out = 0x1000;
+    }
+    else
+    {
+        out = logsinrom[phase & 0xff];
+    }
+    return OPL3_EnvelopeCalcExp(out + (envelope << 3));
+}
+
+static Bit16s OPL3_EnvelopeCalcSin4(Bit16u phase, Bit16u envelope)
+{
+    Bit16u out = 0;
+    Bit16u neg = 0;
+    phase &= 0x3ff;
+    if ((phase & 0x300) == 0x100)
+    {
+        neg = ~0;
+    }
+    if (phase & 0x200)
+    {
+        out = 0x1000;
+    }
+    else if (phase & 0x80)
+    {
+        out = logsinrom[((phase ^ 0xff) << 1) & 0xff];
+    }
+    else
+    {
+        out = logsinrom[(phase << 1) & 0xff];
+    }
+    return OPL3_EnvelopeCalcExp(out + (envelope << 3)) ^ neg;
+}
+
+static Bit16s OPL3_EnvelopeCalcSin5(Bit16u phase, Bit16u envelope)
+{
+    Bit16u out = 0;
+    phase &= 0x3ff;
+    if (phase & 0x200)
+    {
+        out = 0x1000;
+    }
+    else if (phase & 0x80)
+    {
+        out = logsinrom[((phase ^ 0xff) << 1) & 0xff];
+    }
+    else
+    {
+        out = logsinrom[(phase << 1) & 0xff];
+    }
+    return OPL3_EnvelopeCalcExp(out + (envelope << 3));
+}
+
+static Bit16s OPL3_EnvelopeCalcSin6(Bit16u phase, Bit16u envelope)
+{
+    Bit16u neg = 0;
+    phase &= 0x3ff;
+    if (phase & 0x200)
+    {
+        neg = ~0;
+    }
+    return OPL3_EnvelopeCalcExp(envelope << 3) ^ neg;
+}
+
+static Bit16s OPL3_EnvelopeCalcSin7(Bit16u phase, Bit16u envelope)
+{
+    Bit16u out = 0;
+    Bit16u neg = 0;
+    phase &= 0x3ff;
+    if (phase & 0x200)
+    {
+        neg = ~0;
+        phase = (phase & 0x1ff) ^ 0x1ff;
+    }
+    out = phase << 3;
+    return OPL3_EnvelopeCalcExp(out + (envelope << 3)) ^ neg;
+}
+
+static const envelope_sinfunc envelope_sin[8] = {
+    OPL3_EnvelopeCalcSin0,
+    OPL3_EnvelopeCalcSin1,
+    OPL3_EnvelopeCalcSin2,
+    OPL3_EnvelopeCalcSin3,
+    OPL3_EnvelopeCalcSin4,
+    OPL3_EnvelopeCalcSin5,
+    OPL3_EnvelopeCalcSin6,
+    OPL3_EnvelopeCalcSin7
+};
+
+static void OPL3_EnvelopeGenOff(opl3_slot *slot);
+static void OPL3_EnvelopeGenAttack(opl3_slot *slot);
+static void OPL3_EnvelopeGenDecay(opl3_slot *slot);
+static void OPL3_EnvelopeGenSustain(opl3_slot *slot);
+static void OPL3_EnvelopeGenRelease(opl3_slot *slot);
+
+envelope_genfunc envelope_gen[5] = {
+    OPL3_EnvelopeGenOff,
+    OPL3_EnvelopeGenAttack,
+    OPL3_EnvelopeGenDecay,
+    OPL3_EnvelopeGenSustain,
+    OPL3_EnvelopeGenRelease
+};
+
+enum envelope_gen_num
+{
+    envelope_gen_num_off = 0,
+    envelope_gen_num_attack = 1,
+    envelope_gen_num_decay = 2,
+    envelope_gen_num_sustain = 3,
+    envelope_gen_num_release = 4
+};
+
+static Bit8u OPL3_EnvelopeCalcRate(opl3_slot *slot, Bit8u reg_rate)
+{
+    Bit8u rate;
+    if (reg_rate == 0x00)
+    {
+        return 0x00;
+    }
+    rate = (reg_rate << 2)
+         + (slot->reg_ksr ? slot->channel->ksv : (slot->channel->ksv >> 2));
+    if (rate > 0x3c)
+    {
+        rate = 0x3c;
+    }
+    return rate;
+}
+
+static void OPL3_EnvelopeUpdateKSL(opl3_slot *slot)
+{
+    Bit16s ksl = (kslrom[slot->channel->f_num >> 6] << 2)
+               - ((0x08 - slot->channel->block) << 5);
+    if (ksl < 0)
+    {
+        ksl = 0;
+    }
+    slot->eg_ksl = (Bit8u)ksl;
+}
+
+static void OPL3_EnvelopeUpdateRate(opl3_slot *slot)
+{
+    switch (slot->eg_gen)
+    {
+    case envelope_gen_num_off:
+    case envelope_gen_num_attack:
+        slot->eg_rate = OPL3_EnvelopeCalcRate(slot, slot->reg_ar);
+        break;
+    case envelope_gen_num_decay:
+        slot->eg_rate = OPL3_EnvelopeCalcRate(slot, slot->reg_dr);
+        break;
+    case envelope_gen_num_sustain:
+    case envelope_gen_num_release:
+        slot->eg_rate = OPL3_EnvelopeCalcRate(slot, slot->reg_rr);
+        break;
+    }
+}
+
+static void OPL3_EnvelopeGenOff(opl3_slot *slot)
+{
+    slot->eg_rout = 0x1ff;
+}
+
+static void OPL3_EnvelopeGenAttack(opl3_slot *slot)
+{
+    if (slot->eg_rout == 0x00)
+    {
+        slot->eg_gen = envelope_gen_num_decay;
+        OPL3_EnvelopeUpdateRate(slot);
+        return;
+    }
+    slot->eg_rout += ((~slot->eg_rout) * slot->eg_inc) >> 3;
+    if (slot->eg_rout < 0x00)
+    {
+        slot->eg_rout = 0x00;
+    }
+}
+
+static void OPL3_EnvelopeGenDecay(opl3_slot *slot)
+{
+    if (slot->eg_rout >= slot->reg_sl << 4)
+    {
+        slot->eg_gen = envelope_gen_num_sustain;
+        OPL3_EnvelopeUpdateRate(slot);
+        return;
+    }
+    slot->eg_rout += slot->eg_inc;
+}
+
+static void OPL3_EnvelopeGenSustain(opl3_slot *slot)
+{
+    if (!slot->reg_type)
+    {
+        OPL3_EnvelopeGenRelease(slot);
+    }
+}
+
+static void OPL3_EnvelopeGenRelease(opl3_slot *slot)
+{
+    if (slot->eg_rout >= 0x1ff)
+    {
+        slot->eg_gen = envelope_gen_num_off;
+        slot->eg_rout = 0x1ff;
+        OPL3_EnvelopeUpdateRate(slot);
+        return;
+    }
+    slot->eg_rout += slot->eg_inc;
+}
+
+static void OPL3_EnvelopeCalc(opl3_slot *slot)
+{
+    Bit8u rate_h, rate_l;
+    Bit8u inc = 0;
+    rate_h = slot->eg_rate >> 2;
+    rate_l = slot->eg_rate & 3;
+    if (eg_incsh[rate_h] > 0)
+    {
+        if ((slot->chip->timer & ((1 << eg_incsh[rate_h]) - 1)) == 0)
+        {
+            inc = eg_incstep[eg_incdesc[rate_h]][rate_l]
+                            [((slot->chip->timer)>> eg_incsh[rate_h]) & 0x07];
+        }
+    }
+    else
+    {
+        inc = eg_incstep[eg_incdesc[rate_h]][rate_l]
+                        [slot->chip->timer & 0x07] << (-eg_incsh[rate_h]);
+    }
+    slot->eg_inc = inc;
+    slot->eg_out = slot->eg_rout + (slot->reg_tl << 2)
+                 + (slot->eg_ksl >> kslshift[slot->reg_ksl]) + *slot->trem;
+    envelope_gen[slot->eg_gen](slot);
+}
+
+static void OPL3_EnvelopeKeyOn(opl3_slot *slot, Bit8u type)
+{
+    if (!slot->key)
+    {
+        slot->eg_gen = envelope_gen_num_attack;
+        OPL3_EnvelopeUpdateRate(slot);
+        if ((slot->eg_rate >> 2) == 0x0f)
+        {
+            slot->eg_gen = envelope_gen_num_decay;
+            OPL3_EnvelopeUpdateRate(slot);
+            slot->eg_rout = 0x00;
+        }
+        slot->pg_phase = 0x00;
+    }
+    slot->key |= type;
+}
+
+static void OPL3_EnvelopeKeyOff(opl3_slot *slot, Bit8u type)
+{
+    if (slot->key)
+    {
+        slot->key &= (~type);
+        if (!slot->key)
+        {
+            slot->eg_gen = envelope_gen_num_release;
+            OPL3_EnvelopeUpdateRate(slot);
+        }
+    }
+}
+
+//
+// Phase Generator
+//
+
+static void OPL3_PhaseGenerate(opl3_slot *slot)
+{
+    Bit16u f_num;
+    Bit32u basefreq;
+
+    f_num = slot->channel->f_num;
+    if (slot->reg_vib)
+    {
+        Bit8s range;
+        Bit8u vibpos;
+
+        range = (f_num >> 7) & 7;
+        vibpos = slot->chip->vibpos;
+
+        if (!(vibpos & 3))
+        {
+            range = 0;
+        }
+        else if (vibpos & 1)
+        {
+            range >>= 1;
+        }
+        range >>= slot->chip->vibshift;
+
+        if (vibpos & 4)
+        {
+            range = -range;
+        }
+        f_num += range;
+    }
+    basefreq = (f_num << slot->channel->block) >> 1;
+    slot->pg_phase += (basefreq * mt[slot->reg_mult]) >> 1;
+}
+
+//
+// Noise Generator
+//
+
+static void OPL3_NoiseGenerate(opl3_chip *chip)
+{
+    if (chip->noise & 0x01)
+    {
+        chip->noise ^= 0x800302;
+    }
+    chip->noise >>= 1;
+}
+
+//
+// Slot
+//
+
+static void OPL3_SlotWrite20(opl3_slot *slot, Bit8u data)
+{
+    if ((data >> 7) & 0x01)
+    {
+        slot->trem = &slot->chip->tremolo;
+    }
+    else
+    {
+        slot->trem = (Bit8u*)&slot->chip->zeromod;
+    }
+    slot->reg_vib = (data >> 6) & 0x01;
+    slot->reg_type = (data >> 5) & 0x01;
+    slot->reg_ksr = (data >> 4) & 0x01;
+    slot->reg_mult = data & 0x0f;
+    OPL3_EnvelopeUpdateRate(slot);
+}
+
+static void OPL3_SlotWrite40(opl3_slot *slot, Bit8u data)
+{
+    slot->reg_ksl = (data >> 6) & 0x03;
+    slot->reg_tl = data & 0x3f;
+    OPL3_EnvelopeUpdateKSL(slot);
+}
+
+static void OPL3_SlotWrite60(opl3_slot *slot, Bit8u data)
+{
+    slot->reg_ar = (data >> 4) & 0x0f;
+    slot->reg_dr = data & 0x0f;
+    OPL3_EnvelopeUpdateRate(slot);
+}
+
+static void OPL3_SlotWrite80(opl3_slot *slot, Bit8u data)
+{
+    slot->reg_sl = (data >> 4) & 0x0f;
+    if (slot->reg_sl == 0x0f)
+    {
+        slot->reg_sl = 0x1f;
+    }
+    slot->reg_rr = data & 0x0f;
+    OPL3_EnvelopeUpdateRate(slot);
+}
+
+static void OPL3_SlotWriteE0(opl3_slot *slot, Bit8u data)
+{
+    slot->reg_wf = data & 0x07;
+    if (slot->chip->newm == 0x00)
+    {
+        slot->reg_wf &= 0x03;
+    }
+}
+
+static void OPL3_SlotGeneratePhase(opl3_slot *slot, Bit16u phase)
+{
+    slot->out = envelope_sin[slot->reg_wf](phase, slot->eg_out);
+}
+
+static void OPL3_SlotGenerate(opl3_slot *slot)
+{
+    OPL3_SlotGeneratePhase(slot, (Bit16u)(slot->pg_phase >> 9) + *slot->mod);
+}
+
+static void OPL3_SlotGenerateZM(opl3_slot *slot)
+{
+    OPL3_SlotGeneratePhase(slot, (Bit16u)(slot->pg_phase >> 9));
+}
+
+static void OPL3_SlotCalcFB(opl3_slot *slot)
+{
+    if (slot->channel->fb != 0x00)
+    {
+        slot->fbmod = (slot->prout + slot->out) >> (0x09 - slot->channel->fb);
+    }
+    else
+    {
+        slot->fbmod = 0;
+    }
+    slot->prout = slot->out;
+}
+
+//
+// Channel
+//
+
+static void OPL3_ChannelSetupAlg(opl3_channel *channel);
+
+static void OPL3_ChannelUpdateRhythm(opl3_chip *chip, Bit8u data)
+{
+    opl3_channel *channel6;
+    opl3_channel *channel7;
+    opl3_channel *channel8;
+    Bit8u chnum;
+
+    chip->rhy = data & 0x3f;
+    if (chip->rhy & 0x20)
+    {
+        channel6 = &chip->channel[6];
+        channel7 = &chip->channel[7];
+        channel8 = &chip->channel[8];
+        channel6->out[0] = &channel6->slots[1]->out;
+        channel6->out[1] = &channel6->slots[1]->out;
+        channel6->out[2] = &chip->zeromod;
+        channel6->out[3] = &chip->zeromod;
+        channel7->out[0] = &channel7->slots[0]->out;
+        channel7->out[1] = &channel7->slots[0]->out;
+        channel7->out[2] = &channel7->slots[1]->out;
+        channel7->out[3] = &channel7->slots[1]->out;
+        channel8->out[0] = &channel8->slots[0]->out;
+        channel8->out[1] = &channel8->slots[0]->out;
+        channel8->out[2] = &channel8->slots[1]->out;
+        channel8->out[3] = &channel8->slots[1]->out;
+        for (chnum = 6; chnum < 9; chnum++)
+        {
+            chip->channel[chnum].chtype = ch_drum;
+        }
+        OPL3_ChannelSetupAlg(channel6);
+        //hh
+        if (chip->rhy & 0x01)
+        {
+            OPL3_EnvelopeKeyOn(channel7->slots[0], egk_drum);
+        }
+        else
+        {
+            OPL3_EnvelopeKeyOff(channel7->slots[0], egk_drum);
+        }
+        //tc
+        if (chip->rhy & 0x02)
+        {
+            OPL3_EnvelopeKeyOn(channel8->slots[1], egk_drum);
+        }
+        else
+        {
+            OPL3_EnvelopeKeyOff(channel8->slots[1], egk_drum);
+        }
+        //tom
+        if (chip->rhy & 0x04)
+        {
+            OPL3_EnvelopeKeyOn(channel8->slots[0], egk_drum);
+        }
+        else
+        {
+            OPL3_EnvelopeKeyOff(channel8->slots[0], egk_drum);
+        }
+        //sd
+        if (chip->rhy & 0x08)
+        {
+            OPL3_EnvelopeKeyOn(channel7->slots[1], egk_drum);
+        }
+        else
+        {
+            OPL3_EnvelopeKeyOff(channel7->slots[1], egk_drum);
+        }
+        //bd
+        if (chip->rhy & 0x10)
+        {
+            OPL3_EnvelopeKeyOn(channel6->slots[0], egk_drum);
+            OPL3_EnvelopeKeyOn(channel6->slots[1], egk_drum);
+        }
+        else
+        {
+            OPL3_EnvelopeKeyOff(channel6->slots[0], egk_drum);
+            OPL3_EnvelopeKeyOff(channel6->slots[1], egk_drum);
+        }
+    }
+    else
+    {
+        for (chnum = 6; chnum < 9; chnum++)
+        {
+            chip->channel[chnum].chtype = ch_2op;
+            OPL3_ChannelSetupAlg(&chip->channel[chnum]);
+            OPL3_EnvelopeKeyOff(chip->channel[chnum].slots[0], egk_drum);
+            OPL3_EnvelopeKeyOff(chip->channel[chnum].slots[1], egk_drum);
+        }
+    }
+}
+
+static void OPL3_ChannelWriteA0(opl3_channel *channel, Bit8u data)
+{
+    if (channel->chip->newm && channel->chtype == ch_4op2)
+    {
+        return;
+    }
+    channel->f_num = (channel->f_num & 0x300) | data;
+    channel->ksv = (channel->block << 1)
+                 | ((channel->f_num >> (0x09 - channel->chip->nts)) & 0x01);
+    OPL3_EnvelopeUpdateKSL(channel->slots[0]);
+    OPL3_EnvelopeUpdateKSL(channel->slots[1]);
+    OPL3_EnvelopeUpdateRate(channel->slots[0]);
+    OPL3_EnvelopeUpdateRate(channel->slots[1]);
+    if (channel->chip->newm && channel->chtype == ch_4op)
+    {
+        channel->pair->f_num = channel->f_num;
+        channel->pair->ksv = channel->ksv;
+        OPL3_EnvelopeUpdateKSL(channel->pair->slots[0]);
+        OPL3_EnvelopeUpdateKSL(channel->pair->slots[1]);
+        OPL3_EnvelopeUpdateRate(channel->pair->slots[0]);
+        OPL3_EnvelopeUpdateRate(channel->pair->slots[1]);
+    }
+}
+
+static void OPL3_ChannelWriteB0(opl3_channel *channel, Bit8u data)
+{
+    if (channel->chip->newm && channel->chtype == ch_4op2)
+    {
+        return;
+    }
+    channel->f_num = (channel->f_num & 0xff) | ((data & 0x03) << 8);
+    channel->block = (data >> 2) & 0x07;
+    channel->ksv = (channel->block << 1)
+                 | ((channel->f_num >> (0x09 - channel->chip->nts)) & 0x01);
+    OPL3_EnvelopeUpdateKSL(channel->slots[0]);
+    OPL3_EnvelopeUpdateKSL(channel->slots[1]);
+    OPL3_EnvelopeUpdateRate(channel->slots[0]);
+    OPL3_EnvelopeUpdateRate(channel->slots[1]);
+    if (channel->chip->newm && channel->chtype == ch_4op)
+    {
+        channel->pair->f_num = channel->f_num;
+        channel->pair->block = channel->block;
+        channel->pair->ksv = channel->ksv;
+        OPL3_EnvelopeUpdateKSL(channel->pair->slots[0]);
+        OPL3_EnvelopeUpdateKSL(channel->pair->slots[1]);
+        OPL3_EnvelopeUpdateRate(channel->pair->slots[0]);
+        OPL3_EnvelopeUpdateRate(channel->pair->slots[1]);
+    }
+}
+
+static void OPL3_ChannelSetupAlg(opl3_channel *channel)
+{
+    if (channel->chtype == ch_drum)
+    {
+        switch (channel->alg & 0x01)
+        {
+        case 0x00:
+            channel->slots[0]->mod = &channel->slots[0]->fbmod;
+            channel->slots[1]->mod = &channel->slots[0]->out;
+            break;
+        case 0x01:
+            channel->slots[0]->mod = &channel->slots[0]->fbmod;
+            channel->slots[1]->mod = &channel->chip->zeromod;
+            break;
+        }
+        return;
+    }
+    if (channel->alg & 0x08)
+    {
+        return;
+    }
+    if (channel->alg & 0x04)
+    {
+        channel->pair->out[0] = &channel->chip->zeromod;
+        channel->pair->out[1] = &channel->chip->zeromod;
+        channel->pair->out[2] = &channel->chip->zeromod;
+        channel->pair->out[3] = &channel->chip->zeromod;
+        switch (channel->alg & 0x03)
+        {
+        case 0x00:
+            channel->pair->slots[0]->mod = &channel->pair->slots[0]->fbmod;
+            channel->pair->slots[1]->mod = &channel->pair->slots[0]->out;
+            channel->slots[0]->mod = &channel->pair->slots[1]->out;
+            channel->slots[1]->mod = &channel->slots[0]->out;
+            channel->out[0] = &channel->slots[1]->out;
+            channel->out[1] = &channel->chip->zeromod;
+            channel->out[2] = &channel->chip->zeromod;
+            channel->out[3] = &channel->chip->zeromod;
+            break;
+        case 0x01:
+            channel->pair->slots[0]->mod = &channel->pair->slots[0]->fbmod;
+            channel->pair->slots[1]->mod = &channel->pair->slots[0]->out;
+            channel->slots[0]->mod = &channel->chip->zeromod;
+            channel->slots[1]->mod = &channel->slots[0]->out;
+            channel->out[0] = &channel->pair->slots[1]->out;
+            channel->out[1] = &channel->slots[1]->out;
+            channel->out[2] = &channel->chip->zeromod;
+            channel->out[3] = &channel->chip->zeromod;
+            break;
+        case 0x02:
+            channel->pair->slots[0]->mod = &channel->pair->slots[0]->fbmod;
+            channel->pair->slots[1]->mod = &channel->chip->zeromod;
+            channel->slots[0]->mod = &channel->pair->slots[1]->out;
+            channel->slots[1]->mod = &channel->slots[0]->out;
+            channel->out[0] = &channel->pair->slots[0]->out;
+            channel->out[1] = &channel->slots[1]->out;
+            channel->out[2] = &channel->chip->zeromod;
+            channel->out[3] = &channel->chip->zeromod;
+            break;
+        case 0x03:
+            channel->pair->slots[0]->mod = &channel->pair->slots[0]->fbmod;
+            channel->pair->slots[1]->mod = &channel->chip->zeromod;
+            channel->slots[0]->mod = &channel->pair->slots[1]->out;
+            channel->slots[1]->mod = &channel->chip->zeromod;
+            channel->out[0] = &channel->pair->slots[0]->out;
+            channel->out[1] = &channel->slots[0]->out;
+            channel->out[2] = &channel->slots[1]->out;
+            channel->out[3] = &channel->chip->zeromod;
+            break;
+        }
+    }
+    else
+    {
+        switch (channel->alg & 0x01)
+        {
+        case 0x00:
+            channel->slots[0]->mod = &channel->slots[0]->fbmod;
+            channel->slots[1]->mod = &channel->slots[0]->out;
+            channel->out[0] = &channel->slots[1]->out;
+            channel->out[1] = &channel->chip->zeromod;
+            channel->out[2] = &channel->chip->zeromod;
+            channel->out[3] = &channel->chip->zeromod;
+            break;
+        case 0x01:
+            channel->slots[0]->mod = &channel->slots[0]->fbmod;
+            channel->slots[1]->mod = &channel->chip->zeromod;
+            channel->out[0] = &channel->slots[0]->out;
+            channel->out[1] = &channel->slots[1]->out;
+            channel->out[2] = &channel->chip->zeromod;
+            channel->out[3] = &channel->chip->zeromod;
+            break;
+        }
+    }
+}
+
+static void OPL3_ChannelWriteC0(opl3_channel *channel, Bit8u data)
+{
+    channel->fb = (data & 0x0e) >> 1;
+    channel->con = data & 0x01;
+    channel->alg = channel->con;
+    if (channel->chip->newm)
+    {
+        if (channel->chtype == ch_4op)
+        {
+            channel->pair->alg = 0x04 | (channel->con << 1) | (channel->pair->con);
+            channel->alg = 0x08;
+            OPL3_ChannelSetupAlg(channel->pair);
+        }
+        else if (channel->chtype == ch_4op2)
+        {
+            channel->alg = 0x04 | (channel->pair->con << 1) | (channel->con);
+            channel->pair->alg = 0x08;
+            OPL3_ChannelSetupAlg(channel);
+        }
+        else
+        {
+            OPL3_ChannelSetupAlg(channel);
+        }
+    }
+    else
+    {
+        OPL3_ChannelSetupAlg(channel);
+    }
+    if (channel->chip->newm)
+    {
+        channel->cha = ((data >> 4) & 0x01) ? ~0 : 0;
+        channel->chb = ((data >> 5) & 0x01) ? ~0 : 0;
+    }
+    else
+    {
+        channel->cha = channel->chb = ~0;
+    }
+}
+
+static void OPL3_ChannelKeyOn(opl3_channel *channel)
+{
+    if (channel->chip->newm)
+    {
+        if (channel->chtype == ch_4op)
+        {
+            OPL3_EnvelopeKeyOn(channel->slots[0], egk_norm);
+            OPL3_EnvelopeKeyOn(channel->slots[1], egk_norm);
+            OPL3_EnvelopeKeyOn(channel->pair->slots[0], egk_norm);
+            OPL3_EnvelopeKeyOn(channel->pair->slots[1], egk_norm);
+        }
+        else if (channel->chtype == ch_2op || channel->chtype == ch_drum)
+        {
+            OPL3_EnvelopeKeyOn(channel->slots[0], egk_norm);
+            OPL3_EnvelopeKeyOn(channel->slots[1], egk_norm);
+        }
+    }
+    else
+    {
+        OPL3_EnvelopeKeyOn(channel->slots[0], egk_norm);
+        OPL3_EnvelopeKeyOn(channel->slots[1], egk_norm);
+    }
+}
+
+static void OPL3_ChannelKeyOff(opl3_channel *channel)
+{
+    if (channel->chip->newm)
+    {
+        if (channel->chtype == ch_4op)
+        {
+            OPL3_EnvelopeKeyOff(channel->slots[0], egk_norm);
+            OPL3_EnvelopeKeyOff(channel->slots[1], egk_norm);
+            OPL3_EnvelopeKeyOff(channel->pair->slots[0], egk_norm);
+            OPL3_EnvelopeKeyOff(channel->pair->slots[1], egk_norm);
+        }
+        else if (channel->chtype == ch_2op || channel->chtype == ch_drum)
+        {
+            OPL3_EnvelopeKeyOff(channel->slots[0], egk_norm);
+            OPL3_EnvelopeKeyOff(channel->slots[1], egk_norm);
+        }
+    }
+    else
+    {
+        OPL3_EnvelopeKeyOff(channel->slots[0], egk_norm);
+        OPL3_EnvelopeKeyOff(channel->slots[1], egk_norm);
+    }
+}
+
+static void OPL3_ChannelSet4Op(opl3_chip *chip, Bit8u data)
+{
+    Bit8u bit;
+    Bit8u chnum;
+    for (bit = 0; bit < 6; bit++)
+    {
+        chnum = bit;
+        if (bit >= 3)
+        {
+            chnum += 9 - 3;
+        }
+        if ((data >> bit) & 0x01)
+        {
+            chip->channel[chnum].chtype = ch_4op;
+            chip->channel[chnum + 3].chtype = ch_4op2;
+        }
+        else
+        {
+            chip->channel[chnum].chtype = ch_2op;
+            chip->channel[chnum + 3].chtype = ch_2op;
+        }
+    }
+}
+
+static Bit16s OPL3_ClipSample(Bit32s sample)
+{
+    if (sample > 32767)
+    {
+        sample = 32767;
+    }
+    else if (sample < -32768)
+    {
+        sample = -32768;
+    }
+    return (Bit16s)sample;
+}
+
+static void OPL3_GenerateRhythm1(opl3_chip *chip)
+{
+    opl3_channel *channel6;
+    opl3_channel *channel7;
+    opl3_channel *channel8;
+    Bit16u phase14;
+    Bit16u phase17;
+    Bit16u phase;
+    Bit16u phasebit;
+
+    channel6 = &chip->channel[6];
+    channel7 = &chip->channel[7];
+    channel8 = &chip->channel[8];
+    OPL3_SlotGenerate(channel6->slots[0]);
+    phase14 = (channel7->slots[0]->pg_phase >> 9) & 0x3ff;
+    phase17 = (channel8->slots[1]->pg_phase >> 9) & 0x3ff;
+    phase = 0x00;
+    //hh tc phase bit
+    phasebit = ((phase14 & 0x08) | (((phase14 >> 5) ^ phase14) & 0x04)
+             | (((phase17 >> 2) ^ phase17) & 0x08)) ? 0x01 : 0x00;
+    //hh
+    phase = (phasebit << 9)
+          | (0x34 << ((phasebit ^ (chip->noise & 0x01)) << 1));
+    OPL3_SlotGeneratePhase(channel7->slots[0], phase);
+    //tt
+    OPL3_SlotGenerateZM(channel8->slots[0]);
+}
+
+static void OPL3_GenerateRhythm2(opl3_chip *chip)
+{
+    opl3_channel *channel6;
+    opl3_channel *channel7;
+    opl3_channel *channel8;
+    Bit16u phase14;
+    Bit16u phase17;
+    Bit16u phase;
+    Bit16u phasebit;
+
+    channel6 = &chip->channel[6];
+    channel7 = &chip->channel[7];
+    channel8 = &chip->channel[8];
+    OPL3_SlotGenerate(channel6->slots[1]);
+    phase14 = (channel7->slots[0]->pg_phase >> 9) & 0x3ff;
+    phase17 = (channel8->slots[1]->pg_phase >> 9) & 0x3ff;
+    phase = 0x00;
+    //hh tc phase bit
+    phasebit = ((phase14 & 0x08) | (((phase14 >> 5) ^ phase14) & 0x04)
+             | (((phase17 >> 2) ^ phase17) & 0x08)) ? 0x01 : 0x00;
+    //sd
+    phase = (0x100 << ((phase14 >> 8) & 0x01)) ^ ((chip->noise & 0x01) << 8);
+    OPL3_SlotGeneratePhase(channel7->slots[1], phase);
+    //tc
+    phase = 0x100 | (phasebit << 9);
+    OPL3_SlotGeneratePhase(channel8->slots[1], phase);
+}
+
+void OPL3_Generate(opl3_chip *chip, Bit16s *buf)
+{
+    Bit8u ii;
+    Bit8u jj;
+    Bit16s accm;
+
+    buf[1] = OPL3_ClipSample(chip->mixbuff[1]);
+
+    for (ii = 0; ii < 12; ii++)
+    {
+        OPL3_SlotCalcFB(&chip->slot[ii]);
+        OPL3_PhaseGenerate(&chip->slot[ii]);
+        OPL3_EnvelopeCalc(&chip->slot[ii]);
+        OPL3_SlotGenerate(&chip->slot[ii]);
+    }
+
+    for (ii = 12; ii < 15; ii++)
+    {
+        OPL3_SlotCalcFB(&chip->slot[ii]);
+        OPL3_PhaseGenerate(&chip->slot[ii]);
+        OPL3_EnvelopeCalc(&chip->slot[ii]);
+    }
+
+    if (chip->rhy & 0x20)
+    {
+        OPL3_GenerateRhythm1(chip);
+    }
+    else
+    {
+        OPL3_SlotGenerate(&chip->slot[12]);
+        OPL3_SlotGenerate(&chip->slot[13]);
+        OPL3_SlotGenerate(&chip->slot[14]);
+    }
+
+    chip->mixbuff[0] = 0;
+    for (ii = 0; ii < 18; ii++)
+    {
+        accm = 0;
+        for (jj = 0; jj < 4; jj++)
+        {
+            accm += *chip->channel[ii].out[jj];
+        }
+        chip->mixbuff[0] += (Bit16s)(accm & chip->channel[ii].cha);
+    }
+
+    for (ii = 15; ii < 18; ii++)
+    {
+        OPL3_SlotCalcFB(&chip->slot[ii]);
+        OPL3_PhaseGenerate(&chip->slot[ii]);
+        OPL3_EnvelopeCalc(&chip->slot[ii]);
+    }
+
+    if (chip->rhy & 0x20)
+    {
+        OPL3_GenerateRhythm2(chip);
+    }
+    else
+    {
+        OPL3_SlotGenerate(&chip->slot[15]);
+        OPL3_SlotGenerate(&chip->slot[16]);
+        OPL3_SlotGenerate(&chip->slot[17]);
+    }
+
+    buf[0] = OPL3_ClipSample(chip->mixbuff[0]);
+
+    for (ii = 18; ii < 33; ii++)
+    {
+        OPL3_SlotCalcFB(&chip->slot[ii]);
+        OPL3_PhaseGenerate(&chip->slot[ii]);
+        OPL3_EnvelopeCalc(&chip->slot[ii]);
+        OPL3_SlotGenerate(&chip->slot[ii]);
+    }
+
+    chip->mixbuff[1] = 0;
+    for (ii = 0; ii < 18; ii++)
+    {
+        accm = 0;
+        for (jj = 0; jj < 4; jj++)
+        {
+            accm += *chip->channel[ii].out[jj];
+        }
+        chip->mixbuff[1] += (Bit16s)(accm & chip->channel[ii].chb);
+    }
+
+    for (ii = 33; ii < 36; ii++)
+    {
+        OPL3_SlotCalcFB(&chip->slot[ii]);
+        OPL3_PhaseGenerate(&chip->slot[ii]);
+        OPL3_EnvelopeCalc(&chip->slot[ii]);
+        OPL3_SlotGenerate(&chip->slot[ii]);
+    }
+
+    OPL3_NoiseGenerate(chip);
+
+    if ((chip->timer & 0x3f) == 0x3f)
+    {
+        chip->tremolopos = (chip->tremolopos + 1) % 210;
+    }
+    if (chip->tremolopos < 105)
+    {
+        chip->tremolo = chip->tremolopos >> chip->tremoloshift;
+    }
+    else
+    {
+        chip->tremolo = (210 - chip->tremolopos) >> chip->tremoloshift;
+    }
+
+    if ((chip->timer & 0x3ff) == 0x3ff)
+    {
+        chip->vibpos = (chip->vibpos + 1) & 7;
+    }
+
+    chip->timer++;
+
+    while (chip->writebuf[chip->writebuf_cur].time <= chip->writebuf_samplecnt)
+    {
+        if (!(chip->writebuf[chip->writebuf_cur].reg & 0x200))
+        {
+            break;
+        }
+        chip->writebuf[chip->writebuf_cur].reg &= 0x1ff;
+        OPL3_WriteReg(chip, chip->writebuf[chip->writebuf_cur].reg,
+                      chip->writebuf[chip->writebuf_cur].data);
+        chip->writebuf_cur = (chip->writebuf_cur + 1) % OPL_WRITEBUF_SIZE;
+    }
+    chip->writebuf_samplecnt++;
+}
+
+void OPL3_GenerateResampled(opl3_chip *chip, Bit16s *buf)
+{
+    while (chip->samplecnt >= chip->rateratio)
+    {
+        chip->oldsamples[0] = chip->samples[0];
+        chip->oldsamples[1] = chip->samples[1];
+        OPL3_Generate(chip, chip->samples);
+        chip->samplecnt -= chip->rateratio;
+    }
+    buf[0] = (Bit16s)((chip->oldsamples[0] * (chip->rateratio - chip->samplecnt)
+                     + chip->samples[0] * chip->samplecnt) / chip->rateratio);
+    buf[1] = (Bit16s)((chip->oldsamples[1] * (chip->rateratio - chip->samplecnt)
+                     + chip->samples[1] * chip->samplecnt) / chip->rateratio);
+    chip->samplecnt += 1 << RSM_FRAC;
+}
+
+void OPL3_Reset(opl3_chip *chip, Bit32u samplerate)
+{
+    Bit8u slotnum;
+    Bit8u channum;
+
+    memset(chip, 0, sizeof(opl3_chip));
+    for (slotnum = 0; slotnum < 36; slotnum++)
+    {
+        chip->slot[slotnum].chip = chip;
+        chip->slot[slotnum].mod = &chip->zeromod;
+        chip->slot[slotnum].eg_rout = 0x1ff;
+        chip->slot[slotnum].eg_out = 0x1ff;
+        chip->slot[slotnum].eg_gen = envelope_gen_num_off;
+        chip->slot[slotnum].trem = (Bit8u*)&chip->zeromod;
+    }
+    for (channum = 0; channum < 18; channum++)
+    {
+        chip->channel[channum].slots[0] = &chip->slot[ch_slot[channum]];
+        chip->channel[channum].slots[1] = &chip->slot[ch_slot[channum] + 3];
+        chip->slot[ch_slot[channum]].channel = &chip->channel[channum];
+        chip->slot[ch_slot[channum] + 3].channel = &chip->channel[channum];
+        if ((channum % 9) < 3)
+        {
+            chip->channel[channum].pair = &chip->channel[channum + 3];
+        }
+        else if ((channum % 9) < 6)
+        {
+            chip->channel[channum].pair = &chip->channel[channum - 3];
+        }
+        chip->channel[channum].chip = chip;
+        chip->channel[channum].out[0] = &chip->zeromod;
+        chip->channel[channum].out[1] = &chip->zeromod;
+        chip->channel[channum].out[2] = &chip->zeromod;
+        chip->channel[channum].out[3] = &chip->zeromod;
+        chip->channel[channum].chtype = ch_2op;
+        chip->channel[channum].cha = ~0;
+        chip->channel[channum].chb = ~0;
+        OPL3_ChannelSetupAlg(&chip->channel[channum]);
+    }
+    chip->noise = 0x306600;
+    chip->rateratio = (samplerate << RSM_FRAC) / 49716;
+    chip->tremoloshift = 4;
+    chip->vibshift = 1;
+}
+
+void OPL3_WriteReg(opl3_chip *chip, Bit16u reg, Bit8u v)
+{
+    Bit8u high = (reg >> 8) & 0x01;
+    Bit8u regm = reg & 0xff;
+    switch (regm & 0xf0)
+    {
+    case 0x00:
+        if (high)
+        {
+            switch (regm & 0x0f)
+            {
+            case 0x04:
+                OPL3_ChannelSet4Op(chip, v);
+                break;
+            case 0x05:
+                chip->newm = v & 0x01;
+                break;
+            }
+        }
+        else
+        {
+            switch (regm & 0x0f)
+            {
+            case 0x08:
+                chip->nts = (v >> 6) & 0x01;
+                break;
+            }
+        }
+        break;
+    case 0x20:
+    case 0x30:
+        if (ad_slot[regm & 0x1f] >= 0)
+        {
+            OPL3_SlotWrite20(&chip->slot[18 * high + ad_slot[regm & 0x1f]], v);
+        }
+        break;
+    case 0x40:
+    case 0x50:
+        if (ad_slot[regm & 0x1f] >= 0)
+        {
+            OPL3_SlotWrite40(&chip->slot[18 * high + ad_slot[regm & 0x1f]], v);
+        }
+        break;
+    case 0x60:
+    case 0x70:
+        if (ad_slot[regm & 0x1f] >= 0)
+        {
+            OPL3_SlotWrite60(&chip->slot[18 * high + ad_slot[regm & 0x1f]], v);
+        }
+        break;
+    case 0x80:
+    case 0x90:
+        if (ad_slot[regm & 0x1f] >= 0)
+        {
+            OPL3_SlotWrite80(&chip->slot[18 * high + ad_slot[regm & 0x1f]], v);
+        }
+        break;
+    case 0xe0:
+    case 0xf0:
+        if (ad_slot[regm & 0x1f] >= 0)
+        {
+            OPL3_SlotWriteE0(&chip->slot[18 * high + ad_slot[regm & 0x1f]], v);
+        }
+        break;
+    case 0xa0:
+        if ((regm & 0x0f) < 9)
+        {
+            OPL3_ChannelWriteA0(&chip->channel[9 * high + (regm & 0x0f)], v);
+        }
+        break;
+    case 0xb0:
+        if (regm == 0xbd && !high)
+        {
+            chip->tremoloshift = (((v >> 7) ^ 1) << 1) + 2;
+            chip->vibshift = ((v >> 6) & 0x01) ^ 1;
+            OPL3_ChannelUpdateRhythm(chip, v);
+        }
+        else if ((regm & 0x0f) < 9)
+        {
+            OPL3_ChannelWriteB0(&chip->channel[9 * high + (regm & 0x0f)], v);
+            if (v & 0x20)
+            {
+                OPL3_ChannelKeyOn(&chip->channel[9 * high + (regm & 0x0f)]);
+            }
+            else
+            {
+                OPL3_ChannelKeyOff(&chip->channel[9 * high + (regm & 0x0f)]);
+            }
+        }
+        break;
+    case 0xc0:
+        if ((regm & 0x0f) < 9)
+        {
+            OPL3_ChannelWriteC0(&chip->channel[9 * high + (regm & 0x0f)], v);
+        }
+        break;
+    }
+}
+
+void OPL3_WriteRegBuffered(opl3_chip *chip, Bit16u reg, Bit8u v)
+{
+    Bit64u time1, time2;
+
+    if (chip->writebuf[chip->writebuf_last].reg & 0x200)
+    {
+        OPL3_WriteReg(chip, chip->writebuf[chip->writebuf_last].reg & 0x1ff,
+                      chip->writebuf[chip->writebuf_last].data);
+
+        chip->writebuf_cur = (chip->writebuf_last + 1) % OPL_WRITEBUF_SIZE;
+        chip->writebuf_samplecnt = chip->writebuf[chip->writebuf_last].time;
+    }
+
+    chip->writebuf[chip->writebuf_last].reg = reg | 0x200;
+    chip->writebuf[chip->writebuf_last].data = v;
+    time1 = chip->writebuf_lasttime + OPL_WRITEBUF_DELAY;
+    time2 = chip->writebuf_samplecnt;
+
+    if (time1 < time2)
+    {
+        time1 = time2;
+    }
+
+    chip->writebuf[chip->writebuf_last].time = time1;
+    chip->writebuf_lasttime = time1;
+    chip->writebuf_last = (chip->writebuf_last + 1) % OPL_WRITEBUF_SIZE;
+}
+
+void OPL3_GenerateStream(opl3_chip *chip, Bit16s *sndptr, Bit32u numsamples)
+{
+    Bit32u i;
+
+    for(i = 0; i < numsamples; i++)
+    {
+        OPL3_GenerateResampled(chip, sndptr);
+        sndptr += 2;
+    }
+}
diff --git a/opl/opl3.h b/opl/opl3.h
new file mode 100644
index 0000000..760dd15
--- /dev/null
+++ b/opl/opl3.h
@@ -0,0 +1,135 @@
+//
+// Copyright (C) 2013-2016 Alexey Khokholov (Nuke.YKT)
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+//
+//  Nuked OPL3 emulator.
+//  Thanks:
+//      MAME Development Team(Jarek Burczynski, Tatsuyuki Satoh):
+//          Feedback and Rhythm part calculation information.
+//      forums.submarine.org.uk(carbon14, opl3):
+//          Tremolo and phase generator calculation information.
+//      OPLx decapsulated(Matthew Gambrell, Olli Niemitalo):
+//          OPL2 ROMs.
+//
+// version: 1.7.4
+//
+
+#ifndef OPL_OPL3_H
+#define OPL_OPL3_H
+
+#include <inttypes.h>
+
+#define OPL_WRITEBUF_SIZE   1024
+#define OPL_WRITEBUF_DELAY  2
+
+typedef uintptr_t       Bitu;
+typedef intptr_t        Bits;
+typedef uint64_t        Bit64u;
+typedef int64_t         Bit64s;
+typedef uint32_t        Bit32u;
+typedef int32_t         Bit32s;
+typedef uint16_t        Bit16u;
+typedef int16_t         Bit16s;
+typedef uint8_t         Bit8u;
+typedef int8_t          Bit8s;
+
+typedef struct _opl3_slot opl3_slot;
+typedef struct _opl3_channel opl3_channel;
+typedef struct _opl3_chip opl3_chip;
+
+struct _opl3_slot {
+    opl3_channel *channel;
+    opl3_chip *chip;
+    Bit16s out;
+    Bit16s fbmod;
+    Bit16s *mod;
+    Bit16s prout;
+    Bit16s eg_rout;
+    Bit16s eg_out;
+    Bit8u eg_inc;
+    Bit8u eg_gen;
+    Bit8u eg_rate;
+    Bit8u eg_ksl;
+    Bit8u *trem;
+    Bit8u reg_vib;
+    Bit8u reg_type;
+    Bit8u reg_ksr;
+    Bit8u reg_mult;
+    Bit8u reg_ksl;
+    Bit8u reg_tl;
+    Bit8u reg_ar;
+    Bit8u reg_dr;
+    Bit8u reg_sl;
+    Bit8u reg_rr;
+    Bit8u reg_wf;
+    Bit8u key;
+    Bit32u pg_phase;
+    Bit32u timer;
+};
+
+struct _opl3_channel {
+    opl3_slot *slots[2];
+    opl3_channel *pair;
+    opl3_chip *chip;
+    Bit16s *out[4];
+    Bit8u chtype;
+    Bit16u f_num;
+    Bit8u block;
+    Bit8u fb;
+    Bit8u con;
+    Bit8u alg;
+    Bit8u ksv;
+    Bit16u cha, chb;
+};
+
+typedef struct _opl3_writebuf {
+    Bit64u time;
+    Bit16u reg;
+    Bit8u data;
+} opl3_writebuf;
+
+struct _opl3_chip {
+    opl3_channel channel[18];
+    opl3_slot slot[36];
+    Bit16u timer;
+    Bit8u newm;
+    Bit8u nts;
+    Bit8u rhy;
+    Bit8u vibpos;
+    Bit8u vibshift;
+    Bit8u tremolo;
+    Bit8u tremolopos;
+    Bit8u tremoloshift;
+    Bit32u noise;
+    Bit16s zeromod;
+    Bit32s mixbuff[2];
+    //OPL3L
+    Bit32s rateratio;
+    Bit32s samplecnt;
+    Bit16s oldsamples[2];
+    Bit16s samples[2];
+
+    Bit64u writebuf_samplecnt;
+    Bit32u writebuf_cur;
+    Bit32u writebuf_last;
+    Bit64u writebuf_lasttime;
+    opl3_writebuf writebuf[OPL_WRITEBUF_SIZE];
+};
+
+void OPL3_Generate(opl3_chip *chip, Bit16s *buf);
+void OPL3_GenerateResampled(opl3_chip *chip, Bit16s *buf);
+void OPL3_Reset(opl3_chip *chip, Bit32u samplerate);
+void OPL3_WriteReg(opl3_chip *chip, Bit16u reg, Bit8u v);
+void OPL3_WriteRegBuffered(opl3_chip *chip, Bit16u reg, Bit8u v);
+void OPL3_GenerateStream(opl3_chip *chip, Bit16s *sndptr, Bit32u numsamples);
+#endif
diff --git a/opl/opl_linux.c b/opl/opl_linux.c
index 5df5d46..19e4c3e 100644
--- a/opl/opl_linux.c
+++ b/opl/opl_linux.c
@@ -17,7 +17,7 @@
 
 #include "config.h"
 
-#ifdef HAVE_IOPERM
+#if (defined(__i386__) || defined(__x86_64__)) && defined(HAVE_IOPERM)
 
 #include <stdio.h>
 #include <string.h>
@@ -99,5 +99,5 @@ opl_driver_t opl_linux_driver =
     OPL_Timer_AdjustCallbacks,
 };
 
-#endif /* #ifdef HAVE_IOPERM */
+#endif /* #if (defined(__i386__) || defined(__x86_64__)) && defined(HAVE_IOPERM) */
 
diff --git a/opl/opl_queue.c b/opl/opl_queue.c
index 4b4e4e3..e948b64 100644
--- a/opl/opl_queue.c
+++ b/opl/opl_queue.c
@@ -210,7 +210,7 @@ void OPL_Queue_AdjustCallbacks(opl_callback_queue_t *queue,
     for (i = 0; i < queue->num_entries; ++i)
     {
         offset = queue->entries[i].time - time;
-        queue->entries[i].time = time + (uint64_t) (offset * factor);
+        queue->entries[i].time = time + (uint64_t) (offset / factor);
     }
 }
 
diff --git a/opl/opl_sdl.c b/opl/opl_sdl.c
index 54164f3..3905497 100644
--- a/opl/opl_sdl.c
+++ b/opl/opl_sdl.c
@@ -25,7 +25,7 @@
 #include "SDL.h"
 #include "SDL_mixer.h"
 
-#include "dbopl.h"
+#include "opl3.h"
 
 #include "opl.h"
 #include "opl_internal.h"
@@ -70,7 +70,7 @@ static uint64_t pause_offset;
 
 // OPL software emulator structure.
 
-static Chip opl_chip;
+static opl3_chip opl_chip;
 static int opl_opl3mode;
 
 // Temporary mixing buffer used by the mixing callback.
@@ -157,38 +157,13 @@ static void AdvanceTime(unsigned int nsamples)
 
 static void FillBuffer(int16_t *buffer, unsigned int nsamples)
 {
-    unsigned int i;
-
     // This seems like a reasonable assumption.  mix_buffer is
     // 1 second long, which should always be much longer than the
     // SDL mix buffer.
 
     assert(nsamples < mixing_freq);
 
-    if (opl_opl3mode)
-    {
-        Chip__GenerateBlock3(&opl_chip, nsamples, mix_buffer);
-
-        // Mix into the destination buffer, doubling up into stereo.
-
-        for (i=0; i<nsamples; ++i)
-        {
-            buffer[i * 2] = (int16_t) mix_buffer[i * 2];
-            buffer[i * 2 + 1] = (int16_t) mix_buffer[i * 2 + 1];
-        }
-    }
-    else
-    {
-        Chip__GenerateBlock2(&opl_chip, nsamples, mix_buffer);
-
-        // Mix into the destination buffer, doubling up into stereo.
-
-        for (i=0; i<nsamples; ++i)
-        {
-            buffer[i * 2] = (int16_t) mix_buffer[i];
-            buffer[i * 2 + 1] = (int16_t) mix_buffer[i];
-        }
-    }
+    OPL3_GenerateStream(&opl_chip, buffer, nsamples);
 }
 
 // Callback function to fill a new sound buffer:
@@ -371,9 +346,7 @@ static int OPL_SDL_Init(unsigned int port_base)
 
     // Create the emulator structure:
 
-    DBOPL_InitTables();
-    Chip__Chip(&opl_chip);
-    Chip__Setup(&opl_chip, mixing_freq);
+    OPL3_Reset(&opl_chip, mixing_freq);
     opl_opl3mode = 0;
 
     callback_mutex = SDL_CreateMutex();
@@ -465,7 +438,7 @@ static void WriteRegister(unsigned int reg_num, unsigned int value)
             opl_opl3mode = value & 0x01;
 
         default:
-            Chip__WriteReg(&opl_chip, reg_num, value);
+            OPL3_WriteRegBuffered(&opl_chip, reg_num, value);
             break;
     }
 }
diff --git a/pkg/config.make.in b/pkg/config.make.in
index edbc637..023b3e9 100644
--- a/pkg/config.make.in
+++ b/pkg/config.make.in
@@ -19,9 +19,8 @@ PACKAGE_VERSION = @PACKAGE_VERSION@
 
 # Documentation files to distribute with packages.
 
-DOC_FILES = README        \
-            README.Music  \
-            COPYING       \
-            ChangeLog     \
-            NEWS
+DOC_FILES = README.md        \
+            README.Music.md  \
+            COPYING          \
+            NEWS.md
 
diff --git a/pkg/osx/AppController.m b/pkg/osx/AppController.m
index b3c0324..4d682ee 100644
--- a/pkg/osx/AppController.m
+++ b/pkg/osx/AppController.m
@@ -101,6 +101,11 @@
         [self->launcherManager addFileToCommandLine: fileName
                                forArgument: @"-merge"];
     }
+    else if (![extension caseInsensitiveCompare: @"lmp"])
+    {
+        [self->launcherManager addFileToCommandLine: fileName
+                               forArgument: @"-playdemo"];
+    }
     else if (![extension caseInsensitiveCompare: @"deh"])
     {
         [self->launcherManager addFileToCommandLine: fileName
diff --git a/pkg/osx/Execute.m b/pkg/osx/Execute.m
index bc5d8c5..2d5d593 100644
--- a/pkg/osx/Execute.m
+++ b/pkg/osx/Execute.m
@@ -26,6 +26,7 @@
 
 #define RESPONSE_FILE "/tmp/launcher.rsp"
 #define TEMP_SCRIPT "/tmp/tempscript.sh"
+#define WINDOW_TITLE PACKAGE_STRING " command prompt"
 
 static char *executable_path;
 
@@ -183,6 +184,9 @@ void OpenTerminalWindow(const char *doomwadpath)
     fprintf(stream, "export DOOMWADPATH\n");
     fprintf(stream, "rm -f \"%s\"\n", TEMP_SCRIPT);
 
+    // Window title to something more interesting than "tempscript":
+    fprintf(stream, "echo -en \"\\033]0;%s\\a\"\n", WINDOW_TITLE);
+
     // Display a useful message:
 
     fprintf(stream, "clear\n");
diff --git a/pkg/osx/GNUmakefile b/pkg/osx/GNUmakefile
index f4ad51d..da7775c 100644
--- a/pkg/osx/GNUmakefile
+++ b/pkg/osx/GNUmakefile
@@ -6,11 +6,9 @@
 
 include ../config.make
 
-DOC_FILES += README.Strife NOT-BUGS
+DOC_FILES += README.Strife.md NOT-BUGS.md
 
-# Build so that the package will work on older versions.
-
-export MACOSX_DEPLOYMENT_TARGET=10.4
+export MACOSX_DEPLOYMENT_TARGET=10.7
 
 STAGING_DIR=staging
 DMG=$(PACKAGE_TARNAME)-$(PACKAGE_VERSION).dmg
@@ -73,6 +71,7 @@ $(STAGING_DIR): launcher $(TOPLEVEL_DOCS)
 	mkdir -p "$(APP_DOC_DIR)"
 	cp $(TOPLEVEL_DOCS) "$(APP_DOC_DIR)"
 
+	mv "$(APP_DOC_DIR)/README.md" "$(APP_DOC_DIR)/README"
 	ln -s "$(APP_DOC_RELDIR)/COPYING" "$(STAGING_DIR)/Software License"
 	ln -s "$(APP_DOC_RELDIR)/README" "$(STAGING_DIR)/README"
 	ln -s /Applications "$(STAGING_DIR)"
diff --git a/pkg/osx/Info.plist.in b/pkg/osx/Info.plist.in
index a648ed8..a9ba568 100644
--- a/pkg/osx/Info.plist.in
+++ b/pkg/osx/Info.plist.in
@@ -51,6 +51,18 @@ Licensed under the GNU GPL v2.</string>
                 </dict>
                 <dict>
                         <key>CFBundleTypeName</key>
+                        <string>Doom demo recording</string>
+                        <key>CFBundleTypeIconFile</key>
+                        <string>wadfile.icns</string>
+                        <key>CFBundleTypeRole</key>
+                        <string>Viewer</string>
+                        <key>CFBundleTypeExtensions</key>
+                        <array>
+                                <string>lmp</string>
+                        </array>
+                </dict>
+                <dict>
+                        <key>CFBundleTypeName</key>
                         <string>Doom Dehacked patch</string>
                         <key>CFBundleTypeIconFile</key>
                         <string>wadfile.icns</string>
diff --git a/pkg/win32/GNUmakefile b/pkg/win32/GNUmakefile
index c64baae..69f6bd1 100644
--- a/pkg/win32/GNUmakefile
+++ b/pkg/win32/GNUmakefile
@@ -27,12 +27,12 @@ $(STRIFE_ZIP): staging-strife hook-strife
 # Special hooks to custom modify files for particular games.
 
 hook-doom: staging-doom
-	cp $(TOPLEVEL)/NOT-BUGS $</NOT-BUGS.txt
+	cp $(TOPLEVEL)/NOT-BUGS.md $</NOT-BUGS.txt
 
 # Chocolate Strife has its own custom README file:
 
 hook-strife: staging-strife
-	cp $(TOPLEVEL)/README.Strife $</README.txt
+	cp $(TOPLEVEL)/README.Strife.md $</README.txt
 
 # Build up a staging dir for a particular game.
 
@@ -45,8 +45,8 @@ staging-%:
 	   $@/$(PROGRAM_PREFIX)$*-setup.exe
 	$(STRIP) $@/*.exe
 	
-	for f in $(DOC_FILES); do                    \
-		cp $(TOPLEVEL)/$$f $@/$$f.txt;       \
+	for f in $(DOC_FILES); do                                \
+		cp $(TOPLEVEL)/$$f $@/$$(basename $$f .md).txt;  \
 	done
 	cp $(TOPLEVEL)/man/CMDLINE.$* $@/CMDLINE.txt
 	
diff --git a/rpm.spec.in b/rpm.spec.in
index 6797601..8ceb3f0 100644
--- a/rpm.spec.in
+++ b/rpm.spec.in
@@ -3,7 +3,7 @@ Name: @PACKAGE@
 Summary: @PACKAGE_SHORTDESC@
 Version: @VERSION@
 Release: 1
-Source: http://www.chocolate-doom.org/downloads/@VERSION@/@PACKAGE@-@VERSION@.tar.gz
+Source: https://www.chocolate-doom.org/downloads/@VERSION@/@PACKAGE@-@VERSION@.tar.gz
 URL: @PACKAGE_URL@
 Group: Amusements/Games
 BuildRoot: /var/tmp/@PACKAGE at -buildroot
@@ -42,7 +42,7 @@ make
 rm -rf $RPM_BUILD_ROOT
 
 %description
-%(sed -n "/==/ q; p " < README)
+%(sed -n "/##/ q; p " < README.md)
 
 See @PACKAGE_URL@ for more information.
 
@@ -65,7 +65,7 @@ Requires: libSDL-1.2.so.0, libSDL_mixer-1.2.so.0, libSDL_net-1.2.so.0
 /usr/share/applications/*
 
 %description -n @PROGRAM_PREFIX at heretic
-%(sed -n "/==/ q; p " < README)
+%(sed -n "/##/ q; p " < README.md)
 
 These are the Heretic binaries.
 
@@ -85,7 +85,7 @@ Group: Amusements/Games
 Requires: libSDL-1.2.so.0, libSDL_mixer-1.2.so.0, libSDL_net-1.2.so.0
 
 %description -n @PROGRAM_PREFIX at hexen
-%(sed -n "/==/ q; p " < README)
+%(sed -n "/##/ q; p " < README.md)
 
 These are the Hexen binaries.
 
@@ -105,7 +105,7 @@ Group: Amusements/Games
 Requires: libSDL-1.2.so.0, libSDL_mixer-1.2.so.0, libSDL_net-1.2.so.0
 
 %description -n @PROGRAM_PREFIX at strife
-%(sed -n "/==/ q; p " < README)
+%(sed -n "/##/ q; p " < README.md)
 
 These are the Strife binaries.
 
diff --git a/src/.gitignore b/src/.gitignore
index 84a9eae..6986ecf 100644
--- a/src/.gitignore
+++ b/src/.gitignore
@@ -12,8 +12,10 @@ chocolate-heretic-setup
 chocolate-hexen-setup
 chocolate-strife-setup
 chocolate-setup
+*.cfg
 *.exe
 *.desktop
+*.txt
 *.appdata.xml
 tags
 TAGS
diff --git a/src/Makefile.am b/src/Makefile.am
index 78ee3ba..4aaa248 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -1,7 +1,7 @@
 
 SUBDIRS = doom heretic hexen strife setup
 
-execgamesdir = ${exec_prefix}/games
+execgamesdir = ${bindir}
 
 execgames_PROGRAMS = @PROGRAM_PREFIX at doom     \
                      @PROGRAM_PREFIX at heretic  \
@@ -79,6 +79,7 @@ m_fixed.c            m_fixed.h             \
 sha1.c               sha1.h                \
 memio.c              memio.h               \
 tables.c             tables.h              \
+v_diskicon.c         v_diskicon.h          \
 v_video.c            v_video.h             \
                      v_patch.h             \
 w_checksum.c         w_checksum.h          \
@@ -185,7 +186,7 @@ endif
 @PROGRAM_PREFIX at strife_LDADD = strife/libstrife.a $(EXTRA_LIBS)
 
 $(SETUP_BINARIES): @PROGRAM_PREFIX at setup$(EXEEXT)
-	cp $< $@
+	cp @PROGRAM_PREFIX at setup$(EXEEXT) $@
 
 # Source files needed for chocolate-setup:
 
@@ -271,13 +272,15 @@ CLEANFILES = $(execgames_SCRIPTS) $(app_DATA) $(screensaver_DATA)
 if HAVE_PYTHON
 
 icon.c : $(top_builddir)/data/doom8.ico
-	$(top_builddir)/data/convert-icon $< $@
+	$(top_builddir)/data/convert-icon $(top_builddir)/data/doom8.ico $@
 
 endif
 
 midiread : midifile.c
-	$(CC) -DTEST $(CFLAGS) @LDFLAGS@ $< -o $@
+	$(CC) -DTEST $(CFLAGS) @LDFLAGS@ midifile.c -o $@
 
-mus2mid : mus2mid.c memio.c z_native.c i_system.c m_argv.c m_misc.c
-	$(CC) -DSTANDALONE -I$(top_builddir) $(CFLAGS) @LDFLAGS@ $^ -o $@
+MUS2MID_SRC_FILES = mus2mid.c memio.c z_native.c i_system.c m_argv.c m_misc.c
+mus2mid : $(MUS2MID_SRC_FILES)
+	$(CC) -DSTANDALONE -I$(top_builddir) $(CFLAGS) @LDFLAGS@ \
+              $(MUS2MID_SRC_FILES) -o $@
 
diff --git a/src/d_iwad.c b/src/d_iwad.c
index b413c01..ef67f1c 100644
--- a/src/d_iwad.c
+++ b/src/d_iwad.c
@@ -38,7 +38,7 @@ static const iwad_t iwads[] =
     { "tnt.wad",      pack_tnt,  commercial, "Final Doom: TNT: Evilution" },
     { "doom.wad",     doom,      retail,     "Doom" },
     { "doom1.wad",    doom,      shareware,  "Doom Shareware" },
-    { "chex.wad",     pack_chex, shareware,  "Chex Quest" },
+    { "chex.wad",     pack_chex, retail,     "Chex Quest" },
     { "hacx.wad",     pack_hacx, commercial, "Hacx" },
     { "freedm.wad",   doom2,     commercial, "FreeDM" },
     { "freedoom2.wad", doom2,    commercial, "Freedoom: Phase 2" },
@@ -154,29 +154,37 @@ static registry_value_t root_path_keys[] =
         "INSTALLPATH",
     },
 
-    // Ultimate Doom
+    // Doom II
 
     {
         HKEY_LOCAL_MACHINE,
-        SOFTWARE_KEY "\\GOG.com\\Games\\1435827232",
+        SOFTWARE_KEY "\\GOG.com\\Games\\1435848814",
         "PATH",
     },
 
-    // Doom II
+    // Final Doom
 
     {
         HKEY_LOCAL_MACHINE,
-        SOFTWARE_KEY "\\GOG.com\\Games\\1435848814",
+        SOFTWARE_KEY "\\GOG.com\\Games\\1435848742",
         "PATH",
     },
 
-    // Final Doom
+    // Ultimate Doom
 
     {
         HKEY_LOCAL_MACHINE,
-        SOFTWARE_KEY "\\GOG.com\\Games\\1435848742",
+        SOFTWARE_KEY "\\GOG.com\\Games\\1435827232",
         "PATH",
     },
+
+    // Strife: Veteran Edition
+
+    {
+	HKEY_LOCAL_MACHINE,
+	SOFTWARE_KEY "\\GOG.com\\Games\\1432899949",
+	"PATH",
+    },
 };
 
 // Subdirectories of the above install path, where IWADs are installed.
@@ -187,8 +195,8 @@ static char *root_path_subdirs[] =
     "Doom2",
     "Final Doom",
     "Ultimate Doom",
-    "TNT",
     "Plutonia",
+    "TNT",
 };
 
 // Location where Steam is installed
@@ -248,7 +256,7 @@ static char *GetRegistryString(registry_value_t *reg_val)
     {
         // Allocate a buffer for the value and read the value
 
-        result = malloc(len);
+        result = malloc(len + 1);
 
         if (RegQueryValueEx(key, reg_val->value, NULL, &valtype,
                             (unsigned char *) result, &len) != ERROR_SUCCESS)
@@ -256,6 +264,11 @@ static char *GetRegistryString(registry_value_t *reg_val)
             free(result);
             result = NULL;
         }
+        else
+        {
+            // Ensure the value is null-terminated
+            result[len] = '\0';
+        }
     }
 
     // Close the key
@@ -446,13 +459,15 @@ static boolean DirIsFile(char *path, char *filename)
 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
@@ -467,9 +482,10 @@ static char *CheckDirectoryHasIWAD(char *dir, char *iwadname)
         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);
@@ -579,6 +595,7 @@ static void AddIWADPath(char *path, char *suffix)
     free(path);
 }
 
+#ifndef _WIN32
 // Add standard directories where IWADs are located on Unix systems.
 // To respect the freedesktop.org specification we support overriding
 // using standard environment variables. See the XDG Base Directory
@@ -634,6 +651,7 @@ static void AddXdgDirs(void)
     // XDG_DATA_DIRS mechanism, through which it can be overridden.
     AddIWADPath(env, "/games/doom");
 }
+#endif
 
 //
 // Build a list of IWAD files
@@ -694,13 +712,15 @@ static void BuildIWADDirList(void)
 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();
@@ -713,18 +733,20 @@ char *D_FindWADByName(char *name)
         // 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
 
         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);
diff --git a/src/d_loop.c b/src/d_loop.c
index ad9d4a4..4459a2a 100644
--- a/src/d_loop.c
+++ b/src/d_loop.c
@@ -816,3 +816,85 @@ void D_RegisterLoopCallbacks(loop_interface_t *i)
 {
     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;
+}
+
diff --git a/src/d_loop.h b/src/d_loop.h
index eb87d84..a003a98 100644
--- a/src/d_loop.h
+++ b/src/d_loop.h
@@ -77,5 +77,12 @@ void D_StartNetGame(net_gamesettings_t *settings,
 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
 
diff --git a/src/d_mode.c b/src/d_mode.c
index afd84ac..2d3ef3c 100644
--- a/src/d_mode.c
+++ b/src/d_mode.c
@@ -30,7 +30,7 @@ static struct
     int episode;
     int map;
 } valid_modes[] = {
-    { pack_chex, shareware,  1, 5 },
+    { pack_chex, retail,     1, 5 },
     { doom,      shareware,  1, 9 },
     { doom,      registered, 3, 9 },
     { doom,      retail,     4, 9 },
@@ -120,6 +120,9 @@ static struct {
     GameMission_t mission;
     GameVersion_t version;
 } valid_versions[] = {
+    { doom,     exe_doom_1_666 },
+    { doom,     exe_doom_1_7 },
+    { doom,     exe_doom_1_8 },
     { doom,     exe_doom_1_9 },
     { doom,     exe_hacx },
     { doom,     exe_ultimate },
diff --git a/src/d_mode.h b/src/d_mode.h
index ebbd0e8..0be4937 100644
--- a/src/d_mode.h
+++ b/src/d_mode.h
@@ -74,6 +74,16 @@ typedef enum
     exe_strife_1_31  // Strife v1.31
 } GameVersion_t;
 
+// What IWAD variant are we using?
+
+typedef enum
+{
+    vanilla,    // Vanilla Doom
+    freedoom,   // FreeDoom: Phase 1 + 2
+    freedm,     // FreeDM
+    bfgedition, // Doom Classic (Doom 3: BFG Edition)
+} GameVariant_t;
+
 // Skill level.
 
 typedef enum
diff --git a/src/deh_io.c b/src/deh_io.c
index e8beb43..31fde9f 100644
--- a/src/deh_io.c
+++ b/src/deh_io.c
@@ -120,7 +120,7 @@ deh_context_t *DEH_OpenLump(int lumpnum)
     context->input_buffer_pos = 0;
 
     context->filename = malloc(9);
-    M_StringCopy(context->filename, lumpinfo[lumpnum].name, 9);
+    M_StringCopy(context->filename, lumpinfo[lumpnum]->name, 9);
 
     return context;
 }
diff --git a/src/doom.appdata.xml.in b/src/doom.appdata.xml.in
index 1c3aa54..ed499f4 100644
--- a/src/doom.appdata.xml.in
+++ b/src/doom.appdata.xml.in
@@ -26,19 +26,19 @@
   </description>
   <screenshots>
     <screenshot type="default">
-      <image>http://www.chocolate-doom.org/wiki/images/9/97/GNOME_FreeDM_DEMO4.png</image>
+      <image>https://www.chocolate-doom.org/wiki/images/9/97/GNOME_FreeDM_DEMO4.png</image>
       <caption>FreeDM, DM05: Metal</caption>
     </screenshot>
     <screenshot>
-      <image>http://www.chocolate-doom.org/wiki/images/a/a6/GNOME_Doom_II_DEMO2.png</image>
+      <image>https://www.chocolate-doom.org/wiki/images/a/a6/GNOME_Doom_II_DEMO2.png</image>
       <caption>Doom II, Level 5: The Waste Tunnels</caption>
     </screenshot>
     <screenshot>
-      <image>http://www.chocolate-doom.org/wiki/images/4/41/GNOME_Doomsday_of_UAC.png</image>
+      <image>https://www.chocolate-doom.org/wiki/images/4/41/GNOME_Doomsday_of_UAC.png</image>
       <caption>Doomsday of UAC (uac_dead.wad)</caption>
     </screenshot>
     <screenshot>
-      <image>http://www.chocolate-doom.org/wiki/images/2/2a/GNOME_Freedoom_DTWID_DEMO3.png</image>
+      <image>https://www.chocolate-doom.org/wiki/images/2/2a/GNOME_Freedoom_DTWID_DEMO3.png</image>
       <caption>Doom the Way id Did, on Freedoom. Level 3-2: City of Corpses</caption>
     </screenshot>
   </screenshots>
diff --git a/src/doom/am_map.c b/src/doom/am_map.c
index d3d504e..2e4e9e3 100644
--- a/src/doom/am_map.c
+++ b/src/doom/am_map.c
@@ -32,6 +32,7 @@
 #include "m_controls.h"
 #include "m_misc.h"
 #include "i_system.h"
+#include "i_timer.h"
 
 // Needs access to LFB.
 #include "v_video.h"
@@ -99,8 +100,8 @@
 #define M_ZOOMOUT       ((int) (FRACUNIT/1.02))
 
 // translates between frame-buffer and map distances
-#define FTOM(x) FixedMul(((x)<<16),scale_ftom)
-#define MTOF(x) (FixedMul((x),scale_mtof)>>16)
+#define FTOM(x) FixedMul(((x)<<FRACBITS),scale_ftom)
+#define MTOF(x) (FixedMul((x),scale_mtof)>>FRACBITS)
 // translates between frame-buffer and map coordinates
 #define CXMTOF(x)  (f_x + MTOF((x)-m_x))
 #define CYMTOF(y)  (f_y + (f_h - MTOF((y)-m_y)))
@@ -199,7 +200,7 @@ static int 	leveljuststarted = 1; 	// kluge until AM_LevelInit() is called
 
 boolean    	automapactive = false;
 static int 	finit_width = SCREENWIDTH;
-static int 	finit_height = SCREENHEIGHT - 32;
+static int 	finit_height = SCREENHEIGHT - ST_HEIGHT;
 
 // location of window on screen
 static int 	f_x;
@@ -598,11 +599,32 @@ AM_Responder
 
     int rc;
     static int bigstate=0;
+    static int joywait = 0;
     static char buffer[20];
     int key;
 
     rc = false;
 
+    if (ev->type == ev_joystick && joybautomap >= 0
+        && (ev->data1 & (1 << joybautomap)) != 0 && joywait < I_GetTime())
+    {
+        joywait = I_GetTime() + 5;
+
+        if (!automapactive)
+        {
+            AM_Start ();
+            viewactive = false;
+        }
+        else
+        {
+            bigstate = 0;
+            viewactive = true;
+            AM_Stop ();
+        }
+
+        return true;
+    }
+
     if (!automapactive)
     {
 	if (ev->type == ev_keydown && ev->data1 == key_map_toggle)
@@ -697,11 +719,12 @@ AM_Responder
             rc = false;
         }
 
-	if (!deathmatch && cht_CheckCheat(&cheat_amap, ev->data2))
-	{
-	    rc = false;
-	    cheating = (cheating+1) % 3;
-	}
+        if ((!deathmatch || gameversion <= exe_doom_1_8)
+         && cht_CheckCheat(&cheat_amap, ev->data2))
+        {
+            rc = false;
+            cheating = (cheating + 1) % 3;
+        }
     }
     else if (ev->type == ev_keyup)
     {
@@ -833,7 +856,7 @@ void AM_Ticker (void)
 //
 void AM_clearFB(int color)
 {
-    memset(fb, color, f_w*f_h);
+    memset(fb, color, f_w*f_h*sizeof(*fb));
 }
 
 
diff --git a/src/doom/d_main.c b/src/doom/d_main.c
index bcdfcaa..65a39a1 100644
--- a/src/doom/d_main.c
+++ b/src/doom/d_main.c
@@ -40,6 +40,7 @@
 #include "w_main.h"
 #include "w_wad.h"
 #include "s_sound.h"
+#include "v_diskicon.h"
 #include "v_video.h"
 
 #include "f_finale.h"
@@ -117,9 +118,6 @@ boolean		advancedemo;
 // Store demo, do not accept any inputs
 boolean         storedemo;
 
-// "BFG Edition" version of doom2.wad does not include TITLEPIC.
-boolean         bfgedition;
-
 // If true, the main game loop has started.
 boolean         main_loop_started = false;
 
@@ -127,6 +125,7 @@ char		wadfile[1024];		// primary wad file
 char		mapdir[1024];           // directory of development maps
 
 int             show_endoom = 1;
+int             show_diskicon = 1;
 
 
 void D_ConnectNetGame(void);
@@ -216,12 +215,12 @@ void D_Display (void)
 	    break;
 	if (automapactive)
 	    AM_Drawer ();
-	if (wipe || (viewheight != 200 && fullscreen) )
+	if (wipe || (viewheight != SCREENHEIGHT && fullscreen))
 	    redrawsbar = true;
 	if (inhelpscreensstate && !inhelpscreens)
 	    redrawsbar = true;              // just put away the help screen
-	ST_Drawer (viewheight == 200, redrawsbar );
-	fullscreen = viewheight == 200;
+	ST_Drawer (viewheight == SCREENHEIGHT, redrawsbar );
+	fullscreen = viewheight == SCREENHEIGHT;
 	break;
 
       case GS_INTERMISSION:
@@ -259,7 +258,7 @@ void D_Display (void)
     }
 
     // see if the border needs to be updated to the screen
-    if (gamestate == GS_LEVEL && !automapactive && scaledviewwidth != 320)
+    if (gamestate == GS_LEVEL && !automapactive && scaledviewwidth != SCREENWIDTH)
     {
 	if (menuactive || menuactivestate || !viewactivestate)
 	    borderdrawcount = 3;
@@ -330,6 +329,27 @@ void D_Display (void)
     } while (!done);
 }
 
+static void EnableLoadingDisk(void)
+{
+    char *disk_lump_name;
+
+    if (show_diskicon)
+    {
+        if (M_CheckParm("-cdrom") > 0)
+        {
+            disk_lump_name = DEH_String("STCDROM");
+        }
+        else
+        {
+            disk_lump_name = DEH_String("STDISK");
+        }
+
+        V_EnableLoadingDisk(disk_lump_name,
+                            SCREENWIDTH - LOADING_DISK_W,
+                            SCREENHEIGHT - LOADING_DISK_H);
+    }
+}
+
 //
 // Add configuration file variable bindings.
 //
@@ -369,6 +389,7 @@ void D_BindVariables(void)
     M_BindIntVariable("vanilla_savegame_limit", &vanilla_savegame_limit);
     M_BindIntVariable("vanilla_demo_limit",     &vanilla_demo_limit);
     M_BindIntVariable("show_endoom",            &show_endoom);
+    M_BindIntVariable("show_diskicon",          &show_diskicon);
 
     // Multiplayer chat macros
 
@@ -409,7 +430,7 @@ boolean D_GrabMouseCallback(void)
 //
 void D_DoomLoop (void)
 {
-    if (bfgedition &&
+    if (gamevariant == bfgedition &&
         (demorecording || (gameaction == ga_playdemo) || netgame))
     {
         printf(" WARNING: You are playing using one of the Doom Classic\n"
@@ -429,7 +450,7 @@ void D_DoomLoop (void)
     I_GraphicsCheckCommandLine();
     I_SetGrabMouseCallback(D_GrabMouseCallback);
     I_InitGraphics();
-    I_EnableLoadingDisk();
+    EnableLoadingDisk();
 
     V_RestoreBuffer();
     R_ExecuteSetViewSize();
@@ -560,7 +581,7 @@ void D_DoAdvanceDemo (void)
 	{
 	    pagetic = 200;
 
-	    if ( gamemode == retail )
+	    if (gameversion >= exe_ultimate)
 	      pagename = DEH_String("CREDIT");
 	    else
 	      pagename = DEH_String("HELP2");
@@ -577,7 +598,7 @@ void D_DoAdvanceDemo (void)
 
     // The Doom 3: BFG Edition version of doom2.wad does not have a
     // TITLETPIC lump. Use INTERPIC instead as a workaround.
-    if (bfgedition && !strcasecmp(pagename, "TITLEPIC")
+    if (gamevariant == bfgedition && !strcasecmp(pagename, "TITLEPIC")
         && W_CheckNumForName("titlepic") < 0)
     {
         pagename = DEH_String("INTERPIC");
@@ -607,6 +628,10 @@ static char *banners[] =
     "                         "
     "DOOM 2: Hell on Earth v%i.%i"
     "                           ",
+    // doom2.wad v1.666
+    "                         "
+    "DOOM 2: Hell on Earth v%i.%i66"
+    "                          ",
     // doom1.wad
     "                            "
     "DOOM Shareware Startup v%i.%i"
@@ -619,6 +644,10 @@ static char *banners[] =
     "                          "
     "DOOM System Startup v%i.%i"
     "                          ",
+    // Doom v1.666
+    "                          "
+    "DOOM System Startup v%i.%i66"
+    "                          "
     // doom.wad (Ultimate DOOM)
     "                         "
     "The Ultimate DOOM Startup v%i.%i"
@@ -731,12 +760,12 @@ void D_IdentifyVersion(void)
 
         for (i=0; i<numlumps; ++i)
         {
-            if (!strncasecmp(lumpinfo[i].name, "MAP01", 8))
+            if (!strncasecmp(lumpinfo[i]->name, "MAP01", 8))
             {
                 gamemission = doom2;
                 break;
             } 
-            else if (!strncasecmp(lumpinfo[i].name, "E1M1", 8))
+            else if (!strncasecmp(lumpinfo[i]->name, "E1M1", 8))
             {
                 gamemission = doom;
                 break;
@@ -784,6 +813,7 @@ void D_IdentifyVersion(void)
         // with Freedoom and get the right level names.
 
         //!
+        // @category compat
         // @arg <pack>
         //
         // Explicitly specify a Doom II "mission pack" to run as, instead of
@@ -802,16 +832,13 @@ void D_IdentifyVersion(void)
 
 void D_SetGameDescription(void)
 {
-    boolean is_freedoom = W_CheckNumForName("FREEDOOM") >= 0,
-            is_freedm = W_CheckNumForName("FREEDM") >= 0;
-
     gamedescription = "Unknown";
 
     if (logical_gamemission == doom)
     {
         // Doom 1.  But which version?
 
-        if (is_freedoom)
+        if (gamevariant == freedoom)
         {
             gamedescription = GetGameName("Freedoom: Phase 1");
         }
@@ -834,16 +861,13 @@ void D_SetGameDescription(void)
     {
         // Doom 2 of some kind.  But which mission?
 
-        if (is_freedoom)
+        if (gamevariant == freedm)
         {
-            if (is_freedm)
-            {
-                gamedescription = GetGameName("FreeDM");
-            }
-            else
-            {
-                gamedescription = GetGameName("Freedoom: Phase 2");
-            }
+            gamedescription = GetGameName("FreeDM");
+        }
+        else if (gamevariant == freedoom)
+        {
+            gamedescription = GetGameName("Freedoom: Phase 2");
         }
         else if (logical_gamemission == doom2)
         {
@@ -945,15 +969,20 @@ static struct
 
 static void InitGameVersion(void)
 {
+    byte *demolump;
+    char demolumpname[6];
+    int demoversion;
     int p;
     int i;
+    boolean status;
 
     //! 
     // @arg <version>
     // @category compat
     //
-    // Emulate a specific version of Doom.  Valid values are "1.9",
-    // "ultimate", "final", "final2", "hacx" and "chex".
+    // Emulate a specific version of Doom.  Valid values are "1.666",
+    // "1.7", "1.8", "1.9", "ultimate", "final", "final2", "hacx" and
+    // "chex".
     //
 
     p = M_CheckParmWithArgs("-gameversion", 1);
@@ -998,13 +1027,46 @@ static void InitGameVersion(void)
 
             gameversion = exe_hacx;
         }
-        else if (gamemode == shareware || gamemode == registered)
+        else if (gamemode == shareware || gamemode == registered
+              || (gamemode == commercial && gamemission == doom2))
         {
             // original
-
             gameversion = exe_doom_1_9;
 
-            // TODO: Detect IWADs earlier than Doom v1.9.
+            // Detect version from demo lump
+            for (i = 1; i <= 3; ++i)
+            {
+                M_snprintf(demolumpname, 6, "demo%i", i);
+                if (W_CheckNumForName(demolumpname) > 0)
+                {
+                    demolump = W_CacheLumpName(demolumpname, PU_STATIC);
+                    demoversion = demolump[0];
+                    W_ReleaseLumpName(demolumpname);
+                    status = true;
+                    switch (demoversion)
+                    {
+                        case 106:
+                            gameversion = exe_doom_1_666;
+                            break;
+                        case 107:
+                            gameversion = exe_doom_1_7;
+                            break;
+                        case 108:
+                            gameversion = exe_doom_1_8;
+                            break;
+                        case 109:
+                            gameversion = exe_doom_1_9;
+                            break;
+                        default:
+                            status = false;
+                            break;
+                    }
+                    if (status)
+                    {
+                        break;
+                    }
+                }
+            }
         }
         else if (gamemode == retail)
         {
@@ -1012,20 +1074,13 @@ static void InitGameVersion(void)
         }
         else if (gamemode == commercial)
         {
-            if (gamemission == doom2)
-            {
-                gameversion = exe_doom_1_9;
-            }
-            else
-            {
-                // Final Doom: tnt or plutonia
-                // Defaults to emulating the first Final Doom executable,
-                // which has the crash in the demo loop; however, having
-                // this as the default should mean that it plays back
-                // most demos correctly.
+            // Final Doom: tnt or plutonia
+            // Defaults to emulating the first Final Doom executable,
+            // which has the crash in the demo loop; however, having
+            // this as the default should mean that it plays back
+            // most demos correctly.
 
-                gameversion = exe_final;
-            }
+            gameversion = exe_final;
         }
     }
     
@@ -1085,7 +1140,7 @@ static void D_Endoom(void)
 static void LoadIwadDeh(void)
 {
     // The Freedoom IWADs have DEHACKED lumps that must be loaded.
-    if (W_CheckNumForName("FREEDOOM") >= 0)
+    if (gamevariant == freedoom || gamevariant == freedm)
     {
         // Old versions of Freedoom (before 2014-09) did not have technically
         // valid DEHACKED lumps, so ignore errors and just continue if this
@@ -1383,6 +1438,24 @@ void D_DoomMain (void)
     D_IdentifyVersion();
     InitGameVersion();
 
+    // Check which IWAD variant we are using.
+
+    if (W_CheckNumForName("FREEDOOM") >= 0)
+    {
+        if (W_CheckNumForName("FREEDM") >= 0)
+        {
+            gamevariant = freedm;
+        }
+        else
+        {
+            gamevariant = freedoom;
+        }
+    }
+    else if (W_CheckNumForName("DMENUPIC") >= 0)
+    {
+        gamevariant = bfgedition;
+    }
+
     //!
     // @category mod
     //
@@ -1404,10 +1477,9 @@ void D_DoomMain (void)
     // We specifically check for DMENUPIC here, before PWADs have been
     // loaded which could probably include a lump of that name.
 
-    if (W_CheckNumForName("dmenupic") >= 0)
+    if (gamevariant == bfgedition)
     {
         printf("BFG Edition: Using workarounds as needed.\n");
-        bfgedition = true;
 
         // BFG Edition changes the names of the secret levels to
         // censor the Wolfenstein references. It also has an extra
@@ -1493,7 +1565,7 @@ void D_DoomMain (void)
 
         if (D_AddFile(file))
         {
-            M_StringCopy(demolumpname, lumpinfo[numlumps - 1].name,
+            M_StringCopy(demolumpname, lumpinfo[numlumps - 1]->name,
                          sizeof(demolumpname));
         }
         else
@@ -1528,7 +1600,7 @@ void D_DoomMain (void)
 
         for (i = numiwadlumps; i < numlumps; ++i)
         {
-            if (!strncmp(lumpinfo[i].name, "DEHACKED", 8))
+            if (!strncmp(lumpinfo[i]->name, "DEHACKED", 8))
             {
                 DEH_LoadLump(i, false, false);
                 loaded++;
@@ -1555,7 +1627,7 @@ void D_DoomMain (void)
     }
 
     // Check for -file in shareware
-    if (modifiedgame)
+    if (modifiedgame && (gamevariant != freedoom))
     {
 	// These are the lumps that will be checked in IWAD,
 	// if any one is not present, execution will be aborted.
@@ -1594,12 +1666,12 @@ void D_DoomMain (void)
     // Freedoom's IWADs are Boom-compatible, which means they usually
     // don't work in Vanilla (though FreeDM is okay). Show a warning
     // message and give a link to the website.
-    if (W_CheckNumForName("FREEDOOM") >= 0 && W_CheckNumForName("FREEDM") < 0)
+    if (gamevariant == freedoom)
     {
         printf(" WARNING: You are playing using one of the Freedoom IWAD\n"
                " files, which might not work in this port. See this page\n"
                " for more information on how to play using Freedoom:\n"
-               "   http://www.chocolate-doom.org/wiki/index.php/Freedoom\n");
+               "   https://www.chocolate-doom.org/wiki/index.php/Freedoom\n");
         I_PrintDivider();
     }
 
diff --git a/src/doom/doomstat.c b/src/doom/doomstat.c
index ed4c6cc..0b880ec 100644
--- a/src/doom/doomstat.c
+++ b/src/doom/doomstat.c
@@ -25,6 +25,7 @@
 GameMode_t gamemode = indetermined;
 GameMission_t	gamemission = doom;
 GameVersion_t   gameversion = exe_final2;
+GameVariant_t   gamevariant = vanilla;
 char *gamedescription;
 
 // Set if homebrew PWAD stuff has been added.
diff --git a/src/doom/doomstat.h b/src/doom/doomstat.h
index acd65dc..8340851 100644
--- a/src/doom/doomstat.h
+++ b/src/doom/doomstat.h
@@ -56,11 +56,9 @@ extern  boolean	devparm;	// DEBUG: launched with -devparm
 extern GameMode_t	gamemode;
 extern GameMission_t	gamemission;
 extern GameVersion_t    gameversion;
+extern GameVariant_t    gamevariant;
 extern char            *gamedescription;
 
-// If true, we're using one of the mangled BFG edition IWADs.
-extern boolean bfgedition;
-
 // Convenience macro.
 // 'gamemission' can be equal to pack_chex or pack_hacx, but these are
 // just modified versions of doom and doom2, and should be interpreted
diff --git a/src/doom/f_finale.c b/src/doom/f_finale.c
index ca6775e..467483a 100644
--- a/src/doom/f_finale.c
+++ b/src/doom/f_finale.c
@@ -512,7 +512,7 @@ void F_CastPrint (char* text)
     }
     
     // draw it
-    cx = 160-width/2;
+    cx = SCREENWIDTH/2-width/2;
     ch = text;
     while (ch)
     {
@@ -559,9 +559,9 @@ void F_CastDrawer (void)
 			
     patch = W_CacheLumpNum (lump+firstspritelump, PU_CACHE);
     if (flip)
-	V_DrawPatchFlipped(160, 170, patch);
+	V_DrawPatchFlipped(SCREENWIDTH/2, 170, patch);
     else
-	V_DrawPatch(160, 170, patch);
+	V_DrawPatch(SCREENWIDTH/2, 170, patch);
 }
 
 
@@ -618,18 +618,18 @@ void F_BunnyScroll (void)
 
     V_MarkRect (0, 0, SCREENWIDTH, SCREENHEIGHT);
 	
-    scrolled = (320 - ((signed int) finalecount-230)/2);
-    if (scrolled > 320)
-	scrolled = 320;
+    scrolled = (SCREENWIDTH - ((signed int) finalecount-230)/2);
+    if (scrolled > SCREENWIDTH)
+	scrolled = SCREENWIDTH;
     if (scrolled < 0)
 	scrolled = 0;
 		
     for ( x=0 ; x<SCREENWIDTH ; x++)
     {
-	if (x+scrolled < 320)
+	if (x+scrolled < SCREENWIDTH)
 	    F_DrawPatchCol (x, p1, x+scrolled);
 	else
-	    F_DrawPatchCol (x, p2, x+scrolled - 320);		
+	    F_DrawPatchCol (x, p2, x+scrolled - SCREENWIDTH);		
     }
 	
     if (finalecount < 1130)
@@ -671,7 +671,7 @@ static void F_ArtScreenDrawer(void)
         switch (gameepisode)
         {
             case 1:
-                if (gamemode == retail)
+                if (gameversion >= exe_ultimate)
                 {
                     lumpname = "CREDIT";
                 }
diff --git a/src/doom/f_wipe.c b/src/doom/f_wipe.c
index 05852fd..73d4063 100644
--- a/src/doom/f_wipe.c
+++ b/src/doom/f_wipe.c
@@ -49,13 +49,13 @@ wipe_shittyColMajorXform
     int		y;
     short*	dest;
 
-    dest = (short*) Z_Malloc(width*height*2, PU_STATIC, 0);
+    dest = (short*) Z_Malloc(width*height*sizeof(*dest), PU_STATIC, 0);
 
     for(y=0;y<height;y++)
 	for(x=0;x<width;x++)
 	    dest[x*height+y] = array[y*width+x];
 
-    memcpy(array, dest, width*height*2);
+    memcpy(array, dest, width*height*sizeof(*dest));
 
     Z_Free(dest);
 
@@ -67,7 +67,7 @@ wipe_initColorXForm
   int	height,
   int	ticks )
 {
-    memcpy(wipe_scr, wipe_scr_start, width*height);
+    memcpy(wipe_scr, wipe_scr_start, width*height*sizeof(*wipe_scr));
     return 0;
 }
 
@@ -138,7 +138,7 @@ wipe_initMelt
     int i, r;
     
     // copy start screen to main screen
-    memcpy(wipe_scr, wipe_scr_start, width*height);
+    memcpy(wipe_scr, wipe_scr_start, width*height*sizeof(*wipe_scr));
     
     // makes this wipe faster (in theory)
     // to have stuff in column-major format
@@ -234,7 +234,7 @@ wipe_StartScreen
   int	width,
   int	height )
 {
-    wipe_scr_start = Z_Malloc(SCREENWIDTH * SCREENHEIGHT, PU_STATIC, NULL);
+    wipe_scr_start = Z_Malloc(SCREENWIDTH * SCREENHEIGHT * sizeof(*wipe_scr_start), PU_STATIC, NULL);
     I_ReadScreen(wipe_scr_start);
     return 0;
 }
@@ -246,7 +246,7 @@ wipe_EndScreen
   int	width,
   int	height )
 {
-    wipe_scr_end = Z_Malloc(SCREENWIDTH * SCREENHEIGHT, PU_STATIC, NULL);
+    wipe_scr_end = Z_Malloc(SCREENWIDTH * SCREENHEIGHT * sizeof(*wipe_scr_end), PU_STATIC, NULL);
     I_ReadScreen(wipe_scr_end);
     V_DrawBlock(x, y, width, height, wipe_scr_start); // restore start scr.
     return 0;
diff --git a/src/doom/g_game.c b/src/doom/g_game.c
index 3001e03..346bb80 100644
--- a/src/doom/g_game.c
+++ b/src/doom/g_game.c
@@ -614,7 +614,8 @@ void G_DoLoadLevel (void)
 
     // The "Sky never changes in Doom II" bug was fixed in
     // the id Anthology version of doom2.exe for Final Doom.
-    if ((gamemode == commercial) && (gameversion == exe_final2))
+    if ((gamemode == commercial)
+     && (gameversion == exe_final2 || gameversion == exe_chex))
     {
         char *skytexturename;
 
@@ -1540,9 +1541,6 @@ void G_LoadGame (char* name)
     M_StringCopy(savename, name, sizeof(savename));
     gameaction = ga_loadgame; 
 } 
- 
-#define VERSIONSIZE		16 
-
 
 void G_DoLoadGame (void) 
 { 
@@ -1610,59 +1608,78 @@ void G_DoSaveGame (void)
 { 
     char *savegame_file;
     char *temp_savegame_file;
+    char *recovery_savegame_file;
 
+    recovery_savegame_file = NULL;
     temp_savegame_file = P_TempSaveGameFile();
     savegame_file = P_SaveGameFile(savegameslot);
 
     // Open the savegame file for writing.  We write to a temporary file
     // and then rename it at the end if it was successfully written.
-    // This prevents an existing savegame from being overwritten by 
+    // This prevents an existing savegame from being overwritten by
     // a corrupted one, or if a savegame buffer overrun occurs.
-
     save_stream = fopen(temp_savegame_file, "wb");
 
     if (save_stream == NULL)
     {
-        return;
+        // Failed to save the game, so we're going to have to abort. But
+        // to be nice, save to somewhere else before we call I_Error().
+        recovery_savegame_file = M_TempFile("recovery.dsg");
+        save_stream = fopen(recovery_savegame_file, "wb");
+        if (save_stream == NULL)
+        {
+            I_Error("Failed to open either '%s' or '%s' to write savegame.",
+                    temp_savegame_file, recovery_savegame_file);
+        }
     }
 
     savegame_error = false;
 
     P_WriteSaveGameHeader(savedescription);
- 
-    P_ArchivePlayers (); 
-    P_ArchiveWorld (); 
-    P_ArchiveThinkers (); 
-    P_ArchiveSpecials (); 
-	 
+
+    P_ArchivePlayers ();
+    P_ArchiveWorld ();
+    P_ArchiveThinkers ();
+    P_ArchiveSpecials ();
+
     P_WriteSaveGameEOF();
-	 
-    // Enforce the same savegame size limit as in Vanilla Doom, 
+
+    // Enforce the same savegame size limit as in Vanilla Doom,
     // except if the vanilla_savegame_limit setting is turned off.
 
     if (vanilla_savegame_limit && ftell(save_stream) > SAVEGAMESIZE)
     {
-        I_Error ("Savegame buffer overrun");
+        I_Error("Savegame buffer overrun");
     }
-    
+
     // Finish up, close the savegame file.
 
     fclose(save_stream);
 
+    if (recovery_savegame_file != NULL)
+    {
+        // We failed to save to the normal location, but we wrote a
+        // recovery file to the temp directory. Now we can bomb out
+        // with an error.
+        I_Error("Failed to open savegame file '%s' for writing.\n"
+                "But your game has been saved to '%s' for recovery.",
+                temp_savegame_file, recovery_savegame_file);
+    }
+
     // Now rename the temporary savegame file to the actual savegame
     // file, overwriting the old savegame if there was one there.
 
     remove(savegame_file);
     rename(temp_savegame_file, savegame_file);
-    
-    gameaction = ga_nothing; 
+
+    gameaction = ga_nothing;
     M_StringCopy(savedescription, "", sizeof(savedescription));
 
     players[consoleplayer].message = DEH_String(GGSAVED);
 
     // draw the pattern into the back screen
-    R_FillBackScreen ();	
-} 
+    R_FillBackScreen ();
+}
  
 
 //
@@ -1725,9 +1742,6 @@ G_InitNew
     // the -warp command line parameter to behave differently.
     // This is left here for posterity.
 
-    if (skill > sk_nightmare)
-	skill = sk_nightmare;
-
     // This was quite messy with SPECIAL and commented parts.
     // Supposedly hacks to make the latest edition work.
     // It might not work properly.
@@ -1751,6 +1765,33 @@ G_InitNew
     }
     */
 
+    if (skill > sk_nightmare)
+	skill = sk_nightmare;
+
+    if (gameversion >= exe_ultimate)
+    {
+        if (episode == 0)
+        {
+            episode = 4;
+        }
+    }
+    else
+    {
+        if (episode < 1)
+        {
+            episode = 1;
+        }
+        if (episode > 3)
+        {
+            episode = 3;
+        }
+    }
+
+    if (episode > 1 && gamemode == shareware)
+    {
+        episode = 1;
+    }
+
     if (map < 1)
 	map = 1;
 
@@ -2015,22 +2056,20 @@ void G_BeginRecording (void)
 { 
     int             i; 
 
+    demo_p = demobuffer;
+
     //!
     // @category demo
     //
     // 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;
@@ -2086,6 +2125,8 @@ static char *DemoVersionDescription(int version)
             return "v1.8";
         case 109:
             return "v1.9";
+        case 111:
+            return "v1.91 hack demo?";
         default:
             break;
     }
@@ -2105,40 +2146,43 @@ static char *DemoVersionDescription(int version)
     }
 }
 
-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;
-    }
-    else if (demoversion == DOOM_191_VERSION)
+    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"))
     {
-        // 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"
                         "\n"
                         "*** You may need to upgrade your version "
                             "of Doom to v1.9. ***\n"
-                        "    See: http://doomworld.com/files/patches.shtml\n"
+                        "    See: https://www.doomworld.com/classicdoom"
+                                  "/info/patches.php\n"
                         "    This appears to be %s.";
 
         I_Error(message, demoversion, G_VanillaVersionCode(),
                          DemoVersionDescription(demoversion));
     }
-    
+
     skill = *demo_p++; 
     episode = *demo_p++; 
     map = *demo_p++; 
@@ -2174,12 +2218,13 @@ void G_DoPlayDemo (void)
 void G_TimeDemo (char* name) 
 {
     //!
-    // @vanilla 
+    // @category video
+    // @vanilla
     //
     // Disable rendering the screen entirely.
     //
 
-    nodrawers = M_CheckParm ("-nodraw"); 
+    nodrawers = M_CheckParm ("-nodraw");
 
     timingdemo = true; 
     singletics = true; 
diff --git a/src/doom/hu_stuff.c b/src/doom/hu_stuff.c
index b63cac7..a3d0442 100644
--- a/src/doom/hu_stuff.c
+++ b/src/doom/hu_stuff.c
@@ -48,7 +48,7 @@
 #define HU_TITLE2	(mapnames_commercial[gamemap-1])
 #define HU_TITLEP	(mapnames_commercial[gamemap-1 + 32])
 #define HU_TITLET	(mapnames_commercial[gamemap-1 + 64])
-#define HU_TITLE_CHEX   (mapnames[gamemap - 1])
+#define HU_TITLE_CHEX   (mapnames_chex[(gameepisode-1)*9+gamemap-1])
 #define HU_TITLEHEIGHT	1
 #define HU_TITLEX	0
 #define HU_TITLEY	(167 - SHORT(hu_font[0]->height))
@@ -163,6 +163,60 @@ char*	mapnames[] =	// DOOM shareware/registered/retail (Ultimate) names.
     "NEWLEVEL"
 };
 
+char*   mapnames_chex[] =   // Chex Quest names.
+{
+
+    HUSTR_E1M1,
+    HUSTR_E1M2,
+    HUSTR_E1M3,
+    HUSTR_E1M4,
+    HUSTR_E1M5,
+    HUSTR_E1M5,
+    HUSTR_E1M5,
+    HUSTR_E1M5,
+    HUSTR_E1M5,
+
+    HUSTR_E1M5,
+    HUSTR_E1M5,
+    HUSTR_E1M5,
+    HUSTR_E1M5,
+    HUSTR_E1M5,
+    HUSTR_E1M5,
+    HUSTR_E1M5,
+    HUSTR_E1M5,
+    HUSTR_E1M5,
+
+    HUSTR_E1M5,
+    HUSTR_E1M5,
+    HUSTR_E1M5,
+    HUSTR_E1M5,
+    HUSTR_E1M5,
+    HUSTR_E1M5,
+    HUSTR_E1M5,
+    HUSTR_E1M5,
+    HUSTR_E1M5,
+
+    HUSTR_E1M5,
+    HUSTR_E1M5,
+    HUSTR_E1M5,
+    HUSTR_E1M5,
+    HUSTR_E1M5,
+    HUSTR_E1M5,
+    HUSTR_E1M5,
+    HUSTR_E1M5,
+    HUSTR_E1M5,
+
+    "NEWLEVEL",
+    "NEWLEVEL",
+    "NEWLEVEL",
+    "NEWLEVEL",
+    "NEWLEVEL",
+    "NEWLEVEL",
+    "NEWLEVEL",
+    "NEWLEVEL",
+    "NEWLEVEL"
+};
+
 // List of names for levels in commercial IWADs
 // (doom2.wad, plutonia.wad, tnt.wad).  These are stored in a
 // single large array; WADs like pl2.wad have a MAP33, and rely on
@@ -351,10 +405,7 @@ void HU_Start(void)
          break;
     }
 
-    // Chex.exe always uses the episode 1 level title
-    // eg. E2M1 gives the title for E1M1
-
-    if (gameversion == exe_chex)
+    if (logical_gamemission == doom && gameversion == exe_chex)
     {
         s = HU_TITLE_CHEX;
     }
diff --git a/src/doom/m_menu.c b/src/doom/m_menu.c
index 9a166a4..ba8e16a 100644
--- a/src/doom/m_menu.c
+++ b/src/doom/m_menu.c
@@ -742,71 +742,9 @@ void M_QuickLoad(void)
 //
 void M_DrawReadThis1(void)
 {
-    char *lumpname = "CREDIT";
-    int skullx = 330, skully = 175;
-
     inhelpscreens = true;
-    
-    // Different versions of Doom 1.9 work differently
-
-    switch (gameversion)
-    {
-        case exe_doom_1_9:
-        case exe_hacx:
-
-            if (gamemode == commercial)
-            {
-                // Doom 2
-
-                lumpname = "HELP";
-
-                skullx = 330;
-                skully = 165;
-            }
-            else
-            {
-                // Doom 1
-                // HELP2 is the first screen shown in Doom 1
-                
-                lumpname = "HELP2";
-
-                skullx = 280;
-                skully = 185;
-            }
-            break;
-
-        case exe_ultimate:
-        case exe_chex:
-
-            // Ultimate Doom always displays "HELP1".
-
-            // Chex Quest version also uses "HELP1", even though it is based
-            // on Final Doom.
-
-            lumpname = "HELP1";
-
-            break;
-
-        case exe_final:
-        case exe_final2:
-
-            // Final Doom always displays "HELP".
-
-            lumpname = "HELP";
-
-            break;
-
-        default:
-            I_Error("Unhandled game version");
-            break;
-    }
-
-    lumpname = DEH_String(lumpname);
-    
-    V_DrawPatchDirect (0, 0, W_CacheLumpName(lumpname, PU_CACHE));
 
-    ReadDef1.x = skullx;
-    ReadDef1.y = skully;
+    V_DrawPatchDirect(0, 0, W_CacheLumpName(DEH_String("HELP2"), PU_CACHE));
 }
 
 
@@ -824,6 +762,13 @@ void M_DrawReadThis2(void)
     V_DrawPatchDirect(0, 0, W_CacheLumpName(DEH_String("HELP1"), PU_CACHE));
 }
 
+void M_DrawReadThisCommercial(void)
+{
+    inhelpscreens = true;
+
+    V_DrawPatchDirect(0, 0, W_CacheLumpName(DEH_String("HELP"), PU_CACHE));
+}
+
 
 //
 // Change Sfx & Music volumes
@@ -960,15 +905,6 @@ void M_Episode(int choice)
 	return;
     }
 
-    // Yet another hack...
-    if ( (gamemode == registered)
-	 && (choice > 2))
-    {
-      fprintf( stderr,
-	       "M_Episode: 4th episode requires UltimateDOOM\n");
-      choice = 0;
-    }
-	 
     epi = choice;
     M_SetupNextMenu(&NewDef);
 }
@@ -1071,20 +1007,8 @@ void M_ReadThis(int choice)
 
 void M_ReadThis2(int choice)
 {
-    // Doom 1.9 had two menus when playing Doom 1
-    // All others had only one
-
-    if (gameversion == exe_doom_1_9 && gamemode != commercial)
-    {
-        choice = 0;
-        M_SetupNextMenu(&ReadDef2);
-    }
-    else
-    {
-        // Close the menu
-
-        M_FinishReadThis(0);
-    }
+    choice = 0;
+    M_SetupNextMenu(&ReadDef2);
 }
 
 void M_FinishReadThis(int choice)
@@ -1670,7 +1594,7 @@ boolean M_Responder (event_t* ev)
         {
 	    M_StartControlPanel ();
 
-	    if ( gamemode == retail )
+	    if (gameversion >= exe_ultimate)
 	      currentMenu = &ReadDef2;
 	    else
 	      currentMenu = &ReadDef1;
@@ -1959,7 +1883,7 @@ void M_Drawer (void)
     if (messageToPrint)
     {
 	start = 0;
-	y = 100 - M_StringHeight(messageString) / 2;
+	y = SCREENHEIGHT/2 - M_StringHeight(messageString) / 2;
 	while (messageString[start] != '\0')
 	{
 	    int foundnewline = 0;
@@ -1987,7 +1911,7 @@ void M_Drawer (void)
                 start += strlen(string);
             }
 
-	    x = 160 - M_StringWidth(string) / 2;
+	    x = SCREENWIDTH/2 - M_StringWidth(string) / 2;
 	    M_WriteText(x, y, string);
 	    y += SHORT(hu_font[0]->height);
 	}
@@ -2085,25 +2009,29 @@ void M_Init (void)
     // Here we could catch other version dependencies,
     //  like HELP1/2, and four episodes.
 
-  
-    switch ( gamemode )
+    // The same hacks were used in the original Doom EXEs.
+
+    if (gameversion >= exe_ultimate)
     {
-      case commercial:
-        // Commercial has no "read this" entry.
-	MainMenu[readthis] = MainMenu[quitdoom];
-	MainDef.numitems--;
-	MainDef.y += 8;
-	NewDef.prevMenu = &MainDef;
-	break;
-      case shareware:
-	// Episode 2 and 3 are handled,
-	//  branching to an ad screen.
-      case registered:
-	break;
-      case retail:
-	// We are fine.
-      default:
-	break;
+        MainMenu[readthis].routine = M_ReadThis2;
+        ReadDef2.prevMenu = NULL;
+    }
+
+    if (gameversion >= exe_final && gameversion <= exe_final2)
+    {
+        ReadDef2.routine = M_DrawReadThisCommercial;
+    }
+
+    if (gamemode == commercial)
+    {
+        MainMenu[readthis] = MainMenu[quitdoom];
+        MainDef.numitems--;
+        MainDef.y += 8;
+        NewDef.prevMenu = &MainDef;
+        ReadDef1.routine = M_DrawReadThisCommercial;
+        ReadDef1.x = 330;
+        ReadDef1.y = 165;
+        ReadMenu1[rdthsempty1].routine = M_FinishReadThis;
     }
 
     // Versions of doom.exe before the Ultimate Doom release only had
@@ -2112,7 +2040,12 @@ void M_Init (void)
     // (should crash if missing).
     if (gameversion < exe_ultimate)
     {
-	EpiDef.numitems--;
+        EpiDef.numitems--;
+    }
+    // chex.exe shows only one episode.
+    else if (gameversion == exe_chex)
+    {
+        EpiDef.numitems = 1;
     }
 
     opldev = M_CheckParm("-opldev") > 0;
diff --git a/src/doom/m_random.c b/src/doom/m_random.c
index cc31a0b..b4f59b3 100644
--- a/src/doom/m_random.c
+++ b/src/doom/m_random.c
@@ -16,11 +16,6 @@
 //	Random number LUT.
 //
 
-
-#include <time.h>
-
-#include "m_random.h"
-
 //
 // M_Random
 // Returns a 0-255 number
@@ -66,13 +61,12 @@ int M_Random (void)
 
 void M_ClearRandom (void)
 {
-    prndindex = 0;
-
-    // Seed the M_Random counter from the system time
-
-    rndindex = time(NULL) & 0xff;
+    rndindex = prndindex = 0;
 }
 
-
-
-
+// inspired by the same routine in Eternity, thanks haleyjd
+int P_SubRandom (void)
+{
+    int r = P_Random();
+    return r - P_Random();
+}
diff --git a/src/doom/m_random.h b/src/doom/m_random.h
index aa6291a..902691f 100644
--- a/src/doom/m_random.h
+++ b/src/doom/m_random.h
@@ -24,7 +24,6 @@
 #include "doomtype.h"
 
 
-
 // Returns a number from 0 to 255,
 // from a lookup table.
 int M_Random (void);
@@ -35,5 +34,7 @@ int P_Random (void);
 // Fix randoms for demos.
 void M_ClearRandom (void);
 
+// Defined version of P_Random() - P_Random()
+int P_SubRandom (void);
 
 #endif
diff --git a/src/doom/p_doors.c b/src/doom/p_doors.c
index cafab0f..6f01043 100644
--- a/src/doom/p_doors.c
+++ b/src/doom/p_doors.c
@@ -21,6 +21,7 @@
 #include "doomdef.h"
 #include "deh_main.h"
 #include "p_local.h"
+#include "i_system.h"
 
 #include "s_sound.h"
 
@@ -392,6 +393,12 @@ EV_VerticalDoor
     }
 	
     // if the sector has an active thinker, use it
+
+    if (line->sidenum[side^1] == -1)
+    {
+        I_Error("EV_VerticalDoor: DR special type on 1-sided linedef");
+    }
+
     sec = sides[ line->sidenum[side^1]] .sector;
 
     if (sec->specialdata)
diff --git a/src/doom/p_enemy.c b/src/doom/p_enemy.c
index f2b44d1..49e30b4 100644
--- a/src/doom/p_enemy.c
+++ b/src/doom/p_enemy.c
@@ -212,7 +212,7 @@ boolean P_CheckMissileRange (mobj_t* actor)
     if (!actor->info->meleestate)
 	dist -= 128*FRACUNIT;	// no melee attack, so fire more
 
-    dist >>= 16;
+    dist >>= FRACBITS;
 
     if (actor->type == MT_VILE)
     {
@@ -777,7 +777,7 @@ void A_FaceTarget (mobj_t* actor)
 				    actor->target->y);
     
     if (actor->target->flags & MF_SHADOW)
-	actor->angle += (P_Random()-P_Random())<<21;
+	actor->angle += P_SubRandom() << 21;
 }
 
 
@@ -798,7 +798,7 @@ void A_PosAttack (mobj_t* actor)
     slope = P_AimLineAttack (actor, angle, MISSILERANGE);
 
     S_StartSound (actor, sfx_pistol);
-    angle += (P_Random()-P_Random())<<20;
+    angle += P_SubRandom() << 20;
     damage = ((P_Random()%5)+1)*3;
     P_LineAttack (actor, angle, MISSILERANGE, slope, damage);
 }
@@ -821,7 +821,7 @@ void A_SPosAttack (mobj_t* actor)
 
     for (i=0 ; i<3 ; i++)
     {
-	angle = bangle + ((P_Random()-P_Random())<<20);
+	angle = bangle + (P_SubRandom() << 20);
 	damage = ((P_Random()%5)+1)*3;
 	P_LineAttack (actor, angle, MISSILERANGE, slope, damage);
     }
@@ -842,7 +842,7 @@ void A_CPosAttack (mobj_t* actor)
     bangle = actor->angle;
     slope = P_AimLineAttack (actor, bangle, MISSILERANGE);
 
-    angle = bangle + ((P_Random()-P_Random())<<20);
+    angle = bangle + (P_SubRandom() << 20);
     damage = ((P_Random()%5)+1)*3;
     P_LineAttack (actor, angle, MISSILERANGE, slope, damage);
 }
@@ -1877,7 +1877,7 @@ void A_BrainExplode (mobj_t* mo)
     int		z;
     mobj_t*	th;
 	
-    x = mo->x + (P_Random () - P_Random ())*2048;
+    x = mo->x +  P_SubRandom() * 2048;
     y = mo->y;
     z = 128 + P_Random()*2*FRACUNIT;
     th = P_SpawnMobj (x,y,z, MT_ROCKET);
diff --git a/src/doom/p_map.c b/src/doom/p_map.c
index 9d3086f..005a815 100644
--- a/src/doom/p_map.c
+++ b/src/doom/p_map.c
@@ -1083,8 +1083,8 @@ P_AimLineAttack
     shootz = t1->z + (t1->height>>1) + 8*FRACUNIT;
 
     // can't shoot outside view angles
-    topslope = 100*FRACUNIT/160;	
-    bottomslope = -100*FRACUNIT/160;
+    topslope = (SCREENHEIGHT/2)*FRACUNIT/(SCREENWIDTH/2);	
+    bottomslope = -(SCREENHEIGHT/2)*FRACUNIT/(SCREENWIDTH/2);
     
     attackrange = distance;
     linetarget = NULL;
@@ -1351,8 +1351,8 @@ boolean PIT_ChangeSector (mobj_t*	thing)
 			  thing->y,
 			  thing->z + thing->height/2, MT_BLOOD);
 	
-	mo->momx = (P_Random() - P_Random ())<<12;
-	mo->momy = (P_Random() - P_Random ())<<12;
+	mo->momx = P_SubRandom() << 12;
+	mo->momy = P_SubRandom() << 12;
     }
 
     // keep checking (crush other things)	
diff --git a/src/doom/p_mobj.c b/src/doom/p_mobj.c
index a3b9c43..3091de1 100644
--- a/src/doom/p_mobj.c
+++ b/src/doom/p_mobj.c
@@ -44,12 +44,19 @@ void P_SpawnMapThing (mapthing_t*	mthing);
 //
 int test;
 
+// Use a heuristic approach to detect infinite state cycles: Count the number
+// of times the loop in P_SetMobjState() executes and exit with an error once
+// an arbitrary very large limit is reached.
+
+#define MOBJ_CYCLE_LIMIT 1000000
+
 boolean
 P_SetMobjState
 ( mobj_t*	mobj,
   statenum_t	state )
 {
     state_t*	st;
+    int	cycle_counter = 0;
 
     do
     {
@@ -72,6 +79,11 @@ P_SetMobjState
 	    st->action.acp1(mobj);	
 	
 	state = st->nextstate;
+
+	if (cycle_counter++ > MOBJ_CYCLE_LIMIT)
+	{
+	    I_Error("P_SetMobjState: Infinite state cycle detected!");
+	}
     } while (!mobj->tics);
 				
     return true;
@@ -855,7 +867,7 @@ P_SpawnPuff
 {
     mobj_t*	th;
 	
-    z += ((P_Random()-P_Random())<<10);
+    z += (P_SubRandom() << 10);
 
     th = P_SpawnMobj (x,y,z, MT_PUFF);
     th->momz = FRACUNIT;
@@ -883,7 +895,7 @@ P_SpawnBlood
 {
     mobj_t*	th;
 	
-    z += ((P_Random()-P_Random())<<10);
+    z += (P_SubRandom() << 10);
     th = P_SpawnMobj (x,y,z, MT_BLOOD);
     th->momz = FRACUNIT*2;
     th->tics -= P_Random()&3;
@@ -968,7 +980,7 @@ P_SpawnMissile
 
     // fuzzy player
     if (dest->flags & MF_SHADOW)
-	an += (P_Random()-P_Random())<<20;	
+	an += P_SubRandom() << 20;
 
     th->angle = an;
     an >>= ANGLETOFINESHIFT;
diff --git a/src/doom/p_pspr.c b/src/doom/p_pspr.c
index e4774c7..097cace 100644
--- a/src/doom/p_pspr.c
+++ b/src/doom/p_pspr.c
@@ -470,7 +470,7 @@ A_Punch
 	damage *= 10;
 
     angle = player->mo->angle;
-    angle += (P_Random()-P_Random())<<18;
+    angle += P_SubRandom() << 18;
     slope = P_AimLineAttack (player->mo, angle, MELEERANGE);
     P_LineAttack (player->mo, angle, MELEERANGE, slope, damage);
 
@@ -500,7 +500,7 @@ A_Saw
 
     damage = 2*(P_Random ()%10+1);
     angle = player->mo->angle;
-    angle += (P_Random()-P_Random())<<18;
+    angle += P_SubRandom() << 18;
     
     // use meleerange + 1 se the puff doesn't skip the flash
     slope = P_AimLineAttack (player->mo, angle, MELEERANGE+1);
@@ -643,7 +643,7 @@ P_GunShot
     angle = mo->angle;
 
     if (!accurate)
-	angle += (P_Random()-P_Random())<<18;
+	angle += P_SubRandom() << 18;
 
     P_LineAttack (mo, angle, MISSILERANGE, bulletslope, damage);
 }
@@ -726,11 +726,11 @@ A_FireShotgun2
     {
 	damage = 5*(P_Random ()%3+1);
 	angle = player->mo->angle;
-	angle += (P_Random()-P_Random())<<19;
+	angle += P_SubRandom() << ANGLETOFINESHIFT;
 	P_LineAttack (player->mo,
 		      angle,
 		      MISSILERANGE,
-		      bulletslope + ((P_Random()-P_Random())<<5), damage);
+		      bulletslope + (P_SubRandom() << 5), damage);
     }
 }
 
diff --git a/src/doom/p_saveg.c b/src/doom/p_saveg.c
index 5cb8196..77acb96 100644
--- a/src/doom/p_saveg.c
+++ b/src/doom/p_saveg.c
@@ -33,9 +33,6 @@
 #include "m_misc.h"
 #include "r_state.h"
 
-#define SAVEGAME_EOF 0x1d
-#define VERSIONSIZE 16 
-
 FILE *save_stream;
 int savegamelength;
 boolean savegame_error;
@@ -80,7 +77,7 @@ char *P_SaveGameFile(int slot)
 
 static byte saveg_read8(void)
 {
-    byte result;
+    byte result = -1;
 
     if (fread(&result, 1, 1, save_stream) < 1)
     {
diff --git a/src/doom/p_saveg.h b/src/doom/p_saveg.h
index 2d7beba..518a5b7 100644
--- a/src/doom/p_saveg.h
+++ b/src/doom/p_saveg.h
@@ -22,6 +22,9 @@
 
 #include <stdio.h>
 
+#define SAVEGAME_EOF 0x1d
+#define VERSIONSIZE 16
+
 // maximum size of a savegame description
 
 #define SAVESTRINGSIZE 24
diff --git a/src/doom/p_setup.c b/src/doom/p_setup.c
index 08fed7a..1b8c02b 100644
--- a/src/doom/p_setup.c
+++ b/src/doom/p_setup.c
@@ -192,12 +192,20 @@ void P_LoadSegs (int lump)
 	li->v1 = &vertexes[SHORT(ml->v1)];
 	li->v2 = &vertexes[SHORT(ml->v2)];
 
-	li->angle = (SHORT(ml->angle))<<16;
-	li->offset = (SHORT(ml->offset))<<16;
+	li->angle = (SHORT(ml->angle))<<FRACBITS;
+	li->offset = (SHORT(ml->offset))<<FRACBITS;
 	linedef = SHORT(ml->linedef);
 	ldef = &lines[linedef];
 	li->linedef = ldef;
 	side = SHORT(ml->side);
+
+        // e6y: check for wrong indexes
+        if ((unsigned)ldef->sidenum[side] >= (unsigned)numsides)
+        {
+            I_Error("P_LoadSegs: linedef %d for seg %d references a non-existent sidedef %d",
+                    linedef, i, (unsigned)ldef->sidenum[side]);
+        }
+
 	li->sidedef = &sides[ldef->sidenum[side]];
 	li->frontsector = sides[ldef->sidenum[side]].sector;
 
diff --git a/src/doom/p_spec.c b/src/doom/p_spec.c
index 17446b5..0d8f051 100644
--- a/src/doom/p_spec.c
+++ b/src/doom/p_spec.c
@@ -356,7 +356,7 @@ P_FindNextHighestFloor
             }
             else if (h == MAX_ADJOINING_SECTORS + 2)
             {
-                // Fatal overflow: game crashes at 22 textures
+                // Fatal overflow: game crashes at 22 sectors
                 I_Error("Sector with more than 22 adjoining sectors. "
                         "Vanilla will crash here");
             }
diff --git a/src/doom/p_user.c b/src/doom/p_user.c
index 73c0dc9..772871e 100644
--- a/src/doom/p_user.c
+++ b/src/doom/p_user.c
@@ -144,7 +144,7 @@ void P_MovePlayer (player_t* player)
 	
     cmd = &player->cmd;
 	
-    player->mo->angle += (cmd->angleturn<<16);
+    player->mo->angle += (cmd->angleturn<<FRACBITS);
 
     // Do not let the player control movement
     //  if not onground.
diff --git a/src/doom/r_bsp.c b/src/doom/r_bsp.c
index 9a78812..755d2ee 100644
--- a/src/doom/r_bsp.c
+++ b/src/doom/r_bsp.c
@@ -77,8 +77,14 @@ typedef	struct
     
 } cliprange_t;
 
-
-#define MAXSEGS		32
+// We must expand MAXSEGS to the theoretical limit of the number of solidsegs
+// that can be generated in a scene by the DOOM engine. This was determined by
+// Lee Killough during BOOM development to be a function of the screensize.
+// The simplest thing we can do, other than fix this bug, is to let the game
+// render overage and then bomb out by detecting the overflow after the 
+// fact. -haleyjd
+//#define MAXSEGS 32
+#define MAXSEGS (SCREENWIDTH / 2 + 1)
 
 // newend is one past the last valid seg
 cliprange_t*	newend;
@@ -532,6 +538,10 @@ void R_Subsector (int num)
 	R_AddLine (line);
 	line++;
     }
+
+    // check for solidsegs overflow - extremely unsatisfactory!
+    if(newend > &solidsegs[32])
+        I_Error("R_Subsector: solidsegs overflow (vanilla may crash here)\n");
 }
 
 
diff --git a/src/doom/r_data.c b/src/doom/r_data.c
index 2d4b65b..0de1218 100644
--- a/src/doom/r_data.c
+++ b/src/doom/r_data.c
@@ -832,7 +832,7 @@ void R_PrecacheLevel (void)
 	if (flatpresent[i])
 	{
 	    lump = firstflat + i;
-	    flatmemory += lumpinfo[lump].size;
+	    flatmemory += lumpinfo[lump]->size;
 	    W_CacheLumpNum(lump, PU_CACHE);
 	}
     }
@@ -869,7 +869,7 @@ void R_PrecacheLevel (void)
 	for (j=0 ; j<texture->patchcount ; j++)
 	{
 	    lump = texture->patches[j].patch;
-	    texturememory += lumpinfo[lump].size;
+	    texturememory += lumpinfo[lump]->size;
 	    W_CacheLumpNum(lump , PU_CACHE);
 	}
     }
@@ -898,7 +898,7 @@ void R_PrecacheLevel (void)
 	    for (k=0 ; k<8 ; k++)
 	    {
 		lump = firstspritelump + sf->lump[k];
-		spritememory += lumpinfo[lump].size;
+		spritememory += lumpinfo[lump]->size;
 		W_CacheLumpNum(lump , PU_CACHE);
 	    }
 	}
diff --git a/src/doom/r_draw.c b/src/doom/r_draw.c
index 9271bcd..4a7679f 100644
--- a/src/doom/r_draw.c
+++ b/src/doom/r_draw.c
@@ -843,7 +843,7 @@ void R_FillBackScreen (void)
 	
     if (background_buffer == NULL)
     {
-        background_buffer = Z_Malloc(SCREENWIDTH * (SCREENHEIGHT - SBARHEIGHT),
+        background_buffer = Z_Malloc(SCREENWIDTH * (SCREENHEIGHT - SBARHEIGHT) * sizeof(*background_buffer),
                                      PU_STATIC, NULL);
     }
 
@@ -928,7 +928,7 @@ R_VideoErase
 
     if (background_buffer != NULL)
     {
-        memcpy(I_VideoBuffer + ofs, background_buffer + ofs, count); 
+        memcpy(I_VideoBuffer + ofs, background_buffer + ofs, count * sizeof(*I_VideoBuffer));
     }
 } 
 
diff --git a/src/doom/r_main.c b/src/doom/r_main.c
index 22278fe..567ea4b 100644
--- a/src/doom/r_main.c
+++ b/src/doom/r_main.c
@@ -482,7 +482,7 @@ fixed_t R_ScaleFromGlobalAngle (angle_t visangle)
     num = FixedMul(projection,sineb)<<detailshift;
     den = FixedMul(rw_distance,sinea);
 
-    if (den > num>>16)
+    if (den > num>>FRACBITS)
     {
 	scale = FixedDiv (num, den);
 
@@ -841,7 +841,7 @@ void R_SetupFrame (player_t* player)
     {
 	fixedcolormap =
 	    colormaps
-	    + player->fixedcolormap*256*sizeof(lighttable_t);
+	    + player->fixedcolormap*256;
 	
 	walllights = scalelightfixed;
 
diff --git a/src/doom/r_segs.c b/src/doom/r_segs.c
index 9b4e413..461aa4a 100644
--- a/src/doom/r_segs.c
+++ b/src/doom/r_segs.c
@@ -715,7 +715,7 @@ R_StoreWallRange
     if ( ((ds_p->silhouette & SIL_TOP) || maskedtexture)
 	 && !ds_p->sprtopclip)
     {
-	memcpy (lastopening, ceilingclip+start, 2*(rw_stopx-start));
+	memcpy (lastopening, ceilingclip+start, sizeof(*lastopening)*(rw_stopx-start));
 	ds_p->sprtopclip = lastopening - start;
 	lastopening += rw_stopx - start;
     }
@@ -723,7 +723,7 @@ R_StoreWallRange
     if ( ((ds_p->silhouette & SIL_BOTTOM) || maskedtexture)
 	 && !ds_p->sprbottomclip)
     {
-	memcpy (lastopening, floorclip+start, 2*(rw_stopx-start));
+	memcpy (lastopening, floorclip+start, sizeof(*lastopening)*(rw_stopx-start));
 	ds_p->sprbottomclip = lastopening - start;
 	lastopening += rw_stopx - start;	
     }
diff --git a/src/doom/r_sky.c b/src/doom/r_sky.c
index 667c88d..bcd5a75 100644
--- a/src/doom/r_sky.c
+++ b/src/doom/r_sky.c
@@ -47,6 +47,6 @@ int			skytexturemid;
 void R_InitSkyMap (void)
 {
   // skyflatnum = R_FlatNumForName ( SKYFLATNAME );
-    skytexturemid = 100*FRACUNIT;
+    skytexturemid = SCREENHEIGHT/2*FRACUNIT;
 }
 
diff --git a/src/doom/r_things.c b/src/doom/r_things.c
index 764b1d7..f805430 100644
--- a/src/doom/r_things.c
+++ b/src/doom/r_things.c
@@ -38,7 +38,7 @@
 
 
 #define MINZ				(FRACUNIT*4)
-#define BASEYCENTER			100
+#define BASEYCENTER			(SCREENHEIGHT/2)
 
 //void R_DrawColumn (void);
 //void R_DrawFuzzColumn (void);
@@ -208,22 +208,22 @@ void R_InitSpriteDefs (char** namelist)
 	//  filling in the frames for whatever is found
 	for (l=start+1 ; l<end ; l++)
 	{
-	    if (!strncasecmp(lumpinfo[l].name, spritename, 4))
+	    if (!strncasecmp(lumpinfo[l]->name, spritename, 4))
 	    {
-		frame = lumpinfo[l].name[4] - 'A';
-		rotation = lumpinfo[l].name[5] - '0';
+		frame = lumpinfo[l]->name[4] - 'A';
+		rotation = lumpinfo[l]->name[5] - '0';
 
 		if (modifiedgame)
-		    patched = W_GetNumForName (lumpinfo[l].name);
+		    patched = W_GetNumForName (lumpinfo[l]->name);
 		else
 		    patched = l;
 
 		R_InstallSpriteLump (patched, frame, rotation, false);
 
-		if (lumpinfo[l].name[6])
+		if (lumpinfo[l]->name[6])
 		{
-		    frame = lumpinfo[l].name[6] - 'A';
-		    rotation = lumpinfo[l].name[7] - '0';
+		    frame = lumpinfo[l]->name[6] - 'A';
+		    rotation = lumpinfo[l]->name[7] - '0';
 		    R_InstallSpriteLump (l, frame, rotation, true);
 		}
 	    }
@@ -665,7 +665,7 @@ void R_DrawPSprite (pspdef_t* psp)
     flip = (boolean)sprframe->flip[0];
     
     // calculate edges of the shape
-    tx = psp->sx-160*FRACUNIT;
+    tx = psp->sx-(SCREENWIDTH/2)*FRACUNIT;
 	
     tx -= spriteoffset[lump];	
     x1 = (centerxfrac + FixedMul (tx,pspritescale) ) >>FRACBITS;
diff --git a/src/doom/s_sound.c b/src/doom/s_sound.c
index 884e0c1..4f48a3e 100644
--- a/src/doom/s_sound.c
+++ b/src/doom/s_sound.c
@@ -46,7 +46,7 @@
 // Distance tp origin when sounds should be maxed out.
 // This should relate to movement clipping resolution
 // (see BLOCKMAP handling).
-// In the source code release: (160*FRACUNIT).  Changed back to the 
+// In the source code release: (160*FRACUNIT).  Changed back to the
 // Vanilla value of 200 (why was this changed?)
 
 #define S_CLOSE_DIST (200 * FRACUNIT)
@@ -59,7 +59,6 @@
 
 #define S_STEREO_SWING (96 * FRACUNIT)
 
-#define NORM_PITCH 128
 #define NORM_PRIORITY 64
 #define NORM_SEP 128
 
@@ -73,7 +72,9 @@ typedef struct
 
     // handle of the sound being played
     int handle;
-    
+
+    int pitch;
+
 } channel_t;
 
 // The set of channels available
@@ -85,7 +86,7 @@ static channel_t *channels;
 
 int sfxVolume = 8;
 
-// Maximum volume of music. 
+// Maximum volume of music.
 
 int musicVolume = 8;
 
@@ -95,7 +96,7 @@ static int snd_SfxVolume;
 
 // Whether songs are mus_paused
 
-static boolean mus_paused;        
+static boolean mus_paused;
 
 // Music currently being played
 
@@ -112,10 +113,25 @@ int snd_channels = 8;
 //
 
 void S_Init(int sfxVolume, int musicVolume)
-{  
+{
     int i;
 
-    I_SetOPLDriverVer(opl_v_new);
+    if (gameversion == exe_doom_1_666)
+    {
+        if (logical_gamemission == doom)
+        {
+            I_SetOPLDriverVer(opl_doom1_1_666);
+        }
+        else
+        {
+            I_SetOPLDriverVer(opl_doom2_1_666);
+        }
+    }
+    else
+    {
+        I_SetOPLDriverVer(opl_doom_1_9);
+    }
+
     I_PrecacheSounds(S_sfx, NUMSFX);
 
     S_SetSfxVolume(sfxVolume);
@@ -141,6 +157,12 @@ void S_Init(int sfxVolume, int musicVolume)
         S_sfx[i].lumpnum = S_sfx[i].usefulness = -1;
     }
 
+    // Doom defaults to pitch-shifting off.
+    if (snd_pitchshift == -1)
+    {
+        snd_pitchshift = 0;
+    }
+
     I_AtExit(S_Shutdown, true);
 }
 
@@ -237,10 +259,10 @@ void S_Start(void)
         {
             mnum = spmus[gamemap-1];
         }
-    }        
+    }
 
     S_ChangeMusic(mnum, true);
-}        
+}
 
 void S_StopSound(mobj_t *origin)
 {
@@ -265,7 +287,7 @@ static int S_GetChannel(mobj_t *origin, sfxinfo_t *sfxinfo)
 {
     // channel number to use
     int                cnum;
-    
+
     channel_t*        c;
 
     // Find an open channel
@@ -296,7 +318,7 @@ static int S_GetChannel(mobj_t *origin, sfxinfo_t *sfxinfo)
 
         if (cnum == snd_channels)
         {
-            // FUCK!  No lower priority.  Sorry, Charlie.    
+            // FUCK!  No lower priority.  Sorry, Charlie.
             return -1;
         }
         else
@@ -337,12 +359,12 @@ static int S_AdjustSoundParams(mobj_t *listener, mobj_t *source,
 
     // From _GG1_ p.428. Appox. eucledian distance fast.
     approx_dist = adx + ady - ((adx < ady ? adx : ady)>>1);
-    
+
     if (gamemap != 8 && approx_dist > S_CLIPPING_DIST)
     {
         return 0;
     }
-    
+
     // angle of source to listener
     angle = R_PointToAngle2(listener->x,
                             listener->y,
@@ -384,18 +406,34 @@ static int S_AdjustSoundParams(mobj_t *listener, mobj_t *source,
         // distance effect
         *vol = (snd_SfxVolume
                 * ((S_CLIPPING_DIST - approx_dist)>>FRACBITS))
-            / S_ATTENUATOR; 
+            / S_ATTENUATOR;
     }
-    
+
     return (*vol > 0);
 }
 
+// clamp supplied integer to the range 0 <= x <= 255.
+
+static int Clamp(int x)
+{
+    if (x < 0)
+    {
+        return 0;
+    }
+    else if (x > 255)
+    {
+        return 255;
+    }
+    return x;
+}
+
 void S_StartSound(void *origin_p, int sfx_id)
 {
     sfxinfo_t *sfx;
     mobj_t *origin;
     int rc;
     int sep;
+    int pitch;
     int cnum;
     int volume;
 
@@ -411,9 +449,11 @@ void S_StartSound(void *origin_p, int sfx_id)
     sfx = &S_sfx[sfx_id];
 
     // Initialize sound parameters
+    pitch = NORM_PITCH;
     if (sfx->link)
     {
         volume += sfx->volume;
+        pitch = sfx->pitch;
 
         if (volume < 1)
         {
@@ -438,7 +478,7 @@ void S_StartSound(void *origin_p, int sfx_id)
 
         if (origin->x == players[consoleplayer].mo->x
          && origin->y == players[consoleplayer].mo->y)
-        {        
+        {
             sep = NORM_SEP;
         }
 
@@ -446,12 +486,23 @@ void S_StartSound(void *origin_p, int sfx_id)
         {
             return;
         }
-    }        
+    }
     else
     {
         sep = NORM_SEP;
     }
 
+    // hacks to vary the sfx pitches
+    if (sfx_id >= sfx_sawup && sfx_id <= sfx_sawhit)
+    {
+        pitch += 8 - (M_Random()&15);
+    }
+    else if (sfx_id != sfx_itemup && sfx_id != sfx_tink)
+    {
+        pitch += 16 - (M_Random()&31);
+    }
+    pitch = Clamp(pitch);
+
     // kill old sound
     S_StopSound(origin);
 
@@ -474,8 +525,9 @@ void S_StartSound(void *origin_p, int sfx_id)
         sfx->lumpnum = I_GetSfxLumpNum(sfx);
     }
 
-    channels[cnum].handle = I_StartSound(sfx, cnum, volume, sep);
-}        
+    channels[cnum].pitch = pitch;
+    channels[cnum].handle = I_StartSound(sfx, cnum, volume, sep, channels[cnum].pitch);
+}
 
 //
 // Stop and resume music, during game PAUSE.
@@ -549,7 +601,7 @@ void S_UpdateSounds(mobj_t *listener)
                                                   c->origin,
                                                   &volume,
                                                   &sep);
-                    
+
                     if (!audible)
                     {
                         S_StopChannel(cnum);
@@ -576,7 +628,7 @@ void S_SetMusicVolume(int volume)
     {
         I_Error("Attempt to set music volume at %d",
                 volume);
-    }    
+    }
 
     I_SetMusicVolume(volume);
 }
diff --git a/src/doom/st_stuff.c b/src/doom/st_stuff.c
index 693b6ef..93665be 100644
--- a/src/doom/st_stuff.c
+++ b/src/doom/st_stuff.c
@@ -528,7 +528,8 @@ ST_Responder (event_t* ev)
 	{
 	  musnum = mus_runnin + (buf[0]-'0')*10 + buf[1]-'0' - 1;
 	  
-	  if (((buf[0]-'0')*10 + buf[1]-'0') > 35)
+	  if (((buf[0]-'0')*10 + buf[1]-'0') > 35
+       && gameversion >= exe_doom_1_8)
 	    plyr->message = DEH_String(STSTR_NOMUS);
 	  else
 	    S_ChangeMusic(musnum, 1);
@@ -610,47 +611,64 @@ ST_Responder (event_t* ev)
       
       if (gamemode == commercial)
       {
-	epsd = 1;
+	epsd = 0;
 	map = (buf[0] - '0')*10 + buf[1] - '0';
       }
       else
       {
 	epsd = buf[0] - '0';
 	map = buf[1] - '0';
-      }
-
-      // Chex.exe always warps to episode 1.
 
-      if (gameversion == exe_chex)
-      {
-        epsd = 1;
+        // Chex.exe always warps to episode 1.
+
+        if (gameversion == exe_chex)
+        {
+            if (epsd > 1)
+            {
+                epsd = 1;
+            }
+            if (map > 5)
+            {
+                map = 5;
+            }
+        }
       }
 
       // Catch invalid maps.
-      if (epsd < 1)
-	return false;
-
-      if (map < 1)
-	return false;
-
-      // Ohmygod - this is not going to work.
-      if ((gamemode == retail)
-	  && ((epsd > 4) || (map > 9)))
-	return false;
-
-      if ((gamemode == registered)
-	  && ((epsd > 3) || (map > 9)))
-	return false;
-
-      if ((gamemode == shareware)
-	  && ((epsd > 1) || (map > 9)))
-	return false;
-
-      // The source release has this check as map > 34. However, Vanilla
-      // Doom allows IDCLEV up to MAP40 even though it normally crashes.
-      if ((gamemode == commercial)
-	&& (( epsd > 1) || (map > 40)))
-	return false;
+      if (gamemode != commercial)
+      {
+          if (epsd < 1)
+          {
+              return false;
+          }
+          if (epsd > 4)
+          {
+              return false;
+          }
+          if (epsd == 4 && gameversion < exe_ultimate)
+          {
+              return false;
+          }
+          if (map < 1)
+          {
+              return false;
+          }
+          if (map > 9)
+          {
+              return false;
+          }
+      }
+      else
+      {
+          if (map < 1)
+          {
+              return false;
+          }
+          if (map > 40)
+          {
+              return false;
+          }
+      }
 
       // So be it.
       plyr->message = DEH_String(STSTR_CLEV);
@@ -1412,6 +1430,6 @@ void ST_Stop (void)
 void ST_Init (void)
 {
     ST_loadData();
-    st_backing_screen = (byte *) Z_Malloc(ST_WIDTH * ST_HEIGHT, PU_STATIC, 0);
+    st_backing_screen = (byte *) Z_Malloc(ST_WIDTH * ST_HEIGHT * sizeof(*st_backing_screen), PU_STATIC, 0);
 }
 
diff --git a/src/doom/statdump.c b/src/doom/statdump.c
index 9988691..5bacad6 100644
--- a/src/doom/statdump.c
+++ b/src/doom/statdump.c
@@ -1,356 +1,356 @@
- /*
-
- Copyright(C) 2005-2014 Simon Howard
-
- This program is free software; you can redistribute it and/or
- modify it under the terms of the GNU General Public License
- as published by the Free Software Foundation; either version 2
- of the License, or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- GNU General Public License for more details.
-
- --
-
- Functions for presenting the information captured from the statistics
- buffer to a file.
-
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-
-#include "d_player.h"
-#include "d_mode.h"
-#include "m_argv.h"
-
-#include "statdump.h"
-
-/* Par times for E1M1-E1M9. */
-static const int doom1_par_times[] =
-{
-    30, 75, 120, 90, 165, 180, 180, 30, 165,
-};
-
-/* Par times for MAP01-MAP09. */
-static const int doom2_par_times[] =
-{
-    30, 90, 120, 120, 90, 150, 120, 120, 270,
-};
-
-/* Player colors. */
-static const char *player_colors[] =
-{
-    "Green", "Indigo", "Brown", "Red"
-};
-
-// Array of end-of-level statistics that have been captured.
-
-#define MAX_CAPTURES 32
-static wbstartstruct_t captured_stats[MAX_CAPTURES];
-static int num_captured_stats = 0;
-
-static GameMission_t discovered_gamemission = none;
-
-/* Try to work out whether this is a Doom 1 or Doom 2 game, by looking
- * at the episode and map, and the par times.  This is used to decide
- * how to format the level name.  Unfortunately, in some cases it is
- * impossible to determine whether this is Doom 1 or Doom 2. */
-
-static void DiscoverGamemode(wbstartstruct_t *stats, int num_stats)
-{
-    int partime;
-    int level;
-    int i;
-
-    if (discovered_gamemission != none)
-    {
-        return;
-    }
-
-    for (i=0; i<num_stats; ++i)
-    {
-        level = stats[i].last;
-
-        /* If episode 2, 3 or 4, this is Doom 1. */
-
-        if (stats[i].epsd > 0)
-        {
-            discovered_gamemission = doom;
-            return;
-        }
-
-        /* This is episode 1.  If this is level 10 or higher,
-           it must be Doom 2. */
-
-        if (level >= 9)
-        {
-            discovered_gamemission = doom2;
-            return;
-        }
-
-        /* Try to work out if this is Doom 1 or Doom 2 by looking
-           at the par time. */
-
-        partime = stats[i].partime;
-
-        if (partime == doom1_par_times[level] * TICRATE
-         && partime != doom2_par_times[level] * TICRATE)
-        {
-            discovered_gamemission = doom;
-            return;
-        }
-
-        if (partime != doom1_par_times[level] * TICRATE
-         && partime == doom2_par_times[level] * TICRATE)
-        {
-            discovered_gamemission = doom2;
-            return;
-        }
-    }
-}
-
-/* Returns the number of players active in the given stats buffer. */
-
-static int GetNumPlayers(wbstartstruct_t *stats)
-{
-    int i;
-    int num_players = 0;
-
-    for (i=0; i<MAXPLAYERS; ++i)
-    {
-        if (stats->plyr[i].in)
-        {
-            ++num_players;
-        }
-    }
-
-    return num_players;
-}
-
-static void PrintBanner(FILE *stream)
-{
-    fprintf(stream, "===========================================\n");
-}
-
-static void PrintPercentage(FILE *stream, int amount, int total)
-{
-    if (total == 0)
-    {
-        fprintf(stream, "0");
-    }
-    else
-    {
-        fprintf(stream, "%i / %i", amount, total);
-
-        // statdump.exe is a 16-bit program, so very occasionally an
-        // integer overflow can occur when doing this calculation with
-        // a large value. Therefore, cast to short to give the same
-        // output.
-
-        fprintf(stream, " (%i%%)", (short) (amount * 100) / total);
-    }
-}
-
-/* Display statistics for a single player. */
-
-static void PrintPlayerStats(FILE *stream, wbstartstruct_t *stats,
-        int player_num)
-{
-    wbplayerstruct_t *player = &stats->plyr[player_num];
-
-    fprintf(stream, "Player %i (%s):\n", player_num + 1,
-            player_colors[player_num]);
-
-    /* Kills percentage */
-
-    fprintf(stream, "\tKills: ");
-    PrintPercentage(stream, player->skills, stats->maxkills);
-    fprintf(stream, "\n");
-
-    /* Items percentage */
-
-    fprintf(stream, "\tItems: ");
-    PrintPercentage(stream, player->sitems, stats->maxitems);
-    fprintf(stream, "\n");
-
-    /* Secrets percentage */
-
-    fprintf(stream, "\tSecrets: ");
-    PrintPercentage(stream, player->ssecret, stats->maxsecret);
-    fprintf(stream, "\n");
-}
-
-/* Frags table for multiplayer games. */
-
-static void PrintFragsTable(FILE *stream, wbstartstruct_t *stats)
-{
-    int x, y;
-
-    fprintf(stream, "Frags:\n");
-
-    /* Print header */
-
-    fprintf(stream, "\t\t");
-
-    for (x=0; x<MAXPLAYERS; ++x)
-    {
-
-        if (!stats->plyr[x].in)
-        {
-            continue;
-        }
-
-        fprintf(stream, "%s\t", player_colors[x]);
-    }
-
-    fprintf(stream, "\n");
-
-    fprintf(stream, "\t\t-------------------------------- VICTIMS\n");
-
-    /* Print table */
-
-    for (y=0; y<MAXPLAYERS; ++y)
-    {
-        if (!stats->plyr[y].in)
-        {
-            continue;
-        }
-
-        fprintf(stream, "\t%s\t|", player_colors[y]);
-
-        for (x=0; x<MAXPLAYERS; ++x)
-        {
-            if (!stats->plyr[x].in)
-            {
-                continue;
-            }
-
-            fprintf(stream, "%i\t", stats->plyr[y].frags[x]);
-        }
-
-        fprintf(stream, "\n");
-    }
-
-    fprintf(stream, "\t\t|\n");
-    fprintf(stream, "\t     KILLERS\n");
-}
-
-/* Displays the level name: MAPxy or ExMy, depending on game mode. */
-
-static void PrintLevelName(FILE *stream, int episode, int level)
-{
-    PrintBanner(stream);
-
-    switch (discovered_gamemission)
-    {
-
-        case doom:
-            fprintf(stream, "E%iM%i\n", episode + 1, level + 1);
-            break;
-        case doom2:
-            fprintf(stream, "MAP%02i\n", level + 1);
-            break;
-        default:
-        case none:
-            fprintf(stream, "E%iM%i / MAP%02i\n", 
-                    episode + 1, level + 1, level + 1);
-            break;
-    }
-
-    PrintBanner(stream);
-}
-
-/* Print details of a statistics buffer to the given file. */
-
-static void PrintStats(FILE *stream, wbstartstruct_t *stats)
-{
-    int leveltime, partime;
-    int i;
-
-    PrintLevelName(stream, stats->epsd, stats->last);
-    fprintf(stream, "\n");
-
-    leveltime = stats->plyr[0].stime / TICRATE;
-    partime = stats->partime / TICRATE;
-    fprintf(stream, "Time: %i:%02i", leveltime / 60, leveltime % 60);
-    fprintf(stream, " (par: %i:%02i)\n", partime / 60, partime % 60);
-    fprintf(stream, "\n");
-
-    for (i=0; i<MAXPLAYERS; ++i)
-    {
-        if (stats->plyr[i].in)
-        {
-            PrintPlayerStats(stream, stats, i);
-        }
-    }
-
-    if (GetNumPlayers(stats) >= 2)
-    {
-        PrintFragsTable(stream, stats);
-    }
-
-    fprintf(stream, "\n");
-}
-
-void StatCopy(wbstartstruct_t *stats)
-{
-    if (M_ParmExists("-statdump") && num_captured_stats < MAX_CAPTURES)
-    {
-        memcpy(&captured_stats[num_captured_stats], stats,
-               sizeof(wbstartstruct_t));
-        ++num_captured_stats;
-    }
-}
-
-void StatDump(void)
-{
-    FILE *dumpfile;
-    int i;
-
-    //!
-    // @category compat
-    // @arg <filename>
-    //
-    // Dump statistics information to the specified file on the levels
-    // that were played. The output from this option matches the output
-    // from statdump.exe (see ctrlapi.zip in the /idgames archive).
-    //
-
-    i = M_CheckParmWithArgs("-statdump", 1);
-
-    if (i > 0)
-    {
-        printf("Statistics captured for %i level(s)\n", num_captured_stats);
-
-        // We actually know what the real gamemission is, but this has
-        // to match the output from statdump.exe.
-
-        DiscoverGamemode(captured_stats, num_captured_stats);
-
-        // Allow "-" as output file, for stdout.
-
-        if (strcmp(myargv[i + 1], "-") != 0)
-        {
-            dumpfile = fopen(myargv[i + 1], "w");
-        }
-        else
-        {
-            dumpfile = NULL;
-        }
-
-        for (i = 0; i < num_captured_stats; ++i)
-        {
-            PrintStats(dumpfile, &captured_stats[i]);
-        }
-
-        if (dumpfile != NULL)
-        {
-            fclose(dumpfile);
-        }
-    }
-}
-
+ /*
+
+ Copyright(C) 2005-2014 Simon Howard
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation; either version 2
+ of the License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ GNU General Public License for more details.
+
+ --
+
+ Functions for presenting the information captured from the statistics
+ buffer to a file.
+
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "d_player.h"
+#include "d_mode.h"
+#include "m_argv.h"
+
+#include "statdump.h"
+
+/* Par times for E1M1-E1M9. */
+static const int doom1_par_times[] =
+{
+    30, 75, 120, 90, 165, 180, 180, 30, 165,
+};
+
+/* Par times for MAP01-MAP09. */
+static const int doom2_par_times[] =
+{
+    30, 90, 120, 120, 90, 150, 120, 120, 270,
+};
+
+/* Player colors. */
+static const char *player_colors[] =
+{
+    "Green", "Indigo", "Brown", "Red"
+};
+
+// Array of end-of-level statistics that have been captured.
+
+#define MAX_CAPTURES 32
+static wbstartstruct_t captured_stats[MAX_CAPTURES];
+static int num_captured_stats = 0;
+
+static GameMission_t discovered_gamemission = none;
+
+/* Try to work out whether this is a Doom 1 or Doom 2 game, by looking
+ * at the episode and map, and the par times.  This is used to decide
+ * how to format the level name.  Unfortunately, in some cases it is
+ * impossible to determine whether this is Doom 1 or Doom 2. */
+
+static void DiscoverGamemode(wbstartstruct_t *stats, int num_stats)
+{
+    int partime;
+    int level;
+    int i;
+
+    if (discovered_gamemission != none)
+    {
+        return;
+    }
+
+    for (i=0; i<num_stats; ++i)
+    {
+        level = stats[i].last;
+
+        /* If episode 2, 3 or 4, this is Doom 1. */
+
+        if (stats[i].epsd > 0)
+        {
+            discovered_gamemission = doom;
+            return;
+        }
+
+        /* This is episode 1.  If this is level 10 or higher,
+           it must be Doom 2. */
+
+        if (level >= 9)
+        {
+            discovered_gamemission = doom2;
+            return;
+        }
+
+        /* Try to work out if this is Doom 1 or Doom 2 by looking
+           at the par time. */
+
+        partime = stats[i].partime;
+
+        if (partime == doom1_par_times[level] * TICRATE
+         && partime != doom2_par_times[level] * TICRATE)
+        {
+            discovered_gamemission = doom;
+            return;
+        }
+
+        if (partime != doom1_par_times[level] * TICRATE
+         && partime == doom2_par_times[level] * TICRATE)
+        {
+            discovered_gamemission = doom2;
+            return;
+        }
+    }
+}
+
+/* Returns the number of players active in the given stats buffer. */
+
+static int GetNumPlayers(wbstartstruct_t *stats)
+{
+    int i;
+    int num_players = 0;
+
+    for (i=0; i<MAXPLAYERS; ++i)
+    {
+        if (stats->plyr[i].in)
+        {
+            ++num_players;
+        }
+    }
+
+    return num_players;
+}
+
+static void PrintBanner(FILE *stream)
+{
+    fprintf(stream, "===========================================\n");
+}
+
+static void PrintPercentage(FILE *stream, int amount, int total)
+{
+    if (total == 0)
+    {
+        fprintf(stream, "0");
+    }
+    else
+    {
+        fprintf(stream, "%i / %i", amount, total);
+
+        // statdump.exe is a 16-bit program, so very occasionally an
+        // integer overflow can occur when doing this calculation with
+        // a large value. Therefore, cast to short to give the same
+        // output.
+
+        fprintf(stream, " (%i%%)", (short) (amount * 100) / total);
+    }
+}
+
+/* Display statistics for a single player. */
+
+static void PrintPlayerStats(FILE *stream, wbstartstruct_t *stats,
+        int player_num)
+{
+    wbplayerstruct_t *player = &stats->plyr[player_num];
+
+    fprintf(stream, "Player %i (%s):\n", player_num + 1,
+            player_colors[player_num]);
+
+    /* Kills percentage */
+
+    fprintf(stream, "\tKills: ");
+    PrintPercentage(stream, player->skills, stats->maxkills);
+    fprintf(stream, "\n");
+
+    /* Items percentage */
+
+    fprintf(stream, "\tItems: ");
+    PrintPercentage(stream, player->sitems, stats->maxitems);
+    fprintf(stream, "\n");
+
+    /* Secrets percentage */
+
+    fprintf(stream, "\tSecrets: ");
+    PrintPercentage(stream, player->ssecret, stats->maxsecret);
+    fprintf(stream, "\n");
+}
+
+/* Frags table for multiplayer games. */
+
+static void PrintFragsTable(FILE *stream, wbstartstruct_t *stats)
+{
+    int x, y;
+
+    fprintf(stream, "Frags:\n");
+
+    /* Print header */
+
+    fprintf(stream, "\t\t");
+
+    for (x=0; x<MAXPLAYERS; ++x)
+    {
+
+        if (!stats->plyr[x].in)
+        {
+            continue;
+        }
+
+        fprintf(stream, "%s\t", player_colors[x]);
+    }
+
+    fprintf(stream, "\n");
+
+    fprintf(stream, "\t\t-------------------------------- VICTIMS\n");
+
+    /* Print table */
+
+    for (y=0; y<MAXPLAYERS; ++y)
+    {
+        if (!stats->plyr[y].in)
+        {
+            continue;
+        }
+
+        fprintf(stream, "\t%s\t|", player_colors[y]);
+
+        for (x=0; x<MAXPLAYERS; ++x)
+        {
+            if (!stats->plyr[x].in)
+            {
+                continue;
+            }
+
+            fprintf(stream, "%i\t", stats->plyr[y].frags[x]);
+        }
+
+        fprintf(stream, "\n");
+    }
+
+    fprintf(stream, "\t\t|\n");
+    fprintf(stream, "\t     KILLERS\n");
+}
+
+/* Displays the level name: MAPxy or ExMy, depending on game mode. */
+
+static void PrintLevelName(FILE *stream, int episode, int level)
+{
+    PrintBanner(stream);
+
+    switch (discovered_gamemission)
+    {
+
+        case doom:
+            fprintf(stream, "E%iM%i\n", episode + 1, level + 1);
+            break;
+        case doom2:
+            fprintf(stream, "MAP%02i\n", level + 1);
+            break;
+        default:
+        case none:
+            fprintf(stream, "E%iM%i / MAP%02i\n", 
+                    episode + 1, level + 1, level + 1);
+            break;
+    }
+
+    PrintBanner(stream);
+}
+
+/* Print details of a statistics buffer to the given file. */
+
+static void PrintStats(FILE *stream, wbstartstruct_t *stats)
+{
+    int leveltime, partime;
+    int i;
+
+    PrintLevelName(stream, stats->epsd, stats->last);
+    fprintf(stream, "\n");
+
+    leveltime = stats->plyr[0].stime / TICRATE;
+    partime = stats->partime / TICRATE;
+    fprintf(stream, "Time: %i:%02i", leveltime / 60, leveltime % 60);
+    fprintf(stream, " (par: %i:%02i)\n", partime / 60, partime % 60);
+    fprintf(stream, "\n");
+
+    for (i=0; i<MAXPLAYERS; ++i)
+    {
+        if (stats->plyr[i].in)
+        {
+            PrintPlayerStats(stream, stats, i);
+        }
+    }
+
+    if (GetNumPlayers(stats) >= 2)
+    {
+        PrintFragsTable(stream, stats);
+    }
+
+    fprintf(stream, "\n");
+}
+
+void StatCopy(wbstartstruct_t *stats)
+{
+    if (M_ParmExists("-statdump") && num_captured_stats < MAX_CAPTURES)
+    {
+        memcpy(&captured_stats[num_captured_stats], stats,
+               sizeof(wbstartstruct_t));
+        ++num_captured_stats;
+    }
+}
+
+void StatDump(void)
+{
+    FILE *dumpfile;
+    int i;
+
+    //!
+    // @category compat
+    // @arg <filename>
+    //
+    // Dump statistics information to the specified file on the levels
+    // that were played. The output from this option matches the output
+    // from statdump.exe (see ctrlapi.zip in the /idgames archive).
+    //
+
+    i = M_CheckParmWithArgs("-statdump", 1);
+
+    if (i > 0)
+    {
+        printf("Statistics captured for %i level(s)\n", num_captured_stats);
+
+        // We actually know what the real gamemission is, but this has
+        // to match the output from statdump.exe.
+
+        DiscoverGamemode(captured_stats, num_captured_stats);
+
+        // Allow "-" as output file, for stdout.
+
+        if (strcmp(myargv[i + 1], "-") != 0)
+        {
+            dumpfile = fopen(myargv[i + 1], "w");
+        }
+        else
+        {
+            dumpfile = NULL;
+        }
+
+        for (i = 0; i < num_captured_stats; ++i)
+        {
+            PrintStats(dumpfile, &captured_stats[i]);
+        }
+
+        if (dumpfile != NULL)
+        {
+            fclose(dumpfile);
+        }
+    }
+}
+
diff --git a/src/doom/statdump.h b/src/doom/statdump.h
index bddf9a2..48db2ad 100644
--- a/src/doom/statdump.h
+++ b/src/doom/statdump.h
@@ -1,23 +1,23 @@
- /*
-
- Copyright(C) 2005-2014 Simon Howard
-
- This program is free software; you can redistribute it and/or
- modify it under the terms of the GNU General Public License
- as published by the Free Software Foundation; either version 2
- of the License, or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- GNU General Public License for more details.
-
- */
-
-#ifndef DOOM_STATDUMP_H
-#define DOOM_STATDUMP_H
-
-void StatCopy(wbstartstruct_t *stats);
-void StatDump(void);
-
-#endif /* #ifndef DOOM_STATDUMP_H */
+ /*
+
+ Copyright(C) 2005-2014 Simon Howard
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation; either version 2
+ of the License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ GNU General Public License for more details.
+
+ */
+
+#ifndef DOOM_STATDUMP_H
+#define DOOM_STATDUMP_H
+
+void StatCopy(wbstartstruct_t *stats);
+void StatDump(void);
+
+#endif /* #ifndef DOOM_STATDUMP_H */
diff --git a/src/doom/wi_stuff.c b/src/doom/wi_stuff.c
index ddb9a66..1d6466b 100644
--- a/src/doom/wi_stuff.c
+++ b/src/doom/wi_stuff.c
@@ -1687,7 +1687,7 @@ static void WI_loadUnloadData(load_callback_t callback)
     {
         M_StringCopy(name, DEH_String("INTERPIC"), sizeof(name));
     }
-    else if (gamemode == retail && wbs->epsd == 3)
+    else if (gameversion >= exe_ultimate && wbs->epsd == 3)
     {
         M_StringCopy(name, DEH_String("INTERPIC"), sizeof(name));
     }
@@ -1781,7 +1781,7 @@ void WI_initVariables(wbstartstruct_t* wbstartstruct)
 #ifdef RANGECHECKING
     if (gamemode != commercial)
     {
-      if ( gamemode == retail )
+      if (gameversion >= exe_ultimate)
 	RNGCHECK(wbs->epsd, 0, 3);
       else
 	RNGCHECK(wbs->epsd, 0, 2);
@@ -1810,7 +1810,7 @@ void WI_initVariables(wbstartstruct_t* wbstartstruct)
     if (!wbs->maxsecret)
 	wbs->maxsecret = 1;
 
-    if ( gamemode != retail )
+    if ( gameversion < exe_ultimate )
       if (wbs->epsd > 2)
 	wbs->epsd -= 3;
 }
diff --git a/src/doomtype.h b/src/doomtype.h
index bf0a40e..d2c2883 100644
--- a/src/doomtype.h
+++ b/src/doomtype.h
@@ -31,6 +31,7 @@
 
 #ifdef _WIN32
 
+#include <string.h>
 #define strcasecmp stricmp
 #define strncasecmp strnicmp
 
@@ -52,10 +53,10 @@
 
 #ifdef __GNUC__
 
-#ifdef __clang__
-#define PACKEDATTR __attribute__((packed))
-#else
+#if defined(_WIN32) && !defined(__clang__)
 #define PACKEDATTR __attribute__((packed,gcc_struct))
+#else
+#define PACKEDATTR __attribute__((packed))
 #endif
 
 #else
@@ -72,7 +73,7 @@
 
 #include <inttypes.h>
 
-#ifdef __cplusplus
+#if defined(__cplusplus) || defined(__bool_true_false_are_defined)
 
 // Use builtin bool type with C++.
 
diff --git a/src/gusconf.c b/src/gusconf.c
index f1575c4..c3d0c54 100644
--- a/src/gusconf.c
+++ b/src/gusconf.c
@@ -35,7 +35,9 @@
 typedef struct
 {
     char *patch_names[MAX_INSTRUMENTS];
+    int used[MAX_INSTRUMENTS];
     int mapping[MAX_INSTRUMENTS];
+    unsigned int count;
 } gus_config_t;
 
 char *gus_patch_path = "";
@@ -109,6 +111,7 @@ static int SplitLine(char *line, char **fields, unsigned int max_fields)
 static void ParseLine(gus_config_t *config, char *line)
 {
     char *fields[6];
+    unsigned int i;
     unsigned int num_fields;
     unsigned int instr_id, mapped_id;
 
@@ -120,11 +123,33 @@ static void ParseLine(gus_config_t *config, char *line)
     }
 
     instr_id = atoi(fields[0]);
+
+    // Skip non GM percussions.
+    if ((instr_id >= 128 && instr_id < 128 + 35) || instr_id > 128 + 81)
+    {
+        return;
+    }
+
     mapped_id = atoi(fields[MappingIndex()]);
 
-    free(config->patch_names[instr_id]);
-    config->patch_names[instr_id] = M_StringDuplicate(fields[5]);
-    config->mapping[instr_id] = mapped_id;
+    for (i = 0; i < config->count; i++)
+    {
+        if (config->used[i] == mapped_id)
+        {
+            break;
+        }
+    }
+    
+    if (i == config->count)
+    {
+        // DMX uses wrong patch name (we should use name of 'mapped_id'
+        // instrument, but DMX uses name of 'instr_id' instead).
+        free(config->patch_names[i]);
+        config->patch_names[i] = M_StringDuplicate(fields[5]);
+        config->used[i] = mapped_id;
+        config->count++;
+    }
+    config->mapping[instr_id] = i;
 }
 
 static void ParseDMXConfig(char *dmxconf, gus_config_t *config)
@@ -137,8 +162,11 @@ static void ParseDMXConfig(char *dmxconf, gus_config_t *config)
     for (i = 0; i < MAX_INSTRUMENTS; ++i)
     {
         config->mapping[i] = -1;
+        config->used[i] = -1;
     }
 
+    config->count = 0;
+
     p = dmxconf;
 
     for (;;)
@@ -192,6 +220,7 @@ static char *ReadDMXConfig(void)
     data = Z_Malloc(len + 1, PU_STATIC, NULL);
     W_ReadLump(lumpnum, data);
 
+    data[len] = '\0';
     return data;
 }
 
@@ -224,8 +253,8 @@ static boolean WriteTimidityConfig(char *path, gus_config_t *config)
     }
 
     fprintf(fstream, "\ndrumset 0\n\n");
-
-    for (i = 128 + 25; i < MAX_INSTRUMENTS; ++i)
+    
+    for (i = 128 + 35; i <= 128 + 81; ++i)
     {
         if (config->mapping[i] >= 0 && config->mapping[i] < MAX_INSTRUMENTS
          && config->patch_names[config->mapping[i]] != NULL)
diff --git a/src/heretic.appdata.xml.in b/src/heretic.appdata.xml.in
index dff8441..36a1739 100644
--- a/src/heretic.appdata.xml.in
+++ b/src/heretic.appdata.xml.in
@@ -25,19 +25,19 @@
   </description>
   <screenshots>
     <screenshot type="default">
-      <image>http://www.chocolate-doom.org/wiki/images/9/93/GNOME_Heretic_E5M4.png</image>
+      <image>https://www.chocolate-doom.org/wiki/images/9/93/GNOME_Heretic_E5M4.png</image>
       <caption>Level E5M4: Courtyard</caption>
     </screenshot>
     <screenshot>
-      <image>http://www.chocolate-doom.org/wiki/images/1/14/GNOME_Heretic_Shareware_DEMO3.png</image>
+      <image>https://www.chocolate-doom.org/wiki/images/1/14/GNOME_Heretic_Shareware_DEMO3.png</image>
       <caption>Shareware Level E1M9: The Graveyard</caption>
     </screenshot>
     <screenshot>
-      <image>http://www.chocolate-doom.org/wiki/images/3/34/GNOME_Heretic_E4M1.png</image>
+      <image>https://www.chocolate-doom.org/wiki/images/3/34/GNOME_Heretic_E4M1.png</image>
       <caption>Level E4M1: Catafalque</caption>
     </screenshot>
     <screenshot>
-      <image>http://www.chocolate-doom.org/wiki/images/4/42/GNOME_Heretic_Shareware_DEMO1.png</image>
+      <image>https://www.chocolate-doom.org/wiki/images/4/42/GNOME_Heretic_Shareware_DEMO1.png</image>
       <caption>Shareware Level E1M3: The Gatehouse</caption>
     </screenshot>
   </screenshots>
diff --git a/src/heretic/am_map.c b/src/heretic/am_map.c
index 0e4276a..5010b72 100644
--- a/src/heretic/am_map.c
+++ b/src/heretic/am_map.c
@@ -20,6 +20,7 @@
 
 #include "doomdef.h"
 #include "deh_str.h"
+#include "i_timer.h"
 #include "i_video.h"
 #include "m_controls.h"
 #include "p_local.h"
@@ -83,7 +84,11 @@ char *LevelNames[] = {
     "E5M6:  COLONNADE",
     "E5M7:  FOETID MANSE",
     "E5M8:  FIELD OF JUDGEMENT",
-    "E5M9:  SKEIN OF D'SPARIL"
+    "E5M9:  SKEIN OF D'SPARIL",
+    // EPISODE 6: unnamed
+    "E6M1:  ",
+    "E6M2:  ",
+    "E6M3:  ",
 };
 
 static int cheating = 0;
@@ -504,10 +509,29 @@ boolean AM_Responder(event_t * ev)
     int rc;
     int key;
     static int bigstate = 0;
+    static int joywait = 0;
 
     key = ev->data1;
     rc = false;
 
+    if (ev->type == ev_joystick && joybautomap >= 0
+        && (ev->data1 & (1 << joybautomap)) != 0 && joywait < I_GetTime())
+    {
+        joywait = I_GetTime() + 5;
+
+        if (!automapactive)
+        {
+            AM_Start ();
+            viewactive = false;
+        }
+        else
+        {
+            bigstate = 0;
+            viewactive = true;
+            AM_Stop ();
+        }
+    }
+
     if (!automapactive)
     {
 
diff --git a/src/heretic/d_main.c b/src/heretic/d_main.c
index 6521aff..b63a248 100644
--- a/src/heretic/d_main.c
+++ b/src/heretic/d_main.c
@@ -755,11 +755,17 @@ void D_BindVariables(void)
     M_BindMenuControls();
     M_BindMapControls();
 
+#ifdef FEATURE_MULTIPLAYER
+    NET_BindVariables();
+#endif
+
     M_BindIntVariable("mouse_sensitivity",      &mouseSensitivity);
     M_BindIntVariable("sfx_volume",             &snd_MaxVolume);
     M_BindIntVariable("music_volume",           &snd_MusicVolume);
     M_BindIntVariable("screenblocks",           &screenblocks);
     M_BindIntVariable("snd_channels",           &snd_Channels);
+    M_BindIntVariable("vanilla_savegame_limit", &vanilla_savegame_limit);
+    M_BindIntVariable("vanilla_demo_limit",     &vanilla_demo_limit);
     M_BindIntVariable("show_endoom",            &show_endoom);
     M_BindIntVariable("graphical_startup",      &graphical_startup);
 
@@ -1018,7 +1024,7 @@ void D_DoomMain(void)
 
         if (D_AddFile(file))
         {
-            M_StringCopy(demolumpname, lumpinfo[numlumps - 1].name,
+            M_StringCopy(demolumpname, lumpinfo[numlumps - 1]->name,
                          sizeof(demolumpname));
         }
         else
@@ -1031,6 +1037,15 @@ void D_DoomMain(void)
         printf("Playing demo %s.\n", file);
     }
 
+    //!
+    // @category demo
+    //
+    // Record or playback a demo without automatically quitting
+    // after either level exit or player respawn.
+    //
+
+    demoextend = M_ParmExists("-demoextend");
+
     if (W_CheckNumForName(DEH_String("E2M1")) == -1)
     {
         gamemode = shareware;
diff --git a/src/heretic/d_net.c b/src/heretic/d_net.c
index 517df0c..85f8bfa 100644
--- a/src/heretic/d_net.c
+++ b/src/heretic/d_net.c
@@ -115,10 +115,17 @@ static void LoadGameSettings(net_gamesettings_t *settings)
     startmap = settings->map;
     startskill = settings->skill;
     // TODO startloadgame = settings->loadgame;
+    lowres_turn = settings->lowres_turn;
     nomonsters = settings->nomonsters;
     respawnparm = settings->respawn_monsters;
     consoleplayer = settings->consoleplayer;
 
+    if (lowres_turn)
+    {
+        printf("NOTE: Turning resolution is reduced; this is probably "
+               "because there is a client recording a Vanilla demo.\n");
+    }
+
     for (i = 0; i < MAXPLAYERS; ++i)
     {
         playeringame[i] = i < settings->num_players;
@@ -142,7 +149,9 @@ static void SaveGameSettings(net_gamesettings_t *settings)
     settings->nomonsters = nomonsters;
     settings->respawn_monsters = respawnparm;
     settings->timelimit = 0;
-    settings->lowres_turn = false;
+
+    settings->lowres_turn = M_ParmExists("-record")
+                         && !M_ParmExists("-longtics");
 }
 
 static void InitConnectData(net_connect_data_t *connect_data)
@@ -159,7 +168,10 @@ static void InitConnectData(net_connect_data_t *connect_data)
     connect_data->gamemode = gamemode;
     connect_data->gamemission = heretic;
 
-    connect_data->lowres_turn = false;
+    // Are we recording a demo? Possibly set lowres turn mode
+
+    connect_data->lowres_turn = M_ParmExists("-record")
+                             && !M_ParmExists("-longtics");
 
     // Read checksums of our WAD directory and dehacked information
 
diff --git a/src/heretic/doomdef.h b/src/heretic/doomdef.h
index afb11b6..5e3dc59 100644
--- a/src/heretic/doomdef.h
+++ b/src/heretic/doomdef.h
@@ -528,8 +528,13 @@ extern int GetWeaponAmmo[NUMWEAPONS];
 
 extern boolean demorecording;
 extern boolean demoplayback;
+extern boolean demoextend;      // allow demos to persist through exit/respawn
 extern int skytexture;
 
+// Truncate angleturn in ticcmds to nearest 256.
+// Used when recording Vanilla demos in netgames.
+extern boolean lowres_turn;
+
 extern gamestate_t gamestate;
 extern skill_t gameskill;
 extern boolean respawnmonsters;
@@ -564,6 +569,9 @@ extern boolean autostart;
 extern boolean testcontrols;
 extern int testcontrols_mousespeed;
 
+extern int vanilla_savegame_limit;
+extern int vanilla_demo_limit;
+
 /*
 ===============================================================================
 
diff --git a/src/heretic/g_game.c b/src/heretic/g_game.c
index 6ad3860..6b6dbb9 100644
--- a/src/heretic/g_game.c
+++ b/src/heretic/g_game.c
@@ -24,6 +24,7 @@
 #include "deh_str.h"
 #include "i_timer.h"
 #include "i_system.h"
+#include "m_argv.h"
 #include "m_controls.h"
 #include "m_misc.h"
 #include "m_random.h"
@@ -109,8 +110,12 @@ int mouseSensitivity;
 
 char demoname[32];
 boolean demorecording;
+boolean longtics;               // specify high resolution turning in demos
+boolean lowres_turn;
+boolean shortticfix;            // calculate lowres turning like doom
 boolean demoplayback;
-byte *demobuffer, *demo_p;
+boolean demoextend;
+byte *demobuffer, *demo_p, *demoend;
 boolean singledemo;             // quit after playing a demo from cmdline
 
 boolean precache = true;        // if true, load all graphics at start
@@ -193,6 +198,8 @@ boolean *joybuttons = &joyarray[1];     // allow [-1]
 int savegameslot;
 char savedescription[32];
 
+int vanilla_demo_limit = 1;
+
 int inventoryTics;
 
 // haleyjd: removed WATCOMC
@@ -621,6 +628,32 @@ void G_BuildTiccmd(ticcmd_t *cmd, int maketic)
             BT_SPECIAL | BTS_SAVEGAME | (savegameslot << BTS_SAVESHIFT);
     }
 
+    if (lowres_turn)
+    {
+        if (shortticfix)
+        {
+            static signed short carry = 0;
+            signed short desired_angleturn;
+
+            desired_angleturn = cmd->angleturn + carry;
+
+            // round angleturn to the nearest 256 unit boundary
+            // for recording demos with single byte values for turn
+
+            cmd->angleturn = (desired_angleturn + 128) & 0xff00;
+
+            // Carry forward the error from the reduced resolution to the
+            // next tic, so that successive small movements can accumulate.
+
+            carry = desired_angleturn - cmd->angleturn;
+        }
+        else
+        {
+            // truncate angleturn to the nearest 256 boundary
+            // for recording demos with single byte values for turn
+            cmd->angleturn &= 0xff00;
+        }
+    }
 }
 
 
@@ -647,7 +680,6 @@ void G_DoLoadLevel(void)
 
     P_SetupLevel(gameepisode, gamemap, 0, gameskill);
     displayplayer = consoleplayer;      // view the guy you are playing
-    starttime = I_GetTime();
     gameaction = ga_nothing;
     Z_CheckHeap();
 
@@ -1290,7 +1322,8 @@ void G_DoReborn(int playernum)
 {
     int i;
 
-    if (G_CheckDemoStatus())
+    // quit demo unless -demoextend
+    if (!demoextend && G_CheckDemoStatus())
         return;
     if (!netgame)
         gameaction = ga_loadlevel;      // reload the level from scratch
@@ -1359,7 +1392,9 @@ void G_DoCompleted(void)
     static int afterSecret[5] = { 7, 5, 5, 5, 4 };
 
     gameaction = ga_nothing;
-    if (G_CheckDemoStatus())
+
+    // quit demo unless -demoextend
+    if (!demoextend && G_CheckDemoStatus())
     {
         return;
     }
@@ -1619,6 +1654,9 @@ void G_InitNew(skill_t skill, int episode, int map)
 */
 
 #define DEMOMARKER      0x80
+#define DEMOHEADER_RESPAWN    0x20
+#define DEMOHEADER_LONGTICS   0x10
+#define DEMOHEADER_NOMONSTERS 0x02
 
 void G_ReadDemoTiccmd(ticcmd_t * cmd)
 {
@@ -1629,23 +1667,104 @@ void G_ReadDemoTiccmd(ticcmd_t * cmd)
     }
     cmd->forwardmove = ((signed char) *demo_p++);
     cmd->sidemove = ((signed char) *demo_p++);
-    cmd->angleturn = ((unsigned char) *demo_p++) << 8;
+
+    // If this is a longtics demo, read back in higher resolution
+
+    if (longtics)
+    {
+        cmd->angleturn = *demo_p++;
+        cmd->angleturn |= (*demo_p++) << 8;
+    }
+    else
+    {
+        cmd->angleturn = ((unsigned char) *demo_p++) << 8;
+    }
+
     cmd->buttons = (unsigned char) *demo_p++;
     cmd->lookfly = (unsigned char) *demo_p++;
     cmd->arti = (unsigned char) *demo_p++;
 }
 
+// Increase the size of the demo buffer to allow unlimited demos
+
+static void IncreaseDemoBuffer(void)
+{
+    int current_length;
+    byte *new_demobuffer;
+    byte *new_demop;
+    int new_length;
+
+    // Find the current size
+
+    current_length = demoend - demobuffer;
+
+    // Generate a new buffer twice the size
+    new_length = current_length * 2;
+
+    new_demobuffer = Z_Malloc(new_length, PU_STATIC, 0);
+    new_demop = new_demobuffer + (demo_p - demobuffer);
+
+    // Copy over the old data
+
+    memcpy(new_demobuffer, demobuffer, current_length);
+
+    // Free the old buffer and point the demo pointers at the new buffer.
+
+    Z_Free(demobuffer);
+
+    demobuffer = new_demobuffer;
+    demo_p = new_demop;
+    demoend = demobuffer + new_length;
+}
+
 void G_WriteDemoTiccmd(ticcmd_t * cmd)
 {
-    if (gamekeydown['q'])       // press q to end demo recording
+    byte *demo_start;
+
+    if (gamekeydown[key_demo_quit]) // press to end demo recording
         G_CheckDemoStatus();
+
+    demo_start = demo_p;
+
     *demo_p++ = cmd->forwardmove;
     *demo_p++ = cmd->sidemove;
-    *demo_p++ = cmd->angleturn >> 8;
+
+    // If this is a longtics demo, record in higher resolution
+
+    if (longtics)
+    {
+        *demo_p++ = (cmd->angleturn & 0xff);
+        *demo_p++ = (cmd->angleturn >> 8) & 0xff;
+    }
+    else
+    {
+        *demo_p++ = cmd->angleturn >> 8;
+    }
+
     *demo_p++ = cmd->buttons;
     *demo_p++ = cmd->lookfly;
     *demo_p++ = cmd->arti;
-    demo_p -= 6;
+
+    // reset demo pointer back
+    demo_p = demo_start;
+
+    if (demo_p > demoend - 16)
+    {
+        if (vanilla_demo_limit)
+        {
+            // no more space
+            G_CheckDemoStatus();
+            return;
+        }
+        else
+        {
+            // Vanilla demo limit disabled: unlimited
+            // demo lengths!
+
+            IncreaseDemoBuffer();
+        }
+    }
+
     G_ReadDemoTiccmd(cmd);      // make SURE it is exactly the same
 }
 
@@ -1663,17 +1782,76 @@ void G_RecordDemo(skill_t skill, int numplayers, int episode, int map,
                   char *name)
 {
     int i;
+    int maxsize;
+
+    //!
+    // @category demo
+    //
+    // Record or playback a demo with high resolution turning.
+    //
+
+    longtics = D_NonVanillaRecord(M_ParmExists("-longtics"),
+                                  "vvHeretic longtics demo");
+
+    // If not recording a longtics demo, record in low res
+
+    lowres_turn = !longtics;
+
+    //!
+    // @category demo
+    //
+    // Smooth out low resolution turning when recording a demo.
+    //
+
+    shortticfix = M_ParmExists("-shortticfix");
 
     G_InitNew(skill, episode, map);
     usergame = false;
     M_StringCopy(demoname, name, sizeof(demoname));
     M_StringConcat(demoname, ".lmp", sizeof(demoname));
-    demobuffer = demo_p = Z_Malloc(0x20000, PU_STATIC, NULL);
+    maxsize = 0x20000;
+
+    //!
+    // @arg <size>
+    // @category demo
+    // @vanilla
+    //
+    // Specify the demo buffer size (KiB)
+    //
+
+    i = M_CheckParmWithArgs("-maxdemo", 1);
+    if (i)
+        maxsize = atoi(myargv[i + 1]) * 1024;
+    demobuffer = Z_Malloc(maxsize, PU_STATIC, NULL);
+    demoend = demobuffer + maxsize;
+
+    demo_p = demobuffer;
     *demo_p++ = skill;
     *demo_p++ = episode;
     *demo_p++ = map;
 
-    for (i = 0; i < MAXPLAYERS; i++)
+    // Write special parameter bits onto player one byte.
+    // This aligns with vvHeretic demo usage:
+    //   0x20 = -respawn
+    //   0x10 = -longtics
+    //   0x02 = -nomonsters
+
+    *demo_p = 1; // assume player one exists
+    if (D_NonVanillaRecord(respawnparm, "vvHeretic -respawn header flag"))
+    {
+        *demo_p |= DEMOHEADER_RESPAWN;
+    }
+    if (longtics)
+    {
+        *demo_p |= DEMOHEADER_LONGTICS;
+    }
+    if (D_NonVanillaRecord(nomonsters, "vvHeretic -nomonsters header flag"))
+    {
+        *demo_p |= DEMOHEADER_NOMONSTERS;
+    }
+    demo_p++;
+
+    for (i = 1; i < MAXPLAYERS; i++)
         *demo_p++ = playeringame[i];
 
     demorecording = true;
@@ -1699,16 +1877,36 @@ void G_DeferedPlayDemo(char *name)
 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++;
 
+    // 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++;
+        playeringame[i] = (*demo_p++) != 0;
 
     precache = false;           // don't spend a lot of time in loadlevel
     G_InitNew(skill, episode, map);
@@ -1736,12 +1934,21 @@ void G_TimeDemo(char *name)
     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;
+
     for (i = 0; i < MAXPLAYERS; i++)
     {
-        playeringame[i] = *demo_p++;
+        playeringame[i] = (*demo_p++) != 0;
     }
 
     G_InitNew(skill, episode, map);
+    starttime = I_GetTime();
+
     usergame = false;
     demoplayback = true;
     timingdemo = true;
diff --git a/src/heretic/m_random.c b/src/heretic/m_random.c
index 53b0f4d..7d535b8 100644
--- a/src/heretic/m_random.c
+++ b/src/heretic/m_random.c
@@ -68,3 +68,9 @@ void M_ClearRandom(void)
     rndindex = prndindex = 0;
 }
 
+// inspired by the same routine in Eternity, thanks haleyjd
+int P_SubRandom (void)
+{
+    int r = P_Random();
+    return r - P_Random();
+}
diff --git a/src/heretic/m_random.h b/src/heretic/m_random.h
index 817d757..d7ec2d1 100644
--- a/src/heretic/m_random.h
+++ b/src/heretic/m_random.h
@@ -30,5 +30,8 @@ void M_ClearRandom(void);
 
 extern int rndindex;
 
+// Defined version of P_Random() - P_Random()
+int P_SubRandom (void);
+
 #endif // HERETIC_M_RANDOM_H
 
diff --git a/src/heretic/p_enemy.c b/src/heretic/p_enemy.c
index 55608c5..882be72 100644
--- a/src/heretic/p_enemy.c
+++ b/src/heretic/p_enemy.c
@@ -813,7 +813,7 @@ void A_FaceTarget(mobj_t * actor)
                                    actor->target->y);
     if (actor->target->flags & MF_SHADOW)
     {                           // Target is a ghost
-        actor->angle += (P_Random() - P_Random()) << 21;
+        actor->angle += P_SubRandom() << 21;
     }
 }
 
@@ -840,12 +840,16 @@ void A_Pain(mobj_t * actor)
 void A_DripBlood(mobj_t * actor)
 {
     mobj_t *mo;
+    int r1,r2;
 
-    mo = P_SpawnMobj(actor->x + ((P_Random() - P_Random()) << 11),
-                     actor->y + ((P_Random() - P_Random()) << 11), actor->z,
+    r1 = P_SubRandom();
+    r2 = P_SubRandom();
+
+    mo = P_SpawnMobj(actor->x + (r2 << 11),
+                     actor->y + (r1 << 11), actor->z,
                      MT_BLOOD);
-    mo->momx = (P_Random() - P_Random()) << 10;
-    mo->momy = (P_Random() - P_Random()) << 10;
+    mo->momx = P_SubRandom() << 10;
+    mo->momy = P_SubRandom() << 10;
     mo->flags2 |= MF2_LOGRAV;
 }
 
@@ -889,12 +893,12 @@ void A_ImpExplode(mobj_t * actor)
     mobj_t *mo;
 
     mo = P_SpawnMobj(actor->x, actor->y, actor->z, MT_IMPCHUNK1);
-    mo->momx = (P_Random() - P_Random()) << 10;
-    mo->momy = (P_Random() - P_Random()) << 10;
+    mo->momx = P_SubRandom() << 10;
+    mo->momy = P_SubRandom() << 10;
     mo->momz = 9 * FRACUNIT;
     mo = P_SpawnMobj(actor->x, actor->y, actor->z, MT_IMPCHUNK2);
-    mo->momx = (P_Random() - P_Random()) << 10;
-    mo->momy = (P_Random() - P_Random()) << 10;
+    mo->momx = P_SubRandom() << 10;
+    mo->momy = P_SubRandom() << 10;
     mo->momz = 9 * FRACUNIT;
     if (actor->special1.i == 666)
     {                           // Extreme death crash
@@ -912,9 +916,13 @@ void A_BeastPuff(mobj_t * actor)
 {
     if (P_Random() > 64)
     {
-        P_SpawnMobj(actor->x + ((P_Random() - P_Random()) << 10),
-                    actor->y + ((P_Random() - P_Random()) << 10),
-                    actor->z + ((P_Random() - P_Random()) << 10), MT_PUFFY);
+        int r1,r2,r3;
+        r1 = P_SubRandom();
+        r2 = P_SubRandom();
+        r3 = P_SubRandom();
+        P_SpawnMobj(actor->x + (r3 << 10),
+                    actor->y + (r2 << 10),
+                    actor->z + (r1 << 10), MT_PUFFY);
     }
 }
 
@@ -1179,8 +1187,8 @@ void A_Feathers(mobj_t * actor)
         mo = P_SpawnMobj(actor->x, actor->y, actor->z + 20 * FRACUNIT,
                          MT_FEATHER);
         mo->target = actor;
-        mo->momx = (P_Random() - P_Random()) << 8;
-        mo->momy = (P_Random() - P_Random()) << 8;
+        mo->momx = P_SubRandom() << 8;
+        mo->momy = P_SubRandom() << 8;
         mo->momz = FRACUNIT + (P_Random() << 9);
         P_SetMobjState(mo, S_FEATHER1 + (P_Random() & 7));
     }
@@ -1475,8 +1483,8 @@ void A_BlueSpark(mobj_t * actor)
     for (i = 0; i < 2; i++)
     {
         mo = P_SpawnMobj(actor->x, actor->y, actor->z, MT_SOR2FXSPARK);
-        mo->momx = (P_Random() - P_Random()) << 9;
-        mo->momy = (P_Random() - P_Random()) << 9;
+        mo->momx = P_SubRandom() << 9;
+        mo->momy = P_SubRandom() << 9;
         mo->momz = FRACUNIT + (P_Random() << 8);
     }
 }
@@ -1755,10 +1763,14 @@ void A_MinotaurAtk3(mobj_t * actor)
 void A_MntrFloorFire(mobj_t * actor)
 {
     mobj_t *mo;
+    int r1, r2;
+
+    r1 = P_SubRandom();
+    r2 = P_SubRandom();
 
     actor->z = actor->floorz;
-    mo = P_SpawnMobj(actor->x + ((P_Random() - P_Random()) << 10),
-                     actor->y + ((P_Random() - P_Random()) << 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
@@ -2122,8 +2134,8 @@ void P_DropItem(mobj_t * source, mobjtype_t type, int special, int chance)
     }
     mo = P_SpawnMobj(source->x, source->y,
                      source->z + (source->height >> 1), type);
-    mo->momx = (P_Random() - P_Random()) << 8;
-    mo->momy = (P_Random() - P_Random()) << 8;
+    mo->momx = P_SubRandom() << 8;
+    mo->momy = P_SubRandom() << 8;
     mo->momz = FRACUNIT * 5 + (P_Random() << 10);
     mo->flags |= MF_DROPPED;
     mo->health = special;
@@ -2233,8 +2245,8 @@ void A_PodPain(mobj_t * actor)
         goo = P_SpawnMobj(actor->x, actor->y,
                           actor->z + 48 * FRACUNIT, MT_PODGOO);
         goo->target = actor;
-        goo->momx = (P_Random() - P_Random()) << 9;
-        goo->momy = (P_Random() - P_Random()) << 9;
+        goo->momx = P_SubRandom() << 9;
+        goo->momy = P_SubRandom() << 9;
         goo->momz = FRACUNIT / 2 + (P_Random() << 9);
     }
 }
@@ -2404,9 +2416,12 @@ void A_ESound(mobj_t * mo)
 void A_SpawnTeleGlitter(mobj_t * actor)
 {
     mobj_t *mo;
+    int r1, r2;
 
-    mo = P_SpawnMobj(actor->x + ((P_Random() & 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;
 }
@@ -2420,9 +2435,12 @@ void A_SpawnTeleGlitter(mobj_t * actor)
 void A_SpawnTeleGlitter2(mobj_t * actor)
 {
     mobj_t *mo;
+    int r1, r2;
 
-    mo = P_SpawnMobj(actor->x + ((P_Random() & 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;
 }
@@ -2560,8 +2578,8 @@ void A_SkullPop(mobj_t * actor)
     mo = P_SpawnMobj(actor->x, actor->y, actor->z + 48 * FRACUNIT,
                      MT_BLOODYSKULL);
     //mo->target = actor;
-    mo->momx = (P_Random() - P_Random()) << 9;
-    mo->momy = (P_Random() - P_Random()) << 9;
+    mo->momx = P_SubRandom() << 9;
+    mo->momy = P_SubRandom() << 9;
     mo->momz = FRACUNIT * 2 + (P_Random() << 6);
     // Attach player mobj to bloody skull
     player = actor->player;
diff --git a/src/heretic/p_inter.c b/src/heretic/p_inter.c
index 81150f0..190bf85 100644
--- a/src/heretic/p_inter.c
+++ b/src/heretic/p_inter.c
@@ -988,9 +988,9 @@ void P_TouchWhirlwind(mobj_t * target)
 {
     int randVal;
 
-    target->angle += (P_Random() - P_Random()) << 20;
-    target->momx += (P_Random() - P_Random()) << 10;
-    target->momy += (P_Random() - P_Random()) << 10;
+    target->angle += P_SubRandom() << 20;
+    target->momx += P_SubRandom() << 10;
+    target->momy += P_SubRandom() << 10;
     if (leveltime & 16 && !(target->flags2 & MF2_BOSS))
     {
         randVal = P_Random();
diff --git a/src/heretic/p_map.c b/src/heretic/p_map.c
index 6f1e64d..8afcf86 100644
--- a/src/heretic/p_map.c
+++ b/src/heretic/p_map.c
@@ -1656,8 +1656,8 @@ boolean PIT_ChangeSector(mobj_t * thing)
         // spray blood in a random direction
         mo = P_SpawnMobj(thing->x, thing->y, thing->z + thing->height / 2,
                          MT_BLOOD);
-        mo->momx = (P_Random() - P_Random()) << 12;
-        mo->momy = (P_Random() - P_Random()) << 12;
+        mo->momx = P_SubRandom() << 12;
+        mo->momy = P_SubRandom() << 12;
     }
 
     return true;                // keep checking (crush other things)   
diff --git a/src/heretic/p_mobj.c b/src/heretic/p_mobj.c
index 732a46b..00e6a33 100644
--- a/src/heretic/p_mobj.c
+++ b/src/heretic/p_mobj.c
@@ -1192,7 +1192,7 @@ void P_SpawnPuff(fixed_t x, fixed_t y, fixed_t z)
 {
     mobj_t *puff;
 
-    z += ((P_Random() - P_Random()) << 10);
+    z += (P_SubRandom() << 10);
     puff = P_SpawnMobj(x, y, z, PuffType);
     if (puff->info->attacksound)
     {
@@ -1225,7 +1225,7 @@ void P_SpawnBlood (fixed_t x, fixed_t y, fixed_t z, int damage)
 {
 	mobj_t  *th;
 	
-	z += ((P_Random()-P_Random())<<10);
+	z += (P_SubRandom()<<10);
 	th = P_SpawnMobj (x,y,z, MT_BLOOD);
 	th->momz = FRACUNIT*2;
 	th->tics -= P_Random()&3;
@@ -1249,8 +1249,8 @@ void P_BloodSplatter(fixed_t x, fixed_t y, fixed_t z, mobj_t * originator)
 
     mo = P_SpawnMobj(x, y, z, MT_BLOODSPLATTER);
     mo->target = originator;
-    mo->momx = (P_Random() - P_Random()) << 9;
-    mo->momy = (P_Random() - P_Random()) << 9;
+    mo->momx = P_SubRandom() << 9;
+    mo->momy = P_SubRandom() << 9;
     mo->momz = FRACUNIT * 2;
 }
 
@@ -1265,9 +1265,9 @@ void P_RipperBlood(mobj_t * mo)
     mobj_t *th;
     fixed_t x, y, z;
 
-    x = mo->x + ((P_Random() - P_Random()) << 12);
-    y = mo->y + ((P_Random() - P_Random()) << 12);
-    z = mo->z + ((P_Random() - P_Random()) << 12);
+    x = mo->x + (P_SubRandom() << 12);
+    y = mo->y + (P_SubRandom() << 12);
+    z = mo->z + (P_SubRandom() << 12);
     th = P_SpawnMobj(x, y, z, MT_BLOOD);
     th->flags |= MF_NOGRAVITY;
     th->momx = mo->momx >> 1;
@@ -1317,8 +1317,8 @@ int P_HitFloor(mobj_t * thing)
             P_SpawnMobj(thing->x, thing->y, ONFLOORZ, MT_SPLASHBASE);
             mo = P_SpawnMobj(thing->x, thing->y, ONFLOORZ, MT_SPLASH);
             mo->target = thing;
-            mo->momx = (P_Random() - P_Random()) << 8;
-            mo->momy = (P_Random() - P_Random()) << 8;
+            mo->momx = P_SubRandom() << 8;
+            mo->momy = P_SubRandom() << 8;
             mo->momz = 2 * FRACUNIT + (P_Random() << 8);
             S_StartSound(mo, sfx_gloop);
             return (FLOOR_WATER);
@@ -1332,8 +1332,8 @@ int P_HitFloor(mobj_t * thing)
             P_SpawnMobj(thing->x, thing->y, ONFLOORZ, MT_SLUDGESPLASH);
             mo = P_SpawnMobj(thing->x, thing->y, ONFLOORZ, MT_SLUDGECHUNK);
             mo->target = thing;
-            mo->momx = (P_Random() - P_Random()) << 8;
-            mo->momy = (P_Random() - P_Random()) << 8;
+            mo->momx = P_SubRandom() << 8;
+            mo->momy = P_SubRandom() << 8;
             mo->momz = FRACUNIT + (P_Random() << 8);
             return (FLOOR_SLUDGE);
     }
@@ -1414,7 +1414,7 @@ mobj_t *P_SpawnMissile(mobj_t * source, mobj_t * dest, mobjtype_t type)
     an = R_PointToAngle2(source->x, source->y, dest->x, dest->y);
     if (dest->flags & MF_SHADOW)
     {                           // Invisible target
-        an += (P_Random() - P_Random()) << 21;
+        an += P_SubRandom() << 21;
     }
     th->angle = an;
     an >>= ANGLETOFINESHIFT;
diff --git a/src/heretic/p_pspr.c b/src/heretic/p_pspr.c
index 813dd6a..7daeebc 100644
--- a/src/heretic/p_pspr.c
+++ b/src/heretic/p_pspr.c
@@ -896,7 +896,7 @@ void A_StaffAttackPL1(player_t * player, pspdef_t * psp)
 
     damage = 5 + (P_Random() & 15);
     angle = player->mo->angle;
-    angle += (P_Random() - P_Random()) << 18;
+    angle += P_SubRandom() << 18;
     slope = P_AimLineAttack(player->mo, angle, MELEERANGE);
     PuffType = MT_STAFFPUFF;
     P_LineAttack(player->mo, angle, MELEERANGE, slope, damage);
@@ -925,7 +925,7 @@ void A_StaffAttackPL2(player_t * player, pspdef_t * psp)
     // P_inter.c:P_DamageMobj() handles target momentums
     damage = 18 + (P_Random() & 63);
     angle = player->mo->angle;
-    angle += (P_Random() - P_Random()) << 18;
+    angle += P_SubRandom() << 18;
     slope = P_AimLineAttack(player->mo, angle, MELEERANGE);
     PuffType = MT_STAFFPUFF2;
     P_LineAttack(player->mo, angle, MELEERANGE, slope, damage);
@@ -959,7 +959,7 @@ void A_FireBlasterPL1(player_t * player, pspdef_t * psp)
     angle = mo->angle;
     if (player->refire)
     {
-        angle += (P_Random() - P_Random()) << 18;
+        angle += P_SubRandom() << 18;
     }
     PuffType = MT_BLASTERPUFF1;
     P_LineAttack(mo, angle, MISSILERANGE, bulletslope, damage);
@@ -1005,7 +1005,7 @@ void A_FireGoldWandPL1(player_t * player, pspdef_t * psp)
     angle = mo->angle;
     if (player->refire)
     {
-        angle += (P_Random() - P_Random()) << 18;
+        angle += P_SubRandom() << 18;
     }
     PuffType = MT_GOLDWANDPUFF1;
     P_LineAttack(mo, angle, MISSILERANGE, bulletslope, damage);
@@ -1397,8 +1397,8 @@ void A_BoltSpark(mobj_t * bolt)
     if (P_Random() > 50)
     {
         spark = P_SpawnMobj(bolt->x, bolt->y, bolt->z, MT_CRBOWFX4);
-        spark->x += (P_Random() - P_Random()) << 10;
-        spark->y += (P_Random() - P_Random()) << 10;
+        spark->x += P_SubRandom() << 10;
+        spark->y += P_SubRandom() << 10;
     }
 }
 
@@ -1696,8 +1696,8 @@ void A_FirePhoenixPL2(player_t * player, pspdef_t * psp)
     }
     pmo = player->mo;
     angle = pmo->angle;
-    x = pmo->x + ((P_Random() - P_Random()) << 9);
-    y = pmo->y + ((P_Random() - P_Random()) << 9);
+    x = pmo->x + (P_SubRandom() << 9);
+    y = pmo->y + (P_SubRandom() << 9);
     z = pmo->z + 26 * FRACUNIT + ((player->lookdir) << FRACBITS) / 173;
     if (pmo->flags2 & MF2_FEETARECLIPPED)
     {
@@ -1773,14 +1773,14 @@ void A_GauntletAttack(player_t * player, pspdef_t * psp)
     {
         damage = HITDICE(2);
         dist = 4 * MELEERANGE;
-        angle += (P_Random() - P_Random()) << 17;
+        angle += P_SubRandom() << 17;
         PuffType = MT_GAUNTLETPUFF2;
     }
     else
     {
         damage = HITDICE(2);
         dist = MELEERANGE + 1;
-        angle += (P_Random() - P_Random()) << 18;
+        angle += P_SubRandom() << 18;
         PuffType = MT_GAUNTLETPUFF1;
     }
     slope = P_AimLineAttack(player->mo, angle, dist);
diff --git a/src/heretic/p_saveg.c b/src/heretic/p_saveg.c
index 34e9e90..8f33b31 100644
--- a/src/heretic/p_saveg.c
+++ b/src/heretic/p_saveg.c
@@ -25,12 +25,9 @@
 #include "p_local.h"
 #include "v_video.h"
 
-#define SVG_RAM 0
-#define SVG_FILE 1
-
 static FILE *SaveGameFP;
-static int SaveGameType;
-static byte *savebuffer, *save_p;
+
+int vanilla_savegame_limit = 1;
 
 
 //==========================================================================
@@ -63,13 +60,11 @@ char *SV_Filename(int slot)
 
 void SV_Open(char *fileName)
 {
-    SaveGameType = SVG_FILE;
     SaveGameFP = fopen(fileName, "wb");
 }
 
 void SV_OpenRead(char *filename)
 {
-    SaveGameType = SVG_FILE;
     SaveGameFP = fopen(filename, "rb");
 }
 
@@ -81,23 +76,16 @@ void SV_OpenRead(char *filename)
 
 void SV_Close(char *fileName)
 {
-    int length;
-
     SV_WriteByte(SAVE_GAME_TERMINATOR);
-    if (SaveGameType == SVG_RAM)
+
+    // Enforce the same savegame size limit as in Vanilla Heretic
+
+    if (vanilla_savegame_limit && ftell(SaveGameFP) > SAVEGAMESIZE)
     {
-        length = save_p - savebuffer;
-        if (length > SAVEGAMESIZE)
-        {
-            I_Error("Savegame buffer overrun");
-        }
-        M_WriteFile(fileName, savebuffer, length);
-        Z_Free(savebuffer);
-    }
-    else
-    {                           // SVG_FILE
-        fclose(SaveGameFP);
+        I_Error("Savegame buffer overrun");
     }
+
+    fclose(SaveGameFP);
 }
 
 //==========================================================================
@@ -108,15 +96,7 @@ void SV_Close(char *fileName)
 
 void SV_Write(void *buffer, int size)
 {
-    if (SaveGameType == SVG_RAM)
-    {
-        memcpy(save_p, buffer, size);
-        save_p += size;
-    }
-    else
-    {                           // SVG_FILE
-        fwrite(buffer, size, 1, SaveGameFP);
-    }
+    fwrite(buffer, size, 1, SaveGameFP);
 }
 
 void SV_WriteByte(byte val)
@@ -151,15 +131,7 @@ void SV_WritePtr(void *ptr)
 
 void SV_Read(void *buffer, int size)
 {
-    if (SaveGameType == SVG_RAM)
-    {
-        memcpy(buffer, save_p, size);
-        save_p += size;
-    }
-    else
-    {                           // SVG_FILE
-        fread(buffer, size, 1, SaveGameFP);
-    }
+    fread(buffer, size, 1, SaveGameFP);
 }
 
 byte SV_ReadByte(void)
diff --git a/src/heretic/p_user.c b/src/heretic/p_user.c
index f4011ee..9c22d0b 100644
--- a/src/heretic/p_user.c
+++ b/src/heretic/p_user.c
@@ -421,7 +421,7 @@ void P_ChickenPlayerThink(player_t * player)
     pmo = player->mo;
     if (!(pmo->momx + pmo->momy) && P_Random() < 160)
     {                           // Twitch view angle
-        pmo->angle += (P_Random() - P_Random()) << 19;
+        pmo->angle += P_SubRandom() << 19;
     }
     if ((pmo->z <= pmo->floorz) && (P_Random() < 32))
     {                           // Jump and noise
diff --git a/src/heretic/r_bsp.c b/src/heretic/r_bsp.c
index d2e404b..6fed7f7 100644
--- a/src/heretic/r_bsp.c
+++ b/src/heretic/r_bsp.c
@@ -16,6 +16,7 @@
 // R_bsp.c
 
 #include "doomdef.h"
+#include "i_system.h"
 #include "m_bbox.h"
 #include "r_local.h"
 
@@ -58,7 +59,14 @@ typedef struct
     int first, last;
 } cliprange_t;
 
-#define	MAXSEGS	32
+// We must expand MAXSEGS to the theoretical limit of the number of solidsegs
+// that can be generated in a scene by the DOOM engine. This was determined by
+// Lee Killough during BOOM development to be a function of the screensize.
+// The simplest thing we can do, other than fix this bug, is to let the game
+// render overage and then bomb out by detecting the overflow after the 
+// fact. -haleyjd
+//#define MAXSEGS 32
+#define MAXSEGS (SCREENWIDTH / 2 + 1)
 
 cliprange_t solidsegs[MAXSEGS], *newend;        // newend is one past the last valid seg
 
@@ -437,6 +445,10 @@ void R_Subsector(int num)
         R_AddLine(line);
         line++;
     }
+
+    // check for solidsegs overflow - extremely unsatisfactory!
+    if(newend > &solidsegs[32])
+        I_Error("R_Subsector: solidsegs overflow (vanilla may crash here)\n");
 }
 
 
diff --git a/src/heretic/r_data.c b/src/heretic/r_data.c
index 870f5c3..b7f51fb 100644
--- a/src/heretic/r_data.c
+++ b/src/heretic/r_data.c
@@ -671,7 +671,7 @@ void R_PrecacheLevel(void)
         if (flatpresent[i])
         {
             lump = firstflat + i;
-            flatmemory += lumpinfo[lump].size;
+            flatmemory += lumpinfo[lump]->size;
             W_CacheLumpNum(lump, PU_CACHE);
         }
 
@@ -701,7 +701,7 @@ void R_PrecacheLevel(void)
         for (j = 0; j < texture->patchcount; j++)
         {
             lump = texture->patches[j].patch;
-            texturememory += lumpinfo[lump].size;
+            texturememory += lumpinfo[lump]->size;
             W_CacheLumpNum(lump, PU_CACHE);
         }
     }
@@ -731,7 +731,7 @@ void R_PrecacheLevel(void)
             for (k = 0; k < 8; k++)
             {
                 lump = firstspritelump + sf->lump[k];
-                spritememory += lumpinfo[lump].size;
+                spritememory += lumpinfo[lump]->size;
                 W_CacheLumpNum(lump, PU_CACHE);
             }
         }
diff --git a/src/heretic/r_things.c b/src/heretic/r_things.c
index 489c94b..0e759e5 100644
--- a/src/heretic/r_things.c
+++ b/src/heretic/r_things.c
@@ -175,15 +175,15 @@ void R_InitSpriteDefs(char **namelist)
         // scan the lumps, filling in the frames for whatever is found
         //
         for (l = start + 1; l < end; l++)
-            if (!strncasecmp(lumpinfo[l].name, spritename, 4))
+            if (!strncasecmp(lumpinfo[l]->name, spritename, 4))
             {
-                frame = lumpinfo[l].name[4] - 'A';
-                rotation = lumpinfo[l].name[5] - '0';
+                frame = lumpinfo[l]->name[4] - 'A';
+                rotation = lumpinfo[l]->name[5] - '0';
                 R_InstallSpriteLump(l, frame, rotation, false);
-                if (lumpinfo[l].name[6])
+                if (lumpinfo[l]->name[6])
                 {
-                    frame = lumpinfo[l].name[6] - 'A';
-                    rotation = lumpinfo[l].name[7] - '0';
+                    frame = lumpinfo[l]->name[6] - 'A';
+                    rotation = lumpinfo[l]->name[7] - '0';
                     R_InstallSpriteLump(l, frame, rotation, true);
                 }
             }
diff --git a/src/heretic/s_sound.c b/src/heretic/s_sound.c
index 988dc03..8292789 100644
--- a/src/heretic/s_sound.c
+++ b/src/heretic/s_sound.c
@@ -268,10 +268,8 @@ void S_StartSound(void *_origin, int sound_id)
             sep = 512 - sep;
     }
 
-    // TODO: Play pitch-shifted sounds as in Vanilla Heretic
-
-    channel[i].pitch = (byte) (127 + (M_Random() & 7) - (M_Random() & 7));
-    channel[i].handle = I_StartSound(&S_sfx[sound_id], i, vol, sep);
+    channel[i].pitch = (byte) (NORM_PITCH + (M_Random() & 7) - (M_Random() & 7));
+    channel[i].handle = I_StartSound(&S_sfx[sound_id], i, vol, sep, channel[i].pitch);
     channel[i].mo = origin;
     channel[i].sound_id = sound_id;
     channel[i].priority = priority;
@@ -327,9 +325,8 @@ void S_StartSoundAtVolume(void *_origin, int sound_id, int volume)
         S_sfx[sound_id].lumpnum = I_GetSfxLumpNum(&S_sfx[sound_id]);
     }
 
-    // TODO: Pitch shifting.
-    channel[i].pitch = (byte) (127 - (M_Random() & 3) + (M_Random() & 3));
-    channel[i].handle = I_StartSound(&S_sfx[sound_id], i, volume, 128);
+    channel[i].pitch = (byte) (NORM_PITCH - (M_Random() & 3) + (M_Random() & 3));
+    channel[i].handle = I_StartSound(&S_sfx[sound_id], i, volume, 128, channel[i].pitch);
     channel[i].mo = origin;
     channel[i].sound_id = sound_id;
     channel[i].priority = 1;    //super low priority.
@@ -516,7 +513,7 @@ void S_UpdateSounds(mobj_t * listener)
 
 void S_Init(void)
 {
-    I_SetOPLDriverVer(opl_v_old);
+    I_SetOPLDriverVer(opl_doom2_1_666);
     soundCurve = Z_Malloc(MAX_SND_DIST, PU_STATIC, NULL);
     if (snd_Channels > 8)
     {
@@ -527,6 +524,12 @@ void S_Init(void)
 
     I_AtExit(S_ShutDown, true);
 
+    // Heretic defaults to pitch-shifting on
+    if (snd_pitchshift == -1)
+    {
+        snd_pitchshift = 1;
+    }
+
     I_PrecacheSounds(S_sfx, NUMSFX);
 }
 
diff --git a/src/hexen.appdata.xml.in b/src/hexen.appdata.xml.in
index e7745be..b5a526f 100644
--- a/src/hexen.appdata.xml.in
+++ b/src/hexen.appdata.xml.in
@@ -25,19 +25,19 @@
   </description>
   <screenshots>
     <screenshot type="default">
-      <image>http://www.chocolate-doom.org/wiki/images/0/0f/GNOME_Hexen_Guardian_of_Fire.png</image>
+      <image>https://www.chocolate-doom.org/wiki/images/0/0f/GNOME_Hexen_Guardian_of_Fire.png</image>
       <caption>Level "Guardian of Fire"</caption>
     </screenshot>
     <screenshot>
-      <image>http://www.chocolate-doom.org/wiki/images/5/5c/GNOME_Hexen_Effluvium.png</image>
+      <image>https://www.chocolate-doom.org/wiki/images/5/5c/GNOME_Hexen_Effluvium.png</image>
       <caption>Level "Effluvium"</caption>
     </screenshot>
     <screenshot>
-      <image>http://www.chocolate-doom.org/wiki/images/c/c1/GNOME_Hexen_Dragon_Chapel.png</image>
+      <image>https://www.chocolate-doom.org/wiki/images/c/c1/GNOME_Hexen_Dragon_Chapel.png</image>
       <caption>Level "Dragon Chapel"</caption>
     </screenshot>
     <screenshot>
-      <image>http://www.chocolate-doom.org/wiki/images/a/a7/GNOME_Hexen_Darkmere.png</image>
+      <image>https://www.chocolate-doom.org/wiki/images/a/a7/GNOME_Hexen_Darkmere.png</image>
       <caption>Level "Darkmere"</caption>
     </screenshot>
   </screenshots>
diff --git a/src/hexen/a_action.c b/src/hexen/a_action.c
index 1923ff7..21b9e85 100644
--- a/src/hexen/a_action.c
+++ b/src/hexen/a_action.c
@@ -126,11 +126,14 @@ int orbitTableY[256] = {
 void A_DripBlood(mobj_t *actor)
 {
 	mobj_t *mo;
+    int r;
 
-	mo = P_SpawnMobj(actor->x+((P_Random()-P_Random())<<11),
-		actor->y+((P_Random()-P_Random())<<11), actor->z, MT_BLOOD);
-	mo->momx = (P_Random()-P_Random())<<10;
-	mo->momy = (P_Random()-P_Random())<<10;
+    r = P_SubRandom();
+
+	mo = P_SpawnMobj(actor->x+(r<<11),
+		actor->y+(P_SubRandom()<<11), actor->z, MT_BLOOD);
+	mo->momx = P_SubRandom()<<10;
+	mo->momy = P_SubRandom()<<10;
 	mo->flags2 |= MF2_LOGRAV;
 }
 */
@@ -153,8 +156,8 @@ void A_PotteryExplode(mobj_t * actor)
         if (mo)
         {
             mo->momz = ((P_Random() & 7) + 5) * (3 * FRACUNIT / 4);
-            mo->momx = (P_Random() - P_Random()) << (FRACBITS - 6);
-            mo->momy = (P_Random() - P_Random()) << (FRACBITS - 6);
+            mo->momx = P_SubRandom() << (FRACBITS - 6);
+            mo->momy = P_SubRandom() << (FRACBITS - 6);
         }
     }
     S_StartSound(mo, SFX_POTTERY_EXPLODE);
@@ -265,8 +268,8 @@ void A_CorpseExplode(mobj_t * actor)
         if (mo)
         {
             mo->momz = ((P_Random() & 7) + 5) * (3 * FRACUNIT / 4);
-            mo->momx = (P_Random() - P_Random()) << (FRACBITS - 6);
-            mo->momy = (P_Random() - P_Random()) << (FRACBITS - 6);
+            mo->momx = P_SubRandom() << (FRACBITS - 6);
+            mo->momy = P_SubRandom() << (FRACBITS - 6);
         }
     }
     // Spawn a skull
@@ -275,8 +278,8 @@ void A_CorpseExplode(mobj_t * actor)
     if (mo)
     {
         mo->momz = ((P_Random() & 7) + 5) * (3 * FRACUNIT / 4);
-        mo->momx = (P_Random() - P_Random()) << (FRACBITS - 6);
-        mo->momy = (P_Random() - P_Random()) << (FRACBITS - 6);
+        mo->momx = P_SubRandom() << (FRACBITS - 6);
+        mo->momy = P_SubRandom() << (FRACBITS - 6);
         S_StartSound(mo, SFX_FIRED_DEATH);
     }
     P_RemoveMobj(actor);
@@ -300,8 +303,8 @@ void A_LeafSpawn(mobj_t * actor)
         // see ISO-IEC 9899-1999, [6.5.2.2.10]
         mobjtype_t type = MT_LEAF1 + (P_Random() & 1);
         fixed_t z = actor->z + (P_Random() << 14);
-        fixed_t y = actor->y + ((P_Random() - P_Random()) << 14);
-        fixed_t x = actor->x + ((P_Random() - P_Random()) << 14);
+        fixed_t y = actor->y + (P_SubRandom() << 14);
+        fixed_t x = actor->x + (P_SubRandom() << 14);
 
         mo = P_SpawnMobj(x, y, z, type);
         if (mo)
@@ -1040,8 +1043,8 @@ void P_SpawnDirt(mobj_t * actor, fixed_t radius)
     angle = P_Random() << 5;    // <<24 >>19
     x = actor->x + FixedMul(radius, finecosine[angle]);
     y = actor->y + FixedMul(radius, finesine[angle]);
-//      x = actor->x + ((P_Random()-P_Random())%radius)<<FRACBITS;
-//      y = actor->y + ((P_Random()-P_Random()<<FRACBITS)%radius);
+//      x = actor->x + (P_SubRandom()%radius)<<FRACBITS;
+//      y = actor->y + ((P_SubRandom()<<FRACBITS)%radius);
     z = actor->z + (P_Random() << 9) + FRACUNIT;
     switch (P_Random() % 6)
     {
@@ -1165,19 +1168,23 @@ void A_SoAExplode(mobj_t * actor)
 {
     mobj_t *mo;
     int i;
+    int r1,r2,r3;
 
     for (i = 0; i < 10; i++)
     {
-        mo = P_SpawnMobj(actor->x + ((P_Random() - 128) << 12),
-                         actor->y + ((P_Random() - 128) << 12),
-                         actor->z + (P_Random() * actor->height / 256),
+        r1 = P_Random();
+        r2 = P_Random();
+        r3 = P_Random();
+        mo = P_SpawnMobj(actor->x + ((r3 - 128) << 12),
+                         actor->y + ((r2 - 128) << 12),
+                         actor->z + (r1 * actor->height / 256),
                          MT_ZARMORCHUNK);
         P_SetMobjState(mo, mo->info->spawnstate + i);
         if (mo)
         {
             mo->momz = ((P_Random() & 7) + 5) * FRACUNIT;
-            mo->momx = (P_Random() - P_Random()) << (FRACBITS - 6);
-            mo->momy = (P_Random() - P_Random()) << (FRACBITS - 6);
+            mo->momx = P_SubRandom() << (FRACBITS - 6);
+            mo->momy = P_SubRandom() << (FRACBITS - 6);
         }
     }
     if (actor->args[0])
diff --git a/src/hexen/am_map.c b/src/hexen/am_map.c
index 6a28b45..1bd9a75 100644
--- a/src/hexen/am_map.c
+++ b/src/hexen/am_map.c
@@ -20,6 +20,7 @@
 #include "doomkeys.h"
 #include "i_video.h"
 #include "i_swap.h"
+#include "i_timer.h"
 #include "m_controls.h"
 #include "m_misc.h"
 #include "p_local.h"
@@ -421,9 +422,33 @@ boolean AM_Responder(event_t * ev)
     int rc;
     int key;
     static int bigstate = 0;
+    static int joywait = 0;
 
     key = ev->data1;
 
+    if (ev->type == ev_joystick && joybautomap >= 0
+        && (ev->data1 & (1 << joybautomap)) != 0 && joywait < I_GetTime())
+    {
+        joywait = I_GetTime() + 5;
+
+        if (!automapactive)
+        {
+            AM_Start ();
+            SB_state = -1;
+            viewactive = false;
+        }
+        else
+        {
+            bigstate = 0;
+            viewactive = true;
+            AM_Stop ();
+            SB_state = -1;
+        }
+
+        return true;
+    }
+
+
     rc = false;
     if (!automapactive)
     {
diff --git a/src/hexen/d_net.c b/src/hexen/d_net.c
index bd0e8b2..cfaa589 100644
--- a/src/hexen/d_net.c
+++ b/src/hexen/d_net.c
@@ -116,10 +116,17 @@ static void LoadGameSettings(net_gamesettings_t *settings)
     startmap = settings->map;
     startskill = settings->skill;
     // TODO startloadgame = settings->loadgame;
+    lowres_turn = settings->lowres_turn;
     nomonsters = settings->nomonsters;
     respawnparm = settings->respawn_monsters;
     consoleplayer = settings->consoleplayer;
 
+    if (lowres_turn)
+    {
+        printf("NOTE: Turning resolution is reduced; this is probably "
+               "because there is a client recording a Vanilla demo.\n");
+    }
+
     for (i=0; i<maxplayers; ++i)
     {
         playeringame[i] = i < settings->num_players;
@@ -154,7 +161,9 @@ static void SaveGameSettings(net_gamesettings_t *settings)
     settings->nomonsters = nomonsters;
     settings->respawn_monsters = respawnparm;
     settings->timelimit = 0;
-    settings->lowres_turn = false;
+
+    settings->lowres_turn = M_ParmExists("-record")
+                         && !M_ParmExists("-longtics");
 }
 
 static void InitConnectData(net_connect_data_t *connect_data)
@@ -170,7 +179,11 @@ static void InitConnectData(net_connect_data_t *connect_data)
     connect_data->gamemode = gamemode;
     connect_data->gamemission = hexen;
 
-    connect_data->lowres_turn = false;
+    // Are we recording a demo? Possibly set lowres turn mode
+
+    connect_data->lowres_turn = M_ParmExists("-record")
+                             && !M_ParmExists("-longtics");
+
     connect_data->drone = false;
     connect_data->max_players = maxplayers;
 
diff --git a/src/hexen/g_game.c b/src/hexen/g_game.c
index fd72d44..d1c508b 100644
--- a/src/hexen/g_game.c
+++ b/src/hexen/g_game.c
@@ -23,6 +23,7 @@
 #include "i_video.h"
 #include "i_system.h"
 #include "i_timer.h"
+#include "m_argv.h"
 #include "m_controls.h"
 #include "m_misc.h"
 #include "p_local.h"
@@ -92,8 +93,12 @@ int levelstarttic;              // gametic at level start
 
 char demoname[32];
 boolean demorecording;
+boolean longtics;               // specify high resolution turning in demos
+boolean lowres_turn;
+boolean shortticfix;            // calculate lowres turning like doom
 boolean demoplayback;
-byte *demobuffer, *demo_p;
+boolean demoextend;
+byte *demobuffer, *demo_p, *demoend;
 boolean singledemo;             // quit after playing a demo from cmdline
 
 boolean precache = true;        // if true, load all graphics at start
@@ -160,6 +165,8 @@ boolean *joybuttons = &joyarray[1];     // allow [-1]
 int savegameslot;
 char savedescription[32];
 
+int vanilla_demo_limit = 1;
+
 int inventoryTics;
 
 // haleyjd: removed externdriver crap
@@ -305,13 +312,13 @@ void G_BuildTiccmd(ticcmd_t *cmd, int maketic)
     {
         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];
     }
@@ -497,57 +504,66 @@ void G_BuildTiccmd(ticcmd_t *cmd, int maketic)
     {
         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;
+            }
         }
     }
 
@@ -621,6 +637,33 @@ void G_BuildTiccmd(ticcmd_t *cmd, int maketic)
         cmd->buttons =
             BT_SPECIAL | BTS_SAVEGAME | (savegameslot << BTS_SAVESHIFT);
     }
+
+    if (lowres_turn)
+    {
+        if (shortticfix)
+        {
+            static signed short carry = 0;
+            signed short desired_angleturn;
+
+            desired_angleturn = cmd->angleturn + carry;
+
+            // round angleturn to the nearest 256 unit boundary
+            // for recording demos with single byte values for turn
+
+            cmd->angleturn = (desired_angleturn + 128) & 0xff00;
+
+            // Carry forward the error from the reduced resolution to the
+            // next tic, so that successive small movements can accumulate.
+
+            carry = desired_angleturn - cmd->angleturn;
+        }
+        else
+        {
+            // truncate angleturn to the nearest 256 boundary
+            // for recording demos with single byte values for turn
+            cmd->angleturn &= 0xff00;
+        }
+    }
 }
 
 
@@ -648,7 +691,6 @@ void G_DoLoadLevel(void)
     SN_StopAllSequences();
     P_SetupLevel(gameepisode, gamemap, 0, gameskill);
     displayplayer = consoleplayer;      // view the guy you are playing   
-    starttime = I_GetTime();
     gameaction = ga_nothing;
     Z_CheckHeap();
 
@@ -1292,7 +1334,8 @@ void G_DoReborn(int playernum)
     boolean foundSpot;
     int bestWeapon;
 
-    if (G_CheckDemoStatus())
+    // quit demo unless -demoextend
+    if (!demoextend && G_CheckDemoStatus())
     {
         return;
     }
@@ -1487,7 +1530,9 @@ void G_DoCompleted(void)
     int i;
 
     gameaction = ga_nothing;
-    if (G_CheckDemoStatus())
+
+    // quit demo unless -demoextend
+    if (!demoextend && G_CheckDemoStatus())
     {
         return;
     }
@@ -1738,10 +1783,16 @@ void G_InitNew(skill_t skill, int episode, int map)
     }
 
     // Set up a bunch of globals
-    usergame = true;            // will be set false if a demo
+    if (!demoextend)
+    {
+        // This prevents map-loading from interrupting a demo.
+        // demoextend is set back to false only if starting a new game or
+        // loading a saved one from the menu, and only during playback.
+        demorecording = false;
+        demoplayback = false;
+        usergame = true;            // will be set false if a demo
+    }
     paused = false;
-    demorecording = false;
-    demoplayback = false;
     viewactive = true;
     gameepisode = episode;
     gamemap = map;
@@ -1771,6 +1822,9 @@ void G_InitNew(skill_t skill, int episode, int map)
 */
 
 #define DEMOMARKER      0x80
+#define DEMOHEADER_RESPAWN    0x20
+#define DEMOHEADER_LONGTICS   0x10
+#define DEMOHEADER_NOMONSTERS 0x02
 
 void G_ReadDemoTiccmd(ticcmd_t * cmd)
 {
@@ -1781,23 +1835,104 @@ void G_ReadDemoTiccmd(ticcmd_t * cmd)
     }
     cmd->forwardmove = ((signed char) *demo_p++);
     cmd->sidemove = ((signed char) *demo_p++);
-    cmd->angleturn = ((unsigned char) *demo_p++) << 8;
+
+    // If this is a longtics demo, read back in higher resolution
+
+    if (longtics)
+    {
+        cmd->angleturn = *demo_p++;
+        cmd->angleturn |= (*demo_p++) << 8;
+    }
+    else
+    {
+        cmd->angleturn = ((unsigned char) *demo_p++) << 8;
+    }
+
     cmd->buttons = (unsigned char) *demo_p++;
     cmd->lookfly = (unsigned char) *demo_p++;
     cmd->arti = (unsigned char) *demo_p++;
 }
 
+// Increase the size of the demo buffer to allow unlimited demos
+
+static void IncreaseDemoBuffer(void)
+{
+    int current_length;
+    byte *new_demobuffer;
+    byte *new_demop;
+    int new_length;
+
+    // Find the current size
+
+    current_length = demoend - demobuffer;
+
+    // Generate a new buffer twice the size
+    new_length = current_length * 2;
+
+    new_demobuffer = Z_Malloc(new_length, PU_STATIC, 0);
+    new_demop = new_demobuffer + (demo_p - demobuffer);
+
+    // Copy over the old data
+
+    memcpy(new_demobuffer, demobuffer, current_length);
+
+    // Free the old buffer and point the demo pointers at the new buffer.
+
+    Z_Free(demobuffer);
+
+    demobuffer = new_demobuffer;
+    demo_p = new_demop;
+    demoend = demobuffer + new_length;
+}
+
 void G_WriteDemoTiccmd(ticcmd_t * cmd)
 {
-    if (gamekeydown['q'])       // press q to end demo recording
+    byte *demo_start;
+
+    if (gamekeydown[key_demo_quit]) // press to end demo recording
         G_CheckDemoStatus();
+
+    demo_start = demo_p;
+
     *demo_p++ = cmd->forwardmove;
     *demo_p++ = cmd->sidemove;
-    *demo_p++ = cmd->angleturn >> 8;
+
+    // If this is a longtics demo, record in higher resolution
+
+    if (longtics)
+    {
+        *demo_p++ = (cmd->angleturn & 0xff);
+        *demo_p++ = (cmd->angleturn >> 8) & 0xff;
+    }
+    else
+    {
+        *demo_p++ = cmd->angleturn >> 8;
+    }
+
     *demo_p++ = cmd->buttons;
     *demo_p++ = cmd->lookfly;
     *demo_p++ = cmd->arti;
-    demo_p -= 6;
+
+    // reset demo pointer back
+    demo_p = demo_start;
+
+    if (demo_p > demoend - 16)
+    {
+        if (vanilla_demo_limit)
+        {
+            // no more space
+            G_CheckDemoStatus();
+            return;
+        }
+        else
+        {
+            // Vanilla demo limit disabled: unlimited
+            // demo lengths!
+
+            IncreaseDemoBuffer();
+        }
+    }
+
     G_ReadDemoTiccmd(cmd);      // make SURE it is exactly the same
 }
 
@@ -1815,21 +1950,83 @@ void G_RecordDemo(skill_t skill, int numplayers, int episode, int map,
                   char *name)
 {
     int i;
+    int maxsize;
+
+    //!
+    // @category demo
+    //
+    // Record or playback a demo with high resolution turning.
+    //
+
+    longtics = D_NonVanillaRecord(M_ParmExists("-longtics"),
+                                  "vvHeretic longtics demo");
+
+    // If not recording a longtics demo, record in low res
+
+    lowres_turn = !longtics;
+
+    //!
+    // @category demo
+    //
+    // Smooth out low resolution turning when recording a demo.
+    //
+
+    shortticfix = M_ParmExists("-shortticfix");
 
     G_InitNew(skill, episode, map);
     usergame = false;
     M_StringCopy(demoname, name, sizeof(demoname));
     M_StringConcat(demoname, ".lmp", sizeof(demoname));
-    demobuffer = demo_p = Z_Malloc(0x20000, PU_STATIC, NULL);
+    maxsize = 0x20000;
+
+    //!
+    // @arg <size>
+    // @category demo
+    // @vanilla
+    //
+    // Specify the demo buffer size (KiB)
+    //
+
+    i = M_CheckParmWithArgs("-maxdemo", 1);
+    if (i)
+        maxsize = atoi(myargv[i + 1]) * 1024;
+    demobuffer = Z_Malloc(maxsize, PU_STATIC, NULL);
+    demoend = demobuffer + maxsize;
+
+    demo_p = demobuffer;
     *demo_p++ = skill;
     *demo_p++ = episode;
     *demo_p++ = map;
 
-    for (i = 0; i < maxplayers; i++)
+    // Write special parameter bits onto player one byte.
+    // This aligns with vvHeretic demo usage. Hexen demo support has no
+    // precedent here so consistency with another game is chosen:
+    //   0x20 = -respawn
+    //   0x10 = -longtics
+    //   0x02 = -nomonsters
+
+    *demo_p = 1; // assume player one exists
+    if (D_NonVanillaRecord(respawnparm, "vvHeretic -respawn header flag"))
+    {
+        *demo_p |= DEMOHEADER_RESPAWN;
+    }
+    if (longtics)
+    {
+        *demo_p |= DEMOHEADER_LONGTICS;
+    }
+    if (D_NonVanillaRecord(nomonsters, "vvHeretic -nomonsters header flag"))
+    {
+        *demo_p |= DEMOHEADER_NOMONSTERS;
+    }
+    demo_p++;
+    *demo_p++ = PlayerClass[0];
+
+    for (i = 1; i < maxplayers; i++)
     {
         *demo_p++ = playeringame[i];
         *demo_p++ = PlayerClass[i];
     }
+
     demorecording = true;
 }
 
@@ -1853,17 +2050,40 @@ void G_DeferedPlayDemo(char *name)
 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++;
 
+    // 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++)
     {
-        playeringame[i] = *demo_p++;
+        playeringame[i] = (*demo_p++) != 0;
         PlayerClass[i] = *demo_p++;
     }
 
@@ -1896,13 +2116,22 @@ void G_TimeDemo(char *name)
     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;
+
     for (i = 0; i < maxplayers; i++)
     {
-        playeringame[i] = *demo_p++;
+        playeringame[i] = (*demo_p++) != 0;
         PlayerClass[i] = *demo_p++;
     }
 
     G_InitNew(skill, episode, map);
+    starttime = I_GetTime();
+
     usergame = false;
     demoplayback = true;
     timingdemo = true;
diff --git a/src/hexen/h2_main.c b/src/hexen/h2_main.c
index 47c5e28..ef6ea9e 100644
--- a/src/hexen/h2_main.c
+++ b/src/hexen/h2_main.c
@@ -150,6 +150,10 @@ void D_BindVariables(void)
     key_multi_msgplayer[6] = CT_KEY_PLAYER7;
     key_multi_msgplayer[7] = CT_KEY_PLAYER8;
 
+#ifdef FEATURE_MULTIPLAYER
+    NET_BindVariables();
+#endif
+
     M_BindIntVariable("graphical_startup",      &graphical_startup);
     M_BindIntVariable("mouse_sensitivity",      &mouseSensitivity);
     M_BindIntVariable("sfx_volume",             &snd_MaxVolume);
@@ -157,6 +161,8 @@ void D_BindVariables(void)
     M_BindIntVariable("messageson",             &messageson);
     M_BindIntVariable("screenblocks",           &screenblocks);
     M_BindIntVariable("snd_channels",           &snd_Channels);
+    M_BindIntVariable("vanilla_savegame_limit", &vanilla_savegame_limit);
+    M_BindIntVariable("vanilla_demo_limit",     &vanilla_demo_limit);
 
     M_BindStringVariable("savedir", &SavePath);
 
@@ -288,7 +294,7 @@ void D_IdentifyVersion(void)
             "You are trying to use the Hexen v1.0 IWAD. This isn't\n"
             "supported by " PACKAGE_NAME ". Please upgrade to the v1.1\n"
             "IWAD file. See here for more information:\n"
-            "  http://www.doomworld.com/classicdoom/info/patches.php");
+            "  https://www.doomworld.com/classicdoom/info/patches.php");
     }
 }
 
@@ -663,7 +669,7 @@ static void HandleArgs(void)
 
         if (W_AddFile(file) != NULL)
         {
-            M_StringCopy(demolumpname, lumpinfo[numlumps - 1].name,
+            M_StringCopy(demolumpname, lumpinfo[numlumps - 1]->name,
                          sizeof(demolumpname));
         }
         else
@@ -676,6 +682,15 @@ static void HandleArgs(void)
         ST_Message("Playing demo %s.\n", myargv[p+1]);
     }
 
+    //!
+    // @category demo
+    //
+    // Record or playback a demo without automatically quitting
+    // after either level exit or player respawn.
+    //
+
+    demoextend = M_ParmExists("-demoextend");
+
     if (M_ParmExists("-testcontrols"))
     {
         autostart = true;
diff --git a/src/hexen/h2def.h b/src/hexen/h2def.h
index 0658b7d..b57cedd 100644
--- a/src/hexen/h2def.h
+++ b/src/hexen/h2def.h
@@ -58,19 +58,22 @@
 #endif
 
 // Past distributions
-#ifndef VER_ID
-#define VER_ID "DVL"
-#endif
+//#ifndef VER_ID
+//#define VER_ID "DVL"
+//#endif
 //#define HEXEN_VERSIONTEXT "ID V1.2"
 //#define HEXEN_VERSIONTEXT "RETAIL STORE BETA"               // 9/26/95
 //#define HEXEN_VERSIONTEXT "DVL BETA 10 05 95" // Used for GT for testing
 //#define HEXEN_VERSIONTEXT "DVL BETA 10 07 95" // Just an update for Romero
 //#define HEXEN_VERSIONTEXT "FINAL 1.0 (10 13 95)" // Just an update for Romero
-#ifdef RANGECHECK
-#define HEXEN_VERSIONTEXT "Version 1.1 +R "__DATE__" ("VER_ID")"
-#else
-#define HEXEN_VERSIONTEXT "Version 1.1 "__DATE__" ("VER_ID")"
-#endif
+//#ifdef RANGECHECK
+//#define HEXEN_VERSIONTEXT "Version 1.1 +R "__DATE__" ("VER_ID")"
+//#else
+//#define HEXEN_VERSIONTEXT "Version 1.1 "__DATE__" ("VER_ID")"
+//#endif
+#define HEXEN_VERSIONTEXT ((gamemode == shareware) ? \
+                           "DEMO 10 16 95" : \
+                           "VERSION 1.1 MAR 22 1996 (BCP)")
 
 // all exterior data is defined here
 #include "xddefs.h"
@@ -169,7 +172,7 @@ struct player_s;
 
 typedef union
 {
-    int i;
+    intptr_t i;
     struct mobj_s *m;
     struct player_s *p;
 } specialval_t;
@@ -630,8 +633,13 @@ extern player_t players[MAXPLAYERS];
 extern boolean DebugSound;      // debug flag for displaying sound info
 
 extern boolean demoplayback;
+extern boolean demoextend;      // allow demos to persist through exit/respawn
 extern int maxzone;             // Maximum chunk allocated for zone heap
 
+// Truncate angleturn in ticcmds to nearest 256.
+// Used when recording Vanilla demos in netgames.
+extern boolean lowres_turn;
+
 extern int Sky1Texture;
 extern int Sky2Texture;
 
@@ -674,6 +682,9 @@ extern boolean autostart;
 extern boolean testcontrols;
 extern int testcontrols_mousespeed;
 
+extern int vanilla_savegame_limit;
+extern int vanilla_demo_limit;
+
 /*
 ===============================================================================
 
diff --git a/src/hexen/m_random.c b/src/hexen/m_random.c
index 43ad64e..422b1a5 100644
--- a/src/hexen/m_random.c
+++ b/src/hexen/m_random.c
@@ -72,3 +72,9 @@ void M_ClearRandom(void)
     rndindex = prndindex = 0;
 }
 
+// inspired by the same routine in Eternity, thanks haleyjd
+int P_SubRandom (void)
+{
+    int r = P_Random();
+    return r - P_Random();
+}
diff --git a/src/hexen/m_random.h b/src/hexen/m_random.h
index a0e8db0..2de7630 100644
--- a/src/hexen/m_random.h
+++ b/src/hexen/m_random.h
@@ -30,5 +30,8 @@ void M_ClearRandom(void);
 
 extern int rndindex;
 
+// Defined version of P_Random() - P_Random()
+int P_SubRandom (void);
+
 #endif // HEXEN_M_RANDOM_H
 
diff --git a/src/hexen/mn_menu.c b/src/hexen/mn_menu.c
index 1517027..3cb9274 100644
--- a/src/hexen/mn_menu.c
+++ b/src/hexen/mn_menu.c
@@ -130,6 +130,7 @@ boolean MenuActive;
 int InfoType;
 int messageson = true;
 boolean mn_SuicideConsole;
+boolean demoextend; // from h2def.h
 
 // PRIVATE DATA DEFINITIONS ------------------------------------------------
 
@@ -895,6 +896,11 @@ static void SCNetCheck2(int option)
 
 static void SCLoadGame(int option)
 {
+    if (demoplayback)
+    {
+        // deactivate playback, return control to player
+        demoextend = false;
+    }
     if (!SlotStatus[option])
     {                           // Don't try to load from an empty slot
         return;
@@ -1003,6 +1009,12 @@ static void SCClass(int option)
 
 static void SCSkill(int option)
 {
+    if (demoplayback)
+    {
+        // deactivate playback, return control to player
+        demoextend = false;
+    }
+
     PlayerClass[consoleplayer] = MenuPClass;
     G_DeferredNewGame(option);
     SB_SetClassData();
diff --git a/src/hexen/p_acs.c b/src/hexen/p_acs.c
index b7a63fe..5c124cb 100644
--- a/src/hexen/p_acs.c
+++ b/src/hexen/p_acs.c
@@ -787,6 +787,23 @@ static int GetACSIndex(int number)
 
 //==========================================================================
 //
+// CheckACSPresent
+//
+// Placing Korax in a PWAD without extra steps will result in a crash in
+// Vanilla because the relevant ACS scripts are not initialised
+//
+//==========================================================================
+
+void CheckACSPresent(int number)
+{
+    if (GetACSIndex(number) == -1)
+    {
+        I_Error("Required ACS script %d not initialised", number);
+    }
+}
+
+//==========================================================================
+//
 // Push
 //
 //==========================================================================
diff --git a/src/hexen/p_enemy.c b/src/hexen/p_enemy.c
index 38f3a2b..ff265c8 100644
--- a/src/hexen/p_enemy.c
+++ b/src/hexen/p_enemy.c
@@ -820,7 +820,7 @@ void A_FaceTarget(mobj_t * actor)
                                    actor->target->y);
     if (actor->target->flags & MF_SHADOW)
     {                           // Target is a ghost
-        actor->angle += (P_Random() - P_Random()) << 21;
+        actor->angle += P_SubRandom() << 21;
     }
 }
 
@@ -1481,10 +1481,14 @@ void A_MinotaurAtk3(mobj_t * actor)
 void A_MntrFloorFire(mobj_t * actor)
 {
     mobj_t *mo;
+    int r1, r2;
+
+    r1 = P_SubRandom();
+    r2 = P_SubRandom();
 
     actor->z = actor->floorz;
-    mo = P_SpawnMobj(actor->x + ((P_Random() - P_Random()) << 10),
-                     actor->y + ((P_Random() - P_Random()) << 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
@@ -1597,8 +1601,8 @@ void P_DropItem(mobj_t *source, mobjtype_t type, int special, int chance)
 	}
 	mo = P_SpawnMobj(source->x, source->y,
 		source->z+(source->height>>1), type);
-	mo->momx = (P_Random()-P_Random())<<8;
-	mo->momy = (P_Random()-P_Random())<<8;
+	mo->momx = P_SubRandom()<<8;
+	mo->momy = P_SubRandom()<<8;
 	mo->momz = FRACUNIT*5+(P_Random()<<10);
 	mo->flags2 |= MF2_DROPPED;
 	mo->health = special;
@@ -1773,8 +1777,8 @@ void A_SkullPop(mobj_t * actor)
     mo = P_SpawnMobj(actor->x, actor->y, actor->z + 48 * FRACUNIT,
                      MT_BLOODYSKULL);
     //mo->target = actor;
-    mo->momx = (P_Random() - P_Random()) << 9;
-    mo->momy = (P_Random() - P_Random()) << 9;
+    mo->momx = P_SubRandom() << 9;
+    mo->momy = P_SubRandom() << 9;
     mo->momz = FRACUNIT * 2 + (P_Random() << 6);
     // Attach player mobj to bloody skull
     player = actor->player;
@@ -2408,9 +2412,12 @@ void A_SerpentHeadPop(mobj_t * actor)
 void A_SerpentSpawnGibs(mobj_t * actor)
 {
     mobj_t *mo;
+    int r1, r2;
 
-    mo = P_SpawnMobj(actor->x + ((P_Random() - 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)
     {
@@ -2418,8 +2425,10 @@ void A_SerpentSpawnGibs(mobj_t * actor)
         mo->momy = (P_Random() - 128) << 6;
         mo->floorclip = 6 * FRACUNIT;
     }
-    mo = P_SpawnMobj(actor->x + ((P_Random() - 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)
     {
@@ -2427,8 +2436,10 @@ void A_SerpentSpawnGibs(mobj_t * actor)
         mo->momy = (P_Random() - 128) << 6;
         mo->floorclip = 6 * FRACUNIT;
     }
-    mo = P_SpawnMobj(actor->x + ((P_Random() - 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)
     {
@@ -2779,15 +2790,21 @@ void A_BishopPuff(mobj_t * actor)
 void A_BishopPainBlur(mobj_t * actor)
 {
     mobj_t *mo;
+    int r1,r2,r3;
 
     if (P_Random() < 64)
     {
         P_SetMobjState(actor, S_BISHOP_BLUR1);
         return;
     }
-    mo = P_SpawnMobj(actor->x + ((P_Random() - P_Random()) << 12), actor->y
-                     + ((P_Random() - P_Random()) << 12),
-                     actor->z + ((P_Random() - P_Random()) << 11),
+
+    r1 = P_SubRandom();
+    r2 = P_SubRandom();
+    r3 = P_SubRandom();
+
+    mo = P_SpawnMobj(actor->x + (r3 << 12), actor->y
+                     + (r2 << 12),
+                     actor->z + (r1 << 11),
                      MT_BISHOPPAINBLUR);
     if (mo)
     {
@@ -3027,14 +3044,18 @@ void A_DragonFX2(mobj_t * actor)
 {
     mobj_t *mo;
     int i;
+    int r1,r2,r3;
     int delay;
 
     delay = 16 + (P_Random() >> 3);
     for (i = 1 + (P_Random() & 3); i; i--)
     {
-        mo = P_SpawnMobj(actor->x + ((P_Random() - 128) << 14),
-                         actor->y + ((P_Random() - 128) << 14),
-                         actor->z + ((P_Random() - 128) << 12),
+        r1 = P_Random();
+        r2 = P_Random();
+        r3 = P_Random();
+        mo = P_SpawnMobj(actor->x + ((r3 - 128) << 14),
+                         actor->y + ((r2 - 128) << 14),
+                         actor->z + ((r1 - 128) << 12),
                          MT_DRAGON_FX2);
         if (mo)
         {
@@ -4764,7 +4785,8 @@ void A_CheckFloor(mobj_t * actor)
 
 void A_FreezeDeath(mobj_t * actor)
 {
-    actor->tics = 75 + P_Random() + P_Random();
+    int r = P_Random();
+    actor->tics = 75 + r + P_Random();
     actor->flags |= MF_SOLID | MF_SHOOTABLE | MF_NOBLOOD;
     actor->flags2 |= MF2_PUSHABLE | MF2_TELESTOMP | MF2_PASSMOBJ | MF2_SLIDE;
     actor->height <<= 2;
@@ -4832,6 +4854,7 @@ void A_IceCheckHeadDone(mobj_t * actor)
 void A_FreezeDeathChunks(mobj_t * actor)
 {
     int i;
+    int r1,r2,r3;
     mobj_t *mo;
 
     if (actor->momx || actor->momy || actor->momz)
@@ -4843,35 +4866,41 @@ void A_FreezeDeathChunks(mobj_t * actor)
 
     for (i = 12 + (P_Random() & 15); i >= 0; i--)
     {
+        r1 = P_Random();
+        r2 = P_Random();
+        r3 = P_Random();
         mo = P_SpawnMobj(actor->x +
-                         (((P_Random() - 128) * actor->radius) >> 7),
+                         (((r3 - 128) * actor->radius) >> 7),
                          actor->y +
-                         (((P_Random() - 128) * actor->radius) >> 7),
-                         actor->z + (P_Random() * actor->height / 255),
+                         (((r2 - 128) * actor->radius) >> 7),
+                         actor->z + (r1 * actor->height / 255),
                          MT_ICECHUNK);
         P_SetMobjState(mo, mo->info->spawnstate + (P_Random() % 3));
         if (mo)
         {
             mo->momz = FixedDiv(mo->z - actor->z, actor->height) << 2;
-            mo->momx = (P_Random() - P_Random()) << (FRACBITS - 7);
-            mo->momy = (P_Random() - P_Random()) << (FRACBITS - 7);
+            mo->momx = P_SubRandom() << (FRACBITS - 7);
+            mo->momy = P_SubRandom() << (FRACBITS - 7);
             A_IceSetTics(mo);   // set a random tic wait
         }
     }
     for (i = 12 + (P_Random() & 15); i >= 0; i--)
     {
+        r1 = P_Random();
+        r2 = P_Random();
+        r3 = P_Random();
         mo = P_SpawnMobj(actor->x +
-                         (((P_Random() - 128) * actor->radius) >> 7),
+                         (((r3 - 128) * actor->radius) >> 7),
                          actor->y +
-                         (((P_Random() - 128) * actor->radius) >> 7),
-                         actor->z + (P_Random() * actor->height / 255),
+                         (((r2 - 128) * actor->radius) >> 7),
+                         actor->z + (r1 * actor->height / 255),
                          MT_ICECHUNK);
         P_SetMobjState(mo, mo->info->spawnstate + (P_Random() % 3));
         if (mo)
         {
             mo->momz = FixedDiv(mo->z - actor->z, actor->height) << 2;
-            mo->momx = (P_Random() - P_Random()) << (FRACBITS - 7);
-            mo->momy = (P_Random() - P_Random()) << (FRACBITS - 7);
+            mo->momx = P_SubRandom() << (FRACBITS - 7);
+            mo->momy = P_SubRandom() << (FRACBITS - 7);
             A_IceSetTics(mo);   // set a random tic wait
         }
     }
@@ -4881,8 +4910,8 @@ void A_FreezeDeathChunks(mobj_t * actor)
                          MT_ICECHUNK);
         P_SetMobjState(mo, S_ICECHUNK_HEAD);
         mo->momz = FixedDiv(mo->z - actor->z, actor->height) << 2;
-        mo->momx = (P_Random() - P_Random()) << (FRACBITS - 7);
-        mo->momy = (P_Random() - P_Random()) << (FRACBITS - 7);
+        mo->momx = P_SubRandom() << (FRACBITS - 7);
+        mo->momy = P_SubRandom() << (FRACBITS - 7);
         mo->flags2 |= MF2_ICEDAMAGE;    // used to force blue palette
         mo->flags2 &= ~MF2_FLOORCLIP;
         mo->player = actor->player;
@@ -4946,6 +4975,7 @@ void A_KoraxChase(mobj_t * actor)
             P_Teleport(actor, spot->x, spot->y, spot->angle, true);
         }
 
+        CheckACSPresent(249);
         P_StartACS(249, 0, args, actor, NULL, 0);
         actor->special2.i = 1;    // Don't run again
 
@@ -5023,6 +5053,7 @@ void A_KoraxBonePop(mobj_t * actor)
     if (mo)
         KSpiritInit(mo, actor);
 
+    CheckACSPresent(255);
     P_StartACS(255, 0, args, actor, NULL, 0);   // Death script
 }
 
@@ -5140,18 +5171,23 @@ void A_KoraxCommand(mobj_t * actor)
     switch (P_Random() % numcommands)
     {
         case 0:
+            CheckACSPresent(250);
             P_StartACS(250, 0, args, actor, NULL, 0);
             break;
         case 1:
+            CheckACSPresent(251);
             P_StartACS(251, 0, args, actor, NULL, 0);
             break;
         case 2:
+            CheckACSPresent(252);
             P_StartACS(252, 0, args, actor, NULL, 0);
             break;
         case 3:
+            CheckACSPresent(253);
             P_StartACS(253, 0, args, actor, NULL, 0);
             break;
         case 4:
+            CheckACSPresent(254);
             P_StartACS(254, 0, args, actor, NULL, 0);
             break;
     }
diff --git a/src/hexen/p_map.c b/src/hexen/p_map.c
index b1c542c..4fa3d5e 100644
--- a/src/hexen/p_map.c
+++ b/src/hexen/p_map.c
@@ -2277,8 +2277,8 @@ boolean PIT_ChangeSector(mobj_t * thing)
         {
             mo = P_SpawnMobj(thing->x, thing->y, thing->z + thing->height / 2,
                              MT_BLOOD);
-            mo->momx = (P_Random() - P_Random()) << 12;
-            mo->momy = (P_Random() - P_Random()) << 12;
+            mo->momx = P_SubRandom() << 12;
+            mo->momy = P_SubRandom() << 12;
         }
     }
 
diff --git a/src/hexen/p_mobj.c b/src/hexen/p_mobj.c
index b868da8..1f5db12 100644
--- a/src/hexen/p_mobj.c
+++ b/src/hexen/p_mobj.c
@@ -1755,7 +1755,7 @@ void P_SpawnPuff(fixed_t x, fixed_t y, fixed_t z)
 {
     mobj_t *puff;
 
-    z += ((P_Random() - P_Random()) << 10);
+    z += (P_SubRandom() << 10);
     puff = P_SpawnMobj(x, y, z, PuffType);
     if (linetarget && puff->info->seesound)
     {                           // Hit thing sound
@@ -1792,7 +1792,7 @@ void P_SpawnBlood (fixed_t x, fixed_t y, fixed_t z, int damage)
 {
 	mobj_t	*th;
 	
-	z += ((P_Random()-P_Random())<<10);
+	z += (P_SubRandom()<<10);
 	th = P_SpawnMobj (x,y,z, MT_BLOOD);
 	th->momz = FRACUNIT*2;
 	th->tics -= P_Random()&3;
@@ -1816,8 +1816,8 @@ void P_BloodSplatter(fixed_t x, fixed_t y, fixed_t z, mobj_t * originator)
 
     mo = P_SpawnMobj(x, y, z, MT_BLOODSPLATTER);
     mo->target = originator;
-    mo->momx = (P_Random() - P_Random()) << 10;
-    mo->momy = (P_Random() - P_Random()) << 10;
+    mo->momx = P_SubRandom() << 10;
+    mo->momy = P_SubRandom() << 10;
     mo->momz = 3 * FRACUNIT;
 }
 
@@ -1830,9 +1830,12 @@ void P_BloodSplatter(fixed_t x, fixed_t y, fixed_t z, mobj_t * originator)
 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;
 }
 
@@ -1847,9 +1850,9 @@ void P_RipperBlood(mobj_t * mo)
     mobj_t *th;
     fixed_t x, y, z;
 
-    x = mo->x + ((P_Random() - P_Random()) << 12);
-    y = mo->y + ((P_Random() - P_Random()) << 12);
-    z = mo->z + ((P_Random() - P_Random()) << 12);
+    x = mo->x + (P_SubRandom() << 12);
+    y = mo->y + (P_SubRandom() << 12);
+    z = mo->z + (P_SubRandom() << 12);
     th = P_SpawnMobj(x, y, z, MT_BLOOD);
 //      th->flags |= MF_NOGRAVITY;
     th->momx = mo->momx >> 1;
@@ -1935,8 +1938,8 @@ int P_HitFloor(mobj_t * thing)
             {
                 mo = P_SpawnMobj(thing->x, thing->y, ONFLOORZ, MT_SPLASH);
                 mo->target = thing;
-                mo->momx = (P_Random() - P_Random()) << 8;
-                mo->momy = (P_Random() - P_Random()) << 8;
+                mo->momx = P_SubRandom() << 8;
+                mo->momy = P_SubRandom() << 8;
                 mo->momz = 2 * FRACUNIT + (P_Random() << 8);
                 mo = P_SpawnMobj(thing->x, thing->y, ONFLOORZ, MT_SPLASHBASE);
                 if (thing->player)
@@ -1978,8 +1981,8 @@ int P_HitFloor(mobj_t * thing)
                 mo = P_SpawnMobj(thing->x, thing->y, ONFLOORZ,
                                  MT_SLUDGECHUNK);
                 mo->target = thing;
-                mo->momx = (P_Random() - P_Random()) << 8;
-                mo->momy = (P_Random() - P_Random()) << 8;
+                mo->momx = P_SubRandom() << 8;
+                mo->momy = P_SubRandom() << 8;
                 mo->momz = FRACUNIT + (P_Random() << 8);
                 mo = P_SpawnMobj(thing->x, thing->y, ONFLOORZ,
                                  MT_SLUDGESPLASH);
@@ -2066,7 +2069,7 @@ mobj_t *P_SpawnMissile(mobj_t * source, mobj_t * dest, mobjtype_t type)
     an = R_PointToAngle2(source->x, source->y, dest->x, dest->y);
     if (dest->flags & MF_SHADOW)
     {                           // Invisible target
-        an += (P_Random() - P_Random()) << 21;
+        an += P_SubRandom() << 21;
     }
     th->angle = an;
     an >>= ANGLETOFINESHIFT;
@@ -2108,7 +2111,7 @@ mobj_t *P_SpawnMissileXYZ(fixed_t x, fixed_t y, fixed_t z,
     an = R_PointToAngle2(source->x, source->y, dest->x, dest->y);
     if (dest->flags & MF_SHADOW)
     {                           // Invisible target
-        an += (P_Random() - P_Random()) << 21;
+        an += P_SubRandom() << 21;
     }
     th->angle = an;
     an >>= ANGLETOFINESHIFT;
@@ -2449,7 +2452,7 @@ mobj_t *P_SpawnKoraxMissile(fixed_t x, fixed_t y, fixed_t z,
     an = R_PointToAngle2(x, y, dest->x, dest->y);
     if (dest->flags & MF_SHADOW)
     {                           // Invisible target
-        an += (P_Random() - P_Random()) << 21;
+        an += P_SubRandom() << 21;
     }
     th->angle = an;
     an >>= ANGLETOFINESHIFT;
diff --git a/src/hexen/p_pspr.c b/src/hexen/p_pspr.c
index bc8947c..9ab2a75 100644
--- a/src/hexen/p_pspr.c
+++ b/src/hexen/p_pspr.c
@@ -861,12 +861,16 @@ void A_FSwordAttack2(mobj_t * actor)
 void A_FSwordFlames(mobj_t * actor)
 {
     int i;
+    int r1,r2,r3;
 
     for (i = 1 + (P_Random() & 3); i; i--)
     {
-        P_SpawnMobj(actor->x + ((P_Random() - 128) << 12), actor->y
-                    + ((P_Random() - 128) << 12),
-                    actor->z + ((P_Random() - 128) << 11), MT_FSWORD_FLAME);
+        r1 = P_Random();
+        r2 = P_Random();
+        r3 = P_Random();
+        P_SpawnMobj(actor->x + ((r3 - 128) << 12), actor->y
+                    + ((r2 - 128) << 12),
+                    actor->z + ((r1 - 128) << 11), MT_FSWORD_FLAME);
     }
 }
 
@@ -979,6 +983,7 @@ void A_LightningZap(mobj_t * actor)
 {
     mobj_t *mo;
     fixed_t deltaZ;
+    int r1,r2;
 
     A_LightningClip(actor);
 
@@ -996,8 +1001,10 @@ void A_LightningZap(mobj_t * actor)
     {
         deltaZ = -10 * FRACUNIT;
     }
-    mo = P_SpawnMobj(actor->x + ((P_Random() - 128) * actor->radius / 256),
-                     actor->y + ((P_Random() - 128) * actor->radius / 256),
+    r1 = P_Random();
+    r2 = P_Random();
+    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)
     {
@@ -1388,8 +1395,10 @@ void A_FAxeAttack(player_t * player, pspdef_t * psp)
     int slope;
     int i;
     int useMana;
+    int r;
 
-    damage = 40 + (P_Random() & 15) + (P_Random() & 7);
+    r = P_Random();
+    damage = 40 + (r & 15) + (P_Random() & 7);
     power = 0;
     if (player->mana[MANA_1] > 0)
     {
@@ -1741,7 +1750,7 @@ void A_CFlameAttack(player_t *player, pspdef_t *psp)
 	angle = pmo->angle;
 	if(player->refire)
 	{
-		angle += (P_Random()-P_Random())<<17;
+		angle += P_SubRandom()<<17;
 	}
 	P_AimLineAttack(pmo, angle, CFLAMERANGE); // Correctly set linetarget
 	if(!linetarget)
@@ -1851,6 +1860,7 @@ void A_CHolyAttack2(mobj_t * actor)
 {
     int j;
     int i;
+    int r;
     mobj_t *mo;
     mobj_t *tail, *next;
 
@@ -1873,8 +1883,9 @@ void A_CHolyAttack2(mobj_t * actor)
                 mo->special2.i = (32 + (P_Random() & 7)) << 16;   // lower-left
                 break;
             case 3:
+                r = P_Random();
                 mo->special2.i =
-                    ((32 + (P_Random() & 7)) << 16) + 32 + (P_Random() & 7);
+                    ((32 + (r & 7)) << 16) + 32 + (P_Random() & 7);
                 break;
         }
         mo->z = actor->z;
diff --git a/src/hexen/p_spec.h b/src/hexen/p_spec.h
index 879b21a..750e918 100644
--- a/src/hexen/p_spec.h
+++ b/src/hexen/p_spec.h
@@ -546,6 +546,7 @@ void P_TagFinished(int tag);
 void P_PolyobjFinished(int po);
 void P_ACSInitNewGame(void);
 void P_CheckACSStore(void);
+void CheckACSPresent(int number);
 
 extern int ACScriptCount;
 extern byte *ActionCodeBase;
diff --git a/src/hexen/r_bsp.c b/src/hexen/r_bsp.c
index ece47a9..cae9cbf 100644
--- a/src/hexen/r_bsp.c
+++ b/src/hexen/r_bsp.c
@@ -59,7 +59,14 @@ typedef struct
     int first, last;
 } cliprange_t;
 
-#define MAXSEGS 32
+// We must expand MAXSEGS to the theoretical limit of the number of solidsegs
+// that can be generated in a scene by the DOOM engine. This was determined by
+// Lee Killough during BOOM development to be a function of the screensize.
+// The simplest thing we can do, other than fix this bug, is to let the game
+// render overage and then bomb out by detecting the overflow after the 
+// fact. -haleyjd
+//#define MAXSEGS 32
+#define MAXSEGS (SCREENWIDTH / 2 + 1)
 
 cliprange_t solidsegs[MAXSEGS], *newend;        // newend is one past the last valid seg
 
@@ -458,6 +465,10 @@ void R_Subsector(int num)
         R_AddLine(line);
         line++;
     }
+
+    // check for solidsegs overflow - extremely unsatisfactory!
+    if(newend > &solidsegs[32])
+        I_Error("R_Subsector: solidsegs overflow (vanilla may crash here)\n");
 }
 
 
diff --git a/src/hexen/r_data.c b/src/hexen/r_data.c
index e99ecc2..311d8e8 100644
--- a/src/hexen/r_data.c
+++ b/src/hexen/r_data.c
@@ -627,7 +627,7 @@ void R_PrecacheLevel(void)
         if (flatpresent[i])
         {
             lump = firstflat + i;
-            flatmemory += lumpinfo[lump].size;
+            flatmemory += lumpinfo[lump]->size;
             W_CacheLumpNum(lump, PU_CACHE);
         }
 
@@ -658,7 +658,7 @@ void R_PrecacheLevel(void)
         for (j = 0; j < texture->patchcount; j++)
         {
             lump = texture->patches[j].patch;
-            texturememory += lumpinfo[lump].size;
+            texturememory += lumpinfo[lump]->size;
             W_CacheLumpNum(lump, PU_CACHE);
         }
     }
@@ -688,7 +688,7 @@ void R_PrecacheLevel(void)
             for (k = 0; k < 8; k++)
             {
                 lump = firstspritelump + sf->lump[k];
-                spritememory += lumpinfo[lump].size;
+                spritememory += lumpinfo[lump]->size;
                 W_CacheLumpNum(lump, PU_CACHE);
             }
         }
diff --git a/src/hexen/r_things.c b/src/hexen/r_things.c
index 9d1cb6b..33ad92d 100644
--- a/src/hexen/r_things.c
+++ b/src/hexen/r_things.c
@@ -178,15 +178,15 @@ void R_InitSpriteDefs(char **namelist)
         // scan the lumps, filling in the frames for whatever is found
         //
         for (l = start + 1; l < end; l++)
-            if (!strncmp(lumpinfo[l].name, namelist[i], 4))
+            if (!strncmp(lumpinfo[l]->name, namelist[i], 4))
             {
-                frame = lumpinfo[l].name[4] - 'A';
-                rotation = lumpinfo[l].name[5] - '0';
+                frame = lumpinfo[l]->name[4] - 'A';
+                rotation = lumpinfo[l]->name[5] - '0';
                 R_InstallSpriteLump(l, frame, rotation, false);
-                if (lumpinfo[l].name[6])
+                if (lumpinfo[l]->name[6])
                 {
-                    frame = lumpinfo[l].name[6] - 'A';
-                    rotation = lumpinfo[l].name[7] - '0';
+                    frame = lumpinfo[l]->name[6] - 'A';
+                    rotation = lumpinfo[l]->name[7] - '0';
                     R_InstallSpriteLump(l, frame, rotation, true);
                 }
             }
diff --git a/src/hexen/s_sound.c b/src/hexen/s_sound.c
index 6066656..bd16d10 100644
--- a/src/hexen/s_sound.c
+++ b/src/hexen/s_sound.c
@@ -498,17 +498,16 @@ void S_StartSoundAtVolume(mobj_t * origin, int sound_id, int volume)
 //              vol = SoundCurve[dist];
     }
 
-#if 0
-// TODO
-    if (S_sfx[sound_id].changePitch)
+    // if the sfxinfo_t is marked as 'can be pitch shifted'
+    if (S_sfx[sound_id].pitch)
     {
-        Channel[i].pitch = (byte) (127 + (M_Random() & 7) - (M_Random() & 7));
+        Channel[i].pitch = (byte) (NORM_PITCH + (M_Random() & 7) - (M_Random() & 7));
     }
     else
     {
-        Channel[i].pitch = 127;
+        Channel[i].pitch = NORM_PITCH;
     }
-#endif
+
     if (S_sfx[sound_id].lumpnum == 0)
     {
         S_sfx[sound_id].lumpnum = I_GetSfxLumpNum(&S_sfx[sound_id]);
@@ -517,7 +516,8 @@ void S_StartSoundAtVolume(mobj_t * origin, int sound_id, int volume)
     Channel[i].handle = I_StartSound(&S_sfx[sound_id],
                                      i,
                                      vol,
-                                     sep /* , Channel[i].pitch] */);
+                                     sep,
+                                     Channel[i].pitch);
     Channel[i].sound_id = sound_id;
     Channel[i].priority = priority;
     Channel[i].volume = volume;
@@ -771,7 +771,7 @@ void S_UpdateSounds(mobj_t * listener)
                 if (sep > 192)
                     sep = 512 - sep;
             }
-            I_UpdateSoundParams(i, vol, sep /*, Channel[i].pitch */);
+            I_UpdateSoundParams(i, vol, sep);
             priority = S_sfx[Channel[i].sound_id].priority;
             priority *= PRIORITY_MAX_ADJUST - (dist / DIST_ADJUST);
             Channel[i].priority = priority;
@@ -787,7 +787,7 @@ void S_UpdateSounds(mobj_t * listener)
 
 void S_Init(void)
 {
-    I_SetOPLDriverVer(opl_v_old);
+    I_SetOPLDriverVer(opl_doom2_1_666);
     SoundCurve = W_CacheLumpName("SNDCURVE", PU_STATIC);
 //      SoundCurve = Z_Malloc(MAX_SND_DIST, PU_STATIC, NULL);
 
@@ -799,6 +799,12 @@ void S_Init(void)
 
     I_AtExit(S_ShutDown, true);
 
+    // Hexen defaults to pitch-shifting on
+    if (snd_pitchshift == -1)
+    {
+        snd_pitchshift = 1;
+    }
+
     I_PrecacheSounds(S_sfx, NUMSFX);
 
     // Attempt to setup CD music
diff --git a/src/hexen/sv_save.c b/src/hexen/sv_save.c
index 8fa211d..d3cea15 100644
--- a/src/hexen/sv_save.c
+++ b/src/hexen/sv_save.c
@@ -28,9 +28,6 @@
 #define MAX_TARGET_PLAYERS 512
 #define MOBJ_NULL -1
 #define MOBJ_XX_PLAYER -2
-#define GET_BYTE (*SavePtr.b++)
-#define GET_WORD SHORT(*SavePtr.w++)
-#define GET_LONG LONG(*SavePtr.l++)
 #define MAX_MAPS 99
 #define BASE_SLOT 6
 #define REBORN_SLOT 7
@@ -123,13 +120,19 @@ static void ClearSaveSlot(int slot);
 static void CopySaveSlot(int sourceSlot, int destSlot);
 static void CopyFile(char *sourceName, char *destName);
 static boolean ExistingFile(char *name);
-static void OpenStreamOut(char *fileName);
-static void CloseStreamOut(void);
-static void StreamOutBuffer(void *buffer, int size);
-static void StreamOutByte(byte val);
-static void StreamOutWord(unsigned short val);
-static void StreamOutLong(unsigned int val);
-static void StreamOutPtr(void *ptr);
+static void SV_OpenRead(char *fileName);
+static void SV_OpenWrite(char *fileName);
+static void SV_Close(void);
+static void SV_Read(void *buffer, int size);
+static byte SV_ReadByte(void);
+static uint16_t SV_ReadWord(void);
+static uint32_t SV_ReadLong(void);
+static void *SV_ReadPtr(void);
+static void SV_Write(void *buffer, int size);
+static void SV_WriteByte(byte val);
+static void SV_WriteWord(unsigned short val);
+static void SV_WriteLong(unsigned int val);
+static void SV_WritePtr(void *ptr);
 
 // EXTERNAL DATA DECLARATIONS ----------------------------------------------
 
@@ -139,20 +142,15 @@ static void StreamOutPtr(void *ptr);
 
 char *SavePath = DEFAULT_SAVEPATH;
 
+int vanilla_savegame_limit = 1;
+
 // PRIVATE DATA DEFINITIONS ------------------------------------------------
 
 static int MobjCount;
 static mobj_t **MobjList;
 static mobj_t ***TargetPlayerAddrs;
 static int TargetPlayerCount;
-static byte *SaveBuffer;
 static boolean SavingPlayers;
-static union
-{
-    byte *b;
-    short *w;
-    int *l;
-} SavePtr;
 static FILE *SavingFP;
 
 // CODE --------------------------------------------------------------------
@@ -168,15 +166,15 @@ static void StreamIn_acsstore_t(acsstore_t *str)
     int i;
 
     // int map;
-    str->map = GET_LONG;
+    str->map = SV_ReadLong();
 
     // int script;
-    str->script = GET_LONG;
+    str->script = SV_ReadLong();
 
     // byte args[4];
     for (i=0; i<4; ++i)
     {
-        str->args[i] = GET_BYTE;
+        str->args[i] = SV_ReadByte();
     }
 }
 
@@ -185,15 +183,15 @@ static void StreamOut_acsstore_t(acsstore_t *str)
     int i;
 
     // int map;
-    StreamOutLong(str->map);
+    SV_WriteLong(str->map);
 
     // int script;
-    StreamOutLong(str->script);
+    SV_WriteLong(str->script);
 
     // byte args[4];
     for (i=0; i<4; ++i)
     {
-        StreamOutByte(str->args[i]);
+        SV_WriteByte(str->args[i]);
     }
 }
 
@@ -206,55 +204,55 @@ static void StreamOut_acsstore_t(acsstore_t *str)
 static void StreamIn_ticcmd_t(ticcmd_t *str)
 {
     // char forwardmove;
-    str->forwardmove = GET_BYTE;
+    str->forwardmove = SV_ReadByte();
 
     // char sidemove;
-    str->sidemove = GET_BYTE;
+    str->sidemove = SV_ReadByte();
 
     // short angleturn;
-    str->angleturn = GET_WORD;
+    str->angleturn = SV_ReadWord();
 
     // short consistancy;
-    str->consistancy = GET_WORD;
+    str->consistancy = SV_ReadWord();
 
     // byte chatchar;
-    str->chatchar = GET_BYTE;
+    str->chatchar = SV_ReadByte();
 
     // byte buttons;
-    str->buttons = GET_BYTE;
+    str->buttons = SV_ReadByte();
 
     // byte lookfly;
-    str->lookfly = GET_BYTE;
+    str->lookfly = SV_ReadByte();
 
     // byte arti;
-    str->arti = GET_BYTE;
+    str->arti = SV_ReadByte();
 }
 
 static void StreamOut_ticcmd_t(ticcmd_t *str)
 {
     // char forwardmove;
-    StreamOutByte(str->forwardmove);
+    SV_WriteByte(str->forwardmove);
 
     // char sidemove;
-    StreamOutByte(str->sidemove);
+    SV_WriteByte(str->sidemove);
 
     // short angleturn;
-    StreamOutWord(str->angleturn);
+    SV_WriteWord(str->angleturn);
 
     // short consistancy;
-    StreamOutWord(str->consistancy);
+    SV_WriteWord(str->consistancy);
 
     // byte chatchar;
-    StreamOutByte(str->chatchar);
+    SV_WriteByte(str->chatchar);
 
     // byte buttons;
-    StreamOutByte(str->buttons);
+    SV_WriteByte(str->buttons);
 
     // byte lookfly;
-    StreamOutByte(str->lookfly);
+    SV_WriteByte(str->lookfly);
 
     // byte arti;
-    StreamOutByte(str->arti);
+    SV_WriteByte(str->arti);
 }
 
 
@@ -266,19 +264,19 @@ static void StreamOut_ticcmd_t(ticcmd_t *str)
 static void StreamIn_inventory_t(inventory_t *str)
 {
     // int type;
-    str->type = GET_LONG;
+    str->type = SV_ReadLong();
 
     // int count;
-    str->count = GET_LONG;
+    str->count = SV_ReadLong();
 }
 
 static void StreamOut_inventory_t(inventory_t *str)
 {
     // int type;
-    StreamOutLong(str->type);
+    SV_WriteLong(str->type);
 
     // int count;
-    StreamOutLong(str->count);
+    SV_WriteLong(str->count);
 }
 
 
@@ -294,7 +292,7 @@ static void StreamIn_pspdef_t(pspdef_t *str)
 
     // This is a pointer; it is stored as an index into the states table.
 
-    state_num = GET_LONG;
+    state_num = SV_ReadLong();
 
     if (state_num != 0)
     {
@@ -306,11 +304,11 @@ static void StreamIn_pspdef_t(pspdef_t *str)
     }
 
     // int tics;
-    str->tics = GET_LONG;
+    str->tics = SV_ReadLong();
 
     // fixed_t sx, sy;
-    str->sx = GET_LONG;
-    str->sy = GET_LONG;
+    str->sx = SV_ReadLong();
+    str->sy = SV_ReadLong();
 }
 
 static void StreamOut_pspdef_t(pspdef_t *str)
@@ -320,19 +318,19 @@ static void StreamOut_pspdef_t(pspdef_t *str)
     // rather than the pointer itself.
     if (str->state != NULL)
     {
-        StreamOutLong(str->state - states);
+        SV_WriteLong(str->state - states);
     }
     else
     {
-        StreamOutLong(0);
+        SV_WriteLong(0);
     }
 
     // int tics;
-    StreamOutLong(str->tics);
+    SV_WriteLong(str->tics);
 
     // fixed_t sx, sy;
-    StreamOutLong(str->sx);
-    StreamOutLong(str->sy);
+    SV_WriteLong(str->sx);
+    SV_WriteLong(str->sy);
 }
 
 
@@ -346,46 +344,46 @@ static void StreamIn_player_t(player_t *str)
 
     // mobj_t *mo;
     // Pointer value is reset on load.
-    str->mo = (void *) (intptr_t) GET_LONG;
+    str->mo = SV_ReadPtr();
     str->mo = NULL;
 
     // playerstate_t playerstate;
-    str->playerstate = GET_LONG;
+    str->playerstate = SV_ReadLong();
 
     // ticcmd_t cmd;
     StreamIn_ticcmd_t(&str->cmd);
 
     // pclass_t class;
-    str->class = GET_LONG;
+    str->class = SV_ReadLong();
 
     // fixed_t viewz;
-    str->viewz = GET_LONG;
+    str->viewz = SV_ReadLong();
 
     // fixed_t viewheight;
-    str->viewheight = GET_LONG;
+    str->viewheight = SV_ReadLong();
 
     // fixed_t deltaviewheight;
-    str->deltaviewheight = GET_LONG;
+    str->deltaviewheight = SV_ReadLong();
 
     // fixed_t bob;
-    str->bob = GET_LONG;
+    str->bob = SV_ReadLong();
 
     // int flyheight;
-    str->flyheight = GET_LONG;
+    str->flyheight = SV_ReadLong();
 
     // int lookdir;
-    str->lookdir = GET_LONG;
+    str->lookdir = SV_ReadLong();
 
     // boolean centering;
-    str->centering = GET_LONG;
+    str->centering = SV_ReadLong();
 
     // int health;
-    str->health = GET_LONG;
+    str->health = SV_ReadLong();
 
     // int armorpoints[NUMARMOR];
     for (i=0; i<NUMARMOR; ++i)
     {
-        str->armorpoints[i] = GET_LONG;
+        str->armorpoints[i] = SV_ReadLong();
     }
 
     // inventory_t inventory[NUMINVENTORYSLOTS];
@@ -395,105 +393,105 @@ static void StreamIn_player_t(player_t *str)
     }
 
     // artitype_t readyArtifact;
-    str->readyArtifact = GET_LONG;
+    str->readyArtifact = SV_ReadLong();
 
     // int artifactCount;
-    str->artifactCount = GET_LONG;
+    str->artifactCount = SV_ReadLong();
 
     // int inventorySlotNum;
-    str->inventorySlotNum = GET_LONG;
+    str->inventorySlotNum = SV_ReadLong();
 
     // int powers[NUMPOWERS];
     for (i=0; i<NUMPOWERS; ++i)
     {
-        str->powers[i] = GET_LONG;
+        str->powers[i] = SV_ReadLong();
     }
 
     // int keys;
-    str->keys = GET_LONG;
+    str->keys = SV_ReadLong();
 
     // int pieces;
-    str->pieces = GET_LONG;
+    str->pieces = SV_ReadLong();
 
     // signed int frags[MAXPLAYERS];
     for (i=0; i<maxplayers; ++i)
     {
-        str->frags[i] = GET_LONG;
+        str->frags[i] = SV_ReadLong();
     }
 
     // weapontype_t readyweapon;
-    str->readyweapon = GET_LONG;
+    str->readyweapon = SV_ReadLong();
 
     // weapontype_t pendingweapon;
-    str->pendingweapon = GET_LONG;
+    str->pendingweapon = SV_ReadLong();
 
     // boolean weaponowned[NUMWEAPONS];
     for (i=0; i<NUMWEAPONS; ++i)
     {
-        str->weaponowned[i] = GET_LONG;
+        str->weaponowned[i] = SV_ReadLong();
     }
 
     // int mana[NUMMANA];
     for (i=0; i<NUMMANA; ++i)
     {
-        str->mana[i] = GET_LONG;
+        str->mana[i] = SV_ReadLong();
     }
 
     // int attackdown, usedown;
-    str->attackdown = GET_LONG;
-    str->usedown = GET_LONG;
+    str->attackdown = SV_ReadLong();
+    str->usedown = SV_ReadLong();
 
     // int cheats;
-    str->cheats = GET_LONG;
+    str->cheats = SV_ReadLong();
 
     // int refire;
-    str->refire = GET_LONG;
+    str->refire = SV_ReadLong();
 
     // int killcount, itemcount, secretcount;
-    str->killcount = GET_LONG;
-    str->itemcount = GET_LONG;
-    str->secretcount = GET_LONG;
+    str->killcount = SV_ReadLong();
+    str->itemcount = SV_ReadLong();
+    str->secretcount = SV_ReadLong();
 
     // char message[80];
     for (i=0; i<80; ++i)
     {
-        str->message[i] = GET_BYTE;
+        str->message[i] = SV_ReadByte();
     }
 
     // int messageTics;
-    str->messageTics = GET_LONG;
+    str->messageTics = SV_ReadLong();
 
     // short ultimateMessage;
-    str->ultimateMessage = GET_WORD;
+    str->ultimateMessage = SV_ReadWord();
 
     // short yellowMessage;
-    str->yellowMessage = GET_WORD;
+    str->yellowMessage = SV_ReadWord();
 
     // int damagecount, bonuscount;
-    str->damagecount = GET_LONG;
-    str->bonuscount = GET_LONG;
+    str->damagecount = SV_ReadLong();
+    str->bonuscount = SV_ReadLong();
 
     // int poisoncount;
-    str->poisoncount = GET_LONG;
+    str->poisoncount = SV_ReadLong();
 
     // mobj_t *poisoner;
     // Pointer value is reset.
-    str->poisoner = (void *) (intptr_t) GET_LONG;
+    str->poisoner = SV_ReadPtr();
     str->poisoner = NULL;
 
     // mobj_t *attacker;
     // Pointer value is reset.
-    str->attacker = (void *) (intptr_t) GET_LONG;
+    str->attacker = SV_ReadPtr();
     str->attacker = NULL;
 
     // int extralight;
-    str->extralight = GET_LONG;
+    str->extralight = SV_ReadLong();
 
     // int fixedcolormap;
-    str->fixedcolormap = GET_LONG;
+    str->fixedcolormap = SV_ReadLong();
 
     // int colormap;
-    str->colormap = GET_LONG;
+    str->colormap = SV_ReadLong();
 
     // pspdef_t psprites[NUMPSPRITES];
     for (i=0; i<NUMPSPRITES; ++i)
@@ -502,13 +500,13 @@ static void StreamIn_player_t(player_t *str)
     }
 
     // int morphTics;
-    str->morphTics = GET_LONG;
+    str->morphTics = SV_ReadLong();
 
     // unsigned int jumpTics;
-    str->jumpTics = GET_LONG;
+    str->jumpTics = SV_ReadLong();
 
     // unsigned int worldTimer;
-    str->worldTimer = GET_LONG;
+    str->worldTimer = SV_ReadLong();
 }
 
 static void StreamOut_player_t(player_t *str)
@@ -516,45 +514,45 @@ static void StreamOut_player_t(player_t *str)
     int i;
 
     // mobj_t *mo;
-    StreamOutPtr(str->mo);
+    SV_WritePtr(str->mo);
 
     // playerstate_t playerstate;
-    StreamOutLong(str->playerstate);
+    SV_WriteLong(str->playerstate);
 
     // ticcmd_t cmd;
     StreamOut_ticcmd_t(&str->cmd);
 
     // pclass_t class;
-    StreamOutLong(str->class);
+    SV_WriteLong(str->class);
 
     // fixed_t viewz;
-    StreamOutLong(str->viewz);
+    SV_WriteLong(str->viewz);
 
     // fixed_t viewheight;
-    StreamOutLong(str->viewheight);
+    SV_WriteLong(str->viewheight);
 
     // fixed_t deltaviewheight;
-    StreamOutLong(str->deltaviewheight);
+    SV_WriteLong(str->deltaviewheight);
 
     // fixed_t bob;
-    StreamOutLong(str->bob);
+    SV_WriteLong(str->bob);
 
     // int flyheight;
-    StreamOutLong(str->flyheight);
+    SV_WriteLong(str->flyheight);
 
     // int lookdir;
-    StreamOutLong(str->lookdir);
+    SV_WriteLong(str->lookdir);
 
     // boolean centering;
-    StreamOutLong(str->centering);
+    SV_WriteLong(str->centering);
 
     // int health;
-    StreamOutLong(str->health);
+    SV_WriteLong(str->health);
 
     // int armorpoints[NUMARMOR];
     for (i=0; i<NUMARMOR; ++i)
     {
-        StreamOutLong(str->armorpoints[i]);
+        SV_WriteLong(str->armorpoints[i]);
     }
 
     // inventory_t inventory[NUMINVENTORYSLOTS];
@@ -564,101 +562,101 @@ static void StreamOut_player_t(player_t *str)
     }
 
     // artitype_t readyArtifact;
-    StreamOutLong(str->readyArtifact);
+    SV_WriteLong(str->readyArtifact);
 
     // int artifactCount;
-    StreamOutLong(str->artifactCount);
+    SV_WriteLong(str->artifactCount);
 
     // int inventorySlotNum;
-    StreamOutLong(str->inventorySlotNum);
+    SV_WriteLong(str->inventorySlotNum);
 
     // int powers[NUMPOWERS];
     for (i=0; i<NUMPOWERS; ++i)
     {
-        StreamOutLong(str->powers[i]);
+        SV_WriteLong(str->powers[i]);
     }
 
     // int keys;
-    StreamOutLong(str->keys);
+    SV_WriteLong(str->keys);
 
     // int pieces;
-    StreamOutLong(str->pieces);
+    SV_WriteLong(str->pieces);
 
     // signed int frags[MAXPLAYERS];
     for (i=0; i<maxplayers; ++i)
     {
-        StreamOutLong(str->frags[i]);
+        SV_WriteLong(str->frags[i]);
     }
 
     // weapontype_t readyweapon;
-    StreamOutLong(str->readyweapon);
+    SV_WriteLong(str->readyweapon);
 
     // weapontype_t pendingweapon;
-    StreamOutLong(str->pendingweapon);
+    SV_WriteLong(str->pendingweapon);
 
     // boolean weaponowned[NUMWEAPONS];
     for (i=0; i<NUMWEAPONS; ++i)
     {
-        StreamOutLong(str->weaponowned[i]);
+        SV_WriteLong(str->weaponowned[i]);
     }
 
     // int mana[NUMMANA];
     for (i=0; i<NUMMANA; ++i)
     {
-        StreamOutLong(str->mana[i]);
+        SV_WriteLong(str->mana[i]);
     }
 
     // int attackdown, usedown;
-    StreamOutLong(str->attackdown);
-    StreamOutLong(str->usedown);
+    SV_WriteLong(str->attackdown);
+    SV_WriteLong(str->usedown);
 
     // int cheats;
-    StreamOutLong(str->cheats);
+    SV_WriteLong(str->cheats);
 
     // int refire;
-    StreamOutLong(str->refire);
+    SV_WriteLong(str->refire);
 
     // int killcount, itemcount, secretcount;
-    StreamOutLong(str->killcount);
-    StreamOutLong(str->itemcount);
-    StreamOutLong(str->secretcount);
+    SV_WriteLong(str->killcount);
+    SV_WriteLong(str->itemcount);
+    SV_WriteLong(str->secretcount);
 
     // char message[80];
     for (i=0; i<80; ++i)
     {
-        StreamOutByte(str->message[i]);
+        SV_WriteByte(str->message[i]);
     }
 
     // int messageTics;
-    StreamOutLong(str->messageTics);
+    SV_WriteLong(str->messageTics);
 
     // short ultimateMessage;
-    StreamOutWord(str->ultimateMessage);
+    SV_WriteWord(str->ultimateMessage);
 
     // short yellowMessage;
-    StreamOutWord(str->yellowMessage);
+    SV_WriteWord(str->yellowMessage);
 
     // int damagecount, bonuscount;
-    StreamOutLong(str->damagecount);
-    StreamOutLong(str->bonuscount);
+    SV_WriteLong(str->damagecount);
+    SV_WriteLong(str->bonuscount);
 
     // int poisoncount;
-    StreamOutLong(str->poisoncount);
+    SV_WriteLong(str->poisoncount);
 
     // mobj_t *poisoner;
-    StreamOutPtr(str->poisoner);
+    SV_WritePtr(str->poisoner);
 
     // mobj_t *attacker;
-    StreamOutPtr(str->attacker);
+    SV_WritePtr(str->attacker);
 
     // int extralight;
-    StreamOutLong(str->extralight);
+    SV_WriteLong(str->extralight);
 
     // int fixedcolormap;
-    StreamOutLong(str->fixedcolormap);
+    SV_WriteLong(str->fixedcolormap);
 
     // int colormap;
-    StreamOutLong(str->colormap);
+    SV_WriteLong(str->colormap);
 
     // pspdef_t psprites[NUMPSPRITES];
     for (i=0; i<NUMPSPRITES; ++i)
@@ -667,13 +665,13 @@ static void StreamOut_player_t(player_t *str)
     }
 
     // int morphTics;
-    StreamOutLong(str->morphTics);
+    SV_WriteLong(str->morphTics);
 
     // unsigned int jumpTics;
-    StreamOutLong(str->jumpTics);
+    SV_WriteLong(str->jumpTics);
 
     // unsigned int worldTimer;
-    StreamOutLong(str->worldTimer);
+    SV_WriteLong(str->worldTimer);
 }
 
 
@@ -685,25 +683,25 @@ static void StreamIn_thinker_t(thinker_t *str)
 {
     // struct thinker_s *prev, *next;
     // Pointers are discarded:
-    str->prev = (void *) (intptr_t) GET_LONG;
+    str->prev = SV_ReadPtr();
     str->prev = NULL;
-    str->next = (void *) (intptr_t) GET_LONG;
+    str->next = SV_ReadPtr();
     str->next = NULL;
 
     // think_t function;
     // Function pointer is discarded:
-    str->function = (void *) (intptr_t) GET_LONG;
+    str->function = SV_ReadPtr();
     str->function = NULL;
 }
 
 static void StreamOut_thinker_t(thinker_t *str)
 {
     // struct thinker_s *prev, *next;
-    StreamOutPtr(str->prev);
-    StreamOutPtr(str->next);
+    SV_WritePtr(str->prev);
+    SV_WritePtr(str->next);
 
     // think_t function;
-    StreamOutPtr(&str->function);
+    SV_WritePtr(&str->function);
 }
 
 
@@ -715,8 +713,8 @@ static void StreamInMobjSpecials(mobj_t *mobj)
 {
     unsigned int special1, special2;
 
-    special1 = GET_LONG;
-    special2 = GET_LONG;
+    special1 = SV_ReadLong();
+    special2 = SV_ReadLong();
 
     mobj->special1.i = special1;
     mobj->special2.i = special2;
@@ -760,82 +758,82 @@ static void StreamIn_mobj_t(mobj_t *str)
     StreamIn_thinker_t(&str->thinker);
 
     // fixed_t x, y, z;
-    str->x = GET_LONG;
-    str->y = GET_LONG;
-    str->z = GET_LONG;
+    str->x = SV_ReadLong();
+    str->y = SV_ReadLong();
+    str->z = SV_ReadLong();
 
     // struct mobj_s *snext, *sprev;
     // Pointer values are discarded:
-    str->snext = (void *) (intptr_t) GET_LONG;
+    str->snext = SV_ReadPtr();
     str->snext = NULL;
-    str->sprev = (void *) (intptr_t) GET_LONG;
+    str->sprev = SV_ReadPtr();
     str->sprev = NULL;
 
     // angle_t angle;
-    str->angle = GET_LONG;
+    str->angle = SV_ReadLong();
 
     // spritenum_t sprite;
-    str->sprite = GET_LONG;
+    str->sprite = SV_ReadLong();
 
     // int frame;
-    str->frame = GET_LONG;
+    str->frame = SV_ReadLong();
 
     // struct mobj_s *bnext, *bprev;
     // Values are read but discarded; this will be restored when the thing's
     // position is set.
-    str->bnext = (void *) (intptr_t) GET_LONG;
+    str->bnext = SV_ReadPtr();
     str->bnext = NULL;
-    str->bprev = (void *) (intptr_t) GET_LONG;
+    str->bprev = SV_ReadPtr();
     str->bprev = NULL;
 
     // struct subsector_s *subsector;
     // Read but discard: pointer will be restored when thing position is set.
-    str->subsector = (void *) (intptr_t) GET_LONG;
+    str->subsector = SV_ReadPtr();
     str->subsector = NULL;
 
     // fixed_t floorz, ceilingz;
-    str->floorz = GET_LONG;
-    str->ceilingz = GET_LONG;
+    str->floorz = SV_ReadLong();
+    str->ceilingz = SV_ReadLong();
 
     // fixed_t floorpic;
-    str->floorpic = GET_LONG;
+    str->floorpic = SV_ReadLong();
 
     // fixed_t radius, height;
-    str->radius = GET_LONG;
-    str->height = GET_LONG;
+    str->radius = SV_ReadLong();
+    str->height = SV_ReadLong();
 
     // fixed_t momx, momy, momz;
-    str->momx = GET_LONG;
-    str->momy = GET_LONG;
-    str->momz = GET_LONG;
+    str->momx = SV_ReadLong();
+    str->momy = SV_ReadLong();
+    str->momz = SV_ReadLong();
 
     // int validcount;
-    str->validcount = GET_LONG;
+    str->validcount = SV_ReadLong();
 
     // mobjtype_t type;
-    str->type = GET_LONG;
+    str->type = SV_ReadLong();
 
     // mobjinfo_t *info;
     // Pointer value is read but discarded.
-    str->info = (void *) (intptr_t) GET_LONG;
+    str->info = SV_ReadPtr();
     str->info = NULL;
 
     // int tics;
-    str->tics = GET_LONG;
+    str->tics = SV_ReadLong();
 
     // state_t *state;
     // Restore as index into states table.
-    i = GET_LONG;
+    i = SV_ReadLong();
     str->state = &states[i];
 
     // int damage;
-    str->damage = GET_LONG;
+    str->damage = SV_ReadLong();
 
     // int flags;
-    str->flags = GET_LONG;
+    str->flags = SV_ReadLong();
 
     // int flags2;
-    str->flags2 = GET_LONG;
+    str->flags2 = SV_ReadLong();
 
     // specialval_t special1;
     // specialval_t special2;
@@ -844,27 +842,27 @@ static void StreamIn_mobj_t(mobj_t *str)
     StreamInMobjSpecials(str);
 
     // int health;
-    str->health = GET_LONG;
+    str->health = SV_ReadLong();
 
     // int movedir;
-    str->movedir = GET_LONG;
+    str->movedir = SV_ReadLong();
 
     // int movecount;
-    str->movecount = GET_LONG;
+    str->movecount = SV_ReadLong();
 
     // struct mobj_s *target;
-    i = GET_LONG;
+    i = SV_ReadLong();
     SetMobjPtr(&str->target, i);
 
     // int reactiontime;
-    str->reactiontime = GET_LONG;
+    str->reactiontime = SV_ReadLong();
 
     // int threshold;
-    str->threshold = GET_LONG;
+    str->threshold = SV_ReadLong();
 
     // struct player_s *player;
     // Saved as player number.
-    i = GET_LONG;
+    i = SV_ReadLong();
     if (i == 0)
     {
         str->player = NULL;
@@ -876,24 +874,24 @@ static void StreamIn_mobj_t(mobj_t *str)
     }
 
     // int lastlook;
-    str->lastlook = GET_LONG;
+    str->lastlook = SV_ReadLong();
 
     // fixed_t floorclip;
-    str->floorclip = GET_LONG;
+    str->floorclip = SV_ReadLong();
 
     // int archiveNum;
-    str->archiveNum = GET_LONG;
+    str->archiveNum = SV_ReadLong();
 
     // short tid;
-    str->tid = GET_WORD;
+    str->tid = SV_ReadWord();
 
     // byte special;
-    str->special = GET_BYTE;
+    str->special = SV_ReadByte();
 
     // byte args[5];
     for (i=0; i<5; ++i)
     {
-        str->args[i] = GET_BYTE;
+        str->args[i] = SV_ReadByte();
     }
 }
 
@@ -966,8 +964,8 @@ static void StreamOutMobjSpecials(mobj_t *mobj)
 
     // Write special values to savegame file.
 
-    StreamOutLong(special1);
-    StreamOutLong(special2);
+    SV_WriteLong(special1);
+    SV_WriteLong(special2);
 }
 
 static void StreamOut_mobj_t(mobj_t *str)
@@ -978,70 +976,70 @@ static void StreamOut_mobj_t(mobj_t *str)
     StreamOut_thinker_t(&str->thinker);
 
     // fixed_t x, y, z;
-    StreamOutLong(str->x);
-    StreamOutLong(str->y);
-    StreamOutLong(str->z);
+    SV_WriteLong(str->x);
+    SV_WriteLong(str->y);
+    SV_WriteLong(str->z);
 
     // struct mobj_s *snext, *sprev;
-    StreamOutPtr(str->snext);
-    StreamOutPtr(str->sprev);
+    SV_WritePtr(str->snext);
+    SV_WritePtr(str->sprev);
 
     // angle_t angle;
-    StreamOutLong(str->angle);
+    SV_WriteLong(str->angle);
 
     // spritenum_t sprite;
-    StreamOutLong(str->sprite);
+    SV_WriteLong(str->sprite);
 
     // int frame;
-    StreamOutLong(str->frame);
+    SV_WriteLong(str->frame);
 
     // struct mobj_s *bnext, *bprev;
-    StreamOutPtr(str->bnext);
-    StreamOutPtr(str->bprev);
+    SV_WritePtr(str->bnext);
+    SV_WritePtr(str->bprev);
 
     // struct subsector_s *subsector;
-    StreamOutPtr(str->subsector);
+    SV_WritePtr(str->subsector);
 
     // fixed_t floorz, ceilingz;
-    StreamOutLong(str->floorz);
-    StreamOutLong(str->ceilingz);
+    SV_WriteLong(str->floorz);
+    SV_WriteLong(str->ceilingz);
 
     // fixed_t floorpic;
-    StreamOutLong(str->floorpic);
+    SV_WriteLong(str->floorpic);
 
     // fixed_t radius, height;
-    StreamOutLong(str->radius);
-    StreamOutLong(str->height);
+    SV_WriteLong(str->radius);
+    SV_WriteLong(str->height);
 
     // fixed_t momx, momy, momz;
-    StreamOutLong(str->momx);
-    StreamOutLong(str->momy);
-    StreamOutLong(str->momz);
+    SV_WriteLong(str->momx);
+    SV_WriteLong(str->momy);
+    SV_WriteLong(str->momz);
 
     // int validcount;
-    StreamOutLong(str->validcount);
+    SV_WriteLong(str->validcount);
 
     // mobjtype_t type;
-    StreamOutLong(str->type);
+    SV_WriteLong(str->type);
 
     // mobjinfo_t *info;
-    StreamOutPtr(str->info);
+    SV_WritePtr(str->info);
 
     // int tics;
-    StreamOutLong(str->tics);
+    SV_WriteLong(str->tics);
 
     // state_t *state;
     // Save as index into the states table.
-    StreamOutLong(str->state - states);
+    SV_WriteLong(str->state - states);
 
     // int damage;
-    StreamOutLong(str->damage);
+    SV_WriteLong(str->damage);
 
     // int flags;
-    StreamOutLong(str->flags);
+    SV_WriteLong(str->flags);
 
     // int flags2;
-    StreamOutLong(str->flags2);
+    SV_WriteLong(str->flags2);
 
     // specialval_t special1;
     // specialval_t special2;
@@ -1049,60 +1047,60 @@ static void StreamOut_mobj_t(mobj_t *str)
     StreamOutMobjSpecials(str);
 
     // int health;
-    StreamOutLong(str->health);
+    SV_WriteLong(str->health);
 
     // int movedir;
-    StreamOutLong(str->movedir);
+    SV_WriteLong(str->movedir);
 
     // int movecount;
-    StreamOutLong(str->movecount);
+    SV_WriteLong(str->movecount);
 
     // struct mobj_s *target;
     if ((str->flags & MF_CORPSE) != 0)
     {
-        StreamOutLong(MOBJ_NULL);
+        SV_WriteLong(MOBJ_NULL);
     }
     else
     {
-        StreamOutLong(GetMobjNum(str->target));
+        SV_WriteLong(GetMobjNum(str->target));
     }
 
     // int reactiontime;
-    StreamOutLong(str->reactiontime);
+    SV_WriteLong(str->reactiontime);
 
     // int threshold;
-    StreamOutLong(str->threshold);
+    SV_WriteLong(str->threshold);
 
     // struct player_s *player;
     // Stored as index into players[] array, if there is a player pointer.
     if (str->player != NULL)
     {
-        StreamOutLong(str->player - players + 1);
+        SV_WriteLong(str->player - players + 1);
     }
     else
     {
-        StreamOutLong(0);
+        SV_WriteLong(0);
     }
 
     // int lastlook;
-    StreamOutLong(str->lastlook);
+    SV_WriteLong(str->lastlook);
 
     // fixed_t floorclip;
-    StreamOutLong(str->floorclip);
+    SV_WriteLong(str->floorclip);
 
     // int archiveNum;
-    StreamOutLong(str->archiveNum);
+    SV_WriteLong(str->archiveNum);
 
     // short tid;
-    StreamOutWord(str->tid);
+    SV_WriteWord(str->tid);
 
     // byte special;
-    StreamOutByte(str->special);
+    SV_WriteByte(str->special);
 
     // byte args[5];
     for (i=0; i<5; ++i)
     {
-        StreamOutByte(str->args[i]);
+        SV_WriteByte(str->args[i]);
     }
 }
 
@@ -1119,53 +1117,53 @@ static void StreamIn_floormove_t(floormove_t *str)
     StreamIn_thinker_t(&str->thinker);
 
     // sector_t *sector;
-    i = GET_LONG;
+    i = SV_ReadLong();
     str->sector = sectors + i;
 
     // floor_e type;
-    str->type = GET_LONG;
+    str->type = SV_ReadLong();
 
     // int crush;
-    str->crush = GET_LONG;
+    str->crush = SV_ReadLong();
 
     // int direction;
-    str->direction = GET_LONG;
+    str->direction = SV_ReadLong();
 
     // int newspecial;
-    str->newspecial = GET_LONG;
+    str->newspecial = SV_ReadLong();
 
     // short texture;
-    str->texture = GET_WORD;
+    str->texture = SV_ReadWord();
 
     // fixed_t floordestheight;
-    str->floordestheight = GET_LONG;
+    str->floordestheight = SV_ReadLong();
 
     // fixed_t speed;
-    str->speed = GET_LONG;
+    str->speed = SV_ReadLong();
 
     // int delayCount;
-    str->delayCount = GET_LONG;
+    str->delayCount = SV_ReadLong();
 
     // int delayTotal;
-    str->delayTotal = GET_LONG;
+    str->delayTotal = SV_ReadLong();
 
     // fixed_t stairsDelayHeight;
-    str->stairsDelayHeight = GET_LONG;
+    str->stairsDelayHeight = SV_ReadLong();
 
     // fixed_t stairsDelayHeightDelta;
-    str->stairsDelayHeightDelta = GET_LONG;
+    str->stairsDelayHeightDelta = SV_ReadLong();
 
     // fixed_t resetHeight;
-    str->resetHeight = GET_LONG;
+    str->resetHeight = SV_ReadLong();
 
     // short resetDelay;
-    str->resetDelay = GET_WORD;
+    str->resetDelay = SV_ReadWord();
 
     // short resetDelayCount;
-    str->resetDelayCount = GET_WORD;
+    str->resetDelayCount = SV_ReadWord();
 
     // byte textureChange;
-    str->textureChange = GET_BYTE;
+    str->textureChange = SV_ReadByte();
 }
 
 static void StreamOut_floormove_t(floormove_t *str)
@@ -1174,52 +1172,52 @@ static void StreamOut_floormove_t(floormove_t *str)
     StreamOut_thinker_t(&str->thinker);
 
     // sector_t *sector;
-    StreamOutLong(str->sector - sectors);
+    SV_WriteLong(str->sector - sectors);
 
     // floor_e type;
-    StreamOutLong(str->type);
+    SV_WriteLong(str->type);
 
     // int crush;
-    StreamOutLong(str->crush);
+    SV_WriteLong(str->crush);
 
     // int direction;
-    StreamOutLong(str->direction);
+    SV_WriteLong(str->direction);
 
     // int newspecial;
-    StreamOutLong(str->newspecial);
+    SV_WriteLong(str->newspecial);
 
     // short texture;
-    StreamOutWord(str->texture);
+    SV_WriteWord(str->texture);
 
     // fixed_t floordestheight;
-    StreamOutLong(str->floordestheight);
+    SV_WriteLong(str->floordestheight);
 
     // fixed_t speed;
-    StreamOutLong(str->speed);
+    SV_WriteLong(str->speed);
 
     // int delayCount;
-    StreamOutLong(str->delayCount);
+    SV_WriteLong(str->delayCount);
 
     // int delayTotal;
-    StreamOutLong(str->delayTotal);
+    SV_WriteLong(str->delayTotal);
 
     // fixed_t stairsDelayHeight;
-    StreamOutLong(str->stairsDelayHeight);
+    SV_WriteLong(str->stairsDelayHeight);
 
     // fixed_t stairsDelayHeightDelta;
-    StreamOutLong(str->stairsDelayHeightDelta);
+    SV_WriteLong(str->stairsDelayHeightDelta);
 
     // fixed_t resetHeight;
-    StreamOutLong(str->resetHeight);
+    SV_WriteLong(str->resetHeight);
 
     // short resetDelay;
-    StreamOutWord(str->resetDelay);
+    SV_WriteWord(str->resetDelay);
 
     // short resetDelayCount;
-    StreamOutWord(str->resetDelayCount);
+    SV_WriteWord(str->resetDelayCount);
 
     // byte textureChange;
-    StreamOutByte(str->textureChange);
+    SV_WriteByte(str->textureChange);
 }
 
 
@@ -1235,38 +1233,38 @@ static void StreamIn_plat_t(plat_t *str)
     StreamIn_thinker_t(&str->thinker);
 
     // sector_t *sector;
-    i = GET_LONG;
+    i = SV_ReadLong();
     str->sector = sectors + i;
 
     // fixed_t speed;
-    str->speed = GET_LONG;
+    str->speed = SV_ReadLong();
 
     // fixed_t low;
-    str->low = GET_LONG;
+    str->low = SV_ReadLong();
 
     // fixed_t high;
-    str->high = GET_LONG;
+    str->high = SV_ReadLong();
 
     // int wait;
-    str->wait = GET_LONG;
+    str->wait = SV_ReadLong();
 
     // int count;
-    str->count = GET_LONG;
+    str->count = SV_ReadLong();
 
     // plat_e status;
-    str->status = GET_LONG;
+    str->status = SV_ReadLong();
 
     // plat_e oldstatus;
-    str->oldstatus = GET_LONG;
+    str->oldstatus = SV_ReadLong();
 
     // int crush;
-    str->crush = GET_LONG;
+    str->crush = SV_ReadLong();
 
     // int tag;
-    str->tag = GET_LONG;
+    str->tag = SV_ReadLong();
 
     // plattype_e type;
-    str->type = GET_LONG;
+    str->type = SV_ReadLong();
 }
 
 static void StreamOut_plat_t(plat_t *str)
@@ -1275,37 +1273,37 @@ static void StreamOut_plat_t(plat_t *str)
     StreamOut_thinker_t(&str->thinker);
 
     // sector_t *sector;
-    StreamOutLong(str->sector - sectors);
+    SV_WriteLong(str->sector - sectors);
 
     // fixed_t speed;
-    StreamOutLong(str->speed);
+    SV_WriteLong(str->speed);
 
     // fixed_t low;
-    StreamOutLong(str->low);
+    SV_WriteLong(str->low);
 
     // fixed_t high;
-    StreamOutLong(str->high);
+    SV_WriteLong(str->high);
 
     // int wait;
-    StreamOutLong(str->wait);
+    SV_WriteLong(str->wait);
 
     // int count;
-    StreamOutLong(str->count);
+    SV_WriteLong(str->count);
 
     // plat_e status;
-    StreamOutLong(str->status);
+    SV_WriteLong(str->status);
 
     // plat_e oldstatus;
-    StreamOutLong(str->oldstatus);
+    SV_WriteLong(str->oldstatus);
 
     // int crush;
-    StreamOutLong(str->crush);
+    SV_WriteLong(str->crush);
 
     // int tag;
-    StreamOutLong(str->tag);
+    SV_WriteLong(str->tag);
 
     // plattype_e type;
-    StreamOutLong(str->type);
+    SV_WriteLong(str->type);
 }
 
 
@@ -1321,30 +1319,30 @@ static void StreamIn_ceiling_t(ceiling_t *str)
     StreamIn_thinker_t(&str->thinker);
 
     // sector_t *sector;
-    i = GET_LONG;
+    i = SV_ReadLong();
     str->sector = sectors + i;
 
     // ceiling_e type;
-    str->type = GET_LONG;
+    str->type = SV_ReadLong();
 
     // fixed_t bottomheight, topheight;
-    str->bottomheight = GET_LONG;
-    str->topheight = GET_LONG;
+    str->bottomheight = SV_ReadLong();
+    str->topheight = SV_ReadLong();
 
     // fixed_t speed;
-    str->speed = GET_LONG;
+    str->speed = SV_ReadLong();
 
     // int crush;
-    str->crush = GET_LONG;
+    str->crush = SV_ReadLong();
 
     // int direction;
-    str->direction = GET_LONG;
+    str->direction = SV_ReadLong();
 
     // int tag;
-    str->tag = GET_LONG;
+    str->tag = SV_ReadLong();
 
     // int olddirection;
-    str->olddirection = GET_LONG;
+    str->olddirection = SV_ReadLong();
 }
 
 static void StreamOut_ceiling_t(ceiling_t *str)
@@ -1353,29 +1351,29 @@ static void StreamOut_ceiling_t(ceiling_t *str)
     StreamOut_thinker_t(&str->thinker);
 
     // sector_t *sector;
-    StreamOutLong(str->sector - sectors);
+    SV_WriteLong(str->sector - sectors);
 
     // ceiling_e type;
-    StreamOutLong(str->type);
+    SV_WriteLong(str->type);
 
     // fixed_t bottomheight, topheight;
-    StreamOutLong(str->bottomheight);
-    StreamOutLong(str->topheight);
+    SV_WriteLong(str->bottomheight);
+    SV_WriteLong(str->topheight);
 
     // fixed_t speed;
-    StreamOutLong(str->speed);
+    SV_WriteLong(str->speed);
 
     // int crush;
-    StreamOutLong(str->crush);
+    SV_WriteLong(str->crush);
 
     // int direction;
-    StreamOutLong(str->direction);
+    SV_WriteLong(str->direction);
 
     // int tag;
-    StreamOutLong(str->tag);
+    SV_WriteLong(str->tag);
 
     // int olddirection;
-    StreamOutLong(str->olddirection);
+    SV_WriteLong(str->olddirection);
 }
 
 
@@ -1391,26 +1389,26 @@ static void StreamIn_light_t(light_t *str)
     StreamIn_thinker_t(&str->thinker);
 
     // sector_t *sector;
-    i = GET_LONG;
+    i = SV_ReadLong();
     str->sector = sectors + i;
 
     // lighttype_t type;
-    str->type = GET_LONG;
+    str->type = SV_ReadLong();
 
     // int value1;
-    str->value1 = GET_LONG;
+    str->value1 = SV_ReadLong();
 
     // int value2;
-    str->value2 = GET_LONG;
+    str->value2 = SV_ReadLong();
 
     // int tics1;
-    str->tics1 = GET_LONG;
+    str->tics1 = SV_ReadLong();
 
     // int tics2;
-    str->tics2 = GET_LONG;
+    str->tics2 = SV_ReadLong();
 
     // int count;
-    str->count = GET_LONG;
+    str->count = SV_ReadLong();
 }
 
 static void StreamOut_light_t(light_t *str)
@@ -1419,25 +1417,25 @@ static void StreamOut_light_t(light_t *str)
     StreamOut_thinker_t(&str->thinker);
 
     // sector_t *sector;
-    StreamOutLong(str->sector - sectors);
+    SV_WriteLong(str->sector - sectors);
 
     // lighttype_t type;
-    StreamOutLong(str->type);
+    SV_WriteLong(str->type);
 
     // int value1;
-    StreamOutLong(str->value1);
+    SV_WriteLong(str->value1);
 
     // int value2;
-    StreamOutLong(str->value2);
+    SV_WriteLong(str->value2);
 
     // int tics1;
-    StreamOutLong(str->tics1);
+    SV_WriteLong(str->tics1);
 
     // int tics2;
-    StreamOutLong(str->tics2);
+    SV_WriteLong(str->tics2);
 
     // int count;
-    StreamOutLong(str->count);
+    SV_WriteLong(str->count);
 }
 
 
@@ -1453,26 +1451,26 @@ static void StreamIn_vldoor_t(vldoor_t *str)
     StreamIn_thinker_t(&str->thinker);
 
     // sector_t *sector;
-    i = GET_LONG;
+    i = SV_ReadLong();
     str->sector = &sectors[i];
 
     // vldoor_e type;
-    str->type = GET_LONG;
+    str->type = SV_ReadLong();
 
     // fixed_t topheight;
-    str->topheight = GET_LONG;
+    str->topheight = SV_ReadLong();
 
     // fixed_t speed;
-    str->speed = GET_LONG;
+    str->speed = SV_ReadLong();
 
     // int direction;
-    str->direction = GET_LONG;
+    str->direction = SV_ReadLong();
 
     // int topwait;
-    str->topwait = GET_LONG;
+    str->topwait = SV_ReadLong();
 
     // int topcountdown;
-    str->topcountdown = GET_LONG;
+    str->topcountdown = SV_ReadLong();
 }
 
 static void StreamOut_vldoor_t(vldoor_t *str)
@@ -1481,25 +1479,25 @@ static void StreamOut_vldoor_t(vldoor_t *str)
     StreamOut_thinker_t(&str->thinker);
 
     // sector_t *sector;
-    StreamOutLong(str->sector - sectors);
+    SV_WriteLong(str->sector - sectors);
 
     // vldoor_e type;
-    StreamOutLong(str->type);
+    SV_WriteLong(str->type);
 
     // fixed_t topheight;
-    StreamOutLong(str->topheight);
+    SV_WriteLong(str->topheight);
 
     // fixed_t speed;
-    StreamOutLong(str->speed);
+    SV_WriteLong(str->speed);
 
     // int direction;
-    StreamOutLong(str->direction);
+    SV_WriteLong(str->direction);
 
     // int topwait;
-    StreamOutLong(str->topwait);
+    SV_WriteLong(str->topwait);
 
     // int topcountdown;
-    StreamOutLong(str->topcountdown);
+    SV_WriteLong(str->topcountdown);
 }
 
 
@@ -1515,14 +1513,14 @@ static void StreamIn_phase_t(phase_t *str)
     StreamIn_thinker_t(&str->thinker);
 
     // sector_t *sector;
-    i = GET_LONG;
+    i = SV_ReadLong();
     str->sector = &sectors[i];
 
     // int index;
-    str->index = GET_LONG;
+    str->index = SV_ReadLong();
 
     // int base;
-    str->base = GET_LONG;
+    str->base = SV_ReadLong();
 }
 
 static void StreamOut_phase_t(phase_t *str)
@@ -1531,13 +1529,13 @@ static void StreamOut_phase_t(phase_t *str)
     StreamOut_thinker_t(&str->thinker);
 
     // sector_t *sector;
-    StreamOutLong(str->sector - sectors);
+    SV_WriteLong(str->sector - sectors);
 
     // int index;
-    StreamOutLong(str->index);
+    SV_WriteLong(str->index);
 
     // int base;
-    StreamOutLong(str->base);
+    SV_WriteLong(str->base);
 }
 
 
@@ -1553,11 +1551,11 @@ static void StreamIn_acs_t(acs_t *str)
     StreamIn_thinker_t(&str->thinker);
 
     // mobj_t *activator;
-    i = GET_LONG;
+    i = SV_ReadLong();
     SetMobjPtr(&str->activator, i);
 
     // line_t *line;
-    i = GET_LONG;
+    i = SV_ReadLong();
     if (i != -1)
     {
         str->line = &lines[i];
@@ -1568,34 +1566,34 @@ static void StreamIn_acs_t(acs_t *str)
     }
 
     // int side;
-    str->side = GET_LONG;
+    str->side = SV_ReadLong();
 
     // int number;
-    str->number = GET_LONG;
+    str->number = SV_ReadLong();
 
     // int infoIndex;
-    str->infoIndex = GET_LONG;
+    str->infoIndex = SV_ReadLong();
 
     // int delayCount;
-    str->delayCount = GET_LONG;
+    str->delayCount = SV_ReadLong();
 
     // int stack[ACS_STACK_DEPTH];
     for (i=0; i<ACS_STACK_DEPTH; ++i)
     {
-        str->stack[i] = GET_LONG;
+        str->stack[i] = SV_ReadLong();
     }
 
     // int stackPtr;
-    str->stackPtr = GET_LONG;
+    str->stackPtr = SV_ReadLong();
 
     // int vars[MAX_ACS_SCRIPT_VARS];
     for (i=0; i<MAX_ACS_SCRIPT_VARS; ++i)
     {
-        str->vars[i] = GET_LONG;
+        str->vars[i] = SV_ReadLong();
     }
 
     // int *ip;
-    i = GET_LONG;
+    i = SV_ReadLong();
     str->ip = (int *) (ActionCodeBase + i);
 }
 
@@ -1607,47 +1605,47 @@ static void StreamOut_acs_t(acs_t *str)
     StreamOut_thinker_t(&str->thinker);
 
     // mobj_t *activator;
-    StreamOutLong(GetMobjNum(str->activator));
+    SV_WriteLong(GetMobjNum(str->activator));
 
     // line_t *line;
     if (str->line != NULL)
     {
-        StreamOutLong(str->line - lines);
+        SV_WriteLong(str->line - lines);
     }
     else
     {
-        StreamOutLong(-1);
+        SV_WriteLong(-1);
     }
 
     // int side;
-    StreamOutLong(str->side);
+    SV_WriteLong(str->side);
 
     // int number;
-    StreamOutLong(str->number);
+    SV_WriteLong(str->number);
 
     // int infoIndex;
-    StreamOutLong(str->infoIndex);
+    SV_WriteLong(str->infoIndex);
 
     // int delayCount;
-    StreamOutLong(str->delayCount);
+    SV_WriteLong(str->delayCount);
 
     // int stack[ACS_STACK_DEPTH];
     for (i=0; i<ACS_STACK_DEPTH; ++i)
     {
-        StreamOutLong(str->stack[i]);
+        SV_WriteLong(str->stack[i]);
     }
 
     // int stackPtr;
-    StreamOutLong(str->stackPtr);
+    SV_WriteLong(str->stackPtr);
 
     // int vars[MAX_ACS_SCRIPT_VARS];
     for (i=0; i<MAX_ACS_SCRIPT_VARS; ++i)
     {
-        StreamOutLong(str->vars[i]);
+        SV_WriteLong(str->vars[i]);
     }
 
     // int *ip;
-    StreamOutLong((byte *) str->ip - ActionCodeBase);
+    SV_WriteLong((byte *) str->ip - ActionCodeBase);
 }
 
 
@@ -1661,22 +1659,22 @@ static void StreamIn_polyevent_t(polyevent_t *str)
     StreamIn_thinker_t(&str->thinker);
 
     // int polyobj;
-    str->polyobj = GET_LONG;
+    str->polyobj = SV_ReadLong();
 
     // int speed;
-    str->speed = GET_LONG;
+    str->speed = SV_ReadLong();
 
     // unsigned int dist;
-    str->dist = GET_LONG;
+    str->dist = SV_ReadLong();
 
     // int angle;
-    str->angle = GET_LONG;
+    str->angle = SV_ReadLong();
 
     // fixed_t xSpeed;
-    str->xSpeed = GET_LONG;
+    str->xSpeed = SV_ReadLong();
 
     // fixed_t ySpeed;
-    str->ySpeed = GET_LONG;
+    str->ySpeed = SV_ReadLong();
 }
 
 static void StreamOut_polyevent_t(polyevent_t *str)
@@ -1685,22 +1683,22 @@ static void StreamOut_polyevent_t(polyevent_t *str)
     StreamOut_thinker_t(&str->thinker);
 
     // int polyobj;
-    StreamOutLong(str->polyobj);
+    SV_WriteLong(str->polyobj);
 
     // int speed;
-    StreamOutLong(str->speed);
+    SV_WriteLong(str->speed);
 
     // unsigned int dist;
-    StreamOutLong(str->dist);
+    SV_WriteLong(str->dist);
 
     // int angle;
-    StreamOutLong(str->angle);
+    SV_WriteLong(str->angle);
 
     // fixed_t xSpeed;
-    StreamOutLong(str->xSpeed);
+    SV_WriteLong(str->xSpeed);
 
     // fixed_t ySpeed;
-    StreamOutLong(str->ySpeed);
+    SV_WriteLong(str->ySpeed);
 }
 
 
@@ -1716,26 +1714,26 @@ static void StreamIn_pillar_t(pillar_t *str)
     StreamIn_thinker_t(&str->thinker);
 
     // sector_t *sector;
-    i = GET_LONG;
+    i = SV_ReadLong();
     str->sector = &sectors[i];
 
     // int ceilingSpeed;
-    str->ceilingSpeed = GET_LONG;
+    str->ceilingSpeed = SV_ReadLong();
 
     // int floorSpeed;
-    str->floorSpeed = GET_LONG;
+    str->floorSpeed = SV_ReadLong();
 
     // int floordest;
-    str->floordest = GET_LONG;
+    str->floordest = SV_ReadLong();
 
     // int ceilingdest;
-    str->ceilingdest = GET_LONG;
+    str->ceilingdest = SV_ReadLong();
 
     // int direction;
-    str->direction = GET_LONG;
+    str->direction = SV_ReadLong();
 
     // int crush;
-    str->crush = GET_LONG;
+    str->crush = SV_ReadLong();
 }
 
 static void StreamOut_pillar_t(pillar_t *str)
@@ -1744,25 +1742,25 @@ static void StreamOut_pillar_t(pillar_t *str)
     StreamOut_thinker_t(&str->thinker);
 
     // sector_t *sector;
-    StreamOutLong(str->sector - sectors);
+    SV_WriteLong(str->sector - sectors);
 
     // int ceilingSpeed;
-    StreamOutLong(str->ceilingSpeed);
+    SV_WriteLong(str->ceilingSpeed);
 
     // int floorSpeed;
-    StreamOutLong(str->floorSpeed);
+    SV_WriteLong(str->floorSpeed);
 
     // int floordest;
-    StreamOutLong(str->floordest);
+    SV_WriteLong(str->floordest);
 
     // int ceilingdest;
-    StreamOutLong(str->ceilingdest);
+    SV_WriteLong(str->ceilingdest);
 
     // int direction;
-    StreamOutLong(str->direction);
+    SV_WriteLong(str->direction);
 
     // int crush;
-    StreamOutLong(str->crush);
+    SV_WriteLong(str->crush);
 }
 
 
@@ -1776,35 +1774,35 @@ static void StreamIn_polydoor_t(polydoor_t *str)
     StreamIn_thinker_t(&str->thinker);
 
     // int polyobj;
-    str->polyobj = GET_LONG;
+    str->polyobj = SV_ReadLong();
 
     // int speed;
-    str->speed = GET_LONG;
+    str->speed = SV_ReadLong();
 
     // int dist;
-    str->dist = GET_LONG;
+    str->dist = SV_ReadLong();
 
     // int totalDist;
-    str->totalDist = GET_LONG;
+    str->totalDist = SV_ReadLong();
 
     // int direction;
-    str->direction = GET_LONG;
+    str->direction = SV_ReadLong();
 
     // fixed_t xSpeed, ySpeed;
-    str->xSpeed = GET_LONG;
-    str->ySpeed = GET_LONG;
+    str->xSpeed = SV_ReadLong();
+    str->ySpeed = SV_ReadLong();
 
     // int tics;
-    str->tics = GET_LONG;
+    str->tics = SV_ReadLong();
 
     // int waitTics;
-    str->waitTics = GET_LONG;
+    str->waitTics = SV_ReadLong();
 
     // podoortype_t type;
-    str->type = GET_LONG;
+    str->type = SV_ReadLong();
 
     // boolean close;
-    str->close = GET_LONG;
+    str->close = SV_ReadLong();
 }
 
 static void StreamOut_polydoor_t(polydoor_t *str)
@@ -1813,35 +1811,35 @@ static void StreamOut_polydoor_t(polydoor_t *str)
     StreamOut_thinker_t(&str->thinker);
 
     // int polyobj;
-    StreamOutLong(str->polyobj);
+    SV_WriteLong(str->polyobj);
 
     // int speed;
-    StreamOutLong(str->speed);
+    SV_WriteLong(str->speed);
 
     // int dist;
-    StreamOutLong(str->dist);
+    SV_WriteLong(str->dist);
 
     // int totalDist;
-    StreamOutLong(str->totalDist);
+    SV_WriteLong(str->totalDist);
 
     // int direction;
-    StreamOutLong(str->direction);
+    SV_WriteLong(str->direction);
 
     // fixed_t xSpeed, ySpeed;
-    StreamOutLong(str->xSpeed);
-    StreamOutLong(str->ySpeed);
+    SV_WriteLong(str->xSpeed);
+    SV_WriteLong(str->ySpeed);
 
     // int tics;
-    StreamOutLong(str->tics);
+    SV_WriteLong(str->tics);
 
     // int waitTics;
-    StreamOutLong(str->waitTics);
+    SV_WriteLong(str->waitTics);
 
     // podoortype_t type;
-    StreamOutLong(str->type);
+    SV_WriteLong(str->type);
 
     // boolean close;
-    StreamOutLong(str->close);
+    SV_WriteLong(str->close);
 }
 
 
@@ -1857,32 +1855,32 @@ static void StreamIn_floorWaggle_t(floorWaggle_t *str)
     StreamIn_thinker_t(&str->thinker);
 
     // sector_t *sector;
-    i = GET_LONG;
+    i = SV_ReadLong();
     str->sector = &sectors[i];
 
     // fixed_t originalHeight;
-    str->originalHeight = GET_LONG;
+    str->originalHeight = SV_ReadLong();
 
     // fixed_t accumulator;
-    str->accumulator = GET_LONG;
+    str->accumulator = SV_ReadLong();
 
     // fixed_t accDelta;
-    str->accDelta = GET_LONG;
+    str->accDelta = SV_ReadLong();
 
     // fixed_t targetScale;
-    str->targetScale = GET_LONG;
+    str->targetScale = SV_ReadLong();
 
     // fixed_t scale;
-    str->scale = GET_LONG;
+    str->scale = SV_ReadLong();
 
     // fixed_t scaleDelta;
-    str->scaleDelta = GET_LONG;
+    str->scaleDelta = SV_ReadLong();
 
     // int ticker;
-    str->ticker = GET_LONG;
+    str->ticker = SV_ReadLong();
 
     // int state;
-    str->state = GET_LONG;
+    str->state = SV_ReadLong();
 }
 
 static void StreamOut_floorWaggle_t(floorWaggle_t *str)
@@ -1891,31 +1889,31 @@ static void StreamOut_floorWaggle_t(floorWaggle_t *str)
     StreamOut_thinker_t(&str->thinker);
 
     // sector_t *sector;
-    StreamOutLong(str->sector - sectors);
+    SV_WriteLong(str->sector - sectors);
 
     // fixed_t originalHeight;
-    StreamOutLong(str->originalHeight);
+    SV_WriteLong(str->originalHeight);
 
     // fixed_t accumulator;
-    StreamOutLong(str->accumulator);
+    SV_WriteLong(str->accumulator);
 
     // fixed_t accDelta;
-    StreamOutLong(str->accDelta);
+    SV_WriteLong(str->accDelta);
 
     // fixed_t targetScale;
-    StreamOutLong(str->targetScale);
+    SV_WriteLong(str->targetScale);
 
     // fixed_t scale;
-    StreamOutLong(str->scale);
+    SV_WriteLong(str->scale);
 
     // fixed_t scaleDelta;
-    StreamOutLong(str->scaleDelta);
+    SV_WriteLong(str->scaleDelta);
 
     // int ticker;
-    StreamOutLong(str->ticker);
+    SV_WriteLong(str->ticker);
 
     // int state;
-    StreamOutLong(str->state);
+    SV_WriteLong(str->state);
 }
 
 
@@ -1933,27 +1931,27 @@ void SV_SaveGame(int slot, char *description)
 
     // Open the output file
     M_snprintf(fileName, sizeof(fileName), "%shex6.hxs", SavePath);
-    OpenStreamOut(fileName);
+    SV_OpenWrite(fileName);
 
     // Write game save description
-    StreamOutBuffer(description, HXS_DESCRIPTION_LENGTH);
+    SV_Write(description, HXS_DESCRIPTION_LENGTH);
 
     // Write version info
     memset(versionText, 0, HXS_VERSION_TEXT_LENGTH);
     M_StringCopy(versionText, HXS_VERSION_TEXT, HXS_VERSION_TEXT_LENGTH);
-    StreamOutBuffer(versionText, HXS_VERSION_TEXT_LENGTH);
+    SV_Write(versionText, HXS_VERSION_TEXT_LENGTH);
 
     // Place a header marker
-    StreamOutLong(ASEG_GAME_HEADER);
+    SV_WriteLong(ASEG_GAME_HEADER);
 
     // Write current map and difficulty
-    StreamOutByte(gamemap);
-    StreamOutByte(gameskill);
+    SV_WriteByte(gamemap);
+    SV_WriteByte(gameskill);
 
     // Write global script info
     for (i = 0; i < MAX_ACS_WORLD_VARS; ++i)
     {
-        StreamOutLong(WorldVars[i]);
+        SV_WriteLong(WorldVars[i]);
     }
 
     for (i = 0; i < MAX_ACS_STORE + 1; ++i)
@@ -1964,10 +1962,10 @@ void SV_SaveGame(int slot, char *description)
     ArchivePlayers();
 
     // Place a termination marker
-    StreamOutLong(ASEG_END);
+    SV_WriteLong(ASEG_END);
 
     // Close the output file
-    CloseStreamOut();
+    SV_Close();
 
     // Save out the current map
     SV_SaveMap(true);           // true = save player info
@@ -1993,13 +1991,13 @@ void SV_SaveMap(boolean savePlayers)
 
     // Open the output file
     M_snprintf(fileName, sizeof(fileName), "%shex6%02d.hxs", SavePath, gamemap);
-    OpenStreamOut(fileName);
+    SV_OpenWrite(fileName);
 
     // Place a header marker
-    StreamOutLong(ASEG_MAP_HEADER);
+    SV_WriteLong(ASEG_MAP_HEADER);
 
     // Write the level timer
-    StreamOutLong(leveltime);
+    SV_WriteLong(leveltime);
 
     // Set the mobj archive numbers
     SetMobjArchiveNums();
@@ -2013,10 +2011,10 @@ void SV_SaveMap(boolean savePlayers)
     ArchiveMisc();
 
     // Place a termination marker
-    StreamOutLong(ASEG_END);
+    SV_WriteLong(ASEG_END);
 
     // Close the output file
-    CloseStreamOut();
+    SV_Close();
 }
 
 //==========================================================================
@@ -2029,6 +2027,7 @@ void SV_LoadGame(int slot)
 {
     int i;
     char fileName[100];
+    char version_text[HXS_VERSION_TEXT_LENGTH];
     player_t playerBackup[MAXPLAYERS];
     mobj_t *mobj;
 
@@ -2043,29 +2042,33 @@ void SV_LoadGame(int slot)
     M_snprintf(fileName, sizeof(fileName), "%shex6.hxs", SavePath);
 
     // Load the file
-    M_ReadFile(fileName, &SaveBuffer);
+    SV_OpenRead(fileName);
 
     // Set the save pointer and skip the description field
-    SavePtr.b = SaveBuffer + HXS_DESCRIPTION_LENGTH;
+    fseek(SavingFP, HXS_DESCRIPTION_LENGTH, SEEK_CUR);
 
     // Check the version text
-    if (strcmp((char *) SavePtr.b, HXS_VERSION_TEXT))
+
+    for (i = 0; i < sizeof(version_text); ++i)
+    {
+        version_text[i] = SV_ReadByte();
+    }
+    if (strncmp(version_text, HXS_VERSION_TEXT, HXS_VERSION_TEXT_LENGTH) != 0)
     {                           // Bad version
         return;
     }
-    SavePtr.b += HXS_VERSION_TEXT_LENGTH;
 
     AssertSegment(ASEG_GAME_HEADER);
 
     gameepisode = 1;
-    gamemap = GET_BYTE;
-    gameskill = GET_BYTE;
+    gamemap = SV_ReadByte();
+    gameskill = SV_ReadByte();
 
     // Read global script info
 
     for (i = 0; i < MAX_ACS_WORLD_VARS; ++i)
     {
-        WorldVars[i] = GET_LONG;
+        WorldVars[i] = SV_ReadLong();
     }
 
     for (i = 0; i < MAX_ACS_STORE + 1; ++i)
@@ -2078,14 +2081,14 @@ void SV_LoadGame(int slot)
 
     AssertSegment(ASEG_END);
 
-    Z_Free(SaveBuffer);
-
     // Save player structs
     for (i = 0; i < maxplayers; i++)
     {
         playerBackup[i] = players[i];
     }
 
+    SV_Close();
+
     // Load the current map
     SV_LoadMap();
 
@@ -2358,13 +2361,12 @@ void SV_LoadMap(void)
     M_snprintf(fileName, sizeof(fileName), "%shex6%02d.hxs", SavePath, gamemap);
 
     // Load the file
-    M_ReadFile(fileName, &SaveBuffer);
-    SavePtr.b = SaveBuffer;
+    SV_OpenRead(fileName);
 
     AssertSegment(ASEG_MAP_HEADER);
 
     // Read the level timer
-    leveltime = GET_LONG;
+    leveltime = SV_ReadLong();
 
     UnarchiveWorld();
     UnarchivePolyobjs();
@@ -2378,7 +2380,7 @@ void SV_LoadMap(void)
 
     // Free mobj list and save buffer
     Z_Free(MobjList);
-    Z_Free(SaveBuffer);
+    SV_Close();
 }
 
 //==========================================================================
@@ -2402,10 +2404,10 @@ static void ArchivePlayers(void)
 {
     int i;
 
-    StreamOutLong(ASEG_PLAYERS);
+    SV_WriteLong(ASEG_PLAYERS);
     for (i = 0; i < maxplayers; i++)
     {
-        StreamOutByte(playeringame[i]);
+        SV_WriteByte(playeringame[i]);
     }
     for (i = 0; i < maxplayers; i++)
     {
@@ -2413,7 +2415,7 @@ static void ArchivePlayers(void)
         {
             continue;
         }
-        StreamOutByte(PlayerClass[i]);
+        SV_WriteByte(PlayerClass[i]);
         StreamOut_player_t(&players[i]);
     }
 }
@@ -2431,7 +2433,7 @@ static void UnarchivePlayers(void)
     AssertSegment(ASEG_PLAYERS);
     for (i = 0; i < maxplayers; i++)
     {
-        playeringame[i] = GET_BYTE;
+        playeringame[i] = SV_ReadByte();
     }
     for (i = 0; i < maxplayers; i++)
     {
@@ -2439,7 +2441,7 @@ static void UnarchivePlayers(void)
         {
             continue;
         }
-        PlayerClass[i] = GET_BYTE;
+        PlayerClass[i] = SV_ReadByte();
         StreamIn_player_t(&players[i]);
         P_ClearMessage(&players[i]);
     }
@@ -2459,27 +2461,27 @@ static void ArchiveWorld(void)
     line_t *li;
     side_t *si;
 
-    StreamOutLong(ASEG_WORLD);
+    SV_WriteLong(ASEG_WORLD);
     for (i = 0, sec = sectors; i < numsectors; i++, sec++)
     {
-        StreamOutWord(sec->floorheight >> FRACBITS);
-        StreamOutWord(sec->ceilingheight >> FRACBITS);
-        StreamOutWord(sec->floorpic);
-        StreamOutWord(sec->ceilingpic);
-        StreamOutWord(sec->lightlevel);
-        StreamOutWord(sec->special);
-        StreamOutWord(sec->tag);
-        StreamOutWord(sec->seqType);
+        SV_WriteWord(sec->floorheight >> FRACBITS);
+        SV_WriteWord(sec->ceilingheight >> FRACBITS);
+        SV_WriteWord(sec->floorpic);
+        SV_WriteWord(sec->ceilingpic);
+        SV_WriteWord(sec->lightlevel);
+        SV_WriteWord(sec->special);
+        SV_WriteWord(sec->tag);
+        SV_WriteWord(sec->seqType);
     }
     for (i = 0, li = lines; i < numlines; i++, li++)
     {
-        StreamOutWord(li->flags);
-        StreamOutByte(li->special);
-        StreamOutByte(li->arg1);
-        StreamOutByte(li->arg2);
-        StreamOutByte(li->arg3);
-        StreamOutByte(li->arg4);
-        StreamOutByte(li->arg5);
+        SV_WriteWord(li->flags);
+        SV_WriteByte(li->special);
+        SV_WriteByte(li->arg1);
+        SV_WriteByte(li->arg2);
+        SV_WriteByte(li->arg3);
+        SV_WriteByte(li->arg4);
+        SV_WriteByte(li->arg5);
         for (j = 0; j < 2; j++)
         {
             if (li->sidenum[j] == -1)
@@ -2487,11 +2489,11 @@ static void ArchiveWorld(void)
                 continue;
             }
             si = &sides[li->sidenum[j]];
-            StreamOutWord(si->textureoffset >> FRACBITS);
-            StreamOutWord(si->rowoffset >> FRACBITS);
-            StreamOutWord(si->toptexture);
-            StreamOutWord(si->bottomtexture);
-            StreamOutWord(si->midtexture);
+            SV_WriteWord(si->textureoffset >> FRACBITS);
+            SV_WriteWord(si->rowoffset >> FRACBITS);
+            SV_WriteWord(si->toptexture);
+            SV_WriteWord(si->bottomtexture);
+            SV_WriteWord(si->midtexture);
         }
     }
 }
@@ -2513,26 +2515,26 @@ static void UnarchiveWorld(void)
     AssertSegment(ASEG_WORLD);
     for (i = 0, sec = sectors; i < numsectors; i++, sec++)
     {
-        sec->floorheight = GET_WORD << FRACBITS;
-        sec->ceilingheight = GET_WORD << FRACBITS;
-        sec->floorpic = GET_WORD;
-        sec->ceilingpic = GET_WORD;
-        sec->lightlevel = GET_WORD;
-        sec->special = GET_WORD;
-        sec->tag = GET_WORD;
-        sec->seqType = GET_WORD;
+        sec->floorheight = SV_ReadWord() << FRACBITS;
+        sec->ceilingheight = SV_ReadWord() << FRACBITS;
+        sec->floorpic = SV_ReadWord();
+        sec->ceilingpic = SV_ReadWord();
+        sec->lightlevel = SV_ReadWord();
+        sec->special = SV_ReadWord();
+        sec->tag = SV_ReadWord();
+        sec->seqType = SV_ReadWord();
         sec->specialdata = 0;
         sec->soundtarget = 0;
     }
     for (i = 0, li = lines; i < numlines; i++, li++)
     {
-        li->flags = GET_WORD;
-        li->special = GET_BYTE;
-        li->arg1 = GET_BYTE;
-        li->arg2 = GET_BYTE;
-        li->arg3 = GET_BYTE;
-        li->arg4 = GET_BYTE;
-        li->arg5 = GET_BYTE;
+        li->flags = SV_ReadWord();
+        li->special = SV_ReadByte();
+        li->arg1 = SV_ReadByte();
+        li->arg2 = SV_ReadByte();
+        li->arg3 = SV_ReadByte();
+        li->arg4 = SV_ReadByte();
+        li->arg5 = SV_ReadByte();
         for (j = 0; j < 2; j++)
         {
             if (li->sidenum[j] == -1)
@@ -2540,11 +2542,11 @@ static void UnarchiveWorld(void)
                 continue;
             }
             si = &sides[li->sidenum[j]];
-            si->textureoffset = GET_WORD << FRACBITS;
-            si->rowoffset = GET_WORD << FRACBITS;
-            si->toptexture = GET_WORD;
-            si->bottomtexture = GET_WORD;
-            si->midtexture = GET_WORD;
+            si->textureoffset = SV_ReadWord() << FRACBITS;
+            si->rowoffset = SV_ReadWord() << FRACBITS;
+            si->toptexture = SV_ReadWord();
+            si->bottomtexture = SV_ReadWord();
+            si->midtexture = SV_ReadWord();
         }
     }
 }
@@ -2590,8 +2592,8 @@ static void ArchiveMobjs(void)
     int count;
     thinker_t *thinker;
 
-    StreamOutLong(ASEG_MOBJS);
-    StreamOutLong(MobjCount);
+    SV_WriteLong(ASEG_MOBJS);
+    SV_WriteLong(MobjCount);
     count = 0;
     for (thinker = thinkercap.next; thinker != &thinkercap;
          thinker = thinker->next)
@@ -2628,7 +2630,7 @@ static void UnarchiveMobjs(void)
     TargetPlayerAddrs = Z_Malloc(MAX_TARGET_PLAYERS * sizeof(mobj_t **),
                                  PU_STATIC, NULL);
     TargetPlayerCount = 0;
-    MobjCount = GET_LONG;
+    MobjCount = SV_ReadLong();
     MobjList = Z_Malloc(MobjCount * sizeof(mobj_t *), PU_STATIC, NULL);
     for (i = 0; i < MobjCount; i++)
     {
@@ -2820,7 +2822,7 @@ static void ArchiveThinkers(void)
     thinker_t *thinker;
     thinkInfo_t *info;
 
-    StreamOutLong(ASEG_THINKERS);
+    SV_WriteLong(ASEG_THINKERS);
     for (thinker = thinkercap.next; thinker != &thinkercap;
          thinker = thinker->next)
     {
@@ -2828,14 +2830,14 @@ static void ArchiveThinkers(void)
         {
             if (thinker->function == info->thinkerFunc)
             {
-                StreamOutByte(info->tClass);
+                SV_WriteByte(info->tClass);
                 info->writeFunc(thinker);
                 break;
             }
         }
     }
     // Add a termination marker
-    StreamOutByte(TC_NULL);
+    SV_WriteByte(TC_NULL);
 }
 
 //==========================================================================
@@ -2851,7 +2853,7 @@ static void UnarchiveThinkers(void)
     thinkInfo_t *info;
 
     AssertSegment(ASEG_THINKERS);
-    while ((tClass = GET_BYTE) != TC_NULL)
+    while ((tClass = SV_ReadByte()) != TC_NULL)
     {
         for (info = ThinkerInfo; info->tClass != TC_NULL; info++)
         {
@@ -2921,16 +2923,16 @@ static void ArchiveScripts(void)
 {
     int i;
 
-    StreamOutLong(ASEG_SCRIPTS);
+    SV_WriteLong(ASEG_SCRIPTS);
     for (i = 0; i < ACScriptCount; i++)
     {
-        StreamOutWord(ACSInfo[i].state);
-        StreamOutWord(ACSInfo[i].waitValue);
+        SV_WriteWord(ACSInfo[i].state);
+        SV_WriteWord(ACSInfo[i].waitValue);
     }
 
     for (i = 0; i< MAX_ACS_MAP_VARS; ++i)
     {
-        StreamOutLong(MapVars[i]);
+        SV_WriteLong(MapVars[i]);
     }
 }
 
@@ -2947,13 +2949,13 @@ static void UnarchiveScripts(void)
     AssertSegment(ASEG_SCRIPTS);
     for (i = 0; i < ACScriptCount; i++)
     {
-        ACSInfo[i].state = GET_WORD;
-        ACSInfo[i].waitValue = GET_WORD;
+        ACSInfo[i].state = SV_ReadWord();
+        ACSInfo[i].waitValue = SV_ReadWord();
     }
 
     for (i = 0; i < MAX_ACS_MAP_VARS; ++i)
     {
-        MapVars[i] = GET_LONG;
+        MapVars[i] = SV_ReadLong();
     }
 }
 
@@ -2967,10 +2969,10 @@ static void ArchiveMisc(void)
 {
     int ix;
 
-    StreamOutLong(ASEG_MISC);
+    SV_WriteLong(ASEG_MISC);
     for (ix = 0; ix < maxplayers; ix++)
     {
-        StreamOutLong(localQuakeHappening[ix]);
+        SV_WriteLong(localQuakeHappening[ix]);
     }
 }
 
@@ -2987,7 +2989,7 @@ static void UnarchiveMisc(void)
     AssertSegment(ASEG_MISC);
     for (ix = 0; ix < maxplayers; ix++)
     {
-        localQuakeHappening[ix] = GET_LONG;
+        localQuakeHappening[ix] = SV_ReadLong();
     }
 }
 
@@ -3032,18 +3034,18 @@ static void ArchiveSounds(void)
     int difference;
     int i;
 
-    StreamOutLong(ASEG_SOUNDS);
+    SV_WriteLong(ASEG_SOUNDS);
 
     // Save the sound sequences
-    StreamOutLong(ActiveSequences);
+    SV_WriteLong(ActiveSequences);
     for (node = SequenceListHead; node; node = node->next)
     {
-        StreamOutLong(node->sequence);
-        StreamOutLong(node->delayTics);
-        StreamOutLong(node->volume);
-        StreamOutLong(SN_GetSequenceOffset(node->sequence,
+        SV_WriteLong(node->sequence);
+        SV_WriteLong(node->delayTics);
+        SV_WriteLong(node->volume);
+        SV_WriteLong(SN_GetSequenceOffset(node->sequence,
                                            node->sequencePtr));
-        StreamOutLong(node->currentSoundID);
+        SV_WriteLong(node->currentSoundID);
         for (i = 0; i < po_NumPolyobjs; i++)
         {
             if (node->mobj == (mobj_t *) & polyobjs[i].startSpot)
@@ -3056,14 +3058,14 @@ static void ArchiveSounds(void)
             sec = R_PointInSubsector(node->mobj->x, node->mobj->y)->sector;
             difference = (int) ((byte *) sec
                                 - (byte *) & sectors[0]) / sizeof(sector_t);
-            StreamOutLong(0);   // 0 -- sector sound origin
+            SV_WriteLong(0);   // 0 -- sector sound origin
         }
         else
         {
-            StreamOutLong(1);   // 1 -- polyobj sound origin
+            SV_WriteLong(1);   // 1 -- polyobj sound origin
             difference = i;
         }
-        StreamOutLong(difference);
+        SV_WriteLong(difference);
     }
 }
 
@@ -3089,18 +3091,18 @@ static void UnarchiveSounds(void)
     AssertSegment(ASEG_SOUNDS);
 
     // Reload and restart all sound sequences
-    numSequences = GET_LONG;
+    numSequences = SV_ReadLong();
     i = 0;
     while (i < numSequences)
     {
-        sequence = GET_LONG;
-        delayTics = GET_LONG;
-        volume = GET_LONG;
-        seqOffset = GET_LONG;
+        sequence = SV_ReadLong();
+        delayTics = SV_ReadLong();
+        volume = SV_ReadLong();
+        seqOffset = SV_ReadLong();
 
-        soundID = GET_LONG;
-        polySnd = GET_LONG;
-        secNum = GET_LONG;
+        soundID = SV_ReadLong();
+        polySnd = SV_ReadLong();
+        secNum = SV_ReadLong();
         if (!polySnd)
         {
             sndMobj = (mobj_t *) & sectors[secNum].soundorg;
@@ -3125,14 +3127,14 @@ static void ArchivePolyobjs(void)
 {
     int i;
 
-    StreamOutLong(ASEG_POLYOBJS);
-    StreamOutLong(po_NumPolyobjs);
+    SV_WriteLong(ASEG_POLYOBJS);
+    SV_WriteLong(po_NumPolyobjs);
     for (i = 0; i < po_NumPolyobjs; i++)
     {
-        StreamOutLong(polyobjs[i].tag);
-        StreamOutLong(polyobjs[i].angle);
-        StreamOutLong(polyobjs[i].startSpot.x);
-        StreamOutLong(polyobjs[i].startSpot.y);
+        SV_WriteLong(polyobjs[i].tag);
+        SV_WriteLong(polyobjs[i].angle);
+        SV_WriteLong(polyobjs[i].startSpot.x);
+        SV_WriteLong(polyobjs[i].startSpot.y);
     }
 }
 
@@ -3149,19 +3151,19 @@ static void UnarchivePolyobjs(void)
     fixed_t deltaY;
 
     AssertSegment(ASEG_POLYOBJS);
-    if (GET_LONG != po_NumPolyobjs)
+    if (SV_ReadLong() != po_NumPolyobjs)
     {
         I_Error("UnarchivePolyobjs: Bad polyobj count");
     }
     for (i = 0; i < po_NumPolyobjs; i++)
     {
-        if (GET_LONG != polyobjs[i].tag)
+        if (SV_ReadLong() != polyobjs[i].tag)
         {
             I_Error("UnarchivePolyobjs: Invalid polyobj tag");
         }
-        PO_RotatePolyobj(polyobjs[i].tag, (angle_t) GET_LONG);
-        deltaX = GET_LONG - polyobjs[i].startSpot.x;
-        deltaY = GET_LONG - polyobjs[i].startSpot.y;
+        PO_RotatePolyobj(polyobjs[i].tag, (angle_t) SV_ReadLong());
+        deltaX = SV_ReadLong() - polyobjs[i].startSpot.x;
+        deltaY = SV_ReadLong() - polyobjs[i].startSpot.y;
         PO_MovePolyobj(polyobjs[i].tag, deltaX, deltaY);
     }
 }
@@ -3174,7 +3176,7 @@ static void UnarchivePolyobjs(void)
 
 static void AssertSegment(gameArchiveSegment_t segType)
 {
-    if (GET_LONG != segType)
+    if (SV_ReadLong() != segType)
     {
         I_Error("Corrupt save game: Segment [%d] failed alignment check",
                 segType);
@@ -3243,16 +3245,73 @@ static void CopySaveSlot(int sourceSlot, int destSlot)
 //
 // CopyFile
 //
+// This function was rewritten to copy files with minimal strain on zone
+// allocation and allow for big maps that technically work in vanilla to
+// save without error.
 //==========================================================================
 
-static void CopyFile(char *sourceName, char *destName)
+static void CopyFile(char *source_name, char *dest_name)
 {
-    int length;
+    const int BUFFER_CHUNK_SIZE = 0x10000;
+
     byte *buffer;
+    int file_length, file_remaining;
+    FILE *read_handle, *write_handle;
+    int buf_count, read_count, write_count;
+
+    read_handle = fopen(source_name, "rb");
+    if (read_handle == NULL)
+    {
+        I_Error ("Couldn't read file %s", source_name);
+    }
+    file_length = file_remaining = M_FileLength(read_handle);
+
+    // Vanilla savegame emulation.
+    //
+    // CopyFile() typically calls M_ReadFile() which stores the entire file
+    // in memory: Chocolate Hexen should force an allocation error here
+    // whenever it's appropriate.
+
+    if (vanilla_savegame_limit)
+    {
+        buffer = Z_Malloc(file_length, PU_STATIC, NULL);
+        Z_Free(buffer);
+    }
+
+    write_handle = fopen(dest_name, "wb");
+    if (write_handle == NULL)
+    {
+        I_Error ("Couldn't read file %s", dest_name);
+    }
+
+    buffer = Z_Malloc (BUFFER_CHUNK_SIZE, PU_STATIC, NULL);
+
+    do
+    {
+        buf_count = BUFFER_CHUNK_SIZE;
+        if( file_remaining < BUFFER_CHUNK_SIZE)
+        {
+            buf_count = file_remaining;
+        }
+
+        read_count = fread(buffer, 1, buf_count, read_handle);
+        if (read_count < buf_count)
+        {
+            I_Error ("Couldn't read file %s", source_name);
+        }
+
+        write_count = fwrite(buffer, 1, buf_count, write_handle);
+        if (write_count < buf_count)
+        {
+            I_Error ("Couldn't write to file %s", dest_name);
+        }
+
+        file_remaining -= buf_count;
+    } while (file_remaining > 0);
 
-    length = M_ReadFile(sourceName, &buffer);
-    M_WriteFile(destName, buffer, length);
     Z_Free(buffer);
+    fclose(read_handle);
+    fclose(write_handle);
 }
 
 //==========================================================================
@@ -3278,22 +3337,27 @@ static boolean ExistingFile(char *name)
 
 //==========================================================================
 //
-// OpenStreamOut
+// SV_Open
 //
 //==========================================================================
 
-static void OpenStreamOut(char *fileName)
+static void SV_OpenRead(char *fileName)
+{
+    SavingFP = fopen(fileName, "rb");
+}
+
+static void SV_OpenWrite(char *fileName)
 {
     SavingFP = fopen(fileName, "wb");
 }
 
 //==========================================================================
 //
-// CloseStreamOut
+// SV_Close
 //
 //==========================================================================
 
-static void CloseStreamOut(void)
+static void SV_Close(void)
 {
     if (SavingFP)
     {
@@ -3303,57 +3367,70 @@ static void CloseStreamOut(void)
 
 //==========================================================================
 //
-// StreamOutBuffer
+// SV_Read
 //
 //==========================================================================
 
-static void StreamOutBuffer(void *buffer, int size)
+static void SV_Read(void *buffer, int size)
 {
-    fwrite(buffer, size, 1, SavingFP);
+    fread(buffer, size, 1, SavingFP);
 }
 
-//==========================================================================
-//
-// StreamOutByte
-//
-//==========================================================================
+static byte SV_ReadByte(void)
+{
+    byte result;
+    SV_Read(&result, sizeof(byte));
+    return result;
+}
 
-static void StreamOutByte(byte val)
+static uint16_t SV_ReadWord(void)
 {
-    fwrite(&val, sizeof(byte), 1, SavingFP);
+    uint16_t result;
+    SV_Read(&result, sizeof(unsigned short));
+    return SHORT(result);
+}
+
+static uint32_t SV_ReadLong(void)
+{
+    uint32_t result;
+    SV_Read(&result, sizeof(int));
+    return LONG(result);
+}
+
+static void *SV_ReadPtr(void)
+{
+    return (void *) (intptr_t) SV_ReadLong();
 }
 
 //==========================================================================
 //
-// StreamOutWord
+// SV_Write
 //
 //==========================================================================
 
-static void StreamOutWord(unsigned short val)
+static void SV_Write(void *buffer, int size)
+{
+    fwrite(buffer, size, 1, SavingFP);
+}
+
+static void SV_WriteByte(byte val)
+{
+    fwrite(&val, sizeof(byte), 1, SavingFP);
+}
+
+static void SV_WriteWord(unsigned short val)
 {
     val = SHORT(val);
     fwrite(&val, sizeof(unsigned short), 1, SavingFP);
 }
 
-//==========================================================================
-//
-// StreamOutLong
-//
-//==========================================================================
-
-static void StreamOutLong(unsigned int val)
+static void SV_WriteLong(unsigned int val)
 {
     val = LONG(val);
     fwrite(&val, sizeof(int), 1, SavingFP);
 }
 
-//==========================================================================
-//
-// StreamOutPtr
-//
-//==========================================================================
-
-static void StreamOutPtr(void *val)
+static void SV_WritePtr(void *val)
 {
     long ptr;
 
@@ -3362,7 +3439,5 @@ static void StreamOutPtr(void *val)
     // going to be much use when we reload the game.
 
     ptr = (long) val;
-    StreamOutLong((unsigned int) (ptr & 0xffffffff));
+    SV_WriteLong((unsigned int) (ptr & 0xffffffff));
 }
-
-
diff --git a/src/i_joystick.c b/src/i_joystick.c
index eba7f21..2ef27f1 100644
--- a/src/i_joystick.c
+++ b/src/i_joystick.c
@@ -68,7 +68,7 @@ static int joystick_strafe_invert = 0;
 // Virtual to physical button joystick button mapping. By default this
 // is a straight mapping.
 static int joystick_physical_buttons[NUM_VIRTUAL_BUTTONS] = {
-    0, 1, 2, 3, 4, 5, 6, 7, 8, 9
+    0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10
 };
 
 void I_ShutdownJoystick(void)
@@ -107,7 +107,7 @@ static boolean IsValidAxis(int axis)
 
 void I_InitJoystick(void)
 {
-    if (!usejoystick)
+    if (!usejoystick || joystick_index < 0)
     {
         return;
     }
@@ -117,7 +117,7 @@ void I_InitJoystick(void)
         return;
     }
 
-    if (joystick_index < 0 || joystick_index >= SDL_NumJoysticks())
+    if (joystick_index >= SDL_NumJoysticks())
     {
         printf("I_InitJoystick: Invalid joystick ID: %i\n", joystick_index);
         SDL_QuitSubSystem(SDL_INIT_JOYSTICK);
diff --git a/src/i_joystick.h b/src/i_joystick.h
index b8815e2..1c9744f 100644
--- a/src/i_joystick.h
+++ b/src/i_joystick.h
@@ -22,7 +22,7 @@
 // Number of "virtual" joystick buttons defined in configuration files.
 // This needs to be at least as large as the number of different key
 // bindings supported by the higher-level game code (joyb* variables).
-#define NUM_VIRTUAL_BUTTONS 10
+#define NUM_VIRTUAL_BUTTONS 11
 
 // If this bit is set in a configuration file axis value, the axis is
 // not actually a joystick axis, but instead is a "button axis". This
diff --git a/src/i_oplmusic.c b/src/i_oplmusic.c
index 239c979..7502af4 100644
--- a/src/i_oplmusic.c
+++ b/src/i_oplmusic.c
@@ -85,6 +85,7 @@ typedef struct
     // Volume level
 
     int volume;
+    int volume_base;
 
     // Pan
 
@@ -100,10 +101,6 @@ typedef struct
 
 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;
@@ -148,17 +145,14 @@ struct opl_voice_s
     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;
 
     // 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.
@@ -307,10 +301,11 @@ static const unsigned int volume_mapping_table[] = {
     124, 124, 125, 125, 126, 126, 127, 127
 };
 
-static opl_driver_ver_t opl_drv_ver = opl_v_new;
+static opl_driver_ver_t opl_drv_ver = opl_doom_1_9;
 static boolean music_initialized = false;
 
 //static boolean musicpaused = false;
+static int start_music_volume;
 static int current_music_volume;
 
 // GENMIDI lump instrument data:
@@ -323,12 +318,17 @@ static char (*percussion_names)[32];
 // 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;
@@ -352,22 +352,21 @@ static unsigned int last_perc_count;
 char *snd_dmxoption = "";
 int opl_io_port = 0x388;
 
+// If true, OPL sound channels are reversed to their correct arrangement
+// (as intended by the MIDI standard) rather than the backwards one
+// used by DMX due to a bug.
+
+static boolean opl_stereo_correct = false;
+
 // Load instrument table from GENMIDI lump:
 
 static boolean LoadInstrumentTable(void)
 {
     byte *lump;
 
-    lump = W_CacheLumpName("GENMIDI", PU_STATIC);
-
-    // Check header
-
-    if (strncmp((char *) lump, GENMIDI_HEADER, strlen(GENMIDI_HEADER)) != 0)
-    {
-        W_ReleaseLumpName("GENMIDI");
+    lump = W_CacheLumpName(DEH_String("genmidi"), PU_STATIC);
 
-        return false;
-    }
+    // DMX does not check header
 
     main_instrs = (genmidi_instr_t *) (lump + strlen(GENMIDI_HEADER));
     percussion_instrs = main_instrs + GENMIDI_NUM_INSTRS;
@@ -383,116 +382,102 @@ static boolean LoadInstrumentTable(void)
 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;
     }
 
     // Remove from free list
 
-    result = voice_free_list;
-    voice_free_list = voice_free_list->next;
+    result = voice_free_list[0];
 
-    // Add to allocated list
-
-    rover = &voice_alloced_list;
+    voice_free_num--;
 
-    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.
-
-static void RemoveVoiceFromAllocedList(opl_voice_t *voice)
-{
-    opl_voice_t **rover;
+// Release a voice back to the freelist.
 
-    rover = &voice_alloced_list;
+static void VoiceKeyOff(opl_voice_t *voice);
 
-    // Search the list until we find the voice, then remove it.
+static void ReleaseVoice(int index)
+{
+    opl_voice_t *voice;
+    boolean double_voice;
+    int i;
 
-    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.
 
-static void VoiceKeyOff(opl_voice_t *voice);
+    voice = voice_alloced_list[index];
 
-static void ReleaseVoice(opl_voice_t *voice)
-{
-    opl_voice_t **rover;
-    opl_voice_t *next;
-    boolean double_voice;
+    VoiceKeyOff(voice);
 
     voice->channel = NULL;
     voice->note = 0;
 
     double_voice = voice->current_instr_voice != 0;
-    next = voice->next;
-
+    
     // Remove from alloced list.
 
-    RemoveVoiceFromAllocedList(voice);
-
-    // Search to the end of the freelist (This is how Doom behaves!)
-
-    rover = &voice_free_list;
+    voice_alloced_num--;
 
-    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!)
+
+    voice_free_list[voice_free_num++] = voice;
 
-    if (next != NULL && double_voice && opl_drv_ver == opl_v_old)
+    if (double_voice && opl_drv_ver < opl_doom_1_9)
     {
-        VoiceKeyOff(next);
-        ReleaseVoice(next);
+        ReleaseVoice(index);
     }
 }
 
 // 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;
 
     // 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);
@@ -532,8 +517,10 @@ static void SetVoiceInstrument(opl_voice_t *voice,
     // 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
@@ -542,10 +529,6 @@ static void SetVoiceInstrument(opl_voice_t *voice,
     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)
@@ -566,8 +549,7 @@ static void SetVoiceVolume(opl_voice_t *voice, unsigned int volume)
 
     // Multiply note volume and channel volume to get the actual volume.
 
-    midi_volume = 2 * (volume_mapping_table[(voice->channel->volume
-                  * current_music_volume) / 127] + 1);
+    midi_volume = 2 * (volume_mapping_table[voice->channel->volume] + 1);
 
     full_volume = (volume_mapping_table[voice->note_volume] * midi_volume)
                 >> 9;
@@ -577,12 +559,12 @@ static void SetVoiceVolume(opl_voice_t *voice, unsigned int volume)
 
     // 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.
@@ -590,14 +572,21 @@ static void SetVoiceVolume(opl_voice_t *voice, unsigned int volume)
         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));
+            }
         }
     }
 }
@@ -620,8 +609,9 @@ static void InitVoices(void)
     int i;
 
     // Start with an empty free list.
-
-    voice_free_list = NULL;
+    
+    voice_free_num = num_opl_voices;
+    voice_alloced_num = 0;
 
     // Initialize each voice.
 
@@ -635,27 +625,39 @@ static void InitVoices(void)
 
         // Add this voice to the freelist.
 
-        ReleaseVoice(&voices[i]);
+        voice_free_list[i] = &voices[i];
     }
 }
 
+static void SetChannelVolume(opl_channel_data_t *channel, unsigned int volume,
+                             boolean clip_start);
+
 // Set music volume (0 - 127)
 
 static void I_OPL_SetMusicVolume(int volume)
 {
     unsigned int i;
 
+    if (current_music_volume == volume)
+    {
+        return;
+    }
+
     // Internal state variable.
 
     current_music_volume = volume;
 
     // Update the volume of all voices.
 
-    for (i = 0; i < num_opl_voices; ++i)
+    for (i = 0; i < MIDI_CHANNELS_PER_TRACK; ++i)
     {
-        if (voices[i].channel != NULL)
+        if (i == 15)
         {
-            SetVoiceVolume(&voices[i], voices[i].note_volume);
+            SetChannelVolume(&channels[i], volume, false);
+        }
+        else
+        {
+            SetChannelVolume(&channels[i], channels[i].volume_base, false);
         }
     }
 }
@@ -683,7 +685,7 @@ static opl_channel_data_t *TrackChannelForEvent(opl_track_data_t *track,
         channel_num = 9;
     }
 
-    return &track->channels[channel_num];
+    return &channels[channel_num];
 }
 
 // Get the frequency that we should be using for a voice.
@@ -691,8 +693,7 @@ static opl_channel_data_t *TrackChannelForEvent(opl_track_data_t *track,
 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;
 
 /*
@@ -708,31 +709,16 @@ static void KeyOffEvent(opl_track_data_t *track, midi_event_t *event)
     // 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;
-            }
-        }
-        else
-        {
-            prev = rover;
-            rover = rover->next;
+            ReleaseVoice(i);
+
+            i--;
         }
     }
 }
@@ -744,8 +730,8 @@ static void KeyOffEvent(opl_track_data_t *track, midi_event_t *event)
 
 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.
@@ -755,54 +741,63 @@ static void ReplaceExistingVoice(void)
     // 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);
 }
 
-// Alternate version of ReplaceExistingVoice() used when emulating old
-// versions of the DMX library used in Heretic and Hexen.
+// Alternate versions of ReplaceExistingVoice() used when emulating old
+// versions of the DMX library used in Doom 1.666, Heretic and Hexen.
 
-static void ReplaceExistingVoiceOld(opl_channel_data_t *channel)
+static void ReplaceExistingVoiceDoom1(void)
 {
-    opl_voice_t *rover;
-    opl_voice_t *result;
-    opl_voice_t *roverend;
     int i;
-    int priority;
-
-    result = voice_alloced_list;
+    int result;
 
-    roverend = voice_alloced_list;
+    result = 0;
 
-    for (i = 0; i < voice_alloced_num - 3; i++)
+    for (i = 0; i < voice_alloced_num; i++)
     {
-        roverend = roverend->next;
+        if (voice_alloced_list[i]->channel
+          > voice_alloced_list[result]->channel)
+        {
+            result = i;
+        }
     }
 
+    ReleaseVoice(result);
+}
+
+static void ReplaceExistingVoiceDoom2(opl_channel_data_t *channel)
+{
+    int i;
+    int result;
+    int priority;
+
+    result = 0;
+
     priority = 0x8000;
 
-    for (rover = voice_alloced_list; rover != roverend; rover = rover->next)
+    for (i = 0; i < voice_alloced_num - 3; i++)
     {
-        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);
 }
 
@@ -918,6 +913,11 @@ static void VoiceKeyOn(opl_channel_data_t *channel,
 {
     opl_voice_t *voice;
 
+    if (!opl_opl3mode && opl_drv_ver == opl_doom1_1_666)
+    {
+        instrument_voice = 0;
+    }
+
     // Find a voice to use for this new note.
 
     voice = GetFreeVoice();
@@ -962,7 +962,7 @@ static void KeyOnEvent(opl_track_data_t *track, midi_event_t *event)
 {
     genmidi_instr_t *instrument;
     opl_channel_data_t *channel;
-    unsigned int note, key, volume;
+    unsigned int note, key, volume, voicenum;
     boolean double_voice;
 
 /*
@@ -1009,43 +1009,66 @@ static void KeyOnEvent(opl_track_data_t *track, midi_event_t *event)
 
     double_voice = (SHORT(instrument->flags) & GENMIDI_FLAG_2VOICE) != 0;
 
-    if (opl_drv_ver == opl_v_old)
+    switch (opl_drv_ver)
     {
-        if (voice_alloced_num == num_opl_voices)
-        {
-            ReplaceExistingVoiceOld(channel);
-        }
-        if (voice_alloced_num == num_opl_voices - 1 && double_voice)
-        {
-            ReplaceExistingVoiceOld(channel);
-        }
+        case opl_doom1_1_666:
+            voicenum = double_voice + 1;
+            if (!opl_opl3mode)
+            {
+                voicenum = 1;
+            }
+            while (voice_alloced_num > num_opl_voices - voicenum)
+            {
+                ReplaceExistingVoiceDoom1();
+            }
 
-        // Find and program a voice for this instrument.  If this
-        // is a double voice instrument, we must do this twice.
+            // Find and program a voice for this instrument.  If this
+            // is a double voice instrument, we must do this twice.
 
-        if (double_voice)
-        {
-            VoiceKeyOn(channel, instrument, 1, note, key, volume);
-        }
+            if (double_voice)
+            {
+                VoiceKeyOn(channel, instrument, 1, note, key, volume);
+            }
 
-        VoiceKeyOn(channel, instrument, 0, note, key, volume);
-    }
-    else
-    {
-        if (voice_free_list == NULL)
-        {
-            ReplaceExistingVoice();
-        }
+            VoiceKeyOn(channel, instrument, 0, note, key, volume);
+            break;
+        case opl_doom2_1_666:
+            if (voice_alloced_num == num_opl_voices)
+            {
+                ReplaceExistingVoiceDoom2(channel);
+            }
+            if (voice_alloced_num == num_opl_voices - 1 && double_voice)
+            {
+                ReplaceExistingVoiceDoom2(channel);
+            }
 
-        // Find and program a voice for this instrument.  If this
-        // is a double voice instrument, we must do this twice.
+            // Find and program a voice for this instrument.  If this
+            // is a double voice instrument, we must do this twice.
 
-        VoiceKeyOn(channel, instrument, 0, note, key, volume);
+            if (double_voice)
+            {
+                VoiceKeyOn(channel, instrument, 1, note, key, volume);
+            }
 
-        if (double_voice)
-        {
-            VoiceKeyOn(channel, instrument, 1, note, key, volume);
-        }
+            VoiceKeyOn(channel, instrument, 0, note, key, volume);
+            break;
+        default:
+        case opl_doom_1_9:
+            if (voice_free_num == 0)
+            {
+                ReplaceExistingVoice();
+            }
+
+            // Find and program a voice for this instrument.  If this
+            // is a double voice instrument, we must do this twice.
+
+            VoiceKeyOn(channel, instrument, 0, note, key, volume);
+
+            if (double_voice)
+            {
+                VoiceKeyOn(channel, instrument, 1, note, key, volume);
+            }
+            break;
     }
 }
 
@@ -1064,10 +1087,23 @@ static void ProgramChangeEvent(opl_track_data_t *track, midi_event_t *event)
     // channel, and change the instrument.
 }
 
-static void SetChannelVolume(opl_channel_data_t *channel, unsigned int volume)
+static void SetChannelVolume(opl_channel_data_t *channel, unsigned int volume,
+                             boolean clip_start)
 {
     unsigned int i;
 
+    channel->volume_base = volume;
+
+    if (volume > current_music_volume)
+    {
+        volume = current_music_volume;
+    }
+
+    if (clip_start && volume > start_music_volume)
+    {
+        volume = start_music_volume;
+    }
+
     channel->volume = volume;
 
     // Update all voices that this channel is using.
@@ -1086,6 +1122,16 @@ static void SetChannelPan(opl_channel_data_t *channel, unsigned int pan)
     unsigned int reg_pan;
     unsigned int i;
 
+    // The DMX library has the stereo channels backwards, maybe because
+    // Paul Radek had a Soundblaster card with the channels reversed, or
+    // perhaps it was just a bug in the OPL3 support that was never
+    // finished. By default we preserve this bug, but we also provide a
+    // secret DMXOPTION to fix it.
+    if (opl_stereo_correct)
+    {
+        pan = 144 - pan;
+    }
+
     if (opl_opl3mode)
     {
         if (pan >= 96)
@@ -1117,34 +1163,17 @@ static void SetChannelPan(opl_channel_data_t *channel, unsigned int pan)
 // 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;
-
-    rover = voice_alloced_list;
-    prev = NULL;
+    int i;
 
-    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;
-            }
-        }
-        else
-        {
-            prev = rover;
-            rover = rover->next;
+            ReleaseVoice(i);
+            
+            i--;
         }
     }
 }
@@ -1169,7 +1198,7 @@ static void ControllerEvent(opl_track_data_t *track, midi_event_t *event)
     switch (controller)
     {
         case MIDI_CONTROLLER_MAIN_VOLUME:
-            SetChannelVolume(channel, param);
+            SetChannelVolume(channel, param, true);
             break;
 
         case MIDI_CONTROLLER_PAN:
@@ -1193,7 +1222,11 @@ static void ControllerEvent(opl_track_data_t *track, midi_event_t *event)
 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.
@@ -1203,13 +1236,29 @@ static void PitchBendEvent(opl_track_data_t *track, midi_event_t *event)
 
     // 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(voice_alloced_list[i]);
+            voice_updated_list[voice_updated_num++] = voice_alloced_list[i];
+        }
+        else
         {
-            UpdateVoiceFrequency(&voices[i]);
+            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)
@@ -1307,6 +1356,7 @@ static void ProcessEvent(opl_track_data_t *track, midi_event_t *event)
 }
 
 static void ScheduleTrack(opl_track_data_t *track);
+static void InitChannel(opl_channel_data_t *channel);
 
 // Restart a song from the beginning.
 
@@ -1316,11 +1366,18 @@ static void RestartSong(void *unused)
 
     running_tracks = num_tracks;
 
+    start_music_volume = current_music_volume;
+
     for (i = 0; i < num_tracks; ++i)
     {
         MIDI_RestartIterator(tracks[i].iter);
         ScheduleTrack(&tracks[i]);
     }
+
+    for (i = 0; i < MIDI_CHANNELS_PER_TRACK; ++i)
+    {
+        InitChannel(&channels[i]);
+    }
 }
 
 // Callback function invoked when another event needs to be read from
@@ -1385,12 +1442,17 @@ static void ScheduleTrack(opl_track_data_t *track)
 
 // 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?
 
     channel->instrument = &main_instrs[0];
-    channel->volume = 127;
+    channel->volume = current_music_volume;
+    channel->volume_base = 100;
+    if (channel->volume > channel->volume_base)
+    {
+        channel->volume = channel->volume_base;
+    }
     channel->pan = 0x30;
     channel->bend = 0;
 }
@@ -1400,16 +1462,10 @@ static void InitChannel(opl_track_data_t *track, opl_channel_data_t *channel)
 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);
@@ -1444,10 +1500,17 @@ static void I_OPL_PlaySong(void *handle, boolean looping)
 
     us_per_beat = 500 * 1000;
 
+    start_music_volume = current_music_volume;
+
     for (i = 0; i < num_tracks; ++i)
     {
         StartTrack(file, i);
     }
+
+    for (i = 0; i < MIDI_CHANNELS_PER_TRACK; ++i)
+    {
+        InitChannel(&channels[i]);
+    }
 }
 
 static void I_OPL_PauseSong(void)
@@ -1503,13 +1566,9 @@ static void I_OPL_StopSong(void)
 
     // 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.
@@ -1640,7 +1699,7 @@ static void I_OPL_ShutdownMusic(void)
 
         // Release GENMIDI lump
 
-        W_ReleaseLumpName("GENMIDI");
+        W_ReleaseLumpName(DEH_String("genmidi"));
 
         music_initialized = false;
     }
@@ -1681,6 +1740,10 @@ static boolean I_OPL_InitMusic(void)
         num_opl_voices = OPL_NUM_VOICES;
     }
 
+    // Secret, undocumented DMXOPTION that reverses the stereo channels
+    // into their correct orientation.
+    opl_stereo_correct = strstr(dmxoption, "-reverse") != NULL;
+
     // Initialize all registers.
 
     OPL_InitRegisters(opl_opl3mode);
@@ -1743,7 +1806,7 @@ static int NumActiveChannels(void)
 
     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;
         }
@@ -1754,11 +1817,11 @@ static int NumActiveChannels(void)
 
 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;
         }
@@ -1785,17 +1848,17 @@ void I_OPL_DevMessages(char *result, size_t result_len)
 
     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);
diff --git a/src/i_pcsound.c b/src/i_pcsound.c
index a7a8381..ff5e0b0 100644
--- a/src/i_pcsound.c
+++ b/src/i_pcsound.c
@@ -174,7 +174,8 @@ static boolean IsDisabledSound(sfxinfo_t *sfxinfo)
 static int I_PCS_StartSound(sfxinfo_t *sfxinfo,
                             int channel,
                             int vol,
-                            int sep)
+                            int sep,
+                            int pitch)
 {
     int result;
 
diff --git a/src/i_scale.c b/src/i_scale.c
index 545a1f1..bc673d7 100644
--- a/src/i_scale.c
+++ b/src/i_scale.c
@@ -983,7 +983,7 @@ static boolean I_Stretch5x(int x1, int y1, int x2, int y2)
     }
 
     // test hack for Porsche Monty... scan line simulation:
-    // See here: http://www.doomworld.com/vb/post/962612
+    // See here: https://www.doomworld.com/vb/post/962612
 
     if (M_CheckParm("-scanline") > 0)
     {
@@ -1387,5 +1387,5 @@ screen_mode_t mode_squash_4x = {
 // ratio and have slightly larger borders than to have slightly smaller
 // windowboxing borders. It also means that the aspect ratio is correct
 // when running at 1280x1024. See bug #460 for more details, or this
-// post: http://www.doomworld.com/vb/post/1316735
+// post: https://www.doomworld.com/vb/post/1316735
 
diff --git a/src/i_sdlmusic.c b/src/i_sdlmusic.c
index 80bc49a..ca04218 100644
--- a/src/i_sdlmusic.c
+++ b/src/i_sdlmusic.c
@@ -752,7 +752,7 @@ static void DumpSubstituteConfig(char *filename)
 
     for (lumpnum = 0; lumpnum < numlumps; ++lumpnum)
     {
-        strncpy(name, lumpinfo[lumpnum].name, 8);
+        strncpy(name, lumpinfo[lumpnum]->name, 8);
         name[8] = '\0';
 
         if (!IsMusicLump(lumpnum))
@@ -922,7 +922,7 @@ static boolean I_SDL_InitMusic(void)
 #endif
 
     //!
-    // @arg <output filename>
+    // @arg <filename>
     //
     // Read all MIDI files from loaded WAD files, dump an example substitution
     // music config file to the specified filename and quit.
diff --git a/src/i_sdlsound.c b/src/i_sdlsound.c
index 79a15db..016ffe2 100644
--- a/src/i_sdlsound.c
+++ b/src/i_sdlsound.c
@@ -52,6 +52,7 @@ struct allocated_sound_s
     sfxinfo_t *sfxinfo;
     Mix_Chunk chunk;
     int use_count;
+    int pitch;
     allocated_sound_t *prev, *next;
 };
 
@@ -59,7 +60,7 @@ static boolean setpanning_workaround = false;
 
 static boolean sound_initialized = false;
 
-static sfxinfo_t *channels_playing[NUM_CHANNELS];
+static allocated_sound_t *channels_playing[NUM_CHANNELS];
 
 static int mixer_freq;
 static Uint16 mixer_format;
@@ -136,10 +137,6 @@ static void FreeAllocatedSound(allocated_sound_t *snd)
 
     AllocatedSoundUnlink(snd);
 
-    // Unlink from higher-level code.
-
-    snd->sfxinfo->driver_data = NULL;
-
     // Keep track of the amount of allocated sound data:
 
     allocated_sounds_size -= snd->chunk.alen;
@@ -200,7 +197,7 @@ static void ReserveCacheSpace(size_t len)
 
 // Allocate a block for a new sound effect.
 
-static Mix_Chunk *AllocateSound(sfxinfo_t *sfxinfo, size_t len)
+static allocated_sound_t *AllocateSound(sfxinfo_t *sfxinfo, size_t len)
 {
     allocated_sound_t *snd;
 
@@ -231,21 +228,18 @@ static Mix_Chunk *AllocateSound(sfxinfo_t *sfxinfo, size_t len)
     snd->chunk.alen = len;
     snd->chunk.allocated = 1;
     snd->chunk.volume = MIX_MAX_VOLUME;
+    snd->pitch = NORM_PITCH;
 
     snd->sfxinfo = sfxinfo;
     snd->use_count = 0;
 
-    // driver_data pointer points to the allocated_sound structure.
-
-    sfxinfo->driver_data = snd;
-
     // Keep track of how much memory all these cached sounds are using...
 
     allocated_sounds_size += len;
 
     AllocatedSoundLink(snd);
 
-    return &snd->chunk;
+    return snd;
 }
 
 // Lock a sound, to indicate that it may not be freed.
@@ -279,22 +273,93 @@ static void UnlockAllocatedSound(allocated_sound_t *snd)
     //printf("-- %s: Use count=%i\n", snd->sfxinfo->name, snd->use_count);
 }
 
-// When a sound stops, check if it is still playing.  If it is not, 
+// Search through the list of allocated sounds and return the one that matches
+// the supplied sfxinfo entry and pitch level.
+
+static allocated_sound_t * GetAllocatedSoundBySfxInfoAndPitch(sfxinfo_t *sfxinfo, int pitch)
+{
+    allocated_sound_t * p = allocated_sounds_head;
+
+    while (p != NULL)
+    {
+        if (p->sfxinfo == sfxinfo && p->pitch == pitch)
+        {
+            return p;
+        }
+        p = p->next;
+    }
+
+    return NULL;
+}
+
+// Allocate a new sound chunk and pitch-shift an existing sound up-or-down
+// into it.
+
+static allocated_sound_t * PitchShift(allocated_sound_t *insnd, int pitch)
+{
+    allocated_sound_t * outsnd;
+    Sint16 *inp, *outp;
+    Sint16 *srcbuf, *dstbuf;
+    Uint32 srclen, dstlen;
+
+    srcbuf = (Sint16 *)insnd->chunk.abuf;
+    srclen = insnd->chunk.alen;
+
+    // determine ratio pitch:NORM_PITCH and apply to srclen, then invert.
+    // This is an approximation of vanilla behaviour based on measurements
+    dstlen = (int)((1 + (1 - (float)pitch / NORM_PITCH)) * srclen);
+
+    // ensure that the new buffer is an even length
+    if ((dstlen % 2) == 0)
+    {
+        dstlen++;
+    }
+
+    outsnd = AllocateSound(insnd->sfxinfo, dstlen);
+
+    if (!outsnd)
+    {
+        return NULL;
+    }
+
+    outsnd->pitch = pitch;
+    dstbuf = (Sint16 *)outsnd->chunk.abuf;
+
+    // loop over output buffer. find corresponding input cell, copy over
+    for (outp = dstbuf; outp < dstbuf + dstlen/2; ++outp)
+    {
+        inp = srcbuf + (int)((float)(outp - dstbuf) / dstlen * srclen);
+        *outp = *inp;
+    }
+
+    return outsnd;
+}
+
+// When a sound stops, check if it is still playing.  If it is not,
 // we can mark the sound data as CACHE to be freed back for other
 // means.
 
 static void ReleaseSoundOnChannel(int channel)
 {
-    sfxinfo_t *sfxinfo = channels_playing[channel];
+    allocated_sound_t *snd = channels_playing[channel];
 
-    if (sfxinfo == NULL)
+    Mix_HaltChannel(channel);
+
+    if (snd == NULL)
     {
         return;
     }
 
     channels_playing[channel] = NULL;
 
-    UnlockAllocatedSound(sfxinfo->driver_data);
+    UnlockAllocatedSound(snd);
+
+    // if the sound is a pitch-shift and it's not in use, immediately
+    // free it
+    if (snd->pitch != NORM_PITCH && snd->use_count <= 0)
+    {
+        FreeAllocatedSound(snd);
+    }
 }
 
 #ifdef HAVE_LIBSAMPLERATE
@@ -339,14 +404,17 @@ static boolean ExpandSoundData_SRC(sfxinfo_t *sfxinfo,
                                    int length)
 {
     SRC_DATA src_data;
+    float *data_in;
     uint32_t i, abuf_index=0, clipped=0;
 //    uint32_t alen;
     int retn;
     int16_t *expanded;
+    allocated_sound_t *snd;
     Mix_Chunk *chunk;
 
     src_data.input_frames = length;
-    src_data.data_in = malloc(length * sizeof(float));
+    data_in = malloc(length * sizeof(float));
+    src_data.data_in = data_in;
     src_data.src_ratio = (double)mixer_freq / samplerate;
 
     // We include some extra space here in case of rounding-up.
@@ -362,7 +430,7 @@ static boolean ExpandSoundData_SRC(sfxinfo_t *sfxinfo,
         // Unclear whether 128 should be interpreted as "zero" or whether a
         // symmetrical range should be assumed.  The following assumes a
         // symmetrical range.
-        src_data.data_in[i] = data[i] / 127.5 - 1;
+        data_in[i] = data[i] / 127.5 - 1;
     }
 
     // Do the sound conversion
@@ -374,13 +442,14 @@ static boolean ExpandSoundData_SRC(sfxinfo_t *sfxinfo,
 
 //    alen = src_data.output_frames_gen * 4;
 
-    chunk = AllocateSound(sfxinfo, src_data.output_frames_gen * 4);
+    snd = AllocateSound(sfxinfo, src_data.output_frames_gen * 4);
 
-    if (chunk == NULL)
+    if (snd == NULL)
     {
         return false;
     }
 
+    chunk = &snd->chunk;
     expanded = (int16_t *) chunk->abuf;
 
     // Convert the result back into 16-bit integers.
@@ -430,7 +499,7 @@ static boolean ExpandSoundData_SRC(sfxinfo_t *sfxinfo,
         expanded[abuf_index++] = cvtval_i;
     }
 
-    free(src_data.data_in);
+    free(data_in);
     free(src_data.data_out);
 
     if (clipped > 0)
@@ -533,10 +602,11 @@ static boolean ExpandSoundData_SDL(sfxinfo_t *sfxinfo,
                                    int length)
 {
     SDL_AudioCVT convertor;
+    allocated_sound_t *snd;
     Mix_Chunk *chunk;
     uint32_t expanded_length;
- 
-    // Calculate the length of the expanded version of the sample.    
+
+    // Calculate the length of the expanded version of the sample.
 
     expanded_length = (uint32_t) ((((uint64_t) length) * mixer_freq) / samplerate);
 
@@ -546,13 +616,15 @@ static boolean ExpandSoundData_SDL(sfxinfo_t *sfxinfo,
 
     // Allocate a chunk in which to expand the sound
 
-    chunk = AllocateSound(sfxinfo, expanded_length);
+    snd = AllocateSound(sfxinfo, expanded_length);
 
-    if (chunk == NULL)
+    if (snd == NULL)
     {
         return false;
     }
 
+    chunk = &snd->chunk;
+
     // If we can, use the standard / optimized SDL conversion routines.
 
     if (samplerate <= mixer_freq
@@ -696,11 +768,12 @@ static boolean CacheSFX(sfxinfo_t *sfxinfo)
 #ifdef DEBUG_DUMP_WAVS
     {
         char filename[16];
+        allocated_sound_t * snd;
 
         M_snprintf(filename, sizeof(filename), "%s.wav",
-                   DEH_String(S_sfx[sound].name));
-        WriteWAV(filename, sound_chunks[sound].abuf,
-                 sound_chunks[sound].alen, mixer_freq);
+                   DEH_String(sfxinfo->name));
+        snd = GetAllocatedSoundBySfxInfoAndPitch(sfxinfo, NORM_PITCH);
+        WriteWAV(filename, snd->chunk.abuf, snd->chunk.alen,mixer_freq);
     }
 #endif
 
@@ -786,8 +859,7 @@ static void I_SDL_PrecacheSounds(sfxinfo_t *sounds, int num_sounds)
 static boolean LockSound(sfxinfo_t *sfxinfo)
 {
     // If the sound isn't loaded, load it now
-
-    if (sfxinfo->driver_data == NULL)
+    if (GetAllocatedSoundBySfxInfoAndPitch(sfxinfo, NORM_PITCH) == NULL)
     {
         if (!CacheSFX(sfxinfo))
         {
@@ -795,7 +867,7 @@ static boolean LockSound(sfxinfo_t *sfxinfo)
         }
     }
 
-    LockAllocatedSound(sfxinfo->driver_data);
+    LockAllocatedSound(GetAllocatedSoundBySfxInfoAndPitch(sfxinfo, NORM_PITCH));
 
     return true;
 }
@@ -857,7 +929,7 @@ static void I_SDL_UpdateSoundParams(int handle, int vol, int sep)
 //  is set, but currently not used by mixing.
 //
 
-static int I_SDL_StartSound(sfxinfo_t *sfxinfo, int channel, int vol, int sep)
+static int I_SDL_StartSound(sfxinfo_t *sfxinfo, int channel, int vol, int sep, int pitch)
 {
     allocated_sound_t *snd;
 
@@ -875,19 +947,47 @@ static int I_SDL_StartSound(sfxinfo_t *sfxinfo, int channel, int vol, int sep)
 
     if (!LockSound(sfxinfo))
     {
-	return -1;
+        return -1;
     }
 
-    snd = sfxinfo->driver_data;
+    snd = GetAllocatedSoundBySfxInfoAndPitch(sfxinfo, pitch);
+
+    if (snd == NULL)
+    {
+        allocated_sound_t *newsnd;
+        // fetch the base sound effect, un-pitch-shifted
+        snd = GetAllocatedSoundBySfxInfoAndPitch(sfxinfo, NORM_PITCH);
+
+        if (snd == NULL)
+        {
+            return -1;
+        }
+
+        if (snd_pitchshift)
+        {
+            newsnd = PitchShift(snd, pitch);
+
+            if (newsnd)
+            {
+                LockAllocatedSound(newsnd);
+                UnlockAllocatedSound(snd);
+                snd = newsnd;
+            }
+        }
+    }
+    else
+    {
+        LockAllocatedSound(snd);
+    }
 
     // play sound
 
-    Mix_PlayChannelTimed(channel, &snd->chunk, 0, -1);
+    Mix_PlayChannel(channel, &snd->chunk, 0);
 
-    channels_playing[channel] = sfxinfo;
+    channels_playing[channel] = snd;
 
     // set separation, etc.
- 
+
     I_SDL_UpdateSoundParams(channel, vol, sep);
 
     return channel;
@@ -900,8 +1000,6 @@ static void I_SDL_StopSound(int handle)
         return;
     }
 
-    Mix_HaltChannel(handle);
-
     // Sound data is no longer needed; release the
     // sound data being used for this channel
 
@@ -919,7 +1017,7 @@ static boolean I_SDL_SoundIsPlaying(int handle)
     return Mix_Playing(handle);
 }
 
-// 
+//
 // Periodically called to update the sound system
 //
 
@@ -935,14 +1033,14 @@ static void I_SDL_UpdateSound(void)
         {
             // Sound has finished playing on this channel,
             // but sound data has not been released to cache
-            
+
             ReleaseSoundOnChannel(i);
         }
     }
 }
 
 static void I_SDL_ShutdownSound(void)
-{    
+{
     if (!sound_initialized)
     {
         return;
diff --git a/src/i_sound.c b/src/i_sound.c
index a646aed..759bd6b 100644
--- a/src/i_sound.c
+++ b/src/i_sound.c
@@ -48,6 +48,11 @@ int snd_maxslicetime_ms = 28;
 
 char *snd_musiccmd = "";
 
+// Whether to vary the pitch of sound effects
+// Each game will set the default differently
+
+int snd_pitchshift = -1;
+
 // Low-level sound and music modules we are using
 
 static sound_module_t *sound_module;
@@ -308,12 +313,12 @@ void I_UpdateSoundParams(int channel, int vol, int sep)
     }
 }
 
-int I_StartSound(sfxinfo_t *sfxinfo, int channel, int vol, int sep)
+int I_StartSound(sfxinfo_t *sfxinfo, int channel, int vol, int sep, int pitch)
 {
     if (sound_module != NULL)
     {
         CheckVolumeSeparation(&vol, &sep);
-        return sound_module->StartSound(sfxinfo, channel, vol, sep);
+        return sound_module->StartSound(sfxinfo, channel, vol, sep, pitch);
     }
     else
     {
@@ -345,7 +350,7 @@ void I_PrecacheSounds(sfxinfo_t *sounds, int num_sounds)
 {
     if (sound_module != NULL && sound_module->CacheSounds != NULL)
     {
-	sound_module->CacheSounds(sounds, num_sounds);
+        sound_module->CacheSounds(sounds, num_sounds);
     }
 }
 
@@ -448,6 +453,7 @@ void I_BindSoundVariables(void)
     M_BindIntVariable("snd_samplerate",          &snd_samplerate);
     M_BindIntVariable("snd_cachesize",           &snd_cachesize);
     M_BindIntVariable("opl_io_port",             &opl_io_port);
+    M_BindIntVariable("snd_pitchshift",          &snd_pitchshift);
 
     M_BindStringVariable("timidity_cfg_path",    &timidity_cfg_path);
     M_BindStringVariable("gus_patch_path",       &gus_patch_path);
diff --git a/src/i_sound.h b/src/i_sound.h
index 4490b96..f984c8f 100644
--- a/src/i_sound.h
+++ b/src/i_sound.h
@@ -22,6 +22,8 @@
 
 #include "doomtype.h"
 
+// so that the individual game logic and sound driver code agree
+#define NORM_PITCH 127
 
 //
 // SoundFX struct.
@@ -32,7 +34,7 @@ struct sfxinfo_struct
 {
     // tag name, used for hexen.
     char *tagname;
-    
+
     // lump name.  If we are running with use_sfx_prefix=true, a
     // 'DS' (or 'DP' for PC speaker sounds) is prepended to this.
 
@@ -44,7 +46,7 @@ struct sfxinfo_struct
     // referenced sound if a link
     sfxinfo_t *link;
 
-    // pitch if a link
+    // pitch if a link (Doom), whether to pitch-shift (Hexen)
     int pitch;
 
     // volume if a link
@@ -56,7 +58,7 @@ struct sfxinfo_struct
     int usefulness;
 
     // lump number of sfx
-    int lumpnum;		
+    int lumpnum;
 
     // Maximum number of channels that the sound can be played on 
     // (Heretic)
@@ -76,13 +78,13 @@ typedef struct
 
     // lump number of music
     int lumpnum;
-    
+
     // music data
     void *data;
 
     // music handle once registered
     void *handle;
-    
+
 } musicinfo_t;
 
 typedef enum 
@@ -133,7 +135,7 @@ typedef struct
     // Start a sound on a given channel.  Returns the channel id
     // or -1 on failure.
 
-    int (*StartSound)(sfxinfo_t *sfxinfo, int channel, int vol, int sep);
+    int (*StartSound)(sfxinfo_t *sfxinfo, int channel, int vol, int sep, int pitch);
 
     // Stop the sound playing on the given channel.
 
@@ -154,7 +156,7 @@ void I_ShutdownSound(void);
 int I_GetSfxLumpNum(sfxinfo_t *sfxinfo);
 void I_UpdateSound(void);
 void I_UpdateSoundParams(int channel, int vol, int sep);
-int I_StartSound(sfxinfo_t *sfxinfo, int channel, int vol, int sep);
+int I_StartSound(sfxinfo_t *sfxinfo, int channel, int vol, int sep, int pitch);
 void I_StopSound(int channel);
 boolean I_SoundIsPlaying(int channel);
 void I_PrecacheSounds(sfxinfo_t *sounds, int num_sounds);
@@ -231,13 +233,15 @@ extern int snd_samplerate;
 extern int snd_cachesize;
 extern int snd_maxslicetime_ms;
 extern char *snd_musiccmd;
+extern int snd_pitchshift;
 
 void I_BindSoundVariables(void);
 
 // DMX version to emulate for OPL emulation:
 typedef enum {
-    opl_v_old,   // Hexen, Heretic
-    opl_v_new    // Doom, Strife
+    opl_doom1_1_666,    // Doom 1 v1.666
+    opl_doom2_1_666,    // Doom 2 v1.666, Hexen, Heretic
+    opl_doom_1_9        // Doom v1.9, Strife
 } opl_driver_ver_t;
 
 void I_SetOPLDriverVer(opl_driver_ver_t ver);
diff --git a/src/i_swap.h b/src/i_swap.h
index 835cdc2..3dee8e3 100644
--- a/src/i_swap.h
+++ b/src/i_swap.h
@@ -35,9 +35,7 @@
 
 // Defines for checking the endianness of the system.
 
-#if SDL_BYTEORDER == SYS_LIL_ENDIAN
-#define SYS_LITTLE_ENDIAN
-#elif SDL_BYTEORDER == SYS_BIG_ENDIAN
+#if SDL_BYTEORDER == SDL_BIG_ENDIAN
 #define SYS_BIG_ENDIAN
 #endif
 
diff --git a/src/i_video.c b/src/i_video.c
index ec0fc58..e454aed 100644
--- a/src/i_video.c
+++ b/src/i_video.c
@@ -44,6 +44,7 @@
 #include "m_config.h"
 #include "m_misc.h"
 #include "tables.h"
+#include "v_diskicon.h"
 #include "v_video.h"
 #include "w_wad.h"
 #include "z_zone.h"
@@ -90,10 +91,6 @@ static const char shiftxform[] =
     '{', '|', '}', '~', 127
 };
 
-
-#define LOADING_DISK_W 16
-#define LOADING_DISK_H 16
-
 // Non aspect ratio-corrected modes (direct multiples of 320x200)
 
 static screen_mode_t *screen_modes[] = {
@@ -237,11 +234,6 @@ static boolean noblit;
 
 static grabmouse_callback_t grabmouse_callback = NULL;
 
-// disk image data and background overwritten by the disk to be
-// restored by EndRead
-
-static byte *disk_image = NULL;
-static byte *saved_background;
 static boolean window_focused;
 
 // Empty mouse cursor
@@ -392,59 +384,6 @@ static void SetShowCursor(boolean show)
     }
 }
 
-void I_EnableLoadingDisk(void)
-{
-    patch_t *disk;
-    byte *tmpbuf;
-    char *disk_name;
-    int y;
-    char buf[20];
-
-    SDL_VideoDriverName(buf, 15);
-
-    if (!strcmp(buf, "Quartz"))
-    {
-        // MacOS Quartz gives us pageflipped graphics that screw up the 
-        // display when we use the loading disk.  Disable it.
-        // This is a gross hack.
-
-        return;
-    }
-
-    if (M_CheckParm("-cdrom") > 0)
-        disk_name = DEH_String("STCDROM");
-    else
-        disk_name = DEH_String("STDISK");
-
-    disk = W_CacheLumpName(disk_name, PU_STATIC);
-
-    // Draw the patch into a temporary buffer
-
-    tmpbuf = Z_Malloc(SCREENWIDTH * (disk->height + 1), PU_STATIC, NULL);
-    V_UseBuffer(tmpbuf);
-
-    // Draw the disk to the screen:
-
-    V_DrawPatch(0, 0, disk);
-
-    disk_image = Z_Malloc(LOADING_DISK_W * LOADING_DISK_H, PU_STATIC, NULL);
-    saved_background = Z_Malloc(LOADING_DISK_W * LOADING_DISK_H, PU_STATIC, NULL);
-
-    for (y=0; y<LOADING_DISK_H; ++y) 
-    {
-        memcpy(disk_image + LOADING_DISK_W * y,
-               tmpbuf + SCREENWIDTH * y,
-               LOADING_DISK_W);
-    }
-
-    // All done - free the screen buffer and restore the normal 
-    // video buffer.
-
-    W_ReleaseLumpName(disk_name);
-    V_RestoreBuffer();
-    Z_Free(tmpbuf);
-}
-
 //
 // Translates the SDL key
 //
@@ -970,81 +909,6 @@ static boolean BlitArea(int x1, int y1, int x2, int y2)
     return result;
 }
 
-static void UpdateRect(int x1, int y1, int x2, int y2)
-{
-    int x1_scaled, x2_scaled, y1_scaled, y2_scaled;
-
-    // Do stretching and blitting
-
-    if (BlitArea(x1, y1, x2, y2))
-    {
-        // Update the area
-
-        x1_scaled = (x1 * screen_mode->width) / SCREENWIDTH;
-        y1_scaled = (y1 * screen_mode->height) / SCREENHEIGHT;
-        x2_scaled = (x2 * screen_mode->width) / SCREENWIDTH;
-        y2_scaled = (y2 * screen_mode->height) / SCREENHEIGHT;
-
-        SDL_UpdateRect(screen,
-                       x1_scaled, y1_scaled,
-                       x2_scaled - x1_scaled,
-                       y2_scaled - y1_scaled);
-    }
-}
-
-void I_BeginRead(void)
-{
-    byte *screenloc = I_VideoBuffer
-                    + (SCREENHEIGHT - LOADING_DISK_H) * SCREENWIDTH
-                    + (SCREENWIDTH - LOADING_DISK_W);
-    int y;
-
-    if (!initialized || disk_image == NULL)
-        return;
-
-    // save background and copy the disk image in
-
-    for (y=0; y<LOADING_DISK_H; ++y)
-    {
-        memcpy(saved_background + y * LOADING_DISK_W,
-               screenloc,
-               LOADING_DISK_W);
-        memcpy(screenloc,
-               disk_image + y * LOADING_DISK_W,
-               LOADING_DISK_W);
-
-        screenloc += SCREENWIDTH;
-    }
-
-    UpdateRect(SCREENWIDTH - LOADING_DISK_W, SCREENHEIGHT - LOADING_DISK_H,
-               SCREENWIDTH, SCREENHEIGHT);
-}
-
-void I_EndRead(void)
-{
-    byte *screenloc = I_VideoBuffer
-                    + (SCREENHEIGHT - LOADING_DISK_H) * SCREENWIDTH
-                    + (SCREENWIDTH - LOADING_DISK_W);
-    int y;
-
-    if (!initialized || disk_image == NULL)
-        return;
-
-    // save background and copy the disk image in
-
-    for (y=0; y<LOADING_DISK_H; ++y)
-    {
-        memcpy(screenloc,
-               saved_background + y * LOADING_DISK_W,
-               LOADING_DISK_W);
-
-        screenloc += SCREENWIDTH;
-    }
-
-    UpdateRect(SCREENWIDTH - LOADING_DISK_W, SCREENHEIGHT - LOADING_DISK_H,
-               SCREENWIDTH, SCREENHEIGHT);
-}
-
 //
 // I_FinishUpdate
 //
@@ -1091,6 +955,9 @@ void I_FinishUpdate (void)
 	    I_VideoBuffer[ (SCREENHEIGHT-1)*SCREENWIDTH + i] = 0x0;
     }
 
+    // Draw disk icon before blit, if necessary.
+    V_DrawDiskIcon();
+
     // draw to screen
 
     BlitArea(0, 0, SCREENWIDTH, SCREENHEIGHT);
@@ -1125,6 +992,9 @@ void I_FinishUpdate (void)
     }
 
     SDL_Flip(screen);
+
+    // Restore background and undo the disk indicator, if it was drawn.
+    V_RestoreDiskBackground();
 }
 
 
@@ -1133,7 +1003,7 @@ void I_FinishUpdate (void)
 //
 void I_ReadScreen (byte* scr)
 {
-    memcpy(scr, I_VideoBuffer, SCREENWIDTH*SCREENHEIGHT);
+    memcpy(scr, I_VideoBuffer, SCREENWIDTH*SCREENHEIGHT*sizeof(*scr));
 }
 
 
@@ -1560,12 +1430,13 @@ void I_GraphicsCheckCommandLine(void)
     int i;
 
     //!
+    // @category video
     // @vanilla
     //
     // Disable blitting the screen.
     //
 
-    noblit = M_CheckParm ("-noblit"); 
+    noblit = M_CheckParm ("-noblit");
 
     //!
     // @category video 
diff --git a/src/i_video.h b/src/i_video.h
index ff384d8..49ccb60 100644
--- a/src/i_video.h
+++ b/src/i_video.h
@@ -110,7 +110,6 @@ void I_FinishUpdate (void);
 void I_ReadScreen (byte* scr);
 
 void I_BeginRead (void);
-void I_EndRead (void);
 
 void I_SetWindowTitle(char *title);
 
@@ -135,7 +134,7 @@ void I_StartTic (void);
 
 // Enable the loading disk image displayed when reading from disk.
 
-void I_EnableLoadingDisk(void);
+void I_EnableLoadingDisk(int xoffs, int yoffs);
 
 extern char *video_driver;
 extern boolean screenvisible;
diff --git a/src/m_config.c b/src/m_config.c
index a993eeb..5619926 100644
--- a/src/m_config.c
+++ b/src/m_config.c
@@ -809,6 +809,14 @@ static default_t extra_defaults_list[] =
     CONFIG_VARIABLE_INT(snd_maxslicetime_ms),
 
     //!
+    // If non-zero, sound effects will have their pitch varied up or
+    // down by a random amount during play. If zero, sound effects
+    // play back at their default pitch. The default is zero.
+    //
+
+    CONFIG_VARIABLE_INT(snd_pitchshift),
+
+    //!
     // External command to invoke to perform MIDI playback. If set to
     // the empty string, SDL_mixer's internal MIDI playback is used.
     // This only has any effect when snd_musicdevice is set to General
@@ -840,6 +848,15 @@ static default_t extra_defaults_list[] =
     CONFIG_VARIABLE_INT(show_endoom),
 
     //!
+    // @game doom strife
+    //
+    // If non-zero, a disk activity indicator is displayed when data is read
+    // from disk. If zero, the disk activity indicator is not displayed.
+    //
+
+    CONFIG_VARIABLE_INT(show_diskicon),
+
+    //!
     // If non-zero, save screenshots in PNG format.
     //
 
@@ -1016,6 +1033,13 @@ static default_t extra_defaults_list[] =
     CONFIG_VARIABLE_INT(joystick_physical_button9),
 
     //!
+    // The physical joystick button that corresponds to joystick
+    // virtual button #10.
+    //
+
+    CONFIG_VARIABLE_INT(joystick_physical_button10),
+
+    //!
     // Joystick virtual button to make the player strafe left.
     //
 
@@ -1034,6 +1058,12 @@ static default_t extra_defaults_list[] =
     CONFIG_VARIABLE_INT(joyb_menu_activate),
 
     //!
+    // Joystick virtual button to toggle the automap.
+    //
+
+    CONFIG_VARIABLE_INT(joyb_toggle_automap),
+
+    //!
     // Joystick virtual button that cycles to the previous weapon.
     //
 
diff --git a/src/m_controls.c b/src/m_controls.c
index 35f848c..7225647 100644
--- a/src/m_controls.c
+++ b/src/m_controls.c
@@ -192,6 +192,7 @@ int joybprevweapon = -1;
 int joybnextweapon = -1;
 
 int joybmenu = -1;
+int joybautomap = -1;
 
 // Control whether if a mouse button is double clicked, it acts like 
 // "use" has been pressed
@@ -225,6 +226,7 @@ void M_BindBaseControls(void)
     M_BindIntVariable("joyb_speed",         &joybspeed);
 
     M_BindIntVariable("joyb_menu_activate", &joybmenu);
+    M_BindIntVariable("joyb_toggle_automap", &joybautomap);
 
     // Extra controls that are not in the Vanilla versions:
 
diff --git a/src/m_controls.h b/src/m_controls.h
index aa07a7e..5d12f96 100644
--- a/src/m_controls.h
+++ b/src/m_controls.h
@@ -150,6 +150,7 @@ extern int joybprevweapon;
 extern int joybnextweapon;
 
 extern int joybmenu;
+extern int joybautomap;
 
 extern int dclick_use;
 
diff --git a/src/m_fixed.c b/src/m_fixed.c
index 23f6ff3..d2c1818 100644
--- a/src/m_fixed.c
+++ b/src/m_fixed.c
@@ -54,7 +54,7 @@ fixed_t FixedDiv(fixed_t a, fixed_t b)
     {
 	int64_t result;
 
-	result = ((int64_t) a << 16) / b;
+	result = ((int64_t) a << FRACBITS) / b;
 
 	return (fixed_t) result;
     }
diff --git a/src/m_misc.c b/src/m_misc.c
index 53b86db..7e69da8 100644
--- a/src/m_misc.c
+++ b/src/m_misc.c
@@ -83,6 +83,75 @@ boolean M_FileExists(char *filename)
     }
 }
 
+// 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.
 //
@@ -250,6 +319,24 @@ void M_ForceUppercase(char *text)
     }
 }
 
+//---------------------------------------------------------------------------
+//
+// PROC M_ForceLowercase
+//
+// Change string to lowercase.
+//
+//---------------------------------------------------------------------------
+
+void M_ForceLowercase(char *text)
+{
+    char *p;
+
+    for (p = text; *p != '\0'; ++p)
+    {
+        *p = tolower(*p);
+    }
+}
+
 //
 // M_StrCaseStr
 //
diff --git a/src/m_misc.h b/src/m_misc.h
index 844b485..c6debfd 100644
--- a/src/m_misc.h
+++ b/src/m_misc.h
@@ -30,10 +30,12 @@ int M_ReadFile(char *name, byte **buffer);
 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);
diff --git a/src/mus2mid.c b/src/mus2mid.c
index 3cbcb0d..2b56636 100644
--- a/src/mus2mid.c
+++ b/src/mus2mid.c
@@ -403,7 +403,7 @@ static int GetMIDIChannel(int mus_channel, MEMFILE *midioutput)
 
             // First time using the channel, send an "all notes off"
             // event. This fixes "The D_DDTBLU disease" described here:
-            // http://www.doomworld.com/vb/source-ports/66802-the
+            // https://www.doomworld.com/vb/source-ports/66802-the
             WriteChangeController_Valueless(channel_map[mus_channel], 0x7b,
                                             midioutput);
         }
diff --git a/src/net_server.c b/src/net_server.c
index b3ec169..885fffd 100644
--- a/src/net_server.c
+++ b/src/net_server.c
@@ -1345,6 +1345,7 @@ void NET_SV_SendQueryResponse(net_addr_t *addr)
     querydata.gamemission = sv_gamemission;
 
     //!
+    // @category net
     // @arg <name>
     //
     // When starting a network server, specify a name for the server.
@@ -1785,11 +1786,11 @@ static void UpdateMasterServer(void)
 void NET_SV_RegisterWithMaster(void)
 {
     //!
+    // @category net
+    //
     // When running a server, don't register with the global master server.
     // Implies -server.
     //
-    // @category net
-    //
 
     if (!M_CheckParm("-privateserver"))
     {
diff --git a/src/setup/Makefile.am b/src/setup/Makefile.am
index 0adc90d..07c8bcb 100644
--- a/src/setup/Makefile.am
+++ b/src/setup/Makefile.am
@@ -1,6 +1,4 @@
 
-gamesdir = $(prefix)/games
-
 AM_CFLAGS = @SDL_CFLAGS@                                           \
             @SDLMIXER_CFLAGS@                                      \
             -I$(top_srcdir)/textscreen -I$(top_srcdir)/src
@@ -39,7 +37,7 @@ CLEANFILES = $(app_DATA)
 if HAVE_PYTHON
 
 setup_icon.c : $(top_builddir)/data/setup8.ico
-	$(top_builddir)/data/convert-icon $^ $@
+	$(top_builddir)/data/convert-icon $(top_builddir)/data/setup8.ico $@
 
 endif
 
diff --git a/src/setup/compatibility.c b/src/setup/compatibility.c
index 14d5bc3..31a48e9 100644
--- a/src/setup/compatibility.c
+++ b/src/setup/compatibility.c
@@ -22,7 +22,7 @@
 
 #include "compatibility.h"
 
-#define WINDOW_HELP_URL "http://www.chocolate-doom.org/setup-compat"
+#define WINDOW_HELP_URL "https://www.chocolate-doom.org/setup-compat"
 
 int vanilla_savegame_limit = 1;
 int vanilla_demo_limit = 1;
@@ -45,10 +45,7 @@ void CompatibilitySettings(void)
 
 void BindCompatibilityVariables(void)
 {
-    if (gamemission == doom || gamemission == strife)
-    {
-        M_BindIntVariable("vanilla_savegame_limit", &vanilla_savegame_limit);
-        M_BindIntVariable("vanilla_demo_limit",     &vanilla_demo_limit);
-    }
+    M_BindIntVariable("vanilla_savegame_limit", &vanilla_savegame_limit);
+    M_BindIntVariable("vanilla_demo_limit",     &vanilla_demo_limit);
 }
 
diff --git a/src/setup/display.c b/src/setup/display.c
index e94db94..a4278ef 100644
--- a/src/setup/display.c
+++ b/src/setup/display.c
@@ -28,7 +28,7 @@
 #include "display.h"
 #include "config.h"
 
-#define WINDOW_HELP_URL "http://www.chocolate-doom.org/setup-display"
+#define WINDOW_HELP_URL "https://www.chocolate-doom.org/setup-display"
 
 extern void RestartTextscreen(void);
 
@@ -106,6 +106,7 @@ static int usegamma = 0;
 
 int graphical_startup = 1;
 int show_endoom = 1;
+int show_diskicon = 1;
 int png_screenshots = 0;
 
 // These are the last screen width/height values that were chosen by the
@@ -725,6 +726,11 @@ void BindDisplayVariables(void)
         M_BindIntVariable("show_endoom",               &show_endoom);
     }
 
+    if (gamemission == doom || gamemission == strife)
+    {
+        M_BindIntVariable("show_diskicon",             &show_diskicon);
+    }
+
     if (gamemission == heretic || gamemission == hexen || gamemission == strife)
     {
         M_BindIntVariable("graphical_startup",        &graphical_startup);
diff --git a/src/setup/display.h b/src/setup/display.h
index 2d7e63e..341ccd8 100644
--- a/src/setup/display.h
+++ b/src/setup/display.h
@@ -21,5 +21,6 @@ void BindDisplayVariables(void);
 
 extern int show_endoom;
 extern int graphical_startup;
+extern int png_screenshots;
 
 #endif /* #ifndef SETUP_DISPLAY_H */
diff --git a/src/setup/joystick.c b/src/setup/joystick.c
index 22b1a08..e488034 100644
--- a/src/setup/joystick.c
+++ b/src/setup/joystick.c
@@ -29,7 +29,7 @@
 #include "txt_joyaxis.h"
 #include "txt_joybinput.h"
 
-#define WINDOW_HELP_URL "http://www.chocolate-doom.org/setup-gamepad"
+#define WINDOW_HELP_URL "https://www.chocolate-doom.org/setup-gamepad"
 
 typedef struct
 {
@@ -81,7 +81,7 @@ static int joystick_strafe_invert = 0;
 
 // Virtual to physical mapping.
 int joystick_physical_buttons[NUM_VIRTUAL_BUTTONS] = {
-    0, 1, 2, 3, 4, 5, 6, 7, 8, 9
+    0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10
 };
 
 static txt_button_t *joystick_button;
@@ -125,22 +125,33 @@ static SDL_Joystick **all_joysticks = NULL;
 // Always loaded before others, to get a known starting configuration.
 static const joystick_config_t empty_defaults[] =
 {
-    {"joystick_x_axis",        -1},
-    {"joystick_x_invert",      0},
-    {"joystick_y_axis",        -1},
-    {"joystick_y_invert",      0},
-    {"joystick_strafe_axis",   -1},
-    {"joystick_strafe_invert", 0},
-    {"joyb_fire",              -1},
-    {"joyb_use",               -1},
-    {"joyb_strafe",            -1},
-    {"joyb_speed",             -1},
-    {"joyb_strafeleft",        -1},
-    {"joyb_straferight",       -1},
-    {"joyb_prevweapon",        -1},
-    {"joyb_nextweapon",        -1},
-    {"joyb_jump",              -1},
-    {"joyb_menu_activate",     -1},
+    {"joystick_x_axis",            -1},
+    {"joystick_x_invert",          0},
+    {"joystick_y_axis",            -1},
+    {"joystick_y_invert",          0},
+    {"joystick_strafe_axis",       -1},
+    {"joystick_strafe_invert",     0},
+    {"joyb_fire",                  -1},
+    {"joyb_use",                   -1},
+    {"joyb_strafe",                -1},
+    {"joyb_speed",                 -1},
+    {"joyb_strafeleft",            -1},
+    {"joyb_straferight",           -1},
+    {"joyb_prevweapon",            -1},
+    {"joyb_nextweapon",            -1},
+    {"joyb_jump",                  -1},
+    {"joyb_menu_activate",         -1},
+    {"joyb_toggle_automap",        -1},
+    {"joystick_physical_button0",  0},
+    {"joystick_physical_button1",  1},
+    {"joystick_physical_button2",  2},
+    {"joystick_physical_button3",  3},
+    {"joystick_physical_button4",  4},
+    {"joystick_physical_button5",  5},
+    {"joystick_physical_button6",  6},
+    {"joystick_physical_button7",  7},
+    {"joystick_physical_button8",  8},
+    {"joystick_physical_button9",  9},
     {NULL, 0},
 };
 
@@ -313,6 +324,38 @@ static const joystick_config_t pc_gameport_controller[] =
     {NULL, 0},
 };
 
+// http://www.8bitdo.com/nes30pro/
+static const joystick_config_t nes30_pro_controller[] =
+{
+    {"joystick_x_axis",        CREATE_HAT_AXIS(0, HAT_AXIS_HORIZONTAL)},
+    {"joystick_y_axis",        CREATE_HAT_AXIS(0, HAT_AXIS_VERTICAL)},
+    {"joyb_fire",              4},  // Y
+    {"joyb_speed",             1},  // B
+    {"joyb_jump",              2},  // X
+    {"joyb_use",               0},  // A
+    {"joyb_strafeleft",        8},  // L1
+    {"joyb_straferight",       9}, // R1
+    {"joyb_prevweapon",        6},  // L2
+    {"joyb_nextweapon",        7},  // R2
+    {"joyb_menu_activate",     11}, // Start
+    {NULL, 0},
+};
+
+// http://www.8bitdo.com/sfc30/ or http://www.8bitdo.com/snes30/
+static const joystick_config_t sfc30_controller[] =
+{
+    {"joystick_x_axis",        0},
+    {"joystick_y_axis",        1},
+    {"joyb_fire",              4}, // Y
+    {"joyb_speed",             1}, // B
+    {"joyb_jump",              3}, // X
+    {"joyb_use",               0}, // A
+    {"joyb_strafeleft",        6}, // L
+    {"joyb_straferight",       7}, // R
+    {"joyb_menu_activate",    11}, // Start
+    {"joyb_toggle_automap",   10}, // Select
+    {NULL, 0},
+};
 
 static const known_joystick_t known_joysticks[] =
 {
@@ -405,6 +448,52 @@ static const known_joystick_t known_joysticks[] =
         2, 8, 1,
         pc_gameport_controller,
     },
+
+    // 8Bitdo NES30 Pro, http://www.8bitdo.com/nes30pro/
+    // Probably some of their other controllers can use the same config.
+    {
+        "8Bitdo NES30 Pro",
+        4, 16, 1,
+        nes30_pro_controller,
+    },
+
+    // 8Bitdo SFC30 SNES replica controller
+    // in default mode and in controller mode (Start+R)
+    // the latter suffixes "Joystick" to the name
+    // http://www.8bitdo.com/sfc30/
+    {
+        "8Bitdo SFC30 GamePad*",
+        4, 16, 1,
+        sfc30_controller,
+    },
+
+    // As above, but as detected on RHEL Linux (odd extra axes)
+    {
+        "8Bitdo SFC30 GamePad*",
+        6, 16, 1,
+        sfc30_controller,
+    },
+
+    // SNES30 colour variation of the above
+    // http://www.8bitdo.com/snes30/
+    {
+        "8Bitdo SNES30 GamePad*",
+        4, 16, 1,
+        sfc30_controller,
+    },
+
+    // 8Bitdo SFC30 SNES replica controller in USB controller mode
+    // tested with firmware V2.68 (Beta); latest stable V2.65 doesn't work on
+    // OS X in USB controller mode
+    // Names seen so far:
+    //     'SFC30 Joystick' (OS X)
+    //     'SFC30              SFC30 Joystick' (Fedora 24; RHEL7)
+    // XXX: there is probably a SNES30 variant of this too
+    {
+        "SFC30 *",
+        4, 12, 1,
+        sfc30_controller,
+    },
 };
 
 static const known_joystick_t *GetJoystickType(int index)
@@ -716,70 +805,76 @@ static void CalibrateJoystick(TXT_UNCAST_ARG(widget), TXT_UNCAST_ARG(unused))
 // GUI
 //
 
-static void AddJoystickControl(txt_table_t *table, char *label, int *var)
+static void AddJoystickControl(TXT_UNCAST_ARG(table), char *label, int *var)
 {
+    TXT_CAST_ARG(txt_table_t, table);
     txt_joystick_input_t *joy_input;
 
     joy_input = TXT_NewJoystickInput(var);
 
-    TXT_AddWidget(table, TXT_NewLabel(label));
-    TXT_AddWidget(table, joy_input);
+    TXT_AddWidgets(table,
+                   TXT_NewLabel(label),
+                   joy_input,
+                   TXT_TABLE_EMPTY,
+                   NULL);
 }
 
 void ConfigJoystick(void)
 {
     txt_window_t *window;
-    txt_table_t *button_table, *axis_table;
-    txt_table_t *joystick_table;
 
     window = TXT_NewWindow("Gamepad/Joystick configuration");
-
+    TXT_SetTableColumns(window, 6);
+    TXT_SetColumnWidths(window, 18, 10, 1, 15, 10, 0);
     TXT_SetWindowHelpURL(window, WINDOW_HELP_URL);
 
     TXT_AddWidgets(window,
-                   joystick_table = TXT_NewTable(2),
-                   TXT_NewSeparator("Axes"),
-                   axis_table = TXT_NewTable(2),
-                   TXT_NewSeparator("Buttons"),
-                   button_table = TXT_NewTable(4),
-                   NULL);
-
-    TXT_SetColumnWidths(joystick_table, 13, 40);
-
-    TXT_AddWidgets(joystick_table,
                    TXT_NewLabel("Controller"),
                    joystick_button = TXT_NewButton("zzzz"),
-                   NULL);
+                   TXT_TABLE_EOL,
 
-    TXT_SetColumnWidths(axis_table, 20, 15);
-
-    TXT_AddWidgets(axis_table,
+                   TXT_NewSeparator("Axes"),
                    TXT_NewLabel("Forward/backward"),
                    y_axis_widget = TXT_NewJoystickAxis(&joystick_y_axis,
                                                        &joystick_y_invert,
                                                        JOYSTICK_AXIS_VERTICAL),
+                   TXT_TABLE_OVERFLOW_RIGHT,
+                   TXT_TABLE_OVERFLOW_RIGHT,
+                   TXT_TABLE_EMPTY,
+                   TXT_TABLE_EMPTY,
+
                    TXT_NewLabel("Turn left/right"),
-                   x_axis_widget = TXT_NewJoystickAxis(&joystick_x_axis,
-                                                       &joystick_x_invert,
-                                                       JOYSTICK_AXIS_HORIZONTAL),
+                   x_axis_widget =
+                        TXT_NewJoystickAxis(&joystick_x_axis,
+                                            &joystick_x_invert,
+                                            JOYSTICK_AXIS_HORIZONTAL),
+                   TXT_TABLE_OVERFLOW_RIGHT,
+                   TXT_TABLE_OVERFLOW_RIGHT,
+                   TXT_TABLE_EMPTY,
+                   TXT_TABLE_EMPTY,
+
                    TXT_NewLabel("Strafe left/right"),
                    TXT_NewJoystickAxis(&joystick_strafe_axis,
                                        &joystick_strafe_invert,
                                         JOYSTICK_AXIS_HORIZONTAL),
-                   NULL);
+                   TXT_TABLE_OVERFLOW_RIGHT,
+                   TXT_TABLE_OVERFLOW_RIGHT,
+                   TXT_TABLE_EMPTY,
+                   TXT_TABLE_EMPTY,
 
-    TXT_SetColumnWidths(button_table, 16, 12, 14, 11);
+                   TXT_NewSeparator("Buttons"),
+                   NULL);
 
-    AddJoystickControl(button_table, "Fire/Attack", &joybfire);
-    AddJoystickControl(button_table, "Strafe Left", &joybstrafeleft);
+    AddJoystickControl(window, "Fire/Attack", &joybfire);
+    AddJoystickControl(window, "Strafe Left", &joybstrafeleft);
 
-    AddJoystickControl(button_table, "Use", &joybuse);
-    AddJoystickControl(button_table, "Strafe Right", &joybstraferight);
+    AddJoystickControl(window, "Use", &joybuse);
+    AddJoystickControl(window, "Strafe Right", &joybstraferight);
 
-    AddJoystickControl(button_table, "Previous weapon", &joybprevweapon);
-    AddJoystickControl(button_table, "Strafe", &joybstrafe);
+    AddJoystickControl(window, "Previous weapon", &joybprevweapon);
+    AddJoystickControl(window, "Strafe", &joybstrafe);
 
-    AddJoystickControl(button_table, "Next weapon", &joybnextweapon);
+    AddJoystickControl(window, "Next weapon", &joybnextweapon);
 
     // High values of joybspeed are used to activate the "always run mode"
     // trick in Vanilla Doom.  If this has been enabled, not only is the
@@ -787,15 +882,17 @@ void ConfigJoystick(void)
 
     if (joybspeed < 20)
     {
-        AddJoystickControl(button_table, "Speed", &joybspeed);
+        AddJoystickControl(window, "Speed", &joybspeed);
     }
 
     if (gamemission == hexen || gamemission == strife)
     {
-        AddJoystickControl(button_table, "Jump", &joybjump);
+        AddJoystickControl(window, "Jump", &joybjump);
     }
 
-    AddJoystickControl(button_table, "Activate menu", &joybmenu);
+    AddJoystickControl(window, "Activate menu", &joybmenu);
+
+    AddJoystickControl(window, "Toggle Automap", &joybautomap);
 
     TXT_SignalConnect(joystick_button, "pressed", CalibrateJoystick, NULL);
     TXT_SetWindowAction(window, TXT_HORIZ_CENTER, TestConfigAction());
diff --git a/src/setup/keyboard.c b/src/setup/keyboard.c
index 4bbe150..18accf8 100644
--- a/src/setup/keyboard.c
+++ b/src/setup/keyboard.c
@@ -25,7 +25,7 @@
 #include "joystick.h"
 #include "keyboard.h"
 
-#define WINDOW_HELP_URL "http://www.chocolate-doom.org/setup-keyboard"
+#define WINDOW_HELP_URL "https://www.chocolate-doom.org/setup-keyboard"
 
 int vanilla_keyboard_mapping = 1;
 
@@ -146,8 +146,9 @@ static void KeySetCallback(TXT_UNCAST_ARG(widget), TXT_UNCAST_ARG(variable))
 
 // Add a label and keyboard input to the specified table.
 
-static void AddKeyControl(txt_table_t *table, char *name, int *var)
+static void AddKeyControl(TXT_UNCAST_ARG(table), char *name, int *var)
 {
+    TXT_CAST_ARG(txt_table_t, table);
     txt_key_input_t *key_input;
 
     TXT_AddWidget(table, TXT_NewLabel(name));
@@ -157,20 +158,26 @@ static void AddKeyControl(txt_table_t *table, char *name, int *var)
     TXT_SignalConnect(key_input, "set", KeySetCallback, var);
 }
 
-static void AddSectionLabel(txt_table_t *table, char *title, boolean add_space)
+static void AddSectionLabel(TXT_UNCAST_ARG(table), char *title,
+                            boolean add_space)
 {
+    TXT_CAST_ARG(txt_table_t, table);
     char buf[64];
 
     if (add_space)
     {
-        TXT_AddWidgets(table, TXT_NewStrut(0, 1), TXT_NewStrut(0, 1),
-                              NULL);
+        TXT_AddWidgets(table,
+                       TXT_NewStrut(0, 1),
+                       TXT_TABLE_EOL,
+                       NULL);
     }
 
     M_snprintf(buf, sizeof(buf), " - %s - ", title);
 
-    TXT_AddWidgets(table, TXT_NewLabel(buf),  TXT_NewStrut(0, 0),
-                          NULL);
+    TXT_AddWidgets(table,
+                   TXT_NewLabel(buf),
+                   TXT_TABLE_EOL,
+                   NULL);
 }
 static void ConfigExtraKeys(TXT_UNCAST_ARG(widget), TXT_UNCAST_ARG(unused))
 {
@@ -244,7 +251,8 @@ static void ConfigExtraKeys(TXT_UNCAST_ARG(widget), TXT_UNCAST_ARG(unused))
             AddKeyControl(table, "Chaos Device", &key_arti_teleport);
             AddKeyControl(table, "Banishment Device", &key_arti_teleportother);
             AddKeyControl(table, "Porkalator", &key_arti_egg);
-            AddKeyControl(table, "Icon of the Defender", &key_arti_invulnerability);
+            AddKeyControl(table, "Icon of the Defender",
+                          &key_arti_invulnerability);
         }
     }
     else
@@ -262,8 +270,8 @@ static void ConfigExtraKeys(TXT_UNCAST_ARG(widget), TXT_UNCAST_ARG(unused))
     AddKeyControl(table, "Weapon 6", &key_weapon6);
     AddKeyControl(table, "Weapon 7", &key_weapon7);
     AddKeyControl(table, "Weapon 8", &key_weapon8);
-    AddKeyControl(table, "Previous weapon",       &key_prevweapon);
-    AddKeyControl(table, "Next weapon",           &key_nextweapon);
+    AddKeyControl(table, "Previous weapon", &key_prevweapon);
+    AddKeyControl(table, "Next weapon", &key_nextweapon);
 }
 
 static void OtherKeysDialog(TXT_UNCAST_ARG(widget), TXT_UNCAST_ARG(unused))
@@ -353,9 +361,6 @@ static void OtherKeysDialog(TXT_UNCAST_ARG(widget), TXT_UNCAST_ARG(unused))
 void ConfigKeyboard(void)
 {
     txt_window_t *window;
-    txt_table_t *movement_table;
-    txt_table_t *action_table;
-    txt_table_t *dialogs_table;
     txt_checkbox_t *run_control;
 
     always_run = joybspeed >= 20;
@@ -364,54 +369,57 @@ void ConfigKeyboard(void)
 
     TXT_SetWindowHelpURL(window, WINDOW_HELP_URL);
 
-    TXT_AddWidgets(window,
-                   TXT_NewSeparator("Movement"),
-                   movement_table = TXT_NewTable(4),
+    // The window is on a 5-column grid layout that looks like:
+    // Label | Control | | Label | Control
+    // There is a small gap between the two conceptual "columns" of
+    // controls, just for spacing.
+    TXT_SetTableColumns(window, 5);
+    TXT_SetColumnWidths(window, 15, 8, 2, 15, 8);
 
-                   TXT_NewSeparator("Action"),
-                   action_table = TXT_NewTable(4),
-                   dialogs_table = TXT_NewTable(2),
+    TXT_AddWidget(window, TXT_NewSeparator("Movement"));
+    AddKeyControl(window, "Move Forward", &key_up);
+    TXT_AddWidget(window, TXT_TABLE_EMPTY);
+    AddKeyControl(window, "Strafe Left", &key_strafeleft);
 
-                   TXT_NewSeparator("Misc."),
-                   run_control = TXT_NewCheckBox("Always run", &always_run),
-                   TXT_NewInvertedCheckBox("Use native keyboard mapping", 
-                                           &vanilla_keyboard_mapping),
-                   NULL);
-
-    TXT_SetColumnWidths(movement_table, 15, 8, 15, 8);
+    AddKeyControl(window, "Move Backward", &key_down);
+    TXT_AddWidget(window, TXT_TABLE_EMPTY);
+    AddKeyControl(window, "Strafe Right", &key_straferight);
 
-    TXT_SignalConnect(run_control, "changed", UpdateJoybSpeed, NULL);
+    AddKeyControl(window, "Turn Left", &key_left);
+    TXT_AddWidget(window, TXT_TABLE_EMPTY);
+    AddKeyControl(window, "Speed On", &key_speed);
 
-    AddKeyControl(movement_table, "Move Forward", &key_up);
-    AddKeyControl(movement_table, " Strafe Left", &key_strafeleft);
-    AddKeyControl(movement_table, "Move Backward", &key_down);
-    AddKeyControl(movement_table, " Strafe Right", &key_straferight);
-    AddKeyControl(movement_table, "Turn Left", &key_left);
-    AddKeyControl(movement_table, " Speed On", &key_speed);
-    AddKeyControl(movement_table, "Turn Right", &key_right);
-    AddKeyControl(movement_table, " Strafe On", &key_strafe);
+    AddKeyControl(window, "Turn Right", &key_right);
+    TXT_AddWidget(window, TXT_TABLE_EMPTY);
+    AddKeyControl(window, "Strafe On", &key_strafe);
 
     if (gamemission == hexen || gamemission == strife)
     {
-        AddKeyControl(movement_table, "Jump", &key_jump);
+        AddKeyControl(window, "Jump", &key_jump);
     }
 
-    TXT_SetColumnWidths(action_table, 15, 8, 15, 8);
-
-    AddKeyControl(action_table, "Fire/Attack", &key_fire);
-    AddKeyControl(action_table, " Use", &key_use);
+    TXT_AddWidget(window, TXT_NewSeparator("Action"));
+    AddKeyControl(window, "Fire/Attack", &key_fire);
+    TXT_AddWidget(window, TXT_TABLE_EMPTY);
+    AddKeyControl(window, "Use", &key_use);
 
-    // Other key bindings are stored in separate sub-dialogs:
-
-    TXT_SetColumnWidths(dialogs_table, 24, 24);
-
-    TXT_AddWidgets(dialogs_table,
+    TXT_AddWidgets(window,
                    TXT_NewButton2("More controls...", ConfigExtraKeys, NULL),
+                   TXT_TABLE_OVERFLOW_RIGHT,
+                   TXT_TABLE_EMPTY,
                    TXT_NewButton2("Other keys...", OtherKeysDialog, NULL),
+                   TXT_TABLE_OVERFLOW_RIGHT,
+
+                   TXT_NewSeparator("Misc."),
+                   run_control = TXT_NewCheckBox("Always run", &always_run),
+                   TXT_TABLE_EOL,
+                   TXT_NewInvertedCheckBox("Use native keyboard mapping",
+                                           &vanilla_keyboard_mapping),
+                   TXT_TABLE_EOL,
                    NULL);
 
+    TXT_SignalConnect(run_control, "changed", UpdateJoybSpeed, NULL);
     TXT_SetWindowAction(window, TXT_HORIZ_CENTER, TestConfigAction());
-
 }
 
 void BindKeyboardVariables(void)
diff --git a/src/setup/mainmenu.c b/src/setup/mainmenu.c
index e35decd..974f4bb 100644
--- a/src/setup/mainmenu.c
+++ b/src/setup/mainmenu.c
@@ -38,7 +38,7 @@
 #include "multiplayer.h"
 #include "sound.h"
 
-#define WINDOW_HELP_URL "http://www.chocolate-doom.org/setup"
+#define WINDOW_HELP_URL "https://www.chocolate-doom.org/setup"
 
 static const int cheat_sequence[] =
 {
@@ -91,6 +91,8 @@ static void SensibleDefaults(void)
     show_endoom = 0;
     dclick_use = 0;
     novert = 1;
+    snd_dmxoption = "-opl3 -reverse";
+    png_screenshots = 1;
 }
 
 static int MainMenuKeyPress(txt_window_t *window, int key, void *user_data)
@@ -224,21 +226,10 @@ void MainMenu(void)
                          (TxtWidgetSignalFunc) ConfigMouse, NULL),
           TXT_NewButton2("Configure Gamepad/Joystick",
                          (TxtWidgetSignalFunc) ConfigJoystick, NULL),
+          TXT_NewButton2("Compatibility",
+                         (TxtWidgetSignalFunc) CompatibilitySettings, NULL),
           NULL);
 
-    // The compatibility window is only appropriate for Doom/Strife.
-
-    if (gamemission == doom || gamemission == strife)
-    {
-        txt_button_t *button;
-
-        button = TXT_NewButton2("Compatibility", 
-                                (TxtWidgetSignalFunc) CompatibilitySettings,
-                                NULL);
-
-        TXT_AddWidget(window, button);
-    }
-
     TXT_AddWidgets(window,
           GetLaunchButton(),
           TXT_NewStrut(0, 1),
diff --git a/src/setup/mode.c b/src/setup/mode.c
index ed63784..71d9e12 100644
--- a/src/setup/mode.c
+++ b/src/setup/mode.c
@@ -140,6 +140,9 @@ static void BindMiscVariables(void)
 
     if (gamemission == strife)
     {
+        // Strife has a different default value than the other games
+        screenblocks = 10;
+
         M_BindStringVariable("back_flat",   &back_flat);
         M_BindStringVariable("nickname",    &nickname);
 
diff --git a/src/setup/mouse.c b/src/setup/mouse.c
index 91e3887..36a8ef8 100644
--- a/src/setup/mouse.c
+++ b/src/setup/mouse.c
@@ -25,7 +25,7 @@
 #include "mode.h"
 #include "mouse.h"
 
-#define WINDOW_HELP_URL "http://www.chocolate-doom.org/setup-mouse"
+#define WINDOW_HELP_URL "https://www.chocolate-doom.org/setup-mouse"
 
 static int usemouse = 1;
 
@@ -67,8 +67,9 @@ static void MouseSetCallback(TXT_UNCAST_ARG(widget), TXT_UNCAST_ARG(variable))
     }
 }
 
-static void AddMouseControl(txt_table_t *table, char *label, int *var)
+static void AddMouseControl(TXT_UNCAST_ARG(table), char *label, int *var)
 {
+    TXT_CAST_ARG(txt_table_t, table);
     txt_mouse_input_t *mouse_input;
 
     TXT_AddWidget(table, TXT_NewLabel(label));
@@ -92,14 +93,14 @@ static void ConfigExtraButtons(TXT_UNCAST_ARG(widget), TXT_UNCAST_ARG(unused))
                    buttons_table = TXT_NewTable(2),
                    NULL);
 
-    TXT_SetColumnWidths(buttons_table, 29, 5);
+    TXT_SetColumnWidths(buttons_table, 24, 5);
 
     AddMouseControl(buttons_table, "Move backward", &mousebbackward);
     AddMouseControl(buttons_table, "Use", &mousebuse);
     AddMouseControl(buttons_table, "Strafe left", &mousebstrafeleft);
     AddMouseControl(buttons_table, "Strafe right", &mousebstraferight);
 
-    if (gamemission == hexen)
+    if (gamemission == hexen || gamemission == strife)
     {
         AddMouseControl(buttons_table, "Jump", &mousebjump);
     }
@@ -111,50 +112,44 @@ static void ConfigExtraButtons(TXT_UNCAST_ARG(widget), TXT_UNCAST_ARG(unused))
 void ConfigMouse(void)
 {
     txt_window_t *window;
-    txt_table_t *motion_table;
-    txt_table_t *buttons_table;
 
     window = TXT_NewWindow("Mouse configuration");
 
+    TXT_SetTableColumns(window, 2);
+
+    TXT_SetWindowAction(window, TXT_HORIZ_CENTER, TestConfigAction());
     TXT_SetWindowHelpURL(window, WINDOW_HELP_URL);
 
     TXT_AddWidgets(window,
                    TXT_NewCheckBox("Enable mouse", &usemouse),
+                   TXT_TABLE_OVERFLOW_RIGHT,
                    TXT_NewInvertedCheckBox("Allow vertical mouse movement", 
                                            &novert),
+                   TXT_TABLE_OVERFLOW_RIGHT,
                    TXT_NewCheckBox("Grab mouse in windowed mode", 
                                    &grabmouse),
+                   TXT_TABLE_OVERFLOW_RIGHT,
                    TXT_NewCheckBox("Double click acts as \"use\"",
                                    &dclick_use),
+                   TXT_TABLE_OVERFLOW_RIGHT,
 
                    TXT_NewSeparator("Mouse motion"),
-                   motion_table = TXT_NewTable(2),
-    
-                   TXT_NewSeparator("Buttons"),
-                   buttons_table = TXT_NewTable(2),
-                   TXT_NewButton2("More controls...",
-                                  ConfigExtraButtons,
-                                  NULL),
-                   NULL);
-
-    TXT_SetColumnWidths(motion_table, 27, 5);
-
-    TXT_AddWidgets(motion_table,
                    TXT_NewLabel("Speed"),
                    TXT_NewSpinControl(&mouseSensitivity, 1, 256),
                    TXT_NewLabel("Acceleration"),
                    TXT_NewFloatSpinControl(&mouse_acceleration, 1.0, 5.0),
                    TXT_NewLabel("Acceleration threshold"),
                    TXT_NewSpinControl(&mouse_threshold, 0, 32),
+
+                   TXT_NewSeparator("Buttons"),
                    NULL);
 
-    TXT_SetColumnWidths(buttons_table, 27, 5);
+    AddMouseControl(window, "Fire/Attack", &mousebfire);
+    AddMouseControl(window, "Move forward", &mousebforward);
+    AddMouseControl(window, "Strafe on", &mousebstrafe);
 
-    AddMouseControl(buttons_table, "Fire/Attack", &mousebfire);
-    AddMouseControl(buttons_table, "Move forward", &mousebforward);
-    AddMouseControl(buttons_table, "Strafe on", &mousebstrafe);
-    
-    TXT_SetWindowAction(window, TXT_HORIZ_CENTER, TestConfigAction());
+    TXT_AddWidget(window,
+                  TXT_NewButton2("More controls...", ConfigExtraButtons, NULL));
 }
 
 void BindMouseVariables(void)
diff --git a/src/setup/multiplayer.c b/src/setup/multiplayer.c
index 3fe6a82..90aa99a 100644
--- a/src/setup/multiplayer.c
+++ b/src/setup/multiplayer.c
@@ -34,10 +34,10 @@
 #include "net_io.h"
 #include "net_query.h"
 
-#define MULTI_START_HELP_URL "http://www.chocolate-doom.org/setup-multi-start"
-#define MULTI_JOIN_HELP_URL "http://www.chocolate-doom.org/setup-multi-join"
-#define MULTI_CONFIG_HELP_URL "http://www.chocolate-doom.org/setup-multi-config"
-#define LEVEL_WARP_HELP_URL "http://www.chocolate-doom.org/setup-level-warp"
+#define MULTI_START_HELP_URL "https://www.chocolate-doom.org/setup-multi-start"
+#define MULTI_JOIN_HELP_URL "https://www.chocolate-doom.org/setup-multi-join"
+#define MULTI_CONFIG_HELP_URL "https://www.chocolate-doom.org/setup-multi-config"
+#define LEVEL_WARP_HELP_URL "https://www.chocolate-doom.org/setup-level-warp"
 
 #define NUM_WADS 10
 #define NUM_EXTRA_PARAMS 10
@@ -389,7 +389,6 @@ static void CloseLevelSelectDialog(TXT_UNCAST_ARG(button), TXT_UNCAST_ARG(window
 static void LevelSelectDialog(TXT_UNCAST_ARG(widget), TXT_UNCAST_ARG(user_data))
 {
     txt_window_t *window;
-    txt_table_t *table;
     txt_button_t *button;
     const iwad_t *iwad;
     char buf[10];
@@ -404,7 +403,7 @@ static void LevelSelectDialog(TXT_UNCAST_ARG(widget), TXT_UNCAST_ARG(user_data))
     if (warptype == WARP_ExMy)
     {
         episodes = D_GetNumEpisodes(iwad->mission, iwad->mode);
-        table = TXT_NewTable(episodes);
+        TXT_SetTableColumns(window, episodes);
 
         // ExMy levels
 
@@ -419,7 +418,7 @@ static void LevelSelectDialog(TXT_UNCAST_ARG(widget), TXT_UNCAST_ARG(user_data))
 
                 if (!D_ValidEpisodeMap(iwad->mission, iwad->mode, x, y))
                 {
-                    TXT_AddWidget(table, NULL);
+                    TXT_AddWidget(window, NULL);
                     continue;
                 }
 
@@ -429,18 +428,18 @@ static void LevelSelectDialog(TXT_UNCAST_ARG(widget), TXT_UNCAST_ARG(user_data))
                                   SetExMyWarp, (void *) (x * 10 + y));
                 TXT_SignalConnect(button, "pressed",
                                   CloseLevelSelectDialog, window);
-                TXT_AddWidget(table, button);
+                TXT_AddWidget(window, button);
 
                 if (warpepisode == x && warpmap == y)
                 {
-                    TXT_SelectWidget(table, button);
+                    TXT_SelectWidget(window, button);
                 }
             }
         }
     }
     else
     {
-        table = TXT_NewTable(6);
+        TXT_SetTableColumns(window, 6);
 
         for (i=0; i<60; ++i)
         {
@@ -451,7 +450,7 @@ static void LevelSelectDialog(TXT_UNCAST_ARG(widget), TXT_UNCAST_ARG(user_data))
 
             if (!D_ValidEpisodeMap(iwad->mission, iwad->mode, 1, l))
             {
-                TXT_AddWidget(table, NULL);
+                TXT_AddWidget(window, NULL);
                 continue;
             }
 
@@ -461,16 +460,14 @@ static void LevelSelectDialog(TXT_UNCAST_ARG(widget), TXT_UNCAST_ARG(user_data))
                               SetMAPxyWarp, (void *) l);
             TXT_SignalConnect(button, "pressed",
                               CloseLevelSelectDialog, window);
-            TXT_AddWidget(table, button);
+            TXT_AddWidget(window, button);
 
             if (warpmap == l)
             {
-                TXT_SelectWidget(table, button);
+                TXT_SelectWidget(window, button);
             }
         }
     }
-
-    TXT_AddWidget(window, table);
 }
 
 static void IWADSelected(TXT_UNCAST_ARG(widget), TXT_UNCAST_ARG(unused))
@@ -711,11 +708,11 @@ static txt_dropdown_list_t *GameTypeDropdown(void)
 static void StartGameMenu(char *window_title, int multiplayer)
 {
     txt_window_t *window;
-    txt_table_t *gameopt_table;
-    txt_table_t *advanced_table;
     txt_widget_t *iwad_selector;
 
     window = TXT_NewWindow(window_title);
+    TXT_SetTableColumns(window, 2);
+    TXT_SetColumnWidths(window, 12, 6);
 
     if (multiplayer)
     {
@@ -726,30 +723,18 @@ static void StartGameMenu(char *window_title, int multiplayer)
         TXT_SetWindowHelpURL(window, LEVEL_WARP_HELP_URL);
     }
 
-    TXT_AddWidgets(window,
-                   gameopt_table = TXT_NewTable(2),
-                   TXT_NewSeparator("Monster options"),
-                   TXT_NewInvertedCheckBox("Monsters enabled", &nomonsters),
-                   TXT_NewCheckBox("Fast monsters", &fast),
-                   TXT_NewCheckBox("Respawning monsters", &respawn),
-                   TXT_NewSeparator("Advanced"),
-                   advanced_table = TXT_NewTable(2),
-                   NULL);
-
     TXT_SetWindowAction(window, TXT_HORIZ_CENTER, WadWindowAction());
     TXT_SetWindowAction(window, TXT_HORIZ_RIGHT, StartGameAction(multiplayer));
 
-    TXT_SetColumnWidths(gameopt_table, 12, 6);
-
-    TXT_AddWidgets(gameopt_table,
-           TXT_NewLabel("Game"),
-           iwad_selector = IWADSelector(),
-           NULL);
+    TXT_AddWidgets(window,
+                   TXT_NewLabel("Game"),
+                   iwad_selector = IWADSelector(),
+                   NULL);
 
     if (gamemission == hexen)
     {
         txt_dropdown_list_t *cc_dropdown;
-        TXT_AddWidgets(gameopt_table,
+        TXT_AddWidgets(window,
                        TXT_NewLabel("Character class "),
                        cc_dropdown = TXT_NewDropdownList(&character_class,
                                                          character_classes, 3),
@@ -760,16 +745,16 @@ static void StartGameMenu(char *window_title, int multiplayer)
         TXT_SignalConnect(cc_dropdown, "changed", UpdateWarpType, NULL);
     }
 
-    TXT_AddWidgets(gameopt_table,
-           TXT_NewLabel("Skill"),
-           skillbutton = TXT_NewDropdownList(&skill, doom_skills, 5),
-           TXT_NewLabel("Level warp"),
-           warpbutton = TXT_NewButton2("????", LevelSelectDialog, NULL),
-           NULL);
+    TXT_AddWidgets(window,
+                   TXT_NewLabel("Skill"),
+                   skillbutton = TXT_NewDropdownList(&skill, doom_skills, 5),
+                   TXT_NewLabel("Level warp"),
+                   warpbutton = TXT_NewButton2("?", LevelSelectDialog, NULL),
+                   NULL);
 
     if (multiplayer)
     {
-        TXT_AddWidgets(gameopt_table,
+        TXT_AddWidgets(window,
                TXT_NewLabel("Game type"),
                GameTypeDropdown(),
                TXT_NewLabel("Time limit"),
@@ -777,22 +762,35 @@ static void StartGameMenu(char *window_title, int multiplayer)
                                TXT_NewLabel("minutes"),
                                NULL),
                NULL);
+    }
 
-        TXT_AddWidget(window,
-                      TXT_NewInvertedCheckBox("Register with master server",
-                                              &privateserver));
+    TXT_AddWidgets(window,
+                   TXT_NewSeparator("Monster options"),
+                   TXT_NewInvertedCheckBox("Monsters enabled", &nomonsters),
+                   TXT_TABLE_OVERFLOW_RIGHT,
+                   TXT_NewCheckBox("Fast monsters", &fast),
+                   TXT_TABLE_OVERFLOW_RIGHT,
+                   TXT_NewCheckBox("Respawning monsters", &respawn),
+                   TXT_TABLE_OVERFLOW_RIGHT,
+                   NULL);
 
-        TXT_AddWidgets(advanced_table,
+    if (multiplayer)
+    {
+        TXT_AddWidgets(window,
+                       TXT_NewSeparator("Advanced"),
                        TXT_NewLabel("UDP port"),
                        TXT_NewIntInputBox(&udpport, 5),
+                       TXT_NewInvertedCheckBox("Register with master server",
+                                               &privateserver),
+                       TXT_TABLE_OVERFLOW_RIGHT,
                        NULL);
     }
 
-    TXT_AddWidget(window,
-                  TXT_NewButton2("Add extra parameters...", 
-                                 OpenExtraParamsWindow, NULL));
-
-    TXT_SetColumnWidths(advanced_table, 12, 6);
+    TXT_AddWidgets(window,
+                   TXT_NewButton2("Add extra parameters...",
+                                  OpenExtraParamsWindow, NULL),
+                   TXT_TABLE_OVERFLOW_RIGHT,
+                   NULL);
 
     TXT_SignalConnect(iwad_selector, "changed", UpdateWarpType, NULL);
 
@@ -994,46 +992,43 @@ static void FindLANServer(TXT_UNCAST_ARG(widget),
 void JoinMultiGame(void)
 {
     txt_window_t *window;
-    txt_table_t *gameopt_table;
-    txt_table_t *serveropt_table;
     txt_inputbox_t *address_box;
 
     window = TXT_NewWindow("Join multiplayer game");
-    TXT_SetWindowHelpURL(window, MULTI_JOIN_HELP_URL);
+    TXT_SetTableColumns(window, 2);
+    TXT_SetColumnWidths(window, 12, 12);
 
-    TXT_AddWidgets(window, 
-        gameopt_table = TXT_NewTable(2),
-        TXT_NewSeparator("Server"),
-        serveropt_table = TXT_NewTable(1),
-        TXT_NewStrut(0, 1),
-        TXT_NewButton2("Add extra parameters...", OpenExtraParamsWindow, NULL),
-        NULL);
-
-    TXT_SetColumnWidths(gameopt_table, 12, 12);
+    TXT_SetWindowHelpURL(window, MULTI_JOIN_HELP_URL);
 
-    TXT_AddWidgets(gameopt_table,
+    TXT_AddWidgets(window,
                    TXT_NewLabel("Game"),
                    IWADSelector(),
                    NULL);
 
     if (gamemission == hexen)
     {
-        TXT_AddWidgets(gameopt_table,
+        TXT_AddWidgets(window,
                        TXT_NewLabel("Character class "),
                        TXT_NewDropdownList(&character_class,
                                            character_classes, 3),
                        NULL);
     }
 
-    TXT_AddWidgets(serveropt_table,
-                   TXT_NewHorizBox(
-                           TXT_NewLabel("Connect to address: "),
-                           address_box = TXT_NewInputBox(&connect_address, 30),
-                           NULL),
+    TXT_AddWidgets(window,
+                   TXT_NewSeparator("Server"),
+                   TXT_NewLabel("Connect to address: "),
+                   address_box = TXT_NewInputBox(&connect_address, 30),
+
                    TXT_NewButton2("Find server on Internet...",
                                   FindInternetServer, NULL),
+                   TXT_TABLE_OVERFLOW_RIGHT,
                    TXT_NewButton2("Find server on local network...",
                                   FindLANServer, NULL),
+                   TXT_TABLE_OVERFLOW_RIGHT,
+                   TXT_NewStrut(0, 1),
+                   TXT_TABLE_OVERFLOW_RIGHT,
+                   TXT_NewButton2("Add extra parameters...",
+                                  OpenExtraParamsWindow, NULL),
                    NULL);
 
     TXT_SelectWidget(window, address_box);
@@ -1110,7 +1105,7 @@ void MultiplayerConfig(void)
     window = TXT_NewWindow("Multiplayer Configuration");
     TXT_SetWindowHelpURL(window, MULTI_CONFIG_HELP_URL);
 
-    TXT_AddWidgets(window, 
+    TXT_AddWidgets(window,
                    TXT_NewStrut(0, 1),
                    TXT_NewHorizBox(TXT_NewLabel("Player name:  "),
                                    TXT_NewInputBox(&net_player_name, 25),
@@ -1133,7 +1128,7 @@ void MultiplayerConfig(void)
                        TXT_NewInputBox(&chat_macros[(i + 1) % 10], 40),
                        NULL);
     }
-    
+
     TXT_AddWidget(window, table);
 }
 
diff --git a/src/setup/sound.c b/src/setup/sound.c
index fe9b2c7..3a8533b 100644
--- a/src/setup/sound.c
+++ b/src/setup/sound.c
@@ -25,7 +25,7 @@
 #include "mode.h"
 #include "sound.h"
 
-#define WINDOW_HELP_URL "http://www.chocolate-doom.org/setup-sound"
+#define WINDOW_HELP_URL "https://www.chocolate-doom.org/setup-sound"
 
 typedef enum
 {
@@ -85,6 +85,8 @@ int opl_io_port = 0x388;
 int snd_cachesize = 64 * 1024 * 1024;
 int snd_maxslicetime_ms = 28;
 char *snd_musiccmd = "";
+int snd_pitchshift = 0;
+char *snd_dmxoption = "";
 
 static int numChannels = 8;
 static int sfxVolume = 8;
@@ -94,7 +96,6 @@ static int show_talk = 0;
 static int use_libsamplerate = 0;
 static float libsamplerate_scale = 0.65;
 
-static char *snd_dmxoption = "";
 static char *timidity_cfg_path = NULL;
 static char *gus_patch_path = NULL;
 static int gus_ram_kb = 1024;
@@ -186,44 +187,47 @@ static void UpdateExtraTable(TXT_UNCAST_ARG(widget),
 {
     TXT_CAST_ARG(txt_table_t, extra_table);
 
+    TXT_ClearTable(extra_table);
+
     switch (snd_musicmode)
     {
-    case MUSICMODE_OPL:
-        TXT_InitTable(extra_table, 2);
-        TXT_SetColumnWidths(extra_table, 19, 4);
-        TXT_AddWidgets(extra_table,
-                        TXT_NewLabel("OPL type"),
-                        OPLTypeSelector(),
-                        NULL);
-        break;
-
-    case MUSICMODE_GUS:
-        TXT_InitTable(extra_table, 1);
-        TXT_AddWidgets(extra_table,
-                        TXT_NewLabel("GUS patch path:"),
-                        TXT_NewFileSelector(&gus_patch_path, 30,
-                                            "Select path to GUS patches",
-                                            TXT_DIRECTORY),
-                        NULL);
-        break;
-
-    case MUSICMODE_NATIVE:
-        TXT_InitTable(extra_table, 1);
-        TXT_AddWidgets(extra_table,
-                        TXT_NewLabel("Timidity configuration file:"),
-                        TXT_NewFileSelector(&timidity_cfg_path, 30,
-                                            "Select Timidity config file",
-                                            cfg_extension),
-                        NULL);
-        break;
+        case MUSICMODE_OPL:
+            TXT_AddWidgets(extra_table,
+                           TXT_NewLabel("OPL type"),
+                           OPLTypeSelector(),
+                           NULL);
+            break;
+
+        case MUSICMODE_GUS:
+            TXT_AddWidgets(extra_table,
+                           TXT_NewLabel("GUS patch path:"),
+                           TXT_TABLE_OVERFLOW_RIGHT,
+                           TXT_NewFileSelector(&gus_patch_path, 34,
+                                               "Select path to GUS patches",
+                                               TXT_DIRECTORY),
+                           TXT_TABLE_OVERFLOW_RIGHT,
+                           NULL);
+            break;
+
+        case MUSICMODE_NATIVE:
+            TXT_AddWidgets(extra_table,
+                           TXT_NewLabel("Timidity configuration file:"),
+                           TXT_TABLE_OVERFLOW_RIGHT,
+                           TXT_NewFileSelector(&timidity_cfg_path, 34,
+                                               "Select Timidity config file",
+                                               cfg_extension),
+                           TXT_TABLE_OVERFLOW_RIGHT,
+                           NULL);
+            break;
+
+        default:
+            break;
     }
 }
 
 void ConfigSound(void)
 {
     txt_window_t *window;
-    txt_table_t *sfx_table;
-    txt_table_t *music_table;
     txt_table_t *extra_table;
     txt_dropdown_list_t *sfx_mode_control;
     txt_dropdown_list_t *music_mode_control;
@@ -292,20 +296,15 @@ void ConfigSound(void)
     // Build the window
 
     window = TXT_NewWindow("Sound configuration");
-
     TXT_SetWindowHelpURL(window, WINDOW_HELP_URL);
+    TXT_SetTableColumns(window, 2);
+    TXT_SetColumnWidths(window, 19, 15);
 
     TXT_SetWindowPosition(window, TXT_HORIZ_CENTER, TXT_VERT_TOP,
                                   TXT_SCREEN_W / 2, 5);
 
     TXT_AddWidgets(window,
-               TXT_NewSeparator("Sound effects"),
-               sfx_table = TXT_NewTable(2),
-               NULL);
-
-    TXT_SetColumnWidths(sfx_table, 19, 15);
-
-    TXT_AddWidgets(sfx_table,
+                   TXT_NewSeparator("Sound effects"),
                    TXT_NewLabel("Sound effects"),
                    sfx_mode_control = TXT_NewDropdownList(&snd_sfxmode,
                                                           sfxmode_strings,
@@ -316,33 +315,39 @@ void ConfigSound(void)
                    TXT_NewSpinControl(&sfxVolume, 0, 15),
                    NULL);
 
+    // Only show for games that implemented pitch shifting:
+    if (gamemission == doom || gamemission == heretic || gamemission == hexen)
+    {
+        TXT_AddWidgets(window,
+                       TXT_NewCheckBox("Pitch-shifted sounds",
+                                       &snd_pitchshift),
+                       TXT_TABLE_OVERFLOW_RIGHT,
+                       NULL);
+    }
+
     if (gamemission == strife)
     {
-        TXT_AddWidgets(sfx_table,
+        TXT_AddWidgets(window,
                        TXT_NewLabel("Voice volume"),
                        TXT_NewSpinControl(&voiceVolume, 0, 15),
+                       TXT_NewCheckBox("Show text with voices", &show_talk),
+                       TXT_TABLE_OVERFLOW_RIGHT,
                        NULL);
-        TXT_AddWidget(window,
-                      TXT_NewCheckBox("Show text with voices", &show_talk));
     }
 
     TXT_AddWidgets(window,
-               TXT_NewSeparator("Music"),
-               music_table = TXT_NewTable(2),
-               extra_table = TXT_NewTable(1),
-               NULL);
-
-    TXT_SetColumnWidths(music_table, 19, 15);
-
-    TXT_AddWidgets(music_table,
+                   TXT_NewSeparator("Music"),
                    TXT_NewLabel("Music"),
                    music_mode_control = TXT_NewDropdownList(&snd_musicmode,
                                                             musicmode_strings,
                                                             num_music_modes),
                    TXT_NewLabel("Music volume"),
                    TXT_NewSpinControl(&musicVolume, 0, 15),
+                   extra_table = TXT_NewTable(2),
+                   TXT_TABLE_OVERFLOW_RIGHT,
                    NULL);
 
+    TXT_SetColumnWidths(extra_table, 19, 15);
 
     TXT_SignalConnect(sfx_mode_control, "changed", UpdateSndDevices, NULL);
     TXT_SignalConnect(music_mode_control, "changed", UpdateSndDevices, NULL);
@@ -380,6 +385,8 @@ void BindSoundVariables(void)
     M_BindIntVariable("snd_cachesize",            &snd_cachesize);
     M_BindIntVariable("opl_io_port",              &opl_io_port);
 
+    M_BindIntVariable("snd_pitchshift",           &snd_pitchshift);
+
     if (gamemission == strife)
     {
         M_BindIntVariable("voice_volume",         &voiceVolume);
@@ -389,6 +396,10 @@ void BindSoundVariables(void)
     timidity_cfg_path = M_StringDuplicate("");
     gus_patch_path = M_StringDuplicate("");
 
+    // All versions of Heretic and Hexen did pitch-shifting.
+    // Most versions of Doom did not and Strife never did.
+    snd_pitchshift = gamemission == heretic || gamemission == hexen;
+
     // Default sound volumes - different games use different values.
 
     switch (gamemission)
diff --git a/src/setup/sound.h b/src/setup/sound.h
index c4a8ac6..a907f36 100644
--- a/src/setup/sound.h
+++ b/src/setup/sound.h
@@ -20,4 +20,6 @@
 void ConfigSound(void);
 void BindSoundVariables(void);
 
+extern char *snd_dmxoption;
+
 #endif /* #ifndef SETUP_SOUND_H */
diff --git a/src/setup/txt_joyaxis.c b/src/setup/txt_joyaxis.c
index 5e59b9f..df9deaa 100644
--- a/src/setup/txt_joyaxis.c
+++ b/src/setup/txt_joyaxis.c
@@ -367,7 +367,7 @@ void TXT_ConfigureJoystickAxis(txt_joystick_axis_t *joystick_axis,
     joystick_axis->joystick = SDL_JoystickOpen(joystick_index);
     if (joystick_axis->joystick == NULL)
     {
-        // TODO: OpenErrorWindow();
+        TXT_MessageBox(NULL, "Please configure a controller first!");
         return;
     }
 
@@ -461,7 +461,7 @@ static void TXT_JoystickAxisDrawer(TXT_UNCAST_ARG(joystick_axis))
 
     TXT_DrawString(buf);
 
-    for (i=strlen(buf); i<JOYSTICK_AXIS_WIDTH; ++i)
+    for (i = strlen(buf); i < joystick_axis->widget.w; ++i)
     {
         TXT_DrawString(" ");
     }
diff --git a/src/setup/txt_joybinput.c b/src/setup/txt_joybinput.c
index adbbc94..d0c3926 100644
--- a/src/setup/txt_joybinput.c
+++ b/src/setup/txt_joybinput.c
@@ -44,7 +44,7 @@ extern int joystick_physical_buttons[NUM_VIRTUAL_BUTTONS];
 // button that the user wants to use for firing. We do this so that
 // the menus work (the game code is hard coded to interpret
 // button #0 = select menu item, button #1 = go back to previous menu).
-static int *all_joystick_buttons[] =
+static int *all_joystick_buttons[NUM_VIRTUAL_BUTTONS] =
 {
     &joybfire,
     &joybuse,
@@ -56,6 +56,7 @@ static int *all_joystick_buttons[] =
     &joybnextweapon,
     &joybjump,
     &joybmenu,
+    &joybautomap,
 };
 
 static int PhysicalForVirtualButton(int vbutton)
@@ -71,7 +72,7 @@ static int PhysicalForVirtualButton(int vbutton)
 }
 
 // Get the virtual button number for the given variable, ie. the
-// variable's index in all_joystick_buttons[].
+// variable's index in all_joystick_buttons[NUM_VIRTUAL_BUTTONS].
 static int VirtualButtonForVariable(int *variable)
 {
     int i;
@@ -90,7 +91,7 @@ static int VirtualButtonForVariable(int *variable)
 
 // Rearrange joystick button configuration to be in "canonical" form:
 // each joyb* variable should have a value equal to its index in
-// all_joystick_buttons[] above.
+// all_joystick_buttons[NUM_VIRTUAL_BUTTONS] above.
 static void CanonicalizeButtons(void)
 {
     int new_mapping[NUM_VIRTUAL_BUTTONS];
@@ -189,7 +190,7 @@ static void PromptWindowClosed(TXT_UNCAST_ARG(widget), TXT_UNCAST_ARG(joystick))
 
 static void OpenErrorWindow(void)
 {
-    TXT_MessageBox(NULL, "Please configure a joystick first!");
+    TXT_MessageBox(NULL, "Please configure a controller first!");
 }
 
 static void OpenPromptWindow(txt_joystick_input_t *joystick_input)
@@ -218,7 +219,7 @@ static void OpenPromptWindow(txt_joystick_input_t *joystick_input)
 
     // Open the prompt window
 
-    window = TXT_MessageBox(NULL, "Press the new joystick button...");
+    window = TXT_MessageBox(NULL, "Press the new button on the controller...");
 
     TXT_SDL_SetEventCallback(EventCallback, joystick_input);
     TXT_SignalConnect(window, "closed", PromptWindowClosed, joystick);
@@ -296,7 +297,8 @@ static int TXT_JoystickInputKeyPress(TXT_UNCAST_ARG(joystick_input), int key)
     return 0;
 }
 
-static void TXT_JoystickInputMousePress(TXT_UNCAST_ARG(widget), int x, int y, int b)
+static void TXT_JoystickInputMousePress(TXT_UNCAST_ARG(widget),
+                                        int x, int y, int b)
 {
     TXT_CAST_ARG(txt_joystick_input_t, widget);
 
diff --git a/src/strife.appdata.xml.in b/src/strife.appdata.xml.in
index ff54553..7be869c 100644
--- a/src/strife.appdata.xml.in
+++ b/src/strife.appdata.xml.in
@@ -26,19 +26,19 @@
   </description>
   <screenshots>
     <screenshot type="default">
-      <image>http://www.chocolate-doom.org/wiki/images/b/b2/GNOME_Strife_Rowan.png</image>
+      <image>https://www.chocolate-doom.org/wiki/images/b/b2/GNOME_Strife_Rowan.png</image>
       <caption>Talking to Rowan</caption>
     </screenshot>
     <screenshot>
-      <image>http://www.chocolate-doom.org/wiki/images/1/1f/GNOME_Strife_Town.png</image>
+      <image>https://www.chocolate-doom.org/wiki/images/1/1f/GNOME_Strife_Town.png</image>
       <caption>The Town</caption>
     </screenshot>
     <screenshot>
-      <image>http://www.chocolate-doom.org/wiki/images/8/8a/GNOME_Strife_Opening.png</image>
+      <image>https://www.chocolate-doom.org/wiki/images/8/8a/GNOME_Strife_Opening.png</image>
       <caption>Opening Cinematic</caption>
     </screenshot>
     <screenshot>
-      <image>http://www.chocolate-doom.org/wiki/images/c/c4/GNOME_Strife_Sewage.png</image>
+      <image>https://www.chocolate-doom.org/wiki/images/c/c4/GNOME_Strife_Sewage.png</image>
       <caption>In the sewage</caption>
     </screenshot>
   </screenshots>
diff --git a/src/strife/am_map.c b/src/strife/am_map.c
index 148fa34..22913c3 100644
--- a/src/strife/am_map.c
+++ b/src/strife/am_map.c
@@ -32,6 +32,7 @@
 #include "m_cheat.h"
 #include "m_controls.h"
 #include "i_system.h"
+#include "i_timer.h"
 
 // Needs access to LFB.
 #include "v_video.h"
@@ -320,11 +321,10 @@ void AM_restoreScaleAndLoc(void)
 //
 void AM_addMark(void)
 {
-    markpoints[markpointnum].x = m_x + m_w/2;
-    markpoints[markpointnum].y = m_y + m_h/2;
+    markpoints[markpointnum].x = plr->mo->x; // 20160306 [STRIFE]: use player position
+    markpoints[markpointnum].y = plr->mo->y;
     //markpointnum = (markpointnum + 1) % AM_NUMMARKPOINTS;
     ++markpointnum; // haleyjd 20141101: [STRIFE] does not wrap around
-
 }
 
 //
@@ -580,11 +580,33 @@ AM_Responder
 
     int rc;
     static int bigstate=0;
+    static int joywait = 0;
     static char buffer[20];
     int key;
 
     rc = false;
 
+    if (ev->type == ev_joystick && joybautomap >= 0
+        && (ev->data1 & (1 << joybautomap)) != 0 && joywait < I_GetTime())
+    {
+        joywait = I_GetTime() + 5;
+
+        if (!automapactive)
+        {
+            AM_Start ();
+            viewactive = false;
+        }
+        else
+        {
+            bigstate = 0;
+            viewactive = true;
+            AM_Stop ();
+        }
+
+        return true;
+    }
+
+
     if (!automapactive)
     {
 	if (ev->type == ev_keydown && ev->data1 == key_map_toggle)
diff --git a/src/strife/d_main.c b/src/strife/d_main.c
index cc1a902..240cc06 100644
--- a/src/strife/d_main.c
+++ b/src/strife/d_main.c
@@ -40,6 +40,7 @@
 #include "w_main.h"
 #include "w_wad.h"
 #include "s_sound.h"
+#include "v_diskicon.h"
 #include "v_video.h"
 
 #include "f_finale.h"
@@ -141,6 +142,7 @@ char		wadfile[1024];          // primary wad file
 char		mapdir[1024];           // directory of development maps
 
 int             show_endoom = 1;
+int             show_diskicon = 1;
 int             graphical_startup = 1;
 
 // If true, startup has completed and the main game loop has started.
@@ -441,6 +443,7 @@ void D_BindVariables(void)
     M_BindIntVariable("vanilla_savegame_limit", &vanilla_savegame_limit);
     M_BindIntVariable("vanilla_demo_limit",     &vanilla_demo_limit);
     M_BindIntVariable("show_endoom",            &show_endoom);
+    M_BindIntVariable("show_diskicon",          &show_diskicon);
     M_BindIntVariable("graphical_startup",      &graphical_startup);
 
     M_BindStringVariable("back_flat",           &back_flat);
@@ -509,7 +512,10 @@ void D_DoomLoop (void)
         I_InitGraphics();
     }
 
-    I_EnableLoadingDisk();
+    if (show_diskicon)
+    {
+        V_EnableLoadingDisk("STDISK", SCREENWIDTH - LOADING_DISK_W, 3);
+    }
     I_SetGrabMouseCallback(D_GrabMouseCallback);
 
     V_RestoreBuffer();
@@ -828,49 +834,48 @@ void D_IdentifyVersion(void)
     // Load voices.wad 
     if(isregistered)
     {
-        char *name = D_FindWADByName("voices.wad");
+        char *name = NULL;
+        int p;
 
-        if(!name) // not found?
+        // If -iwad was used, check and see if voices.wad exists on the same
+        // filepath.
+        if((p = M_CheckParm("-iwad")) && p < myargc - 1)
         {
-            int p;
+            char   *iwad     = myargv[p + 1];
+            size_t  len      = strlen(iwad) + 1;
+            char   *iwadpath = Z_Malloc(len, PU_STATIC, NULL);
+            char   *voiceswad;
 
-            // haleyjd STRIFE-FIXME: Temporary?
-            // If -iwad was used, check and see if voices.wad exists on the
-            // same filepath.
-            if((p = M_CheckParm("-iwad")) && p < myargc - 1)
-            {
-                char   *iwad     = myargv[p + 1];
-                size_t  len      = strlen(iwad) + 1;
-                char   *iwadpath = Z_Malloc(len, PU_STATIC, NULL);
-                char   *voiceswad;
-                
-                // extract base path of IWAD parameter
-                M_GetFilePath(iwad, iwadpath, len);
-                
-                // concatenate with /voices.wad
-                voiceswad = M_SafeFilePath(iwadpath, "voices.wad");
-                Z_Free(iwadpath);
-
-                if(!M_FileExists(voiceswad))
-                {
-                    disable_voices = 1;
-                    Z_Free(voiceswad);
-                }
-                else
-                    name = voiceswad; // STRIFE-FIXME: memory leak!!
-            }
+            // extract base path of IWAD parameter
+            M_GetFilePath(iwad, iwadpath, len);
+
+            // concatenate with /voices.wad
+            voiceswad = M_SafeFilePath(iwadpath, "voices.wad");
+            Z_Free(iwadpath);
+
+            if(!M_FileExists(voiceswad))
+                Z_Free(voiceswad);
             else
-                disable_voices = 1;
+                name = voiceswad; // STRIFE-FIXME: memory leak!!
         }
 
-        if(disable_voices) // voices disabled?
+        // not found? try global search paths
+        if(!name)
+            name = D_FindWADByName("voices.wad");
+
+        // still not found? too bad.
+        if(!name)
         {
+            disable_voices = 1;
+
             if(devparm)
                  printf("Voices disabled\n");
-            return;
         }
-
-        D_AddFile(name);
+        else
+        {
+            // add it.
+            D_AddFile(name);
+        }
     }
 }
 
@@ -1629,7 +1634,7 @@ void D_DoomMain (void)
 
         if (D_AddFile (file))
         {
-            M_StringCopy(demolumpname, lumpinfo[numlumps - 1].name,
+            M_StringCopy(demolumpname, lumpinfo[numlumps - 1]->name,
                          sizeof(demolumpname));
         }
         else
diff --git a/src/strife/g_game.c b/src/strife/g_game.c
index c12e856..e238b0c 100644
--- a/src/strife/g_game.c
+++ b/src/strife/g_game.c
@@ -2328,7 +2328,8 @@ void G_DoPlayDemo (void)
                         "\n"
                         "*** You may need to upgrade your version "
                             "of Strife to v1.1 or later. ***\n"
-                        "    See: http://doomworld.com/files/patches.shtml\n"
+                        "    See: https://www.doomworld.com/classicdoom"
+                                  "/info/patches.php\n"
                         "    This appears to be %s.";
 
         I_Error(message, demoversion, STRIFE_VERSION,
@@ -2379,7 +2380,8 @@ void G_DoPlayDemo (void)
 void G_TimeDemo (char* name) 
 {
     //!
-    // @vanilla 
+    // @category video
+    // @vanilla
     //
     // Disable rendering the screen entirely.
     //
diff --git a/src/strife/m_saves.c b/src/strife/m_saves.c
index e4d64e5..8702a95 100644
--- a/src/strife/m_saves.c
+++ b/src/strife/m_saves.c
@@ -1,528 +1,528 @@
-//
-// Copyright(C) 1993-1996 Id Software, Inc.
-// Copyright(C) 2010 James Haley, Samuel Villarreal
-//
-// This program is free software; you can redistribute it and/or
-// modify it under the terms of the GNU General Public License
-// as published by the Free Software Foundation; either version 2
-// of the License, or (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-//
-// DESCRIPTION:
-//
-// [STRIFE] New Module
-//
-// Strife Hub Saving Code
-//
-
-// For GNU C and POSIX targets, dirent.h should be available. Otherwise, for
-// Visual C++, we need to include the win_opendir module.
-#if defined(_MSC_VER)
-#include <win_opendir.h>
-#elif defined(__GNUC__) || defined(POSIX)
-#include <dirent.h>
-#else
-#error Need an include for dirent.h!
-#endif
-#include <stdarg.h>
-#include <stdlib.h>
-#include <string.h>
-
-#include "z_zone.h"
-#include "i_system.h"
-#include "d_player.h"
-#include "deh_str.h"
-#include "doomstat.h"
-#include "m_misc.h"
-#include "m_saves.h"
-#include "p_dialog.h"
-
-//
-// File Paths
-//
-// Strife maintains multiple file paths related to savegames.
-//
-char *savepath;     // The actual path of the selected saveslot
-char *savepathtemp; // The path of the temporary saveslot (strfsav6.ssg)
-char *loadpath;     // Path used while loading the game
-
-char character_name[CHARACTER_NAME_LEN]; // Name of "character" for saveslot
-
-//
-// ClearTmp
-//
-// Clear the temporary save directory
-//
-void ClearTmp(void)
-{
-    DIR *sp2dir = NULL;
-    struct dirent *f = NULL;
-
-    if(savepathtemp == NULL)
-        I_Error("you fucked up savedir man!");
-
-    if(!(sp2dir = opendir(savepathtemp)))
-        I_Error("ClearTmp: Couldn't open dir %s", savepathtemp);
-
-    while((f = readdir(sp2dir)))
-    {
-        char *filepath = NULL;
-
-        // haleyjd: skip "." and ".." without assuming they're the
-        // first two entries like the original code did.
-        if(!strcmp(f->d_name, ".") || !strcmp(f->d_name, ".."))
-            continue;
-
-        // haleyjd: use M_SafeFilePath, not sprintf
-        filepath = M_SafeFilePath(savepathtemp, f->d_name);
-        remove(filepath);
-
-        Z_Free(filepath);
-    }
-
-    closedir(sp2dir);
-}
-
-//
-// ClearSlot
-//
-// Clear a single save slot folder
-//
-void ClearSlot(void)
-{
-    DIR *spdir = NULL;
-    struct dirent *f = NULL;
-
-    if(savepath == NULL)
-        I_Error("userdir is fucked up man!");
-
-    if(!(spdir = opendir(savepath)))
-        I_Error("ClearSlot: Couldn't open dir %s", savepath);
-
-    while((f = readdir(spdir)))
-    {
-        char *filepath = NULL;
-
-        if(!strcmp(f->d_name, ".") || !strcmp(f->d_name, ".."))
-            continue;
-        
-        // haleyjd: use M_SafeFilePath, not sprintf
-        filepath = M_SafeFilePath(savepath, f->d_name);
-        remove(filepath);
-
-        Z_Free(filepath);
-    }
-
-    closedir(spdir);
-}
-
-//
-// FromCurr
-//
-// Copying files from savepathtemp to savepath
-//
-void FromCurr(void)
-{
-    DIR *sp2dir = NULL;
-    struct dirent *f = NULL;
-
-    if(!(sp2dir = opendir(savepathtemp)))
-        I_Error("FromCurr: Couldn't open dir %s", savepathtemp);
-
-    while((f = readdir(sp2dir)))
-    {
-        byte *filebuffer  = NULL;
-        int   filelen     = 0;
-        char *srcfilename = NULL;
-        char *dstfilename = NULL;
-
-        // haleyjd: skip "." and ".." without assuming they're the
-        // first two entries like the original code did.
-        if(!strcmp(f->d_name, ".") || !strcmp(f->d_name, ".."))
-            continue;
-
-        // haleyjd: use M_SafeFilePath, NOT sprintf.
-        srcfilename = M_SafeFilePath(savepathtemp, f->d_name);
-        dstfilename = M_SafeFilePath(savepath,     f->d_name);
-
-        filelen = M_ReadFile(srcfilename, &filebuffer);
-        M_WriteFile(dstfilename, filebuffer, filelen);
-
-        Z_Free(filebuffer);
-        Z_Free(srcfilename);
-        Z_Free(dstfilename);
-    }
-
-    closedir(sp2dir);
-}
-
-//
-// ToCurr
-//
-// Copying files from savepath to savepathtemp
-//
-void ToCurr(void)
-{
-    DIR *spdir = NULL;
-    struct dirent *f = NULL;
-
-    ClearTmp();
-
-    // BUG: Rogue copypasta'd this error message, which is why we don't know
-    // the real original name of this function.
-    if(!(spdir = opendir(savepath)))
-        I_Error("ClearSlot: Couldn't open dir %s", savepath);
-
-    while((f = readdir(spdir)))
-    {
-        byte *filebuffer  = NULL;
-        int   filelen     = 0;
-        char *srcfilename = NULL;
-        char *dstfilename = NULL;
-
-        if(!strcmp(f->d_name, ".") || !strcmp(f->d_name, ".."))
-            continue;
-
-        // haleyjd: use M_SafeFilePath, NOT sprintf.
-        srcfilename = M_SafeFilePath(savepath,     f->d_name);
-        dstfilename = M_SafeFilePath(savepathtemp, f->d_name);
-
-        filelen = M_ReadFile(srcfilename, &filebuffer);
-        M_WriteFile(dstfilename, filebuffer, filelen);
-
-        Z_Free(filebuffer);
-        Z_Free(srcfilename);
-        Z_Free(dstfilename);
-    }
-
-    closedir(spdir);
-}
-
-//
-// M_SaveMoveMapToHere
-//
-// Moves a map to the "HERE" save.
-//
-void M_SaveMoveMapToHere(void)
-{
-    char *mapsave  = NULL;
-    char *heresave = NULL;
-    char tmpnum[33];
-
-    // haleyjd: no itoa available...
-    M_snprintf(tmpnum, sizeof(tmpnum), "%d", gamemap);
-
-    // haleyjd: use M_SafeFilePath, not sprintf
-    mapsave  = M_SafeFilePath(savepath, tmpnum);
-    heresave = M_SafeFilePath(savepath, "here");
-
-    // haleyjd: use M_FileExists, not access
-    if(M_FileExists(mapsave))
-    {
-        remove(heresave);
-        rename(mapsave, heresave);
-    }
-
-    Z_Free(mapsave);
-    Z_Free(heresave);
-}
-
-//
-// M_SaveMoveHereToMap
-//
-// Moves the "HERE" save to a map.
-//
-void M_SaveMoveHereToMap(void)
-{
-    char *mapsave  = NULL;
-    char *heresave = NULL;
-    char tmpnum[33];
-
-    // haleyjd: no itoa available...
-    M_snprintf(tmpnum, sizeof(tmpnum), "%d", gamemap);
-
-    mapsave  = M_SafeFilePath(savepathtemp, tmpnum);
-    heresave = M_SafeFilePath(savepathtemp, "here");
-
-    if(M_FileExists(heresave))
-    {
-        remove(mapsave);
-        rename(heresave, mapsave);
-    }
-
-    Z_Free(mapsave);
-    Z_Free(heresave);
-}
-
-//
-// M_SaveMisObj
-//
-// Writes the mission objective into the MIS_OBJ file.
-//
-boolean M_SaveMisObj(const char *path)
-{
-    boolean result;
-    char *destpath = NULL;
-
-    // haleyjd 20110210: use M_SafeFilePath, not sprintf
-    destpath = M_SafeFilePath(path, "mis_obj");
-    result   = M_WriteFile(destpath, mission_objective, OBJECTIVE_LEN);
-
-    Z_Free(destpath);
-    return result;
-}
-
-//
-// M_ReadMisObj
-//
-// Reads the mission objective from the MIS_OBJ file.
-//
-void M_ReadMisObj(void)
-{
-    FILE *f = NULL;
-    char *srcpath = NULL;
-
-    // haleyjd: use M_SafeFilePath, not sprintf
-    srcpath = M_SafeFilePath(savepathtemp, "mis_obj");
-
-    if((f = fopen(srcpath, "rb")))
-    {
-        fread(mission_objective, 1, OBJECTIVE_LEN, f);
-        fclose(f);
-    }
-
-    Z_Free(srcpath);
-}
-
-//=============================================================================
-//
-// Original Routines
-//
-// haleyjd - None of the below code is derived from Strife itself, but has been
-// adapted or created in order to provide secure, portable filepath handling
-// for the purposes of savegame support. This is partially needed to allow for
-// differences in Choco due to it being multiplatform. The rest exists because
-// I cannot stand programming in an impoverished ANSI C environment that
-// calls sprintf on fixed-size buffers. :P
-//
-
-//
-// M_Calloc
-//
-// haleyjd 20110210 - original routine
-// Because Choco doesn't have Z_Calloc O_o
-//
-void *M_Calloc(size_t n1, size_t n2)
-{
-    return (n1 *= n2) ? memset(Z_Malloc(n1, PU_STATIC, NULL), 0, n1) : NULL;
-}
-
-//
-// M_StringAlloc
-//
-// haleyjd: This routine takes any number of strings and a number of extra
-// characters, calculates their combined length, and calls Z_Alloca to create
-// a temporary buffer of that size. This is extremely useful for allocation of
-// file paths, and is used extensively in d_main.c.  The pointer returned is
-// to a temporary Z_Alloca buffer, which lives until the next main loop
-// iteration, so don't cache it. Note that this idiom is not possible with the
-// normal non-standard alloca function, which allocates stack space.
-//
-// [STRIFE] - haleyjd 20110210
-// This routine is taken from the Eternity Engine and adapted to do without
-// Z_Alloca. I need secure string concatenation for filepath handling. The
-// only difference from use in EE is that the pointer returned in *str must
-// be manually freed.
-//
-int M_StringAlloc(char **str, int numstrs, size_t extra, const char *str1, ...)
-{
-    va_list args;
-    size_t len = extra;
-
-    if(numstrs < 1)
-        I_Error("M_StringAlloc: invalid input\n");
-
-    len += strlen(str1);
-
-    --numstrs;
-
-    if(numstrs != 0)
-    {   
-        va_start(args, str1);
-
-        while(numstrs != 0)
-        {
-            const char *argstr = va_arg(args, const char *);
-
-            len += strlen(argstr);
-
-            --numstrs;
-        }
-
-        va_end(args);
-    }
-
-    ++len;
-
-    *str = (char *)(M_Calloc(1, len));
-
-    return len;
-}
-
-//
-// M_NormalizeSlashes
-//
-// Remove trailing slashes, translate backslashes to slashes
-// The string to normalize is passed and returned in str
-//
-// killough 11/98: rewritten
-//
-// [STRIFE] - haleyjd 20110210: Borrowed from Eternity and adapted to respect 
-// the DIR_SEPARATOR define used by Choco Doom. This routine originated in
-// BOOM.
-//
-void M_NormalizeSlashes(char *str)
-{
-    char *p;
-   
-    // Convert all slashes/backslashes to DIR_SEPARATOR
-    for(p = str; *p; p++)
-    {
-        if((*p == '/' || *p == '\\') && *p != DIR_SEPARATOR)
-            *p = DIR_SEPARATOR;
-    }
-
-    // Remove trailing slashes
-    while(p > str && *--p == DIR_SEPARATOR)
-        *p = 0;
-
-    // Collapse multiple slashes
-    for(p = str; (*str++ = *p); )
-        if(*p++ == DIR_SEPARATOR)
-            while(*p == DIR_SEPARATOR)
-                p++;
-}
-
-//
-// M_SafeFilePath
-//
-// haleyjd 20110210 - original routine.
-// This routine performs safe, portable concatenation of a base file path
-// with another path component or file name. The returned string is Z_Malloc'd
-// and should be freed when it has exhausted its usefulness.
-//
-char *M_SafeFilePath(const char *basepath, const char *newcomponent)
-{
-    int   newstrlen = 0;
-    char *newstr = NULL;
-
-    if (!strcmp(basepath, ""))
-    {
-        basepath = ".";
-    }
-
-    // Always throw in a slash. M_NormalizeSlashes will remove it in the case
-    // that either basepath or newcomponent includes a redundant slash at the
-    // end or beginning respectively.
-    newstrlen = M_StringAlloc(&newstr, 3, 1, basepath, "/", newcomponent);
-    M_snprintf(newstr, newstrlen, "%s/%s", basepath, newcomponent);
-    M_NormalizeSlashes(newstr);
-
-    return newstr;
-}
-
-//
-// M_CreateSaveDirs
-//
-// haleyjd 20110210: Vanilla Strife went tits-up if it didn't have the full set
-// of save folders which were created externally by the installer. fraggle says
-// that's no good for Choco purposes, and I agree, so this routine will create
-// the full set of folders under the configured savegamedir.
-//
-void M_CreateSaveDirs(const char *savedir)
-{
-    int i;
-
-    for(i = 0; i < 7; i++)
-    {
-        char *compositedir;
-
-        // compose the full path by concatenating with savedir
-        compositedir = M_SafeFilePath(savedir, M_MakeStrifeSaveDir(i, ""));
-
-        M_MakeDirectory(compositedir);
-
-        Z_Free(compositedir);
-    }
-}
-
-//
-// M_MakeStrifeSaveDir
-//
-// haleyjd 20110211: Convenience routine
-//
-char *M_MakeStrifeSaveDir(int slotnum, const char *extra)
-{
-    static char tmpbuffer[32];
-
-    M_snprintf(tmpbuffer, sizeof(tmpbuffer),
-               "strfsav%d.ssg%s", slotnum, extra);
-
-    return tmpbuffer;
-}
-
-// 
-// M_GetFilePath
-//
-// haleyjd: STRIFE-FIXME: Temporary?
-// Code borrowed from Eternity, and modified to return separator char
-//
-char M_GetFilePath(const char *fn, char *dest, size_t len)
-{
-    boolean found_slash = false;
-    char *p;
-    char sepchar = '\0';
-
-    memset(dest, 0, len);
-
-    p = dest + len - 1;
-
-    M_StringCopy(dest, fn, len);
-
-    while(p >= dest)
-    {
-        if(*p == '/' || *p == '\\')
-        {
-            sepchar = *p;
-            found_slash = true; // mark that the path ended with a slash
-            *p = '\0';
-            break;
-        }
-        *p = '\0';
-        p--;
-    }
-
-    // haleyjd: in the case that no slash was ever found, yet the
-    // path string is empty, we are dealing with a file local to the
-    // working directory. The proper path to return for such a string is
-    // not "", but ".", since the format strings add a slash now. When
-    // the string is empty but a slash WAS found, we really do want to
-    // return the empty string, since the path is relative to the root.
-    if(!found_slash && *dest == '\0')
-        *dest = '.';
-
-    // if a separator is not found, default to forward, because Windows 
-    // supports that too.
-    if(sepchar == '\0') 
-        sepchar = '/';
-
-    return sepchar;
-}
-
-// EOF
-
-
+//
+// Copyright(C) 1993-1996 Id Software, Inc.
+// Copyright(C) 2010 James Haley, Samuel Villarreal
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// DESCRIPTION:
+//
+// [STRIFE] New Module
+//
+// Strife Hub Saving Code
+//
+
+// For GNU C and POSIX targets, dirent.h should be available. Otherwise, for
+// Visual C++, we need to include the win_opendir module.
+#if defined(_MSC_VER)
+#include <win_opendir.h>
+#elif defined(__GNUC__) || defined(POSIX)
+#include <dirent.h>
+#else
+#error Need an include for dirent.h!
+#endif
+#include <stdarg.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "z_zone.h"
+#include "i_system.h"
+#include "d_player.h"
+#include "deh_str.h"
+#include "doomstat.h"
+#include "m_misc.h"
+#include "m_saves.h"
+#include "p_dialog.h"
+
+//
+// File Paths
+//
+// Strife maintains multiple file paths related to savegames.
+//
+char *savepath;     // The actual path of the selected saveslot
+char *savepathtemp; // The path of the temporary saveslot (strfsav6.ssg)
+char *loadpath;     // Path used while loading the game
+
+char character_name[CHARACTER_NAME_LEN]; // Name of "character" for saveslot
+
+//
+// ClearTmp
+//
+// Clear the temporary save directory
+//
+void ClearTmp(void)
+{
+    DIR *sp2dir = NULL;
+    struct dirent *f = NULL;
+
+    if(savepathtemp == NULL)
+        I_Error("you fucked up savedir man!");
+
+    if(!(sp2dir = opendir(savepathtemp)))
+        I_Error("ClearTmp: Couldn't open dir %s", savepathtemp);
+
+    while((f = readdir(sp2dir)))
+    {
+        char *filepath = NULL;
+
+        // haleyjd: skip "." and ".." without assuming they're the
+        // first two entries like the original code did.
+        if(!strcmp(f->d_name, ".") || !strcmp(f->d_name, ".."))
+            continue;
+
+        // haleyjd: use M_SafeFilePath, not sprintf
+        filepath = M_SafeFilePath(savepathtemp, f->d_name);
+        remove(filepath);
+
+        Z_Free(filepath);
+    }
+
+    closedir(sp2dir);
+}
+
+//
+// ClearSlot
+//
+// Clear a single save slot folder
+//
+void ClearSlot(void)
+{
+    DIR *spdir = NULL;
+    struct dirent *f = NULL;
+
+    if(savepath == NULL)
+        I_Error("userdir is fucked up man!");
+
+    if(!(spdir = opendir(savepath)))
+        I_Error("ClearSlot: Couldn't open dir %s", savepath);
+
+    while((f = readdir(spdir)))
+    {
+        char *filepath = NULL;
+
+        if(!strcmp(f->d_name, ".") || !strcmp(f->d_name, ".."))
+            continue;
+        
+        // haleyjd: use M_SafeFilePath, not sprintf
+        filepath = M_SafeFilePath(savepath, f->d_name);
+        remove(filepath);
+
+        Z_Free(filepath);
+    }
+
+    closedir(spdir);
+}
+
+//
+// FromCurr
+//
+// Copying files from savepathtemp to savepath
+//
+void FromCurr(void)
+{
+    DIR *sp2dir = NULL;
+    struct dirent *f = NULL;
+
+    if(!(sp2dir = opendir(savepathtemp)))
+        I_Error("FromCurr: Couldn't open dir %s", savepathtemp);
+
+    while((f = readdir(sp2dir)))
+    {
+        byte *filebuffer  = NULL;
+        int   filelen     = 0;
+        char *srcfilename = NULL;
+        char *dstfilename = NULL;
+
+        // haleyjd: skip "." and ".." without assuming they're the
+        // first two entries like the original code did.
+        if(!strcmp(f->d_name, ".") || !strcmp(f->d_name, ".."))
+            continue;
+
+        // haleyjd: use M_SafeFilePath, NOT sprintf.
+        srcfilename = M_SafeFilePath(savepathtemp, f->d_name);
+        dstfilename = M_SafeFilePath(savepath,     f->d_name);
+
+        filelen = M_ReadFile(srcfilename, &filebuffer);
+        M_WriteFile(dstfilename, filebuffer, filelen);
+
+        Z_Free(filebuffer);
+        Z_Free(srcfilename);
+        Z_Free(dstfilename);
+    }
+
+    closedir(sp2dir);
+}
+
+//
+// ToCurr
+//
+// Copying files from savepath to savepathtemp
+//
+void ToCurr(void)
+{
+    DIR *spdir = NULL;
+    struct dirent *f = NULL;
+
+    ClearTmp();
+
+    // BUG: Rogue copypasta'd this error message, which is why we don't know
+    // the real original name of this function.
+    if(!(spdir = opendir(savepath)))
+        I_Error("ClearSlot: Couldn't open dir %s", savepath);
+
+    while((f = readdir(spdir)))
+    {
+        byte *filebuffer  = NULL;
+        int   filelen     = 0;
+        char *srcfilename = NULL;
+        char *dstfilename = NULL;
+
+        if(!strcmp(f->d_name, ".") || !strcmp(f->d_name, ".."))
+            continue;
+
+        // haleyjd: use M_SafeFilePath, NOT sprintf.
+        srcfilename = M_SafeFilePath(savepath,     f->d_name);
+        dstfilename = M_SafeFilePath(savepathtemp, f->d_name);
+
+        filelen = M_ReadFile(srcfilename, &filebuffer);
+        M_WriteFile(dstfilename, filebuffer, filelen);
+
+        Z_Free(filebuffer);
+        Z_Free(srcfilename);
+        Z_Free(dstfilename);
+    }
+
+    closedir(spdir);
+}
+
+//
+// M_SaveMoveMapToHere
+//
+// Moves a map to the "HERE" save.
+//
+void M_SaveMoveMapToHere(void)
+{
+    char *mapsave  = NULL;
+    char *heresave = NULL;
+    char tmpnum[33];
+
+    // haleyjd: no itoa available...
+    M_snprintf(tmpnum, sizeof(tmpnum), "%d", gamemap);
+
+    // haleyjd: use M_SafeFilePath, not sprintf
+    mapsave  = M_SafeFilePath(savepath, tmpnum);
+    heresave = M_SafeFilePath(savepath, "here");
+
+    // haleyjd: use M_FileExists, not access
+    if(M_FileExists(mapsave))
+    {
+        remove(heresave);
+        rename(mapsave, heresave);
+    }
+
+    Z_Free(mapsave);
+    Z_Free(heresave);
+}
+
+//
+// M_SaveMoveHereToMap
+//
+// Moves the "HERE" save to a map.
+//
+void M_SaveMoveHereToMap(void)
+{
+    char *mapsave  = NULL;
+    char *heresave = NULL;
+    char tmpnum[33];
+
+    // haleyjd: no itoa available...
+    M_snprintf(tmpnum, sizeof(tmpnum), "%d", gamemap);
+
+    mapsave  = M_SafeFilePath(savepathtemp, tmpnum);
+    heresave = M_SafeFilePath(savepathtemp, "here");
+
+    if(M_FileExists(heresave))
+    {
+        remove(mapsave);
+        rename(heresave, mapsave);
+    }
+
+    Z_Free(mapsave);
+    Z_Free(heresave);
+}
+
+//
+// M_SaveMisObj
+//
+// Writes the mission objective into the MIS_OBJ file.
+//
+boolean M_SaveMisObj(const char *path)
+{
+    boolean result;
+    char *destpath = NULL;
+
+    // haleyjd 20110210: use M_SafeFilePath, not sprintf
+    destpath = M_SafeFilePath(path, "mis_obj");
+    result   = M_WriteFile(destpath, mission_objective, OBJECTIVE_LEN);
+
+    Z_Free(destpath);
+    return result;
+}
+
+//
+// M_ReadMisObj
+//
+// Reads the mission objective from the MIS_OBJ file.
+//
+void M_ReadMisObj(void)
+{
+    FILE *f = NULL;
+    char *srcpath = NULL;
+
+    // haleyjd: use M_SafeFilePath, not sprintf
+    srcpath = M_SafeFilePath(savepathtemp, "mis_obj");
+
+    if((f = fopen(srcpath, "rb")))
+    {
+        fread(mission_objective, 1, OBJECTIVE_LEN, f);
+        fclose(f);
+    }
+
+    Z_Free(srcpath);
+}
+
+//=============================================================================
+//
+// Original Routines
+//
+// haleyjd - None of the below code is derived from Strife itself, but has been
+// adapted or created in order to provide secure, portable filepath handling
+// for the purposes of savegame support. This is partially needed to allow for
+// differences in Choco due to it being multiplatform. The rest exists because
+// I cannot stand programming in an impoverished ANSI C environment that
+// calls sprintf on fixed-size buffers. :P
+//
+
+//
+// M_Calloc
+//
+// haleyjd 20110210 - original routine
+// Because Choco doesn't have Z_Calloc O_o
+//
+void *M_Calloc(size_t n1, size_t n2)
+{
+    return (n1 *= n2) ? memset(Z_Malloc(n1, PU_STATIC, NULL), 0, n1) : NULL;
+}
+
+//
+// M_StringAlloc
+//
+// haleyjd: This routine takes any number of strings and a number of extra
+// characters, calculates their combined length, and calls Z_Alloca to create
+// a temporary buffer of that size. This is extremely useful for allocation of
+// file paths, and is used extensively in d_main.c.  The pointer returned is
+// to a temporary Z_Alloca buffer, which lives until the next main loop
+// iteration, so don't cache it. Note that this idiom is not possible with the
+// normal non-standard alloca function, which allocates stack space.
+//
+// [STRIFE] - haleyjd 20110210
+// This routine is taken from the Eternity Engine and adapted to do without
+// Z_Alloca. I need secure string concatenation for filepath handling. The
+// only difference from use in EE is that the pointer returned in *str must
+// be manually freed.
+//
+int M_StringAlloc(char **str, int numstrs, size_t extra, const char *str1, ...)
+{
+    va_list args;
+    size_t len = extra;
+
+    if(numstrs < 1)
+        I_Error("M_StringAlloc: invalid input\n");
+
+    len += strlen(str1);
+
+    --numstrs;
+
+    if(numstrs != 0)
+    {   
+        va_start(args, str1);
+
+        while(numstrs != 0)
+        {
+            const char *argstr = va_arg(args, const char *);
+
+            len += strlen(argstr);
+
+            --numstrs;
+        }
+
+        va_end(args);
+    }
+
+    ++len;
+
+    *str = (char *)(M_Calloc(1, len));
+
+    return len;
+}
+
+//
+// M_NormalizeSlashes
+//
+// Remove trailing slashes, translate backslashes to slashes
+// The string to normalize is passed and returned in str
+//
+// killough 11/98: rewritten
+//
+// [STRIFE] - haleyjd 20110210: Borrowed from Eternity and adapted to respect 
+// the DIR_SEPARATOR define used by Choco Doom. This routine originated in
+// BOOM.
+//
+void M_NormalizeSlashes(char *str)
+{
+    char *p;
+   
+    // Convert all slashes/backslashes to DIR_SEPARATOR
+    for(p = str; *p; p++)
+    {
+        if((*p == '/' || *p == '\\') && *p != DIR_SEPARATOR)
+            *p = DIR_SEPARATOR;
+    }
+
+    // Remove trailing slashes
+    while(p > str && *--p == DIR_SEPARATOR)
+        *p = 0;
+
+    // Collapse multiple slashes
+    for(p = str; (*str++ = *p); )
+        if(*p++ == DIR_SEPARATOR)
+            while(*p == DIR_SEPARATOR)
+                p++;
+}
+
+//
+// M_SafeFilePath
+//
+// haleyjd 20110210 - original routine.
+// This routine performs safe, portable concatenation of a base file path
+// with another path component or file name. The returned string is Z_Malloc'd
+// and should be freed when it has exhausted its usefulness.
+//
+char *M_SafeFilePath(const char *basepath, const char *newcomponent)
+{
+    int   newstrlen = 0;
+    char *newstr = NULL;
+
+    if (!strcmp(basepath, ""))
+    {
+        basepath = ".";
+    }
+
+    // Always throw in a slash. M_NormalizeSlashes will remove it in the case
+    // that either basepath or newcomponent includes a redundant slash at the
+    // end or beginning respectively.
+    newstrlen = M_StringAlloc(&newstr, 3, 1, basepath, "/", newcomponent);
+    M_snprintf(newstr, newstrlen, "%s/%s", basepath, newcomponent);
+    M_NormalizeSlashes(newstr);
+
+    return newstr;
+}
+
+//
+// M_CreateSaveDirs
+//
+// haleyjd 20110210: Vanilla Strife went tits-up if it didn't have the full set
+// of save folders which were created externally by the installer. fraggle says
+// that's no good for Choco purposes, and I agree, so this routine will create
+// the full set of folders under the configured savegamedir.
+//
+void M_CreateSaveDirs(const char *savedir)
+{
+    int i;
+
+    for(i = 0; i < 7; i++)
+    {
+        char *compositedir;
+
+        // compose the full path by concatenating with savedir
+        compositedir = M_SafeFilePath(savedir, M_MakeStrifeSaveDir(i, ""));
+
+        M_MakeDirectory(compositedir);
+
+        Z_Free(compositedir);
+    }
+}
+
+//
+// M_MakeStrifeSaveDir
+//
+// haleyjd 20110211: Convenience routine
+//
+char *M_MakeStrifeSaveDir(int slotnum, const char *extra)
+{
+    static char tmpbuffer[32];
+
+    M_snprintf(tmpbuffer, sizeof(tmpbuffer),
+               "strfsav%d.ssg%s", slotnum, extra);
+
+    return tmpbuffer;
+}
+
+// 
+// M_GetFilePath
+//
+// haleyjd: STRIFE-FIXME: Temporary?
+// Code borrowed from Eternity, and modified to return separator char
+//
+char M_GetFilePath(const char *fn, char *dest, size_t len)
+{
+    boolean found_slash = false;
+    char *p;
+    char sepchar = '\0';
+
+    memset(dest, 0, len);
+
+    p = dest + len - 1;
+
+    M_StringCopy(dest, fn, len);
+
+    while(p >= dest)
+    {
+        if(*p == '/' || *p == '\\')
+        {
+            sepchar = *p;
+            found_slash = true; // mark that the path ended with a slash
+            *p = '\0';
+            break;
+        }
+        *p = '\0';
+        p--;
+    }
+
+    // haleyjd: in the case that no slash was ever found, yet the
+    // path string is empty, we are dealing with a file local to the
+    // working directory. The proper path to return for such a string is
+    // not "", but ".", since the format strings add a slash now. When
+    // the string is empty but a slash WAS found, we really do want to
+    // return the empty string, since the path is relative to the root.
+    if(!found_slash && *dest == '\0')
+        *dest = '.';
+
+    // if a separator is not found, default to forward, because Windows 
+    // supports that too.
+    if(sepchar == '\0') 
+        sepchar = '/';
+
+    return sepchar;
+}
+
+// EOF
+
+
diff --git a/src/strife/m_saves.h b/src/strife/m_saves.h
index 0a65b24..8b6bced 100644
--- a/src/strife/m_saves.h
+++ b/src/strife/m_saves.h
@@ -1,56 +1,56 @@
-//
-// Copyright(C) 1993-1996 Id Software, Inc.
-// Copyright(C) 2010 James Haley, Samuel Villareal
-//
-// This program is free software; you can redistribute it and/or
-// modify it under the terms of the GNU General Public License
-// as published by the Free Software Foundation; either version 2
-// of the License, or (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-//
-// DESCRIPTION:
-//
-// [STRIFE] New Module
-//
-// Strife Hub Saving Code
-//
-
-#ifndef M_SAVES_H__
-#define M_SAVES_H__
-
-#define CHARACTER_NAME_LEN 32
-
-extern char *savepath;
-extern char *savepathtemp;
-extern char *loadpath;
-extern char character_name[CHARACTER_NAME_LEN];
-
-// Strife Savegame Functions
-void ClearTmp(void);
-void ClearSlot(void);
-void FromCurr(void);
-void ToCurr(void);
-void M_SaveMoveMapToHere(void);
-void M_SaveMoveHereToMap(void);
-
-boolean M_SaveMisObj(const char *path);
-void    M_ReadMisObj(void);
-
-// Custom Utilities for Filepath Handling
-void *M_Calloc(size_t n1, size_t n2);
-void  M_NormalizeSlashes(char *str);
-int   M_StringAlloc(char **str, int numstrs, size_t extra, const char *str1, ...);
-char *M_SafeFilePath(const char *basepath, const char *newcomponent);
-char  M_GetFilePath(const char *fn, char *dest, size_t len);
-char *M_MakeStrifeSaveDir(int slotnum, const char *extra);
-void  M_CreateSaveDirs(const char *savedir);
-
-#endif
-
-// EOF
-
-
+//
+// Copyright(C) 1993-1996 Id Software, Inc.
+// Copyright(C) 2010 James Haley, Samuel Villareal
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// DESCRIPTION:
+//
+// [STRIFE] New Module
+//
+// Strife Hub Saving Code
+//
+
+#ifndef M_SAVES_H__
+#define M_SAVES_H__
+
+#define CHARACTER_NAME_LEN 32
+
+extern char *savepath;
+extern char *savepathtemp;
+extern char *loadpath;
+extern char character_name[CHARACTER_NAME_LEN];
+
+// Strife Savegame Functions
+void ClearTmp(void);
+void ClearSlot(void);
+void FromCurr(void);
+void ToCurr(void);
+void M_SaveMoveMapToHere(void);
+void M_SaveMoveHereToMap(void);
+
+boolean M_SaveMisObj(const char *path);
+void    M_ReadMisObj(void);
+
+// Custom Utilities for Filepath Handling
+void *M_Calloc(size_t n1, size_t n2);
+void  M_NormalizeSlashes(char *str);
+int   M_StringAlloc(char **str, int numstrs, size_t extra, const char *str1, ...);
+char *M_SafeFilePath(const char *basepath, const char *newcomponent);
+char  M_GetFilePath(const char *fn, char *dest, size_t len);
+char *M_MakeStrifeSaveDir(int slotnum, const char *extra);
+void  M_CreateSaveDirs(const char *savedir);
+
+#endif
+
+// EOF
+
+
diff --git a/src/strife/p_dialog.c b/src/strife/p_dialog.c
index a18fec7..eb31301 100644
--- a/src/strife/p_dialog.c
+++ b/src/strife/p_dialog.c
@@ -1,1413 +1,1413 @@
-//
-// Copyright(C) 1993-1996 Id Software, Inc.
-// Copyright(C) 2010 James Haley, Samuel Villarreal
-//
-// This program is free software; you can redistribute it and/or
-// modify it under the terms of the GNU General Public License
-// as published by the Free Software Foundation; either version 2
-// of the License, or (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-//
-// DESCRIPTION:
-//
-// [STRIFE] New Module
-//
-// Dialog Engine for Strife
-//
-
-#include <stdlib.h>
-
-#include "z_zone.h"
-#include "w_wad.h"
-#include "deh_str.h"
-#include "d_main.h"
-#include "d_mode.h"
-#include "d_player.h"
-#include "doomstat.h"
-#include "m_random.h"
-#include "m_menu.h"
-#include "m_misc.h"
-#include "r_main.h"
-#include "v_video.h"
-#include "p_local.h"
-#include "sounds.h"
-#include "p_dialog.h"
-#include "s_sound.h"
-#include "p_local.h"
-#include "p_inter.h"
-
-//
-// Defines and Macros
-//
-
-// haleyjd: size of the original Strife mapdialog_t structure.
-#define ORIG_MAPDIALOG_SIZE 0x5EC
-
-#define DIALOG_INT(field, ptr)    \
-    field = ((int)ptr[0]        | \
-            ((int)ptr[1] <<  8) | \
-            ((int)ptr[2] << 16) | \
-            ((int)ptr[3] << 24)); \
-    ptr += 4;
-
-#define DIALOG_STR(field, ptr, len) \
-    memcpy(field, ptr, len);        \
-    ptr += len;
-
-//
-// Globals
-//
-
-// This can be toggled at runtime to determine if the full dialog messages
-// are subtitled on screen or not. Defaults to off.
-int dialogshowtext = false;
-
-// The global mission objective buffer. This gets written to and read from file,
-// and is set by dialogs and line actions.
-char mission_objective[OBJECTIVE_LEN];
-
-//
-// Static Globals
-//
-
-// True if SCRIPT00 is loaded.
-static boolean script0loaded;
-
-// Number of dialogs defined in the current level's script.
-static int numleveldialogs;
-
-// The actual level dialogs. This didn't exist in Strife, but is new to account
-// for structure alignment/packing concerns, given that Chocolate Doom is
-// multiplatform.
-static mapdialog_t *leveldialogs;
-
-// The actual script00 dialogs. As above.
-static mapdialog_t *script0dialogs;
-
-// Number of dialogs defined in the SCRIPT00 lump.
-static int numscript0dialogs;
-
-// The player engaged in dialog. This is always player 1, though, since Rogue
-// never completed the ability to use dialog outside of single-player mode.
-static player_t *dialogplayer;
-
-// The object to which the player is speaking.
-static mobj_t   *dialogtalker;
-
-// The talker's current angle
-static angle_t dialogtalkerangle;
-
-// The currently active mapdialog object.
-static mapdialog_t *currentdialog;
-
-// Text at the end of the choices
-static char dialoglastmsgbuffer[48];
-
-// Item to display to player when picked up or recieved
-static char pickupstring[46];
-
-// Health based on gameskill given by the front's medic
-static const int healthamounts[] = { -100 , -75, -50, -50, -100 };
-
-//=============================================================================
-//
-// Dialog State Sets
-//
-// These are used to animate certain actors in response to what happens in
-// their dialog sequences.
-//
-
-typedef struct dialogstateset_s
-{
-    mobjtype_t type;  // the type of object
-    statenum_t greet; // greeting state, for start of dialog
-    statenum_t yes;   // "yes" state, for an affirmative response
-    statenum_t no;    // "no" state, when you don't have the right items
-} dialogstateset_t;
-
-static dialogstateset_t dialogstatesets[] =
-{
-    { MT_PLAYER,       S_NULL,    S_NULL,    S_NULL    },
-    { MT_SHOPKEEPER_W, S_MRGT_00, S_MRYS_00, S_MRNO_00 },
-    { MT_SHOPKEEPER_B, S_MRGT_00, S_MRYS_00, S_MRNO_00 },
-    { MT_SHOPKEEPER_A, S_MRGT_00, S_MRYS_00, S_MRNO_00 },
-    { MT_SHOPKEEPER_M, S_MRGT_00, S_MRYS_00, S_MRNO_00 }
-};
-
-// Rogue stored this in a static global rather than making it a define...
-static int numdialogstatesets = arrlen(dialogstatesets);
-
-// Current dialog talker state
-static dialogstateset_t *dialogtalkerstates;
-
-//=============================================================================
-//
-// Random Messages
-//
-// Rogue hard-coded these so they wouldn't have to repeat them several times
-// in the SCRIPT00 lump, apparently.
-//
-
-#define MAXRNDMESSAGES 10
-
-typedef struct rndmessage_s
-{
-    const char *type_name;
-    int nummessages;
-    char *messages[MAXRNDMESSAGES];
-} rndmessage_t;
-
-static rndmessage_t rndMessages[] = 
-{
-    // Peasants
-    {
-        "PEASANT",
-        10,
-        {
-            "PLEASE DON'T HURT ME.",
-            
-            "IF YOU'RE LOOKING TO HURT ME, I'M \n"
-            "NOT REALLY WORTH THE EFFORT.",
-            
-            "I DON'T KNOW ANYTHING.",
-            
-            "GO AWAY OR I'LL CALL THE GUARDS!",
-            
-            "I WISH SOMETIMES THAT ALL THESE \n"
-            "REBELS WOULD JUST LEARN THEIR \n"
-            "PLACE AND STOP THIS NONSENSE.",
-
-            "JUST LEAVE ME ALONE, OK?",
-
-            "I'M NOT SURE, BUT SOMETIMES I THINK \n"
-            "THAT I KNOW SOME OF THE ACOLYTES.",
-
-            "THE ORDER'S GOT EVERYTHING AROUND HERE PRETTY WELL LOCKED UP TIGHT.",
-
-            "THERE'S NO WAY THAT THIS IS JUST A \n"
-            "SECURITY FORCE.",
-
-            "I'VE HEARD THAT THE ORDER IS REALLY \n"
-            "NERVOUS ABOUT THE FRONT'S \n"
-            "ACTIONS AROUND HERE."
-        }
-    },
-    // Rebel
-    {
-        "REBEL",
-        10,
-        {
-            "THERE'S NO WAY THE ORDER WILL \n"
-            "STAND AGAINST US.",
-
-            "WE'RE ALMOST READY TO STRIKE. \n"
-            "MACIL'S PLANS ARE FALLING IN PLACE.",
-
-            "WE'RE ALL BEHIND YOU, DON'T WORRY.",
-
-            "DON'T GET TOO CLOSE TO ANY OF THOSE BIG ROBOTS. THEY'LL MELT YOU DOWN \n"
-            "FOR SCRAP!",
-
-            "THE DAY OF OUR GLORY WILL SOON \n"
-            "COME, AND THOSE WHO OPPOSE US WILL \n"
-            "BE CRUSHED!",
-
-            "DON'T GET TOO COMFORTABLE. WE'VE \n"
-            "STILL GOT OUR WORK CUT OUT FOR US.",
-
-            "MACIL SAYS THAT YOU'RE THE NEW \n"
-            "HOPE. BEAR THAT IN MIND.",
-
-            "ONCE WE'VE TAKEN THESE CHARLATANS DOWN, WE'LL BE ABLE TO REBUILD THIS "
-            "WORLD AS IT SHOULD BE.",
-
-            "REMEMBER THAT YOU AREN'T FIGHTING \n"
-            "JUST FOR YOURSELF, BUT FOR \n"
-            "EVERYONE HERE AND OUTSIDE.",
-
-            "AS LONG AS ONE OF US STILL STANDS, \n"
-            "WE WILL WIN."
-        }
-    },
-    // Acolyte
-    {
-        "AGUARD",
-        10,
-        {
-            "MOVE ALONG,  PEASANT.",
-
-            "FOLLOW THE TRUE FAITH, ONLY THEN \n"
-            "WILL YOU BEGIN TO UNDERSTAND.",
-
-            "ONLY THROUGH DEATH CAN ONE BE \n"
-            "TRULY REBORN.",
-
-            "I'M NOT INTERESTED IN YOUR USELESS \n"
-            "DRIVEL.",
-
-            "IF I HAD WANTED TO TALK TO YOU I \n"
-            "WOULD HAVE TOLD YOU SO.",
-
-            "GO AND ANNOY SOMEONE ELSE!",
-
-            "KEEP MOVING!",
-
-            "IF THE ALARM GOES OFF, JUST STAY OUT OF OUR WAY!",
-
-            "THE ORDER WILL CLEANSE THE WORLD \n"
-            "AND USHER IT INTO THE NEW ERA.",
-
-            "PROBLEM?  NO, I THOUGHT NOT.",
-        }
-    },
-    // Beggar
-    {
-        "BEGGAR",
-        10,
-        {
-            "ALMS FOR THE POOR?",
-
-            "WHAT ARE YOU LOOKING AT, SURFACER?",
-
-            "YOU WOULDN'T HAVE ANY EXTRA FOOD, WOULD YOU?",
-
-            "YOU  SURFACE PEOPLE WILL NEVER \n"
-            "                                                                 "
-            "                                      UNDERSTAND US.",
-
-            "HA, THE GUARDS CAN'T FIND US.  THOSE \n"
-            "IDIOTS DON'T EVEN KNOW WE EXIST.",
-
-            "ONE DAY EVERYONE BUT THOSE WHO SERVE THE ORDER WILL BE FORCED TO "
-            "  JOIN US.",
-
-            "STARE NOW,  BUT YOU KNOW THAT THIS WILL BE YOUR OWN FACE ONE DAY.",
-
-            // Note: "NOTHING THING" is an authentic typo
-            "THERE'S NOTHING THING MORE \n"
-            "ANNOYING THAN A SURFACER WITH AN ATTITUDE!",
-
-            "THE ORDER WILL MAKE SHORT WORK OF YOUR PATHETIC FRONT.",
-
-            "WATCH YOURSELF SURFACER. WE KNOW OUR ENEMIES!"
-        }
-    },
-    // Templar
-    {
-        "PGUARD",
-        10,
-        {
-            "WE ARE THE HANDS OF FATE. TO EARN \n"
-            "OUR WRATH IS TO FIND OBLIVION!",
-
-            "THE ORDER WILL CLEANSE THE WORLD \n"
-            "OF THE WEAK AND CORRUPT!",
-
-            "OBEY THE WILL OF THE MASTERS!",
-
-            "LONG LIFE TO THE BROTHERS OF THE \n"
-            "ORDER!",
-
-            "FREE WILL IS AN ILLUSION THAT BINDS \n"
-            "THE WEAK MINDED.",
-
-            "POWER IS THE PATH TO GLORY. TO \n"
-            "FOLLOW THE ORDER IS TO WALK THAT \n"
-            "PATH!",
-
-            "TAKE YOUR PLACE AMONG THE \n"
-            "RIGHTEOUS, JOIN US!",
-
-            "THE ORDER PROTECTS ITS OWN.",
-
-            "ACOLYTES?  THEY HAVE YET TO SEE THE FULL GLORY OF THE ORDER.",
-
-            "IF THERE IS ANY HONOR INSIDE THAT \n"
-            "PATHETIC SHELL OF A BODY, \n"
-            "YOU'LL ENTER INTO THE ARMS OF THE \n"
-            "ORDER."
-        }
-    }
-};
-
-// And again, this could have been a define, but was a variable.
-static int numrndmessages = arrlen(rndMessages);
-
-//=============================================================================
-//
-// Dialog Menu Structure
-//
-// The Strife dialog system is actually just a serious abuse of the DOOM menu
-// engine. Hence why it doesn't work in multiplayer games or during demo
-// recording.
-//
-
-#define NUMDIALOGMENUITEMS 6
-
-static void P_DialogDrawer(void);
-
-static menuitem_t dialogmenuitems[] =
-{
-    { 1, "", P_DialogDoChoice, '1' }, // These items are loaded dynamically
-    { 1, "", P_DialogDoChoice, '2' },
-    { 1, "", P_DialogDoChoice, '3' },
-    { 1, "", P_DialogDoChoice, '4' },
-    { 1, "", P_DialogDoChoice, '5' },
-    { 1, "", P_DialogDoChoice, '6' }  // Item 6 is always the dismissal item
-};
-
-static menu_t dialogmenu =
-{
-    NUMDIALOGMENUITEMS, 
-    NULL, 
-    dialogmenuitems, 
-    P_DialogDrawer, 
-    42, 
-    75, 
-    0
-};
-
-// Lump number of the dialog background picture, if any.
-static int dialogbgpiclumpnum;
-
-// Name of current speaking character.
-static char *dialogname;
-
-// Current dialog text.
-static const char *dialogtext;
-
-//=============================================================================
-//
-// Routines
-//
-
-//
-// P_ParseDialogLump
-//
-// haleyjd 09/02/10: This is an original function added to parse out the 
-// dialogs from the dialog lump rather than reading them raw from the lump 
-// pointer. This avoids problems with structure packing.
-//
-static void P_ParseDialogLump(byte *lump, mapdialog_t **dialogs, 
-                              int numdialogs, int tag)
-{
-    int i;
-    byte *rover = lump;
-
-    *dialogs = Z_Malloc(numdialogs * sizeof(mapdialog_t), tag, NULL);
-
-    for(i = 0; i < numdialogs; i++)
-    {
-        int j;
-        mapdialog_t *curdialog = &((*dialogs)[i]);
-
-        DIALOG_INT(curdialog->speakerid,    rover);
-        DIALOG_INT(curdialog->dropitem,     rover);
-        DIALOG_INT(curdialog->checkitem[0], rover);
-        DIALOG_INT(curdialog->checkitem[1], rover);
-        DIALOG_INT(curdialog->checkitem[2], rover);
-        DIALOG_INT(curdialog->jumptoconv,   rover);
-        DIALOG_STR(curdialog->name,         rover, MDLG_NAMELEN);
-        DIALOG_STR(curdialog->voice,        rover, MDLG_LUMPLEN);
-        DIALOG_STR(curdialog->backpic,      rover, MDLG_LUMPLEN);
-        DIALOG_STR(curdialog->text,         rover, MDLG_TEXTLEN);
-
-        // copy choices
-        for(j = 0; j < 5; j++)
-        {
-            mapdlgchoice_t *curchoice = &(curdialog->choices[j]);
-            DIALOG_INT(curchoice->giveitem,         rover);
-            DIALOG_INT(curchoice->needitems[0],     rover);
-            DIALOG_INT(curchoice->needitems[1],     rover);
-            DIALOG_INT(curchoice->needitems[2],     rover);
-            DIALOG_INT(curchoice->needamounts[0],   rover);
-            DIALOG_INT(curchoice->needamounts[1],   rover);
-            DIALOG_INT(curchoice->needamounts[2],   rover);
-            DIALOG_STR(curchoice->text,             rover, MDLG_CHOICELEN);
-            DIALOG_STR(curchoice->textok,           rover, MDLG_MSGLEN);
-            DIALOG_INT(curchoice->next,             rover);
-            DIALOG_INT(curchoice->objective,        rover);
-            DIALOG_STR(curchoice->textno,           rover, MDLG_MSGLEN);
-        }
-    }
-}
-
-//
-// P_DialogLoad
-//
-// [STRIFE] New function
-// haleyjd 09/02/10: Loads the dialog script for the current map. Also loads 
-// SCRIPT00 if it has not yet been loaded.
-//
-void P_DialogLoad(void)
-{
-    char lumpname[9];
-    int  lumpnum;
-
-    // load the SCRIPTxy lump corresponding to MAPxy, if it exists.
-    DEH_snprintf(lumpname, sizeof(lumpname), "script%02d", gamemap);
-    if((lumpnum = W_CheckNumForName(lumpname)) == -1)
-        numleveldialogs = 0;
-    else
-    {
-        byte *leveldialogptr = W_CacheLumpNum(lumpnum, PU_STATIC);
-        numleveldialogs = W_LumpLength(lumpnum) / ORIG_MAPDIALOG_SIZE;
-        P_ParseDialogLump(leveldialogptr, &leveldialogs, numleveldialogs, 
-                          PU_LEVEL);
-        Z_Free(leveldialogptr); // haleyjd: free the original lump
-    }
-
-    // also load SCRIPT00 if it has not been loaded yet
-    if(!script0loaded)
-    {
-        byte *script0ptr;
-
-        script0loaded = true; 
-        // BUG: Rogue should have used W_GetNumForName here...
-        lumpnum = W_CheckNumForName(DEH_String("script00")); 
-        script0ptr = W_CacheLumpNum(lumpnum, PU_STATIC);
-        numscript0dialogs = W_LumpLength(lumpnum) / ORIG_MAPDIALOG_SIZE;
-        P_ParseDialogLump(script0ptr, &script0dialogs, numscript0dialogs,
-                          PU_STATIC);
-        Z_Free(script0ptr); // haleyjd: free the original lump
-    }
-}
-
-//
-// P_PlayerHasItem
-//
-// [STRIFE] New function
-// haleyjd 09/02/10: Checks for inventory items, quest flags, etc. for dialogs.
-// Returns the amount possessed, or 0 if none.
-//
-int P_PlayerHasItem(player_t *player, mobjtype_t type)
-{
-    int i;
-
-    if(type > 0)
-    {
-        // check keys
-        if(type >= MT_KEY_BASE && type < MT_INV_SHADOWARMOR)
-            return (player->cards[type - MT_KEY_BASE]);
-
-        // check sigil pieces
-        if(type >= MT_SIGIL_A && type <= MT_SIGIL_E)
-            return (type - MT_SIGIL_A <= player->sigiltype);
-
-        // check quest tokens
-        if(type >= MT_TOKEN_QUEST1 && type <= MT_TOKEN_QUEST31)
-            return (player->questflags & (1 << (type - MT_TOKEN_QUEST1)));
-
-        // check inventory
-        for(i = 0; i < 32; i++)
-        {
-            if(type == player->inventory[i].type)
-                return player->inventory[i].amount;
-        }
-    }
-    return 0;
-}
-
-//
-// P_DialogFind
-//
-// [STRIFE] New function
-// haleyjd 09/03/10: Looks for a dialog definition matching the given 
-// Script ID # for an mobj.
-//
-mapdialog_t *P_DialogFind(mobjtype_t type, int jumptoconv)
-{
-    int i;
-
-    // check the map-specific dialogs first
-    for(i = 0; i < numleveldialogs; i++)
-    {
-        if(type == leveldialogs[i].speakerid)
-        {
-            if(jumptoconv <= 1)
-                return &leveldialogs[i];
-            else
-                --jumptoconv;
-        }
-    }
-
-    // check SCRIPT00 dialogs next
-    for(i = 0; i < numscript0dialogs; i++)
-    {
-        if(type == script0dialogs[i].speakerid)
-            return &script0dialogs[i];
-    }
-
-    // the default dialog is script 0 in the SCRIPT00 lump.
-    return &script0dialogs[0];
-}
-
-//
-// P_DialogGetStates
-//
-// [STRIFE] New function
-// haleyjd 09/03/10: Find the set of special dialog states (greetings, yes, no)
-// for a particular thing type.
-//
-static dialogstateset_t *P_DialogGetStates(mobjtype_t type)
-{
-    int i;
-
-    // look for a match by type
-    for(i = 0; i < numdialogstatesets; i++)
-    {
-        if(type == dialogstatesets[i].type)
-            return &dialogstatesets[i];
-    }
-
-    // return the default 0 record if no match.
-    return &dialogstatesets[0];
-}
-
-//
-// P_DialogGetMsg
-//
-// [STRIFE] New function
-// haleyjd 09/03/10: Redirects dialog messages when the script indicates that
-// the actor should use a random message stored in the executable instead.
-//
-static const char *P_DialogGetMsg(const char *message)
-{
-    // if the message starts with "RANDOM"...
-    if(!strncasecmp(message, DEH_String("RANDOM"), 6))
-    {
-        int i;
-        const char *nameloc = message + 7;
-
-        // look for a match in rndMessages for the string starting 
-        // 7 chars after "RANDOM_"
-        for(i = 0; i < numrndmessages; i++)
-        {
-            if(!strncasecmp(nameloc, rndMessages[i].type_name, 4))
-            {
-                // found a match, so return a random message
-                int rnd = M_Random();
-                int nummessages = rndMessages[i].nummessages;
-                return DEH_String(rndMessages[i].messages[rnd % nummessages]);
-            }
-        }
-    }
-
-    // otherwise, just return the message passed in.
-    return message;
-}
-
-//
-// P_GiveInventoryItem
-//
-// [STRIFE] New function
-// haleyjd 09/03/10: Give an inventory item to the player, if possible.
-// villsa 09/09/10: Fleshed out routine
-//
-boolean P_GiveInventoryItem(player_t *player, int sprnum, mobjtype_t type)
-{
-    int curinv = 0;
-    int i;
-    boolean ok = false;
-    mobjtype_t item = 0;
-    inventory_t* invtail;
-
-    // repaint the status bar due to inventory changing
-    player->st_update = true;
-
-    while(1)
-    {
-        // inventory is full
-        if(curinv > player->numinventory)
-            return true;
-
-        item = player->inventory[curinv].type;
-        if(type < item)
-        {
-            if(curinv != MAXINVENTORYSLOTS)
-            {
-                // villsa - sort inventory item if needed
-                invtail = &player->inventory[player->numinventory - 1];
-                if(player->numinventory >= (curinv + 1))
-                {
-                    for(i = player->numinventory; i >= (curinv + 1); --i)    
-                    {
-                        invtail[1].sprite   = invtail[0].sprite;
-                        invtail[1].type     = invtail[0].type;
-                        invtail[1].amount   = invtail[0].amount;
-
-                        invtail--;
-                    }
-                }
-
-                // villsa - add inventory item
-                player->inventory[curinv].amount = 1;
-                player->inventory[curinv].sprite = sprnum;
-                player->inventory[curinv].type = type;
-
-                // sort cursor if needed
-                if(player->numinventory)
-                {
-                    if(curinv <= player->inventorycursor)
-                        player->inventorycursor++;
-                }
-
-                player->numinventory++;
-
-                return true;
-            }
-
-            return false;
-        }
-
-        if(type == item)
-            break;
-
-        curinv++;
-    }
-
-    // check amount of inventory item by using the mass from mobjinfo
-    if(player->inventory[curinv].amount < mobjinfo[item].mass)
-    {
-        player->inventory[curinv].amount++;
-        ok = true;
-    }
-    else
-        ok = false;
-
-    return ok;
-}
-
-//
-// P_GiveItemToPlayer
-//
-// [STRIFE] New function
-// haleyjd 09/03/10: Sorts out how to give something to the player.
-// Not strictly just for inventory items.
-// villsa 09/09/10: Fleshed out function
-//
-boolean P_GiveItemToPlayer(player_t *player, int sprnum, mobjtype_t type)
-{
-    int i = 0;
-    line_t junk;
-    int sound = sfx_itemup; // haleyjd 09/21/10: different sounds for items
-
-    // set quest if mf_givequest flag is set
-    if(mobjinfo[type].flags & MF_GIVEQUEST)
-        player->questflags |= 1 << (mobjinfo[type].speed - 1);
-
-    // check for keys
-    if(type >= MT_KEY_BASE && type <= MT_NEWKEY5)
-    {
-        P_GiveCard(player, type - MT_KEY_BASE);
-        return true;
-    }
-
-    // check for quest tokens
-    if(type >= MT_TOKEN_QUEST1 && type <= MT_TOKEN_QUEST31)
-    {
-        if(mobjinfo[type].name)
-        {
-            M_StringCopy(pickupstring, DEH_String(mobjinfo[type].name), 39);
-            player->message = pickupstring;
-        }
-        player->questflags |= 1 << (type - MT_TOKEN_QUEST1);
-
-        if(player == &players[consoleplayer])
-            S_StartSound(NULL, sound);
-        return true;
-    }
-
-    // haleyjd 09/22/10: Refactored to give sprites higher priority than
-    // mobjtypes and to implement missing logic.
-    switch(sprnum)
-    {
-    case SPR_HELT: // This is given only by the "DONNYTRUMP" cheat (aka Midas)
-        P_GiveInventoryItem(player, SPR_HELT, MT_TOKEN_TOUGHNESS);
-        P_GiveInventoryItem(player, SPR_GUNT, MT_TOKEN_ACCURACY);
-
-        // [STRIFE] Bizarre...
-        for(i = 0; i < 5 * player->accuracy + 300; i++)
-            P_GiveInventoryItem(player, SPR_COIN, MT_MONY_1);
-        break;
-
-    case SPR_ARM1: // Armor 1
-        if(!P_GiveArmor(player, -2))
-            P_GiveInventoryItem(player, sprnum, type);
-        break;
-
-    case SPR_ARM2: // Armor 2
-        if(!P_GiveArmor(player, -1))
-            P_GiveInventoryItem(player, sprnum, type);
-        break;
-
-    case SPR_COIN: // 1 Gold
-        P_GiveInventoryItem(player, SPR_COIN, MT_MONY_1);
-        break;
-
-    case SPR_CRED: // 10 Gold
-        for(i = 0; i < 10; i++)
-            P_GiveInventoryItem(player, SPR_COIN, MT_MONY_1);
-        break;
-
-    case SPR_SACK: // 25 gold
-        for(i = 0; i < 25; i++)
-            P_GiveInventoryItem(player, SPR_COIN, MT_MONY_1);
-        break;
-
-    case SPR_CHST: // 50 gold
-        for(i = 0; i < 50; i++)
-            P_GiveInventoryItem(player, SPR_COIN, MT_MONY_1);
-        break; // haleyjd 20141215: missing break, caused Rowan to not take ring from you.
-
-    case SPR_BBOX: // Box of Bullets
-        if(!P_GiveAmmo(player, am_bullets, 5))
-            return false;
-        break;
-
-    case SPR_BLIT: // Bullet Clip
-        if(!P_GiveAmmo(player, am_bullets, 1))
-            return false;
-        break;
-
-    case SPR_PMAP: // Map powerup
-        if(!P_GivePower(player, pw_allmap))
-            return false;
-        sound = sfx_yeah; // bluh-doop!
-        break;
-
-    case SPR_COMM: // Communicator
-        if(!P_GivePower(player, pw_communicator))
-            return false;
-        sound = sfx_yeah; // bluh-doop!
-        break;
-
-    case SPR_MSSL: // Mini-missile
-        if(!P_GiveAmmo(player, am_missiles, 1))
-            return false;
-        break;
-
-    case SPR_ROKT: // Crate of missiles
-        if(!P_GiveAmmo(player, am_missiles, 5))
-            return false;
-        break;
-
-    case SPR_BRY1: // Battery cell
-        if(!P_GiveAmmo(player, am_cell, 1))
-            return false;
-        break;
-
-    case SPR_CPAC: // Cell pack
-        if(!P_GiveAmmo(player, am_cell, 5))
-            return false;
-        break;
-
-    case SPR_PQRL: // Poison bolts
-        if(!P_GiveAmmo(player, am_poisonbolts, 5))
-            return false;
-        break;
-
-    case SPR_XQRL: // Electric bolts
-        if(!P_GiveAmmo(player, am_elecbolts, 5))
-            return false;
-        break;
-
-    case SPR_GRN1: // HE Grenades
-        if(!P_GiveAmmo(player, am_hegrenades, 1))
-            return false;
-        break;
-
-    case SPR_GRN2: // WP Grenades
-        if(!P_GiveAmmo(player, am_wpgrenades, 1))
-            return false;
-        break;
-
-    case SPR_BKPK: // Backpack (aka Ammo Satchel)
-        if(!player->backpack)
-        {
-            for(i = 0; i < NUMAMMO; i++)
-                player->maxammo[i] *= 2;
-
-            player->backpack = true;
-        }
-        for(i = 0; i < NUMAMMO; i++)
-            P_GiveAmmo(player, i, 1);
-        break;
-
-    case SPR_RIFL: // Assault Rifle
-        if(player->weaponowned[wp_rifle])
-            return false;
-
-        if(!P_GiveWeapon(player, wp_rifle, false))
-            return false;
-        
-        sound = sfx_wpnup; // SHK-CHK!
-        break;
-
-    case SPR_FLAM: // Flamethrower
-        if(player->weaponowned[wp_flame])
-            return false;
-
-        if(!P_GiveWeapon(player, wp_flame, false))
-            return false;
-
-        sound = sfx_wpnup; // SHK-CHK!
-        break;
-
-    case SPR_MMSL: // Mini-missile Launcher
-        if(player->weaponowned[wp_missile])
-            return false;
-
-        if(!P_GiveWeapon(player, wp_missile, false))
-            return false;
-
-        sound = sfx_wpnup; // SHK-CHK!
-        break;
-
-    case SPR_TRPD: // Mauler
-        if(player->weaponowned[wp_mauler])
-            return false;
-
-        if(!P_GiveWeapon(player, wp_mauler, false))
-            return false;
-
-        sound = sfx_wpnup; // SHK-CHK!
-        break;
-
-    case SPR_CBOW: // Here's a crossbow. Just aim straight, and *SPLAT!*
-        if(player->weaponowned[wp_elecbow])
-            return false;
-
-        if(!P_GiveWeapon(player, wp_elecbow, false))
-            return false;
-
-        sound = sfx_wpnup; // SHK-CHK!
-        break;
-
-    case SPR_TOKN: // Miscellaneous items - These are determined by thingtype.
-        switch(type)
-        {
-        case MT_KEY_HAND: // Severed hand
-            P_GiveCard(player, key_SeveredHand);
-            break;
-
-        case MT_MONY_300: // 300 Gold (this is the only way to get it, in fact)
-            for(i = 0; i < 300; i++)
-                P_GiveInventoryItem(player, SPR_COIN, MT_MONY_1);
-            break;
-
-        case MT_TOKEN_AMMO: // Ammo token - you get this from the Weapons Trainer
-            if(player->ammo[am_bullets] >= 50)
-                return false;
-
-            player->ammo[am_bullets] = 50;
-            break;
-
-        case MT_TOKEN_HEALTH: // Health token - from the Front's doctor
-            if(!P_GiveBody(player, healthamounts[gameskill]))
-                return false;
-            break;
-
-        case MT_TOKEN_ALARM: // Alarm token - particularly from the Oracle.
-            P_NoiseAlert(player->mo, player->mo);
-            A_AlertSpectreC(dialogtalker); // BUG: assumes in a dialog o_O
-            break;
-
-        case MT_TOKEN_DOOR1: // Door special 1
-            junk.tag = 222;
-            EV_DoDoor(&junk, vld_open);
-            break;
-
-        case MT_TOKEN_PRISON_PASS: // Door special 1 - Prison pass
-            junk.tag = 223;
-            EV_DoDoor(&junk, vld_open);
-            if(gamemap == 2) // If on Tarnhill, give Prison pass object
-                P_GiveInventoryItem(player, sprnum, type);
-            break;
-
-        case MT_TOKEN_SHOPCLOSE: // Door special 3 - "Shop close" - unused?
-            junk.tag = 222;
-            EV_DoDoor(&junk, vld_close);
-            break;
-
-        case MT_TOKEN_DOOR3: // Door special 4 (or 3? :P ) 
-            junk.tag = 224;
-            EV_DoDoor(&junk, vld_close);
-            break;
-
-        case MT_TOKEN_STAMINA: // Stamina upgrade
-            if(player->stamina >= 100)
-                return false;
-
-            player->stamina += 10;
-            P_GiveBody(player, 200); // full healing
-            break;
-
-        case MT_TOKEN_NEW_ACCURACY: // Accuracy upgrade
-            if(player->accuracy >= 100)
-                return false;
-
-            player->accuracy += 10;
-            break;
-
-        case MT_SLIDESHOW: // Slideshow (start a finale)
-            gameaction = ga_victory;
-            if(gamemap == 10)
-                P_GiveItemToPlayer(player, SPR_TOKN, MT_TOKEN_QUEST17);
-            break;
-        
-        default: // The default is to just give it as an inventory item.
-            P_GiveInventoryItem(player, sprnum, type);
-            break;
-        }
-        break;
-
-    default: // The ultimate default: Give it as an inventory item.
-        if(!P_GiveInventoryItem(player, sprnum, type))
-            return false;
-        break;
-    }
-
-    // Play sound.
-    if(player == &players[consoleplayer])
-        S_StartSound(NULL, sound);
-
-    return true;
-}
-
-//
-// P_TakeDialogItem
-//
-// [STRIFE] New function
-// haleyjd 09/03/10: Removes needed items from the player's inventory.
-//
-static void P_TakeDialogItem(player_t *player, int type, int amount)
-{
-    int i;
-
-    if(amount <= 0)
-        return;
-
-    for(i = 0; i < player->numinventory; i++)
-    {
-        // find a matching item
-        if(type != player->inventory[i].type)
-            continue;
-
-        // if there is none left...
-        if((player->inventory[i].amount -= amount) < 1)
-        {
-            // ...shift everything above it down
-            int j;
-
-            // BUG: They should have stopped at j < numinventory. This
-            // seems to implicitly assume that numinventory is always at
-            // least one less than the max # of slots, otherwise it 
-            // pulls in data from the following player_t fields:
-            // st_update, numinventory, inventorycursor, accuracy, stamina
-            for(j = i + 1; j <= player->numinventory; j++)
-            {
-                inventory_t *item1 = &(player->inventory[j - 1]);
-                inventory_t *item2 = &(player->inventory[j]);
-
-                *item1 = *item2;
-            }
-
-            // blank the topmost slot
-            // BUG: This will overwrite the aforementioned fields if
-            // numinventory is equal to the number of slots!
-            // STRIFE-TODO: Overflow emulation?
-            player->inventory[player->numinventory].type = NUMMOBJTYPES;
-            player->inventory[player->numinventory].sprite = -1;
-            player->numinventory--;
-
-            // update cursor position
-            if(player->inventorycursor >= player->numinventory)
-            {
-                if(player->inventorycursor)
-                    player->inventorycursor--;
-            }
-        } // end if
-        
-        return; // done!
-
-    } // end for
-}
-
-//
-// P_DialogDrawer
-//
-// This function is set as the drawer callback for the dialog menu.
-//
-static void P_DialogDrawer(void)
-{
-    angle_t angle;
-    int y;
-    int i;
-    int height;
-    int finaly;
-    char choicetext[64];
-    char choicetext2[64];
-
-    // Run down bonuscount faster than usual so that flashes from being given
-    // items are less obvious.
-    if(dialogplayer->bonuscount)
-    {
-        dialogplayer->bonuscount -= 3;
-        if(dialogplayer->bonuscount < 0)
-            dialogplayer->bonuscount = 0;
-    }
-
-    angle = R_PointToAngle2(dialogplayer->mo->x,
-                            dialogplayer->mo->y,
-                            dialogtalker->x,
-                            dialogtalker->y);
-    angle -= dialogplayer->mo->angle;
-
-    // Dismiss the dialog if the player is out of alignment, or the thing he was
-    // talking to is now engaged in battle.
-    if ((angle > ANG45 && angle < (ANG270+ANG45))
-     || (dialogtalker->flags & MF_NODIALOG) != 0)
-    {
-        P_DialogDoChoice(dialogmenu.numitems - 1);
-    }
-
-    dialogtalker->reactiontime = 2;
-
-    // draw background
-    if(dialogbgpiclumpnum != -1)
-    {
-        patch_t *patch = W_CacheLumpNum(dialogbgpiclumpnum, PU_CACHE);
-        V_DrawPatchDirect(0, 0, patch);
-    }
-
-    // if there's a valid background pic, delay drawing the rest of the menu 
-    // for a while; otherwise, it will appear immediately
-    if(dialogbgpiclumpnum == -1 || menupausetime <= gametic)
-    {
-        if(menuindialog)
-        {
-            // time to pause the game?
-            if(menupausetime + 3 < gametic)
-                menupause = true;
-        }
-
-        // draw character name
-        M_WriteText(12, 18, dialogname);
-        y = 28;
-
-        // show text (optional for dialogs with voices)
-        if(dialogshowtext || currentdialog->voice[0] == '\0')
-            y = M_WriteText(20, 28, dialogtext);
-
-        height = 20 * dialogmenu.numitems;
-
-        finaly = 175 - height;     // preferred height
-        if(y > finaly)
-            finaly = 199 - height; // height it will bump down to if necessary.
-
-        // draw divider
-        M_WriteText(42, finaly - 6, DEH_String("______________________________"));
-
-        dialogmenu.y = finaly + 6;
-        y = 0;
-
-        // draw the menu items
-        for(i = 0; i < dialogmenu.numitems - 1; i++)
-        {
-            DEH_snprintf(choicetext, sizeof(choicetext),
-                         "%d) %s", i + 1, currentdialog->choices[i].text);
-            
-            // alternate text for items that need money
-            if(currentdialog->choices[i].needamounts[0] > 0)
-            {
-                // haleyjd 20120401: necessary to avoid undefined behavior:
-                M_StringCopy(choicetext2, choicetext, sizeof(choicetext2));
-                DEH_snprintf(choicetext, sizeof(choicetext),
-                             "%s for %d", choicetext2,
-                             currentdialog->choices[i].needamounts[0]);
-            }
-
-            M_WriteText(dialogmenu.x, dialogmenu.y + 3 + y, choicetext);
-            y += 19;
-        }
-
-        // draw the final item for dismissing the dialog
-        M_WriteText(dialogmenu.x, 19 * i + dialogmenu.y + 3, dialoglastmsgbuffer);
-    }
-}
-
-//
-// P_DialogDoChoice
-//
-// [STRIFE] New function
-// haleyjd 09/05/10: Handles making a choice in a dialog. Installed as the
-// callback for all items in the dialogmenu structure.
-//
-void P_DialogDoChoice(int choice)
-{
-    int i = 0, nextdialog = 0;
-    boolean candochoice = true;
-    char *message = NULL;
-    mapdlgchoice_t *currentchoice;
-
-    if(choice == -1)
-        choice = dialogmenu.numitems - 1;
-
-    currentchoice = &(currentdialog->choices[choice]);
-
-    I_StartVoice(NULL); // STRIFE-TODO: verify (should stop previous voice I believe)
-
-    // villsa 09/08/10: converted into for loop
-    for(i = 0; i < MDLG_MAXITEMS; i++)
-    {
-        if(P_PlayerHasItem(dialogplayer, currentchoice->needitems[i]) <
-                                         currentchoice->needamounts[i])
-        {
-            candochoice = false; // nope, missing something
-        }
-    }
-
-    if(choice != dialogmenu.numitems - 1 && candochoice)
-    {
-        int item;
-
-        message = currentchoice->textok;
-        if(dialogtalkerstates->yes)
-            P_SetMobjState(dialogtalker, dialogtalkerstates->yes);
-
-        item = currentchoice->giveitem;
-        if(item < 0 || 
-           P_GiveItemToPlayer(dialogplayer, 
-                              states[mobjinfo[item].spawnstate].sprite, 
-                              item))
-        {
-            // if successful, take needed items
-            int count = 0;
-            // villsa 09/08/10: converted into for loop
-            for(count = 0; count < MDLG_MAXITEMS; count++)
-            {
-                P_TakeDialogItem(dialogplayer, 
-                                 currentchoice->needitems[count],
-                                 currentchoice->needamounts[count]);
-            }
-        }
-        else
-            message = DEH_String("You seem to have enough!");
-
-        // store next dialog into the talking actor
-        nextdialog = currentchoice->next;
-        if(nextdialog != 0)
-            dialogtalker->miscdata = (byte)(abs(nextdialog));
-    }
-    else
-    {
-        // not successful
-        message = currentchoice->textno;
-        if(dialogtalkerstates->no)
-            P_SetMobjState(dialogtalker, dialogtalkerstates->no);
-    }
-    
-    if(choice != dialogmenu.numitems - 1)
-    {
-        int objective;
-        char *objlump;
-
-        if((objective = currentchoice->objective))
-        {
-            DEH_snprintf(mission_objective, OBJECTIVE_LEN, "log%i", objective);
-            objlump = W_CacheLumpName(mission_objective, PU_CACHE);
-            M_StringCopy(mission_objective, objlump, OBJECTIVE_LEN);
-        }
-        // haleyjd 20130301: v1.31 hack: if first char of message is a period,
-        // clear the player's message. Is this actually used anywhere?
-        if(gameversion == exe_strife_1_31 && message[0] == '.')
-            message = NULL;
-        dialogplayer->message = message;
-    }
-
-    dialogtalker->angle = dialogtalkerangle;
-    dialogplayer->st_update = true;
-    M_ClearMenus(0);
-
-    if(nextdialog >= 0 || gameaction == ga_victory) // Macil hack
-        menuindialog = false;
-    else
-        P_DialogStart(dialogplayer);
-}
-
-//
-// P_DialogStartP1
-//
-// [STRIFE] New function
-// haleyjd 09/13/10: This is a hack used by the finale system.
-//
-void P_DialogStartP1(void)
-{
-    P_DialogStart(&players[0]);
-}
-
-//
-// P_DialogStart
-//
-// villsa [STRIFE] New function
-//
-void P_DialogStart(player_t *player)
-{
-    int i = 0;
-    int pic;
-    int rnd = 0;
-    char* byetext;
-    int jumptoconv;
-
-    if(menuactive || netgame)
-        return;
-
-    // are we facing towards our NPC?
-    P_AimLineAttack(player->mo, player->mo->angle, (128*FRACUNIT));
-    if(!linetarget)
-    {
-        P_AimLineAttack(player->mo, player->mo->angle + (ANG90/16), (128*FRACUNIT));
-        if(!linetarget)
-            P_AimLineAttack(player->mo, player->mo->angle - (ANG90/16), (128*FRACUNIT));
-    }
-
-    if(!linetarget)
-       return;
-
-    // already in combat, can't talk to it
-    if(linetarget->flags & MF_NODIALOG)
-       return;
-
-    // set pointer to the character talking
-    dialogtalker = linetarget;
-
-    // play a sound
-    if(player == &players[consoleplayer])
-       S_StartSound(0, sfx_radio);
-
-    linetarget->target = player->mo;         // target the player
-    dialogtalker->reactiontime = 2;          // set reactiontime
-    dialogtalkerangle = dialogtalker->angle; // remember original angle
-
-    // face talker towards player
-    A_FaceTarget(dialogtalker);
-
-    // face towards NPC's direction
-    player->mo->angle = R_PointToAngle2(player->mo->x,
-                                        player->mo->y,
-                                        dialogtalker->x,
-                                        dialogtalker->y);
-    // set pointer to player talking
-    dialogplayer = player;
-
-    // haleyjd 09/08/10: get any stored dialog state from this object
-    jumptoconv = linetarget->miscdata;
-
-    // check item requirements
-    while(1)
-    {
-        int i = 0;
-        currentdialog = P_DialogFind(linetarget->type, jumptoconv);
-
-        // dialog's jumptoconv equal to 0? There's nothing to jump to.
-        if(currentdialog->jumptoconv == 0)
-            break;
-
-        // villsa 09/08/10: converted into for loop
-        for(i = 0; i < MDLG_MAXITEMS; i++)
-        {
-            // if the item is non-zero, the player must have at least one in his
-            // or her inventory
-            if(currentdialog->checkitem[i] != 0 &&
-                P_PlayerHasItem(dialogplayer, currentdialog->checkitem[i]) < 1)
-                break;
-        }
-
-        if(i < MDLG_MAXITEMS) // didn't find them all? this is our dialog!
-            break;
-
-        jumptoconv = currentdialog->jumptoconv;
-    }
-
-    M_DialogDimMsg(20, 28, currentdialog->text, false);
-    dialogtext = P_DialogGetMsg(currentdialog->text);
-
-    // get states
-    dialogtalkerstates = P_DialogGetStates(linetarget->type);
-
-    // have talker greet the player
-    if(dialogtalkerstates->greet)
-        P_SetMobjState(dialogtalker, dialogtalkerstates->greet);
-
-    // get talker's name
-    if(currentdialog->name[0])
-        dialogname = currentdialog->name;
-    else
-    {
-        // use a fallback:
-        if(mobjinfo[linetarget->type].name)
-            dialogname = DEH_String(mobjinfo[linetarget->type].name); // mobjtype name
-        else
-            dialogname = DEH_String("Person"); // default name - like Joe in Doom 3 :P
-    }
-
-    // setup number of choices to choose from
-    for(i = 0; i < MDLG_MAXCHOICES; i++)
-    {
-        if(!currentdialog->choices[i].giveitem)
-            break;
-    }
-
-    // set number of choices to menu
-    dialogmenu.numitems = i + 1;
-
-    rnd = M_Random() % 3;
-
-    // setup dialog menu
-    M_StartControlPanel();
-    menupause = false;
-    menuindialog = true;
-    menupausetime = gametic + 17;
-    currentMenu = &dialogmenu;
-
-    if(i >= dialogmenu.lastOn)
-        itemOn = dialogmenu.lastOn;
-    else
-        itemOn = 0;
-
-    // get backdrop
-    pic = W_CheckNumForName(currentdialog->backpic);
-    dialogbgpiclumpnum = pic;
-    if(pic != -1)
-        V_DrawPatchDirect(0, 0, W_CacheLumpNum(pic, PU_CACHE));
-
-    // get voice
-    I_StartVoice(currentdialog->voice);
-
-    // get bye text
-    switch(rnd)
-    {
-    case 2:
-        byetext = DEH_String("BYE!");
-        break;
-    case 1:
-        byetext = DEH_String("Thanks, Bye!");
-        break;
-    default:
-    case 0:
-        byetext = DEH_String("See you later!");
-        break;
-    }
-
-    DEH_snprintf(dialoglastmsgbuffer, sizeof(dialoglastmsgbuffer),
-                 "%d) %s", i + 1, byetext);
-}
-
-// EOF
-
-
+//
+// Copyright(C) 1993-1996 Id Software, Inc.
+// Copyright(C) 2010 James Haley, Samuel Villarreal
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// DESCRIPTION:
+//
+// [STRIFE] New Module
+//
+// Dialog Engine for Strife
+//
+
+#include <stdlib.h>
+
+#include "z_zone.h"
+#include "w_wad.h"
+#include "deh_str.h"
+#include "d_main.h"
+#include "d_mode.h"
+#include "d_player.h"
+#include "doomstat.h"
+#include "m_random.h"
+#include "m_menu.h"
+#include "m_misc.h"
+#include "r_main.h"
+#include "v_video.h"
+#include "p_local.h"
+#include "sounds.h"
+#include "p_dialog.h"
+#include "s_sound.h"
+#include "p_local.h"
+#include "p_inter.h"
+
+//
+// Defines and Macros
+//
+
+// haleyjd: size of the original Strife mapdialog_t structure.
+#define ORIG_MAPDIALOG_SIZE 0x5EC
+
+#define DIALOG_INT(field, ptr)    \
+    field = ((int)ptr[0]        | \
+            ((int)ptr[1] <<  8) | \
+            ((int)ptr[2] << 16) | \
+            ((int)ptr[3] << 24)); \
+    ptr += 4;
+
+#define DIALOG_STR(field, ptr, len) \
+    memcpy(field, ptr, len);        \
+    ptr += len;
+
+//
+// Globals
+//
+
+// This can be toggled at runtime to determine if the full dialog messages
+// are subtitled on screen or not. Defaults to off.
+int dialogshowtext = false;
+
+// The global mission objective buffer. This gets written to and read from file,
+// and is set by dialogs and line actions.
+char mission_objective[OBJECTIVE_LEN];
+
+//
+// Static Globals
+//
+
+// True if SCRIPT00 is loaded.
+static boolean script0loaded;
+
+// Number of dialogs defined in the current level's script.
+static int numleveldialogs;
+
+// The actual level dialogs. This didn't exist in Strife, but is new to account
+// for structure alignment/packing concerns, given that Chocolate Doom is
+// multiplatform.
+static mapdialog_t *leveldialogs;
+
+// The actual script00 dialogs. As above.
+static mapdialog_t *script0dialogs;
+
+// Number of dialogs defined in the SCRIPT00 lump.
+static int numscript0dialogs;
+
+// The player engaged in dialog. This is always player 1, though, since Rogue
+// never completed the ability to use dialog outside of single-player mode.
+static player_t *dialogplayer;
+
+// The object to which the player is speaking.
+static mobj_t   *dialogtalker;
+
+// The talker's current angle
+static angle_t dialogtalkerangle;
+
+// The currently active mapdialog object.
+static mapdialog_t *currentdialog;
+
+// Text at the end of the choices
+static char dialoglastmsgbuffer[48];
+
+// Item to display to player when picked up or recieved
+static char pickupstring[46];
+
+// Health based on gameskill given by the front's medic
+static const int healthamounts[] = { -100 , -75, -50, -50, -100 };
+
+//=============================================================================
+//
+// Dialog State Sets
+//
+// These are used to animate certain actors in response to what happens in
+// their dialog sequences.
+//
+
+typedef struct dialogstateset_s
+{
+    mobjtype_t type;  // the type of object
+    statenum_t greet; // greeting state, for start of dialog
+    statenum_t yes;   // "yes" state, for an affirmative response
+    statenum_t no;    // "no" state, when you don't have the right items
+} dialogstateset_t;
+
+static dialogstateset_t dialogstatesets[] =
+{
+    { MT_PLAYER,       S_NULL,    S_NULL,    S_NULL    },
+    { MT_SHOPKEEPER_W, S_MRGT_00, S_MRYS_00, S_MRNO_00 },
+    { MT_SHOPKEEPER_B, S_MRGT_00, S_MRYS_00, S_MRNO_00 },
+    { MT_SHOPKEEPER_A, S_MRGT_00, S_MRYS_00, S_MRNO_00 },
+    { MT_SHOPKEEPER_M, S_MRGT_00, S_MRYS_00, S_MRNO_00 }
+};
+
+// Rogue stored this in a static global rather than making it a define...
+static int numdialogstatesets = arrlen(dialogstatesets);
+
+// Current dialog talker state
+static dialogstateset_t *dialogtalkerstates;
+
+//=============================================================================
+//
+// Random Messages
+//
+// Rogue hard-coded these so they wouldn't have to repeat them several times
+// in the SCRIPT00 lump, apparently.
+//
+
+#define MAXRNDMESSAGES 10
+
+typedef struct rndmessage_s
+{
+    const char *type_name;
+    int nummessages;
+    char *messages[MAXRNDMESSAGES];
+} rndmessage_t;
+
+static rndmessage_t rndMessages[] = 
+{
+    // Peasants
+    {
+        "PEASANT",
+        10,
+        {
+            "PLEASE DON'T HURT ME.",
+            
+            "IF YOU'RE LOOKING TO HURT ME, I'M \n"
+            "NOT REALLY WORTH THE EFFORT.",
+            
+            "I DON'T KNOW ANYTHING.",
+            
+            "GO AWAY OR I'LL CALL THE GUARDS!",
+            
+            "I WISH SOMETIMES THAT ALL THESE \n"
+            "REBELS WOULD JUST LEARN THEIR \n"
+            "PLACE AND STOP THIS NONSENSE.",
+
+            "JUST LEAVE ME ALONE, OK?",
+
+            "I'M NOT SURE, BUT SOMETIMES I THINK \n"
+            "THAT I KNOW SOME OF THE ACOLYTES.",
+
+            "THE ORDER'S GOT EVERYTHING AROUND HERE PRETTY WELL LOCKED UP TIGHT.",
+
+            "THERE'S NO WAY THAT THIS IS JUST A \n"
+            "SECURITY FORCE.",
+
+            "I'VE HEARD THAT THE ORDER IS REALLY \n"
+            "NERVOUS ABOUT THE FRONT'S \n"
+            "ACTIONS AROUND HERE."
+        }
+    },
+    // Rebel
+    {
+        "REBEL",
+        10,
+        {
+            "THERE'S NO WAY THE ORDER WILL \n"
+            "STAND AGAINST US.",
+
+            "WE'RE ALMOST READY TO STRIKE. \n"
+            "MACIL'S PLANS ARE FALLING IN PLACE.",
+
+            "WE'RE ALL BEHIND YOU, DON'T WORRY.",
+
+            "DON'T GET TOO CLOSE TO ANY OF THOSE BIG ROBOTS. THEY'LL MELT YOU DOWN \n"
+            "FOR SCRAP!",
+
+            "THE DAY OF OUR GLORY WILL SOON \n"
+            "COME, AND THOSE WHO OPPOSE US WILL \n"
+            "BE CRUSHED!",
+
+            "DON'T GET TOO COMFORTABLE. WE'VE \n"
+            "STILL GOT OUR WORK CUT OUT FOR US.",
+
+            "MACIL SAYS THAT YOU'RE THE NEW \n"
+            "HOPE. BEAR THAT IN MIND.",
+
+            "ONCE WE'VE TAKEN THESE CHARLATANS DOWN, WE'LL BE ABLE TO REBUILD THIS "
+            "WORLD AS IT SHOULD BE.",
+
+            "REMEMBER THAT YOU AREN'T FIGHTING \n"
+            "JUST FOR YOURSELF, BUT FOR \n"
+            "EVERYONE HERE AND OUTSIDE.",
+
+            "AS LONG AS ONE OF US STILL STANDS, \n"
+            "WE WILL WIN."
+        }
+    },
+    // Acolyte
+    {
+        "AGUARD",
+        10,
+        {
+            "MOVE ALONG,  PEASANT.",
+
+            "FOLLOW THE TRUE FAITH, ONLY THEN \n"
+            "WILL YOU BEGIN TO UNDERSTAND.",
+
+            "ONLY THROUGH DEATH CAN ONE BE \n"
+            "TRULY REBORN.",
+
+            "I'M NOT INTERESTED IN YOUR USELESS \n"
+            "DRIVEL.",
+
+            "IF I HAD WANTED TO TALK TO YOU I \n"
+            "WOULD HAVE TOLD YOU SO.",
+
+            "GO AND ANNOY SOMEONE ELSE!",
+
+            "KEEP MOVING!",
+
+            "IF THE ALARM GOES OFF, JUST STAY OUT OF OUR WAY!",
+
+            "THE ORDER WILL CLEANSE THE WORLD \n"
+            "AND USHER IT INTO THE NEW ERA.",
+
+            "PROBLEM?  NO, I THOUGHT NOT.",
+        }
+    },
+    // Beggar
+    {
+        "BEGGAR",
+        10,
+        {
+            "ALMS FOR THE POOR?",
+
+            "WHAT ARE YOU LOOKING AT, SURFACER?",
+
+            "YOU WOULDN'T HAVE ANY EXTRA FOOD, WOULD YOU?",
+
+            "YOU  SURFACE PEOPLE WILL NEVER \n"
+            "                                                                 "
+            "                                      UNDERSTAND US.",
+
+            "HA, THE GUARDS CAN'T FIND US.  THOSE \n"
+            "IDIOTS DON'T EVEN KNOW WE EXIST.",
+
+            "ONE DAY EVERYONE BUT THOSE WHO SERVE THE ORDER WILL BE FORCED TO "
+            "  JOIN US.",
+
+            "STARE NOW,  BUT YOU KNOW THAT THIS WILL BE YOUR OWN FACE ONE DAY.",
+
+            // Note: "NOTHING THING" is an authentic typo
+            "THERE'S NOTHING THING MORE \n"
+            "ANNOYING THAN A SURFACER WITH AN ATTITUDE!",
+
+            "THE ORDER WILL MAKE SHORT WORK OF YOUR PATHETIC FRONT.",
+
+            "WATCH YOURSELF SURFACER. WE KNOW OUR ENEMIES!"
+        }
+    },
+    // Templar
+    {
+        "PGUARD",
+        10,
+        {
+            "WE ARE THE HANDS OF FATE. TO EARN \n"
+            "OUR WRATH IS TO FIND OBLIVION!",
+
+            "THE ORDER WILL CLEANSE THE WORLD \n"
+            "OF THE WEAK AND CORRUPT!",
+
+            "OBEY THE WILL OF THE MASTERS!",
+
+            "LONG LIFE TO THE BROTHERS OF THE \n"
+            "ORDER!",
+
+            "FREE WILL IS AN ILLUSION THAT BINDS \n"
+            "THE WEAK MINDED.",
+
+            "POWER IS THE PATH TO GLORY. TO \n"
+            "FOLLOW THE ORDER IS TO WALK THAT \n"
+            "PATH!",
+
+            "TAKE YOUR PLACE AMONG THE \n"
+            "RIGHTEOUS, JOIN US!",
+
+            "THE ORDER PROTECTS ITS OWN.",
+
+            "ACOLYTES?  THEY HAVE YET TO SEE THE FULL GLORY OF THE ORDER.",
+
+            "IF THERE IS ANY HONOR INSIDE THAT \n"
+            "PATHETIC SHELL OF A BODY, \n"
+            "YOU'LL ENTER INTO THE ARMS OF THE \n"
+            "ORDER."
+        }
+    }
+};
+
+// And again, this could have been a define, but was a variable.
+static int numrndmessages = arrlen(rndMessages);
+
+//=============================================================================
+//
+// Dialog Menu Structure
+//
+// The Strife dialog system is actually just a serious abuse of the DOOM menu
+// engine. Hence why it doesn't work in multiplayer games or during demo
+// recording.
+//
+
+#define NUMDIALOGMENUITEMS 6
+
+static void P_DialogDrawer(void);
+
+static menuitem_t dialogmenuitems[] =
+{
+    { 1, "", P_DialogDoChoice, '1' }, // These items are loaded dynamically
+    { 1, "", P_DialogDoChoice, '2' },
+    { 1, "", P_DialogDoChoice, '3' },
+    { 1, "", P_DialogDoChoice, '4' },
+    { 1, "", P_DialogDoChoice, '5' },
+    { 1, "", P_DialogDoChoice, '6' }  // Item 6 is always the dismissal item
+};
+
+static menu_t dialogmenu =
+{
+    NUMDIALOGMENUITEMS, 
+    NULL, 
+    dialogmenuitems, 
+    P_DialogDrawer, 
+    42, 
+    75, 
+    0
+};
+
+// Lump number of the dialog background picture, if any.
+static int dialogbgpiclumpnum;
+
+// Name of current speaking character.
+static char *dialogname;
+
+// Current dialog text.
+static const char *dialogtext;
+
+//=============================================================================
+//
+// Routines
+//
+
+//
+// P_ParseDialogLump
+//
+// haleyjd 09/02/10: This is an original function added to parse out the 
+// dialogs from the dialog lump rather than reading them raw from the lump 
+// pointer. This avoids problems with structure packing.
+//
+static void P_ParseDialogLump(byte *lump, mapdialog_t **dialogs, 
+                              int numdialogs, int tag)
+{
+    int i;
+    byte *rover = lump;
+
+    *dialogs = Z_Malloc(numdialogs * sizeof(mapdialog_t), tag, NULL);
+
+    for(i = 0; i < numdialogs; i++)
+    {
+        int j;
+        mapdialog_t *curdialog = &((*dialogs)[i]);
+
+        DIALOG_INT(curdialog->speakerid,    rover);
+        DIALOG_INT(curdialog->dropitem,     rover);
+        DIALOG_INT(curdialog->checkitem[0], rover);
+        DIALOG_INT(curdialog->checkitem[1], rover);
+        DIALOG_INT(curdialog->checkitem[2], rover);
+        DIALOG_INT(curdialog->jumptoconv,   rover);
+        DIALOG_STR(curdialog->name,         rover, MDLG_NAMELEN);
+        DIALOG_STR(curdialog->voice,        rover, MDLG_LUMPLEN);
+        DIALOG_STR(curdialog->backpic,      rover, MDLG_LUMPLEN);
+        DIALOG_STR(curdialog->text,         rover, MDLG_TEXTLEN);
+
+        // copy choices
+        for(j = 0; j < 5; j++)
+        {
+            mapdlgchoice_t *curchoice = &(curdialog->choices[j]);
+            DIALOG_INT(curchoice->giveitem,         rover);
+            DIALOG_INT(curchoice->needitems[0],     rover);
+            DIALOG_INT(curchoice->needitems[1],     rover);
+            DIALOG_INT(curchoice->needitems[2],     rover);
+            DIALOG_INT(curchoice->needamounts[0],   rover);
+            DIALOG_INT(curchoice->needamounts[1],   rover);
+            DIALOG_INT(curchoice->needamounts[2],   rover);
+            DIALOG_STR(curchoice->text,             rover, MDLG_CHOICELEN);
+            DIALOG_STR(curchoice->textok,           rover, MDLG_MSGLEN);
+            DIALOG_INT(curchoice->next,             rover);
+            DIALOG_INT(curchoice->objective,        rover);
+            DIALOG_STR(curchoice->textno,           rover, MDLG_MSGLEN);
+        }
+    }
+}
+
+//
+// P_DialogLoad
+//
+// [STRIFE] New function
+// haleyjd 09/02/10: Loads the dialog script for the current map. Also loads 
+// SCRIPT00 if it has not yet been loaded.
+//
+void P_DialogLoad(void)
+{
+    char lumpname[9];
+    int  lumpnum;
+
+    // load the SCRIPTxy lump corresponding to MAPxy, if it exists.
+    DEH_snprintf(lumpname, sizeof(lumpname), "script%02d", gamemap);
+    if((lumpnum = W_CheckNumForName(lumpname)) == -1)
+        numleveldialogs = 0;
+    else
+    {
+        byte *leveldialogptr = W_CacheLumpNum(lumpnum, PU_STATIC);
+        numleveldialogs = W_LumpLength(lumpnum) / ORIG_MAPDIALOG_SIZE;
+        P_ParseDialogLump(leveldialogptr, &leveldialogs, numleveldialogs, 
+                          PU_LEVEL);
+        Z_Free(leveldialogptr); // haleyjd: free the original lump
+    }
+
+    // also load SCRIPT00 if it has not been loaded yet
+    if(!script0loaded)
+    {
+        byte *script0ptr;
+
+        script0loaded = true; 
+        // BUG: Rogue should have used W_GetNumForName here...
+        lumpnum = W_CheckNumForName(DEH_String("script00")); 
+        script0ptr = W_CacheLumpNum(lumpnum, PU_STATIC);
+        numscript0dialogs = W_LumpLength(lumpnum) / ORIG_MAPDIALOG_SIZE;
+        P_ParseDialogLump(script0ptr, &script0dialogs, numscript0dialogs,
+                          PU_STATIC);
+        Z_Free(script0ptr); // haleyjd: free the original lump
+    }
+}
+
+//
+// P_PlayerHasItem
+//
+// [STRIFE] New function
+// haleyjd 09/02/10: Checks for inventory items, quest flags, etc. for dialogs.
+// Returns the amount possessed, or 0 if none.
+//
+int P_PlayerHasItem(player_t *player, mobjtype_t type)
+{
+    int i;
+
+    if(type > 0)
+    {
+        // check keys
+        if(type >= MT_KEY_BASE && type < MT_INV_SHADOWARMOR)
+            return (player->cards[type - MT_KEY_BASE]);
+
+        // check sigil pieces
+        if(type >= MT_SIGIL_A && type <= MT_SIGIL_E)
+            return (type - MT_SIGIL_A <= player->sigiltype);
+
+        // check quest tokens
+        if(type >= MT_TOKEN_QUEST1 && type <= MT_TOKEN_QUEST31)
+            return (player->questflags & (1 << (type - MT_TOKEN_QUEST1)));
+
+        // check inventory
+        for(i = 0; i < 32; i++)
+        {
+            if(type == player->inventory[i].type)
+                return player->inventory[i].amount;
+        }
+    }
+    return 0;
+}
+
+//
+// P_DialogFind
+//
+// [STRIFE] New function
+// haleyjd 09/03/10: Looks for a dialog definition matching the given 
+// Script ID # for an mobj.
+//
+mapdialog_t *P_DialogFind(mobjtype_t type, int jumptoconv)
+{
+    int i;
+
+    // check the map-specific dialogs first
+    for(i = 0; i < numleveldialogs; i++)
+    {
+        if(type == leveldialogs[i].speakerid)
+        {
+            if(jumptoconv <= 1)
+                return &leveldialogs[i];
+            else
+                --jumptoconv;
+        }
+    }
+
+    // check SCRIPT00 dialogs next
+    for(i = 0; i < numscript0dialogs; i++)
+    {
+        if(type == script0dialogs[i].speakerid)
+            return &script0dialogs[i];
+    }
+
+    // the default dialog is script 0 in the SCRIPT00 lump.
+    return &script0dialogs[0];
+}
+
+//
+// P_DialogGetStates
+//
+// [STRIFE] New function
+// haleyjd 09/03/10: Find the set of special dialog states (greetings, yes, no)
+// for a particular thing type.
+//
+static dialogstateset_t *P_DialogGetStates(mobjtype_t type)
+{
+    int i;
+
+    // look for a match by type
+    for(i = 0; i < numdialogstatesets; i++)
+    {
+        if(type == dialogstatesets[i].type)
+            return &dialogstatesets[i];
+    }
+
+    // return the default 0 record if no match.
+    return &dialogstatesets[0];
+}
+
+//
+// P_DialogGetMsg
+//
+// [STRIFE] New function
+// haleyjd 09/03/10: Redirects dialog messages when the script indicates that
+// the actor should use a random message stored in the executable instead.
+//
+static const char *P_DialogGetMsg(const char *message)
+{
+    // if the message starts with "RANDOM"...
+    if(!strncasecmp(message, DEH_String("RANDOM"), 6))
+    {
+        int i;
+        const char *nameloc = message + 7;
+
+        // look for a match in rndMessages for the string starting 
+        // 7 chars after "RANDOM_"
+        for(i = 0; i < numrndmessages; i++)
+        {
+            if(!strncasecmp(nameloc, rndMessages[i].type_name, 4))
+            {
+                // found a match, so return a random message
+                int rnd = M_Random();
+                int nummessages = rndMessages[i].nummessages;
+                return DEH_String(rndMessages[i].messages[rnd % nummessages]);
+            }
+        }
+    }
+
+    // otherwise, just return the message passed in.
+    return message;
+}
+
+//
+// P_GiveInventoryItem
+//
+// [STRIFE] New function
+// haleyjd 09/03/10: Give an inventory item to the player, if possible.
+// villsa 09/09/10: Fleshed out routine
+//
+boolean P_GiveInventoryItem(player_t *player, int sprnum, mobjtype_t type)
+{
+    int curinv = 0;
+    int i;
+    boolean ok = false;
+    mobjtype_t item = 0;
+    inventory_t* invtail;
+
+    // repaint the status bar due to inventory changing
+    player->st_update = true;
+
+    while(1)
+    {
+        // inventory is full
+        if(curinv > player->numinventory)
+            return true;
+
+        item = player->inventory[curinv].type;
+        if(type < item)
+        {
+            if(curinv != MAXINVENTORYSLOTS)
+            {
+                // villsa - sort inventory item if needed
+                invtail = &player->inventory[player->numinventory - 1];
+                if(player->numinventory >= (curinv + 1))
+                {
+                    for(i = player->numinventory; i >= (curinv + 1); --i)    
+                    {
+                        invtail[1].sprite   = invtail[0].sprite;
+                        invtail[1].type     = invtail[0].type;
+                        invtail[1].amount   = invtail[0].amount;
+
+                        invtail--;
+                    }
+                }
+
+                // villsa - add inventory item
+                player->inventory[curinv].amount = 1;
+                player->inventory[curinv].sprite = sprnum;
+                player->inventory[curinv].type = type;
+
+                // sort cursor if needed
+                if(player->numinventory)
+                {
+                    if(curinv <= player->inventorycursor)
+                        player->inventorycursor++;
+                }
+
+                player->numinventory++;
+
+                return true;
+            }
+
+            return false;
+        }
+
+        if(type == item)
+            break;
+
+        curinv++;
+    }
+
+    // check amount of inventory item by using the mass from mobjinfo
+    if(player->inventory[curinv].amount < mobjinfo[item].mass)
+    {
+        player->inventory[curinv].amount++;
+        ok = true;
+    }
+    else
+        ok = false;
+
+    return ok;
+}
+
+//
+// P_GiveItemToPlayer
+//
+// [STRIFE] New function
+// haleyjd 09/03/10: Sorts out how to give something to the player.
+// Not strictly just for inventory items.
+// villsa 09/09/10: Fleshed out function
+//
+boolean P_GiveItemToPlayer(player_t *player, int sprnum, mobjtype_t type)
+{
+    int i = 0;
+    line_t junk;
+    int sound = sfx_itemup; // haleyjd 09/21/10: different sounds for items
+
+    // set quest if mf_givequest flag is set
+    if(mobjinfo[type].flags & MF_GIVEQUEST)
+        player->questflags |= 1 << (mobjinfo[type].speed - 1);
+
+    // check for keys
+    if(type >= MT_KEY_BASE && type <= MT_NEWKEY5)
+    {
+        P_GiveCard(player, type - MT_KEY_BASE);
+        return true;
+    }
+
+    // check for quest tokens
+    if(type >= MT_TOKEN_QUEST1 && type <= MT_TOKEN_QUEST31)
+    {
+        if(mobjinfo[type].name)
+        {
+            M_StringCopy(pickupstring, DEH_String(mobjinfo[type].name), 39);
+            player->message = pickupstring;
+        }
+        player->questflags |= 1 << (type - MT_TOKEN_QUEST1);
+
+        if(player == &players[consoleplayer])
+            S_StartSound(NULL, sound);
+        return true;
+    }
+
+    // haleyjd 09/22/10: Refactored to give sprites higher priority than
+    // mobjtypes and to implement missing logic.
+    switch(sprnum)
+    {
+    case SPR_HELT: // This is given only by the "DONNYTRUMP" cheat (aka Midas)
+        P_GiveInventoryItem(player, SPR_HELT, MT_TOKEN_TOUGHNESS);
+        P_GiveInventoryItem(player, SPR_GUNT, MT_TOKEN_ACCURACY);
+
+        // [STRIFE] Bizarre...
+        for(i = 0; i < 5 * player->accuracy + 300; i++)
+            P_GiveInventoryItem(player, SPR_COIN, MT_MONY_1);
+        break;
+
+    case SPR_ARM1: // Armor 1
+        if(!P_GiveArmor(player, -2))
+            P_GiveInventoryItem(player, sprnum, type);
+        break;
+
+    case SPR_ARM2: // Armor 2
+        if(!P_GiveArmor(player, -1))
+            P_GiveInventoryItem(player, sprnum, type);
+        break;
+
+    case SPR_COIN: // 1 Gold
+        P_GiveInventoryItem(player, SPR_COIN, MT_MONY_1);
+        break;
+
+    case SPR_CRED: // 10 Gold
+        for(i = 0; i < 10; i++)
+            P_GiveInventoryItem(player, SPR_COIN, MT_MONY_1);
+        break;
+
+    case SPR_SACK: // 25 gold
+        for(i = 0; i < 25; i++)
+            P_GiveInventoryItem(player, SPR_COIN, MT_MONY_1);
+        break;
+
+    case SPR_CHST: // 50 gold
+        for(i = 0; i < 50; i++)
+            P_GiveInventoryItem(player, SPR_COIN, MT_MONY_1);
+        break; // haleyjd 20141215: missing break, caused Rowan to not take ring from you.
+
+    case SPR_BBOX: // Box of Bullets
+        if(!P_GiveAmmo(player, am_bullets, 5))
+            return false;
+        break;
+
+    case SPR_BLIT: // Bullet Clip
+        if(!P_GiveAmmo(player, am_bullets, 1))
+            return false;
+        break;
+
+    case SPR_PMAP: // Map powerup
+        if(!P_GivePower(player, pw_allmap))
+            return false;
+        sound = sfx_yeah; // bluh-doop!
+        break;
+
+    case SPR_COMM: // Communicator
+        if(!P_GivePower(player, pw_communicator))
+            return false;
+        sound = sfx_yeah; // bluh-doop!
+        break;
+
+    case SPR_MSSL: // Mini-missile
+        if(!P_GiveAmmo(player, am_missiles, 1))
+            return false;
+        break;
+
+    case SPR_ROKT: // Crate of missiles
+        if(!P_GiveAmmo(player, am_missiles, 5))
+            return false;
+        break;
+
+    case SPR_BRY1: // Battery cell
+        if(!P_GiveAmmo(player, am_cell, 1))
+            return false;
+        break;
+
+    case SPR_CPAC: // Cell pack
+        if(!P_GiveAmmo(player, am_cell, 5))
+            return false;
+        break;
+
+    case SPR_PQRL: // Poison bolts
+        if(!P_GiveAmmo(player, am_poisonbolts, 5))
+            return false;
+        break;
+
+    case SPR_XQRL: // Electric bolts
+        if(!P_GiveAmmo(player, am_elecbolts, 5))
+            return false;
+        break;
+
+    case SPR_GRN1: // HE Grenades
+        if(!P_GiveAmmo(player, am_hegrenades, 1))
+            return false;
+        break;
+
+    case SPR_GRN2: // WP Grenades
+        if(!P_GiveAmmo(player, am_wpgrenades, 1))
+            return false;
+        break;
+
+    case SPR_BKPK: // Backpack (aka Ammo Satchel)
+        if(!player->backpack)
+        {
+            for(i = 0; i < NUMAMMO; i++)
+                player->maxammo[i] *= 2;
+
+            player->backpack = true;
+        }
+        for(i = 0; i < NUMAMMO; i++)
+            P_GiveAmmo(player, i, 1);
+        break;
+
+    case SPR_RIFL: // Assault Rifle
+        if(player->weaponowned[wp_rifle])
+            return false;
+
+        if(!P_GiveWeapon(player, wp_rifle, false))
+            return false;
+        
+        sound = sfx_wpnup; // SHK-CHK!
+        break;
+
+    case SPR_FLAM: // Flamethrower
+        if(player->weaponowned[wp_flame])
+            return false;
+
+        if(!P_GiveWeapon(player, wp_flame, false))
+            return false;
+
+        sound = sfx_wpnup; // SHK-CHK!
+        break;
+
+    case SPR_MMSL: // Mini-missile Launcher
+        if(player->weaponowned[wp_missile])
+            return false;
+
+        if(!P_GiveWeapon(player, wp_missile, false))
+            return false;
+
+        sound = sfx_wpnup; // SHK-CHK!
+        break;
+
+    case SPR_TRPD: // Mauler
+        if(player->weaponowned[wp_mauler])
+            return false;
+
+        if(!P_GiveWeapon(player, wp_mauler, false))
+            return false;
+
+        sound = sfx_wpnup; // SHK-CHK!
+        break;
+
+    case SPR_CBOW: // Here's a crossbow. Just aim straight, and *SPLAT!*
+        if(player->weaponowned[wp_elecbow])
+            return false;
+
+        if(!P_GiveWeapon(player, wp_elecbow, false))
+            return false;
+
+        sound = sfx_wpnup; // SHK-CHK!
+        break;
+
+    case SPR_TOKN: // Miscellaneous items - These are determined by thingtype.
+        switch(type)
+        {
+        case MT_KEY_HAND: // Severed hand
+            P_GiveCard(player, key_SeveredHand);
+            break;
+
+        case MT_MONY_300: // 300 Gold (this is the only way to get it, in fact)
+            for(i = 0; i < 300; i++)
+                P_GiveInventoryItem(player, SPR_COIN, MT_MONY_1);
+            break;
+
+        case MT_TOKEN_AMMO: // Ammo token - you get this from the Weapons Trainer
+            if(player->ammo[am_bullets] >= 50)
+                return false;
+
+            player->ammo[am_bullets] = 50;
+            break;
+
+        case MT_TOKEN_HEALTH: // Health token - from the Front's doctor
+            if(!P_GiveBody(player, healthamounts[gameskill]))
+                return false;
+            break;
+
+        case MT_TOKEN_ALARM: // Alarm token - particularly from the Oracle.
+            P_NoiseAlert(player->mo, player->mo);
+            A_AlertSpectreC(dialogtalker); // BUG: assumes in a dialog o_O
+            break;
+
+        case MT_TOKEN_DOOR1: // Door special 1
+            junk.tag = 222;
+            EV_DoDoor(&junk, vld_open);
+            break;
+
+        case MT_TOKEN_PRISON_PASS: // Door special 1 - Prison pass
+            junk.tag = 223;
+            EV_DoDoor(&junk, vld_open);
+            if(gamemap == 2) // If on Tarnhill, give Prison pass object
+                P_GiveInventoryItem(player, sprnum, type);
+            break;
+
+        case MT_TOKEN_SHOPCLOSE: // Door special 3 - "Shop close" - unused?
+            junk.tag = 222;
+            EV_DoDoor(&junk, vld_close);
+            break;
+
+        case MT_TOKEN_DOOR3: // Door special 4 (or 3? :P ) 
+            junk.tag = 224;
+            EV_DoDoor(&junk, vld_close);
+            break;
+
+        case MT_TOKEN_STAMINA: // Stamina upgrade
+            if(player->stamina >= 100)
+                return false;
+
+            player->stamina += 10;
+            P_GiveBody(player, 200); // full healing
+            break;
+
+        case MT_TOKEN_NEW_ACCURACY: // Accuracy upgrade
+            if(player->accuracy >= 100)
+                return false;
+
+            player->accuracy += 10;
+            break;
+
+        case MT_SLIDESHOW: // Slideshow (start a finale)
+            gameaction = ga_victory;
+            if(gamemap == 10)
+                P_GiveItemToPlayer(player, SPR_TOKN, MT_TOKEN_QUEST17);
+            break;
+        
+        default: // The default is to just give it as an inventory item.
+            P_GiveInventoryItem(player, sprnum, type);
+            break;
+        }
+        break;
+
+    default: // The ultimate default: Give it as an inventory item.
+        if(!P_GiveInventoryItem(player, sprnum, type))
+            return false;
+        break;
+    }
+
+    // Play sound.
+    if(player == &players[consoleplayer])
+        S_StartSound(NULL, sound);
+
+    return true;
+}
+
+//
+// P_TakeDialogItem
+//
+// [STRIFE] New function
+// haleyjd 09/03/10: Removes needed items from the player's inventory.
+//
+static void P_TakeDialogItem(player_t *player, int type, int amount)
+{
+    int i;
+
+    if(amount <= 0)
+        return;
+
+    for(i = 0; i < player->numinventory; i++)
+    {
+        // find a matching item
+        if(type != player->inventory[i].type)
+            continue;
+
+        // if there is none left...
+        if((player->inventory[i].amount -= amount) < 1)
+        {
+            // ...shift everything above it down
+            int j;
+
+            // BUG: They should have stopped at j < numinventory. This
+            // seems to implicitly assume that numinventory is always at
+            // least one less than the max # of slots, otherwise it 
+            // pulls in data from the following player_t fields:
+            // st_update, numinventory, inventorycursor, accuracy, stamina
+            for(j = i + 1; j <= player->numinventory; j++)
+            {
+                inventory_t *item1 = &(player->inventory[j - 1]);
+                inventory_t *item2 = &(player->inventory[j]);
+
+                *item1 = *item2;
+            }
+
+            // blank the topmost slot
+            // BUG: This will overwrite the aforementioned fields if
+            // numinventory is equal to the number of slots!
+            // STRIFE-TODO: Overflow emulation?
+            player->inventory[player->numinventory].type = NUMMOBJTYPES;
+            player->inventory[player->numinventory].sprite = -1;
+            player->numinventory--;
+
+            // update cursor position
+            if(player->inventorycursor >= player->numinventory)
+            {
+                if(player->inventorycursor)
+                    player->inventorycursor--;
+            }
+        } // end if
+        
+        return; // done!
+
+    } // end for
+}
+
+//
+// P_DialogDrawer
+//
+// This function is set as the drawer callback for the dialog menu.
+//
+static void P_DialogDrawer(void)
+{
+    angle_t angle;
+    int y;
+    int i;
+    int height;
+    int finaly;
+    char choicetext[64];
+    char choicetext2[64];
+
+    // Run down bonuscount faster than usual so that flashes from being given
+    // items are less obvious.
+    if(dialogplayer->bonuscount)
+    {
+        dialogplayer->bonuscount -= 3;
+        if(dialogplayer->bonuscount < 0)
+            dialogplayer->bonuscount = 0;
+    }
+
+    angle = R_PointToAngle2(dialogplayer->mo->x,
+                            dialogplayer->mo->y,
+                            dialogtalker->x,
+                            dialogtalker->y);
+    angle -= dialogplayer->mo->angle;
+
+    // Dismiss the dialog if the player is out of alignment, or the thing he was
+    // talking to is now engaged in battle.
+    if ((angle > ANG45 && angle < (ANG270+ANG45))
+     || (dialogtalker->flags & MF_NODIALOG) != 0)
+    {
+        P_DialogDoChoice(dialogmenu.numitems - 1);
+    }
+
+    dialogtalker->reactiontime = 2;
+
+    // draw background
+    if(dialogbgpiclumpnum != -1)
+    {
+        patch_t *patch = W_CacheLumpNum(dialogbgpiclumpnum, PU_CACHE);
+        V_DrawPatchDirect(0, 0, patch);
+    }
+
+    // if there's a valid background pic, delay drawing the rest of the menu 
+    // for a while; otherwise, it will appear immediately
+    if(dialogbgpiclumpnum == -1 || menupausetime <= gametic)
+    {
+        if(menuindialog)
+        {
+            // time to pause the game?
+            if(menupausetime + 3 < gametic)
+                menupause = true;
+        }
+
+        // draw character name
+        M_WriteText(12, 18, dialogname);
+        y = 28;
+
+        // show text (optional for dialogs with voices)
+        if(dialogshowtext || currentdialog->voice[0] == '\0')
+            y = M_WriteText(20, 28, dialogtext);
+
+        height = 20 * dialogmenu.numitems;
+
+        finaly = 175 - height;     // preferred height
+        if(y > finaly)
+            finaly = 199 - height; // height it will bump down to if necessary.
+
+        // draw divider
+        M_WriteText(42, finaly - 6, DEH_String("______________________________"));
+
+        dialogmenu.y = finaly + 6;
+        y = 0;
+
+        // draw the menu items
+        for(i = 0; i < dialogmenu.numitems - 1; i++)
+        {
+            DEH_snprintf(choicetext, sizeof(choicetext),
+                         "%d) %s", i + 1, currentdialog->choices[i].text);
+            
+            // alternate text for items that need money
+            if(currentdialog->choices[i].needamounts[0] > 0)
+            {
+                // haleyjd 20120401: necessary to avoid undefined behavior:
+                M_StringCopy(choicetext2, choicetext, sizeof(choicetext2));
+                DEH_snprintf(choicetext, sizeof(choicetext),
+                             "%s for %d", choicetext2,
+                             currentdialog->choices[i].needamounts[0]);
+            }
+
+            M_WriteText(dialogmenu.x, dialogmenu.y + 3 + y, choicetext);
+            y += 19;
+        }
+
+        // draw the final item for dismissing the dialog
+        M_WriteText(dialogmenu.x, 19 * i + dialogmenu.y + 3, dialoglastmsgbuffer);
+    }
+}
+
+//
+// P_DialogDoChoice
+//
+// [STRIFE] New function
+// haleyjd 09/05/10: Handles making a choice in a dialog. Installed as the
+// callback for all items in the dialogmenu structure.
+//
+void P_DialogDoChoice(int choice)
+{
+    int i = 0, nextdialog = 0;
+    boolean candochoice = true;
+    char *message = NULL;
+    mapdlgchoice_t *currentchoice;
+
+    if(choice == -1)
+        choice = dialogmenu.numitems - 1;
+
+    currentchoice = &(currentdialog->choices[choice]);
+
+    I_StartVoice(NULL); // STRIFE-TODO: verify (should stop previous voice I believe)
+
+    // villsa 09/08/10: converted into for loop
+    for(i = 0; i < MDLG_MAXITEMS; i++)
+    {
+        if(P_PlayerHasItem(dialogplayer, currentchoice->needitems[i]) <
+                                         currentchoice->needamounts[i])
+        {
+            candochoice = false; // nope, missing something
+        }
+    }
+
+    if(choice != dialogmenu.numitems - 1 && candochoice)
+    {
+        int item;
+
+        message = currentchoice->textok;
+        if(dialogtalkerstates->yes)
+            P_SetMobjState(dialogtalker, dialogtalkerstates->yes);
+
+        item = currentchoice->giveitem;
+        if(item < 0 || 
+           P_GiveItemToPlayer(dialogplayer, 
+                              states[mobjinfo[item].spawnstate].sprite, 
+                              item))
+        {
+            // if successful, take needed items
+            int count = 0;
+            // villsa 09/08/10: converted into for loop
+            for(count = 0; count < MDLG_MAXITEMS; count++)
+            {
+                P_TakeDialogItem(dialogplayer, 
+                                 currentchoice->needitems[count],
+                                 currentchoice->needamounts[count]);
+            }
+        }
+        else
+            message = DEH_String("You seem to have enough!");
+
+        // store next dialog into the talking actor
+        nextdialog = currentchoice->next;
+        if(nextdialog != 0)
+            dialogtalker->miscdata = (byte)(abs(nextdialog));
+    }
+    else
+    {
+        // not successful
+        message = currentchoice->textno;
+        if(dialogtalkerstates->no)
+            P_SetMobjState(dialogtalker, dialogtalkerstates->no);
+    }
+    
+    if(choice != dialogmenu.numitems - 1)
+    {
+        int objective;
+        char *objlump;
+
+        if((objective = currentchoice->objective))
+        {
+            DEH_snprintf(mission_objective, OBJECTIVE_LEN, "log%i", objective);
+            objlump = W_CacheLumpName(mission_objective, PU_CACHE);
+            M_StringCopy(mission_objective, objlump, OBJECTIVE_LEN);
+        }
+        // haleyjd 20130301: v1.31 hack: if first char of message is a period,
+        // clear the player's message. Is this actually used anywhere?
+        if(gameversion == exe_strife_1_31 && message[0] == '.')
+            message = NULL;
+        dialogplayer->message = message;
+    }
+
+    dialogtalker->angle = dialogtalkerangle;
+    dialogplayer->st_update = true;
+    M_ClearMenus(0);
+
+    if(nextdialog >= 0 || gameaction == ga_victory) // Macil hack
+        menuindialog = false;
+    else
+        P_DialogStart(dialogplayer);
+}
+
+//
+// P_DialogStartP1
+//
+// [STRIFE] New function
+// haleyjd 09/13/10: This is a hack used by the finale system.
+//
+void P_DialogStartP1(void)
+{
+    P_DialogStart(&players[0]);
+}
+
+//
+// P_DialogStart
+//
+// villsa [STRIFE] New function
+//
+void P_DialogStart(player_t *player)
+{
+    int i = 0;
+    int pic;
+    int rnd = 0;
+    char* byetext;
+    int jumptoconv;
+
+    if(menuactive || netgame)
+        return;
+
+    // are we facing towards our NPC?
+    P_AimLineAttack(player->mo, player->mo->angle, (128*FRACUNIT));
+    if(!linetarget)
+    {
+        P_AimLineAttack(player->mo, player->mo->angle + (ANG90/16), (128*FRACUNIT));
+        if(!linetarget)
+            P_AimLineAttack(player->mo, player->mo->angle - (ANG90/16), (128*FRACUNIT));
+    }
+
+    if(!linetarget)
+       return;
+
+    // already in combat, can't talk to it
+    if(linetarget->flags & MF_NODIALOG)
+       return;
+
+    // set pointer to the character talking
+    dialogtalker = linetarget;
+
+    // play a sound
+    if(player == &players[consoleplayer])
+       S_StartSound(0, sfx_radio);
+
+    linetarget->target = player->mo;         // target the player
+    dialogtalker->reactiontime = 2;          // set reactiontime
+    dialogtalkerangle = dialogtalker->angle; // remember original angle
+
+    // face talker towards player
+    A_FaceTarget(dialogtalker);
+
+    // face towards NPC's direction
+    player->mo->angle = R_PointToAngle2(player->mo->x,
+                                        player->mo->y,
+                                        dialogtalker->x,
+                                        dialogtalker->y);
+    // set pointer to player talking
+    dialogplayer = player;
+
+    // haleyjd 09/08/10: get any stored dialog state from this object
+    jumptoconv = linetarget->miscdata;
+
+    // check item requirements
+    while(1)
+    {
+        int i = 0;
+        currentdialog = P_DialogFind(linetarget->type, jumptoconv);
+
+        // dialog's jumptoconv equal to 0? There's nothing to jump to.
+        if(currentdialog->jumptoconv == 0)
+            break;
+
+        // villsa 09/08/10: converted into for loop
+        for(i = 0; i < MDLG_MAXITEMS; i++)
+        {
+            // if the item is non-zero, the player must have at least one in his
+            // or her inventory
+            if(currentdialog->checkitem[i] != 0 &&
+                P_PlayerHasItem(dialogplayer, currentdialog->checkitem[i]) < 1)
+                break;
+        }
+
+        if(i < MDLG_MAXITEMS) // didn't find them all? this is our dialog!
+            break;
+
+        jumptoconv = currentdialog->jumptoconv;
+    }
+
+    M_DialogDimMsg(20, 28, currentdialog->text, false);
+    dialogtext = P_DialogGetMsg(currentdialog->text);
+
+    // get states
+    dialogtalkerstates = P_DialogGetStates(linetarget->type);
+
+    // have talker greet the player
+    if(dialogtalkerstates->greet)
+        P_SetMobjState(dialogtalker, dialogtalkerstates->greet);
+
+    // get talker's name
+    if(currentdialog->name[0])
+        dialogname = currentdialog->name;
+    else
+    {
+        // use a fallback:
+        if(mobjinfo[linetarget->type].name)
+            dialogname = DEH_String(mobjinfo[linetarget->type].name); // mobjtype name
+        else
+            dialogname = DEH_String("Person"); // default name - like Joe in Doom 3 :P
+    }
+
+    // setup number of choices to choose from
+    for(i = 0; i < MDLG_MAXCHOICES; i++)
+    {
+        if(!currentdialog->choices[i].giveitem)
+            break;
+    }
+
+    // set number of choices to menu
+    dialogmenu.numitems = i + 1;
+
+    rnd = M_Random() % 3;
+
+    // setup dialog menu
+    M_StartControlPanel();
+    menupause = false;
+    menuindialog = true;
+    menupausetime = gametic + 17;
+    currentMenu = &dialogmenu;
+
+    if(i >= dialogmenu.lastOn)
+        itemOn = dialogmenu.lastOn;
+    else
+        itemOn = 0;
+
+    // get backdrop
+    pic = W_CheckNumForName(currentdialog->backpic);
+    dialogbgpiclumpnum = pic;
+    if(pic != -1)
+        V_DrawPatchDirect(0, 0, W_CacheLumpNum(pic, PU_CACHE));
+
+    // get voice
+    I_StartVoice(currentdialog->voice);
+
+    // get bye text
+    switch(rnd)
+    {
+    case 2:
+        byetext = DEH_String("BYE!");
+        break;
+    case 1:
+        byetext = DEH_String("Thanks, Bye!");
+        break;
+    default:
+    case 0:
+        byetext = DEH_String("See you later!");
+        break;
+    }
+
+    DEH_snprintf(dialoglastmsgbuffer, sizeof(dialoglastmsgbuffer),
+                 "%d) %s", i + 1, byetext);
+}
+
+// EOF
+
+
diff --git a/src/strife/p_dialog.h b/src/strife/p_dialog.h
index 0cf4c9b..08bef81 100644
--- a/src/strife/p_dialog.h
+++ b/src/strife/p_dialog.h
@@ -1,102 +1,102 @@
-//
-// Copyright(C) 1993-1996 Id Software, Inc.
-// Copyright(C) 1996 Rogue Entertainment / Velocity, Inc.
-// Copyright(C) 2010 James Haley, Samuel Villareal
-//
-// This program is free software; you can redistribute it and/or
-// modify it under the terms of the GNU General Public License
-// as published by the Free Software Foundation; either version 2
-// of the License, or (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-//
-// DESCRIPTION:
-//
-// [STRIFE] New Module
-//
-// Dialog Engine for Strife
-//
-
-#ifndef P_DIALOG_H__
-#define P_DIALOG_H__
-
-#define OBJECTIVE_LEN       300
-
-#define MAXINVENTORYSLOTS   30
-
-#define MDLG_CHOICELEN      32
-#define MDLG_MSGLEN         80
-#define MDLG_NAMELEN        16
-#define MDLG_LUMPLEN        8
-#define MDLG_TEXTLEN        320
-#define MDLG_MAXCHOICES     5
-#define MDLG_MAXITEMS       3
-
-extern char mission_objective[OBJECTIVE_LEN];
-
-extern int dialogshowtext;
-
-// villsa - convenient macro for giving objective logs to player
-#define GiveObjective(x, minlumpnum) \
-do { \
-  int obj_ln  = W_CheckNumForName(DEH_String(x)); \
-  if(obj_ln > minlumpnum) \
-    M_StringCopy(mission_objective, W_CacheLumpNum(obj_ln, PU_CACHE), \
-                 OBJECTIVE_LEN);\
-} while(0)
-
-// haleyjd - voice and objective in one
-#define GiveVoiceObjective(voice, log, minlumpnum) \
-do { \
-  int obj_ln = W_CheckNumForName(DEH_String(log)); \
-  I_StartVoice(DEH_String(voice)); \
-  if(obj_ln > minlumpnum) \
-    M_StringCopy(mission_objective, W_CacheLumpNum(obj_ln, PU_CACHE), \
-                 OBJECTIVE_LEN);\
-} while(0)
-
-typedef struct mapdlgchoice_s
-{
-    int  giveitem;                      // item given when successful
-    int  needitems[MDLG_MAXITEMS];      // item needed for success
-    int  needamounts[MDLG_MAXITEMS];    // amount of items needed
-    char text[MDLG_CHOICELEN];          // normal text
-    char textok[MDLG_MSGLEN];           // message given on success
-    int next;                           // next dialog?
-    int objective;                      // ???
-    char textno[MDLG_MSGLEN];           // message given on failure
-} mapdlgchoice_t;
-
-typedef struct mapdialog_s
-{
-    int speakerid;                      // script ID# for mobjtype that will use this dialog
-    int dropitem;                       // item to drop if that thingtype is killed
-    int checkitem[MDLG_MAXITEMS];       // item(s) needed to see this dialog
-    int jumptoconv;                     // conversation to jump to when... ?
-    char name[MDLG_NAMELEN];            // name of speaker
-    char voice[MDLG_LUMPLEN];           // voice file to play
-    char backpic[MDLG_LUMPLEN];         // backdrop pic for character, if any
-    char text[MDLG_TEXTLEN];            // main message text
-    
-    // options that this dialog gives the player
-    mapdlgchoice_t choices[MDLG_MAXCHOICES];
-} mapdialog_t;
-
-void         P_DialogLoad(void);
-void         P_DialogStart(player_t *player);
-void         P_DialogDoChoice(int choice);
-boolean      P_GiveItemToPlayer(player_t *player, int sprnum, mobjtype_t type);
-boolean      P_GiveInventoryItem(player_t *player, int sprnum, mobjtype_t type);
-boolean      P_UseInventoryItem(player_t* player, int item);
-void         P_DialogStartP1(void);
-mapdialog_t* P_DialogFind(mobjtype_t type, int jumptoconv);
-int          P_PlayerHasItem(player_t *player, mobjtype_t type);
-
-#endif
-
-// EOF
-
-
+//
+// Copyright(C) 1993-1996 Id Software, Inc.
+// Copyright(C) 1996 Rogue Entertainment / Velocity, Inc.
+// Copyright(C) 2010 James Haley, Samuel Villareal
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// DESCRIPTION:
+//
+// [STRIFE] New Module
+//
+// Dialog Engine for Strife
+//
+
+#ifndef P_DIALOG_H__
+#define P_DIALOG_H__
+
+#define OBJECTIVE_LEN       300
+
+#define MAXINVENTORYSLOTS   30
+
+#define MDLG_CHOICELEN      32
+#define MDLG_MSGLEN         80
+#define MDLG_NAMELEN        16
+#define MDLG_LUMPLEN        8
+#define MDLG_TEXTLEN        320
+#define MDLG_MAXCHOICES     5
+#define MDLG_MAXITEMS       3
+
+extern char mission_objective[OBJECTIVE_LEN];
+
+extern int dialogshowtext;
+
+// villsa - convenient macro for giving objective logs to player
+#define GiveObjective(x, minlumpnum) \
+do { \
+  int obj_ln  = W_CheckNumForName(DEH_String(x)); \
+  if(obj_ln > minlumpnum) \
+    M_StringCopy(mission_objective, W_CacheLumpNum(obj_ln, PU_CACHE), \
+                 OBJECTIVE_LEN);\
+} while(0)
+
+// haleyjd - voice and objective in one
+#define GiveVoiceObjective(voice, log, minlumpnum) \
+do { \
+  int obj_ln = W_CheckNumForName(DEH_String(log)); \
+  I_StartVoice(DEH_String(voice)); \
+  if(obj_ln > minlumpnum) \
+    M_StringCopy(mission_objective, W_CacheLumpNum(obj_ln, PU_CACHE), \
+                 OBJECTIVE_LEN);\
+} while(0)
+
+typedef struct mapdlgchoice_s
+{
+    int  giveitem;                      // item given when successful
+    int  needitems[MDLG_MAXITEMS];      // item needed for success
+    int  needamounts[MDLG_MAXITEMS];    // amount of items needed
+    char text[MDLG_CHOICELEN];          // normal text
+    char textok[MDLG_MSGLEN];           // message given on success
+    int next;                           // next dialog?
+    int objective;                      // ???
+    char textno[MDLG_MSGLEN];           // message given on failure
+} mapdlgchoice_t;
+
+typedef struct mapdialog_s
+{
+    int speakerid;                      // script ID# for mobjtype that will use this dialog
+    int dropitem;                       // item to drop if that thingtype is killed
+    int checkitem[MDLG_MAXITEMS];       // item(s) needed to see this dialog
+    int jumptoconv;                     // conversation to jump to when... ?
+    char name[MDLG_NAMELEN];            // name of speaker
+    char voice[MDLG_LUMPLEN];           // voice file to play
+    char backpic[MDLG_LUMPLEN];         // backdrop pic for character, if any
+    char text[MDLG_TEXTLEN];            // main message text
+    
+    // options that this dialog gives the player
+    mapdlgchoice_t choices[MDLG_MAXCHOICES];
+} mapdialog_t;
+
+void         P_DialogLoad(void);
+void         P_DialogStart(player_t *player);
+void         P_DialogDoChoice(int choice);
+boolean      P_GiveItemToPlayer(player_t *player, int sprnum, mobjtype_t type);
+boolean      P_GiveInventoryItem(player_t *player, int sprnum, mobjtype_t type);
+boolean      P_UseInventoryItem(player_t* player, int item);
+void         P_DialogStartP1(void);
+mapdialog_t* P_DialogFind(mobjtype_t type, int jumptoconv);
+int          P_PlayerHasItem(player_t *player, mobjtype_t type);
+
+#endif
+
+// EOF
+
+
diff --git a/src/strife/p_enemy.c b/src/strife/p_enemy.c
index e358e61..1f87102 100644
--- a/src/strife/p_enemy.c
+++ b/src/strife/p_enemy.c
@@ -1240,7 +1240,7 @@ void A_ReaverAttack(mobj_t* actor)
     {
         int     t          = P_Random();
         angle_t shootangle = actor->angle + ((t - P_Random()) << 20);
-        int     damage     = (P_Random() & 7) + 1;
+        int     damage     = 3*((P_Random() & 7) + 1);
 
         P_LineAttack(actor, shootangle, 2048*FRACUNIT, slope, damage);
         ++i;
@@ -2211,7 +2211,7 @@ void A_ProgrammerMelee(mobj_t* actor)
     A_FaceTarget(actor);
     if(P_CheckMeleeRange(actor))
     {
-        int damage = 8 * (P_Random() % 10 + 1);
+        int damage = 6 * (P_Random() % 10 + 1);
         
         S_StartSound(actor, sfx_mtalht);
         P_DamageMobj(actor->target, actor, actor, damage);
@@ -3177,7 +3177,10 @@ void A_TeleportBeacon(mobj_t* actor)
     // beacon no longer special
     actor->flags &= ~MF_SPECIAL;
 
-    // set color and flags
+    // 20160306: set rebel threshold
+    mobj->threshold = 100;
+
+    // set rebel color and flags
     mobj->flags |= ((actor->miscdata << MF_TRANSSHIFT) | MF_NODIALOG);
     mobj->target = NULL;
 
diff --git a/src/strife/p_inter.c b/src/strife/p_inter.c
index f2e16ef..b92cacf 100644
--- a/src/strife/p_inter.c
+++ b/src/strife/p_inter.c
@@ -488,13 +488,13 @@ void P_TouchSpecialThing(mobj_t* special, mobj_t* toucher)
         break;
 
     // missile
-    case SPR_ROKT:
+    case SPR_MSSL:
         if(!P_GiveAmmo(player, am_missiles, 1))
             return;
         break;
 
     // box of missiles
-    case SPR_MSSL:
+    case SPR_ROKT:
         if(!P_GiveAmmo(player, am_missiles, 5))
             return;
         break;
@@ -852,9 +852,9 @@ void P_KillMobj(mobj_t* source, mobj_t* target)
             }
         }
 
-        target->flags &= ~MF_SOLID;
+        //target->flags &= ~MF_SOLID;
         target->player->playerstate = PST_DEAD;
-        target->player->mo->momz = 5*FRACUNIT;  // [STRIFE]: small hop!
+        target->player->mo->momz += 5*FRACUNIT;  // [STRIFE]: small hop!
         P_DropWeapon(target->player);
 
         if(target->player == &players[consoleplayer]
@@ -874,7 +874,8 @@ void P_KillMobj(mobj_t* source, mobj_t* target)
             P_SetMobjState(target, S_DISR_00);  // 373
         else
         {
-            if(target->health < -target->info->spawnhealth 
+            // haleyjd [STRIFE] 20160111: Rogue changed check from < to <=
+            if(target->health <= -target->info->spawnhealth 
                 && target->info->xdeathstate)
                 P_SetMobjState(target, target->info->xdeathstate);
             else
@@ -1351,7 +1352,7 @@ void P_DamageMobj(mobj_t* target, mobj_t* inflictor, mobj_t* source, int damage)
             if(target->player)
             {
                 target->player->cheats |= CF_ONFIRE;
-                target->player->powers[pw_communicator] = false;
+                target->player->powers[pw_invisibility] = false;
                 target->player->readyweapon = 0;
                 P_SetPsprite(target->player, ps_weapon, S_WAVE_00); // 02
                 P_SetPsprite(target->player, ps_flash, S_NULL);
diff --git a/src/strife/p_plats.c b/src/strife/p_plats.c
index 09387c8..aeb0eb5 100644
--- a/src/strife/p_plats.c
+++ b/src/strife/p_plats.c
@@ -325,13 +325,15 @@ void P_AddActivePlat(plat_t* plat)
     int i;
 
     for(i = 0; i < MAXPLATS; i++)
+    {
         if (activeplats[i] == NULL)
         {
             activeplats[i] = plat;
             return;
         }
+    }
 
-        I_Error("P_AddActivePlat: no more plats!");
+    I_Error("P_AddActivePlat: no more plats!");
 }
 
 //
@@ -341,6 +343,7 @@ void P_RemoveActivePlat(plat_t* plat)
 {
     int i;
     for(i = 0; i < MAXPLATS; i++)
+    {
         if(plat == activeplats[i])
         {
             (activeplats[i])->sector->specialdata = NULL;
@@ -349,6 +352,7 @@ void P_RemoveActivePlat(plat_t* plat)
 
             return;
         }
+    }
 
-        I_Error("P_RemoveActivePlat: can't find plat!");
+    I_Error("P_RemoveActivePlat: can't find plat!");
 }
diff --git a/src/strife/p_pspr.c b/src/strife/p_pspr.c
index 2d25c07..4295c97 100644
--- a/src/strife/p_pspr.c
+++ b/src/strife/p_pspr.c
@@ -854,7 +854,7 @@ void A_FireSigil(player_t* player, pspdef_t* pspr)
         mo->health = -1;
         if(!linetarget)
         {
-            an = player->pitch >> ANGLETOFINESHIFT;
+            an = (unsigned int)player->pitch >> ANGLETOFINESHIFT;
             mo->momz += FixedMul(finesine[an], mo->info->speed); 
         }
         break;
diff --git a/src/strife/p_spec.c b/src/strife/p_spec.c
index 30682dd..dc06014 100644
--- a/src/strife/p_spec.c
+++ b/src/strife/p_spec.c
@@ -436,7 +436,7 @@ P_FindNextHighestFloor
             }
             else if (h == MAX_ADJOINING_SECTORS + 2)
             {
-                // Fatal overflow: game crashes at 22 textures
+                // Fatal overflow: game crashes at 22 sectors
                 I_Error("Sector with more than 22 adjoining sectors. "
                         "Vanilla will crash here");
             }
diff --git a/src/strife/r_bsp.c b/src/strife/r_bsp.c
index d335d0b..64b9d5d 100644
--- a/src/strife/r_bsp.c
+++ b/src/strife/r_bsp.c
@@ -77,8 +77,14 @@ typedef	struct
     
 } cliprange_t;
 
-
-#define MAXSEGS		32
+// We must expand MAXSEGS to the theoretical limit of the number of solidsegs
+// that can be generated in a scene by the DOOM engine. This was determined by
+// Lee Killough during BOOM development to be a function of the screensize.
+// The simplest thing we can do, other than fix this bug, is to let the game
+// render overage and then bomb out by detecting the overflow after the 
+// fact. -haleyjd
+//#define MAXSEGS		32
+#define MAXSEGS (SCREENWIDTH / 2 + 1)
 
 // newend is one past the last valid seg
 cliprange_t*	newend;
@@ -532,6 +538,10 @@ void R_Subsector (int num)
 	R_AddLine (line);
 	line++;
     }
+
+    // check for solidsegs overflow - extremely unsatisfactory!
+    if(newend > &solidsegs[32])
+        I_Error("R_Subsector: solidsegs overflow (vanilla may crash here)\n");
 }
 
 
diff --git a/src/strife/r_data.c b/src/strife/r_data.c
index 1cc4f47..f31e0cb 100644
--- a/src/strife/r_data.c
+++ b/src/strife/r_data.c
@@ -938,7 +938,7 @@ void R_PrecacheLevel (void)
 	if (flatpresent[i])
 	{
 	    lump = firstflat + i;
-	    flatmemory += lumpinfo[lump].size;
+	    flatmemory += lumpinfo[lump]->size;
 	    W_CacheLumpNum(lump, PU_CACHE);
 	}
     }
@@ -975,7 +975,7 @@ void R_PrecacheLevel (void)
 	for (j=0 ; j<texture->patchcount ; j++)
 	{
 	    lump = texture->patches[j].patch;
-	    texturememory += lumpinfo[lump].size;
+	    texturememory += lumpinfo[lump]->size;
 	    W_CacheLumpNum(lump , PU_CACHE);
 	}
     }
@@ -1004,7 +1004,7 @@ void R_PrecacheLevel (void)
 	    for (k=0 ; k<8 ; k++)
 	    {
 		lump = firstspritelump + sf->lump[k];
-		spritememory += lumpinfo[lump].size;
+		spritememory += lumpinfo[lump]->size;
 		W_CacheLumpNum(lump , PU_CACHE);
 	    }
 	}
diff --git a/src/strife/r_things.c b/src/strife/r_things.c
index 6d37ca0..230b262 100644
--- a/src/strife/r_things.c
+++ b/src/strife/r_things.c
@@ -211,22 +211,22 @@ void R_InitSpriteDefs (char** namelist)
 	//  filling in the frames for whatever is found
 	for (l=start+1 ; l<end ; l++)
 	{
-	    if (!strncasecmp(lumpinfo[l].name, spritename, 4))
+	    if (!strncasecmp(lumpinfo[l]->name, spritename, 4))
 	    {
-		frame = lumpinfo[l].name[4] - 'A';
-		rotation = lumpinfo[l].name[5] - '0';
+		frame = lumpinfo[l]->name[4] - 'A';
+		rotation = lumpinfo[l]->name[5] - '0';
 
 		if (modifiedgame)
-		    patched = W_GetNumForName (lumpinfo[l].name);
+		    patched = W_GetNumForName (lumpinfo[l]->name);
 		else
 		    patched = l;
 
 		R_InstallSpriteLump (patched, frame, rotation, false);
 
-		if (lumpinfo[l].name[6])
+		if (lumpinfo[l]->name[6])
 		{
-		    frame = lumpinfo[l].name[6] - 'A';
-		    rotation = lumpinfo[l].name[7] - '0';
+		    frame = lumpinfo[l]->name[6] - 'A';
+		    rotation = lumpinfo[l]->name[7] - '0';
 		    R_InstallSpriteLump (l, frame, rotation, true);
 		}
 	    }
diff --git a/src/strife/s_sound.c b/src/strife/s_sound.c
index 3f010ea..f7beaac 100644
--- a/src/strife/s_sound.c
+++ b/src/strife/s_sound.c
@@ -60,7 +60,6 @@
 
 #define S_STEREO_SWING (96 * FRACUNIT)
 
-#define NORM_PITCH 128
 #define NORM_PRIORITY 64
 #define NORM_SEP 128
 
@@ -74,6 +73,8 @@ typedef struct
 
     // handle of the sound being played
     int handle;
+
+    int pitch;
     
 } channel_t;
 
@@ -137,7 +138,7 @@ void S_Init(int sfxVolume, int musicVolume, int voiceVolume)
 {  
     int i;
 
-    I_SetOPLDriverVer(opl_v_new);
+    I_SetOPLDriverVer(opl_doom_1_9);
     I_PrecacheSounds(S_sfx, NUMSFX);
 
     S_SetSfxVolume(sfxVolume);
@@ -408,6 +409,7 @@ void S_StartSound(void *origin_p, int sfx_id)
     mobj_t *origin;
     int rc;
     int sep;
+    int pitch;
     int cnum;
     int volume;
 
@@ -454,7 +456,7 @@ void S_StartSound(void *origin_p, int sfx_id)
 
         if (origin->x == players[consoleplayer].mo->x
          && origin->y == players[consoleplayer].mo->y)
-        {        
+        {
             sep = NORM_SEP;
         }
 
@@ -462,11 +464,12 @@ void S_StartSound(void *origin_p, int sfx_id)
         {
             return;
         }
-    }        
+    }
     else
     {
         sep = NORM_SEP;
     }
+    pitch = NORM_PITCH;
 
     // kill old sound [STRIFE] - nope!
     //S_StopSound(origin);
@@ -490,7 +493,7 @@ void S_StartSound(void *origin_p, int sfx_id)
         sfx->lumpnum = I_GetSfxLumpNum(sfx);
     }
 
-    channels[cnum].handle = I_StartSound(sfx, cnum, volume, sep);
+    channels[cnum].handle = I_StartSound(sfx, cnum, volume, sep, pitch);
 }
 
 
@@ -615,9 +618,9 @@ void I_StartVoice(const char *lumpname)
 
         // get a channel for the voice
         i_voicehandle = S_GetChannel(NULL, &voice->sfx, true);
-        
+
         channels[i_voicehandle].handle 
-            = I_StartSound(&voice->sfx, i_voicehandle, snd_VoiceVolume, NORM_SEP);
+            = I_StartSound(&voice->sfx, i_voicehandle, snd_VoiceVolume, NORM_SEP, NORM_PITCH);
     }
 }
 
@@ -693,7 +696,7 @@ void S_UpdateSounds(mobj_t *listener)
                                                   c->origin,
                                                   &volume,
                                                   &sep);
-                    
+
                     if (!audible)
                     {
                         S_StopChannel(cnum);
diff --git a/src/tables.c b/src/tables.c
index c221e9a..698ae98 100644
--- a/src/tables.c
+++ b/src/tables.c
@@ -61,7 +61,7 @@ int SlopeDiv(unsigned int num, unsigned int den)
     }
 }
 
-const int finetangent[4096] =
+const fixed_t finetangent[4096] =
 {
     -170910304,-56965752,-34178904,-24413316,-18988036,-15535599,-13145455,-11392683,
     -10052327,-8994149,-8137527,-7429880,-6835455,-6329090,-5892567,-5512368,
@@ -578,7 +578,7 @@ const int finetangent[4096] =
 };
 
 
-const int finesine[10240] =
+const fixed_t finesine[10240] =
 {
     25,75,125,175,226,276,326,376,
     427,477,527,578,628,678,728,779,
diff --git a/src/v_diskicon.c b/src/v_diskicon.c
new file mode 100644
index 0000000..58047ce
--- /dev/null
+++ b/src/v_diskicon.c
@@ -0,0 +1,145 @@
+//
+// Copyright(C) 1993-1996 Id Software, Inc.
+// Copyright(C) 2005-2014 Simon Howard
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// DESCRIPTION:
+//	Disk load indicator.
+//
+
+#include "doomtype.h"
+#include "deh_str.h"
+#include "i_swap.h"
+#include "i_video.h"
+#include "m_argv.h"
+#include "v_video.h"
+#include "w_wad.h"
+#include "z_zone.h"
+
+#include "v_diskicon.h"
+
+// Only display the disk icon if more then this much bytes have been read
+// during the previous tic.
+
+static const int diskicon_threshold = 20*1024;
+
+// Two buffers: disk_data contains the data representing the disk icon
+// (raw, not a patch_t) while saved_background is an equivalently-sized
+// buffer where we save the background data while the disk is on screen.
+static byte *disk_data;
+static byte *saved_background;
+
+static int loading_disk_xoffs = 0;
+static int loading_disk_yoffs = 0;
+
+// Number of bytes read since the last call to V_DrawDiskIcon().
+static size_t recent_bytes_read = 0;
+static boolean disk_drawn;
+
+static void CopyRegion(byte *dest, int dest_pitch,
+                       byte *src, int src_pitch,
+                       int w, int h)
+{
+    byte *s, *d;
+    int y;
+
+    s = src; d = dest;
+    for (y = 0; y < h; ++y)
+    {
+        memcpy(d, s, w * sizeof(*d));
+        s += src_pitch;
+        d += dest_pitch;
+    }
+}
+
+static void SaveDiskData(char *disk_lump, int xoffs, int yoffs)
+{
+    byte *tmpscreen;
+    patch_t *disk;
+
+    // Allocate a complete temporary screen where we'll draw the patch.
+    tmpscreen = Z_Malloc(SCREENWIDTH * SCREENHEIGHT * sizeof(*tmpscreen),
+                         PU_STATIC, NULL);
+    memset(tmpscreen, 0, SCREENWIDTH * SCREENHEIGHT * sizeof(*tmpscreen));
+    V_UseBuffer(tmpscreen);
+
+    // Buffer where we'll save the disk data.
+    disk_data = Z_Malloc(LOADING_DISK_W * LOADING_DISK_H * sizeof(*disk_data),
+                         PU_STATIC, NULL);
+
+    // Draw the patch and save the result to disk_data.
+    disk = W_CacheLumpName(disk_lump, PU_STATIC);
+    V_DrawPatch(loading_disk_xoffs, loading_disk_yoffs, disk);
+    CopyRegion(disk_data, LOADING_DISK_W,
+               tmpscreen + yoffs * SCREENWIDTH + xoffs, SCREENWIDTH,
+               LOADING_DISK_W, LOADING_DISK_H);
+    W_ReleaseLumpName(disk_lump);
+
+    V_RestoreBuffer();
+    Z_Free(tmpscreen);
+}
+
+void V_EnableLoadingDisk(char *lump_name, int xoffs, int yoffs)
+{
+    loading_disk_xoffs = xoffs;
+    loading_disk_yoffs = yoffs;
+
+    saved_background = Z_Malloc(LOADING_DISK_W * LOADING_DISK_H
+                                 * sizeof(*saved_background),
+                                PU_STATIC, NULL);
+    SaveDiskData(lump_name, xoffs, yoffs);
+}
+
+void V_BeginRead(size_t nbytes)
+{
+    recent_bytes_read += nbytes;
+}
+
+static byte *DiskRegionPointer(void)
+{
+    return I_VideoBuffer
+         + loading_disk_yoffs * SCREENWIDTH
+         + loading_disk_xoffs;
+}
+
+void V_DrawDiskIcon(void)
+{
+    if (disk_data != NULL && recent_bytes_read > diskicon_threshold)
+    {
+        // Save the background behind the disk before we draw it.
+        CopyRegion(saved_background, LOADING_DISK_W,
+                   DiskRegionPointer(), SCREENWIDTH,
+                   LOADING_DISK_W, LOADING_DISK_H);
+
+        // Write the disk to the screen buffer.
+        CopyRegion(DiskRegionPointer(), SCREENWIDTH,
+                   disk_data, LOADING_DISK_W,
+                   LOADING_DISK_W, LOADING_DISK_H);
+        disk_drawn = true;
+    }
+
+    recent_bytes_read = 0;
+}
+
+void V_RestoreDiskBackground(void)
+{
+    if (disk_drawn)
+    {
+        // Restore the background.
+        CopyRegion(DiskRegionPointer(), SCREENWIDTH,
+                   saved_background, LOADING_DISK_W,
+                   LOADING_DISK_W, LOADING_DISK_H);
+
+        disk_drawn = false;
+    }
+}
+
diff --git a/src/doom/m_random.h b/src/v_diskicon.h
similarity index 63%
copy from src/doom/m_random.h
copy to src/v_diskicon.h
index aa6291a..2934b97 100644
--- a/src/doom/m_random.h
+++ b/src/v_diskicon.h
@@ -13,27 +13,20 @@
 // GNU General Public License for more details.
 //
 // DESCRIPTION:
+//	Disk load indicator.
 //
-//    
 
+#ifndef __V_DISKICON__
+#define __V_DISKICON__
 
-#ifndef __M_RANDOM__
-#define __M_RANDOM__
+// Dimensions of the flashing "loading" disk icon
 
+#define LOADING_DISK_W 16
+#define LOADING_DISK_H 16
 
-#include "doomtype.h"
-
-
-
-// Returns a number from 0 to 255,
-// from a lookup table.
-int M_Random (void);
-
-// As M_Random, but used only by the play simulation.
-int P_Random (void);
-
-// Fix randoms for demos.
-void M_ClearRandom (void);
-
+extern void V_EnableLoadingDisk(char *lump_name, int xoffs, int yoffs);
+extern void V_BeginRead(size_t nbytes);
+extern void V_DrawDiskIcon(void);
+extern void V_RestoreDiskBackground(void);
 
 #endif
diff --git a/src/v_video.c b/src/v_video.c
index 16350d8..fce5589 100644
--- a/src/v_video.c
+++ b/src/v_video.c
@@ -110,7 +110,7 @@ void V_CopyRect(int srcx, int srcy, byte *source,
 
     for ( ; height>0 ; height--) 
     { 
-        memcpy(dest, src, width); 
+        memcpy(dest, src, width * sizeof(*dest));
         src += SCREENWIDTH; 
         dest += SCREENWIDTH; 
     } 
@@ -520,7 +520,7 @@ void V_DrawBlock(int x, int y, int width, int height, byte *src)
 
     while (height--) 
     { 
-	memcpy (dest, src, width); 
+	memcpy (dest, src, width * sizeof(*dest));
 	src += width; 
 	dest += SCREENWIDTH; 
     } 
@@ -672,6 +672,7 @@ void WritePCXfile(char *filename, byte *data,
     pcx->hres = SHORT(width);
     pcx->vres = SHORT(height);
     memset (pcx->palette,0,sizeof(pcx->palette));
+    pcx->reserved = 0;                  // PCX spec: reserved byte must be zero
     pcx->color_planes = 1;		// chunky image
     pcx->bytes_per_line = SHORT(width);
     pcx->palette_type = SHORT(2);	// not a grey scale
@@ -719,14 +720,20 @@ static void warning_fn(png_structp p, png_const_charp s)
 }
 
 void WritePNGfile(char *filename, byte *data,
-                  int width, int height,
+                  int inwidth, int inheight,
                   byte *palette)
 {
     png_structp ppng;
     png_infop pinfo;
     png_colorp pcolor;
     FILE *handle;
-    int i;
+    int i, j;
+    int width, height;
+    byte *rowbuf;
+
+    // scale up to accommodate aspect ratio correction
+    width = inwidth * 5;
+    height = inheight * 6;
 
     handle = fopen(filename, "wb");
     if (!handle)
@@ -773,9 +780,26 @@ void WritePNGfile(char *filename, byte *data,
 
     png_write_info(ppng, pinfo);
 
-    for (i = 0; i < SCREENHEIGHT; i++)
+    rowbuf = malloc(width);
+
+    if (rowbuf)
     {
-        png_write_row(ppng, data + i*SCREENWIDTH);
+        for (i = 0; i < SCREENHEIGHT; i++)
+        {
+            // expand the row 5x
+            for (j = 0; j < SCREENWIDTH; j++)
+            {
+                memset(rowbuf + j * 5, *(data + i*SCREENWIDTH + j), 5);
+            }
+
+            // write the row 6 times
+            for (j = 0; j < 6; j++)
+            {
+                png_write_row(ppng, rowbuf);
+            }
+        }
+
+        free(rowbuf);
     }
 
     png_write_end(ppng, pinfo);
@@ -843,6 +867,18 @@ void V_ScreenShot(char *format)
 #define MOUSE_SPEED_BOX_WIDTH  120
 #define MOUSE_SPEED_BOX_HEIGHT 9
 
+//
+// V_DrawMouseSpeedBox
+//
+
+// If box is only to calibrate speed, testing relative speed (as a measure
+// of game pixels to movement units) is important whether physical mouse DPI
+// is high or low. Line resolution starts at 1 pixel per 1 move-unit: if
+// line maxes out, resolution becomes 1 pixel per 2 move-units, then per
+// 3 move-units, etc.
+
+static int linelen_multiplier = 1;
+
 void V_DrawMouseSpeedBox(int speed)
 {
     extern int usemouse;
@@ -851,6 +887,8 @@ void V_DrawMouseSpeedBox(int speed)
     int original_speed;
     int redline_x;
     int linelen;
+    int i;
+    boolean draw_acceleration = false;
 
     // Get palette indices for colors for widget. These depend on the
     // palette of the game being played.
@@ -862,14 +900,19 @@ void V_DrawMouseSpeedBox(int speed)
     yellow = I_GetPaletteIndex(0xff, 0xff, 0x00);
     white = I_GetPaletteIndex(0xff, 0xff, 0xff);
 
-    // If the mouse is turned off or acceleration is turned off, don't
-    // draw the box at all.
-
-    if (!usemouse || fabs(mouse_acceleration - 1) < 0.01)
+    // If the mouse is turned off, don't draw the box at all.
+    if (!usemouse)
     {
         return;
     }
 
+    // If acceleration is used, draw a box that helps to calibrate the
+    // threshold point.
+    if (mouse_threshold > 0 && fabs(mouse_acceleration - 1) > 0.01)
+    {
+        draw_acceleration = true;
+    }
+
     // Calculate box position
 
     box_x = SCREENWIDTH - MOUSE_SPEED_BOX_WIDTH - 10;
@@ -880,41 +923,44 @@ void V_DrawMouseSpeedBox(int speed)
     V_DrawBox(box_x, box_y,
               MOUSE_SPEED_BOX_WIDTH, MOUSE_SPEED_BOX_HEIGHT, bordercolor);
 
-    // Calculate the position of the red line.  This is 1/3 of the way
-    // along the box.
+    // Calculate the position of the red threshold line when calibrating
+    // acceleration.  This is 1/3 of the way along the box.
 
     redline_x = MOUSE_SPEED_BOX_WIDTH / 3;
 
-    // Undo acceleration and get back the original mouse speed
+    // Calculate line length
 
-    if (speed < mouse_threshold)
-    {
-        original_speed = speed;
-    }
-    else
+    if (draw_acceleration && speed >= mouse_threshold)
     {
+        // Undo acceleration and get back the original mouse speed
         original_speed = speed - mouse_threshold;
         original_speed = (int) (original_speed / mouse_acceleration);
         original_speed += mouse_threshold;
-    }
 
-    // Calculate line length
-
-    linelen = (original_speed * redline_x) / mouse_threshold;
+        linelen = (original_speed * redline_x) / mouse_threshold;
+    }
+    else
+    {
+        linelen = speed / linelen_multiplier;
+    }
 
     // Draw horizontal "thermometer" 
 
     if (linelen > MOUSE_SPEED_BOX_WIDTH - 1)
     {
         linelen = MOUSE_SPEED_BOX_WIDTH - 1;
+        if (!draw_acceleration)
+        {
+            linelen_multiplier++;
+        }
     }
 
     V_DrawHorizLine(box_x + 1, box_y + 4, MOUSE_SPEED_BOX_WIDTH - 2, black);
 
-    if (linelen < redline_x)
+    if (!draw_acceleration || linelen < redline_x)
     {
         V_DrawHorizLine(box_x + 1, box_y + MOUSE_SPEED_BOX_HEIGHT / 2,
-                      linelen, white);
+                        linelen, white);
     }
     else
     {
@@ -924,9 +970,21 @@ void V_DrawMouseSpeedBox(int speed)
                         linelen - redline_x, yellow);
     }
 
-    // Draw red line
-
-    V_DrawVertLine(box_x + redline_x, box_y + 1,
-                 MOUSE_SPEED_BOX_HEIGHT - 2, red);
+    if (draw_acceleration)
+    {
+        // Draw acceleration threshold line
+        V_DrawVertLine(box_x + redline_x, box_y + 1,
+                       MOUSE_SPEED_BOX_HEIGHT - 2, red);
+    }
+    else
+    {
+        // Draw multiplier lines to indicate current resolution
+        for (i = 1; i < linelen_multiplier; i++)
+        {
+            V_DrawVertLine(
+                box_x + (i * MOUSE_SPEED_BOX_WIDTH / linelen_multiplier),
+                box_y + 1, MOUSE_SPEED_BOX_HEIGHT - 2, yellow);
+        }
+    }
 }
 
diff --git a/src/w_checksum.c b/src/w_checksum.c
index 5933fdf..bf95967 100644
--- a/src/w_checksum.c
+++ b/src/w_checksum.c
@@ -33,7 +33,7 @@ static int GetFileNumber(wad_file_t *handle)
     int i;
     int result;
 
-    for (i=0; i<num_open_wadfiles; ++i)
+    for (i = 0; i < num_open_wadfiles; ++i)
     {
         if (open_wadfiles[i] == handle)
         {
@@ -77,11 +77,11 @@ void W_Checksum(sha1_digest_t digest)
     // Go through each entry in the WAD directory, adding information
     // about each entry to the SHA1 hash.
 
-    for (i=0; i<numlumps; ++i)
+    for (i = 0; i < numlumps; ++i)
     {
-        ChecksumAddLump(&sha1_context, &lumpinfo[i]);
+        ChecksumAddLump(&sha1_context, lumpinfo[i]);
     }
-    
+
     SHA1_Final(digest, &sha1_context);
 }
 
diff --git a/src/w_file.h b/src/w_file.h
index f577814..a47d2a7 100644
--- a/src/w_file.h
+++ b/src/w_file.h
@@ -28,35 +28,31 @@ typedef struct _wad_file_s wad_file_t;
 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 
diff --git a/src/w_file_posix.c b/src/w_file_posix.c
index c193ecb..a7b53b4 100644
--- 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 @@ static wad_file_t *W_POSIX_OpenFile(char *path)
     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:
diff --git a/src/w_file_stdc.c b/src/w_file_stdc.c
index 829e960..c41692f 100644
--- a/src/w_file_stdc.c
+++ b/src/w_file_stdc.c
@@ -48,6 +48,7 @@ static wad_file_t *W_StdC_OpenFile(char *path)
     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;
diff --git a/src/w_file_win32.c b/src/w_file_win32.c
index b44d942..a370fb0 100644
--- 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 @@ static wad_file_t *W_Win32_OpenFile(char *path)
     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:
diff --git a/src/w_main.c b/src/w_main.c
index 115f081..ac67f2d 100644
--- a/src/w_main.c
+++ b/src/w_main.c
@@ -16,8 +16,10 @@
 //     Common code to parse command line, identifying WAD files to load.
 //
 
+#include "config.h"
 #include "doomfeatures.h"
 #include "d_iwad.h"
+#include "i_system.h"
 #include "m_argv.h"
 #include "w_main.h"
 #include "w_merge.h"
@@ -26,7 +28,6 @@
 
 // Parse the command line, merging WAD files that are sppecified.
 // Returns true if at least one file was added.
-
 boolean W_ParseCommandLine(void)
 {
     boolean modifiedgame = false;
@@ -196,3 +197,44 @@ boolean W_ParseCommandLine(void)
     return modifiedgame;
 }
 
+// Lump names that are unique to particular game types. This lets us check
+// the user is not trying to play with the wrong executable, eg.
+// chocolate-doom -iwad hexen.wad.
+static const struct
+{
+    GameMission_t mission;
+    char *lumpname;
+} unique_lumps[] = {
+    { doom,    "POSSA1" },
+    { heretic, "IMPXA1" },
+    { hexen,   "ETTNA1" },
+    { strife,  "AGRDA1" },
+};
+
+void W_CheckCorrectIWAD(GameMission_t mission)
+{
+    int i;
+    lumpindex_t lumpnum;
+
+    for (i = 0; i < arrlen(unique_lumps); ++i)
+    {
+        if (mission != unique_lumps[i].mission)
+        {
+            lumpnum = W_CheckNumForName(unique_lumps[i].lumpname);
+
+            if (lumpnum >= 0)
+            {
+                I_Error("\nYou are trying to use a %s IWAD file with "
+                        "the %s%s binary.\nThis isn't going to work.\n"
+                        "You probably want to use the %s%s binary.",
+                        D_SuggestGameName(unique_lumps[i].mission,
+                                          indetermined),
+                        PROGRAM_PREFIX,
+                        D_GameMissionString(mission),
+                        PROGRAM_PREFIX,
+                        D_GameMissionString(unique_lumps[i].mission));
+            }
+        }
+    }
+}
+
diff --git a/src/w_main.h b/src/w_main.h
index 2e39efc..26b487c 100644
--- a/src/w_main.h
+++ b/src/w_main.h
@@ -18,7 +18,10 @@
 #ifndef W_MAIN_H
 #define W_MAIN_H
 
+#include "d_mode.h"
+
 boolean W_ParseCommandLine(void);
+void W_CheckCorrectIWAD(GameMission_t mission);
 
 #endif /* #ifndef W_MAIN_H */
 
diff --git a/src/w_merge.c b/src/w_merge.c
index ae0975b..100050d 100644
--- a/src/w_merge.c
+++ b/src/w_merge.c
@@ -39,7 +39,7 @@ typedef enum
 
 typedef struct
 {
-    lumpinfo_t *lumps;
+    lumpinfo_t **lumps;
     int numlumps;
 } searchlist_t;
 
@@ -74,7 +74,7 @@ static int FindInList(searchlist_t *list, char *name)
 
     for (i=0; i<list->numlumps; ++i)
     {
-        if (!strncasecmp(list->lumps[i].name, name, 8))
+        if (!strncasecmp(list->lumps[i]->name, name, 8))
             return i;
     }
 
@@ -352,7 +352,7 @@ static void GenerateSpriteList(void)
     
     for (i=0; i<iwad_sprites.numlumps; ++i)
     {
-        AddSpriteLump(&iwad_sprites.lumps[i]);
+        AddSpriteLump(iwad_sprites.lumps[i]);
     }
     
     // Add all sprites from the PWAD
@@ -360,7 +360,7 @@ static void GenerateSpriteList(void)
 
     for (i=0; i<pwad_sprites.numlumps; ++i)
     {
-        AddSpriteLump(&pwad_sprites.lumps[i]);
+        AddSpriteLump(pwad_sprites.lumps[i]);
     }
 }
 
@@ -386,13 +386,13 @@ static void GenerateSpriteList(void)
 static void DoMerge(void)
 {
     section_t current_section;
-    lumpinfo_t *newlumps;
+    lumpinfo_t **newlumps;
     int num_newlumps;
     int lumpindex;
     int i, n;
-    
+
     // Can't ever have more lumps than we already have
-    newlumps = malloc(sizeof(lumpinfo_t) * numlumps);
+    newlumps = calloc(numlumps, sizeof(lumpinfo_t *));
     num_newlumps = 0;
 
     // Add IWAD lumps
@@ -400,7 +400,7 @@ static void DoMerge(void)
 
     for (i=0; i<iwad.numlumps; ++i)
     {
-        lumpinfo_t *lump = &iwad.lumps[i];
+        lumpinfo_t *lump = iwad.lumps[i];
 
         switch (current_section)
         {
@@ -414,7 +414,7 @@ static void DoMerge(void)
                     current_section = SECTION_SPRITES;
                 }
 
-                newlumps[num_newlumps++] = *lump;
+                newlumps[num_newlumps++] = lump;
 
                 break;
 
@@ -432,7 +432,7 @@ static void DoMerge(void)
                         newlumps[num_newlumps++] = pwad_flats.lumps[n];
                     }
 
-                    newlumps[num_newlumps++] = *lump;
+                    newlumps[num_newlumps++] = lump;
 
                     // back to normal reading
                     current_section = SECTION_NORMAL;
@@ -448,7 +448,7 @@ static void DoMerge(void)
 
                     if (lumpindex < 0)
                     {
-                        newlumps[num_newlumps++] = *lump;
+                        newlumps[num_newlumps++] = lump;
                     }
                 }
 
@@ -460,18 +460,18 @@ static void DoMerge(void)
 
                 if (!strncasecmp(lump->name, "S_END", 8))
                 {
-                    // add all the pwad sprites
+                    // add all the PWAD sprites
 
                     for (n=0; n<pwad_sprites.numlumps; ++n)
                     {
-                        if (SpriteLumpNeeded(&pwad_sprites.lumps[n]))
+                        if (SpriteLumpNeeded(pwad_sprites.lumps[n]))
                         {
                             newlumps[num_newlumps++] = pwad_sprites.lumps[n];
                         }
                     }
 
                     // copy the ending
-                    newlumps[num_newlumps++] = *lump;
+                    newlumps[num_newlumps++] = lump;
 
                     // back to normal reading
                     current_section = SECTION_NORMAL;
@@ -483,7 +483,7 @@ static void DoMerge(void)
 
                     if (SpriteLumpNeeded(lump))
                     {
-                        newlumps[num_newlumps++] = *lump;
+                        newlumps[num_newlumps++] = lump;
                     }
                 }
 
@@ -496,7 +496,7 @@ static void DoMerge(void)
 
     for (i=0; i<pwad.numlumps; ++i)
     {
-        lumpinfo_t *lump = &pwad.lumps[i];
+        lumpinfo_t *lump = pwad.lumps[i];
 
         switch (current_section)
         {
@@ -515,7 +515,7 @@ static void DoMerge(void)
                 {
                     // Don't include the headers of sections
        
-                    newlumps[num_newlumps++] = *lump;
+                    newlumps[num_newlumps++] = lump;
                 }
                 break;
 
@@ -550,7 +550,6 @@ static void DoMerge(void)
     free(lumpinfo);
     lumpinfo = newlumps;
     numlumps = num_newlumps;
-
 }
 
 void W_PrintDirectory(void)
@@ -560,8 +559,8 @@ void W_PrintDirectory(void)
     // debug
     for (i=0; i<numlumps; ++i)
     {
-        for (n=0; n<8 && lumpinfo[i].name[n] != '\0'; ++n)
-            putchar(lumpinfo[i].name[n]);
+        for (n=0; n<8 && lumpinfo[i]->name[n] != '\0'; ++n)
+            putchar(lumpinfo[i]->name[n]);
         putchar('\n');
     }
 }
@@ -579,7 +578,7 @@ void W_MergeFile(char *filename)
     if (W_AddFile(filename) == NULL)
         return;
 
-    // iwad is at the start, pwad was appended to the end
+    // IWAD is at the start, PWAD was appended to the end
 
     iwad.lumps = lumpinfo;
     iwad.numlumps = old_numlumps;
@@ -606,25 +605,23 @@ static void W_NWTAddLumps(searchlist_t *list)
 {
     int i;
 
-    // Go through the IWAD list given, replacing lumps with lumps of 
+    // Go through the IWAD list given, replacing lumps with lumps of
     // the same name from the PWAD
-
     for (i=0; i<list->numlumps; ++i)
     {
         int index;
 
-        index = FindInList(&pwad, list->lumps[i].name);
+        index = FindInList(&pwad, list->lumps[i]->name);
 
         if (index > 0)
         {
-            memcpy(&list->lumps[i], &pwad.lumps[index], 
+            memcpy(list->lumps[i], pwad.lumps[index],
                    sizeof(lumpinfo_t));
         }
     }
-    
 }
 
-// Merge sprites and flats in the way NWT does with its -af and -as 
+// Merge sprites and flats in the way NWT does with its -af and -as
 // command-line options.
 
 void W_NWTMergeFile(char *filename, int flags)
@@ -638,20 +635,20 @@ void W_NWTMergeFile(char *filename, int flags)
     if (W_AddFile(filename) == NULL)
         return;
 
-    // iwad is at the start, pwad was appended to the end
+    // IWAD is at the start, PWAD was appended to the end
 
     iwad.lumps = lumpinfo;
     iwad.numlumps = old_numlumps;
 
     pwad.lumps = lumpinfo + old_numlumps;
     pwad.numlumps = numlumps - old_numlumps;
-    
+
     // Setup sprite/flat lists
 
     SetupLists();
 
     // Merge in flats?
-    
+
     if (flags & W_NWT_MERGE_FLATS)
     {
         W_NWTAddLumps(&iwad_flats);
@@ -690,14 +687,14 @@ void W_NWTDashMerge(char *filename)
         return;
     }
 
-    // iwad is at the start, pwad was appended to the end
+    // IWAD is at the start, PWAD was appended to the end
 
     iwad.lumps = lumpinfo;
     iwad.numlumps = old_numlumps;
 
     pwad.lumps = lumpinfo + old_numlumps;
     pwad.numlumps = numlumps - old_numlumps;
-    
+
     // Setup sprite/flat lists
 
     SetupLists();
@@ -706,12 +703,12 @@ void W_NWTDashMerge(char *filename)
 
     for (i=0; i<iwad_sprites.numlumps; ++i)
     {
-        if (FindInList(&pwad, iwad_sprites.lumps[i].name) >= 0)
+        if (FindInList(&pwad, iwad_sprites.lumps[i]->name) >= 0)
         {
             // Replace this entry with an empty string.  This is what
             // nwt -merge does.
 
-            M_StringCopy(iwad_sprites.lumps[i].name, "", 8);
+            M_StringCopy(iwad_sprites.lumps[i]->name, "", 8);
         }
     }
 
diff --git a/src/w_wad.c b/src/w_wad.c
index a8a952b..8eed594 100644
--- a/src/w_wad.c
+++ b/src/w_wad.c
@@ -26,12 +26,11 @@
 
 #include "doomtype.h"
 
-#include "config.h"
-#include "d_iwad.h"
 #include "i_swap.h"
 #include "i_system.h"
 #include "i_video.h"
 #include "m_misc.h"
+#include "v_diskicon.h"
 #include "z_zone.h"
 
 #include "w_wad.h"
@@ -57,16 +56,17 @@ typedef struct
 //
 
 // Location of each lump on disk.
-lumpinfo_t *lumpinfo;
+lumpinfo_t **lumpinfo;
 unsigned int numlumps = 0;
 
 // Hash table for fast lookups
-static lumpinfo_t **lumphash;
+static lumpindex_t *lumphash;
 
 // Variables for the reload hack: filename of the PWAD to reload, and the
 // lumps from WADs before the reload file, so we can resent numlumps and
 // load the file again.
 static wad_file_t *reloadhandle = NULL;
+static lumpinfo_t *reloadlumps = NULL;
 static char *reloadname = NULL;
 static int reloadlump = -1;
 
@@ -87,46 +87,6 @@ unsigned int W_LumpNameHash(const char *s)
     return result;
 }
 
-// Increase the size of the lumpinfo[] array to the specified size.
-static void ExtendLumpInfo(int newnumlumps)
-{
-    lumpinfo_t *newlumpinfo;
-    unsigned int i;
-
-    newlumpinfo = calloc(newnumlumps, sizeof(lumpinfo_t));
-
-    if (newlumpinfo == NULL)
-    {
-	I_Error ("Couldn't realloc lumpinfo");
-    }
-
-    // Copy over lumpinfo_t structures from the old array. If any of
-    // these lumps have been cached, we need to update the user
-    // pointers to the new location.
-    for (i = 0; i < numlumps && i < newnumlumps; ++i)
-    {
-        memcpy(&newlumpinfo[i], &lumpinfo[i], sizeof(lumpinfo_t));
-
-        if (newlumpinfo[i].cache != NULL)
-        {
-            Z_ChangeUser(newlumpinfo[i].cache, &newlumpinfo[i].cache);
-        }
-
-        // We shouldn't be generating a hash table until after all WADs have
-        // been loaded, but just in case...
-        if (lumpinfo[i].next != NULL)
-        {
-            int nextlumpnum = lumpinfo[i].next - lumpinfo;
-            newlumpinfo[i].next = &newlumpinfo[nextlumpnum];
-        }
-    }
-
-    // All done.
-    free(lumpinfo);
-    lumpinfo = newlumpinfo;
-    numlumps = newnumlumps;
-}
-
 //
 // LUMP BASED ROUTINES.
 //
@@ -143,14 +103,14 @@ static void ExtendLumpInfo(int newnumlumps)
 wad_file_t *W_AddFile (char *filename)
 {
     wadinfo_t header;
-    lumpinfo_t *lump_p;
-    unsigned int i;
+    lumpindex_t i;
     wad_file_t *wad_file;
     int length;
     int startlump;
     filelump_t *fileinfo;
     filelump_t *filerover;
-    int newnumlumps;
+    lumpinfo_t *filelumps;
+    int numfilelumps;
 
     // If the filename begins with a ~, it indicates that we should use the
     // reload hack.
@@ -180,15 +140,6 @@ wad_file_t *W_AddFile (char *filename)
 	return NULL;
     }
 
-    // If this is the reload file, we need to save the file handle so that we
-    // can close it later on when we do a reload.
-    if (reloadname)
-    {
-        reloadhandle = wad_file;
-    }
-
-    newnumlumps = numlumps;
-
     if (strcasecmp(filename+strlen(filename)-3 , "wad" ) )
     {
 	// single lump file
@@ -206,9 +157,9 @@ wad_file_t *W_AddFile (char *filename)
         // extension).
 
 	M_ExtractFileBase (filename, fileinfo->name);
-	newnumlumps++;
+	numfilelumps = 1;
     }
-    else 
+    else
     {
 	// WAD file
         W_Read(wad_file, 0, &header, sizeof(header));
@@ -218,39 +169,62 @@ wad_file_t *W_AddFile (char *filename)
 	    // Homebrew levels?
 	    if (strncmp(header.identification,"PWAD",4))
 	    {
+		W_CloseFile(wad_file);
 		I_Error ("Wad file %s doesn't have IWAD "
 			 "or PWAD id\n", filename);
 	    }
-	    
-	    // ???modifiedgame = true;		
+
+	    // ???modifiedgame = true;
 	}
 
 	header.numlumps = LONG(header.numlumps);
+
+         // Vanilla Doom doesn't like WADs with more than 4046 lumps
+         // https://www.doomworld.com/vb/post/1010985
+         if (!strncmp(header.identification,"PWAD",4) && header.numlumps > 4046)
+         {
+                 W_CloseFile(wad_file);
+                 I_Error ("Error: Vanilla limit for lumps in a WAD is 4046, "
+                          "PWAD %s has %d", filename, header.numlumps);
+         }
+
 	header.infotableofs = LONG(header.infotableofs);
 	length = header.numlumps*sizeof(filelump_t);
 	fileinfo = Z_Malloc(length, PU_STATIC, 0);
 
         W_Read(wad_file, header.infotableofs, fileinfo, length);
-	newnumlumps += header.numlumps;
+	numfilelumps = header.numlumps;
     }
 
     // Increase size of numlumps array to accomodate the new file.
-    startlump = numlumps;
-    ExtendLumpInfo(newnumlumps);
+    filelumps = calloc(numfilelumps, sizeof(lumpinfo_t));
+    if (filelumps == NULL)
+    {
+        W_CloseFile(wad_file);
+        I_Error("Failed to allocate array for lumps from new file.");
+    }
 
-    lump_p = &lumpinfo[startlump];
+    startlump = numlumps;
+    numlumps += numfilelumps;
+    lumpinfo = realloc(lumpinfo, numlumps * sizeof(lumpinfo_t *));
+    if (lumpinfo == NULL)
+    {
+        W_CloseFile(wad_file);
+        I_Error("Failed to increase lumpinfo[] array size.");
+    }
 
     filerover = fileinfo;
 
-    for (i=startlump; i<numlumps; ++i)
+    for (i = startlump; i < numlumps; ++i)
     {
-	lump_p->wad_file = wad_file;
-	lump_p->position = LONG(filerover->filepos);
-	lump_p->size = LONG(filerover->size);
+        lumpinfo_t *lump_p = &filelumps[i - startlump];
+        lump_p->wad_file = wad_file;
+        lump_p->position = LONG(filerover->filepos);
+        lump_p->size = LONG(filerover->size);
         lump_p->cache = NULL;
-	strncpy(lump_p->name, filerover->name, 8);
+        strncpy(lump_p->name, filerover->name, 8);
+        lumpinfo[i] = lump_p;
 
-        ++lump_p;
         ++filerover;
     }
 
@@ -262,6 +236,14 @@ wad_file_t *W_AddFile (char *filename)
         lumphash = NULL;
     }
 
+    // If this is the reload file, we need to save some details about the
+    // file so that we can close it later on when we do a reload.
+    if (reloadname)
+    {
+        reloadhandle = wad_file;
+        reloadlumps = filelumps;
+    }
+
     return wad_file;
 }
 
@@ -282,38 +264,37 @@ int W_NumLumps (void)
 // Returns -1 if name not found.
 //
 
-int W_CheckNumForName (char* name)
+lumpindex_t W_CheckNumForName(char* name)
 {
-    lumpinfo_t *lump_p;
-    int i;
+    lumpindex_t i;
 
     // Do we have a hash table yet?
 
     if (lumphash != NULL)
     {
         int hash;
-        
+
         // We do! Excellent.
 
         hash = W_LumpNameHash(name) % numlumps;
-        
-        for (lump_p = lumphash[hash]; lump_p != NULL; lump_p = lump_p->next)
+
+        for (i = lumphash[hash]; i != -1; i = lumpinfo[i]->next)
         {
-            if (!strncasecmp(lump_p->name, name, 8))
+            if (!strncasecmp(lumpinfo[i]->name, name, 8))
             {
-                return lump_p - lumpinfo;
+                return i;
             }
         }
-    } 
+    }
     else
     {
         // We don't have a hash table generate yet. Linear search :-(
-        // 
+        //
         // scan backwards so patch lump files take precedence
 
-        for (i=numlumps-1; i >= 0; --i)
+        for (i = numlumps - 1; i >= 0; --i)
         {
-            if (!strncasecmp(lumpinfo[i].name, name, 8))
+            if (!strncasecmp(lumpinfo[i]->name, name, 8))
             {
                 return i;
             }
@@ -332,12 +313,12 @@ int W_CheckNumForName (char* name)
 // W_GetNumForName
 // Calls W_CheckNumForName, but bombs out if not found.
 //
-int W_GetNumForName (char* name)
+lumpindex_t W_GetNumForName(char* name)
 {
-    int	i;
+    lumpindex_t i;
 
     i = W_CheckNumForName (name);
-    
+
     if (i < 0)
     {
         I_Error ("W_GetNumForName: %s not found!", name);
@@ -351,14 +332,14 @@ int W_GetNumForName (char* name)
 // W_LumpLength
 // Returns the buffer size needed to load the given lump.
 //
-int W_LumpLength (unsigned int lump)
+int W_LumpLength(lumpindex_t lump)
 {
     if (lump >= numlumps)
     {
 	I_Error ("W_LumpLength: %i >= numlumps", lump);
     }
 
-    return lumpinfo[lump].size;
+    return lumpinfo[lump]->size;
 }
 
 
@@ -368,29 +349,27 @@ int W_LumpLength (unsigned int lump)
 // Loads the lump into the given buffer,
 //  which must be >= W_LumpLength().
 //
-void W_ReadLump(unsigned int lump, void *dest)
+void W_ReadLump(lumpindex_t lump, void *dest)
 {
     int c;
     lumpinfo_t *l;
-	
+
     if (lump >= numlumps)
     {
-	I_Error ("W_ReadLump: %i >= numlumps", lump);
+        I_Error ("W_ReadLump: %i >= numlumps", lump);
     }
 
-    l = lumpinfo+lump;
-	
-    I_BeginRead ();
-	
+    l = lumpinfo[lump];
+
+    V_BeginRead(l->size);
+
     c = W_Read(l->wad_file, l->position, dest, l->size);
 
     if (c < l->size)
     {
-	I_Error ("W_ReadLump: only read %i of %i on lump %i",
-		 c, l->size, lump);	
+        I_Error("W_ReadLump: only read %i of %i on lump %i",
+                c, l->size, lump);
     }
-
-    I_EndRead ();
 }
 
 
@@ -408,7 +387,7 @@ void W_ReadLump(unsigned int lump, void *dest)
 // when no longer needed (do not use Z_ChangeTag).
 //
 
-void *W_CacheLumpNum(int lumpnum, int tag)
+void *W_CacheLumpNum(lumpindex_t lumpnum, int tag)
 {
     byte *result;
     lumpinfo_t *lump;
@@ -418,7 +397,7 @@ void *W_CacheLumpNum(int lumpnum, int tag)
 	I_Error ("W_CacheLumpNum: %i >= numlumps", lumpnum);
     }
 
-    lump = &lumpinfo[lumpnum];
+    lump = lumpinfo[lumpnum];
 
     // Get the pointer to return.  If the lump is in a memory-mapped
     // file, we can just return a pointer to within the memory-mapped
@@ -470,7 +449,7 @@ void *W_CacheLumpName(char *name, int tag)
 // complicated ...
 //
 
-void W_ReleaseLumpNum(int lumpnum)
+void W_ReleaseLumpNum(lumpindex_t lumpnum)
 {
     lumpinfo_t *lump;
 
@@ -479,7 +458,7 @@ void W_ReleaseLumpNum(int lumpnum)
 	I_Error ("W_ReleaseLumpNum: %i >= numlumps", lumpnum);
     }
 
-    lump = &lumpinfo[lumpnum];
+    lump = lumpinfo[lumpnum];
 
     if (lump->wad_file->mapped != NULL)
     {
@@ -566,7 +545,7 @@ void W_Profile (void)
 
 void W_GenerateHashTable(void)
 {
-    unsigned int i;
+    lumpindex_t i;
 
     // Free the old hash table, if there is one:
     if (lumphash != NULL)
@@ -577,19 +556,23 @@ void W_GenerateHashTable(void)
     // Generate hash table
     if (numlumps > 0)
     {
-        lumphash = Z_Malloc(sizeof(lumpinfo_t *) * numlumps, PU_STATIC, NULL);
-        memset(lumphash, 0, sizeof(lumpinfo_t *) * numlumps);
+        lumphash = Z_Malloc(sizeof(lumpindex_t) * numlumps, PU_STATIC, NULL);
 
-        for (i=0; i<numlumps; ++i)
+        for (i = 0; i < numlumps; ++i)
+        {
+            lumphash[i] = -1;
+        }
+
+        for (i = 0; i < numlumps; ++i)
         {
             unsigned int hash;
 
-            hash = W_LumpNameHash(lumpinfo[i].name) % numlumps;
+            hash = W_LumpNameHash(lumpinfo[i]->name) % numlumps;
 
             // Hook into the hash table
 
-            lumpinfo[i].next = lumphash[hash];
-            lumphash[hash] = &lumpinfo[i];
+            lumpinfo[i]->next = lumphash[hash];
+            lumphash[hash] = i;
         }
     }
 
@@ -605,19 +588,19 @@ void W_GenerateHashTable(void)
 void W_Reload(void)
 {
     char *filename;
-    int i;
+    lumpindex_t i;
 
     if (reloadname == NULL)
     {
         return;
     }
 
-    // We must release any lumps being held in the PWAD we're about to reload:
+    // We must free any lumps being cached from the PWAD we're about to reload:
     for (i = reloadlump; i < numlumps; ++i)
     {
-        if (lumpinfo[i].cache != NULL)
+        if (lumpinfo[i]->cache != NULL)
         {
-            W_ReleaseLumpNum(i);
+            Z_Free(lumpinfo[i]->cache);
         }
     }
 
@@ -628,6 +611,8 @@ void W_Reload(void)
     filename = reloadname;
 
     W_CloseFile(reloadhandle);
+    free(reloadlumps);
+
     reloadname = NULL;
     reloadlump = -1;
     reloadhandle = NULL;
@@ -639,44 +624,3 @@ void W_Reload(void)
     W_GenerateHashTable();
 }
 
-// Lump names that are unique to particular game types. This lets us check
-// the user is not trying to play with the wrong executable, eg.
-// chocolate-doom -iwad hexen.wad.
-static const struct
-{
-    GameMission_t mission;
-    char *lumpname;
-} unique_lumps[] = {
-    { doom,    "POSSA1" },
-    { heretic, "IMPXA1" },
-    { hexen,   "ETTNA1" },
-    { strife,  "AGRDA1" },
-};
-
-void W_CheckCorrectIWAD(GameMission_t mission)
-{
-    int i;
-    int lumpnum;
-
-    for (i = 0; i < arrlen(unique_lumps); ++i)
-    {
-        if (mission != unique_lumps[i].mission)
-        {
-            lumpnum = W_CheckNumForName(unique_lumps[i].lumpname);
-
-            if (lumpnum >= 0)
-            {
-                I_Error("\nYou are trying to use a %s IWAD file with "
-                        "the %s%s binary.\nThis isn't going to work.\n"
-                        "You probably want to use the %s%s binary.",
-                        D_SuggestGameName(unique_lumps[i].mission,
-                                          indetermined),
-                        PROGRAM_PREFIX,
-                        D_GameMissionString(mission),
-                        PROGRAM_PREFIX,
-                        D_GameMissionString(unique_lumps[i].mission));
-            }
-        }
-    }
-}
-
diff --git a/src/w_wad.h b/src/w_wad.h
index e5dc18b..06cf395 100644
--- a/src/w_wad.h
+++ b/src/w_wad.h
@@ -23,8 +23,6 @@
 #include <stdio.h>
 
 #include "doomtype.h"
-#include "d_mode.h"
-
 #include "w_file.h"
 
 
@@ -37,6 +35,7 @@
 //
 
 typedef struct lumpinfo_s lumpinfo_t;
+typedef int lumpindex_t;
 
 struct lumpinfo_s
 {
@@ -47,33 +46,30 @@ struct lumpinfo_s
     void       *cache;
 
     // Used for hash table lookups
-
-    lumpinfo_t *next;
+    lumpindex_t next;
 };
 
 
-extern lumpinfo_t *lumpinfo;
+extern lumpinfo_t **lumpinfo;
 extern unsigned int numlumps;
 
-wad_file_t *W_AddFile (char *filename);
-void    W_Reload (void);
+wad_file_t *W_AddFile(char *filename);
+void W_Reload(void);
 
-int	W_CheckNumForName (char* name);
-int	W_GetNumForName (char* name);
+lumpindex_t W_CheckNumForName(char *name);
+lumpindex_t W_GetNumForName(char *name);
 
-int	W_LumpLength (unsigned int lump);
-void    W_ReadLump (unsigned int lump, void *dest);
+int W_LumpLength(lumpindex_t lump);
+void W_ReadLump(lumpindex_t lump, void *dest);
 
-void*	W_CacheLumpNum (int lump, int tag);
-void*	W_CacheLumpName (char* name, int tag);
+void *W_CacheLumpNum(lumpindex_t lump, int tag);
+void *W_CacheLumpName(char *name, int tag);
 
-void    W_GenerateHashTable(void);
+void W_GenerateHashTable(void);
 
 extern unsigned int W_LumpNameHash(const char *s);
 
-void    W_ReleaseLumpNum(int lump);
-void    W_ReleaseLumpName(char *name);
-
-void W_CheckCorrectIWAD(GameMission_t mission);
+void W_ReleaseLumpNum(lumpindex_t lump);
+void W_ReleaseLumpName(char *name);
 
 #endif
diff --git a/src/z_zone.c b/src/z_zone.c
index f88e3f0..828fd7e 100644
--- a/src/z_zone.c
+++ b/src/z_zone.c
@@ -122,13 +122,13 @@ void Z_Init (void)
 
     block->size = mainzone->size - sizeof(memzone_t);
 
-    //!
+    // [Deliberately undocumented]
     // Zone memory debugging flag. If set, memory is zeroed after it is freed
     // to deliberately break any code that attempts to use it after free.
     //
     zero_on_free = M_ParmExists("-zonezero");
 
-    //!
+    // [Deliberately undocumented]
     // Zone memory debugging flag. If set, each time memory is freed, the zone
     // heap is scanned to look for remaining pointers to the freed block.
     //
diff --git a/textscreen/examples/guitest.c b/textscreen/examples/guitest.c
index 46060bf..2d0060d 100644
--- a/textscreen/examples/guitest.c
+++ b/textscreen/examples/guitest.c
@@ -103,14 +103,14 @@ void SetupWindow(void)
 
     toplabel = TXT_NewLabel("This is a multiline label.\n"
                             "A single label object contains \n"
-                            "all three of these lines.\n");
+                            "all three of these lines.");
     TXT_AddWidget(window, toplabel);
     TXT_SetWidgetAlign(toplabel, TXT_HORIZ_CENTER);
 
     //TXT_AddWidget(window, TXT_NewScrollPane(15, 4, table));
     TXT_AddWidget(window, table);
 
-    for (i=0; i<5; ++i)
+    for (i=0; i<3; ++i)
     {
         TXT_snprintf(buf, sizeof(buf), "Option %i in a table:", i + 1);
         TXT_AddWidget(table, TXT_NewLabel(buf));
@@ -120,6 +120,17 @@ void SetupWindow(void)
         TXT_AddWidget(table, TXT_NewButton(buf));
     }
 
+    TXT_AddWidgets(table,
+                   TXT_NewLabel("Still the same table, but:\n"
+                                "This label magically overflows\n"
+                                "across multiple cells! Cool, huh? "),
+                   TXT_TABLE_OVERFLOW_RIGHT,
+                   TXT_NewButton("Do nothing"),
+                   TXT_TABLE_OVERFLOW_DOWN,
+                   TXT_TABLE_OVERFLOW_DOWN,
+                   TXT_NewButton("Also nothing"),
+                   NULL);
+
     TXT_AddWidget(window, TXT_NewStrut(0, 1));
     value_label = TXT_NewLabel("");
     TXT_AddWidget(window, value_label);
diff --git a/textscreen/txt_checkbox.c b/textscreen/txt_checkbox.c
index 70e6fdc..0b2c2e3 100644
--- a/textscreen/txt_checkbox.c
+++ b/textscreen/txt_checkbox.c
@@ -65,7 +65,7 @@ static void TXT_CheckBoxDrawer(TXT_UNCAST_ARG(checkbox))
     TXT_SetWidgetBG(checkbox);
     TXT_DrawString(checkbox->label);
 
-    for (i=strlen(checkbox->label); i < w-5; ++i)
+    for (i=strlen(checkbox->label); i < w-4; ++i)
     {
         TXT_DrawString(" ");
     }
diff --git a/textscreen/txt_scrollpane.c b/textscreen/txt_scrollpane.c
index e1f2d3c..2388d0c 100644
--- a/textscreen/txt_scrollpane.c
+++ b/textscreen/txt_scrollpane.c
@@ -402,7 +402,8 @@ static int TXT_ScrollPaneKeyPress(TXT_UNCAST_ARG(scrollpane), int key)
 
         if ((key == KEY_UPARROW || key == KEY_DOWNARROW
           || key == KEY_LEFTARROW || key == KEY_RIGHTARROW
-          || key == KEY_PGUP || key == KEY_PGDN)
+          || key == KEY_PGUP || key == KEY_PGDN
+          || key == KEY_TAB)
          && scrollpane->child->widget_class == &txt_table_class)
         {
             if (PageSelectedWidget(scrollpane, key))
diff --git a/textscreen/txt_table.c b/textscreen/txt_table.c
index 218fade..86537c9 100644
--- a/textscreen/txt_table.c
+++ b/textscreen/txt_table.c
@@ -26,6 +26,20 @@
 #include "txt_strut.h"
 #include "txt_table.h"
 
+txt_widget_t txt_table_overflow_right;
+txt_widget_t txt_table_overflow_down;
+txt_widget_t txt_table_eol;
+txt_widget_t txt_table_empty;
+
+// Returns true if the given widget in the table's widgets[] array refers
+// to an actual widget - not NULL, or one of the special overflow pointers.
+static int IsActualWidget(txt_widget_t *widget)
+{
+    return widget != NULL
+        && widget != &txt_table_overflow_right
+        && widget != &txt_table_overflow_down;
+}
+
 // Remove all entries from a table
 
 void TXT_ClearTable(TXT_UNCAST_ARG(table))
@@ -39,12 +53,12 @@ void TXT_ClearTable(TXT_UNCAST_ARG(table))
 
     for (i=table->columns; i<table->num_widgets; ++i)
     {
-        if (table->widgets[i] != NULL)
+        if (IsActualWidget(table->widgets[i]))
         {
             TXT_DestroyWidget(table->widgets[i]);
         }
     }
-    
+
     // Shrink the table to just the column strut widgets
 
     table->num_widgets = table->columns;
@@ -62,8 +76,101 @@ static int TableRows(txt_table_t *table)
     return (table->num_widgets + table->columns - 1) / table->columns;
 }
 
-static void CalcRowColSizes(txt_table_t *table, 
-                            unsigned int *row_heights, 
+// Most widgets occupy just one cell of a table, but if the special
+// overflow constants are used, they can occupy multiple cells.
+// This function figures out for a widget in a given cell, which
+// cells it should actually occupy (always a rectangle).
+static void CellOverflowedSize(txt_table_t *table, int x, int y,
+                               int *w, int *h)
+{
+    txt_widget_t *widget;
+    int x1, y1;
+
+    if (!IsActualWidget(table->widgets[y * table->columns + x]))
+    {
+        *w = 0; *h = 0;
+        return;
+    }
+
+    *w = table->columns - x;
+    *h = 0;
+    for (y1 = y; y1 < TableRows(table); ++y1)
+    {
+        // Every overflow cell must point to either (x, y) or another
+        // overflow cell. This means the first in every row must be
+        // txt_table_overflow_down.
+
+        if (y1 * table->columns + x >= table->num_widgets)
+        {
+            break;
+        }
+
+        widget = table->widgets[y1 * table->columns + x];
+
+        if (y1 != y && widget != &txt_table_overflow_down)
+        {
+            break;
+        }
+
+        for (x1 = x + 1; x1 < x + *w; ++x1)
+        {
+            if (y1 * table->columns + x1 >= table->num_widgets)
+            {
+                break;
+            }
+
+            // Can be either type of overflow, except on the first row.
+            // Otherwise we impose a limit on the width.
+            widget = table->widgets[y1 * table->columns + x1];
+            if (widget != &txt_table_overflow_right
+             && (widget != &txt_table_overflow_down || y1 == y))
+            {
+                *w = x1 - x;
+                break;
+            }
+        }
+
+        ++*h;
+    }
+}
+
+static int IsOverflowingCell(txt_table_t *table, int x, int y)
+{
+    int w, h;
+    CellOverflowedSize(table, x, y, &w, &h);
+    return w > 1 || h > 1;
+}
+
+// Using the given column/row size tables, calculate the size of the given
+// widget, storing the result in (w, h).
+static void CalculateWidgetDimensions(txt_table_t *table,
+                                      int x, int y,
+                                      unsigned int *column_widths,
+                                      unsigned int *row_heights,
+                                      unsigned int *w, unsigned int *h)
+{
+    int cell_w, cell_h;
+    int x1, y1;
+
+    // Find which cells this widget occupies.
+    CellOverflowedSize(table, x, y, &cell_w, &cell_h);
+
+    // Add up column / row widths / heights to get the actual dimensions.
+    *w = 0;
+    for (x1 = x; x1 < x + cell_w; ++x1)
+    {
+        *w += column_widths[x1];
+    }
+
+    *h = 0;
+    for (y1 = y; y1 < y + cell_h; ++y1)
+    {
+        *h += row_heights[y1];
+    }
+}
+
+static void CalcRowColSizes(txt_table_t *table,
+                            unsigned int *row_heights,
                             unsigned int *col_widths)
 {
     int x, y;
@@ -74,22 +181,31 @@ static void CalcRowColSizes(txt_table_t *table,
 
     memset(col_widths, 0, sizeof(int) * table->columns);
 
-    for (y=0; y<rows; ++y)
+    for (y = 0; y < rows; ++y)
     {
         row_heights[y] = 0;
 
-        for (x=0; x<table->columns; ++x)
+        for (x = 0; x < table->columns; ++x)
         {
             if (y * table->columns + x >= table->num_widgets)
                 break;
 
             widget = table->widgets[y * table->columns + x];
 
-            // NULL represents an empty spacer
-
-            if (widget != NULL)
+            if (IsActualWidget(widget))
             {
                 TXT_CalcWidgetSize(widget);
+            }
+
+            // In the first pass we ignore overflowing cells.
+            if (IsOverflowingCell(table, x, y))
+            {
+                continue;
+            }
+
+            // NULL represents an empty spacer
+            if (IsActualWidget(widget))
+            {
                 if (widget->h > row_heights[y])
                     row_heights[y] = widget->h;
                 if (widget->w > col_widths[x])
@@ -97,6 +213,37 @@ static void CalcRowColSizes(txt_table_t *table,
             }
         }
     }
+
+    // In the second pass, we go through again and process overflowing
+    // widgets, to ensure that they will fit.
+    for (y = 0; y < rows; ++y)
+    {
+        for (x = 0; x < table->columns; ++x)
+        {
+            unsigned int w, h;
+
+            if (y * table->columns + x >= table->num_widgets)
+                break;
+
+            widget = table->widgets[y * table->columns + x];
+            if (!IsActualWidget(widget))
+            {
+                continue;
+            }
+
+            // Expand column width and row heights as needed.
+            CalculateWidgetDimensions(table, x, y, col_widths, row_heights,
+                                      &w, &h);
+            if (w < widget->w)
+            {
+                col_widths[x] += widget->w - w;
+            }
+            if (h < widget->h)
+            {
+                row_heights[y] += widget->h - h;
+            }
+        }
+    }
 }
 
 static void TXT_CalcTableSize(TXT_UNCAST_ARG(table))
@@ -132,47 +279,83 @@ static void TXT_CalcTableSize(TXT_UNCAST_ARG(table))
     free(column_widths);
 }
 
+static void FillRowToEnd(txt_table_t *table)
+{
+    while ((table->num_widgets % table->columns) != 0)
+    {
+        TXT_AddWidget(table, &txt_table_overflow_right);
+    }
+}
+
 void TXT_AddWidget(TXT_UNCAST_ARG(table), TXT_UNCAST_ARG(widget))
 {
     TXT_CAST_ARG(txt_table_t, table);
     TXT_CAST_ARG(txt_widget_t, widget);
+    int is_separator;
+    int i;
 
-    if (table->num_widgets > 0)
+    // Convenience alias for NULL:
+    if (widget == &txt_table_empty)
+    {
+        widget = NULL;
+    }
+    else if (widget == &txt_table_eol)
     {
-        txt_widget_t *last_widget;
+        FillRowToEnd(table);
+        return;
+    }
 
-        last_widget = table->widgets[table->num_widgets - 1];
+    // We have special handling for the separator widget:
+    is_separator = IsActualWidget(widget)
+                && widget->widget_class == &txt_separator_class;
 
-        if (widget != NULL && last_widget != NULL
-         && widget->widget_class == &txt_separator_class
-         && last_widget->widget_class == &txt_separator_class)
+    // If we add two separators consecutively, the new separator replaces the
+    // first. This allows us to override the "implicit" separator that is
+    // added at the top of a window when it is created.
+    if (is_separator)
+    {
+        for (i = table->num_widgets - 1; i >= 0; --i)
         {
-            // The previous widget added was a separator; replace 
-            // it with this one.
-            //
-            // This way, if the first widget added to a window is 
-            // a separator, it replaces the "default" separator that
-            // the window itself adds on creation.
-
-            table->widgets[table->num_widgets - 1] = widget;
-
-            TXT_DestroyWidget(last_widget);
+            txt_widget_t *last_widget;
+            last_widget = table->widgets[i];
 
-            return;
+            if (IsActualWidget(last_widget)
+             && widget->widget_class == &txt_separator_class
+             && last_widget->widget_class == &txt_separator_class)
+            {
+                table->widgets[i] = widget;
+                TXT_DestroyWidget(last_widget);
+                return;
+            }
+            else if (last_widget != &txt_table_overflow_right)
+            {
+                break;
+            }
         }
     }
 
+    // Separators begin on a new line.
+    if (is_separator)
+    {
+        FillRowToEnd(table);
+    }
+
     table->widgets = realloc(table->widgets,
                              sizeof(txt_widget_t *) * (table->num_widgets + 1));
     table->widgets[table->num_widgets] = widget;
     ++table->num_widgets;
 
     // Maintain parent pointer.
-
-    if (widget != NULL)
+    if (IsActualWidget(widget))
     {
         widget->parent = &table->widget;
     }
+
+    // Separators always take up the entire line.
+    if (is_separator)
+    {
+        FillRowToEnd(table);
+    }
 }
 
 // Add multiple widgets to a table.
@@ -217,7 +400,7 @@ static int SelectableCell(txt_table_t *table, int x, int y)
     if (i >= 0 && i < table->num_widgets)
     {
         widget = table->widgets[i];
-        return widget != NULL
+        return IsActualWidget(widget)
             && TXT_SelectableWidget(widget)
             && widget->visible;
     }
@@ -280,7 +463,7 @@ static void ChangeSelection(txt_table_t *table, int x, int y)
     {
         cur_widget = table->widgets[i];
 
-        if (table->widget.focused && cur_widget != NULL)
+        if (table->widget.focused && IsActualWidget(cur_widget))
         {
             TXT_SetWidgetFocus(cur_widget, 0);
         }
@@ -313,7 +496,7 @@ static int TXT_TableKeyPress(TXT_UNCAST_ARG(table), int key)
 
     if (selected >= 0 && selected < table->num_widgets)
     {
-        if (table->widgets[selected] != NULL
+        if (IsActualWidget(table->widgets[selected])
          && TXT_SelectableWidget(table->widgets[selected])
          && TXT_WidgetKeyPress(table->widgets[selected], key))
         {
@@ -321,6 +504,29 @@ static int TXT_TableKeyPress(TXT_UNCAST_ARG(table), int key)
         }
     }
 
+    if (key == KEY_TAB)
+    {
+        int dir;
+        int i;
+
+        dir = TXT_GetModifierState(TXT_MOD_SHIFT) ? -1 : 1;
+
+        // Cycle through all widgets until we find one that can be selected.
+        for (i = table->selected_y * table->columns + table->selected_x + dir;
+             i >= 0 && i < table->num_widgets;
+             i += dir)
+        {
+            if (IsActualWidget(table->widgets[i])
+             && TXT_SelectableWidget(table->widgets[i]))
+            {
+                ChangeSelection(table, i % table->columns, i / table->columns);
+                return 1;
+            }
+        }
+
+        return 0;
+    }
+
     if (key == KEY_DOWNARROW)
     {
         int new_x, new_y;
@@ -330,7 +536,7 @@ static int TXT_TableKeyPress(TXT_UNCAST_ARG(table), int key)
         for (new_y = table->selected_y + 1; new_y < rows; ++new_y)
         {
             new_x = FindSelectableColumn(table, new_y, table->selected_x);
-                            
+
             if (new_x >= 0)
             {
                 // Found a selectable widget in this column!
@@ -339,7 +545,7 @@ static int TXT_TableKeyPress(TXT_UNCAST_ARG(table), int key)
 
                 return 1;
             }
-        } 
+        }
     }
 
     if (key == KEY_UPARROW)
@@ -360,7 +566,7 @@ static int TXT_TableKeyPress(TXT_UNCAST_ARG(table), int key)
 
                 return 1;
             }
-        } 
+        }
     }
 
     if (key == KEY_LEFTARROW)
@@ -428,13 +634,16 @@ static void CheckValidSelection(txt_table_t *table)
     }
 }
 
-static void LayoutCell(txt_table_t *table, int x, int y, int col_width,
+static void LayoutCell(txt_table_t *table, int x, int y,
                        int draw_x, int draw_y)
 {
     txt_widget_t *widget;
+    int col_width;
 
     widget = table->widgets[y * table->columns + x];
 
+    col_width = widget->w;
+
     // Adjust x position based on alignment property
 
     switch (widget->align)
@@ -445,24 +654,23 @@ static void LayoutCell(txt_table_t *table, int x, int y, int col_width,
 
         case TXT_HORIZ_CENTER:
             TXT_CalcWidgetSize(widget);
-            
+
             // Separators are always drawn left-aligned.
 
             if (widget->widget_class != &txt_separator_class)
             {
                 draw_x += (col_width - widget->w) / 2;
             }
-            
+
             break;
 
         case TXT_HORIZ_RIGHT:
             TXT_CalcWidgetSize(widget);
-            
+
             if (widget->widget_class != &txt_separator_class)
             {
                 draw_x += col_width - widget->w;
             }
-            
             break;
     }
 
@@ -481,6 +689,7 @@ static void TXT_TableLayout(TXT_UNCAST_ARG(table))
     TXT_CAST_ARG(txt_table_t, table);
     unsigned int *column_widths;
     unsigned int *row_heights;
+    txt_widget_t *widget;
     int draw_x, draw_y;
     int x, y;
     int i;
@@ -505,9 +714,9 @@ static void TXT_TableLayout(TXT_UNCAST_ARG(table))
     }
 
     // Draw all cells
-    
+
     draw_y = table->widget.y;
-    
+
     for (y=0; y<rows; ++y)
     {
         draw_x = table->widget.x;
@@ -519,10 +728,14 @@ static void TXT_TableLayout(TXT_UNCAST_ARG(table))
             if (i >= table->num_widgets)
                 break;
 
-            if (table->widgets[i] != NULL)
+            widget = table->widgets[i];
+
+            if (IsActualWidget(widget))
             {
-                LayoutCell(table, x, y, column_widths[x], 
-                           draw_x, draw_y);
+                CalculateWidgetDimensions(table, x, y,
+                                          column_widths, row_heights,
+                                          &widget->w, &widget->h);
+                LayoutCell(table, x, y, draw_x, draw_y);
             }
 
             draw_x += column_widths[x];
@@ -552,7 +765,7 @@ static void TXT_TableDrawer(TXT_UNCAST_ARG(table))
     {
         widget = table->widgets[i];
 
-        if (widget != NULL)
+        if (IsActualWidget(widget))
         {
             TXT_GotoXY(widget->x, widget->y);
             TXT_DrawWidget(widget);
@@ -574,7 +787,7 @@ static void TXT_TableMousePress(TXT_UNCAST_ARG(table), int x, int y, int b)
 
         // NULL widgets are spacers
 
-        if (widget != NULL)
+        if (IsActualWidget(widget))
         {
             if (x >= widget->x && x < (signed) (widget->x + widget->w)
              && y >= widget->y && y < (signed) (widget->y + widget->h))
@@ -617,7 +830,7 @@ static int TXT_TableSelectable(TXT_UNCAST_ARG(table))
 
     for (i = 0; i < table->num_widgets; ++i)
     {
-        if (table->widgets[i] != NULL
+        if (IsActualWidget(table->widgets[i])
          && TXT_SelectableWidget(table->widgets[i]))
         {
             ChangeSelection(table, i % table->columns, i / table->columns);
@@ -641,7 +854,7 @@ static void TXT_TableFocused(TXT_UNCAST_ARG(table), int focused)
 
     if (i < table->num_widgets)
     {
-        if (table->widgets[i] != NULL)
+        if (IsActualWidget(table->widgets[i]))
         {
             TXT_SetWidgetFocus(table->widgets[i], focused);
         }
@@ -777,6 +990,11 @@ txt_widget_t *TXT_GetSelectedWidget(TXT_UNCAST_ARG(table))
     if (index >= 0 && index < table->num_widgets)
     {
         result = table->widgets[index];
+
+        if (!IsActualWidget(result))
+        {
+            result = NULL;
+        }
     }
 
     if (result != NULL && result->widget_class == &txt_table_class)
@@ -798,7 +1016,7 @@ int TXT_SelectWidget(TXT_UNCAST_ARG(table), TXT_UNCAST_ARG(widget))
 
     for (i=0; i<table->num_widgets; ++i)
     {
-        if (table->widgets[i] == NULL)
+        if (!IsActualWidget(table->widgets[i]))
         {
             continue;
         }
@@ -832,6 +1050,66 @@ int TXT_SelectWidget(TXT_UNCAST_ARG(table), TXT_UNCAST_ARG(widget))
     return 0;
 }
 
+void TXT_SetTableColumns(TXT_UNCAST_ARG(table), int new_columns)
+{
+    TXT_CAST_ARG(txt_table_t, table);
+    txt_widget_t **new_widgets;
+    txt_widget_t *widget;
+    int new_num_widgets;
+    int i, j, x;
+
+    // We need as many full rows as are in the current list, plus the
+    // remainder from the last row.
+    new_num_widgets = (table->num_widgets / table->columns) * new_columns
+                    + (table->num_widgets % table->columns);
+    new_widgets = calloc(new_num_widgets, sizeof(txt_widget_t *));
+
+    // Reset and add one by one from the old table.
+    new_num_widgets = 0;
+
+    for (i = 0; i < table->num_widgets; ++i)
+    {
+        widget = table->widgets[i];
+        x = i % table->columns;
+
+        if (x < new_columns)
+        {
+            new_widgets[new_num_widgets] = widget;
+            ++new_num_widgets;
+        }
+        else if (IsActualWidget(widget))
+        {
+            TXT_DestroyWidget(widget);
+        }
+
+        // When we reach the last column of a row, we must pad it out with
+        // extra widgets to reach the next row.
+        if (x == table->columns - 1)
+        {
+            for (j = table->columns; j < new_columns; ++j)
+            {
+                // First row? We need to add struts that are used to apply
+                // the column widths.
+                if (i < table->columns)
+                {
+                    widget = &TXT_NewStrut(0, 0)->widget;
+                }
+                else
+                {
+                    widget = &txt_table_overflow_right;
+                }
+                new_widgets[new_num_widgets] = widget;
+                ++new_num_widgets;
+            }
+        }
+    }
+
+    free(table->widgets);
+    table->widgets = new_widgets;
+    table->num_widgets = new_num_widgets;
+    table->columns = new_columns;
+}
+
 // Sets the widths of columns in a table.
 
 void TXT_SetColumnWidths(TXT_UNCAST_ARG(table), ...)
diff --git a/textscreen/txt_table.h b/textscreen/txt_table.h
index 6d356cc..98eb30f 100644
--- a/textscreen/txt_table.h
+++ b/textscreen/txt_table.h
@@ -22,6 +22,33 @@
  */
 
 /**
+ * Magic value that if used in a table, will indicate that the cell is
+ * empty and the widget in the cell to the left can overflow into it.
+ */
+
+#define TXT_TABLE_OVERFLOW_RIGHT (&txt_table_overflow_right)
+
+/**
+ * Magic value that if used in a table, will indicate that the cell is
+ * empty and the widget in the cell above it can overflow down into it.
+ */
+
+#define TXT_TABLE_OVERFLOW_DOWN (&txt_table_overflow_down)
+
+/**
+ * Magic value that if given to @ref TXT_AddWidget(), will pad out all
+ * columns until the end of line.
+ */
+#define TXT_TABLE_EOL (&txt_table_eol)
+
+/**
+ * Indicates an empty space to @ref TXT_AddWidgets(). Equivalent to
+ * TXT_AddWidget(table, NULL), except that NULL is used by TXT_AddWidgets()
+ * to indicate the end of input.
+ */
+#define TXT_TABLE_EMPTY (&txt_table_empty)
+
+/**
  * Table widget.
  *
  * A table is a widget that contains other widgets.  It may have
@@ -45,21 +72,22 @@ struct txt_table_s
 
     // Widgets in this table
     // The widget at (x,y) in the table is widgets[columns * y + x]
-
     txt_widget_t **widgets;
     int num_widgets;
 
     // Number of columns
-
     int columns;
 
-    // Currently selected 
-
+    // Currently selected:
     int selected_x;
     int selected_y;
 };
 
 extern txt_widget_class_t txt_table_class;
+extern txt_widget_t txt_table_overflow_right;
+extern txt_widget_t txt_table_overflow_down;
+extern txt_widget_t txt_table_eol;
+extern txt_widget_t txt_table_empty;
 
 void TXT_InitTable(txt_table_t *table, int columns);
 
@@ -145,6 +173,22 @@ void TXT_AddWidgets(TXT_UNCAST_ARG(table), ...);
 int TXT_SelectWidget(TXT_UNCAST_ARG(table), TXT_UNCAST_ARG(widget));
 
 /**
+ * Change the number of columns in the table.
+ *
+ * Existing widgets in the table will be preserved, unless the change
+ * reduces the number of columns, in which case the widgets from the
+ * 'deleted' columns will be freed.
+ *
+ * This function can be useful for changing the number of columns in
+ * a window, which by default are tables containing a single column.
+ *
+ * @param table         The table.
+ * @param new_columns   The new number of columns.
+ */
+
+void TXT_SetTableColumns(TXT_UNCAST_ARG(table), int new_columns);
+
+/**
  * Set the widths of the columns of the table.
  *
  * The arguments to this function are variable, and correspond

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-games/chocolate-doom.git



More information about the Pkg-games-commits mailing list