diff --git a/.travis.yml b/.travis.yml
index f2ed43000addf70ea07416fad3d5281472eec927..9d91b77dfc937a48da16ead5cf80298711e92da5 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -459,11 +459,15 @@ matrix:
         - os: linux
           addons:
             apt:
+              sources:
+              - ubuntu-toolchain-r-test
+              - sourceline: 'ppa:stjr/srb2'
               packages:
               - libsdl2-mixer-dev
               - libpng-dev
               - libgl1-mesa-dev
               - libgme-dev
+              - libopenmpt-dev
               - p7zip-full
               - gcc-4.8
           compiler: gcc-4.8
@@ -473,43 +477,51 @@ matrix:
               AND env(DPL_TERMINATE_MAIN) != "1"
           env:
           - _DPL_JOB_ENABLED=1
-          - _DPL_JOB_NAME=bionic
+          - _DPL_JOB_NAME=eoan
           - _DPL_DPUT_TARGET=1
           - _DPL_PACKAGE_SOURCE=1
-          - PACKAGE_DISTRO=bionic
-          - PACKAGE_SUBVERSION=~18.04bionic
+          - PACKAGE_DISTRO=eoan
+          - PACKAGE_SUBVERSION=~19.10eoan
           #gcc-4.8 (Ubuntu 4.8.5-2ubuntu1~14.04.1) 4.8.5
         - os: linux
           addons:
             apt:
+              sources:
+              - ubuntu-toolchain-r-test
+              - sourceline: 'ppa:stjr/srb2'
               packages:
               - libsdl2-mixer-dev
               - libpng-dev
               - libgl1-mesa-dev
               - libgme-dev
+              - libopenmpt-dev
               - p7zip-full
               - gcc-4.8
           compiler: gcc-4.8
-          dist: trusty
+          dist: xenial
           if: env(DPL_ENABLED) = "1" AND (env(_DPL_JOB_ENABLED) = "1" OR env(DPL_JOB_ENABLE_ALL) = "1")
               AND (branch =~ /^.*deployer.*$/ OR (tag IS present AND env(DPL_TAG_ENABLED) = "1"))
               AND env(DPL_TERMINATE_MAIN) != "1"
           env:
           - _DPL_JOB_ENABLED=1
-          - _DPL_JOB_NAME=trusty
+          - _DPL_JOB_NAME=disco
           - _DPL_DPUT_TARGET=1
           - _DPL_PACKAGE_SOURCE=1
-          - PACKAGE_DISTRO=trusty
-          - PACKAGE_SUBVERSION=~14.04trusty
+          - PACKAGE_DISTRO=disco
+          - PACKAGE_SUBVERSION=~19.04disco
           #gcc-4.8 (Ubuntu 4.8.5-2ubuntu1~14.04.1) 4.8.5
         - os: linux
           addons:
             apt:
+              sources:
+              - ubuntu-toolchain-r-test
+              - sourceline: 'ppa:stjr/srb2'
               packages:
               - libsdl2-mixer-dev
               - libpng-dev
               - libgl1-mesa-dev
               - libgme-dev
+              - libopenmpt-dev
               - p7zip-full
               - gcc-4.8
           compiler: gcc-4.8
@@ -519,20 +531,24 @@ matrix:
               AND env(DPL_TERMINATE_MAIN) != "1"
           env:
           - _DPL_JOB_ENABLED=1
-          - _DPL_JOB_NAME=disco
+          - _DPL_JOB_NAME=bionic
           - _DPL_DPUT_TARGET=1
           - _DPL_PACKAGE_SOURCE=1
-          - PACKAGE_DISTRO=disco
-          - PACKAGE_SUBVERSION=~19.04disco
+          - PACKAGE_DISTRO=bionic
+          - PACKAGE_SUBVERSION=~18.04bionic
           #gcc-4.8 (Ubuntu 4.8.5-2ubuntu1~14.04.1) 4.8.5
         - os: linux
           addons:
             apt:
+              sources:
+              - ubuntu-toolchain-r-test
+              - sourceline: 'ppa:stjr/srb2'
               packages:
               - libsdl2-mixer-dev
               - libpng-dev
               - libgl1-mesa-dev
               - libgme-dev
+              - libopenmpt-dev
               - p7zip-full
               - gcc-4.8
           compiler: gcc-4.8
@@ -542,34 +558,38 @@ matrix:
               AND env(DPL_TERMINATE_MAIN) != "1"
           env:
           - _DPL_JOB_ENABLED=1
-          - _DPL_JOB_NAME=cosmic
+          - _DPL_JOB_NAME=xenial
           - _DPL_DPUT_TARGET=1
           - _DPL_PACKAGE_SOURCE=1
-          - PACKAGE_DISTRO=cosmic
-          - PACKAGE_SUBVERSION=~18.10cosmic
+          - PACKAGE_DISTRO=xenial
+          - PACKAGE_SUBVERSION=~16.04xenial
           #gcc-4.8 (Ubuntu 4.8.5-2ubuntu1~14.04.1) 4.8.5
         - os: linux
           addons:
             apt:
+              sources:
+              - ubuntu-toolchain-r-test
+              - sourceline: 'ppa:stjr/srb2'
               packages:
               - libsdl2-mixer-dev
               - libpng-dev
               - libgl1-mesa-dev
               - libgme-dev
+              - libopenmpt-dev
               - p7zip-full
               - gcc-4.8
           compiler: gcc-4.8
-          dist: xenial
+          dist: trusty
           if: env(DPL_ENABLED) = "1" AND (env(_DPL_JOB_ENABLED) = "1" OR env(DPL_JOB_ENABLE_ALL) = "1")
               AND (branch =~ /^.*deployer.*$/ OR (tag IS present AND env(DPL_TAG_ENABLED) = "1"))
               AND env(DPL_TERMINATE_MAIN) != "1"
           env:
           - _DPL_JOB_ENABLED=1
-          - _DPL_JOB_NAME=xenial
+          - _DPL_JOB_NAME=trusty
           - _DPL_DPUT_TARGET=1
           - _DPL_PACKAGE_SOURCE=1
-          - PACKAGE_DISTRO=xenial
-          - PACKAGE_SUBVERSION=~16.04xenial
+          - PACKAGE_DISTRO=trusty
+          - PACKAGE_SUBVERSION=~14.04trusty
           #gcc-4.8 (Ubuntu 4.8.5-2ubuntu1~14.04.1) 4.8.5
     allow_failures:
       - compiler: clang-3.5
diff --git a/appveyor.yml b/appveyor.yml
index 90ee0048a4a26ea8fcce9b0b94f868f9c8cef60d..0edc7ce280110a6af014bd204bc9a7054c3d6df8 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -29,15 +29,15 @@ environment:
  ##############################
  DPL_ENABLED: 0
  DPL_TAG_ENABLED: 0
- DPL_INSTALLER_NAME: SRB2-v2123
+ DPL_INSTALLER_NAME: SRB2-v220
  # Asset handling is barebones vs. Travis Deployer. We operate on 7z only.
  # Include the README files and the OpenGL batch in the main and patch archives.
  # The x86/x64 archives contain the DLL binaries.
- ASSET_ARCHIVE_PATH: https://github.com/mazmazz/SRB2/releases/download/SRB2_assets/SRB2-v2122-assets.7z
- ASSET_ARCHIVE_PATCH_PATH: https://github.com/mazmazz/SRB2/releases/download/SRB2_assets/SRB2-v2122-patch-assets.7z
- ASSET_ARCHIVE_X86_PATH: https://github.com/mazmazz/SRB2/releases/download/SRB2_assets/SRB2-v2122-x86-assets.7z
- ASSET_ARCHIVE_X64_PATH: https://github.com/mazmazz/SRB2/releases/download/SRB2_assets/SRB2-v2122-x64-assets.7z
- ASSET_ARCHIVE_OPTIONAL_PATH: https://github.com/mazmazz/SRB2/releases/download/SRB2_assets/SRB2-v2122-optional-assets.7z
+ ASSET_ARCHIVE_PATH: https://github.com/mazmazz/SRB2/releases/download/SRB2_assets_220/SRB2-v220-assets.7z
+ ASSET_ARCHIVE_PATCH_PATH: https://github.com/mazmazz/SRB2/releases/download/SRB2_assets_220/SRB2-v220-patch-assets.7z
+ ASSET_ARCHIVE_X86_PATH: https://github.com/mazmazz/SRB2/releases/download/SRB2_assets_220/SRB2-v220-x86-assets.7z
+ ASSET_ARCHIVE_X64_PATH: https://github.com/mazmazz/SRB2/releases/download/SRB2_assets_220/SRB2-v220-x64-assets.7z
+ ASSET_ARCHIVE_OPTIONAL_PATH: https://github.com/mazmazz/SRB2/releases/download/SRB2_assets_220/SRB2-v220-optional-assets.7z
  # This is overridden to 1 for release tag builds
  ASSET_FILES_OPTIONAL_GET: 0
  # For patches, also include the X86/X64 DLLs.
diff --git a/assets/CMakeLists.txt b/assets/CMakeLists.txt
index 8bc20e83359f9204c0006171e5ca832b8aad6014..0636c1e599e4ca793fa0e253b23e7ec76ae8c802 100644
--- a/assets/CMakeLists.txt
+++ b/assets/CMakeLists.txt
@@ -12,6 +12,10 @@ ENDFUNCTION(PREPEND)
 set(SRB2_ASSET_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/installer"
 	CACHE STRING "Path to directory that contains all asset files for the installer.")
 
+####################
+# POST-V2.2 NOTE: Do not forget to add patch.pk3 to the end of this list!
+####################
+
 set(SRB2_ASSET_HASHED
 "srb2.pk3;\
 player.dta;\
@@ -22,7 +26,8 @@ zones.pk3"
 set(SRB2_ASSET_DOCS
 "README.txt;\
 LICENSE.txt;\
-LICENSE-3RD-PARTY.txt"
+LICENSE-3RD-PARTY.txt;\
+README-SDL.txt"
 	CACHE STRING "Documentation filenames. In OS X, these are packaged separately from other assets. No spaces between entries!"
 )
 
diff --git a/debian-template/control b/debian-template/control
index e1348d704e3187dfc0e70a7165d467a2c6354492..74d11ae9023215f91120d549078042bd5bdfade0 100644
--- a/debian-template/control
+++ b/debian-template/control
@@ -10,6 +10,7 @@ Build-Depends: debhelper (>= 7.0.50~),
  libpng-dev | libpng16-dev | libpng12-dev (>= 1.2.7),
  zlib1g-dev,
  libgme-dev,
+ libopenmpt-dev,
  libglu1-dev | libglu-dev,
  libosmesa6-dev | libgl-dev,
  nasm [i386]
@@ -24,6 +25,7 @@ Depends: ${SHLIBS_DEPENDS}, ${MISC_DEPENDS},
  libsdl2-mixer-2.0-0,
  zlib1g,
  libgme0,
+ libopenmpt | libopenmpt0,
  libpng | libpng16-16 | libpng12-0
 Description: A cross-platform 3D Sonic fangame
  Sonic Robo Blast 2 is a 3D open-source Sonic the Hedgehog
diff --git a/deployer/travis/deployer_defaults.sh b/deployer/travis/deployer_defaults.sh
index bccb7409ac2750564d3a75433e12ff747ad21ae7..fe7ba6993fa66ea1fce68f156493a3d45dfd0acc 100644
--- a/deployer/travis/deployer_defaults.sh
+++ b/deployer/travis/deployer_defaults.sh
@@ -27,10 +27,10 @@
 : ${_DPL_PACKAGE_ASSET}         # Build asset installation package. Linux only.
 
 # Asset File Parameters
-: ${ASSET_ARCHIVE_PATH:=https://github.com/mazmazz/SRB2/releases/download/SRB2_assets/SRB2-v2122-assets.7z}
-: ${ASSET_ARCHIVE_OPTIONAL_PATH:=https://github.com/mazmazz/SRB2/releases/download/SRB2_assets/SRB2-v2122-optional-assets.7z}
-: ${ASSET_FILES_HASHED:=srb2.srb zones.dta player.dta rings.dta patch.dta}
-: ${ASSET_FILES_DOCS:=README.txt LICENSE.txt LICENSE-3RD-PARTY.txt}
+: ${ASSET_ARCHIVE_PATH:=https://github.com/mazmazz/SRB2/releases/download/SRB2_assets_220/SRB2-v220-assets.7z}
+: ${ASSET_ARCHIVE_OPTIONAL_PATH:=https://github.com/mazmazz/SRB2/releases/download/SRB2_assets_220/SRB2-v220-optional-assets.7z}
+: ${ASSET_FILES_HASHED:=srb2.pk3 zones.pk3 player.dta} # POST v2.2 NOTE: Don't forget to add patch.pk3!
+: ${ASSET_FILES_DOCS:=README.txt LICENSE.txt LICENSE-3RD-PARTY.txt README-SDL.txt}
 : ${ASSET_FILES_OPTIONAL_GET:=0}
 
 # FTP Parameters
@@ -51,7 +51,7 @@
 
 # Package Parameters
 : ${PACKAGE_NAME:=srb2}
-: ${PACKAGE_VERSION:=2.1.23}
+: ${PACKAGE_VERSION:=2.2.0}
 : ${PACKAGE_SUBVERSION}         # Highly recommended to set this to reflect the distro series target (e.g., ~18.04bionic)
 : ${PACKAGE_REVISION}           # Defaults to UTC timestamp
 : ${PACKAGE_INSTALL_PATH:=/usr/games/SRB2}
@@ -62,12 +62,12 @@
 : ${PACKAGE_GROUP_NAME_EMAIL:=Sonic Team Junior <stjr@srb2.org>}
 : ${PACKAGE_WEBSITE:=<http://www.srb2.org>}
 
-: ${PACKAGE_ASSET_MINVERSION:=2.1.21}  # Number this the version BEFORE the actual required version, because we do a > check
-: ${PACKAGE_ASSET_MAXVERSION:=2.1.24}  # Number this the version AFTER the actual required version, because we do a < check
+: ${PACKAGE_ASSET_MINVERSION:=2.1.26}  # Number this the version BEFORE the actual required version, because we do a > check
+: ${PACKAGE_ASSET_MAXVERSION:=2.2.1}  # Number this the version AFTER the actual required version, because we do a < check
 
 : ${PROGRAM_NAME:=Sonic Robo Blast 2}
 : ${PROGRAM_VENDOR:=Sonic Team Junior}
-: ${PROGRAM_VERSION:=2.1.23}
+: ${PROGRAM_VERSION:=2.2.0}
 : ${PROGRAM_DESCRIPTION:=A free 3D Sonic the Hedgehog fangame closely inspired by the original Sonic games on the Sega Genesis.}
 : ${PROGRAM_FILENAME:=srb2}
 
diff --git a/src/d_clisrv.c b/src/d_clisrv.c
index 7b6c35eb6343b29d5c0a7a35bbd231716f65c766..6520a1aa1bf2da2bc8d5842108d3cc342cf37316 100644
--- a/src/d_clisrv.c
+++ b/src/d_clisrv.c
@@ -70,7 +70,7 @@
 boolean server = true; // true or false but !server == client
 #define client (!server)
 boolean nodownload = false;
-static boolean serverrunning = false;
+boolean serverrunning = false;
 INT32 serverplayer = 0;
 char motd[254], server_context[8]; // Message of the Day, Unique Context (even without Mumble support)
 
diff --git a/src/d_net.h b/src/d_net.h
index 8fd6c67b2960aec2f6a9592f082253d1ca141b5b..c6fe4a27f759ef05e0ebfd2f07c0386c529af36f 100644
--- a/src/d_net.h
+++ b/src/d_net.h
@@ -40,6 +40,8 @@ extern SINT8 nodetoplayer2[MAXNETNODES]; // Say the numplayer for this node if a
 extern UINT8 playerpernode[MAXNETNODES]; // Used specially for splitscreen
 extern boolean nodeingame[MAXNETNODES]; // Set false as nodes leave game
 
+extern boolean serverrunning;
+
 INT32 Net_GetFreeAcks(boolean urgent);
 void Net_AckTicker(void);
 
diff --git a/src/d_netcmd.c b/src/d_netcmd.c
index 00cb6e65eb901aecd0d42aeec4f6d8f91c27e78b..262d0d1bc4d592a2acdde70f7a1985fed3b1433f 100644
--- a/src/d_netcmd.c
+++ b/src/d_netcmd.c
@@ -823,6 +823,10 @@ void D_RegisterClientCommands(void)
 	CV_RegisterVar(&cv_autobrake);
 	CV_RegisterVar(&cv_autobrake2);
 
+	// Ported from kart
+	CV_RegisterVar(&cv_deadzone);
+	CV_RegisterVar(&cv_deadzone2);
+
 	// s_sound.c
 	CV_RegisterVar(&cv_soundvolume);
 	CV_RegisterVar(&cv_closedcaptioning);
diff --git a/src/g_game.c b/src/g_game.c
index 2a12dd298093c091338762fba50b59c711c8eb19..d2d27528c89238f7705cc54fbfcd4b2ce06f0c5a 100644
--- a/src/g_game.c
+++ b/src/g_game.c
@@ -275,6 +275,12 @@ static UINT8 *metalbuffer = NULL;
 static UINT8 *metal_p;
 static UINT16 metalversion;
 
+typedef struct joystickvector2_s
+{
+	INT32 xaxis;
+	INT32 yaxis;
+} joystickvector2_t;
+
 // extra data stuff (events registered this frame while recording)
 static struct {
 	UINT8 flags; // EZT flags
@@ -394,6 +400,11 @@ consvar_t cv_directionchar2 = {"directionchar2", "Movement", CV_SAVE|CV_CALL, di
 consvar_t cv_autobrake = {"autobrake", "On", CV_SAVE|CV_CALL, CV_OnOff, AutoBrake_OnChange, 0, NULL, NULL, 0, 0, NULL};
 consvar_t cv_autobrake2 = {"autobrake2", "On", CV_SAVE|CV_CALL, CV_OnOff, AutoBrake2_OnChange, 0, NULL, NULL, 0, 0, NULL};
 
+static CV_PossibleValue_t deadzone_cons_t[] = {{0, "MIN"}, {FRACUNIT, "MAX"}, {0, NULL}};
+consvar_t cv_deadzone = {"deadzone", "0.25", CV_FLOAT|CV_SAVE, deadzone_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_deadzone2 = {"deadzone2", "0.25", CV_FLOAT|CV_SAVE, deadzone_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
+
+
 typedef enum
 {
 	AXISNONE = 0,
@@ -401,7 +412,6 @@ typedef enum
 	AXISMOVE,
 	AXISLOOK,
 	AXISSTRAFE,
-	AXISDEAD, //Axises that don't want deadzones
 	AXISJUMP,
 	AXISSPIN,
 	AXISFIRE,
@@ -882,12 +892,6 @@ static INT32 JoyAxis(axis_input_e axissel)
 		retaxis = -JOYAXISRANGE;
 	if (retaxis > (+JOYAXISRANGE))
 		retaxis = +JOYAXISRANGE;
-	if (!Joystick.bGamepadStyle && axissel < AXISDEAD)
-	{
-		const INT32 jdeadzone = JOYAXISRANGE/4;
-		if (-jdeadzone < retaxis && retaxis < jdeadzone)
-			return 0;
-	}
 	if (flp) retaxis = -retaxis; //flip it around
 	return retaxis;
 }
@@ -955,16 +959,75 @@ static INT32 Joy2Axis(axis_input_e axissel)
 		retaxis = -JOYAXISRANGE;
 	if (retaxis > (+JOYAXISRANGE))
 		retaxis = +JOYAXISRANGE;
-	if (!Joystick2.bGamepadStyle && axissel < AXISDEAD)
-	{
-		const INT32 jdeadzone = JOYAXISRANGE/4;
-		if (-jdeadzone < retaxis && retaxis < jdeadzone)
-			return 0;
-	}
 	if (flp) retaxis = -retaxis; //flip it around
 	return retaxis;
 }
 
+// Take a magnitude of two axes, and adjust it to take out the deadzone
+// Will return a value between 0 and JOYAXISRANGE
+static INT32 G_BasicDeadZoneCalculation(INT32 magnitude, fixed_t deadZone)
+{
+	const INT32 jdeadzone = (JOYAXISRANGE * deadZone) / FRACUNIT;
+	INT32 deadzoneAppliedValue = 0;
+
+	if (jdeadzone > 0)
+	{
+		if (magnitude > jdeadzone)
+		{
+			INT32 adjustedMagnitude = abs(magnitude);
+			adjustedMagnitude = min(adjustedMagnitude, JOYAXISRANGE);
+
+			adjustedMagnitude -= jdeadzone;
+
+			deadzoneAppliedValue = (adjustedMagnitude * JOYAXISRANGE) / (JOYAXISRANGE - jdeadzone);
+		}
+	}
+
+	return deadzoneAppliedValue;
+}
+
+// Get the actual sensible radial value for a joystick axis when accounting for a deadzone
+static void G_HandleAxisDeadZone(UINT8 splitnum, joystickvector2_t *joystickvector)
+{
+	INT32 gamepadStyle = Joystick.bGamepadStyle;
+	fixed_t deadZone = cv_deadzone.value;
+
+	if (splitnum == 1)
+	{
+		gamepadStyle = Joystick2.bGamepadStyle;
+		deadZone = cv_deadzone2.value;
+	}
+
+	// When gamepadstyle is "true" the values are just -1, 0, or 1. This is done in the interface code.
+	if (!gamepadStyle)
+	{
+		// Get the total magnitude of the 2 axes
+		INT32 magnitude = (joystickvector->xaxis * joystickvector->xaxis) + (joystickvector->yaxis * joystickvector->yaxis);
+		INT32 normalisedXAxis;
+		INT32 normalisedYAxis;
+		INT32 normalisedMagnitude;
+		double dMagnitude = sqrt((double)magnitude);
+		magnitude = (INT32)dMagnitude;
+
+		// Get the normalised xy values from the magnitude
+		normalisedXAxis = (joystickvector->xaxis * magnitude) / JOYAXISRANGE;
+		normalisedYAxis = (joystickvector->yaxis * magnitude) / JOYAXISRANGE;
+
+		// Apply the deadzone to the magnitude to give a correct value between 0 and JOYAXISRANGE
+		normalisedMagnitude = G_BasicDeadZoneCalculation(magnitude, deadZone);
+
+		// Apply the deadzone to the xy axes
+		joystickvector->xaxis = (normalisedXAxis * normalisedMagnitude) / JOYAXISRANGE;
+		joystickvector->yaxis = (normalisedYAxis * normalisedMagnitude) / JOYAXISRANGE;
+
+		// Cap the values so they don't go above the correct maximum
+		joystickvector->xaxis = min(joystickvector->xaxis, JOYAXISRANGE);
+		joystickvector->xaxis = max(joystickvector->xaxis, -JOYAXISRANGE);
+		joystickvector->yaxis = min(joystickvector->yaxis, JOYAXISRANGE);
+		joystickvector->yaxis = max(joystickvector->yaxis, -JOYAXISRANGE);
+	}
+}
+
 
 //
 // G_BuildTiccmd
@@ -985,12 +1048,13 @@ void G_BuildTiccmd(ticcmd_t *cmd, INT32 realtics)
 {
 	boolean forcestrafe = false;
 	boolean forcefullinput = false;
-	INT32 tspeed, forward, side, axis, altaxis, i;
+	INT32 tspeed, forward, side, axis, strafeaxis, moveaxis, turnaxis, lookaxis, i;
 	const INT32 speed = 1;
 	// these ones used for multiple conditions
 	boolean turnleft, turnright, strafelkey, straferkey, movefkey, movebkey, mouseaiming, analogjoystickmove, gamepadjoystickmove, thisjoyaiming;
 	player_t *player = &players[consoleplayer];
 	camera_t *thiscam = &camera;
+	joystickvector2_t movejoystickvector, lookjoystickvector;
 
 	static INT32 turnheld; // for accelerative turning
 	static boolean keyboard_look; // true if lookup/down using keyboard
@@ -1030,11 +1094,16 @@ void G_BuildTiccmd(ticcmd_t *cmd, INT32 realtics)
 		localaiming = 0;
 	joyaiming = thisjoyaiming;
 
-	axis = JoyAxis(AXISTURN);
-	if (gamepadjoystickmove && axis != 0)
+	turnaxis = JoyAxis(AXISTURN);
+	lookaxis = JoyAxis(AXISLOOK);
+	lookjoystickvector.xaxis = turnaxis;
+	lookjoystickvector.yaxis = lookaxis;
+	G_HandleAxisDeadZone(0, &lookjoystickvector);
+
+	if (gamepadjoystickmove && lookjoystickvector.xaxis != 0)
 	{
-		turnright = turnright || (axis > 0);
-		turnleft = turnleft || (axis < 0);
+		turnright = turnright || (lookjoystickvector.xaxis > 0);
+		turnleft = turnleft || (lookjoystickvector.xaxis < 0);
 	}
 	forward = side = 0;
 
@@ -1074,10 +1143,10 @@ void G_BuildTiccmd(ticcmd_t *cmd, INT32 realtics)
 		if (turnleft)
 			side -= sidemove[speed];
 
-		if (analogjoystickmove && axis != 0)
+		if (analogjoystickmove && lookjoystickvector.xaxis != 0)
 		{
 			// JOYAXISRANGE is supposed to be 1023 (divide by 1024)
-			side += ((axis * sidemove[1]) >> 10);
+			side += ((lookjoystickvector.xaxis * sidemove[1]) >> 10);
 		}
 	}
 	else if (cv_analog.value) // Analog
@@ -1094,41 +1163,44 @@ void G_BuildTiccmd(ticcmd_t *cmd, INT32 realtics)
 		else if (turnleft)
 			cmd->angleturn = (INT16)(cmd->angleturn + angleturn[tspeed]);
 
-		if (analogjoystickmove && axis != 0)
+		if (analogjoystickmove && lookjoystickvector.xaxis != 0)
 		{
 			// JOYAXISRANGE should be 1023 (divide by 1024)
-			cmd->angleturn = (INT16)(cmd->angleturn - ((axis * angleturn[1]) >> 10)); // ANALOG!
+			cmd->angleturn = (INT16)(cmd->angleturn - ((lookjoystickvector.xaxis * angleturn[1]) >> 10)); // ANALOG!
 		}
 	}
 
-	axis = JoyAxis(AXISSTRAFE);
-	if (gamepadjoystickmove && axis != 0)
+	strafeaxis = JoyAxis(AXISSTRAFE);
+	moveaxis = JoyAxis(AXISMOVE);
+	movejoystickvector.xaxis = strafeaxis;
+	movejoystickvector.yaxis = moveaxis;
+	G_HandleAxisDeadZone(0, &movejoystickvector);
+
+	if (gamepadjoystickmove && movejoystickvector.xaxis != 0)
 	{
-		if (axis < 0)
+		if (movejoystickvector.xaxis > 0)
 			side += sidemove[speed];
-		else if (axis > 0)
+		else if (movejoystickvector.xaxis < 0)
 			side -= sidemove[speed];
 	}
-	else if (analogjoystickmove && axis != 0)
+	else if (analogjoystickmove && movejoystickvector.xaxis != 0)
 	{
 		// JOYAXISRANGE is supposed to be 1023 (divide by 1024)
-		side += ((axis * sidemove[1]) >> 10);
+		side += ((movejoystickvector.xaxis * sidemove[1]) >> 10);
 	}
 
 	// forward with key or button
-	axis = JoyAxis(AXISMOVE);
-	altaxis = JoyAxis(AXISLOOK);
-	if (movefkey || (gamepadjoystickmove && axis < 0)
+	if (movefkey || (gamepadjoystickmove && movejoystickvector.yaxis < 0)
 		|| ((player->powers[pw_carry] == CR_NIGHTSMODE)
-			&& (PLAYER1INPUTDOWN(gc_lookup) || (gamepadjoystickmove && altaxis < 0))))
+			&& (PLAYER1INPUTDOWN(gc_lookup) || (gamepadjoystickmove && lookjoystickvector.yaxis > 0))))
 		forward = forwardmove[speed];
-	if (movebkey || (gamepadjoystickmove && axis > 0)
+	if (movebkey || (gamepadjoystickmove && movejoystickvector.yaxis > 0)
 		|| ((player->powers[pw_carry] == CR_NIGHTSMODE)
-			&& (PLAYER1INPUTDOWN(gc_lookdown) || (gamepadjoystickmove && altaxis > 0))))
+			&& (PLAYER1INPUTDOWN(gc_lookdown) || (gamepadjoystickmove && lookjoystickvector.yaxis < 0))))
 		forward -= forwardmove[speed];
 
-	if (analogjoystickmove && axis != 0)
-		forward -= ((axis * forwardmove[1]) >> 10); // ANALOG!
+	if (analogjoystickmove && movejoystickvector.yaxis != 0)
+		forward -= ((movejoystickvector.yaxis * forwardmove[1]) >> 10); // ANALOG!
 
 	// some people strafe left & right with mouse buttons
 	// those people are weird
@@ -1211,9 +1283,8 @@ void G_BuildTiccmd(ticcmd_t *cmd, INT32 realtics)
 			localaiming += (mlooky<<19)*player_invert*screen_invert;
 		}
 
-		axis = JoyAxis(AXISLOOK);
-		if (analogjoystickmove && joyaiming && axis != 0 && cv_lookaxis.value != 0)
-			localaiming += (axis<<16) * screen_invert;
+		if (analogjoystickmove && joyaiming && lookjoystickvector.yaxis != 0 && cv_lookaxis.value != 0)
+			localaiming += (lookjoystickvector.yaxis<<16) * screen_invert;
 
 		// spring back if not using keyboard neither mouselookin'
 		if (!keyboard_look && cv_lookaxis.value == 0 && !joyaiming && !mouseaiming)
@@ -1221,12 +1292,12 @@ void G_BuildTiccmd(ticcmd_t *cmd, INT32 realtics)
 
 		if (!(player->powers[pw_carry] == CR_NIGHTSMODE))
 		{
-			if (PLAYER1INPUTDOWN(gc_lookup) || (gamepadjoystickmove && axis < 0))
+			if (PLAYER1INPUTDOWN(gc_lookup) || (gamepadjoystickmove && lookjoystickvector.yaxis > 0))
 			{
 				localaiming += KB_LOOKSPEED * screen_invert;
 				keyboard_look = true;
 			}
-			else if (PLAYER1INPUTDOWN(gc_lookdown) || (gamepadjoystickmove && axis > 0))
+			else if (PLAYER1INPUTDOWN(gc_lookdown) || (gamepadjoystickmove && lookjoystickvector.yaxis < 0))
 			{
 				localaiming -= KB_LOOKSPEED * screen_invert;
 				keyboard_look = true;
@@ -1316,12 +1387,13 @@ void G_BuildTiccmd2(ticcmd_t *cmd, INT32 realtics)
 {
 	boolean forcestrafe = false;
 	boolean forcefullinput = false;
-	INT32 tspeed, forward, side, axis, altaxis, i;
+	INT32 tspeed, forward, side, axis, strafeaxis, moveaxis, turnaxis, lookaxis, i;
 	const INT32 speed = 1;
 	// these ones used for multiple conditions
 	boolean turnleft, turnright, strafelkey, straferkey, movefkey, movebkey, mouseaiming, analogjoystickmove, gamepadjoystickmove, thisjoyaiming;
 	player_t *player = &players[secondarydisplayplayer];
 	camera_t *thiscam = (player->bot == 2 ? &camera : &camera2);
+	joystickvector2_t movejoystickvector, lookjoystickvector;
 
 	static INT32 turnheld; // for accelerative turning
 	static boolean keyboard_look; // true if lookup/down using keyboard
@@ -1359,11 +1431,16 @@ void G_BuildTiccmd2(ticcmd_t *cmd, INT32 realtics)
 		localaiming2 = 0;
 	joyaiming = thisjoyaiming;
 
-	axis = Joy2Axis(AXISTURN);
-	if (gamepadjoystickmove && axis != 0)
+	turnaxis = Joy2Axis(AXISTURN);
+	lookaxis = Joy2Axis(AXISLOOK);
+	lookjoystickvector.xaxis = turnaxis;
+	lookjoystickvector.yaxis = lookaxis;
+	G_HandleAxisDeadZone(1, &lookjoystickvector);
+
+	if (gamepadjoystickmove && lookjoystickvector.xaxis != 0)
 	{
-		turnright = turnright || (axis > 0);
-		turnleft = turnleft || (axis < 0);
+		turnright = turnright || (lookjoystickvector.xaxis > 0);
+		turnleft = turnleft || (lookjoystickvector.xaxis < 0);
 	}
 	forward = side = 0;
 
@@ -1404,10 +1481,10 @@ void G_BuildTiccmd2(ticcmd_t *cmd, INT32 realtics)
 		if (turnleft)
 			side -= sidemove[speed];
 
-		if (analogjoystickmove && axis != 0)
+		if (analogjoystickmove && lookjoystickvector.xaxis != 0)
 		{
 			// JOYAXISRANGE is supposed to be 1023 (divide by 1024)
-			side += ((axis * sidemove[1]) >> 10);
+			side += ((lookjoystickvector.xaxis * sidemove[1]) >> 10);
 		}
 	}
 	else if (cv_analog2.value) // Analog
@@ -1424,41 +1501,44 @@ void G_BuildTiccmd2(ticcmd_t *cmd, INT32 realtics)
 		else if (turnleft)
 			cmd->angleturn = (INT16)(cmd->angleturn + angleturn[tspeed]);
 
-		if (analogjoystickmove && axis != 0)
+		if (analogjoystickmove && lookjoystickvector.xaxis != 0)
 		{
 			// JOYAXISRANGE should be 1023 (divide by 1024)
-			cmd->angleturn = (INT16)(cmd->angleturn - ((axis * angleturn[1]) >> 10)); // ANALOG!
+			cmd->angleturn = (INT16)(cmd->angleturn - ((lookjoystickvector.xaxis * angleturn[1]) >> 10)); // ANALOG!
 		}
 	}
 
-	axis = Joy2Axis(AXISSTRAFE);
-	if (gamepadjoystickmove && axis != 0)
+	strafeaxis = Joy2Axis(AXISSTRAFE);
+	moveaxis = Joy2Axis(AXISMOVE);
+	movejoystickvector.xaxis = strafeaxis;
+	movejoystickvector.yaxis = moveaxis;
+	G_HandleAxisDeadZone(1, &movejoystickvector);
+
+	if (gamepadjoystickmove && movejoystickvector.xaxis != 0)
 	{
-		if (axis < 0)
+		if (movejoystickvector.xaxis > 0)
 			side += sidemove[speed];
-		else if (axis > 0)
+		else if (movejoystickvector.xaxis < 0)
 			side -= sidemove[speed];
 	}
-	else if (analogjoystickmove && axis != 0)
+	else if (analogjoystickmove && movejoystickvector.xaxis != 0)
 	{
 		// JOYAXISRANGE is supposed to be 1023 (divide by 1024)
-		side += ((axis * sidemove[1]) >> 10);
+		side += ((movejoystickvector.xaxis * sidemove[1]) >> 10);
 	}
 
 	// forward with key or button
-	axis = Joy2Axis(AXISMOVE);
-	altaxis = Joy2Axis(AXISLOOK);
-	if (movefkey || (gamepadjoystickmove && axis < 0)
+	if (movefkey || (gamepadjoystickmove && movejoystickvector.yaxis < 0)
 		|| ((player->powers[pw_carry] == CR_NIGHTSMODE)
-			&& (PLAYER2INPUTDOWN(gc_lookup) || (gamepadjoystickmove && altaxis < 0))))
+			&& (PLAYER2INPUTDOWN(gc_lookup) || (gamepadjoystickmove && lookjoystickvector.yaxis > 0))))
 		forward = forwardmove[speed];
-	if (movebkey || (gamepadjoystickmove && axis > 0)
+	if (movebkey || (gamepadjoystickmove && movejoystickvector.yaxis > 0)
 		|| ((player->powers[pw_carry] == CR_NIGHTSMODE)
-			&& (PLAYER2INPUTDOWN(gc_lookdown) || (gamepadjoystickmove && altaxis > 0))))
+			&& (PLAYER2INPUTDOWN(gc_lookdown) || (gamepadjoystickmove && lookjoystickvector.yaxis < 0))))
 		forward -= forwardmove[speed];
 
-	if (analogjoystickmove && axis != 0)
-		forward -= ((axis * forwardmove[1]) >> 10); // ANALOG!
+	if (analogjoystickmove && movejoystickvector.yaxis != 0)
+		forward -= ((movejoystickvector.yaxis * forwardmove[1]) >> 10); // ANALOG!
 
 	// some people strafe left & right with mouse buttons
 	// those people are (still) weird
@@ -1538,9 +1618,8 @@ void G_BuildTiccmd2(ticcmd_t *cmd, INT32 realtics)
 			localaiming2 += (mlook2y<<19)*player_invert*screen_invert;
 		}
 
-		axis = Joy2Axis(AXISLOOK);
-		if (analogjoystickmove && joyaiming && axis != 0 && cv_lookaxis2.value != 0)
-			localaiming2 += (axis<<16) * screen_invert;
+		if (analogjoystickmove && joyaiming && lookjoystickvector.yaxis != 0 && cv_lookaxis2.value != 0)
+			localaiming2 += (lookjoystickvector.yaxis<<16) * screen_invert;
 
 		// spring back if not using keyboard neither mouselookin'
 		if (!keyboard_look && cv_lookaxis2.value == 0 && !joyaiming && !mouseaiming)
@@ -1548,12 +1627,12 @@ void G_BuildTiccmd2(ticcmd_t *cmd, INT32 realtics)
 
 		if (!(player->powers[pw_carry] == CR_NIGHTSMODE))
 		{
-			if (PLAYER2INPUTDOWN(gc_lookup) || (gamepadjoystickmove && axis < 0))
+			if (PLAYER2INPUTDOWN(gc_lookup) || (gamepadjoystickmove && lookjoystickvector.yaxis > 0))
 			{
 				localaiming2 += KB_LOOKSPEED * screen_invert;
 				keyboard_look = true;
 			}
-			else if (PLAYER2INPUTDOWN(gc_lookdown) || (gamepadjoystickmove && axis > 0))
+			else if (PLAYER2INPUTDOWN(gc_lookdown) || (gamepadjoystickmove && lookjoystickvector.yaxis < 0))
 			{
 				localaiming2 -= KB_LOOKSPEED * screen_invert;
 				keyboard_look = true;
diff --git a/src/g_game.h b/src/g_game.h
index e7f4a4677c2a95c19edde0c76a15471a495e6081..c19faebe4721bd4ea2ad25177e350c0f61af300b 100644
--- a/src/g_game.h
+++ b/src/g_game.h
@@ -72,6 +72,7 @@ extern consvar_t cv_useranalog, cv_useranalog2;
 extern consvar_t cv_analog, cv_analog2;
 extern consvar_t cv_directionchar, cv_directionchar2;
 extern consvar_t cv_autobrake, cv_autobrake2;
+extern consvar_t cv_deadzone, cv_deadzone2;
 extern consvar_t cv_sideaxis,cv_turnaxis,cv_moveaxis,cv_lookaxis,cv_jumpaxis,cv_spinaxis,cv_fireaxis,cv_firenaxis;
 extern consvar_t cv_sideaxis2,cv_turnaxis2,cv_moveaxis2,cv_lookaxis2,cv_jumpaxis2,cv_spinaxis2,cv_fireaxis2,cv_firenaxis2;
 extern consvar_t cv_ghost_bestscore, cv_ghost_besttime, cv_ghost_bestrings, cv_ghost_last, cv_ghost_guest;
diff --git a/src/i_tcp.c b/src/i_tcp.c
index 2dd9f0591f73c1f947449f6594bb72f615546160..502eb673243d49ff1cd6bc029b19dc27777493d7 100644
--- a/src/i_tcp.c
+++ b/src/i_tcp.c
@@ -209,7 +209,8 @@ static size_t numbans = 0;
 static boolean SOCK_bannednode[MAXNETNODES+1]; /// \note do we really need the +1?
 static boolean init_tcp_driver = false;
 
-static char port_name[8] = DEFAULTPORT;
+static const char *serverport_name = DEFAULTPORT;
+static const char *clientport_name;/* any port */
 
 #ifndef NONET
 
@@ -887,6 +888,7 @@ static boolean UDP_Socket(void)
 #ifdef HAVE_IPV6
 	const INT32 b_ipv6 = M_CheckParm("-ipv6");
 #endif
+	const char *serv;
 
 
 	for (s = 0; s < mysocketses; s++)
@@ -902,11 +904,16 @@ static boolean UDP_Socket(void)
 	hints.ai_socktype = SOCK_DGRAM;
 	hints.ai_protocol = IPPROTO_UDP;
 
+	if (serverrunning)
+		serv = serverport_name;
+	else
+		serv = clientport_name;
+
 	if (M_CheckParm("-bindaddr"))
 	{
 		while (M_IsNextParm())
 		{
-			gaie = I_getaddrinfo(M_GetNextParm(), port_name, &hints, &ai);
+			gaie = I_getaddrinfo(M_GetNextParm(), serv, &hints, &ai);
 			if (gaie == 0)
 			{
 				runp = ai;
@@ -927,7 +934,7 @@ static boolean UDP_Socket(void)
 	}
 	else
 	{
-		gaie = I_getaddrinfo("0.0.0.0", port_name, &hints, &ai);
+		gaie = I_getaddrinfo("0.0.0.0", serv, &hints, &ai);
 		if (gaie == 0)
 		{
 			runp = ai;
@@ -942,8 +949,8 @@ static boolean UDP_Socket(void)
 #ifdef HAVE_MINIUPNPC
 					if (UPNP_support)
 					{
-						I_UPnP_rem(port_name, "UDP");
-						I_UPnP_add(NULL, port_name, "UDP");
+						I_UPnP_rem(serverport_name, "UDP");
+						I_UPnP_add(NULL, serverport_name, "UDP");
 					}
 #endif
 				}
@@ -960,7 +967,7 @@ static boolean UDP_Socket(void)
 		{
 			while (M_IsNextParm())
 			{
-				gaie = I_getaddrinfo(M_GetNextParm(), port_name, &hints, &ai);
+				gaie = I_getaddrinfo(M_GetNextParm(), serv, &hints, &ai);
 				if (gaie == 0)
 				{
 					runp = ai;
@@ -981,7 +988,7 @@ static boolean UDP_Socket(void)
 		}
 		else
 		{
-			gaie = I_getaddrinfo("::", port_name, &hints, &ai);
+			gaie = I_getaddrinfo("::", serv, &hints, &ai);
 			if (gaie == 0)
 			{
 				runp = ai;
@@ -1421,15 +1428,19 @@ boolean I_InitTcpNetwork(void)
 	if (!I_InitTcpDriver())
 		return false;
 
-	if (M_CheckParm("-port"))
+	if (M_CheckParm("-port") || M_CheckParm("-serverport"))
 	// Combined -udpport and -clientport into -port
 	// As it was really redundant having two seperate parms that does the same thing
+	/* Sorry Steel, I'm adding these back. But -udpport is a stupid name. */
 	{
-		if (M_IsNextParm())
-			strcpy(port_name, M_GetNextParm());
-		else
-			strcpy(port_name, "0");
+		/*
+		If it's NULL, that's okay! Because then
+		we'll get a random port from getaddrinfo.
+		*/
+		serverport_name = M_GetNextParm();
 	}
+	if (M_CheckParm("-clientport"))
+		clientport_name = M_GetNextParm();
 
 	// parse network game options,
 	if (M_CheckParm("-server") || dedicated)
diff --git a/src/m_menu.c b/src/m_menu.c
index 23dc10701c0cb9ffd451eb392619b7d7128770f3..c5ede970f2b8eaa2e92d85d3c372f0add4ddded4 100644
--- a/src/m_menu.c
+++ b/src/m_menu.c
@@ -1118,6 +1118,8 @@ static menuitem_t OP_Joystick1Menu[] =
 
 	{IT_STRING | IT_CVAR, NULL, "First-Person Vert-Look", &cv_alwaysfreelook, 120},
 	{IT_STRING | IT_CVAR, NULL, "Third-Person Vert-Look", &cv_chasefreelook,  130},
+	{IT_STRING | IT_CVAR | IT_CV_FLOATSLIDER,
+	                      NULL, "Deadzone",               &cv_deadzone,       140 },
 };
 
 static menuitem_t OP_Joystick2Menu[] =
@@ -1134,6 +1136,8 @@ static menuitem_t OP_Joystick2Menu[] =
 
 	{IT_STRING | IT_CVAR, NULL, "First-Person Vert-Look", &cv_alwaysfreelook2,120},
 	{IT_STRING | IT_CVAR, NULL, "Third-Person Vert-Look", &cv_chasefreelook2, 130},
+	{IT_STRING | IT_CVAR | IT_CV_FLOATSLIDER,
+	                      NULL, "Deadzone",               &cv_deadzone2,      140 },
 };
 
 static menuitem_t OP_JoystickSetMenu[1+MAX_JOYSTICKS];
@@ -3009,7 +3013,7 @@ boolean M_Responder(event_t *ev)
 		}
 		else if (ev->type == ev_joystick  && ev->data1 == 0 && joywait < I_GetTime())
 		{
-			const INT32 jdeadzone = JOYAXISRANGE/4;
+			const INT32 jdeadzone = (JOYAXISRANGE * cv_deadzone.value) / FRACUNIT;
 			if (ev->data3 != INT32_MAX)
 			{
 				if (Joystick.bGamepadStyle || abs(ev->data3) > jdeadzone)
diff --git a/src/p_enemy.c b/src/p_enemy.c
index 5361f453b574620829eaf3f98ad353d9cf085f9a..6b60a697f4e8b6137cd589abfdc6661868a897d1 100644
--- a/src/p_enemy.c
+++ b/src/p_enemy.c
@@ -13614,12 +13614,12 @@ static boolean PIT_DustDevilLaunch(mobj_t *thing)
 		}
 		else
 		{ //Player on the top of the tornado.
+			P_ResetPlayer(player);
 			thing->z = dustdevil->z + dustdevil->height;
 			thrust = 20 * FRACUNIT;
 			player->powers[pw_nocontrol] = 0;
 			S_StartSound(thing, sfx_wdjump);
 			P_SetPlayerMobjState(thing, S_PLAY_FALL);
-			player->pflags &= ~PF_JUMPED;
 		}
 
 		thing->momz = thrust;
diff --git a/src/p_saveg.c b/src/p_saveg.c
index 89447db80ae08d844d3259c0717f4916f7a380c5..c876713e4cca1c0856ea1b79ded396b189cac514 100644
--- a/src/p_saveg.c
+++ b/src/p_saveg.c
@@ -781,9 +781,10 @@ static void P_NetArchiveWorld(void)
 	UINT8 *put;
 
 	// reload the map just to see difference
-	mapsector_t *ms;
-	mapsidedef_t *msd;
-	maplinedef_t *mld;
+	virtres_t* virt = vres_GetMap(lastloadedmaplumpnum);
+	mapsector_t  *ms  = (mapsector_t*) vres_Find(virt, "SECTORS")->data;
+	mapsidedef_t *msd = (mapsidedef_t*) vres_Find(virt, "SIDEDEFS")->data;
+	maplinedef_t *mld = (maplinedef_t*) vres_Find(virt, "LINEDEFS")->data;
 	const sector_t *ss = sectors;
 	UINT8 diff, diff2, diff3;
 
@@ -793,26 +794,6 @@ static void P_NetArchiveWorld(void)
 	WRITEUINT32(save_p, ARCHIVEBLOCK_WORLD);
 	put = save_p;
 
-	if (W_IsLumpWad(lastloadedmaplumpnum)) // welp it's a map wad in a pk3
-	{ // HACK: Open wad file rather quickly so we can get the data from the relevant lumps
-		UINT8 *wadData = W_CacheLumpNum(lastloadedmaplumpnum, PU_STATIC);
-		filelump_t *fileinfo = (filelump_t *)(wadData + ((wadinfo_t *)wadData)->infotableofs);
-#define retrieve_mapdata(d, f)\
-		d = Z_Malloc((f)->size, PU_CACHE, NULL); \
-		M_Memcpy(d, wadData + (f)->filepos, (f)->size)
-		retrieve_mapdata(ms, fileinfo + ML_SECTORS);
-		retrieve_mapdata(mld, fileinfo + ML_LINEDEFS);
-		retrieve_mapdata(msd, fileinfo + ML_SIDEDEFS);
-#undef retrieve_mapdata
-		Z_Free(wadData); // we're done with this now
-	}
-	else // phew it's just a WAD
-	{
-			ms = W_CacheLumpNum(lastloadedmaplumpnum+ML_SECTORS, PU_CACHE);
-			mld = W_CacheLumpNum(lastloadedmaplumpnum+ML_LINEDEFS, PU_CACHE);
-			msd = W_CacheLumpNum(lastloadedmaplumpnum+ML_SIDEDEFS, PU_CACHE);
-	}
-
 	for (i = 0; i < numsectors; i++, ss++, ms++)
 	{
 		diff = diff2 = diff3 = 0;
@@ -1037,6 +1018,7 @@ static void P_NetArchiveWorld(void)
 	WRITEUINT16(put, 0xffff);
 	R_ClearTextureNumCache(false);
 
+	vres_Free(virt);
 	save_p = put;
 }
 
diff --git a/src/p_setup.c b/src/p_setup.c
index 9528307d6f81e9bd52a45f8a1927cad8f0185e98..bc736588e034ae2e909b3c53ac3a5af2ccb1fae3 100644
--- a/src/p_setup.c
+++ b/src/p_setup.c
@@ -367,27 +367,12 @@ UINT32 P_GetScoreForGrade(INT16 map, UINT8 mare, UINT8 grade)
 	return mapheaderinfo[map-1]->grades[mare].grade[grade-1];
 }
 
-/** Loads the vertexes for a level.
-  *
-  * \param lump VERTEXES lump number.
-  * \sa ML_VERTEXES
-  */
-
-static inline void P_LoadRawVertexes(UINT8 *data, size_t i)
+// Loads the vertexes for a level.
+static inline void P_LoadRawVertexes(UINT8 *data)
 {
-	mapvertex_t *ml;
-	vertex_t *li;
-
-	numvertexes = i / sizeof (mapvertex_t);
-
-	if (numvertexes <= 0)
-		I_Error("Level has no vertices"); // instead of crashing
-
-	// Allocate zone memory for buffer.
-	vertexes = Z_Calloc(numvertexes * sizeof (*vertexes), PU_LEVEL, NULL);
-
-	ml = (mapvertex_t *)data;
-	li = vertexes;
+	mapvertex_t *ml = (mapvertex_t *)data;
+	vertex_t *li = vertexes;
+	size_t i;
 
 	// Copy and convert vertex coordinates, internal representation as fixed.
 	for (i = 0; i < numvertexes; i++, li++, ml++)
@@ -397,13 +382,6 @@ static inline void P_LoadRawVertexes(UINT8 *data, size_t i)
 	}
 }
 
-static inline void P_LoadVertexes(lumpnum_t lumpnum)
-{
-	UINT8 *data = W_CacheLumpNum(lumpnum, PU_STATIC);
-	P_LoadRawVertexes(data, W_LumpLength(lumpnum));
-	Z_Free(data);
-}
-
 /** Computes the length of a seg in fracunits.
   *
   * \param seg Seg to compute length for.
@@ -435,25 +413,15 @@ static inline float P_SegLengthFloat(seg_t *seg)
 }
 #endif
 
-/** Loads the SEGS resource from a level.
-  *
-  * \param lump Lump number of the SEGS resource.
-  * \sa ::ML_SEGS
-  */
-static void P_LoadRawSegs(UINT8 *data, size_t i)
+// Loads the SEGS resource from a level.
+static void P_LoadRawSegs(UINT8 *data)
 {
 	INT32 linedef, side;
-	mapseg_t *ml;
-	seg_t *li;
+	mapseg_t *ml = (mapseg_t*)data;
+	seg_t *li = segs;
 	line_t *ldef;
+	size_t i;
 
-	numsegs = i / sizeof (mapseg_t);
-	if (numsegs <= 0)
-		I_Error("Level has no segs"); // instead of crashing
-	segs = Z_Calloc(numsegs * sizeof (*segs), PU_LEVEL, NULL);
-
-	ml = (mapseg_t *)data;
-	li = segs;
 	for (i = 0; i < numsegs; i++, li++, ml++)
 	{
 		li->v1 = &vertexes[SHORT(ml->v1)];
@@ -478,7 +446,7 @@ static void P_LoadRawSegs(UINT8 *data, size_t i)
 		li->side = side = SHORT(ml->side);
 		li->sidedef = &sides[ldef->sidenum[side]];
 		li->frontsector = sides[ldef->sidenum[side]].sector;
-		if (ldef-> flags & ML_TWOSIDED)
+		if (ldef->flags & ML_TWOSIDED)
 			li->backsector = sides[ldef->sidenum[side^1]].sector;
 		else
 			li->backsector = 0;
@@ -488,30 +456,12 @@ static void P_LoadRawSegs(UINT8 *data, size_t i)
 	}
 }
 
-static void P_LoadSegs(lumpnum_t lumpnum)
-{
-	UINT8 *data = W_CacheLumpNum(lumpnum, PU_STATIC);
-	P_LoadRawSegs(data, W_LumpLength(lumpnum));
-	Z_Free(data);
-}
-
-
-/** Loads the SSECTORS resource from a level.
-  *
-  * \param lump Lump number of the SSECTORS resource.
-  * \sa ::ML_SSECTORS
-  */
-static inline void P_LoadRawSubsectors(void *data, size_t i)
+// Loads the SSECTORS resource from a level.
+static inline void P_LoadRawSubsectors(void *data)
 {
-	mapsubsector_t *ms;
-	subsector_t *ss;
-
-	numsubsectors = i / sizeof (mapsubsector_t);
-	if (numsubsectors <= 0)
-		I_Error("Level has no subsectors (did you forget to run it through a nodesbuilder?)");
-	ss = subsectors = Z_Calloc(numsubsectors * sizeof (*subsectors), PU_LEVEL, NULL);
-
-	ms = (mapsubsector_t *)data;
+	mapsubsector_t *ms = (mapsubsector_t*)data;
+	subsector_t *ss = subsectors;
+	size_t i;
 
 	for (i = 0; i < numsubsectors; i++, ss++, ms++)
 	{
@@ -525,13 +475,6 @@ static inline void P_LoadRawSubsectors(void *data, size_t i)
 	}
 }
 
-static void P_LoadSubsectors(lumpnum_t lumpnum)
-{
-	UINT8 *data = W_CacheLumpNum(lumpnum, PU_STATIC);
-	P_LoadRawSubsectors(data, W_LumpLength(lumpnum));
-	Z_Free(data);
-}
-
 //
 // levelflats
 //
@@ -698,20 +641,12 @@ INT32 P_CheckLevelFlat(const char *flatname)
 }
 
 // Sets up the ingame sectors structures.
-// Lumpnum is the lumpnum of a SECTORS lump.
-static void P_LoadRawSectors(UINT8 *data, size_t i)
+static void P_LoadRawSectors(UINT8 *data)
 {
-	mapsector_t *ms;
-	sector_t *ss;
+	mapsector_t *ms = (mapsector_t *)data;
+	sector_t *ss = sectors;
 	levelflat_t *foundflats;
-
-	// We count how many sectors we got.
-	numsectors = i / sizeof (mapsector_t);
-	if (numsectors <= 0)
-		I_Error("Level has no sectors");
-
-	// Allocate as much memory as we need into the global sectors table.
-	sectors = Z_Calloc(numsectors*sizeof (*sectors), PU_LEVEL, NULL);
+	size_t i;
 
 	// Allocate a big chunk of memory as big as our MAXLEVELFLATS limit.
 	//Fab : FIXME: allocate for whatever number of flats - 512 different flats per level should be plenty
@@ -722,8 +657,6 @@ static void P_LoadRawSectors(UINT8 *data, size_t i)
 	numlevelflats = 0;
 
 	// For each counted sector, copy the sector raw data from our cache pointer ms, to the global table pointer ss.
-	ms = (mapsector_t *)data;
-	ss = sectors;
 	for (i = 0; i < numsectors; i++, ss++, ms++)
 	{
 		ss->floorheight = SHORT(ms->floorheight)<<FRACBITS;
@@ -805,29 +738,15 @@ static void P_LoadRawSectors(UINT8 *data, size_t i)
 	P_SetupLevelFlatAnims();
 }
 
-static void P_LoadSectors(lumpnum_t lumpnum)
-{
-	UINT8 *data = W_CacheLumpNum(lumpnum, PU_STATIC);
-	P_LoadRawSectors(data, W_LumpLength(lumpnum));
-	Z_Free(data);
-}
-
 //
 // P_LoadNodes
 //
-static void P_LoadRawNodes(UINT8 *data, size_t i)
+static void P_LoadRawNodes(UINT8 *data)
 {
 	UINT8 j, k;
-	mapnode_t *mn;
-	node_t *no;
-
-	numnodes = i / sizeof (mapnode_t);
-	if (numnodes <= 0)
-		I_Error("Level has no nodes");
-	nodes = Z_Calloc(numnodes * sizeof (*nodes), PU_LEVEL, NULL);
-
-	mn = (mapnode_t *)data;
-	no = nodes;
+	mapnode_t *mn = (mapnode_t*)data;
+	node_t *no = nodes;
+	size_t i;
 
 	for (i = 0; i < numnodes; i++, no++, mn++)
 	{
@@ -844,13 +763,6 @@ static void P_LoadRawNodes(UINT8 *data, size_t i)
 	}
 }
 
-static void P_LoadNodes(lumpnum_t lumpnum)
-{
-	UINT8 *data = W_CacheLumpNum(lumpnum, PU_STATIC);
-	P_LoadRawNodes(data, W_LumpLength(lumpnum));
-	Z_Free(data);
-}
-
 //
 // P_ReloadRings
 // Used by NiGHTS, clears all ring/wing/etc items and respawns them
@@ -1002,12 +914,10 @@ void P_ScanThings(INT16 mapnum, INT16 wadnum, INT16 lumpnum)
 }
 #endif
 
-static void P_PrepareRawThings(UINT8 *data, size_t i)
+static void P_PrepareRawThings(UINT8 *data)
 {
 	mapthing_t *mt;
-
-	nummapthings = i / (5 * sizeof (INT16));
-	mapthings = Z_Calloc(nummapthings * sizeof (*mapthings), PU_LEVEL, NULL);
+	size_t i;
 
 	for (i = 0, mt = mapthings; i < nummapthings; i++, mt++)
 	{
@@ -1028,13 +938,6 @@ static void P_PrepareRawThings(UINT8 *data, size_t i)
 	}
 }
 
-static void P_PrepareThings(lumpnum_t lumpnum)
-{
-	UINT8 *data = W_CacheLumpNum(lumpnum, PU_STATIC);
-	P_PrepareRawThings(data, W_LumpLength(lumpnum));
-	Z_Free(data);
-}
-
 static void P_SpawnEmeraldHunt(void)
 {
 	INT32 emer1, emer2, emer3;
@@ -1085,7 +988,7 @@ static void P_LoadThings(boolean loademblems)
 	size_t i;
 	mapthing_t *mt;
 
-	// Spawn axis points first so they are at the front of the list for fast searching.
+        // Spawn axis points first so they are at the front of the list for fast searching.
 	for (i = 0, mt = mapthings; i < nummapthings; i++, mt++)
 	{
 		switch (mt->type)
@@ -1183,33 +1086,46 @@ void P_WriteThings(lumpnum_t lumpnum)
 	CONS_Printf(M_GetText("newthings%d.lmp saved.\n"), gamemap);
 }
 
-static void P_LoadRawLineDefs(UINT8 *data, size_t i)
+static void P_LoadRawLineDefs(UINT8 *data)
 {
-	maplinedef_t *mld;
-	line_t *ld;
-	vertex_t *v1, *v2;
-
-	numlines = i / sizeof (maplinedef_t);
-	if (numlines <= 0)
-		I_Error("Level has no linedefs");
-	lines = Z_Calloc(numlines * sizeof (*lines), PU_LEVEL, NULL);
+	maplinedef_t *mld = (maplinedef_t *)data;
+	line_t *ld = lines;
+	size_t i;
 
-	mld = (maplinedef_t *)data;
-	ld = lines;
 	for (i = 0; i < numlines; i++, mld++, ld++)
 	{
 		ld->flags = SHORT(mld->flags);
 		ld->special = SHORT(mld->special);
 		ld->tag = SHORT(mld->tag);
-		v1 = ld->v1 = &vertexes[SHORT(mld->v1)];
-		v2 = ld->v2 = &vertexes[SHORT(mld->v2)];
-		ld->dx = v2->x - v1->x;
-		ld->dy = v2->y - v1->y;
+		ld->v1 = &vertexes[SHORT(mld->v1)];
+		ld->v2 = &vertexes[SHORT(mld->v2)];
+
+		ld->sidenum[0] = SHORT(mld->sidenum[0]);
+		ld->sidenum[1] = SHORT(mld->sidenum[1]);
+	}
+}
+
+static void P_SetupLines(void)
+{
+	line_t *ld = lines;
+	size_t i;
+
+	for (i = 0; i < numlines; i++, ld++)
+	{
+		vertex_t *v1 = ld->v1;
+		vertex_t *v2 = ld->v2;
 
 #ifdef WALLSPLATS
 		ld->splats = NULL;
 #endif
 
+#ifdef POLYOBJECTS
+		ld->polyobj = NULL;
+#endif
+
+		ld->dx = v2->x - v1->x;
+		ld->dy = v2->y - v1->y;
+
 		if (!ld->dx)
 			ld->slopetype = ST_VERTICAL;
 		else if (!ld->dy)
@@ -1241,62 +1157,46 @@ static void P_LoadRawLineDefs(UINT8 *data, size_t i)
 			ld->bbox[BOXTOP] = v1->y;
 		}
 
-		ld->sidenum[0] = SHORT(mld->sidenum[0]);
-		ld->sidenum[1] = SHORT(mld->sidenum[1]);
-
 		{
 			// cph 2006/09/30 - fix sidedef errors right away.
 			// cph 2002/07/20 - these errors are fatal if not fixed, so apply them
 			UINT8 j;
 
 			for (j=0; j < 2; j++)
-			{
 				if (ld->sidenum[j] != 0xffff && ld->sidenum[j] >= (UINT16)numsides)
 				{
 					ld->sidenum[j] = 0xffff;
 					CONS_Debug(DBG_SETUP, "P_LoadRawLineDefs: linedef %s has out-of-range sidedef number\n", sizeu1(numlines-i-1));
 				}
-			}
 		}
 
 		ld->frontsector = ld->backsector = NULL;
 		ld->validcount = 0;
 		ld->firsttag = ld->nexttag = -1;
 		ld->callcount = 0;
-		// killough 11/98: fix common wad errors (missing sidedefs):
 
+		// killough 11/98: fix common wad errors (missing sidedefs):
 		if (ld->sidenum[0] == 0xffff)
 		{
 			ld->sidenum[0] = 0;  // Substitute dummy sidedef for missing right side
 			// cph - print a warning about the bug
-			CONS_Debug(DBG_SETUP, "P_LoadRawLineDefs: linedef %s missing first sidedef\n", sizeu1(numlines-i-1));
+			CONS_Debug(DBG_SETUP, "Linedef %s missing first sidedef\n", sizeu1(numlines-i-1));
 		}
 
 		if ((ld->sidenum[1] == 0xffff) && (ld->flags & ML_TWOSIDED))
 		{
 			ld->flags &= ~ML_TWOSIDED;  // Clear 2s flag for missing left side
 			// cph - print a warning about the bug
-			CONS_Debug(DBG_SETUP, "P_LoadRawLineDefs: linedef %s has two-sided flag set, but no second sidedef\n", sizeu1(numlines-i-1));
+			CONS_Debug(DBG_SETUP, "Linedef %s has two-sided flag set, but no second sidedef\n", sizeu1(numlines-i-1));
 		}
 
 		if (ld->sidenum[0] != 0xffff && ld->special)
 			sides[ld->sidenum[0]].special = ld->special;
 		if (ld->sidenum[1] != 0xffff && ld->special)
 			sides[ld->sidenum[1]].special = ld->special;
-
-#ifdef POLYOBJECTS
-		ld->polyobj = NULL;
-#endif
 	}
 }
 
-static void P_LoadLineDefs(lumpnum_t lumpnum)
-{
-	UINT8 *data = W_CacheLumpNum(lumpnum, PU_STATIC);
-	P_LoadRawLineDefs(data, W_LumpLength(lumpnum));
-	Z_Free(data);
-}
-
 static void P_LoadLineDefs2(void)
 {
 	size_t i = numlines;
@@ -1399,22 +1299,6 @@ static void P_LoadLineDefs2(void)
 	}
 }
 
-
-
-static inline void P_LoadRawSideDefs(size_t i)
-{
-	numsides = i / sizeof (mapsidedef_t);
-	if (numsides <= 0)
-		I_Error("Level has no sidedefs");
-	sides = Z_Calloc(numsides * sizeof (*sides), PU_LEVEL, NULL);
-}
-
-static inline void P_LoadSideDefs(lumpnum_t lumpnum)
-{
-	P_LoadRawSideDefs(W_LumpLength(lumpnum));
-}
-
-
 static void P_LoadRawSideDefs2(void *data)
 {
 	UINT16 i;
@@ -1575,15 +1459,6 @@ static void P_LoadRawSideDefs2(void *data)
 	R_ClearTextureNumCache(true);
 }
 
-// Delay loading texture names until after loaded linedefs.
-static void P_LoadSideDefs2(lumpnum_t lumpnum)
-{
-	UINT8 *data = W_CacheLumpNum(lumpnum, PU_STATIC);
-	P_LoadRawSideDefs2(data);
-	Z_Free(data);
-}
-
-
 static boolean LineInBlock(fixed_t cx1, fixed_t cy1, fixed_t cx2, fixed_t cy2, fixed_t bx1, fixed_t by1)
 {
 	fixed_t bbox[4];
@@ -1863,84 +1738,11 @@ static void P_ReadBlockMapLump(INT16 *wadblockmaplump, size_t count)
 	}
 }
 
-//
-// P_LoadBlockMap
-//
-// Levels might not have a blockmap, so if one does not exist
-// this should return false.
-static boolean P_LoadBlockMap(lumpnum_t lumpnum)
-{
-#if 0
-	(void)lumpnum;
-	return false;
-#else
-	size_t count;
-	const char *lumpname = W_CheckNameForNum(lumpnum);
-
-	// Check if the lump exists, and if it's named "BLOCKMAP"
-	if (!lumpname || memcmp(lumpname, "BLOCKMAP", 8) != 0)
-	{
-		return false;
-	}
-
-	count = W_LumpLength(lumpnum);
-
-	if (!count || count >= 0x20000)
-		return false;
-
-	{
-		INT16 *wadblockmaplump = malloc(count); //INT16 *wadblockmaplump = W_CacheLumpNum (lump, PU_LEVEL);
-		if (!wadblockmaplump)
-			return false;
-		W_ReadLump(lumpnum, wadblockmaplump);
-		count /= 2;
-		P_ReadBlockMapLump(wadblockmaplump, count);
-		free(wadblockmaplump);
-	}
-
-	bmaporgx = blockmaplump[0]<<FRACBITS;
-	bmaporgy = blockmaplump[1]<<FRACBITS;
-	bmapwidth = blockmaplump[2];
-	bmapheight = blockmaplump[3];
-
-	// clear out mobj chains
-	count = sizeof (*blocklinks)* bmapwidth*bmapheight;
-	blocklinks = Z_Calloc(count, PU_LEVEL, NULL);
-	blockmap = blockmaplump+4;
-
-#ifdef POLYOBJECTS
-	// haleyjd 2/22/06: setup polyobject blockmap
-	count = sizeof(*polyblocklinks) * bmapwidth * bmapheight;
-	polyblocklinks = Z_Calloc(count, PU_LEVEL, NULL);
-#endif
-	return true;
-/* Original
-		blockmaplump = W_CacheLumpNum(lump, PU_LEVEL);
-		blockmap = blockmaplump+4;
-		count = W_LumpLength (lump)/2;
-
-		for (i = 0; i < count; i++)
-			blockmaplump[i] = SHORT(blockmaplump[i]);
-
-		bmaporgx = blockmaplump[0]<<FRACBITS;
-		bmaporgy = blockmaplump[1]<<FRACBITS;
-		bmapwidth = blockmaplump[2];
-		bmapheight = blockmaplump[3];
-	}
-
-	// clear out mobj chains
-	count = sizeof (*blocklinks)*bmapwidth*bmapheight;
-	blocklinks = Z_Calloc(count, PU_LEVEL, NULL);
-	return true;
-	*/
-#endif
-}
-
 // This needs to be a separate function
 // because making both the WAD and PK3 loading code use
 // the same functions is trickier than it looks for blockmap
 // -- Monster Iestyn 09/01/18
-static boolean P_LoadRawBlockMap(UINT8 *data, size_t count, const char *lumpname)
+static boolean P_LoadRawBlockMap(UINT8 *data, size_t count)
 {
 #if 0
 	(void)data;
@@ -1948,12 +1750,6 @@ static boolean P_LoadRawBlockMap(UINT8 *data, size_t count, const char *lumpname
 	(void)lumpname;
 	return false;
 #else
-	// Check if the lump is named "BLOCKMAP"
-	if (!lumpname || memcmp(lumpname, "BLOCKMAP", 8) != 0)
-	{
-		CONS_Printf("No blockmap lump found for pk3!\n");
-		return false;
-	}
 
 	if (!count || count >= 0x20000)
 		return false;
@@ -2079,56 +1875,139 @@ static void P_GroupLines(void)
 	}
 }
 
-//
-// P_LoadReject
-//
-// Detect if the REJECT lump is valid,
-// if not, rejectmatrix will be NULL
-static void P_LoadReject(lumpnum_t lumpnum)
+// PK3 version
+// -- Monster Iestyn 09/01/18
+static void P_LoadRawReject(UINT8 *data, size_t count)
 {
-	size_t count;
-	const char *lumpname = W_CheckNameForNum(lumpnum);
-
-	// Check if the lump exists, and if it's named "REJECT"
-	if (!lumpname || memcmp(lumpname, "REJECT\0\0", 8) != 0)
+	if (!count) // zero length, someone probably used ZDBSP
 	{
 		rejectmatrix = NULL;
-		CONS_Debug(DBG_SETUP, "P_LoadReject: No valid REJECT lump found\n");
-		return;
+		CONS_Debug(DBG_SETUP, "P_LoadRawReject: REJECT lump has size 0, will not be loaded\n");
 	}
-
-	count = W_LumpLength(lumpnum);
-
-	if (!count) // zero length, someone probably used ZDBSP
+	else
 	{
-		rejectmatrix = NULL;
-		CONS_Debug(DBG_SETUP, "P_LoadReject: REJECT lump has size 0, will not be loaded\n");
+		rejectmatrix = Z_Malloc(count, PU_LEVEL, NULL); // allocate memory for the reject matrix
+		M_Memcpy(rejectmatrix, data, count); // copy the data into it
 	}
+}
+
+static void P_LoadMapBSP(const virtres_t* virt)
+{
+	virtlump_t* virtssectors = vres_Find(virt, "SSECTORS");
+	virtlump_t* virtsegs     = vres_Find(virt, "SEGS");
+	virtlump_t* virtnodes    = vres_Find(virt, "NODES");
+
+	numsubsectors = virtssectors->size / sizeof(mapsubsector_t);
+	numnodes      = virtnodes->size    / sizeof(mapnode_t);
+	numsegs       = virtsegs->size     / sizeof(mapseg_t);
+
+	if (numsubsectors <= 0)
+		I_Error("Level has no subsectors (did you forget to run it through a nodesbuilder?)");
+	if (numnodes <= 0)
+		I_Error("Level has no nodes");
+	if (numsegs <= 0)
+		I_Error("Level has no segs");
+
+	subsectors = Z_Calloc(numsubsectors * sizeof(*subsectors), PU_LEVEL, NULL);
+	nodes      = Z_Calloc(numnodes * sizeof(*nodes), PU_LEVEL, NULL);
+	segs       = Z_Calloc(numsegs * sizeof(*segs), PU_LEVEL, NULL);
+
+	// Nodes
+	P_LoadRawSubsectors(virtssectors->data);
+	P_LoadRawNodes(virtnodes->data);
+	P_LoadRawSegs(virtsegs->data);
+}
+
+static void P_LoadMapLUT(const virtres_t* virt)
+{
+	virtlump_t* virtblockmap = vres_Find(virt, "BLOCKMAP");
+	virtlump_t* virtreject   = vres_Find(virt, "REJECT");
+
+	// Lookup tables
+	if (virtreject)
+		P_LoadRawReject(virtreject->data, virtreject->size);
 	else
-		rejectmatrix = W_CacheLumpNum(lumpnum, PU_LEVEL);
+		rejectmatrix = NULL;
+
+	if (!(virtblockmap && P_LoadRawBlockMap(virtblockmap->data, virtblockmap->size)))
+		P_CreateBlockMap();
 }
 
-// PK3 version
-// -- Monster Iestyn 09/01/18
-static void P_LoadRawReject(UINT8 *data, size_t count, const char *lumpname)
+static void P_LoadMapData(const virtres_t* virt)
 {
-	// Check if the lump is named "REJECT"
-	if (!lumpname || memcmp(lumpname, "REJECT\0\0", 8) != 0)
+	virtlump_t* virtvertexes = NULL, * virtsectors = NULL, * virtsidedefs = NULL, * virtlinedefs = NULL, * virtthings = NULL;
+#ifdef UDMF
+	virtlump_t* textmap = vres_Find(virt, "TEXTMAP");
+
+	// Count map data.
+	if (textmap)
 	{
-		rejectmatrix = NULL;
-		CONS_Debug(DBG_SETUP, "P_LoadRawReject: No valid REJECT lump found\n");
-		return;
+		nummapthings = 0;
+		numlines = 0;
+		numsides = 0;
+		numvertexes = 0;
+		numsectors = 0;
+
+		// Count how many entries for each type we got in textmap.
+		//TextmapCount(vtextmap->data, vtextmap->size);
+	}
+	else
+#endif
+	{
+		virtthings   = vres_Find(virt, "THINGS");
+		virtvertexes = vres_Find(virt, "VERTEXES");
+		virtsectors  = vres_Find(virt, "SECTORS");
+		virtsidedefs = vres_Find(virt, "SIDEDEFS");
+		virtlinedefs = vres_Find(virt, "LINEDEFS");
+
+		if (!virtthings)
+			I_Error("THINGS lump not found");
+		if (!virtvertexes)
+			I_Error("VERTEXES lump not found");
+		if (!virtsectors)
+			I_Error("SECTORS lump not found");
+		if (!virtsidedefs)
+			I_Error("SIDEDEFS lump not found");
+		if (!virtlinedefs)
+			I_Error("LINEDEFS lump not found");
+
+		// Traditional doom map format just assumes the number of elements from the lump sizes.
+		numvertexes  = virtvertexes->size / sizeof (mapvertex_t);
+		numsectors   = virtsectors->size  / sizeof (mapsector_t);
+		numsides     = virtsidedefs->size / sizeof (mapsidedef_t);
+		numlines     = virtlinedefs->size / sizeof (maplinedef_t);
+		nummapthings = virtthings->size   / (5 * sizeof (INT16));
 	}
 
-	if (!count) // zero length, someone probably used ZDBSP
+	if (numvertexes <= 0)
+		I_Error("Level has no vertices");
+	if (numsectors <= 0)
+		I_Error("Level has no sectors");
+	if (numsides <= 0)
+		I_Error("Level has no sidedefs");
+	if (numlines <= 0)
+		I_Error("Level has no linedefs");
+
+	vertexes  = Z_Calloc(numvertexes * sizeof (*vertexes), PU_LEVEL, NULL);
+	sectors   = Z_Calloc(numsectors * sizeof (*sectors), PU_LEVEL, NULL);
+	sides     = Z_Calloc(numsides * sizeof (*sides), PU_LEVEL, NULL);
+	lines     = Z_Calloc(numlines * sizeof (*lines), PU_LEVEL, NULL);
+	mapthings = Z_Calloc(nummapthings * sizeof (*mapthings), PU_LEVEL, NULL);
+
+#ifdef UDMF
+	if (textmap)
 	{
-		rejectmatrix = NULL;
-		CONS_Debug(DBG_SETUP, "P_LoadRawReject: REJECT lump has size 0, will not be loaded\n");
+
 	}
 	else
+#endif
 	{
-		rejectmatrix = Z_Malloc(count, PU_LEVEL, NULL); // allocate memory for the reject matrix
-		M_Memcpy(rejectmatrix, data, count); // copy the data into it
+		// Strict map data
+		P_LoadRawVertexes(virtvertexes->data);
+		P_LoadRawSectors(virtsectors->data);
+		P_LoadRawLineDefs(virtlinedefs->data);
+		P_SetupLines();
+		P_LoadRawSideDefs2(virtsidedefs->data);
 	}
 }
 
@@ -2307,6 +2186,8 @@ void P_LoadThingsOnly(void)
 	// Search through all the thinkers.
 	thinker_t *think;
 	INT32 i, viewid = -1, centerid = -1; // for skyboxes
+	virtres_t* virt = vres_GetMap(lastloadedmaplumpnum);
+	virtlump_t* vth = vres_Find(virt, "THINGS");
 
 	// check if these are any of the normal viewpoint/centerpoint mobjs in the level or not
 	if (skyboxmo[0] || skyboxmo[1])
@@ -2318,7 +2199,6 @@ void P_LoadThingsOnly(void)
 				centerid = i; // save id just in case
 		}
 
-
 	for (think = thlist[THINK_MOBJ].next; think != &thlist[THINK_MOBJ]; think = think->next)
 	{
 		if (think->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
@@ -2328,18 +2208,11 @@ void P_LoadThingsOnly(void)
 
 	P_LevelInitStuff();
 
-	if (W_IsLumpWad(lastloadedmaplumpnum)) // welp it's a map wad in a pk3
-	{ // HACK: Open wad file rather quickly so we can use the things lump
-		UINT8 *wadData = W_CacheLumpNum(lastloadedmaplumpnum, PU_STATIC);
-		filelump_t *fileinfo = (filelump_t *)(wadData + ((wadinfo_t *)wadData)->infotableofs);
-		fileinfo += ML_THINGS; // we only need the THINGS lump
-		P_PrepareRawThings(wadData + fileinfo->filepos, fileinfo->size);
-		Z_Free(wadData); // we're done with this now
-	}
-	else // phew it's just a WAD
-		P_PrepareThings(lastloadedmaplumpnum + ML_THINGS);
+	P_PrepareRawThings(vth->data);
 	P_LoadThings(true);
 
+	vres_Free(virt);
+
 	// restore skybox viewpoint/centerpoint if necessary, set them to defaults if we can't do that
 	skyboxmo[0] = skyboxviewpnts[(viewid >= 0) ? viewid : 0];
 	skyboxmo[1] = skyboxcenterpnts[(centerid >= 0) ? centerid : 0];
@@ -2371,7 +2244,7 @@ static INT32 P_MakeBufferMD5(const char *buffer, size_t len, void *resblock)
 #endif
 }
 
-static void P_MakeMapMD5(lumpnum_t maplumpnum, void *dest)
+static void P_MakeMapMD5(virtres_t* virt, void *dest)
 {
 	unsigned char linemd5[16];
 	unsigned char sectormd5[16];
@@ -2382,20 +2255,15 @@ static void P_MakeMapMD5(lumpnum_t maplumpnum, void *dest)
 
 	// Create a hash for the current map
 	// get the actual lumps!
-	UINT8 *datalines   = W_CacheLumpNum(maplumpnum + ML_LINEDEFS, PU_CACHE);
-	UINT8 *datasectors = W_CacheLumpNum(maplumpnum + ML_SECTORS, PU_CACHE);
-	UINT8 *datathings  = W_CacheLumpNum(maplumpnum + ML_THINGS, PU_CACHE);
-	UINT8 *datasides   = W_CacheLumpNum(maplumpnum + ML_SIDEDEFS, PU_CACHE);
+	virtlump_t* virtlines   = vres_Find(virt, "LINEDEFS");
+	virtlump_t* virtsectors = vres_Find(virt, "SECTORS");
+	virtlump_t* virtmthings = vres_Find(virt, "THINGS");
+	virtlump_t* virtsides   = vres_Find(virt, "SIDEDEFS");
 
-	P_MakeBufferMD5((char*)datalines,   W_LumpLength(maplumpnum + ML_LINEDEFS), linemd5);
-	P_MakeBufferMD5((char*)datasectors, W_LumpLength(maplumpnum + ML_SECTORS),  sectormd5);
-	P_MakeBufferMD5((char*)datathings,  W_LumpLength(maplumpnum + ML_THINGS),   thingmd5);
-	P_MakeBufferMD5((char*)datasides,   W_LumpLength(maplumpnum + ML_SIDEDEFS), sidedefmd5);
-
-	Z_Free(datalines);
-	Z_Free(datasectors);
-	Z_Free(datathings);
-	Z_Free(datasides);
+	P_MakeBufferMD5((char*)virtlines->data,   virtlines->size, linemd5);
+	P_MakeBufferMD5((char*)virtsectors->data, virtsectors->size,  sectormd5);
+	P_MakeBufferMD5((char*)virtmthings->data, virtmthings->size,   thingmd5);
+	P_MakeBufferMD5((char*)virtsides->data,   virtsides->size, sidedefmd5);
 
 	for (i = 0; i < 16; i++)
 		resmd5[i] = (linemd5[i] + sectormd5[i] + thingmd5[i] + sidedefmd5[i]) & 0xFF;
@@ -2631,7 +2499,6 @@ boolean P_SetupLevel(boolean skipprecip)
 	INT32 i, loadprecip = 1, ranspecialwipe = 0;
 	INT32 loademblems = 1;
 	INT32 fromnetsave = 0;
-	boolean loadedbm = false;
 	sector_t *ss;
 	boolean chase;
 	levelloading = true;
@@ -2880,110 +2747,39 @@ boolean P_SetupLevel(boolean skipprecip)
 	// SRB2 determines the sky texture to be used depending on the map header.
 	P_SetupLevelSky(mapheaderinfo[gamemap-1]->skynum, true);
 
-	P_MakeMapMD5(lastloadedmaplumpnum, &mapmd5);
-
-	// HACK ALERT: Cache the WAD, get the map data into the tables, free memory.
-	// As it is implemented right now, we're assuming an uncompressed WAD.
-	// (As in, a normal PWAD, not ZWAD or anything. The lump itself can be compressed.)
-	// We're not accounting for extra lumps and scrambled lump positions. Any additional data will cause an error.
-	if (W_IsLumpWad(lastloadedmaplumpnum))
-	{
-		// Remember that we're assuming that the WAD will have a specific set of lumps in a specific order.
-		UINT8 *wadData = W_CacheLumpNum(lastloadedmaplumpnum, PU_STATIC);
-		//filelump_t *fileinfo = wadData + ((wadinfo_t *)wadData)->infotableofs;
-		filelump_t *fileinfo = (filelump_t *)(wadData + ((wadinfo_t *)wadData)->infotableofs);
-		UINT32 numlumps = ((wadinfo_t *)wadData)->numlumps;
-
-		if (numlumps < ML_REJECT) // at least 9 lumps should be in the wad for a map to be loaded
-		{
-			I_Error("Bad WAD file for map %s!\n", maplumpname);
-		}
+	numdmstarts = numredctfstarts = numbluectfstarts = 0;
 
-		if (numlumps > ML_BLOCKMAP) // enough room for a BLOCKMAP lump at least
-		{
-			loadedbm = P_LoadRawBlockMap(
-							wadData + (fileinfo + ML_BLOCKMAP)->filepos,
-							(fileinfo + ML_BLOCKMAP)->size,
-							(fileinfo + ML_BLOCKMAP)->name);
-		}
-		P_LoadRawVertexes(wadData + (fileinfo + ML_VERTEXES)->filepos, (fileinfo + ML_VERTEXES)->size);
-		P_LoadRawSectors(wadData + (fileinfo + ML_SECTORS)->filepos, (fileinfo + ML_SECTORS)->size);
-		P_LoadRawSideDefs((fileinfo + ML_SIDEDEFS)->size);
-		P_LoadRawLineDefs(wadData + (fileinfo + ML_LINEDEFS)->filepos, (fileinfo + ML_LINEDEFS)->size);
-		P_LoadRawSideDefs2(wadData + (fileinfo + ML_SIDEDEFS)->filepos);
-		P_LoadRawSubsectors(wadData + (fileinfo + ML_SSECTORS)->filepos, (fileinfo + ML_SSECTORS)->size);
-		P_LoadRawNodes(wadData + (fileinfo + ML_NODES)->filepos, (fileinfo + ML_NODES)->size);
-		P_LoadRawSegs(wadData + (fileinfo + ML_SEGS)->filepos, (fileinfo + ML_SEGS)->size);
-		if (numlumps > ML_REJECT) // enough room for a REJECT lump at least
-		{
-			P_LoadRawReject(
-					wadData + (fileinfo + ML_REJECT)->filepos,
-					(fileinfo + ML_REJECT)->size,
-					(fileinfo + ML_REJECT)->name);
-		}
+	// reset the player starts
+	for (i = 0; i < MAXPLAYERS; i++)
+		playerstarts[i] = bluectfstarts[i] = redctfstarts[i] = NULL;
 
-		// Important: take care of the ordering of the next functions.
-		if (!loadedbm)
-			P_CreateBlockMap(); // Graue 02-29-2004
-		P_LoadLineDefs2();
-		P_GroupLines();
-		numdmstarts = numredctfstarts = numbluectfstarts = 0;
+	for (i = 0; i < MAX_DM_STARTS; i++)
+		deathmatchstarts[i] = NULL;
 
-		// reset the player starts
-		for (i = 0; i < MAXPLAYERS; i++)
-			playerstarts[i] = bluectfstarts[i] = redctfstarts[i] = NULL;
+	for (i = 0; i < 2; i++)
+		skyboxmo[i] = NULL;
 
-		for (i = 0; i < MAX_DM_STARTS; i++)
-			deathmatchstarts[i] = NULL;
+	for (i = 0; i < 16; i++)
+		skyboxviewpnts[i] = skyboxcenterpnts[i] = NULL;
 
-		for (i = 0; i < 2; i++)
-			skyboxmo[i] = NULL;
+	P_MapStart();
 
-		for (i = 0; i < 16; i++)
-			skyboxviewpnts[i] = skyboxcenterpnts[i] = NULL;
+	if (lastloadedmaplumpnum)
+	{
+		virtres_t* virt = vres_GetMap(lastloadedmaplumpnum);
 
-		P_MapStart();
+		P_LoadMapData(virt);
+		P_LoadMapBSP(virt);
+		P_LoadMapLUT(virt);
 
-		P_PrepareRawThings(wadData + (fileinfo + ML_THINGS)->filepos, (fileinfo + ML_THINGS)->size);
-		Z_Free(wadData);
-	}
-	else
-	{
-		// Important: take care of the ordering of the next functions.
-		loadedbm = P_LoadBlockMap(lastloadedmaplumpnum + ML_BLOCKMAP);
-		P_LoadVertexes(lastloadedmaplumpnum + ML_VERTEXES);
-		P_LoadSectors(lastloadedmaplumpnum + ML_SECTORS);
-		P_LoadSideDefs(lastloadedmaplumpnum + ML_SIDEDEFS);
-		P_LoadLineDefs(lastloadedmaplumpnum + ML_LINEDEFS);
-		P_LoadSideDefs2(lastloadedmaplumpnum + ML_SIDEDEFS);
-		P_LoadSubsectors(lastloadedmaplumpnum + ML_SSECTORS);
-		P_LoadNodes(lastloadedmaplumpnum + ML_NODES);
-		P_LoadSegs(lastloadedmaplumpnum + ML_SEGS);
-		P_LoadReject(lastloadedmaplumpnum + ML_REJECT);
-
-		// Important: take care of the ordering of the next functions.
-		if (!loadedbm)
-			P_CreateBlockMap(); // Graue 02-29-2004
 		P_LoadLineDefs2();
 		P_GroupLines();
-		numdmstarts = numredctfstarts = numbluectfstarts = 0;
-
-		// reset the player starts
-		for (i = 0; i < MAXPLAYERS; i++)
-			playerstarts[i] = bluectfstarts[i] = redctfstarts[i] = NULL;
 
-		for (i = 0; i < MAX_DM_STARTS; i++)
-			deathmatchstarts[i] = NULL;
-
-		for (i = 0; i < 2; i++)
-			skyboxmo[i] = NULL;
-
-		for (i = 0; i < 16; i++)
-			skyboxviewpnts[i] = skyboxcenterpnts[i] = NULL;
+		P_PrepareRawThings(vres_Find(virt, "THINGS")->data);
 
-		P_MapStart();
+		P_MakeMapMD5(virt, &mapmd5);
 
-		P_PrepareThings(lastloadedmaplumpnum + ML_THINGS);
+		vres_Free(virt);
 	}
 
 	// init gravity, tag lists,
diff --git a/src/p_spec.c b/src/p_spec.c
index dba4e17b5438a966620d07aec2e232268967a92a..b50fc66c05bc463880d13f06da7eab7d15b89e7a 100644
--- a/src/p_spec.c
+++ b/src/p_spec.c
@@ -6425,7 +6425,7 @@ void P_SpawnSpecials(INT32 fromnetsave)
 	INT32 j;
 	thinkerlist_t *secthinkers;
 	thinker_t *th;
-
+	virtres_t* virt = NULL;
 	// This used to be used, and *should* be used in the future,
 	// but currently isn't.
 	(void)fromnetsave;
@@ -7177,17 +7177,10 @@ void P_SpawnSpecials(INT32 fromnetsave)
 					UINT8 *data;
 					UINT16 b;
 
-					if (W_IsLumpWad(lastloadedmaplumpnum)) // welp it's a map wad in a pk3
-					{ // HACK: Open wad file rather quickly so we can get the data from the sidedefs lump
-						UINT8 *wadData = W_CacheLumpNum(lastloadedmaplumpnum, PU_STATIC);
-						filelump_t *fileinfo = (filelump_t *)(wadData + ((wadinfo_t *)wadData)->infotableofs);
-						fileinfo += ML_SIDEDEFS; // we only need the SIDEDEFS lump
-						data = Z_Malloc(fileinfo->size, PU_STATIC, NULL);
-						M_Memcpy(data, wadData + fileinfo->filepos, fileinfo->size); // copy data
-						Z_Free(wadData); // we're done with this now
-					}
-					else // phew it's just a WAD
-						data = W_CacheLumpNum(lastloadedmaplumpnum + ML_SIDEDEFS,PU_STATIC);
+					if (!virt)
+						virt = vres_GetMap(lastloadedmaplumpnum);
+
+					data = (UINT8*) vres_Find(virt, "SIDEDEFS")->data;
 
 					for (b = 0; b < (INT16)numsides; b++)
 					{
@@ -7207,7 +7200,6 @@ void P_SpawnSpecials(INT32 fromnetsave)
 								I_Error("Make-Your-Own-FOF (tag %d) needs a value in the linedef's second side upper texture field.", lines[i].tag);
 						}
 					}
-					Z_Free(data);
 				}
 				else
 					I_Error("Make-Your-Own FOF (tag %d) found without a 2nd linedef side!", lines[i].tag);
@@ -7433,6 +7425,9 @@ void P_SpawnSpecials(INT32 fromnetsave)
 		}
 	}
 
+	if (virt)
+		vres_Free(virt);
+
 	// Allocate each list
 	for (i = 0; i < numsectors; i++)
 		if(secthinkers[i].thinkers)
diff --git a/src/p_user.c b/src/p_user.c
index c2834f7776175509f54a851f59f5e77fa9d50b55..16244408d5ba3031f32ec14310768662574c312e 100644
--- a/src/p_user.c
+++ b/src/p_user.c
@@ -1600,7 +1600,7 @@ void P_RestoreMusic(player_t *player)
 		P_PlayJingle(player, JT_SUPER);
 
 	// Invulnerability
-	else if (player->powers[pw_invulnerability] > 1)
+	else if (player->powers[pw_invulnerability] > 1 && !player->powers[pw_super])
 	{
 		strlcpy(S_sfx[sfx_None].caption, "Invincibility", 14);
 		S_StartCaption(sfx_None, -1, player->powers[pw_invulnerability]);
@@ -5128,11 +5128,7 @@ static void P_DoJumpStuff(player_t *player, ticcmd_t *cmd)
 									boolean elem = ((player->powers[pw_shield] & SH_NOSTACK) == SH_ELEMENTAL);
 									player->pflags |= PF_THOKKED|PF_SHIELDABILITY;
 									if (elem)
-									{
-										player->pflags |= PF_NOJUMPDAMAGE;
-										P_SetPlayerMobjState(player->mo, S_PLAY_FALL);
 										S_StartSound(player->mo, sfx_s3k43);
-									}
 									else
 									{
 										player->pflags &= ~PF_NOJUMPDAMAGE;
diff --git a/src/sdl/macosx/Srb2mac.xcodeproj/project.pbxproj b/src/sdl/macosx/Srb2mac.xcodeproj/project.pbxproj
index 9297833e26b3baa3ef726d41f87887643bb47dc1..ab3157c44c5eab916918c6370074c70910cfb901 100644
--- a/src/sdl/macosx/Srb2mac.xcodeproj/project.pbxproj
+++ b/src/sdl/macosx/Srb2mac.xcodeproj/project.pbxproj
@@ -1219,7 +1219,7 @@
 		C01FCF4B08A954540054247B /* Debug */ = {
 			isa = XCBuildConfiguration;
 			buildSettings = {
-				CURRENT_PROJECT_VERSION = 2.1.25;
+				CURRENT_PROJECT_VERSION = 2.2.0;
 				GCC_PREPROCESSOR_DEFINITIONS = (
 					"$(inherited)",
 					NORMALSRB2,
@@ -1231,7 +1231,7 @@
 		C01FCF4C08A954540054247B /* Release */ = {
 			isa = XCBuildConfiguration;
 			buildSettings = {
-				CURRENT_PROJECT_VERSION = 2.1.25;
+				CURRENT_PROJECT_VERSION = 2.2.0;
 				GCC_ENABLE_FIX_AND_CONTINUE = NO;
 				GCC_GENERATE_DEBUGGING_SYMBOLS = NO;
 				GCC_PREPROCESSOR_DEFINITIONS = (
diff --git a/src/w_wad.c b/src/w_wad.c
index 950294b1f4ce263224fca3868add4889697f8110..a86e99237bf7f0bbeef2aea99033b85605f469fc 100644
--- a/src/w_wad.c
+++ b/src/w_wad.c
@@ -1888,3 +1888,101 @@ int W_VerifyNMUSlumps(const char *filename)
 	};
 	return W_VerifyFile(filename, NMUSlist, false);
 }
+
+/** \brief Generates a virtual resource used for level data loading.
+ *
+ * \param lumpnum_t reference
+ * \return Virtual resource
+ *
+ */
+virtres_t* vres_GetMap(lumpnum_t lumpnum)
+{
+	UINT32 i;
+	virtres_t* vres = NULL;
+	virtlump_t* vlumps = NULL;
+	size_t numlumps = 0;
+
+	if (W_IsLumpWad(lumpnum))
+	{
+		// Remember that we're assuming that the WAD will have a specific set of lumps in a specific order.
+		UINT8 *wadData = W_CacheLumpNum(lumpnum, PU_LEVEL);
+		filelump_t *fileinfo = (filelump_t *)(wadData + ((wadinfo_t *)wadData)->infotableofs);
+		numlumps = ((wadinfo_t *)wadData)->numlumps;
+		vlumps = Z_Malloc(sizeof(virtlump_t)*numlumps, PU_LEVEL, NULL);
+
+		// Build the lumps.
+		for (i = 0; i < numlumps; i++)
+		{
+			vlumps[i].size = (size_t)(((filelump_t *)(fileinfo + i))->size);
+			// Play it safe with the name in this case.
+			memcpy(vlumps[i].name, (fileinfo + i)->name, 8);
+			vlumps[i].name[8] = '\0';
+			vlumps[i].data = Z_Malloc(vlumps[i].size, PU_LEVEL, NULL); // This is memory inefficient, sorry about that.
+			memcpy(vlumps[i].data, wadData + (fileinfo + i)->filepos, vlumps[i].size);
+		}
+
+		Z_Free(wadData);
+	}
+	else
+	{
+		// Count number of lumps until the end of resource OR up until next "MAPXX" lump.
+		lumpnum_t lumppos = lumpnum + 1;
+		for (i = LUMPNUM(lumppos); i < wadfiles[WADFILENUM(lumpnum)]->numlumps; i++, lumppos++, numlumps++)
+			if (memcmp(W_CheckNameForNum(lumppos), "MAP", 3) == 0)
+				break;
+		numlumps++;
+
+		vlumps = Z_Malloc(sizeof(virtlump_t)*numlumps, PU_LEVEL, NULL);
+		for (i = 0; i < numlumps; i++, lumpnum++)
+		{
+			vlumps[i].size = W_LumpLength(lumpnum);
+			memcpy(vlumps[i].name, W_CheckNameForNum(lumpnum), 8);
+			vlumps[i].name[8] = '\0';
+			vlumps[i].data = W_CacheLumpNum(lumpnum, PU_LEVEL);
+		}
+	}
+	vres = Z_Malloc(sizeof(virtres_t), PU_LEVEL, NULL);
+	vres->vlumps = vlumps;
+	vres->numlumps = numlumps;
+
+	return vres;
+}
+
+/** \brief Frees zone memory for a given virtual resource.
+ *
+ * \param Virtual resource
+ */
+void vres_Free(virtres_t* vres)
+{
+	while (vres->numlumps--)
+		Z_Free(vres->vlumps[vres->numlumps].data);
+	Z_Free(vres->vlumps);
+	Z_Free(vres);
+}
+
+/** (Debug) Prints lumps from a virtual resource into console.
+ */
+/*
+static void vres_Diag(const virtres_t* vres)
+{
+	UINT32 i;
+	for (i = 0; i < vres->numlumps; i++)
+		CONS_Printf("%s\n", vres->vlumps[i].name);
+}
+*/
+
+/** \brief Finds a lump in a given virtual resource.
+ *
+ * \param Virtual resource
+ * \param Lump name to look for
+ * \return Virtual lump if found, NULL otherwise
+ *
+ */
+virtlump_t* vres_Find(const virtres_t* vres, const char* name)
+{
+	UINT32 i;
+	for (i = 0; i < vres->numlumps; i++)
+		if (fastcmp(name, vres->vlumps[i].name))
+			return &vres->vlumps[i];
+	return NULL;
+}
diff --git a/src/w_wad.h b/src/w_wad.h
index a8be13ece29c17511a7dc5579faccdfa0789d9ad..8008a577cf9f5f92631bdd91d7a440ca2688dd68 100644
--- a/src/w_wad.h
+++ b/src/w_wad.h
@@ -72,6 +72,25 @@ typedef struct
 	compmethod compression; // lump compression method
 } lumpinfo_t;
 
+// =========================================================================
+//                         'VIRTUAL' RESOURCES
+// =========================================================================
+
+typedef struct {
+	char name[9];
+	UINT8* data;
+	size_t size;
+} virtlump_t;
+
+typedef struct {
+	size_t numlumps;
+	virtlump_t* vlumps;
+} virtres_t;
+
+virtres_t* vres_GetMap(lumpnum_t);
+void vres_Free(virtres_t*);
+virtlump_t* vres_Find(const virtres_t*, const char*);
+
 // =========================================================================
 //                         DYNAMIC WAD LOADING
 // =========================================================================