diff --git a/src/hu_stuff.c b/src/hu_stuff.c
index 1e5e47df69e91ab6b9067f091fcbc7043c4b767c..dc4dfe4b767df022b8f93339d5a1b376456a73cb 100644
--- a/src/hu_stuff.c
+++ b/src/hu_stuff.c
@@ -21,6 +21,7 @@
 
 #include "netcode/d_clisrv.h"
 #include "netcode/net_command.h"
+#include "netcode/gamestate.h"
 
 #include "g_game.h"
 #include "g_input.h"
diff --git a/src/netcode/Sourcefile b/src/netcode/Sourcefile
index 1039b218a6d9a50f1e3955eff23061d8ea6671ad..c590503678c8a8c263f22d5923dda9e2a500dd85 100644
--- a/src/netcode/Sourcefile
+++ b/src/netcode/Sourcefile
@@ -3,6 +3,7 @@ server_connection.c
 client_connection.c
 tic_command.c
 net_command.c
+gamestate.c
 d_net.c
 d_netcmd.c
 d_netfil.c
diff --git a/src/netcode/d_clisrv.c b/src/netcode/d_clisrv.c
index b834c92f5f85fdce87f73585dc3b0c6f4f70cc41..8ba10f6ea6472ce5cfbd8b34c573d3e732903180 100644
--- a/src/netcode/d_clisrv.c
+++ b/src/netcode/d_clisrv.c
@@ -53,6 +53,7 @@
 #include "client_connection.h"
 #include "tic_command.h"
 #include "net_command.h"
+#include "gamestate.h"
 #include "protocol.h"
 
 //
@@ -93,18 +94,12 @@ tic_t maketic;
 
 INT16 consistancy[BACKUPTICS];
 
-UINT8 hu_redownloadinggamestate = 0;
-
 // true when a player is connecting or disconnecting so that the gameplay has stopped in its tracks
 boolean hu_stopped = false;
 
 UINT8 adminpassmd5[16];
 boolean adminpasswordset = false;
 
-// Client specific
-// here it is for the secondary local player (splitscreen)
-static boolean cl_redownloadinggamestate = false;
-
 tic_t neededtic;
 SINT8 servernode = 0; // the number of the server node
 
@@ -122,233 +117,6 @@ consvar_t cv_playbackspeed = CVAR_INIT ("playbackspeed", "1", 0, playbackspeed_c
 // of 512 bytes is like 0.1)
 UINT16 software_MAXPACKETLENGTH;
 
-#define SAVEGAMESIZE (768*1024)
-
-boolean SV_ResendingSavegameToAnyone(void)
-{
-	INT32 i;
-
-	for (i = 0; i < MAXNETNODES; i++)
-		if (netnodes[i].resendingsavegame)
-			return true;
-	return false;
-}
-
-void SV_SendSaveGame(INT32 node, boolean resending)
-{
-	size_t length, compressedlen;
-	UINT8 *savebuffer;
-	UINT8 *compressedsave;
-	UINT8 *buffertosend;
-
-	// first save it in a malloced buffer
-	savebuffer = (UINT8 *)malloc(SAVEGAMESIZE);
-	if (!savebuffer)
-	{
-		CONS_Alert(CONS_ERROR, M_GetText("No more free memory for savegame\n"));
-		return;
-	}
-
-	// Leave room for the uncompressed length.
-	save_p = savebuffer + sizeof(UINT32);
-
-	P_SaveNetGame(resending);
-
-	length = save_p - savebuffer;
-	if (length > SAVEGAMESIZE)
-	{
-		free(savebuffer);
-		save_p = NULL;
-		I_Error("Savegame buffer overrun");
-	}
-
-	// Allocate space for compressed save: one byte fewer than for the
-	// uncompressed data to ensure that the compression is worthwhile.
-	compressedsave = malloc(length - 1);
-	if (!compressedsave)
-	{
-		CONS_Alert(CONS_ERROR, M_GetText("No more free memory for savegame\n"));
-		return;
-	}
-
-	// Attempt to compress it.
-	if((compressedlen = lzf_compress(savebuffer + sizeof(UINT32), length - sizeof(UINT32), compressedsave + sizeof(UINT32), length - sizeof(UINT32) - 1)))
-	{
-		// Compressing succeeded; send compressed data
-
-		free(savebuffer);
-
-		// State that we're compressed.
-		buffertosend = compressedsave;
-		WRITEUINT32(compressedsave, length - sizeof(UINT32));
-		length = compressedlen + sizeof(UINT32);
-	}
-	else
-	{
-		// Compression failed to make it smaller; send original
-
-		free(compressedsave);
-
-		// State that we're not compressed
-		buffertosend = savebuffer;
-		WRITEUINT32(savebuffer, 0);
-	}
-
-	AddRamToSendQueue(node, buffertosend, length, SF_RAM, 0);
-	save_p = NULL;
-
-	// Remember when we started sending the savegame so we can handle timeouts
-	netnodes[node].sendingsavegame = true;
-	netnodes[node].freezetimeout = I_GetTime() + jointimeout + length / 1024; // 1 extra tic for each kilobyte
-}
-
-#ifdef DUMPCONSISTENCY
-#define TMPSAVENAME "badmath.sav"
-static consvar_t cv_dumpconsistency = CVAR_INIT ("dumpconsistency", "Off", CV_SAVE|CV_NETVAR, CV_OnOff, NULL);
-
-static void SV_SavedGame(void)
-{
-	size_t length;
-	UINT8 *savebuffer;
-	char tmpsave[256];
-
-	if (!cv_dumpconsistency.value)
-		return;
-
-	sprintf(tmpsave, "%s" PATHSEP TMPSAVENAME, srb2home);
-
-	// first save it in a malloced buffer
-	save_p = savebuffer = (UINT8 *)malloc(SAVEGAMESIZE);
-	if (!save_p)
-	{
-		CONS_Alert(CONS_ERROR, M_GetText("No more free memory for savegame\n"));
-		return;
-	}
-
-	P_SaveNetGame(false);
-
-	length = save_p - savebuffer;
-	if (length > SAVEGAMESIZE)
-	{
-		free(savebuffer);
-		save_p = NULL;
-		I_Error("Savegame buffer overrun");
-	}
-
-	// then save it!
-	if (!FIL_WriteFile(tmpsave, savebuffer, length))
-		CONS_Printf(M_GetText("Didn't save %s for netgame"), tmpsave);
-
-	free(savebuffer);
-	save_p = NULL;
-}
-
-#undef  TMPSAVENAME
-#endif
-#define TMPSAVENAME "$$$.sav"
-
-
-void CL_LoadReceivedSavegame(boolean reloading)
-{
-	UINT8 *savebuffer = NULL;
-	size_t length, decompressedlen;
-	char tmpsave[256];
-
-	FreeFileNeeded();
-
-	sprintf(tmpsave, "%s" PATHSEP TMPSAVENAME, srb2home);
-
-	length = FIL_ReadFile(tmpsave, &savebuffer);
-
-	CONS_Printf(M_GetText("Loading savegame length %s\n"), sizeu1(length));
-	if (!length)
-	{
-		I_Error("Can't read savegame sent");
-		return;
-	}
-
-	save_p = savebuffer;
-
-	// Decompress saved game if necessary.
-	decompressedlen = READUINT32(save_p);
-	if(decompressedlen > 0)
-	{
-		UINT8 *decompressedbuffer = Z_Malloc(decompressedlen, PU_STATIC, NULL);
-		lzf_decompress(save_p, length - sizeof(UINT32), decompressedbuffer, decompressedlen);
-		Z_Free(savebuffer);
-		save_p = savebuffer = decompressedbuffer;
-	}
-
-	paused = false;
-	demoplayback = false;
-	titlemapinaction = TITLEMAP_OFF;
-	titledemo = false;
-	automapactive = false;
-
-	P_StopRumble(NULL);
-
-	// load a base level
-	if (P_LoadNetGame(reloading))
-	{
-		const UINT8 actnum = mapheaderinfo[gamemap-1]->actnum;
-		CONS_Printf(M_GetText("Map is now \"%s"), G_BuildMapName(gamemap));
-		if (strcmp(mapheaderinfo[gamemap-1]->lvlttl, ""))
-		{
-			CONS_Printf(": %s", mapheaderinfo[gamemap-1]->lvlttl);
-			if (!(mapheaderinfo[gamemap-1]->levelflags & LF_NOZONE))
-				CONS_Printf(M_GetText(" Zone"));
-			if (actnum > 0)
-				CONS_Printf(" %2d", actnum);
-		}
-		CONS_Printf("\"\n");
-	}
-
-	// done
-	Z_Free(savebuffer);
-	save_p = NULL;
-	if (unlink(tmpsave) == -1)
-		CONS_Alert(CONS_ERROR, M_GetText("Can't delete %s\n"), tmpsave);
-	consistancy[gametic%BACKUPTICS] = Consistancy();
-	CON_ToggleOff();
-
-	// Tell the server we have received and reloaded the gamestate
-	// so they know they can resume the game
-	netbuffer->packettype = PT_RECEIVEDGAMESTATE;
-	HSendPacket(servernode, true, 0, 0);
-}
-
-static void CL_ReloadReceivedSavegame(void)
-{
-	INT32 i;
-
-	for (i = 0; i < MAXPLAYERS; i++)
-	{
-		LUA_InvalidatePlayer(&players[i]);
-		sprintf(player_names[i], "Player %d", i + 1);
-	}
-
-	CL_LoadReceivedSavegame(true);
-
-	if (neededtic < gametic)
-		neededtic = gametic;
-	maketic = neededtic;
-
-	ticcmd_oldangleturn[0] = players[consoleplayer].oldrelangleturn;
-	P_ForceLocalAngle(&players[consoleplayer], (angle_t)(players[consoleplayer].angleturn << 16));
-	if (splitscreen)
-	{
-		ticcmd_oldangleturn[1] = players[secondarydisplayplayer].oldrelangleturn;
-		P_ForceLocalAngle(&players[secondarydisplayplayer], (angle_t)(players[secondarydisplayplayer].angleturn << 16));
-	}
-
-	camera.subsector = R_PointInSubsector(camera.x, camera.y);
-	camera2.subsector = R_PointInSubsector(camera2.x, camera2.y);
-
-	cl_redownloadinggamestate = false;
-
-	CONS_Printf(M_GetText("Game state reloaded\n"));
-}
-
 typedef struct banreason_s
 {
 	char *reason;
@@ -1006,34 +774,6 @@ static void Command_Kick(void)
 		CONS_Printf(M_GetText("Only the server or a remote admin can use this.\n"));
 }
 
-static void Command_ResendGamestate(void)
-{
-	SINT8 playernum;
-
-	if (COM_Argc() == 1)
-	{
-		CONS_Printf(M_GetText("resendgamestate <playername/playernum>: resend the game state to a player\n"));
-		return;
-	}
-	else if (client)
-	{
-		CONS_Printf(M_GetText("Only the server can use this.\n"));
-		return;
-	}
-
-	playernum = nametonum(COM_Argv(1));
-	if (playernum == -1 || playernum == 0)
-		return;
-
-	// Send a PT_WILLRESENDGAMESTATE packet to the client so they know what's going on
-	netbuffer->packettype = PT_WILLRESENDGAMESTATE;
-	if (!HSendPacket(playernode[playernum], true, 0, 0))
-	{
-		CONS_Alert(CONS_ERROR, M_GetText("A problem occured, please try again.\n"));
-		return;
-	}
-}
-
 static void Got_KickCmd(UINT8 **p, INT32 playernum)
 {
 	INT32 pnum, msg;
@@ -1736,17 +1476,6 @@ static void PT_ClientQuit(SINT8 node, INT32 netconsole)
 	netnodes[node].ingame = false;
 }
 
-static void PT_CanReceiveGamestate(SINT8 node)
-{
-	if (client || netnodes[node].sendingsavegame)
-		return;
-
-	CONS_Printf(M_GetText("Resending game state to %s...\n"), player_names[netnodes[node].player]);
-
-	SV_SendSaveGame(node, true); // Resend a complete game state
-	netnodes[node].resendingsavegame = true;
-}
-
 static void PT_AskLuaFile(SINT8 node)
 {
 	if (server && luafiletransfers && luafiletransfers->nodestatus[node] == LFTNS_ASKED)
@@ -1759,13 +1488,6 @@ static void PT_HasLuaFile(SINT8 node)
 		SV_HandleLuaFileSent(node);
 }
 
-static void PT_ReceivedGamestate(SINT8 node)
-{
-	netnodes[node].sendingsavegame = false;
-	netnodes[node].resendingsavegame = false;
-	netnodes[node].savegameresendcooldown = I_GetTime() + 5 * TICRATE;
-}
-
 static void PT_Ping(SINT8 node, INT32 netconsole)
 {
 	// Only accept PT_PING from the server.
@@ -1789,34 +1511,6 @@ static void PT_Ping(SINT8 node, INT32 netconsole)
 	}
 }
 
-static void PT_WillResendGamestate(SINT8 node)
-{
-	(void)node;
-
-	char tmpsave[256];
-
-	if (server || cl_redownloadinggamestate)
-		return;
-
-	// Send back a PT_CANRECEIVEGAMESTATE packet to the server
-	// so they know they can start sending the game state
-	netbuffer->packettype = PT_CANRECEIVEGAMESTATE;
-	if (!HSendPacket(servernode, true, 0, 0))
-		return;
-
-	CONS_Printf(M_GetText("Reloading game state...\n"));
-
-	sprintf(tmpsave, "%s" PATHSEP TMPSAVENAME, srb2home);
-
-	// Don't get a corrupt savegame error because tmpsave already exists
-	if (FIL_FileExists(tmpsave) && unlink(tmpsave) == -1)
-		I_Error("Can't delete %s\n", tmpsave);
-
-	CL_PrepareDownloadSaveGame(tmpsave);
-
-	cl_redownloadinggamestate = true;
-}
-
 static void PT_SendingLuaFile(SINT8 node)
 {
 	(void)node;
diff --git a/src/netcode/d_clisrv.h b/src/netcode/d_clisrv.h
index 691a2787f8b39ab1002100515ca80293fc54e4e2..d5bafe750d1ecfee6efc456d82c5a50d27b27797 100644
--- a/src/netcode/d_clisrv.h
+++ b/src/netcode/d_clisrv.h
@@ -89,19 +89,16 @@ void NetUpdate(void);
 void GetPackets(void);
 void ResetNode(INT32 node);
 INT16 Consistancy(void);
-boolean SV_ResendingSavegameToAnyone(void);
 
 void SV_StartSinglePlayerServer(void);
 void SV_SpawnServer(void);
 void SV_StopServer(void);
 void SV_ResetServer(void);
-void SV_SendSaveGame(INT32 node, boolean resending);
 void CL_AddSplitscreenPlayer(void);
 void CL_RemoveSplitscreenPlayer(void);
 void CL_Reset(void);
 void CL_ClearPlayer(INT32 playernum);
 void CL_RemovePlayer(INT32 playernum, kickreason_t reason);
-void CL_LoadReceivedSavegame(boolean reloading);
 // Is there a game running
 boolean Playing(void);
 
@@ -130,8 +127,6 @@ tic_t GetLag(INT32 node);
 
 void D_MD5PasswordPass(const UINT8 *buffer, size_t len, const char *salt, void *dest);
 
-extern UINT8 hu_redownloadinggamestate;
-
 extern UINT8 adminpassmd5[16];
 extern boolean adminpasswordset;
 
diff --git a/src/netcode/gamestate.c b/src/netcode/gamestate.c
new file mode 100644
index 0000000000000000000000000000000000000000..4a3e9f3af8c2e75d8898e438677e0419fc5bc027
--- /dev/null
+++ b/src/netcode/gamestate.c
@@ -0,0 +1,341 @@
+// SONIC ROBO BLAST 2
+//-----------------------------------------------------------------------------
+// Copyright (C) 1998-2000 by DooM Legacy Team.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
+//
+// This program is free software distributed under the
+// terms of the GNU General Public License, version 2.
+// See the 'LICENSE' file for more details.
+//-----------------------------------------------------------------------------
+/// \file  gamestate.c
+/// \brief Gamestate (re)sending
+
+#include "d_clisrv.h"
+#include "d_netfil.h"
+#include "gamestate.h"
+#include "i_net.h"
+#include "protocol.h"
+#include "server_connection.h"
+#include "../am_map.h"
+#include "../byteptr.h"
+#include "../console.h"
+#include "../d_main.h"
+#include "../doomstat.h"
+#include "../doomtype.h"
+#include "../f_finale.h"
+#include "../g_demo.h"
+#include "../g_game.h"
+#include "../i_time.h"
+#include "../lua_script.h"
+#include "../lzf.h"
+#include "../m_misc.h"
+#include "../p_haptic.h"
+#include "../p_local.h"
+#include "../p_saveg.h"
+#include "../r_main.h"
+#include "../tables.h"
+#include "../z_zone.h"
+
+#define SAVEGAMESIZE (768*1024)
+
+UINT8 hu_redownloadinggamestate = 0;
+boolean cl_redownloadinggamestate = false;
+
+boolean SV_ResendingSavegameToAnyone(void)
+{
+	INT32 i;
+
+	for (i = 0; i < MAXNETNODES; i++)
+		if (netnodes[i].resendingsavegame)
+			return true;
+	return false;
+}
+
+void SV_SendSaveGame(INT32 node, boolean resending)
+{
+	size_t length, compressedlen;
+	UINT8 *savebuffer;
+	UINT8 *compressedsave;
+	UINT8 *buffertosend;
+
+	// first save it in a malloced buffer
+	savebuffer = (UINT8 *)malloc(SAVEGAMESIZE);
+	if (!savebuffer)
+	{
+		CONS_Alert(CONS_ERROR, M_GetText("No more free memory for savegame\n"));
+		return;
+	}
+
+	// Leave room for the uncompressed length.
+	save_p = savebuffer + sizeof(UINT32);
+
+	P_SaveNetGame(resending);
+
+	length = save_p - savebuffer;
+	if (length > SAVEGAMESIZE)
+	{
+		free(savebuffer);
+		save_p = NULL;
+		I_Error("Savegame buffer overrun");
+	}
+
+	// Allocate space for compressed save: one byte fewer than for the
+	// uncompressed data to ensure that the compression is worthwhile.
+	compressedsave = malloc(length - 1);
+	if (!compressedsave)
+	{
+		CONS_Alert(CONS_ERROR, M_GetText("No more free memory for savegame\n"));
+		return;
+	}
+
+	// Attempt to compress it.
+	if((compressedlen = lzf_compress(savebuffer + sizeof(UINT32), length - sizeof(UINT32), compressedsave + sizeof(UINT32), length - sizeof(UINT32) - 1)))
+	{
+		// Compressing succeeded; send compressed data
+
+		free(savebuffer);
+
+		// State that we're compressed.
+		buffertosend = compressedsave;
+		WRITEUINT32(compressedsave, length - sizeof(UINT32));
+		length = compressedlen + sizeof(UINT32);
+	}
+	else
+	{
+		// Compression failed to make it smaller; send original
+
+		free(compressedsave);
+
+		// State that we're not compressed
+		buffertosend = savebuffer;
+		WRITEUINT32(savebuffer, 0);
+	}
+
+	AddRamToSendQueue(node, buffertosend, length, SF_RAM, 0);
+	save_p = NULL;
+
+	// Remember when we started sending the savegame so we can handle timeouts
+	netnodes[node].sendingsavegame = true;
+	netnodes[node].freezetimeout = I_GetTime() + jointimeout + length / 1024; // 1 extra tic for each kilobyte
+}
+
+#ifdef DUMPCONSISTENCY
+#define TMPSAVENAME "badmath.sav"
+static consvar_t cv_dumpconsistency = CVAR_INIT ("dumpconsistency", "Off", CV_SAVE|CV_NETVAR, CV_OnOff, NULL);
+
+void SV_SavedGame(void)
+{
+	size_t length;
+	UINT8 *savebuffer;
+	char tmpsave[256];
+
+	if (!cv_dumpconsistency.value)
+		return;
+
+	sprintf(tmpsave, "%s" PATHSEP TMPSAVENAME, srb2home);
+
+	// first save it in a malloced buffer
+	save_p = savebuffer = (UINT8 *)malloc(SAVEGAMESIZE);
+	if (!save_p)
+	{
+		CONS_Alert(CONS_ERROR, M_GetText("No more free memory for savegame\n"));
+		return;
+	}
+
+	P_SaveNetGame(false);
+
+	length = save_p - savebuffer;
+	if (length > SAVEGAMESIZE)
+	{
+		free(savebuffer);
+		save_p = NULL;
+		I_Error("Savegame buffer overrun");
+	}
+
+	// then save it!
+	if (!FIL_WriteFile(tmpsave, savebuffer, length))
+		CONS_Printf(M_GetText("Didn't save %s for netgame"), tmpsave);
+
+	free(savebuffer);
+	save_p = NULL;
+}
+
+#undef  TMPSAVENAME
+#endif
+#define TMPSAVENAME "$$$.sav"
+
+
+void CL_LoadReceivedSavegame(boolean reloading)
+{
+	UINT8 *savebuffer = NULL;
+	size_t length, decompressedlen;
+	char tmpsave[256];
+
+	FreeFileNeeded();
+
+	sprintf(tmpsave, "%s" PATHSEP TMPSAVENAME, srb2home);
+
+	length = FIL_ReadFile(tmpsave, &savebuffer);
+
+	CONS_Printf(M_GetText("Loading savegame length %s\n"), sizeu1(length));
+	if (!length)
+	{
+		I_Error("Can't read savegame sent");
+		return;
+	}
+
+	save_p = savebuffer;
+
+	// Decompress saved game if necessary.
+	decompressedlen = READUINT32(save_p);
+	if(decompressedlen > 0)
+	{
+		UINT8 *decompressedbuffer = Z_Malloc(decompressedlen, PU_STATIC, NULL);
+		lzf_decompress(save_p, length - sizeof(UINT32), decompressedbuffer, decompressedlen);
+		Z_Free(savebuffer);
+		save_p = savebuffer = decompressedbuffer;
+	}
+
+	paused = false;
+	demoplayback = false;
+	titlemapinaction = TITLEMAP_OFF;
+	titledemo = false;
+	automapactive = false;
+
+	P_StopRumble(NULL);
+
+	// load a base level
+	if (P_LoadNetGame(reloading))
+	{
+		const UINT8 actnum = mapheaderinfo[gamemap-1]->actnum;
+		CONS_Printf(M_GetText("Map is now \"%s"), G_BuildMapName(gamemap));
+		if (strcmp(mapheaderinfo[gamemap-1]->lvlttl, ""))
+		{
+			CONS_Printf(": %s", mapheaderinfo[gamemap-1]->lvlttl);
+			if (!(mapheaderinfo[gamemap-1]->levelflags & LF_NOZONE))
+				CONS_Printf(M_GetText(" Zone"));
+			if (actnum > 0)
+				CONS_Printf(" %2d", actnum);
+		}
+		CONS_Printf("\"\n");
+	}
+
+	// done
+	Z_Free(savebuffer);
+	save_p = NULL;
+	if (unlink(tmpsave) == -1)
+		CONS_Alert(CONS_ERROR, M_GetText("Can't delete %s\n"), tmpsave);
+	consistancy[gametic%BACKUPTICS] = Consistancy();
+	CON_ToggleOff();
+
+	// Tell the server we have received and reloaded the gamestate
+	// so they know they can resume the game
+	netbuffer->packettype = PT_RECEIVEDGAMESTATE;
+	HSendPacket(servernode, true, 0, 0);
+}
+
+void CL_ReloadReceivedSavegame(void)
+{
+	INT32 i;
+
+	for (i = 0; i < MAXPLAYERS; i++)
+	{
+		LUA_InvalidatePlayer(&players[i]);
+		sprintf(player_names[i], "Player %d", i + 1);
+	}
+
+	CL_LoadReceivedSavegame(true);
+
+	if (neededtic < gametic)
+		neededtic = gametic;
+	maketic = neededtic;
+
+	ticcmd_oldangleturn[0] = players[consoleplayer].oldrelangleturn;
+	P_ForceLocalAngle(&players[consoleplayer], (angle_t)(players[consoleplayer].angleturn << 16));
+	if (splitscreen)
+	{
+		ticcmd_oldangleturn[1] = players[secondarydisplayplayer].oldrelangleturn;
+		P_ForceLocalAngle(&players[secondarydisplayplayer], (angle_t)(players[secondarydisplayplayer].angleturn << 16));
+	}
+
+	camera.subsector = R_PointInSubsector(camera.x, camera.y);
+	camera2.subsector = R_PointInSubsector(camera2.x, camera2.y);
+
+	cl_redownloadinggamestate = false;
+
+	CONS_Printf(M_GetText("Game state reloaded\n"));
+}
+
+void Command_ResendGamestate(void)
+{
+	SINT8 playernum;
+
+	if (COM_Argc() == 1)
+	{
+		CONS_Printf(M_GetText("resendgamestate <playername/playernum>: resend the game state to a player\n"));
+		return;
+	}
+	else if (client)
+	{
+		CONS_Printf(M_GetText("Only the server can use this.\n"));
+		return;
+	}
+
+	playernum = nametonum(COM_Argv(1));
+	if (playernum == -1 || playernum == 0)
+		return;
+
+	// Send a PT_WILLRESENDGAMESTATE packet to the client so they know what's going on
+	netbuffer->packettype = PT_WILLRESENDGAMESTATE;
+	if (!HSendPacket(playernode[playernum], true, 0, 0))
+	{
+		CONS_Alert(CONS_ERROR, M_GetText("A problem occured, please try again.\n"));
+		return;
+	}
+}
+
+void PT_CanReceiveGamestate(SINT8 node)
+{
+	if (client || netnodes[node].sendingsavegame)
+		return;
+
+	CONS_Printf(M_GetText("Resending game state to %s...\n"), player_names[netnodes[node].player]);
+
+	SV_SendSaveGame(node, true); // Resend a complete game state
+	netnodes[node].resendingsavegame = true;
+}
+
+void PT_ReceivedGamestate(SINT8 node)
+{
+	netnodes[node].sendingsavegame = false;
+	netnodes[node].resendingsavegame = false;
+	netnodes[node].savegameresendcooldown = I_GetTime() + 5 * TICRATE;
+}
+
+void PT_WillResendGamestate(SINT8 node)
+{
+	(void)node;
+
+	char tmpsave[256];
+
+	if (server || cl_redownloadinggamestate)
+		return;
+
+	// Send back a PT_CANRECEIVEGAMESTATE packet to the server
+	// so they know they can start sending the game state
+	netbuffer->packettype = PT_CANRECEIVEGAMESTATE;
+	if (!HSendPacket(servernode, true, 0, 0))
+		return;
+
+	CONS_Printf(M_GetText("Reloading game state...\n"));
+
+	sprintf(tmpsave, "%s" PATHSEP TMPSAVENAME, srb2home);
+
+	// Don't get a corrupt savegame error because tmpsave already exists
+	if (FIL_FileExists(tmpsave) && unlink(tmpsave) == -1)
+		I_Error("Can't delete %s\n", tmpsave);
+
+	CL_PrepareDownloadSaveGame(tmpsave);
+
+	cl_redownloadinggamestate = true;
+}
diff --git a/src/netcode/gamestate.h b/src/netcode/gamestate.h
new file mode 100644
index 0000000000000000000000000000000000000000..9d277977220fd171e95ae0de33a2ea215c3c30b1
--- /dev/null
+++ b/src/netcode/gamestate.h
@@ -0,0 +1,31 @@
+// SONIC ROBO BLAST 2
+//-----------------------------------------------------------------------------
+// Copyright (C) 1998-2000 by DooM Legacy Team.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
+//
+// This program is free software distributed under the
+// terms of the GNU General Public License, version 2.
+// See the 'LICENSE' file for more details.
+//-----------------------------------------------------------------------------
+/// \file  gamestate.h
+/// \brief Gamestate (re)sending
+
+#ifndef __GAMESTATE__
+#define __GAMESTATE__
+
+#include "../doomtype.h"
+
+extern UINT8 hu_redownloadinggamestate;
+extern boolean cl_redownloadinggamestate;
+
+boolean SV_ResendingSavegameToAnyone(void);
+void SV_SendSaveGame(INT32 node, boolean resending);
+void SV_SavedGame(void);
+void CL_LoadReceivedSavegame(boolean reloading);
+void CL_ReloadReceivedSavegame(void);
+void Command_ResendGamestate(void);
+void PT_CanReceiveGamestate(SINT8 node);
+void PT_ReceivedGamestate(SINT8 node);
+void PT_WillResendGamestate(SINT8 node);
+
+#endif
diff --git a/src/netcode/net_command.c b/src/netcode/net_command.c
index eeb479d21fe52c690451b0f8890f5a4cb2a888e4..6f9f3f79ad4c7e47b7bc5f01a4d92cddb6741161 100644
--- a/src/netcode/net_command.c
+++ b/src/netcode/net_command.c
@@ -12,6 +12,7 @@
 
 #include "net_command.h"
 #include "tic_command.h"
+#include "gamestate.h"
 #include "d_clisrv.h"
 #include "i_net.h"
 #include "../g_game.h"
diff --git a/src/netcode/server_connection.c b/src/netcode/server_connection.c
index b776db4228b6290da7a96587890fd2c6aaa5dc27..28d97c0524a6eea9881e87f0e824d3df7c3aafd6 100644
--- a/src/netcode/server_connection.c
+++ b/src/netcode/server_connection.c
@@ -16,6 +16,7 @@
 #include "d_netfil.h"
 #include "mserv.h"
 #include "net_command.h"
+#include "gamestate.h"
 #include "../byteptr.h"
 #include "../g_game.h"
 #include "../g_state.h"
diff --git a/src/netcode/tic_command.c b/src/netcode/tic_command.c
index e7df895ffb3b1f272db7ed493b3f3f2d5c221f44..180d54e69c5c131689615fe4784d34bc77f52883 100644
--- a/src/netcode/tic_command.c
+++ b/src/netcode/tic_command.c
@@ -14,6 +14,7 @@
 #include "d_clisrv.h"
 #include "net_command.h"
 #include "client_connection.h"
+#include "gamestate.h"
 #include "i_net.h"
 #include "../d_main.h"
 #include "../g_game.h"