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/doomdef.h b/src/doomdef.h
index 380adf781e047b7b40d58f7f14fadbeb4a102547..8853b4aae4978239a367bdbab7f94e764594e27f 100644
--- a/src/doomdef.h
+++ b/src/doomdef.h
@@ -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/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)