diff --git a/src/d_netcmd.c b/src/d_netcmd.c
index 8020378993655afaf0853594ed82dd48e8bb61f4..71adfdf08575f606f106c558b7d2250ac37a0c47 100644
--- a/src/d_netcmd.c
+++ b/src/d_netcmd.c
@@ -793,6 +793,8 @@ void D_RegisterClientCommands(void)
 
 	COM_AddCommand("displayplayer", Command_Displayplayer_f);
 
+	CV_RegisterVar(&cv_recordmultiplayerdemos);
+
 	// FIXME: not to be here.. but needs be done for config loading
 	CV_RegisterVar(&cv_usegamma);
 
@@ -2502,6 +2504,8 @@ static void Got_Mapcmd(UINT8 **cp, INT32 playernum)
 	LUAh_MapChange(mapnumber);
 #endif*/
 
+	demosaved = demodefersave = false;
+	demosavebutton = 0;
 	G_InitNew(pencoremode, mapname, resetplayer, skipprecutscene);
 	if (demoplayback && !timingdemo)
 		precache = true;
diff --git a/src/g_game.c b/src/g_game.c
index 864c7f43bde6d6a70459c11e6f5036762a60ff22..84b5f1ca67555d9394ed6546c58ac266aaf5c908 100644
--- a/src/g_game.c
+++ b/src/g_game.c
@@ -288,8 +288,8 @@ UINT32 timesBeatenWithEmeralds;
 //UINT32 timesBeatenUltimate;
 
 static char demoname[64];
-boolean demorecording;
-boolean demoplayback;
+boolean demorecording, demosaved, demodefersave, demoplayback;
+tic_t demosavebutton;
 boolean titledemo; // Title Screen demo can be cancelled by any key
 boolean fromtitledemo; // SRB2Kart: Don't stop the music
 static UINT8 *demobuffer = NULL;
@@ -334,6 +334,9 @@ boolean precache = true; // if true, load all graphics at start
 
 INT16 prevmap, nextmap;
 
+static CV_PossibleValue_t recordmultiplayerdemos_cons_t[] = {{0, "Disabled"}, {1, "Manual Save"}, {2, "Auto Save"}, {0, NULL}};
+consvar_t cv_recordmultiplayerdemos = {"recordmultiplayerdemos", "Manual Save", CV_SAVE, recordmultiplayerdemos_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
+
 static UINT8 *savebuffer;
 
 // Analog Control
@@ -3206,6 +3209,9 @@ void G_ExitLevel(void)
 
 		// Remove CEcho text on round end.
 		HU_ClearCEcho();
+
+		if (multiplayer && demorecording && cv_recordmultiplayerdemos.value == 2)
+			G_SaveDemo();
 	}
 }
 
@@ -6990,8 +6996,6 @@ void G_StopDemo(void)
 
 boolean G_CheckDemoStatus(void)
 {
-	boolean saved;
-
 	while (ghosts)
 	{
 		demoghost *next = ghosts->next;
@@ -7040,33 +7044,39 @@ boolean G_CheckDemoStatus(void)
 		return true;
 	}
 
-	if (demorecording)
+	if (demorecording && (!multiplayer || cv_recordmultiplayerdemos.value == 2))
 	{
-		UINT8 *p = demobuffer+16; // checksum position
+		G_SaveDemo();
+		return true;
+	}
+	demorecording = false;
+
+	return false;
+}
+
+void G_SaveDemo(void)
+{
+	UINT8 *p = demobuffer+16; // checksum position
 #ifdef NOMD5
-		UINT8 i;
-		WRITEUINT8(demo_p, DEMOMARKER); // add the demo end marker
-		for (i = 0; i < 16; i++, p++)
-			*p = P_RandomByte(); // This MD5 was chosen by fair dice roll and most likely < 50% correct.
+	UINT8 i;
+	WRITEUINT8(demo_p, DEMOMARKER); // add the demo end marker
+	for (i = 0; i < 16; i++, p++)
+		*p = P_RandomByte(); // This MD5 was chosen by fair dice roll and most likely < 50% correct.
 #else
-		WRITEUINT8(demo_p, DEMOMARKER); // add the demo end marker
-		md5_buffer((char *)p+16, demo_p - (p+16), p); // make a checksum of everything after the checksum in the file.
+	WRITEUINT8(demo_p, DEMOMARKER); // add the demo end marker
+	md5_buffer((char *)p+16, demo_p - (p+16), p); // make a checksum of everything after the checksum in the file.
 #endif
-		saved = FIL_WriteFile(va(pandf, srb2home, demoname), demobuffer, demo_p - demobuffer); // finally output the file.
-		free(demobuffer);
-		demorecording = false;
+	demosaved = FIL_WriteFile(va(pandf, srb2home, demoname), demobuffer, demo_p - demobuffer); // finally output the file.
+	free(demobuffer);
+	demorecording = false;
 
-		if (modeattacking != ATTACKING_RECORD)
-		{
-			if (saved)
-				CONS_Printf(M_GetText("Demo %s recorded\n"), demoname);
-			else
-				CONS_Alert(CONS_WARNING, M_GetText("Demo %s not saved\n"), demoname);
-		}
-		return true;
+	if (modeattacking != ATTACKING_RECORD)
+	{
+		if (demosaved)
+			CONS_Printf(M_GetText("Demo %s recorded\n"), demoname);
+		else
+			CONS_Alert(CONS_WARNING, M_GetText("Demo %s not saved\n"), demoname);
 	}
-
-	return false;
 }
 
 //
diff --git a/src/g_game.h b/src/g_game.h
index 73dcb1ce0f257b546a0bf1746c14f6f566dd5da1..458f166b7f681d27ece1a34811b4ac1e7bfb7ad5 100644
--- a/src/g_game.h
+++ b/src/g_game.h
@@ -36,7 +36,9 @@ extern boolean playeringame[MAXPLAYERS];
 // ======================================
 
 // demoplaying back and demo recording
-extern boolean demoplayback, titledemo, fromtitledemo, demorecording, timingdemo;
+extern boolean demoplayback, titledemo, fromtitledemo, demorecording, timingdemo, demosaved, demodefersave;
+extern tic_t demosavebutton;
+extern consvar_t cv_recordmultiplayerdemos;
 
 // Quit after playing a demo from cmdline.
 extern boolean singledemo;
@@ -207,6 +209,7 @@ void G_StopMetalDemo(void);
 ATTRNORETURN void FUNCNORETURN G_StopMetalRecording(void);
 void G_StopDemo(void);
 boolean G_CheckDemoStatus(void);
+void G_SaveDemo(void);
 
 boolean G_IsSpecialStage(INT32 mapnum);
 boolean G_GametypeUsesLives(void);
diff --git a/src/p_tick.c b/src/p_tick.c
index 7e048af59f3b2b3bce734173cdfd93b664136826..f5d5d5b20a3b9da2bf432201ab7e385fa79af817 100644
--- a/src/p_tick.c
+++ b/src/p_tick.c
@@ -13,6 +13,7 @@
 
 #include "doomstat.h"
 #include "g_game.h"
+#include "g_input.h"
 #include "p_local.h"
 #include "z_zone.h"
 #include "s_sound.h"
@@ -737,6 +738,9 @@ void P_Ticker(boolean run)
 
 					G_WriteGhostTic(players[i].mo, i);
 				}
+
+				if (demosavebutton && demosavebutton + 3*TICRATE < leveltime && InputDown(gc_lookback, 1))
+					demodefersave = true;
 			}
 			if (demoplayback) // Use Ghost data for consistency checks.
 			{
diff --git a/src/p_user.c b/src/p_user.c
index 73caa21119d8de48a8238ecb18b08e21e1153fb4..d51c18af10b76c0252b33d90ee69a921f68c69b0 100644
--- a/src/p_user.c
+++ b/src/p_user.c
@@ -1835,6 +1835,9 @@ void P_DoPlayerExit(player_t *player)
 	player->powers[pw_spacetime] = 0;
 	player->kartstuff[k_cardanimation] = 0; // srb2kart: reset battle animation
 
+	if (player == &players[consoleplayer])
+		demosavebutton = leveltime;
+
 	/*if (playeringame[player-players] && netgame && !circuitmap)
 		CONS_Printf(M_GetText("%s has completed the level.\n"), player_names[player-players]);*/
 }
diff --git a/src/y_inter.c b/src/y_inter.c
index 0ca175078469170562120ee21a014e0852f9704d..995cddfe1af7be6b1084e6253a1abef76fa8f28f 100644
--- a/src/y_inter.c
+++ b/src/y_inter.c
@@ -562,6 +562,11 @@ dotimer:
 			string);
 	}
 
+	if (demorecording && cv_recordmultiplayerdemos.value == 1)
+		V_DrawCenteredString(BASEVIDWIDTH/2, 178, V_ALLOWLOWERCASE|hilicol, "Press Look Backward to save the replay");
+	else if (demosaved)
+		V_DrawCenteredString(BASEVIDWIDTH/2, 178, V_ALLOWLOWERCASE|hilicol, "Replay saved!");
+
 	// Make it obvious that scrambling is happening next round.
 	if (cv_scrambleonchange.value && cv_teamscramble.value && (intertic/TICRATE % 2 == 0))
 		V_DrawCenteredString(BASEVIDWIDTH/2, BASEVIDHEIGHT/2, hilicol, M_GetText("Teams will be scrambled next round!"));
@@ -577,6 +582,12 @@ void Y_Ticker(void)
 	if (intertype == int_none)
 		return;
 
+	if (demorecording && cv_recordmultiplayerdemos.value == 1 && (demodefersave || InputDown(gc_lookback, 1)))
+	{
+		demodefersave = false;
+		G_SaveDemo();
+	}
+
 	// Check for pause or menu up in single player
 	if (paused || P_AutoPause())
 		return;