diff --git a/CMakeLists.txt b/CMakeLists.txt
index 34cc12f3c51f7d92244418329ba9a683ead58a27..e12b0d34532546595ea627329eba5e973567c671 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.0)
 # DO NOT CHANGE THIS SRB2 STRING! Some variable names depend on this string.
 # Version change is fine.
 project(SRB2
-	VERSION 2.2.5
+	VERSION 2.2.6
 	LANGUAGES C)
 
 if(${PROJECT_SOURCE_DIR} MATCHES ${PROJECT_BINARY_DIR})
diff --git a/appveyor.yml b/appveyor.yml
index 4a389dca722fddd18e288350ff408806e39fe34c..820c77e8b0c5a05b7ff6bdaa441e7048a23c86fa 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -1,4 +1,4 @@
-version: 2.2.5.{branch}-{build}
+version: 2.2.6.{branch}-{build}
 os: MinGW
 
 environment:
@@ -110,8 +110,8 @@ after_build:
 - set BUILDSARCHIVE=%REPO%-%CONFIGURATION%.7z
 - cmd: 7z a %BUILD_ARCHIVE% %BUILD_PATH% -xr!.gitignore
 - appveyor PushArtifact %BUILD_ARCHIVE%
-- cmd: copy %BUILD_ARCHIVE% %BUILDSARCHIVE%
-- appveyor PushArtifact %BUILDSARCHIVE%
+#- cmd: copy %BUILD_ARCHIVE% %BUILDSARCHIVE%
+#- appveyor PushArtifact %BUILDSARCHIVE%
 ##############################
 # DEPLOYER SCRIPT
 ##############################
diff --git a/src/Makefile.cfg b/src/Makefile.cfg
index 409cc4f22653141214eb1592e631a3a86f5944d8..23e6027981a94bcb652c31d68a92cdfb481c4244 100644
--- a/src/Makefile.cfg
+++ b/src/Makefile.cfg
@@ -1,3 +1,4 @@
+# vim: ft=make
 #
 # Makefile.cfg for SRB2
 #
@@ -7,6 +8,66 @@
 # and other things
 #
 
+# See the following variable don't start with 'GCC'. This is
+# to avoid a false positive with the version detection...
+
+SUPPORTED_GCC_VERSIONS:=\
+	91\
+	81 82 83\
+	71 72\
+	61 62 63 64\
+	51 52 53 54\
+	40 41 42 43 44 45 46 47 48 49
+
+LATEST_GCC_VERSION=9.1
+
+# gcc or g++
+ifdef PREFIX
+	CC=$(PREFIX)-gcc
+	CXX=$(PREFIX)-g++
+	OBJCOPY=$(PREFIX)-objcopy
+	OBJDUMP=$(PREFIX)-objdump
+	STRIP=$(PREFIX)-strip
+	WINDRES=$(PREFIX)-windres
+else
+	OBJCOPY=objcopy
+	OBJDUMP=objdump
+	STRIP=strip
+	WINDRES=windres
+endif
+
+# because Apple screws with us on this
+# need to get bintools from homebrew
+ifdef MACOSX
+	CC=clang
+	CXX=clang
+	OBJCOPY=gobjcopy
+	OBJDUMP=gobjdump
+endif
+
+# Automatically set version flag, but not if one was manually set
+ifeq   (,$(filter GCC%,$(.VARIABLES)))
+ ifneq (,$(findstring GCC,$(shell $(CC) --version))) # if it's GCC
+  version:=$(shell $(CC) -dumpversion)
+
+  # Turn version into words of major, minor
+  v:=$(subst ., ,$(version))
+  # concat. major minor
+  v:=$(word 1,$(v))$(word 2,$(v))
+
+  # If this version is not in the list, default to the latest supported
+  ifeq (,$(filter $(v),$(SUPPORTED_GCC_VERSIONS)))
+   $(info\
+		Your compiler version, GCC $(version), is not supported by the Makefile.\
+		The Makefile will assume GCC $(LATEST_GCC_VERSION).)
+   GCC$(subst .,,$(LATEST_GCC_VERSION))=1
+  else
+   $(info Detected GCC $(version) (GCC$(v)))
+   GCC$(v)=1
+  endif
+ endif
+endif
+
 ifdef GCC91
 GCC83=1
 endif
@@ -358,30 +419,6 @@ ifdef ARCHNAME
 	BIN:=$(BIN)/$(ARCHNAME)
 endif
 
-# gcc or g++
-ifdef PREFIX
-	CC=$(PREFIX)-gcc
-	CXX=$(PREFIX)-g++
-	OBJCOPY=$(PREFIX)-objcopy
-	OBJDUMP=$(PREFIX)-objdump
-	STRIP=$(PREFIX)-strip
-	WINDRES=$(PREFIX)-windres
-else
-	OBJCOPY=objcopy
-	OBJDUMP=objdump
-	STRIP=strip
-	WINDRES=windres
-endif
-
-# because Apple screws with us on this
-# need to get bintools from homebrew
-ifdef MACOSX
-	CC=clang
-	CXX=clang
-	OBJCOPY=gobjcopy
-	OBJDUMP=gobjdump
-endif
-
 OBJDUMP_OPTS?=--wide --source --line-numbers
 LD=$(CC)
 
diff --git a/src/byteptr.h b/src/byteptr.h
index 933c2af34b847ba1c5c10ef48fec205872aad4c6..01a6293b41401f9b663b6b672986a286b85e449a 100644
--- a/src/byteptr.h
+++ b/src/byteptr.h
@@ -150,15 +150,15 @@ FUNCINLINE static ATTRINLINE UINT32 readulong(void *ptr)
 
 #undef DEALIGNED
 
-#define WRITESTRINGN(p,s,n) { size_t tmp_i = 0; for (; tmp_i < n && s[tmp_i] != '\0'; tmp_i++) WRITECHAR(p, s[tmp_i]); if (tmp_i < n) WRITECHAR(p, '\0');}
-#define WRITESTRING(p,s)    { size_t tmp_i = 0; for (;              s[tmp_i] != '\0'; tmp_i++) WRITECHAR(p, s[tmp_i]); WRITECHAR(p, '\0');}
-#define WRITEMEM(p,s,n)     { memcpy(p, s, n); p += n; }
+#define WRITESTRINGN(p,s,n) do { size_t tmp_i = 0; for (; tmp_i < n && s[tmp_i] != '\0'; tmp_i++) WRITECHAR(p, s[tmp_i]); if (tmp_i < n) WRITECHAR(p, '\0');} while (0)
+#define WRITESTRING(p,s)    do { size_t tmp_i = 0; for (;              s[tmp_i] != '\0'; tmp_i++) WRITECHAR(p, s[tmp_i]); WRITECHAR(p, '\0');} while (0)
+#define WRITEMEM(p,s,n)     do { memcpy(p, s, n); p += n; } while (0)
 
 #define SKIPSTRING(p)       while (READCHAR(p) != '\0')
 
-#define READSTRINGN(p,s,n)  { size_t tmp_i = 0; for (; tmp_i < n && (s[tmp_i] = READCHAR(p)) != '\0'; tmp_i++); s[tmp_i] = '\0';}
-#define READSTRING(p,s)     { size_t tmp_i = 0; for (;              (s[tmp_i] = READCHAR(p)) != '\0'; tmp_i++); s[tmp_i] = '\0';}
-#define READMEM(p,s,n)      { memcpy(s, p, n); p += n; }
+#define READSTRINGN(p,s,n)  ({ size_t tmp_i = 0; for (; tmp_i < n && (s[tmp_i] = READCHAR(p)) != '\0'; tmp_i++); s[tmp_i] = '\0';})
+#define READSTRING(p,s)     ({ size_t tmp_i = 0; for (;              (s[tmp_i] = READCHAR(p)) != '\0'; tmp_i++); s[tmp_i] = '\0';})
+#define READMEM(p,s,n)      ({ memcpy(s, p, n); p += n; })
 
 #if 0 // old names
 #define WRITEBYTE(p,b)      WRITEUINT8(p,b)
diff --git a/src/command.c b/src/command.c
index 091dbd8f124826105089b1b24e2a8158052eb5e3..6fd8c11f9540c4cc04e7766e09b358c8d86f3b86 100644
--- a/src/command.c
+++ b/src/command.c
@@ -56,7 +56,13 @@ static boolean CV_FilterVarByVersion(consvar_t *v, const char *valstr);
 static boolean CV_Command(void);
 consvar_t *CV_FindVar(const char *name);
 static const char *CV_StringValue(const char *var_name);
+
 static consvar_t *consvar_vars; // list of registered console variables
+static UINT16     consvar_number_of_netids = 0;
+
+#ifdef OLD22DEMOCOMPAT
+static old_demo_var_t *consvar_old_demo_vars;
+#endif
 
 static char com_token[1024];
 static char *COM_Parse(char *data);
@@ -1121,14 +1127,16 @@ consvar_t *CV_FindVar(const char *name)
 	return NULL;
 }
 
-/** Builds a unique Net Variable identifier number, which is used
-  * in network packets instead of the full name.
+#ifdef OLD22DEMOCOMPAT
+/** Builds a unique Net Variable identifier number, which was used
+  * in network packets and demos instead of the full name.
+  *
+  * This function only still exists to keep compatibility with old demos.
   *
   * \param s Name of the variable.
   * \return A new unique identifier.
-  * \sa CV_FindNetVar
   */
-static inline UINT16 CV_ComputeNetid(const char *s)
+static inline UINT16 CV_ComputeOldDemoID(const char *s)
 {
 	UINT16 ret = 0, i = 0;
 	static UINT16 premiers[16] = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53};
@@ -1142,16 +1150,47 @@ static inline UINT16 CV_ComputeNetid(const char *s)
 	return ret;
 }
 
+/** Finds a net variable based on its old style hash. If a hash collides, a
+  * warning is printed and this function returns NULL.
+  *
+  * \param chk The variable's old style hash.
+  * \return A pointer to the variable itself if found, or NULL.
+  */
+static old_demo_var_t *CV_FindOldDemoVar(UINT16 chk)
+{
+	old_demo_var_t *demovar;
+
+	for (demovar = consvar_old_demo_vars; demovar; demovar = demovar->next)
+	{
+		if (demovar->checksum == chk)
+		{
+			if (demovar->collides)
+			{
+				CONS_Alert(CONS_WARNING,
+						"Old demo netvar id %hu is a collision\n", chk);
+				return NULL;
+			}
+
+			return demovar;
+		}
+	}
+
+	return NULL;
+}
+#endif/*OLD22DEMOCOMPAT*/
+
 /** Finds a net variable based on its identifier number.
   *
   * \param netid The variable's identifier number.
   * \return A pointer to the variable itself if found, or NULL.
-  * \sa CV_ComputeNetid
   */
 static consvar_t *CV_FindNetVar(UINT16 netid)
 {
 	consvar_t *cvar;
 
+	if (netid >= consvar_number_of_netids)
+		return NULL;
+
 	for (cvar = consvar_vars; cvar; cvar = cvar->next)
 		if (cvar->netid == netid)
 			return cvar;
@@ -1161,6 +1200,32 @@ static consvar_t *CV_FindNetVar(UINT16 netid)
 
 static void Setvalue(consvar_t *var, const char *valstr, boolean stealth);
 
+#ifdef OLD22DEMOCOMPAT
+/* Sets up a netvar for compatibility with old demos. */
+static void CV_RegisterOldDemoVar(consvar_t *variable)
+{
+	old_demo_var_t *demovar;
+	UINT16 old_demo_id;
+
+	old_demo_id = CV_ComputeOldDemoID(variable->name);
+
+	demovar = CV_FindOldDemoVar(old_demo_id);
+
+	if (demovar)
+		demovar->collides = true;
+	else
+	{
+		demovar = ZZ_Calloc(sizeof *demovar);
+
+		demovar->checksum = old_demo_id;
+		demovar->cvar = variable;
+
+		demovar->next = consvar_old_demo_vars;
+		consvar_old_demo_vars = demovar;
+	}
+}
+#endif
+
 /** Registers a variable for later use from the console.
   *
   * \param variable The variable to register.
@@ -1184,11 +1249,15 @@ void CV_RegisterVar(consvar_t *variable)
 	// check net variables
 	if (variable->flags & CV_NETVAR)
 	{
-		const consvar_t *netvar;
-		variable->netid = CV_ComputeNetid(variable->name);
-		netvar = CV_FindNetVar(variable->netid);
-		if (netvar)
-			I_Error("Variables %s and %s have same netid\n", variable->name, netvar->name);
+		variable->netid = consvar_number_of_netids++;
+
+		/* in case of overflow... */
+		if (variable->netid > consvar_number_of_netids)
+			I_Error("Way too many netvars");
+
+#ifdef OLD22DEMOCOMPAT
+		CV_RegisterOldDemoVar(variable);
+#endif
 	}
 
 	// link the variable in
@@ -1448,12 +1517,100 @@ badinput:
 
 static boolean serverloading = false;
 
+static consvar_t *
+ReadNetVar (UINT8 **p, char **return_value, boolean *return_stealth)
+{
+	UINT16  netid;
+	char   *val;
+	boolean stealth;
+
+	consvar_t *cvar;
+
+	netid   = READUINT16 (*p);
+	val     = (char *)*p;
+	SKIPSTRING (*p);
+	stealth = READUINT8  (*p);
+
+	cvar = CV_FindNetVar(netid);
+
+	if (cvar)
+	{
+		(*return_value)   = val;
+		(*return_stealth) = stealth;
+
+		DEBFILE(va("Netvar received: %s [netid=%d] value %s\n", cvar->name, netid, val));
+	}
+	else
+		CONS_Alert(CONS_WARNING, "Netvar not found with netid %hu\n", netid);
+
+	return cvar;
+}
+
+#ifdef OLD22DEMOCOMPAT
+static consvar_t *
+ReadOldDemoVar (UINT8 **p, char **return_value, boolean *return_stealth)
+{
+	UINT16  id;
+	char   *val;
+	boolean stealth;
+
+	old_demo_var_t *demovar;
+
+	id      = READUINT16 (*p);
+	val     = (char *)*p;
+	SKIPSTRING (*p);
+	stealth = READUINT8  (*p);
+
+	demovar = CV_FindOldDemoVar(id);
+
+	if (demovar)
+	{
+		(*return_value)   = val;
+		(*return_stealth) = stealth;
+
+		return demovar->cvar;
+	}
+	else
+	{
+		CONS_Alert(CONS_WARNING, "Netvar not found with old demo id %hu\n", id);
+		return NULL;
+	}
+}
+#endif/*OLD22DEMOCOMPAT*/
+
+static consvar_t *
+ReadDemoVar (UINT8 **p, char **return_value, boolean *return_stealth)
+{
+	char   *name;
+	char   *val;
+	boolean stealth;
+
+	consvar_t *cvar;
+
+	name    = (char *)*p;
+	SKIPSTRING (*p);
+	val     = (char *)*p;
+	SKIPSTRING (*p);
+	stealth = READUINT8  (*p);
+
+	cvar = CV_FindVar(name);
+
+	if (cvar)
+	{
+		(*return_value)   = val;
+		(*return_stealth) = stealth;
+	}
+	else
+		CONS_Alert(CONS_WARNING, "Netvar not found with name %s\n", name);
+
+	return cvar;
+}
+
 static void Got_NetVar(UINT8 **p, INT32 playernum)
 {
 	consvar_t *cvar;
-	UINT16 netid;
 	char *svalue;
-	UINT8 stealth = false;
+	boolean stealth;
 
 	if (playernum != serverplayer && !IsPlayerAdmin(playernum) && !serverloading)
 	{
@@ -1463,23 +1620,14 @@ static void Got_NetVar(UINT8 **p, INT32 playernum)
 			SendKick(playernum, KICK_MSG_CON_FAIL | KICK_MSG_KEEP_BODY);
 		return;
 	}
-	netid = READUINT16(*p);
-	cvar = CV_FindNetVar(netid);
-	svalue = (char *)*p;
-	SKIPSTRING(*p);
-	stealth = READUINT8(*p);
 
-	if (!cvar)
-	{
-		CONS_Alert(CONS_WARNING, "Netvar not found with netid %hu\n", netid);
-		return;
-	}
-	DEBFILE(va("Netvar received: %s [netid=%d] value %s\n", cvar->name, netid, svalue));
+	cvar = ReadNetVar(p, &svalue, &stealth);
 
-	Setvalue(cvar, svalue, stealth);
+	if (cvar)
+		Setvalue(cvar, svalue, stealth);
 }
 
-void CV_SaveNetVars(UINT8 **p)
+void CV_SaveVars(UINT8 **p, boolean in_demo)
 {
 	consvar_t *cvar;
 	UINT8 *count_p = *p;
@@ -1491,7 +1639,10 @@ void CV_SaveNetVars(UINT8 **p)
 	for (cvar = consvar_vars; cvar; cvar = cvar->next)
 		if ((cvar->flags & CV_NETVAR) && !CV_IsSetToDefault(cvar))
 		{
-			WRITEUINT16(*p, cvar->netid);
+			if (in_demo)
+				WRITESTRING(*p, cvar->name);
+			else
+				WRITEUINT16(*p, cvar->netid);
 			WRITESTRING(*p, cvar->string);
 			WRITEUINT8(*p, false);
 			++count;
@@ -1499,11 +1650,15 @@ void CV_SaveNetVars(UINT8 **p)
 	WRITEUINT16(count_p, count);
 }
 
-void CV_LoadNetVars(UINT8 **p)
+static void CV_LoadVars(UINT8 **p,
+		consvar_t *(*got)(UINT8 **p, char **ret_value, boolean *ret_stealth))
 {
 	consvar_t *cvar;
 	UINT16 count;
 
+	char *val;
+	boolean stealth;
+
 	// prevent "invalid command received"
 	serverloading = true;
 
@@ -1513,11 +1668,33 @@ void CV_LoadNetVars(UINT8 **p)
 
 	count = READUINT16(*p);
 	while (count--)
-		Got_NetVar(p, 0);
+	{
+		cvar = (*got)(p, &val, &stealth);
+
+		if (cvar)
+			Setvalue(cvar, val, stealth);
+	}
 
 	serverloading = false;
 }
 
+void CV_LoadNetVars(UINT8 **p)
+{
+	CV_LoadVars(p, ReadNetVar);
+}
+
+#ifdef OLD22DEMOCOMPAT
+void CV_LoadOldDemoVars(UINT8 **p)
+{
+	CV_LoadVars(p, ReadOldDemoVar);
+}
+#endif
+
+void CV_LoadDemoVars(UINT8 **p)
+{
+	CV_LoadVars(p, ReadDemoVar);
+}
+
 static void CV_SetCVar(consvar_t *var, const char *value, boolean stealth);
 
 void CV_ResetCheatNetVars(void)
diff --git a/src/command.h b/src/command.h
index 404052ce4775e301cb819931cfaad9ebe369e1f4..b39153a659802cb4fbb54d489e5bdc883f02dbe5 100644
--- a/src/command.h
+++ b/src/command.h
@@ -144,6 +144,19 @@ typedef struct consvar_s //NULL, NULL, 0, NULL, NULL |, 0, NULL, NULL, 0, 0, NUL
 	struct consvar_s *next;
 } consvar_t;
 
+#ifdef OLD22DEMOCOMPAT
+typedef struct old_demo_var old_demo_var_t;
+
+struct old_demo_var
+{
+	UINT16  checksum;
+	boolean collides;/* this var is a collision of multiple hashes */
+
+	consvar_t      *cvar;
+	old_demo_var_t *next;
+};
+#endif/*OLD22DEMOCOMPAT*/
+
 extern CV_PossibleValue_t CV_OnOff[];
 extern CV_PossibleValue_t CV_YesNo[];
 extern CV_PossibleValue_t CV_Unsigned[];
@@ -184,9 +197,18 @@ void CV_AddValue(consvar_t *var, INT32 increment);
 void CV_SaveVariables(FILE *f);
 
 // load/save gamesate (load and save option and for network join in game)
-void CV_SaveNetVars(UINT8 **p);
+void CV_SaveVars(UINT8 **p, boolean in_demo);
+
+#define CV_SaveNetVars(p) CV_SaveVars(p, false)
 void CV_LoadNetVars(UINT8 **p);
 
+#define CV_SaveDemoVars(p) CV_SaveVars(p, true)
+void CV_LoadDemoVars(UINT8 **p);
+
+#ifdef OLD22DEMOCOMPAT
+void CV_LoadOldDemoVars(UINT8 **p);
+#endif
+
 // reset cheat netvars after cheats is deactivated
 void CV_ResetCheatNetVars(void);
 
diff --git a/src/config.h.in b/src/config.h.in
index def5d67ce337e5520c20a65e3d96ff12bfb1c3af..595bea7b388f01de7375c78db36adb025b0dc9ae 100644
--- a/src/config.h.in
+++ b/src/config.h.in
@@ -31,12 +31,13 @@
  * Last updated 2020 / 05 / 10 - v2.2.3 - player.dta & patch.pk3
  * Last updated 2020 / 05 / 11 - v2.2.4 - patch.pk3
  * Last updated 2020 / 07 / 07 - v2.2.5 - player.dta & patch.pk3
+ * Last updated 2020 / 07 / 10 - v2.2.6 - player.dta & patch.pk3
  */
 #define ASSET_HASH_SRB2_PK3   "0277c9416756627004e83cbb5b2e3e28"
 #define ASSET_HASH_ZONES_PK3  "f7e88afb6af7996a834c7d663144bead"
-#define ASSET_HASH_PLAYER_DTA "3a48810db46c7790bd373d7e05af5221"
+#define ASSET_HASH_PLAYER_DTA "49dad7b24634c89728cc3e0b689e12bb"
 #ifdef USE_PATCH_DTA
-#define ASSET_HASH_PATCH_PK3  "57af2ba105dc4eb1e5b8e39e6aafaa4d"
+#define ASSET_HASH_PATCH_PK3  "ecf00060f03c76b3e49c6ae3925b627f"
 #endif
 
 #endif
diff --git a/src/d_clisrv.c b/src/d_clisrv.c
index a1e3976bc5c4e268ef4be770c7d474a634b2faf3..c7c5470ae75f3f996361468a6f21a90be219567a 100644
--- a/src/d_clisrv.c
+++ b/src/d_clisrv.c
@@ -5496,7 +5496,7 @@ void NetUpdate(void)
 		// update node latency values so we can take an average later.
 		for (i = 0; i < MAXPLAYERS; i++)
 			if (playeringame[i] && playernode[i] != UINT8_MAX)
-				realpingtable[i] += GetLag(playernode[i]) * (1000.00f / TICRATE);
+				realpingtable[i] += G_TicsToMilliseconds(GetLag(playernode[i]));
 		pingmeasurecount++;
 	}
 
diff --git a/src/d_netcmd.c b/src/d_netcmd.c
index 4f5c17c95b905c37b055cd40b06e549efb8bf1b8..592734067ffc629b4525251f6d8f207fa40faa60 100644
--- a/src/d_netcmd.c
+++ b/src/d_netcmd.c
@@ -3475,7 +3475,7 @@ static void Command_Version_f(void)
 #ifdef DEVELOP
 	CONS_Printf("Sonic Robo Blast 2 %s-%s (%s %s) ", compbranch, comprevision, compdate, comptime);
 #else
-	CONS_Printf("Sonic Robo Blast 2 %s (%s %s %s) ", VERSIONSTRING, compdate, comptime, comprevision);
+	CONS_Printf("Sonic Robo Blast 2 %s (%s %s %s %s) ", VERSIONSTRING, compdate, comptime, comprevision, compbranch);
 #endif
 
 	// Base library
diff --git a/src/doomdef.h b/src/doomdef.h
index a994d5990af772de09d403ca679edab68c4b743b..8853b4aae4978239a367bdbab7f94e764594e27f 100644
--- a/src/doomdef.h
+++ b/src/doomdef.h
@@ -143,9 +143,9 @@ extern char logfilename[1024];
 // we use comprevision and compbranch instead.
 #else
 #define VERSION    202 // Game version
-#define SUBVERSION 5  // more precise version number
-#define VERSIONSTRING "v2.2.5"
-#define VERSIONSTRINGW L"v2.2.5"
+#define SUBVERSION 6  // more precise version number
+#define VERSIONSTRING "v2.2.6"
+#define VERSIONSTRINGW L"v2.2.6"
 // Hey! If you change this, add 1 to the MODVERSION below!
 // Otherwise we can't force updates!
 #endif
@@ -213,7 +213,7 @@ extern char logfilename[1024];
 // it's only for detection of the version the player is using so the MS can alert them of an update.
 // Only set it higher, not lower, obviously.
 // Note that we use this to help keep internal testing in check; this is why v2.2.0 is not version "1".
-#define MODVERSION 45
+#define MODVERSION 47
 
 // To version config.cfg, MAJOREXECVERSION is set equal to MODVERSION automatically.
 // Increment MINOREXECVERSION whenever a config change is needed that does not correspond
@@ -657,4 +657,7 @@ extern const char *compdate, *comptime, *comprevision, *compbranch;
 /// Render flats on walls
 #define WALLFLATS
 
+/// Maintain compatibility with older 2.2 demos
+#define OLD22DEMOCOMPAT
+
 #endif // __DOOMDEF__
diff --git a/src/f_finale.c b/src/f_finale.c
index eb6e283ad7ff63ff71f38ad2953107aa466e574e..f47c6c1a798317dc9d08b5986b4ecee135386d02 100644
--- a/src/f_finale.c
+++ b/src/f_finale.c
@@ -1674,6 +1674,18 @@ void F_GameEvaluationDrawer(void)
 			V_DrawString(8, 96, V_YELLOWMAP, "Modified games\ncan't unlock\nextras!");
 	}
 #endif
+
+	if (marathonmode)
+	{
+		const char *rtatext, *cuttext;
+		rtatext = (marathonmode & MA_INGAME) ? "In-game timer" : "RTA timer";
+		cuttext = (marathonmode & MA_NOCUTSCENES) ? "" : " w/ cutscenes";
+		if (botskin)
+			endingtext = va("%s & %s, %s%s", skins[players[consoleplayer].skin].realname, skins[botskin-1].realname, rtatext, cuttext);
+		else
+			endingtext = va("%s, %s%s", skins[players[consoleplayer].skin].realname, rtatext, cuttext);
+		V_DrawCenteredString(BASEVIDWIDTH/2, 182, (ultimatemode ? V_REDMAP : V_YELLOWMAP), endingtext);
+	}
 }
 
 void F_GameEvaluationTicker(void)
diff --git a/src/g_demo.c b/src/g_demo.c
index 4dad85a3c93bd05c08c60b117a6529669cc6ebef..6c58f12fb4b7137c1c8be099cde85d3253093aaa 100644
--- a/src/g_demo.c
+++ b/src/g_demo.c
@@ -1525,7 +1525,7 @@ void G_BeginRecording(void)
 	}
 
 	// Save netvar data
-	CV_SaveNetVars(&demo_p);
+	CV_SaveDemoVars(&demo_p);
 
 	memset(&oldcmd,0,sizeof(oldcmd));
 	memset(&oldghost,0,sizeof(oldghost));
@@ -1756,6 +1756,9 @@ void G_DoPlayDemo(char *defdemoname)
 	UINT32 randseed, followitem;
 	fixed_t camerascale,shieldscale,actionspd,mindash,maxdash,normalspeed,runspeed,jumpfactor,height,spinheight;
 	char msg[1024];
+#ifdef OLD22DEMOCOMPAT
+	boolean use_old_demo_vars = false;
+#endif
 
 	skin[16] = '\0';
 	color[MAXCOLORNAME] = '\0';
@@ -1818,10 +1821,13 @@ void G_DoPlayDemo(char *defdemoname)
 	case DEMOVERSION: // latest always supported
 		cnamelen = MAXCOLORNAME;
 		break;
+#ifdef OLD22DEMOCOMPAT
 	// all that changed between then and now was longer color name
 	case 0x000c:
 		cnamelen = 16;
+		use_old_demo_vars = true;
 		break;
+#endif
 	// too old, cannot support.
 	default:
 		snprintf(msg, 1024, M_GetText("%s is an incompatible replay format and cannot be played.\n"), pdemoname);
@@ -1923,7 +1929,13 @@ void G_DoPlayDemo(char *defdemoname)
 	}
 
 	// net var data
-	CV_LoadNetVars(&demo_p);
+#ifdef OLD22DEMOCOMPAT
+	if (use_old_demo_vars)
+		CV_LoadOldDemoVars(&demo_p);
+	else
+#else
+		CV_LoadDemoVars(&demo_p);
+#endif
 
 	// Sigh ... it's an empty demo.
 	if (*demo_p == DEMOMARKER)
diff --git a/src/g_game.c b/src/g_game.c
index b20157156b8c6b969e09ed028c703ad23a05395c..69aac5065dc5f523811fbf72d53840bf78ce2394 100644
--- a/src/g_game.c
+++ b/src/g_game.c
@@ -3729,6 +3729,32 @@ static boolean CanSaveLevel(INT32 mapnum)
 	return (mapheaderinfo[mapnum-1] && (mapheaderinfo[mapnum-1]->levelflags & LF_SAVEGAME));
 }
 
+static void G_HandleSaveLevel(void)
+{
+	// do this before running the intermission or custom cutscene, mostly for the sake of marathon mode but it also massively reduces redundant file save events in f_finale.c
+	if (nextmap >= 1100-1)
+	{
+		if (!gamecomplete)
+			gamecomplete = 2; // special temporary mode to prevent using SP level select in pause menu until the intermission is over without restricting it in every intermission
+		if (cursaveslot > 0)
+		{
+			if (marathonmode)
+			{
+				// don't keep a backup around when the run is done!
+				if (FIL_FileExists(liveeventbackup))
+					remove(liveeventbackup);
+				cursaveslot = 0;
+			}
+			else if ((!modifiedgame || savemoddata) && !(netgame || multiplayer || ultimatemode || demorecording || metalrecording || modeattacking))
+				G_SaveGame((UINT32)cursaveslot, spstage_start);
+		}
+	}
+	// and doing THIS here means you don't lose your progress if you close the game mid-intermission
+	else if (!(ultimatemode || netgame || multiplayer || demoplayback || demorecording || metalrecording || modeattacking)
+		&& (!modifiedgame || savemoddata) && cursaveslot > 0 && CanSaveLevel(lastmap+1))
+		G_SaveGame((UINT32)cursaveslot, lastmap+1); // not nextmap+1 to route around special stages
+}
+
 //
 // G_DoCompleted
 //
@@ -3875,6 +3901,7 @@ static void G_DoCompleted(void)
 	if ((skipstats && !modeattacking) || (spec && modeattacking && stagefailed) || (intertype == int_none))
 	{
 		G_UpdateVisited();
+		G_HandleSaveLevel();
 		G_AfterIntermission();
 	}
 	else
@@ -3882,30 +3909,8 @@ static void G_DoCompleted(void)
 		G_SetGamestate(GS_INTERMISSION);
 		Y_StartIntermission();
 		G_UpdateVisited();
+		G_HandleSaveLevel();
 	}
-
-	// do this before running the intermission or custom cutscene, mostly for the sake of marathon mode but it also massively reduces redundant file save events in f_finale.c
-	if (nextmap >= 1100-1)
-	{
-		if (!gamecomplete)
-			gamecomplete = 2; // special temporary mode to prevent using SP level select in pause menu until the intermission is over without restricting it in every intermission
-		if (cursaveslot > 0)
-		{
-			if (marathonmode)
-			{
-				// don't keep a backup around when the run is done!
-				if (FIL_FileExists(liveeventbackup))
-					remove(liveeventbackup);
-				cursaveslot = 0;
-			}
-			else if ((!modifiedgame || savemoddata) && !(netgame || multiplayer || ultimatemode || demorecording || metalrecording || modeattacking))
-				G_SaveGame((UINT32)cursaveslot, spstage_start);
-		}
-	}
-	// and doing THIS here means you don't lose your progress if you close the game mid-intermission
-	else if (!(ultimatemode || netgame || multiplayer || demoplayback || demorecording || metalrecording || modeattacking)
-		&& (!modifiedgame || savemoddata) && cursaveslot > 0 && CanSaveLevel(lastmap+1))
-		G_SaveGame((UINT32)cursaveslot, lastmap+1); // not nextmap+1 to route around special stages
 }
 
 // See also F_EndCutscene, the only other place which handles intra-map/ending transitions
@@ -4498,7 +4503,10 @@ void G_SaveGame(UINT32 slot, INT16 mapnum)
 		P_SaveGame(mapnum);
 		if (marathonmode)
 		{
-			WRITEUINT32(save_p, marathontime);
+			UINT32 writetime = marathontime;
+			if (!(marathonmode & MA_INGAME))
+				marathontime += TICRATE*5; // live event backup penalty because we don't know how long it takes to get to the next map
+			WRITEUINT32(save_p, writetime);
 			WRITEUINT8(save_p, (marathonmode & ~MA_INIT));
 		}
 
diff --git a/src/hardware/hw_md2.c b/src/hardware/hw_md2.c
index f5df49bcdef2ffa5a76d15f3f6605129de1c9b4c..80c01f98cb78e7f3b81aa47da9144798eed9860a 100644
--- a/src/hardware/hw_md2.c
+++ b/src/hardware/hw_md2.c
@@ -1177,6 +1177,34 @@ static UINT8 HWR_GetModelSprite2(md2_t *md2, skin_t *skin, UINT8 spr2, player_t
 	return spr2;
 }
 
+static void adjustTextureCoords(model_t *model, GLPatch_t *gpatch)
+{
+	int i;
+	for (i = 0; i < model->numMeshes; i++)
+	{
+		int j;
+		mesh_t *mesh = &model->meshes[i];
+		int numVertices;
+		float *uvPtr = mesh->uvs;
+
+		// i dont know if this is actually possible, just logical conclusion of structure in CreateModelVBOs
+		if (!mesh->frames && !mesh->tinyframes) return;
+
+		if (mesh->frames) // again CreateModelVBO and CreateModelVBOTiny iterate like this so I'm gonna do that too
+			numVertices = mesh->numTriangles * 3;
+		else
+			numVertices = mesh->numVertices;
+
+		// fix uvs (texture coordinates) to take into account that the actual texture
+		// has empty space added until the next power of two
+		for (j = 0; j < numVertices; j++)
+		{
+			*uvPtr++ *= gpatch->max_s;
+			*uvPtr++ *= gpatch->max_t;
+		}
+	}
+}
+
 //
 // HWR_DrawModel
 //
@@ -1278,6 +1306,19 @@ boolean HWR_DrawModel(gr_vissprite_t *spr)
 			sprinfo = &spriteinfo[spr->mobj->sprite];
 		}
 
+		// texture loading before model init, so it knows if sprite graphics are used, which
+		// means that texture coordinates have to be adjusted
+		gpatch = md2->grpatch;
+		if (!gpatch || ((!gpatch->mipmap->grInfo.format || !gpatch->mipmap->downloaded) && !md2->notexturefile))
+			md2_loadTexture(md2);
+		gpatch = md2->grpatch; // Load it again, because it isn't being loaded into gpatch after md2_loadtexture...
+
+		if ((gpatch && gpatch->mipmap->grInfo.format) // don't load the blend texture if the base texture isn't available
+			&& (!md2->blendgrpatch
+			|| ((!((GLPatch_t *)md2->blendgrpatch)->mipmap->grInfo.format || !((GLPatch_t *)md2->blendgrpatch)->mipmap->downloaded)
+			&& !md2->noblendfile)))
+			md2_loadBlendTexture(md2);
+
 		if (md2->error)
 			return false; // we already failed loading this before :(
 		if (!md2->model)
@@ -1289,6 +1330,10 @@ boolean HWR_DrawModel(gr_vissprite_t *spr)
 			if (md2->model)
 			{
 				md2_printModelInfo(md2->model);
+				// if model uses sprite patch as texture, then
+				// adjust texture coordinates to take power of two textures into account
+				if (!gpatch || !gpatch->mipmap->grInfo.format)
+					adjustTextureCoords(md2->model, spr->gpatch);
 				HWD.pfnCreateModelVBOs(md2->model);
 			}
 			else
@@ -1306,16 +1351,6 @@ boolean HWR_DrawModel(gr_vissprite_t *spr)
 		//HWD.pfnSetBlend(blend); // This seems to actually break translucency?
 		finalscale = md2->scale;
 		//Hurdler: arf, I don't like that implementation at all... too much crappy
-		gpatch = md2->grpatch;
-		if (!gpatch || ((!gpatch->mipmap->grInfo.format || !gpatch->mipmap->downloaded) && !md2->notexturefile))
-			md2_loadTexture(md2);
-		gpatch = md2->grpatch; // Load it again, because it isn't being loaded into gpatch after md2_loadtexture...
-
-		if ((gpatch && gpatch->mipmap->grInfo.format) // don't load the blend texture if the base texture isn't available
-			&& (!md2->blendgrpatch
-			|| ((!((GLPatch_t *)md2->blendgrpatch)->mipmap->grInfo.format || !((GLPatch_t *)md2->blendgrpatch)->mipmap->downloaded)
-			&& !md2->noblendfile)))
-			md2_loadBlendTexture(md2);
 
 		if (gpatch && gpatch->mipmap->grInfo.format) // else if meant that if a texture couldn't be loaded, it would just end up using something else's texture
 		{
diff --git a/src/hardware/r_opengl/r_opengl.c b/src/hardware/r_opengl/r_opengl.c
index e5f6ff3cfcc1c65a1266ee08248c8f80d910bb06..b7baa4eb0bdd0389338e61e58201bfa21bb6bd27 100644
--- a/src/hardware/r_opengl/r_opengl.c
+++ b/src/hardware/r_opengl/r_opengl.c
@@ -2939,6 +2939,8 @@ EXPORT void HWRAPI(SetTransform) (FTransform *stransform)
 	if (shearing)
 	{
 		float fdy = stransform->viewaiming * 2;
+		if (stransform->flip)
+			fdy *= -1.0f;
 		pglTranslatef(0.0f, -fdy/BASEVIDHEIGHT, 0.0f);
 	}
 
diff --git a/src/m_menu.c b/src/m_menu.c
index dba792e903f5f8e1134f343815f6b41815a9f3c7..131f72c767bd9d4b78eb60522622e99b51f74d15 100644
--- a/src/m_menu.c
+++ b/src/m_menu.c
@@ -487,7 +487,7 @@ CV_PossibleValue_t loadless_cons_t[] = {{0, "Realtime"}, {1, "In-game"}, {0, NUL
 
 consvar_t cv_dummymarathon = {"dummymarathon", "Standard", CV_HIDEN, marathon_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
 consvar_t cv_dummycutscenes = {"dummycutscenes", "Off", CV_HIDEN, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_dummyloadless = {"dummyloadless", "Realtime", CV_HIDEN, loadless_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_dummyloadless = {"dummyloadless", "In-game", CV_HIDEN, loadless_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
 
 // ==========================================================================
 // ORGANIZATION START.
@@ -10563,8 +10563,7 @@ static void M_StartMarathon(INT32 choice)
 	(void)choice;
 	marathontime = 0;
 	marathonmode = MA_RUNNING|MA_INIT;
-	if (cv_dummymarathon.value == 1)
-		cursaveslot = MARATHONSLOT;
+	cursaveslot = (cv_dummymarathon.value == 1) ? MARATHONSLOT : 0;
 	if (!cv_dummycutscenes.value)
 		marathonmode |= MA_NOCUTSCENES;
 	if (cv_dummyloadless.value)
diff --git a/src/p_inter.c b/src/p_inter.c
index 9caed927d31a6ac5036d9dab2eb7fc6a14ae74d2..bd044f32a1672f81eacb28c1b9a284643d796591 100644
--- a/src/p_inter.c
+++ b/src/p_inter.c
@@ -467,10 +467,22 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 
 			if ((P_MobjFlip(toucher)*toucher->momz < 0) && (elementalpierce != 1))
 			{
-				if (elementalpierce == 2)
-					P_DoBubbleBounce(player);
-				else if (!(player->charability2 == CA2_MELEE && player->panim == PA_ABILITY2))
-					toucher->momz = -toucher->momz;
+				if (!(player->charability2 == CA2_MELEE && player->panim == PA_ABILITY2))
+				{
+					fixed_t setmomz = -toucher->momz; // Store this, momz get changed by P_DoJump within P_DoBubbleBounce
+					
+					if (elementalpierce == 2) // Reset bubblewrap, part 1
+						P_DoBubbleBounce(player);
+					toucher->momz = setmomz;
+					if (elementalpierce == 2) // Reset bubblewrap, part 2
+					{
+						boolean underwater = toucher->eflags & MFE_UNDERWATER;
+							
+						if (underwater)
+							toucher->momz /= 2;
+						toucher->momz -= (toucher->momz/(underwater ? 8 : 4)); // Cap the height!
+					}
+				}
 			}
 			if (player->pflags & PF_BOUNCING)
 				P_DoAbilityBounce(player, false);
diff --git a/src/p_map.c b/src/p_map.c
index b7ad14808bde78f5944526f73e1eea28bbd27825..f7db52f6a0353a73d8b149de2f4bb8f6774816a2 100644
--- a/src/p_map.c
+++ b/src/p_map.c
@@ -1678,13 +1678,23 @@ static boolean PIT_CheckThing(mobj_t *thing)
 				&& (flipval*(*momz) < 0) // monitor is on the floor and you're going down, or on the ceiling and you're going up
 				&& (elementalpierce != 1)) // you're not piercing through the monitor...
 				{
-					if (elementalpierce == 2)
-						P_DoBubbleBounce(player);
-					else if (!(player->charability2 == CA2_MELEE && player->panim == PA_ABILITY2))
+					if (!(player->charability2 == CA2_MELEE && player->panim == PA_ABILITY2))
 					{
-						*momz = -*momz; // Therefore, you should be thrust in the opposite direction, vertically.
+						fixed_t setmomz = -*momz; // Store this, momz get changed by P_DoJump within P_DoBubbleBounce
+					
+						if (elementalpierce == 2) // Reset bubblewrap, part 1
+							P_DoBubbleBounce(player);
+						*momz = setmomz; // Therefore, you should be thrust in the opposite direction, vertically.
 						if (player->charability == CA_TWINSPIN && player->panim == PA_ABILITY)
 							P_TwinSpinRejuvenate(player, player->thokitem);
+						if (elementalpierce == 2) // Reset bubblewrap, part 2
+						{
+							boolean underwater = tmthing->eflags & MFE_UNDERWATER;
+							
+							if (underwater)
+								*momz /= 2;
+							*momz -= (*momz/(underwater ? 8 : 4)); // Cap the height!
+						}
 					}
 				}
 				if (!(elementalpierce == 1 && thing->flags & MF_GRENADEBOUNCE)) // prevent gold monitor clipthrough.
diff --git a/src/p_user.c b/src/p_user.c
index 261e4b28cde64f109cc275cfc3b0e6ab7b39b03c..d426277ff06099189ba7d6148c1b53153f82029c 100644
--- a/src/p_user.c
+++ b/src/p_user.c
@@ -4863,7 +4863,7 @@ void P_DoBubbleBounce(player_t *player)
 	player->pflags |= PF_THOKKED;
 	player->pflags &= ~PF_STARTJUMP;
 	player->secondjump = UINT8_MAX;
-	player->mo->momz = FixedMul(player->mo->momz, 5*FRACUNIT/4);
+	player->mo->momz = FixedMul(player->mo->momz, 11*FRACUNIT/8);
 }
 
 //
@@ -5112,15 +5112,19 @@ 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->mo->momx = player->mo->momy = 0;
 										S_StartSound(player->mo, sfx_s3k43);
+									}
 									else
 									{
+										player->mo->momx -= (player->mo->momx/3);
+										player->mo->momy -= (player->mo->momy/3);
 										player->pflags &= ~PF_NOJUMPDAMAGE;
 										P_SetPlayerMobjState(player->mo, S_PLAY_ROLL);
 										S_StartSound(player->mo, sfx_s3k44);
 									}
 									player->secondjump = 0;
-									player->mo->momx = player->mo->momy = 0;
 									P_SetObjectMomZ(player->mo, -24*FRACUNIT, false);
 									break;
 								}
diff --git a/src/sdl/macosx/Srb2mac.xcodeproj/project.pbxproj b/src/sdl/macosx/Srb2mac.xcodeproj/project.pbxproj
index a7d9022f1b4afa9acdfd14a4fc24b0c299569996..eeae1c2de7518e8c2d6678a06adcd09d7b84b9aa 100644
--- a/src/sdl/macosx/Srb2mac.xcodeproj/project.pbxproj
+++ b/src/sdl/macosx/Srb2mac.xcodeproj/project.pbxproj
@@ -1215,7 +1215,7 @@
 		C01FCF4B08A954540054247B /* Debug */ = {
 			isa = XCBuildConfiguration;
 			buildSettings = {
-				CURRENT_PROJECT_VERSION = 2.2.5;
+				CURRENT_PROJECT_VERSION = 2.2.6;
 				GCC_PREPROCESSOR_DEFINITIONS = (
 					"$(inherited)",
 					NORMALSRB2,
@@ -1227,7 +1227,7 @@
 		C01FCF4C08A954540054247B /* Release */ = {
 			isa = XCBuildConfiguration;
 			buildSettings = {
-				CURRENT_PROJECT_VERSION = 2.2.5;
+				CURRENT_PROJECT_VERSION = 2.2.6;
 				GCC_ENABLE_FIX_AND_CONTINUE = NO;
 				GCC_GENERATE_DEBUGGING_SYMBOLS = NO;
 				GCC_PREPROCESSOR_DEFINITIONS = (
diff --git a/src/win32/Srb2win.rc b/src/win32/Srb2win.rc
index c6ec8dff325de2e9396220f4bd978e5c617d452f..b90947a9ece7d48c1166bad85861374687923048 100644
--- a/src/win32/Srb2win.rc
+++ b/src/win32/Srb2win.rc
@@ -66,8 +66,8 @@ END
 #include "../doomdef.h" // Needed for version string
 
 VS_VERSION_INFO VERSIONINFO
- FILEVERSION 2,2,5,0
- PRODUCTVERSION 2,2,5,0
+ FILEVERSION 2,2,6,0
+ PRODUCTVERSION 2,2,6,0
  FILEFLAGSMASK 0x3fL
 #ifdef _DEBUG
  FILEFLAGS 0x1L