diff --git a/src/p_saveg.cpp b/src/p_saveg.cpp
index 80cad3eb5c..e2178f9950 100644
--- a/src/p_saveg.cpp
+++ b/src/p_saveg.cpp
@@ -65,287 +65,8 @@
 #include "serializer.h"
 
 // just the stuff that already got converted to FSerializer so that it can be seen as 'done' when searching.
-#define COMMON_STUFF
 #include "zzz_old.cpp"
 
-void CopyPlayer (player_t *dst, player_t *src, const char *name);
-static void ReadOnePlayer (FArchive &arc, bool skipload);
-static void ReadMultiplePlayers (FArchive &arc, int numPlayers, int numPlayersNow, bool skipload);
-static void SpawnExtraPlayers ();
-
-//
-// P_ArchivePlayers
-//
-void P_SerializePlayers (FArchive &arc, bool skipload)
-{
-	BYTE numPlayers, numPlayersNow;
-	int i;
-
-	// Count the number of players present right now.
-	for (numPlayersNow = 0, i = 0; i < MAXPLAYERS; ++i)
-	{
-		if (playeringame[i])
-		{
-			++numPlayersNow;
-		}
-	}
-
-	if (arc.IsStoring())
-	{
-		// Record the number of players in this save.
-		arc << numPlayersNow;
-
-		// Record each player's name, followed by their data.
-		for (i = 0; i < MAXPLAYERS; ++i)
-		{
-			if (playeringame[i])
-			{
-				arc.WriteString (players[i].userinfo.GetName());
-				players[i].Serialize (arc);
-			}
-		}
-	}
-	else
-	{
-		arc << numPlayers;
-
-		// If there is only one player in the game, they go to the
-		// first player present, no matter what their name.
-		if (numPlayers == 1)
-		{
-			ReadOnePlayer (arc, skipload);
-		}
-		else
-		{
-			ReadMultiplePlayers (arc, numPlayers, numPlayersNow, skipload);
-		}
-		if (!skipload && numPlayersNow > numPlayers)
-		{
-			SpawnExtraPlayers ();
-		}
-		// Redo pitch limits, since the spawned player has them at 0.
-		players[consoleplayer].SendPitchLimits();
-	}
-}
-
-static void ReadOnePlayer (FArchive &arc, bool skipload)
-{
-	int i;
-	char *name = NULL;
-	bool didIt = false;
-
-	arc << name;
-
-	for (i = 0; i < MAXPLAYERS; ++i)
-	{
-		if (playeringame[i])
-		{
-			if (!didIt)
-			{
-				didIt = true;
-				player_t playerTemp;
-				playerTemp.Serialize (arc);
-				if (!skipload)
-				{
-					CopyPlayer (&players[i], &playerTemp, name);
-				}
-			}
-			else
-			{
-				if (players[i].mo != NULL)
-				{
-					players[i].mo->Destroy();
-					players[i].mo = NULL;
-				}
-			}
-		}
-	}
-	delete[] name;
-}
-
-static void ReadMultiplePlayers (FArchive &arc, int numPlayers, int numPlayersNow, bool skipload)
-{
-	// For two or more players, read each player into a temporary array.
-	int i, j;
-	char **nametemp = new char *[numPlayers];
-	player_t *playertemp = new player_t[numPlayers];
-	BYTE *tempPlayerUsed = new BYTE[numPlayers];
-	BYTE playerUsed[MAXPLAYERS];
-
-	for (i = 0; i < numPlayers; ++i)
-	{
-		nametemp[i] = NULL;
-		arc << nametemp[i];
-		playertemp[i].Serialize (arc);
-		tempPlayerUsed[i] = 0;
-	}
-	for (i = 0; i < MAXPLAYERS; ++i)
-	{
-		playerUsed[i] = playeringame[i] ? 0 : 2;
-	}
-
-	if (!skipload)
-	{
-		// Now try to match players from the savegame with players present
-		// based on their names. If two players in the savegame have the
-		// same name, then they are assigned to players in the current game
-		// on a first-come, first-served basis.
-		for (i = 0; i < numPlayers; ++i)
-		{
-			for (j = 0; j < MAXPLAYERS; ++j)
-			{
-				if (playerUsed[j] == 0 && stricmp(players[j].userinfo.GetName(), nametemp[i]) == 0)
-				{ // Found a match, so copy our temp player to the real player
-					Printf ("Found player %d (%s) at %d\n", i, nametemp[i], j);
-					CopyPlayer (&players[j], &playertemp[i], nametemp[i]);
-					playerUsed[j] = 1;
-					tempPlayerUsed[i] = 1;
-					break;
-				}
-			}
-		}
-
-		// Any players that didn't have matching names are assigned to existing
-		// players on a first-come, first-served basis.
-		for (i = 0; i < numPlayers; ++i)
-		{
-			if (tempPlayerUsed[i] == 0)
-			{
-				for (j = 0; j < MAXPLAYERS; ++j)
-				{
-					if (playerUsed[j] == 0)
-					{
-						Printf ("Assigned player %d (%s) to %d (%s)\n", i, nametemp[i], j, players[j].userinfo.GetName());
-						CopyPlayer (&players[j], &playertemp[i], nametemp[i]);
-						playerUsed[j] = 1;
-						tempPlayerUsed[i] = 1;
-						break;
-					}
-				}
-			}
-		}
-
-		// Make sure any extra players don't have actors spawned yet. Happens if the players
-		// present now got the same slots as they had in the save, but there are not as many
-		// as there were in the save.
-		for (j = 0; j < MAXPLAYERS; ++j)
-		{
-			if (playerUsed[j] == 0)
-			{
-				if (players[j].mo != NULL)
-				{
-					players[j].mo->Destroy();
-					players[j].mo = NULL;
-				}
-			}
-		}
-
-		// Remove any temp players that were not used. Happens if there are fewer players
-		// than there were in the save, and they got shuffled.
-		for (i = 0; i < numPlayers; ++i)
-		{
-			if (tempPlayerUsed[i] == 0)
-			{
-				playertemp[i].mo->Destroy();
-				playertemp[i].mo = NULL;
-			}
-		}
-	}
-
-	delete[] tempPlayerUsed;
-	delete[] playertemp;
-	for (i = 0; i < numPlayers; ++i)
-	{
-		delete[] nametemp[i];
-	}
-	delete[] nametemp;
-}
-
-void CopyPlayer (player_t *dst, player_t *src, const char *name)
-{
-	// The userinfo needs to be saved for real players, but it
-	// needs to come from the save for bots.
-	userinfo_t uibackup;
-	userinfo_t uibackup2;
-
-	uibackup.TransferFrom(dst->userinfo);
-	uibackup2.TransferFrom(src->userinfo);
-
-	int chasecam = dst->cheats & CF_CHASECAM;	// Remember the chasecam setting
-	bool attackdown = dst->attackdown;
-	bool usedown = dst->usedown;
-
-	
-	*dst = *src;		// To avoid memory leaks at this point the userinfo in src must be empty which is taken care of by the TransferFrom call above.
-
-	dst->cheats |= chasecam;
-
-	if (dst->Bot != nullptr)
-	{
-		botinfo_t *thebot = bglobal.botinfo;
-		while (thebot && stricmp (name, thebot->name))
-		{
-			thebot = thebot->next;
-		}
-		if (thebot)
-		{
-			thebot->inuse = BOTINUSE_Yes;
-		}
-		bglobal.botnum++;
-		dst->userinfo.TransferFrom(uibackup2);
-	}
-	else
-	{
-		dst->userinfo.TransferFrom(uibackup);
-	}
-	// Validate the skin
-	dst->userinfo.SkinNumChanged(R_FindSkin(skins[dst->userinfo.GetSkin()].name, dst->CurrentPlayerClass));
-
-	// Make sure the player pawn points to the proper player struct.
-	if (dst->mo != nullptr)
-	{
-		dst->mo->player = dst;
-	}
-
-	// Same for the psprites.
-	DPSprite *pspr = dst->psprites;
-	while (pspr)
-	{
-		pspr->Owner = dst;
-
-		pspr = pspr->Next;
-	}
-
-	// Don't let the psprites be destroyed when src is destroyed.
-	src->psprites = nullptr;
-
-	// These 2 variables may not be overwritten.
-	dst->attackdown = attackdown;
-	dst->usedown = usedown;
-}
-
-static void SpawnExtraPlayers ()
-{
-	// If there are more players now than there were in the savegame,
-	// be sure to spawn the extra players.
-	int i;
-
-	if (deathmatch)
-	{
-		return;
-	}
-
-	for (i = 0; i < MAXPLAYERS; ++i)
-	{
-		if (playeringame[i] && players[i].mo == NULL)
-		{
-			players[i].playerstate = PST_ENTER;
-			P_SpawnPlayer(&playerstarts[i], i, (level.flags2 & LEVEL2_PRERAISEWEAPON) ? SPF_WEAPONFULLYUP : 0);
-		}
-	}
-}
-
-
 
 //
 // Thinkers
@@ -807,6 +528,351 @@ FSerializer &Serialize(FSerializer &arc, const char *key, zone_t &z, zone_t *def
 	return Serialize(arc, key, z.Environment, nullptr);
 }
 
+//==========================================================================
+//
+// ArchiveSounds
+//
+//==========================================================================
+
+void P_SerializeSounds(FSerializer &arc)
+{
+	S_SerializeSounds(arc);
+	DSeqNode::SerializeSequences (arc);
+	char *name = NULL;
+	BYTE order;
+
+	if (arc.isWriting())
+	{
+		order = S_GetMusic(&name);
+	}
+	arc("musicname", name)
+		("musicorder", order);
+
+	if (arc.isReading())
+	{
+		if (!S_ChangeMusic(name, order))
+			if (level.cdtrack == 0 || !S_ChangeCDMusic(level.cdtrack, level.cdid))
+				S_ChangeMusic(level.Music, level.musicorder);
+	}
+	delete[] name;
+}
+
+//==========================================================================
+//
+//
+//
+//==========================================================================
+
+void CopyPlayer(player_t *dst, player_t *src, const char *name);
+static void ReadOnePlayer(FSerializer &arc, bool skipload);
+static void ReadMultiplePlayers(FSerializer &arc, int numPlayers, int numPlayersNow, bool skipload);
+static void SpawnExtraPlayers();
+
+//==========================================================================
+//
+// P_ArchivePlayers
+//
+//==========================================================================
+
+void P_SerializePlayers(FSerializer &arc, bool skipload)
+{
+	BYTE numPlayers, numPlayersNow;
+	int i;
+
+	// Count the number of players present right now.
+	for (numPlayersNow = 0, i = 0; i < MAXPLAYERS; ++i)
+	{
+		if (playeringame[i])
+		{
+			++numPlayersNow;
+		}
+	}
+
+#if 0
+	if (arc.isWriting())
+	{
+		// Record the number of players in this save.
+		arc << numPlayersNow;
+
+		// Record each player's name, followed by their data.
+		for (i = 0; i < MAXPLAYERS; ++i)
+		{
+			if (playeringame[i])
+			{
+				arc.WriteString(players[i].userinfo.GetName());
+				players[i].Serialize(arc);
+			}
+		}
+	}
+	else
+	{
+		arc << numPlayers;
+
+		// If there is only one player in the game, they go to the
+		// first player present, no matter what their name.
+		if (numPlayers == 1)
+		{
+			ReadOnePlayer(arc, skipload);
+		}
+		else
+		{
+			ReadMultiplePlayers(arc, numPlayers, numPlayersNow, skipload);
+		}
+		if (!skipload && numPlayersNow > numPlayers)
+		{
+			SpawnExtraPlayers();
+		}
+		// Redo pitch limits, since the spawned player has them at 0.
+		players[consoleplayer].SendPitchLimits();
+	}
+#endif
+}
+
+//==========================================================================
+//
+//
+//
+//==========================================================================
+
+static void ReadOnePlayer(FSerializer &arc, bool skipload)
+{
+#if 0
+	int i;
+	char *name = NULL;
+	bool didIt = false;
+
+	arc << name;
+
+	for (i = 0; i < MAXPLAYERS; ++i)
+	{
+		if (playeringame[i])
+		{
+			if (!didIt)
+			{
+				didIt = true;
+				player_t playerTemp;
+				playerTemp.Serialize(arc);
+				if (!skipload)
+				{
+					CopyPlayer(&players[i], &playerTemp, name);
+				}
+			}
+			else
+			{
+				if (players[i].mo != NULL)
+				{
+					players[i].mo->Destroy();
+					players[i].mo = NULL;
+				}
+			}
+		}
+	}
+	delete[] name;
+#endif
+}
+
+//==========================================================================
+//
+//
+//
+//==========================================================================
+
+static void ReadMultiplePlayers(FSerializer &arc, int numPlayers, int numPlayersNow, bool skipload)
+{
+#if 0
+	// For two or more players, read each player into a temporary array.
+	int i, j;
+	char **nametemp = new char *[numPlayers];
+	player_t *playertemp = new player_t[numPlayers];
+	BYTE *tempPlayerUsed = new BYTE[numPlayers];
+	BYTE playerUsed[MAXPLAYERS];
+
+	for (i = 0; i < numPlayers; ++i)
+	{
+		nametemp[i] = NULL;
+		arc << nametemp[i];
+		playertemp[i].Serialize(arc);
+		tempPlayerUsed[i] = 0;
+	}
+	for (i = 0; i < MAXPLAYERS; ++i)
+	{
+		playerUsed[i] = playeringame[i] ? 0 : 2;
+	}
+
+	if (!skipload)
+	{
+		// Now try to match players from the savegame with players present
+		// based on their names. If two players in the savegame have the
+		// same name, then they are assigned to players in the current game
+		// on a first-come, first-served basis.
+		for (i = 0; i < numPlayers; ++i)
+		{
+			for (j = 0; j < MAXPLAYERS; ++j)
+			{
+				if (playerUsed[j] == 0 && stricmp(players[j].userinfo.GetName(), nametemp[i]) == 0)
+				{ // Found a match, so copy our temp player to the real player
+					Printf("Found player %d (%s) at %d\n", i, nametemp[i], j);
+					CopyPlayer(&players[j], &playertemp[i], nametemp[i]);
+					playerUsed[j] = 1;
+					tempPlayerUsed[i] = 1;
+					break;
+				}
+			}
+		}
+
+		// Any players that didn't have matching names are assigned to existing
+		// players on a first-come, first-served basis.
+		for (i = 0; i < numPlayers; ++i)
+		{
+			if (tempPlayerUsed[i] == 0)
+			{
+				for (j = 0; j < MAXPLAYERS; ++j)
+				{
+					if (playerUsed[j] == 0)
+					{
+						Printf("Assigned player %d (%s) to %d (%s)\n", i, nametemp[i], j, players[j].userinfo.GetName());
+						CopyPlayer(&players[j], &playertemp[i], nametemp[i]);
+						playerUsed[j] = 1;
+						tempPlayerUsed[i] = 1;
+						break;
+					}
+				}
+			}
+		}
+
+		// Make sure any extra players don't have actors spawned yet. Happens if the players
+		// present now got the same slots as they had in the save, but there are not as many
+		// as there were in the save.
+		for (j = 0; j < MAXPLAYERS; ++j)
+		{
+			if (playerUsed[j] == 0)
+			{
+				if (players[j].mo != NULL)
+				{
+					players[j].mo->Destroy();
+					players[j].mo = NULL;
+				}
+			}
+		}
+
+		// Remove any temp players that were not used. Happens if there are fewer players
+		// than there were in the save, and they got shuffled.
+		for (i = 0; i < numPlayers; ++i)
+		{
+			if (tempPlayerUsed[i] == 0)
+			{
+				playertemp[i].mo->Destroy();
+				playertemp[i].mo = NULL;
+			}
+		}
+	}
+
+	delete[] tempPlayerUsed;
+	delete[] playertemp;
+	for (i = 0; i < numPlayers; ++i)
+	{
+		delete[] nametemp[i];
+	}
+	delete[] nametemp;
+#endif
+}
+
+//==========================================================================
+//
+//
+//
+//==========================================================================
+
+void CopyPlayer(player_t *dst, player_t *src, const char *name)
+{
+	// The userinfo needs to be saved for real players, but it
+	// needs to come from the save for bots.
+	userinfo_t uibackup;
+	userinfo_t uibackup2;
+
+	uibackup.TransferFrom(dst->userinfo);
+	uibackup2.TransferFrom(src->userinfo);
+
+	int chasecam = dst->cheats & CF_CHASECAM;	// Remember the chasecam setting
+	bool attackdown = dst->attackdown;
+	bool usedown = dst->usedown;
+
+
+	*dst = *src;		// To avoid memory leaks at this point the userinfo in src must be empty which is taken care of by the TransferFrom call above.
+
+	dst->cheats |= chasecam;
+
+	if (dst->Bot != nullptr)
+	{
+		botinfo_t *thebot = bglobal.botinfo;
+		while (thebot && stricmp(name, thebot->name))
+		{
+			thebot = thebot->next;
+		}
+		if (thebot)
+		{
+			thebot->inuse = BOTINUSE_Yes;
+		}
+		bglobal.botnum++;
+		dst->userinfo.TransferFrom(uibackup2);
+	}
+	else
+	{
+		dst->userinfo.TransferFrom(uibackup);
+	}
+	// Validate the skin
+	dst->userinfo.SkinNumChanged(R_FindSkin(skins[dst->userinfo.GetSkin()].name, dst->CurrentPlayerClass));
+
+	// Make sure the player pawn points to the proper player struct.
+	if (dst->mo != nullptr)
+	{
+		dst->mo->player = dst;
+	}
+
+	// Same for the psprites.
+	DPSprite *pspr = dst->psprites;
+	while (pspr)
+	{
+		pspr->Owner = dst;
+
+		pspr = pspr->Next;
+	}
+
+	// Don't let the psprites be destroyed when src is destroyed.
+	src->psprites = nullptr;
+
+	// These 2 variables may not be overwritten.
+	dst->attackdown = attackdown;
+	dst->usedown = usedown;
+}
+
+//==========================================================================
+//
+//
+//
+//==========================================================================
+
+static void SpawnExtraPlayers()
+{
+	// If there are more players now than there were in the savegame,
+	// be sure to spawn the extra players.
+	int i;
+
+	if (deathmatch)
+	{
+		return;
+	}
+
+	for (i = 0; i < MAXPLAYERS; ++i)
+	{
+		if (playeringame[i] && players[i].mo == NULL)
+		{
+			players[i].playerstate = PST_ENTER;
+			P_SpawnPlayer(&playerstarts[i], i, (level.flags2 & LEVEL2_PRERAISEWEAPON) ? SPF_WEAPONFULLYUP : 0);
+		}
+	}
+}
+
 //============================================================================
 //
 //
@@ -838,7 +904,7 @@ void G_SerializeLevel(FSerializer &arc, bool hubload)
 		}
 	}
 
-	//Renderer->StartSerialize(arc);
+	Renderer->StartSerialize(arc);
 	if (arc.isReading())
 	{
 		P_DestroyThinkers(hubload);
@@ -894,53 +960,10 @@ void G_SerializeLevel(FSerializer &arc, bool hubload)
 	StatusBar->SerializeMessages(arc);
 	AM_SerializeMarkers(arc);
 	FRemapTable::StaticSerializeTranslations(arc);
-
-}
-
-
-
-//==========================================================================
-//
-// ArchiveSounds
-//
-//==========================================================================
-
-void P_SerializeSounds (FArchive &arc)
-{
-	S_SerializeSounds (arc);
-	//DSeqNode::SerializeSequences (arc);
-	char *name = NULL;
-	BYTE order;
-
-	if (arc.IsStoring ())
-	{
-		order = S_GetMusic (&name);
-	}
-	arc << name << order;
-	if (arc.IsLoading ())
-	{
-		if (!S_ChangeMusic (name, order))
-			if (level.cdtrack == 0 || !S_ChangeCDMusic (level.cdtrack, level.cdid))
-				S_ChangeMusic (level.Music, level.musicorder);
-	}
-	delete[] name;
-}
-
-//==========================================================================
-//
-//
-//==========================================================================
-
-void G_SerializeLevel(FArchive &arc, bool hubLoad)
-{
-#if 0
-
-	// This must be saved, too, of course!
 	FCanvasTextureInfo::Serialize(arc);
-
-	P_SerializePlayers(arc, hubLoad);
+	//P_SerializePlayers(arc, hubLoad);
 	P_SerializeSounds(arc);
-	if (arc.IsLoading())
+	if (arc.isReading())
 	{
 		for (int i = 0; i < numsectors; i++)
 		{
@@ -955,6 +978,16 @@ void G_SerializeLevel(FArchive &arc, bool hubLoad)
 		}
 	}
 	Renderer->EndSerialize(arc);
-#endif
+
+}
+
+
+//==========================================================================
+//
+//
+//==========================================================================
+
+void G_SerializeLevel(FArchive &arc, bool hubLoad)
+{
 }
 
diff --git a/src/p_saveg.h b/src/p_saveg.h
index fee2874fd9..63beb72b8b 100644
--- a/src/p_saveg.h
+++ b/src/p_saveg.h
@@ -40,9 +40,7 @@ struct PNGHandle;
 // Persistent storage/archiving.
 // These are the load / save game routines.
 // Also see farchive.(h|cpp)
-void P_SerializePlayers (FArchive &arc, bool fakeload);
 void P_DestroyThinkers(bool hubLoad);
-void P_SerializeSounds (FArchive &arc);
 
 void P_ReadACSDefereds (PNGHandle *png);
 void P_WriteACSDefereds (FILE *file);
diff --git a/src/r_defs.h b/src/r_defs.h
index 4a0f038dec..efd9fc6410 100644
--- a/src/r_defs.h
+++ b/src/r_defs.h
@@ -272,14 +272,13 @@ class ASkyViewpoint;
 
 struct secplane_t
 {
-	friend FArchive &operator<< (FArchive &arc, secplane_t &plane);
 	// the plane is defined as a*x + b*y + c*z + d = 0
 	// ic is 1/c, for faster Z calculations
 
-//private: // restore when JSON serializer is done.
 	DVector3 normal;
 	double  D, negiC;	// negative iC because that also saves a negation in all methods using this.
 public:
+	friend FSerializer &Serialize(FSerializer &arc, const char *key, secplane_t &p, secplane_t *def);
 
 	void set(double aa, double bb, double cc, double dd)
 	{
@@ -437,9 +436,6 @@ public:
 
 };
 
-FArchive &operator<< (FArchive &arc, secplane_t &plane);
-
-
 #include "p_3dfloors.h"
 // Ceiling/floor flags
 enum
@@ -544,8 +540,6 @@ struct extsector_t
 		TArray<lightlist_t>				lightlist;		// 3D light list
 		TArray<sector_t*>				attached;		// 3D floors attached to this sector
 	} XFloor;
-	
-	void Serialize(FArchive &arc);
 };
 
 struct FTransform
@@ -1033,9 +1027,6 @@ public:
 	extsector_t	*				e;		// This stores data that requires construction/destruction. Such data must not be copied by R_FakeFlat.
 };
 
-FArchive &operator<< (FArchive &arc, sector_t::splane &p);
-
-
 struct ReverbContainer;
 struct zone_t
 {
@@ -1198,8 +1189,6 @@ struct side_t
 	vertex_t *V2() const;
 };
 
-FArchive &operator<< (FArchive &arc, side_t::part &p);
-
 struct line_t
 {
 	vertex_t	*v1, *v2;	// vertices, from v1 to v2
diff --git a/src/r_renderer.h b/src/r_renderer.h
index a39520b49a..c5385aadcd 100644
--- a/src/r_renderer.h
+++ b/src/r_renderer.h
@@ -5,7 +5,7 @@
 
 struct FRenderer;
 extern FRenderer *Renderer;
-class FArchive;
+class FSerializer;
 class FTexture;
 class AActor;
 class player_t;
@@ -46,8 +46,8 @@ struct FRenderer
 	virtual void StateChanged(AActor *actor) {}
 
 	// notify the renderer that serialization of the curent level is about to start/end
-	virtual void StartSerialize(FArchive &arc) {}
-	virtual void EndSerialize(FArchive &arc) {}
+	virtual void StartSerialize(FSerializer &arc) {}
+	virtual void EndSerialize(FSerializer &arc) {}
 
 	virtual int GetMaxViewPitch(bool down) = 0;	// return value is in plain degrees
 
diff --git a/src/r_utility.cpp b/src/r_utility.cpp
index 985081a4bd..b7276fe224 100644
--- a/src/r_utility.cpp
+++ b/src/r_utility.cpp
@@ -53,7 +53,7 @@
 #include "v_font.h"
 #include "r_renderer.h"
 #include "r_data/colormaps.h"
-#include "farchive.h"
+#include "serializer.h"
 #include "r_utility.h"
 #include "d_player.h"
 #include "p_local.h"
@@ -1041,37 +1041,49 @@ void FCanvasTextureInfo::EmptyList ()
 //
 //==========================================================================
 
-void FCanvasTextureInfo::Serialize(FArchive &arc)
+void FCanvasTextureInfo::Serialize(FSerializer &arc)
 {
-#if 0
-	if (arc.IsStoring ())
+	if (arc.isWriting())
 	{
-		FCanvasTextureInfo *probe;
-
-		for (probe = List; probe != NULL; probe = probe->Next)
+		if (List != nullptr)
 		{
-			if (probe->Texture != NULL && probe->Viewpoint != NULL)
+			if (arc.BeginArray("canvastextures"))
 			{
-				arc << probe->Viewpoint << probe->FOV << probe->PicNum;
+				FCanvasTextureInfo *probe;
+
+				for (probe = List; probe != nullptr; probe = probe->Next)
+				{
+					if (probe->Texture != nullptr && probe->Viewpoint != nullptr)
+					{
+						if (arc.BeginObject(nullptr))
+						{
+							arc("viewpoint", probe->Viewpoint)
+								("fov", probe->FOV)
+								("texture", probe->PicNum)
+								.EndObject();
+						}
+					}
+				}
 			}
 		}
-		AActor *nullactor = NULL;
-		arc << nullactor;
 	}
 	else
 	{
-		AActor *viewpoint;
-		int fov;
-		FTextureID picnum;
-		
-		EmptyList ();
-		while (arc << viewpoint, viewpoint != NULL)
+		if (arc.BeginArray("canvastextures"))
 		{
-			arc << fov << picnum;
-			Add (viewpoint, picnum, fov);
+			AActor *viewpoint;
+			int fov;
+			FTextureID picnum;
+			while (arc.BeginObject(nullptr))
+			{
+				arc("viewpoint", viewpoint)
+					("fov", fov)
+					("texture", picnum)
+					.EndObject();
+				Add(viewpoint, picnum, fov);
+			}
 		}
 	}
-#endif
 }
 
 //==========================================================================
diff --git a/src/r_utility.h b/src/r_utility.h
index 4d004e835e..da9a32d117 100644
--- a/src/r_utility.h
+++ b/src/r_utility.h
@@ -3,6 +3,8 @@
 
 #include "r_state.h"
 #include "vectors.h"
+
+class FSerializer;
 //
 // Stuff from r_main.h that's needed outside the rendering code.
 
@@ -113,7 +115,7 @@ struct FCanvasTextureInfo
 	static void Add (AActor *viewpoint, FTextureID picnum, int fov);
 	static void UpdateAll ();
 	static void EmptyList ();
-	static void Serialize(FArchive &arc);
+	static void Serialize(FSerializer &arc);
 	static void Mark();
 
 private:
diff --git a/src/resourcefiles/file_zip.cpp b/src/resourcefiles/file_zip.cpp
index 0506a0f3f6..fcf5523ad8 100644
--- a/src/resourcefiles/file_zip.cpp
+++ b/src/resourcefiles/file_zip.cpp
@@ -33,7 +33,7 @@
 **
 */
 
-#include "resourcefile.h"
+#include "file_zip.h"
 #include "cmdlib.h"
 #include "templates.h"
 #include "v_text.h"
@@ -44,6 +44,69 @@
 
 #define BUFREADCOMMENT (0x400)
 
+//==========================================================================
+//
+// Decompression subroutine
+//
+//==========================================================================
+
+static bool UncompressZipLump(char *Cache, FileReader *Reader, int Method, int LumpSize, int CompressedSize, int GPFlags)
+{
+	switch (Method)
+	{
+	case METHOD_STORED:
+	{
+		Reader->Read(Cache, LumpSize);
+		break;
+	}
+
+	case METHOD_DEFLATE:
+	{
+		FileReaderZ frz(*Reader, true);
+		frz.Read(Cache, LumpSize);
+		break;
+	}
+
+	case METHOD_BZIP2:
+	{
+		FileReaderBZ2 frz(*Reader);
+		frz.Read(Cache, LumpSize);
+		break;
+	}
+
+	case METHOD_LZMA:
+	{
+		FileReaderLZMA frz(*Reader, LumpSize, true);
+		frz.Read(Cache, LumpSize);
+		break;
+	}
+
+	case METHOD_IMPLODE:
+	{
+		FZipExploder exploder;
+		exploder.Explode((unsigned char *)Cache, LumpSize, Reader, CompressedSize, GPFlags);
+		break;
+	}
+
+	case METHOD_SHRINK:
+	{
+		ShrinkLoop((unsigned char *)Cache, LumpSize, Reader, CompressedSize);
+		break;
+	}
+
+	default:
+		assert(0);
+		return false;
+	}
+	return true;
+}
+
+bool FCompressedBuffer::Decompress(char *destbuffer)
+{
+	MemoryReader mr(mBuffer, mCompressedSize);
+	return UncompressZipLump(destbuffer, &mr, mMethod, mSize, mCompressedSize, mZipFlags);
+}
+
 //-----------------------------------------------------------------------
 //
 // Finds the central directory end record in the end of the file.
@@ -96,56 +159,6 @@ static DWORD Zip_FindCentralDir(FileReader * fin)
 	return uPosFound;
 }
 
-
-enum
-{
-	LUMPFZIP_NEEDFILESTART = 128
-};
-
-//==========================================================================
-//
-// Zip Lump
-//
-//==========================================================================
-
-struct FZipLump : public FResourceLump
-{
-	WORD	GPFlags;
-	BYTE	Method;
-	int		CompressedSize;
-	int		Position;
-
-	virtual FileReader *GetReader();
-	virtual int FillCache();
-
-private:
-	void SetLumpAddress();
-	virtual int GetFileOffset() 
-	{ 
-		if (Method != METHOD_STORED) return -1;
-		if (Flags & LUMPFZIP_NEEDFILESTART) SetLumpAddress(); return Position; 
-	}
-};
-
-
-//==========================================================================
-//
-// Zip file
-//
-//==========================================================================
-
-class FZipFile : public FResourceFile
-{
-	FZipLump *Lumps;
-
-public:
-	FZipFile(const char * filename, FileReader *file);
-	virtual ~FZipFile();
-	bool Open(bool quiet);
-	virtual FResourceLump *GetLump(int no) { return ((unsigned)no < NumLumps)? &Lumps[no] : NULL; }
-};
-
-
 //==========================================================================
 //
 // Zip file
@@ -285,6 +298,24 @@ FZipFile::~FZipFile()
 	if (Lumps != NULL) delete [] Lumps;
 }
 
+//==========================================================================
+//
+//
+//
+//==========================================================================
+
+FCompressedBuffer FZipFile::GetRawLump(int lumpnum)
+{
+	if ((unsigned)lumpnum >= NumLumps)
+	{
+		return{ 0,0,0,0,nullptr };
+	}
+	FZipLump *lmp = &Lumps[lumpnum];
+	FCompressedBuffer cbuf = { (unsigned)lmp->LumpSize, (unsigned)lmp->CompressedSize, lmp->Method, lmp->GPFlags, new char[lmp->CompressedSize] };
+	Reader->Seek(lmp->Position, SEEK_SET);
+	Reader->Read(cbuf.mBuffer, lmp->CompressedSize);
+}
+
 //==========================================================================
 //
 // SetLumpAddress
@@ -348,56 +379,22 @@ int FZipLump::FillCache()
 
 	Owner->Reader->Seek(Position, SEEK_SET);
 	Cache = new char[LumpSize];
-	switch (Method)
-	{
-		case METHOD_STORED:
-		{
-			Owner->Reader->Read(Cache, LumpSize);
-			break;
-		}
-
-		case METHOD_DEFLATE:
-		{
-			FileReaderZ frz(*Owner->Reader, true);
-			frz.Read(Cache, LumpSize);
-			break;
-		}
-
-		case METHOD_BZIP2:
-		{
-			FileReaderBZ2 frz(*Owner->Reader);
-			frz.Read(Cache, LumpSize);
-			break;
-		}
-
-		case METHOD_LZMA:
-		{
-			FileReaderLZMA frz(*Owner->Reader, LumpSize, true);
-			frz.Read(Cache, LumpSize);
-			break;
-		}
-
-		case METHOD_IMPLODE:
-		{
-			FZipExploder exploder;
-			exploder.Explode((unsigned char *)Cache, LumpSize, Owner->Reader, CompressedSize, GPFlags);
-			break;
-		}
-
-		case METHOD_SHRINK:
-		{
-			ShrinkLoop((unsigned char *)Cache, LumpSize, Owner->Reader, CompressedSize);
-			break;
-		}
-
-		default:
-			assert(0);
-			return 0;
-	}
+	UncompressZipLump(Cache, Owner->Reader, Method, LumpSize, CompressedSize, GPFlags);
 	RefCount = 1;
 	return 1;
 }
 
+//==========================================================================
+//
+//
+//
+//==========================================================================
+
+int FZipLump::GetFileOffset()
+{
+	if (Method != METHOD_STORED) return -1;
+	if (Flags & LUMPFZIP_NEEDFILESTART) SetLumpAddress(); return Position;
+}
 
 //==========================================================================
 //
diff --git a/src/resourcefiles/file_zip.h b/src/resourcefiles/file_zip.h
new file mode 100644
index 0000000000..54fbde92b4
--- /dev/null
+++ b/src/resourcefiles/file_zip.h
@@ -0,0 +1,64 @@
+#ifndef __FILE_ZIP_H
+#define __FILE_ZIP_H
+
+#include "resourcefile.h"
+
+// This holds a compresed Zip entry with all needed info to decompress it.
+struct FCompressedBuffer
+{
+	unsigned mSize;
+	unsigned mCompressedSize;
+	int mMethod;
+	int mZipFlags;
+	char *mBuffer;
+	
+	bool Decompress(char *destbuffer);
+};
+
+enum
+{
+	LUMPFZIP_NEEDFILESTART = 128
+};
+
+//==========================================================================
+//
+// Zip Lump
+//
+//==========================================================================
+
+struct FZipLump : public FResourceLump
+{
+	WORD	GPFlags;
+	BYTE	Method;
+	int		CompressedSize;
+	int		Position;
+
+	virtual FileReader *GetReader();
+	virtual int FillCache();
+
+private:
+	void SetLumpAddress();
+	virtual int GetFileOffset();
+};
+
+
+//==========================================================================
+//
+// Zip file
+//
+//==========================================================================
+
+class FZipFile : public FResourceFile
+{
+	FZipLump *Lumps;
+
+public:
+	FZipFile(const char * filename, FileReader *file);
+	virtual ~FZipFile();
+	bool Open(bool quiet);
+	virtual FResourceLump *GetLump(int no) { return ((unsigned)no < NumLumps)? &Lumps[no] : NULL; }
+	FCompressedBuffer GetRawLump(int lumpnum);
+};
+
+
+#endif
\ No newline at end of file
diff --git a/src/s_sound.cpp b/src/s_sound.cpp
index 0f7d690fa6..7716a8d277 100644
--- a/src/s_sound.cpp
+++ b/src/s_sound.cpp
@@ -50,8 +50,9 @@
 #include "timidity/timidity.h"
 #include "g_level.h"
 #include "po_man.h"
-#include "farchive.h"
+#include "serializer.h"
 #include "d_player.h"
+#include "r_state.h"
 
 // MACROS ------------------------------------------------------------------
 
@@ -2208,58 +2209,41 @@ void S_StopChannel(FSoundChan *chan)
 
 //==========================================================================
 //
-// (FArchive &) << (FSoundID &)
+// 
 //
 //==========================================================================
 
-FArchive &operator<<(FArchive &arc, FSoundID &sid)
+static FSerializer &Serialize(FSerializer &arc, const char *key, FSoundChan &chan, FSoundChan *def)
 {
-	if (arc.IsStoring())
+	if (arc.BeginObject(key))
 	{
-		arc.WriteName((const char *)sid);
-	}
-	else
-	{
-		sid = arc.ReadName();
-	}
-	return arc;
-}
+		arc("sourcetype", chan.SourceType)
+			("soundid", chan.SoundID)
+			("orgid", chan.OrgID)
+			("volume", chan.Volume)
+			("distancescale", chan.DistanceScale)
+			("pitch", chan.Pitch)
+			("chanflags", chan.ChanFlags)
+			("entchannel", chan.EntChannel)
+			("priority", chan.Priority)
+			("nearlimit", chan.NearLimit)
+			("starttime", chan.StartTime.AsOne)
+			("rolloftype", chan.Rolloff.RolloffType)
+			("rolloffmin", chan.Rolloff.MinDistance)
+			("rolloffmax", chan.Rolloff.MaxDistance)
+			("limitrange", chan.LimitRange);
 
-//==========================================================================
-//
-// (FArchive &) << (FSoundChan &)
-//
-//==========================================================================
-
-static FArchive &operator<<(FArchive &arc, FSoundChan &chan)
-{
-	arc << chan.SourceType;
-#if 0
-	switch (chan.SourceType)
-	{
-	case SOURCE_None:								break;
-	case SOURCE_Actor:		arc << chan.Actor;		break;
-	case SOURCE_Sector:		arc << chan.Sector;		break;
-	case SOURCE_Polyobj:	/*arc << chan.Poly;*/		break;
-	case SOURCE_Unattached:	arc << chan.Point[0] << chan.Point[1] << chan.Point[2];	break;
-	default:				I_Error("Unknown sound source type %d\n", chan.SourceType);	break;
+		switch (chan.SourceType)
+		{
+		case SOURCE_None:										break;
+		case SOURCE_Actor:		arc("actor", chan.Actor);		break;
+		case SOURCE_Sector:		arc("sector", chan.Sector);		break;
+		case SOURCE_Polyobj:	arc("poly", chan.Poly);			break;
+		case SOURCE_Unattached:	arc.Array("point", chan.Point, 3); break;
+		default:				I_Error("Unknown sound source type %d\n", chan.SourceType);	break;
+		}
+		arc.EndObject();
 	}
-#endif
-	arc << chan.SoundID
-		<< chan.OrgID
-		<< chan.Volume
-		<< chan.DistanceScale
-		<< chan.Pitch
-		<< chan.ChanFlags
-		<< chan.EntChannel
-		<< chan.Priority
-		<< chan.NearLimit
-		<< chan.StartTime
-		<< chan.Rolloff.RolloffType
-		<< chan.Rolloff.MinDistance
-		<< chan.Rolloff.MaxDistance
-		<< chan.LimitRange;
-
 	return arc;
 }
 
@@ -2269,13 +2253,13 @@ static FArchive &operator<<(FArchive &arc, FSoundChan &chan)
 //
 //==========================================================================
 
-void S_SerializeSounds(FArchive &arc)
+void S_SerializeSounds(FSerializer &arc)
 {
 	FSoundChan *chan;
 
 	GSnd->Sync(true);
 
-	if (arc.IsStoring())
+	if (arc.isWriting())
 	{
 		TArray<FSoundChan *> chans;
 
@@ -2292,16 +2276,17 @@ void S_SerializeSounds(FArchive &arc)
 				chans.Push(chan);
 			}
 		}
-
-		arc.WriteCount(chans.Size());
-
-		for (unsigned int i = chans.Size(); i-- != 0; )
+		if (chans.Size() > 0 && arc.BeginArray("sounds"))
 		{
-			// Replace start time with sample position.
-			QWORD start = chans[i]->StartTime.AsOne;
-			chans[i]->StartTime.AsOne = GSnd ? GSnd->GetPosition(chans[i]) : 0;
-			arc << *chans[i];
-			chans[i]->StartTime.AsOne = start;
+			for (unsigned int i = chans.Size(); i-- != 0; )
+			{
+				// Replace start time with sample position.
+				QWORD start = chans[i]->StartTime.AsOne;
+				chans[i]->StartTime.AsOne = GSnd ? GSnd->GetPosition(chans[i]) : 0;
+				arc(nullptr, *chans[i]);
+				chans[i]->StartTime.AsOne = start;
+			}
+			arc.EndArray();
 		}
 	}
 	else
@@ -2309,13 +2294,17 @@ void S_SerializeSounds(FArchive &arc)
 		unsigned int count;
 
 		S_StopAllChannels();
-		count = arc.ReadCount();
-		for (unsigned int i = 0; i < count; ++i)
+		if (arc.BeginArray("sounds"))
 		{
-			chan = (FSoundChan*)S_GetChannel(NULL);
-			arc << *chan;
-			// Sounds always start out evicted when restored from a save.
-			chan->ChanFlags |= CHAN_EVICTED | CHAN_ABSTIME;
+			count = arc.ArraySize();
+			for (unsigned int i = 0; i < count; ++i)
+			{
+				chan = (FSoundChan*)S_GetChannel(NULL);
+				arc(nullptr, *chan);
+				// Sounds always start out evicted when restored from a save.
+				chan->ChanFlags |= CHAN_EVICTED | CHAN_ABSTIME;
+			}
+			arc.EndArray();
 		}
 		// The two tic delay is to make sure any screenwipes have finished.
 		// This needs to be two because the game is run for one tic before
@@ -2326,7 +2315,6 @@ void S_SerializeSounds(FArchive &arc)
 		// sounds might be heard briefly before pausing for the wipe.
 		RestartEvictionsAt = level.time + 2;
 	}
-	//DSeqNode::SerializeSequences(arc);
 	GSnd->Sync(false);
 	GSnd->UpdateSounds();
 }
diff --git a/src/s_sound.h b/src/s_sound.h
index c2e0f187eb..29b5dfb2d9 100644
--- a/src/s_sound.h
+++ b/src/s_sound.h
@@ -321,7 +321,7 @@ bool S_ChangeSoundVolume(AActor *actor, int channel, float volume);
 void S_RelinkSound (AActor *from, AActor *to);
 
 // Stores/retrieves playing channel information in an archive.
-void S_SerializeSounds(FArchive &arc);
+void S_SerializeSounds(FSerializer &arc);
 
 // Start music using <music_name>
 bool S_StartMusic (const char *music_name);
diff --git a/src/serializer.cpp b/src/serializer.cpp
index 66d23cd72d..511bc60860 100644
--- a/src/serializer.cpp
+++ b/src/serializer.cpp
@@ -30,6 +30,7 @@
 #include "g_shared/a_sharedglobal.h"
 #include "po_man.h"
 #include "v_font.h"
+#include "w_zip.h"
 
 char nulspace[1024 * 1024 * 4];
 
@@ -133,7 +134,7 @@ struct FReader
 				return &it->value;
 			}
 		}
-		else if (obj.mObject->IsArray() && obj.mIndex < obj.mObject->Size())
+		else if (obj.mObject->IsArray() && (unsigned)obj.mIndex < obj.mObject->Size())
 		{
 			return &(*obj.mObject)[obj.mIndex++];
 		}
@@ -175,8 +176,33 @@ bool FSerializer::OpenReader(const char *buffer, size_t length)
 //
 //==========================================================================
 
-void FSerializer::Close()
+bool FSerializer::OpenReader(FCompressedBuffer *input)
 {
+	if (input->mSize <= 0 || input->mBuffer == nullptr) return false;
+	if (w != nullptr || r != nullptr) return false;
+
+	if (input->mMethod == METHOD_STORED)
+	{
+		r = new FReader((char*)input->mBuffer, input->mSize);
+		return true;
+	}
+	else
+	{
+		char *unpacked = new char[input->mSize];
+		input->Decompress(unpacked);
+		r = new FReader(unpacked, input->mSize);
+		return true;
+	}
+}
+
+//==========================================================================
+//
+//
+//
+//==========================================================================
+
+void FSerializer::Close()
+{	
 	if (w != nullptr)
 	{
 		delete w;
@@ -622,8 +648,15 @@ void FSerializer::WriteObjects()
 	}
 }
 
+//==========================================================================
+//
+//
+//
+//==========================================================================
+
 const char *FSerializer::GetOutput(unsigned *len)
 {
+	if (isReading()) return nullptr;
 	if (len != nullptr)
 	{
 		*len = (unsigned)w->mOutString.GetSize();
@@ -637,6 +670,63 @@ const char *FSerializer::GetOutput(unsigned *len)
 //
 //==========================================================================
 
+FCompressedBuffer FSerializer::GetCompressedOutput()
+{
+	if (isReading()) return{ 0,0,0,0,nullptr };
+	FCompressedBuffer buff;
+	buff.mSize = (unsigned)w->mOutString.GetSize();
+	buff.mZipFlags = 0;
+
+	uint8_t *compressbuf = new uint8_t[buff.mSize+1];
+
+	z_stream stream;
+	int err;
+
+	stream.next_in = (Bytef *)w->mOutString.GetString();
+	stream.avail_in = buff.mSize;
+	stream.next_out = (Bytef*)compressbuf;
+	stream.avail_out = buff.mSize;
+	stream.zalloc = (alloc_func)0;
+	stream.zfree = (free_func)0;
+	stream.opaque = (voidpf)0;
+
+	// create output in zip-compatible form as required by FCompressedBuffer
+	err = deflateInit2(&stream, 8, Z_DEFLATED, -15, 9, Z_DEFAULT_STRATEGY);
+	if (err != Z_OK)
+	{
+		goto error;
+	}
+
+	err = deflate(&stream, Z_FINISH);
+	if (err != Z_STREAM_END) 
+	{
+		deflateEnd(&stream);
+		goto error;
+	}
+	buff.mCompressedSize = stream.total_out;
+
+	err = deflateEnd(&stream);
+	if (err == Z_OK)
+	{
+		buff.mBuffer = new char[buff.mCompressedSize];
+		buff.mMethod = METHOD_DEFLATE;
+		memcpy(buff.mBuffer, compressbuf, buff.mCompressedSize);
+		delete[] compressbuf;
+	}
+
+error:
+	memcpy(compressbuf, w->mOutString.GetString(), buff.mSize + 1);
+	buff.mCompressedSize = buff.mSize;
+	buff.mMethod = METHOD_STORED;
+	return buff;
+}
+
+//==========================================================================
+//
+//
+//
+//==========================================================================
+
 FSerializer &Serialize(FSerializer &arc, const char *key, bool &value, bool *defval)
 {
 	if (arc.isWriting())
@@ -916,6 +1006,11 @@ template<> FSerializer &Serialize(FSerializer &arc, const char *key, FPolyObj *&
 	return SerializePointer(arc, key, value, defval, polyobjs);
 }
 
+template<> FSerializer &Serialize(FSerializer &arc, const char *key, const FPolyObj *&value, const FPolyObj **defval)
+{
+	return SerializePointer<const FPolyObj>(arc, key, value, defval, polyobjs);
+}
+
 template<> FSerializer &Serialize(FSerializer &arc, const char *key, side_t *&value, side_t **defval)
 {
 	return SerializePointer(arc, key, value, defval, sides);
@@ -926,6 +1021,11 @@ template<> FSerializer &Serialize(FSerializer &arc, const char *key, sector_t *&
 	return SerializePointer(arc, key, value, defval, sectors);
 }
 
+template<> FSerializer &Serialize(FSerializer &arc, const char *key, const sector_t *&value, const sector_t **defval)
+{
+	return SerializePointer<const sector_t>(arc, key, value, defval, sectors);
+}
+
 template<> FSerializer &Serialize(FSerializer &arc, const char *key, player_t *&value, player_t **defval)
 {
 	return SerializePointer(arc, key, value, defval, players);
diff --git a/src/serializer.h b/src/serializer.h
index cec13ae1cd..7b35d4d9a8 100644
--- a/src/serializer.h
+++ b/src/serializer.h
@@ -5,6 +5,7 @@
 #include <type_traits>
 #include "tarray.h"
 #include "r_defs.h"
+#include "resourcefiles/file_zip.h"
 
 struct FWriter;
 struct FReader;
@@ -38,7 +39,6 @@ struct NumericValue
 };
 
 
-
 class FSerializer
 {
 
@@ -57,6 +57,7 @@ public:
 	}
 	bool OpenWriter();
 	bool OpenReader(const char *buffer, size_t length);
+	bool OpenReader(FCompressedBuffer *input);
 	void Close();
 	bool BeginObject(const char *name, bool randomaccess = false);
 	void EndObject();
@@ -66,6 +67,7 @@ public:
 	unsigned GetSize(const char *group);
 	const char *GetKey();
 	const char *GetOutput(unsigned *len = nullptr);
+	FCompressedBuffer GetCompressedOutput();
 	FSerializer &Args(const char *key, int *args, int *defargs, int special);
 	FSerializer &Terrain(const char *key, int &terrain, int *def = nullptr);
 	FSerializer &Sprite(const char *key, int32_t &spritenum, int32_t *def);
@@ -203,6 +205,8 @@ FSerializer &Serialize(FSerializer &arc, const char *key, TArray<T, TT> &value,
 
 template<> FSerializer &Serialize(FSerializer &arc, const char *key, FPolyObj *&value, FPolyObj **defval);
 template<> FSerializer &Serialize(FSerializer &arc, const char *key, sector_t *&value, sector_t **defval);
+template<> FSerializer &Serialize(FSerializer &arc, const char *key, const FPolyObj *&value, const FPolyObj **defval);
+template<> FSerializer &Serialize(FSerializer &arc, const char *key, const sector_t *&value, const sector_t **defval);
 template<> FSerializer &Serialize(FSerializer &arc, const char *key, player_t *&value, player_t **defval);
 template<> FSerializer &Serialize(FSerializer &arc, const char *key, line_t *&value, line_t **defval);
 template<> FSerializer &Serialize(FSerializer &arc, const char *key, side_t *&value, side_t **defval);
@@ -216,6 +220,9 @@ template<> FSerializer &Serialize(FSerializer &arc, const char *key, FDoorAnimat
 template<> FSerializer &Serialize(FSerializer &arc, const char *key, char *&pstr, char **def);
 template<> FSerializer &Serialize(FSerializer &arc, const char *key, FFont *&font, FFont **def);
 
+
+template<> FSerializer &Serialize(FSerializer &arc, const char *key, sector_t *&value, sector_t **defval);
+
 FSerializer &Serialize(FSerializer &arc, const char *key, FState *&state, FState **def, bool *retcode);
 template<> inline FSerializer &Serialize(FSerializer &arc, const char *key, FState *&state, FState **def)
 {
@@ -250,7 +257,7 @@ inline FSerializer &Serialize(FSerializer &arc, const char *key, PalEntry &pe, P
 
 inline FSerializer &Serialize(FSerializer &arc, const char *key, FRenderStyle &style, FRenderStyle *def)
 {
-	return Serialize(arc, key, style.AsDWORD, def ? &def->AsDWORD : nullptr);
+	return arc.Array(key, &style.BlendOp, def ? &def->BlendOp : nullptr, 4);
 }
 
 template<class T, class TT>