diff --git a/src/console.c b/src/console.c
index a172aa412418341a073bdcbcf58283816610de6f..aac94d473c7cd2469c3700bd9f25cdf3c6b7d961 100644
--- a/src/console.c
+++ b/src/console.c
@@ -770,7 +770,7 @@ boolean CON_Responder(event_t *ev)
 	// check for console toggle key
 	if (ev->type != ev_console)
 	{
-		if (modeattacking || metalrecording)
+		if (modeattacking || metalrecording || marathonmode)
 			return false;
 
 		if (key == gamecontrol[gc_console][0] || key == gamecontrol[gc_console][1])
diff --git a/src/d_main.c b/src/d_main.c
index 6057e65a770056ae74ff92dd9e05a83fa5decb37..28570b90938eaea055bc24b1b058c0ee49b13c6e 100644
--- a/src/d_main.c
+++ b/src/d_main.c
@@ -129,6 +129,7 @@ UINT16 numskincolors;
 menucolor_t *menucolorhead, *menucolortail;
 
 char savegamename[256];
+char liveeventbackup[256];
 
 char srb2home[256] = ".";
 char srb2path[256] = ".";
@@ -896,6 +897,7 @@ void D_StartTitle(void)
 	// In case someone exits out at the same time they start a time attack run,
 	// reset modeattacking
 	modeattacking = ATTACKING_NONE;
+	marathonmode = 0;
 
 	// empty maptol so mario/etc sounds don't play in sound test when they shouldn't
 	maptol = 0;
@@ -1212,6 +1214,7 @@ void D_SRB2Main(void)
 
 	// default savegame
 	strcpy(savegamename, SAVEGAMENAME"%u.ssg");
+	strcpy(liveeventbackup, "live"SAVEGAMENAME".bkp"); // intentionally not ending with .ssg
 
 	{
 		const char *userhome = D_Home(); //Alam: path to home
@@ -1240,6 +1243,7 @@ void D_SRB2Main(void)
 
 			// can't use sprintf since there is %u in savegamename
 			strcatbf(savegamename, srb2home, PATHSEP);
+			strcatbf(liveeventbackup, srb2home, PATHSEP);
 
 			snprintf(luafiledir, sizeof luafiledir, "%s" PATHSEP "luafiles", srb2home);
 #else // DEFAULTDIR
@@ -1252,6 +1256,7 @@ void D_SRB2Main(void)
 
 			// can't use sprintf since there is %u in savegamename
 			strcatbf(savegamename, userhome, PATHSEP);
+			strcatbf(liveeventbackup, userhome, PATHSEP);
 
 			snprintf(luafiledir, sizeof luafiledir, "%s" PATHSEP "luafiles", userhome);
 #endif // DEFAULTDIR
diff --git a/src/dehacked.c b/src/dehacked.c
index 88aaeac5473a0a3328cc432946316774f93af6fe..61775bd56413113287fddbe2d038f31123072a4c 100644
--- a/src/dehacked.c
+++ b/src/dehacked.c
@@ -1669,6 +1669,22 @@ static void readlevelheader(MYFILE *f, INT32 num)
 
 				mapheaderinfo[num-1]->nextlevel = (INT16)i;
 			}
+			else if (fastcmp(word, "MARATHONNEXT"))
+			{
+				if      (fastcmp(word2, "TITLE"))      i = 1100;
+				else if (fastcmp(word2, "EVALUATION")) i = 1101;
+				else if (fastcmp(word2, "CREDITS"))    i = 1102;
+				else if (fastcmp(word2, "ENDING"))     i = 1103;
+				else
+				// Support using the actual map name,
+				// i.e., MarathonNext = AB, MarathonNext = FZ, etc.
+
+				// Convert to map number
+				if (word2[0] >= 'A' && word2[0] <= 'Z' && word2[2] == '\0')
+					i = M_MapNumber(word2[0], word2[1]);
+
+				mapheaderinfo[num-1]->marathonnext = (INT16)i;
+			}
 			else if (fastcmp(word, "TYPEOFLEVEL"))
 			{
 				if (i) // it's just a number
@@ -4011,7 +4027,20 @@ static void readmaincfg(MYFILE *f)
 				else
 					value = get_number(word2);
 
-				spstage_start = (INT16)value;
+				spstage_start = spmarathon_start = (INT16)value;
+			}
+			else if (fastcmp(word, "SPMARATHON_START"))
+			{
+				// Support using the actual map name,
+				// i.e., Level AB, Level FZ, etc.
+
+				// Convert to map number
+				if (word2[0] >= 'A' && word2[0] <= 'Z')
+					value = M_MapNumber(word2[0], word2[1]);
+				else
+					value = get_number(word2);
+
+				spmarathon_start = (INT16)value;
 			}
 			else if (fastcmp(word, "SSTAGE_START"))
 			{
@@ -4105,6 +4134,17 @@ static void readmaincfg(MYFILE *f)
 					introtoplay = 128;
 				introchanged = true;
 			}
+			else if (fastcmp(word, "CREDITSCUTSCENE"))
+			{
+				creditscutscene = (UINT8)get_number(word2);
+				// range check, you morons.
+				if (creditscutscene > 128)
+					creditscutscene = 128;
+			}
+			else if (fastcmp(word, "USEBLACKROCK"))
+			{
+				useBlackRock = (UINT8)(value || word2[0] == 'T' || word2[0] == 'Y');
+			}
 			else if (fastcmp(word, "LOOPTITLE"))
 			{
 				looptitle = (value || word2[0] == 'T' || word2[0] == 'Y');
@@ -4206,13 +4246,6 @@ static void readmaincfg(MYFILE *f)
 				titlescrollyspeed = get_number(word2);
 				titlechanged = true;
 			}
-			else if (fastcmp(word, "CREDITSCUTSCENE"))
-			{
-				creditscutscene = (UINT8)get_number(word2);
-				// range check, you morons.
-				if (creditscutscene > 128)
-					creditscutscene = 128;
-			}
 			else if (fastcmp(word, "DISABLESPEEDADJUST"))
 			{
 				disableSpeedAdjust = (value || word2[0] == 'T' || word2[0] == 'Y');
@@ -4269,6 +4302,9 @@ static void readmaincfg(MYFILE *f)
 				// can't use sprintf since there is %u in savegamename
 				strcatbf(savegamename, srb2home, PATHSEP);
 
+				strcpy(liveeventbackup, va("live%s.bkp", timeattackfolder));
+				strcatbf(liveeventbackup, srb2home, PATHSEP);
+
 				gamedataadded = true;
 				titlechanged = true;
 			}
@@ -9996,6 +10032,12 @@ struct {
 	{"TC_BLINK",TC_BLINK},
 	{"TC_DASHMODE",TC_DASHMODE},
 
+	// marathonmode flags
+	{"MA_INIT",MA_INIT},
+	{"MA_RUNNING",MA_RUNNING},
+	{"MA_NOCUTSCENES",MA_NOCUTSCENES},
+	{"MA_INGAME",MA_INGAME},
+
 	{NULL,0}
 };
 
diff --git a/src/djgppdos/i_video.c b/src/djgppdos/i_video.c
index f525b96ca6b19475148b8c22b213e21e88b3c8b0..d2483e318654f692d68a5270979c8c11c65326a7 100644
--- a/src/djgppdos/i_video.c
+++ b/src/djgppdos/i_video.c
@@ -90,6 +90,9 @@ static   unsigned long  nombre = NEWTICRATE*10;
 static void I_BlitScreenVesa1(void);   //see later
 void I_FinishUpdate (void)
 {
+	if (marathonmode)
+		SCR_DisplayMarathonInfo();
+
 	// draw captions if enabled
 	if (cv_closedcaptioning.value)
 		SCR_ClosedCaptions();
diff --git a/src/doomdef.h b/src/doomdef.h
index 64bf217d34fea0e30935a3bd55cfc21b4aa23f35..0b76cebc7e298b52d4d4377ce86fca1c3daaa32c 100644
--- a/src/doomdef.h
+++ b/src/doomdef.h
@@ -479,6 +479,7 @@ void CONS_Debug(INT32 debugflags, const char *fmt, ...) FUNCDEBUG;
 // Things that used to be in dstrings.h
 #define SAVEGAMENAME "srb2sav"
 extern char savegamename[256];
+extern char liveeventbackup[256];
 
 // m_misc.h
 #ifdef GETTEXT
diff --git a/src/doomstat.h b/src/doomstat.h
index fa346540cf6bb3606c53cc8c90ed048258cbe75d..3abaf298882beec742fe5bc3fb64beb64dd3ce41 100644
--- a/src/doomstat.h
+++ b/src/doomstat.h
@@ -45,7 +45,19 @@ extern INT32 curWeather;
 extern INT32 cursaveslot;
 //extern INT16 lastmapsaved;
 extern INT16 lastmaploaded;
-extern boolean gamecomplete;
+extern UINT8 gamecomplete;
+
+// Extra abilities/settings for skins (combinable stuff)
+typedef enum
+{
+	MA_RUNNING     = 1,    // In action
+	MA_INIT        = 1<<1, // Initialisation
+	MA_NOCUTSCENES = 1<<2, // No cutscenes
+	MA_INGAME      = 1<<3  // Timer ignores loads
+} marathonmode_t;
+
+extern marathonmode_t marathonmode;
+extern tic_t marathontime;
 
 #define maxgameovers 13
 extern UINT8 numgameovers;
@@ -127,7 +139,7 @@ extern INT32 displayplayer;
 extern INT32 secondarydisplayplayer; // for splitscreen
 
 // Maps of special importance
-extern INT16 spstage_start;
+extern INT16 spstage_start, spmarathon_start;
 extern INT16 sstage_start, sstage_end, smpstage_start, smpstage_end;
 
 extern INT16 titlemap;
@@ -289,6 +301,7 @@ typedef struct
 	UINT8 actnum;          ///< Act number or 0 for none.
 	UINT32 typeoflevel;    ///< Combination of typeoflevel flags.
 	INT16 nextlevel;       ///< Map number of next level, or 1100-1102 to end.
+	INT16 marathonnext;    ///< See nextlevel, but for Marathon mode. Necessary to support hub worlds ala SUGOI.
 	char keywords[33];     ///< Keywords separated by space to search for. 32 characters.
 	char musname[7];       ///< Music track to play. "" for no music.
 	UINT16 mustrack;       ///< Subsong to play. Only really relevant for music modules and specific formats supported by GME. 0 to ignore.
@@ -575,11 +588,12 @@ extern UINT16 nightslinktics;
 
 extern UINT8 introtoplay;
 extern UINT8 creditscutscene;
+extern UINT8 useBlackRock;
 
 extern UINT8 use1upSound;
 extern UINT8 maxXtraLife; // Max extra lives from rings
 extern UINT8 useContinues;
-#define continuesInSession (!multiplayer && (useContinues || ultimatemode || !(cursaveslot > 0)))
+#define continuesInSession (!multiplayer && (ultimatemode || (useContinues && !marathonmode) || (!modeattacking && !(cursaveslot > 0))))
 
 extern mobj_t *hunt1, *hunt2, *hunt3; // Emerald hunt locations
 
diff --git a/src/f_finale.c b/src/f_finale.c
index 8b5a69ae4a2390419856a9366240e5eb20674c6c..a41005b43c38c3dfcb1f6bc2681f39e548086410 100644
--- a/src/f_finale.c
+++ b/src/f_finale.c
@@ -1330,10 +1330,6 @@ void F_StartCredits(void)
 	// Just in case they're open ... somehow
 	M_ClearMenus(true);
 
-	// Save the second we enter the credits
-	if ((!modifiedgame || savemoddata) && !(netgame || multiplayer) && cursaveslot > 0)
-		G_SaveGame((UINT32)cursaveslot);
-
 	if (creditscutscene)
 	{
 		F_StartCustomCutscene(creditscutscene - 1, false, false);
@@ -1529,12 +1525,6 @@ void F_StartGameEvaluation(void)
 	// Just in case they're open ... somehow
 	M_ClearMenus(true);
 
-	// Save the second we enter the evaluation
-	// We need to do this again!  Remember, it's possible a mod designed skipped
-	// the credits sequence!
-	if ((!modifiedgame || savemoddata) && !(netgame || multiplayer) && cursaveslot > 0)
-		G_SaveGame((UINT32)cursaveslot);
-
 	goodending = (ALL7EMERALDS(emeralds));
 
 	gameaction = ga_nothing;
@@ -1551,13 +1541,20 @@ void F_GameEvaluationDrawer(void)
 	angle_t fa;
 	INT32 eemeralds_cur;
 	char patchname[7] = "CEMGx0";
-	const char* endingtext = (goodending ? "CONGRATULATIONS!" : "TRY AGAIN...");
+	const char* endingtext;
+
+	if (marathonmode)
+		endingtext = "THANKS FOR THE RUN!";
+	else if (goodending)
+		endingtext = "CONGRATULATIONS!";
+	else
+		endingtext = "TRY AGAIN...";
 
 	V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, 31);
 
 	// Draw all the good crap here.
 
-	if (finalecount > 0)
+	if (finalecount > 0 && useBlackRock)
 	{
 		INT32 scale = FRACUNIT;
 		patch_t *rockpat;
@@ -1684,7 +1681,9 @@ void F_GameEvaluationTicker(void)
 		return;
 	}
 
-	if (!goodending)
+	if (!useBlackRock)
+		;
+	else if (!goodending)
 	{
 		if (sparklloop)
 			sparklloop--;
@@ -1841,10 +1840,6 @@ void F_StartEnding(void)
 	// Just in case they're open ... somehow
 	M_ClearMenus(true);
 
-	// Save before the credits sequence.
-	if ((!modifiedgame || savemoddata) && !(netgame || multiplayer) && cursaveslot > 0)
-		G_SaveGame((UINT32)cursaveslot);
-
 	gameaction = ga_nothing;
 	paused = false;
 	CON_ToggleOff();
@@ -3959,6 +3954,7 @@ static void F_AdvanceToNextScene(void)
 	animtimer = pictime = cutscenes[cutnum]->scene[scenenum].picduration[picnum];
 }
 
+// See also G_AfterIntermission, the only other place which handles intra-map/ending transitions
 void F_EndCutScene(void)
 {
 	cutsceneover = true; // do this first, just in case G_EndGame or something wants to turn it back false later
diff --git a/src/g_game.c b/src/g_game.c
index 2aa11e0db5673efe13d257c30797516a5b4343b9..73d21ad54e87f85b24b6aa933542bb436bebd460 100644
--- a/src/g_game.c
+++ b/src/g_game.c
@@ -82,7 +82,10 @@ INT32 curWeather = PRECIP_NONE;
 INT32 cursaveslot = 0; // Auto-save 1p savegame slot
 //INT16 lastmapsaved = 0; // Last map we auto-saved at
 INT16 lastmaploaded = 0; // Last map the game loaded
-boolean gamecomplete = false;
+UINT8 gamecomplete = 0;
+
+marathonmode_t marathonmode = 0;
+tic_t marathontime = 0;
 
 UINT8 numgameovers = 0; // for startinglives balance
 SINT8 startinglivesbalance[maxgameovers+1] = {3, 5, 7, 9, 12, 15, 20, 25, 30, 40, 50, 75, 99, 0x7F};
@@ -118,7 +121,7 @@ UINT32 ssspheres; // old special stage
 INT16 lastmap; // last level you were at (returning from special stages)
 tic_t timeinmap; // Ticker for time spent in level (used for levelcard display)
 
-INT16 spstage_start;
+INT16 spstage_start, spmarathon_start;
 INT16 sstage_start, sstage_end, smpstage_start, smpstage_end;
 
 INT16 titlemap = 0;
@@ -223,6 +226,7 @@ UINT8 useContinues = 0; // Set to 1 to enable continues outside of no-save scena
 
 UINT8 introtoplay;
 UINT8 creditscutscene;
+UINT8 useBlackRock = 1;
 
 // Emerald locations
 mobj_t *hunt1;
@@ -769,6 +773,8 @@ void G_SetGameModified(boolean silent)
 	// If in record attack recording, cancel it.
 	if (modeattacking)
 		M_EndModeAttackRun();
+	else if (marathonmode)
+		Command_ExitGame_f();
 }
 
 /** Builds an original game map name from a map number.
@@ -2179,6 +2185,10 @@ void G_Ticker(boolean run)
 	UINT32 i;
 	INT32 buf;
 
+	// see also SCR_DisplayMarathonInfo
+	if ((marathonmode & (MA_INIT|MA_INGAME)) == MA_INGAME && gamestate == GS_LEVEL)
+		marathontime++;
+
 	P_MapStart();
 	// do player reborns if needed
 	if (gamestate == GS_LEVEL)
@@ -2195,8 +2205,13 @@ void G_Ticker(boolean run)
 			}
 			else
 			{
-				// Costs a life to retry ... unless the player in question is dead already.
-				if (G_GametypeUsesLives() && players[consoleplayer].playerstate == PST_LIVE && players[consoleplayer].lives != INFLIVES)
+				// Costs a life to retry ... unless the player in question is dead already, or you haven't even touched the first starpost in marathon run.
+				if (marathonmode && gamemap == spmarathon_start && !players[consoleplayer].starposttime)
+				{
+					marathonmode |= MA_INIT;
+					marathontime = 0;
+				}
+				else if (G_GametypeUsesLives() && players[consoleplayer].playerstate == PST_LIVE && players[consoleplayer].lives != INFLIVES)
 					players[consoleplayer].lives -= 1;
 
 				G_DoReborn(consoleplayer);
@@ -2963,7 +2978,7 @@ void G_DoReborn(INT32 playernum)
 				players[i].starpostnum = 0;
 			}
 		}
-		if (!countdowntimeup && (mapheaderinfo[gamemap-1]->levelflags & LF_NORELOAD))
+		if (!countdowntimeup && (mapheaderinfo[gamemap-1]->levelflags & LF_NORELOAD) && !(marathonmode & MA_INIT))
 		{
 			P_RespawnThings();
 
@@ -3696,8 +3711,14 @@ static void G_DoCompleted(void)
 	// nextmap is 0-based, unlike gamemap
 	if (nextmapoverride != 0)
 		nextmap = (INT16)(nextmapoverride-1);
+	else if (marathonmode && mapheaderinfo[gamemap-1]->marathonnext)
+		nextmap = (INT16)(mapheaderinfo[gamemap-1]->marathonnext-1);
 	else
+	{
 		nextmap = (INT16)(mapheaderinfo[gamemap-1]->nextlevel-1);
+		if (marathonmode && nextmap == spmarathon_start-1)
+			nextmap = 1100-1; // No infinite loop for you
+	}
 
 	// Remember last map for when you come out of the special stage.
 	if (!spec)
@@ -3721,10 +3742,12 @@ static void G_DoCompleted(void)
 			visitedmap[cm/8] |= (1<<(cm&7));
 			if (!mapheaderinfo[cm])
 				cm = -1; // guarantee error execution
+			else if (marathonmode && mapheaderinfo[cm]->marathonnext)
+				cm = (INT16)(mapheaderinfo[cm]->marathonnext-1);
 			else
 				cm = (INT16)(mapheaderinfo[cm]->nextlevel-1);
 
-			if (cm >= NUMMAPS || cm < 0) // out of range (either 1100-1102 or error)
+			if (cm >= NUMMAPS || cm < 0) // out of range (either 1100ish or error)
 			{
 				cm = nextmap; //Start the loop again so that the error checking below is executed.
 
@@ -3793,6 +3816,25 @@ static void G_DoCompleted(void)
 	if (nextmap < NUMMAPS && !mapheaderinfo[nextmap])
 		P_AllocMapHeader(nextmap);
 
+	// do this before going to the intermission or starting a 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))
+				G_SaveGame((UINT32)cursaveslot);
+		}
+	}
+
 	if ((skipstats && !modeattacking) || (spec && modeattacking && stagefailed))
 	{
 		G_UpdateVisited();
@@ -3806,6 +3848,7 @@ static void G_DoCompleted(void)
 	}
 }
 
+// See also F_EndCutscene, the only other place which handles intra-map/ending transitions
 void G_AfterIntermission(void)
 {
 	Y_CleanupScreenBuffer();
@@ -3816,9 +3859,12 @@ void G_AfterIntermission(void)
 		return;
 	}
 
+	if (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
+		gamecomplete = 1;
+
 	HU_ClearCEcho();
 
-	if ((gametyperules & GTR_CUTSCENES) && mapheaderinfo[gamemap-1]->cutscenenum && !modeattacking && skipstats <= 1) // Start a custom cutscene.
+	if ((gametyperules & GTR_CUTSCENES) && mapheaderinfo[gamemap-1]->cutscenenum && !modeattacking && skipstats <= 1 && !(marathonmode & MA_NOCUTSCENES)) // Start a custom cutscene.
 		F_StartCustomCutscene(mapheaderinfo[gamemap-1]->cutscenenum-1, false, false);
 	else
 	{
@@ -3959,7 +4005,7 @@ void G_EndGame(void)
 void G_LoadGameSettings(void)
 {
 	// defaults
-	spstage_start = 1;
+	spstage_start = spmarathon_start = 1;
 	sstage_start = 50;
 	sstage_end = 56; // 7 special stages in vanilla SRB2
 	sstage_end++; // plus one weirdo
@@ -4279,7 +4325,10 @@ void G_LoadGame(UINT32 slot, INT16 mapoverride)
 	startonmapnum = mapoverride;
 #endif
 
-	sprintf(savename, savegamename, slot);
+	if (marathonmode)
+		strcpy(savename, liveeventbackup);
+	else
+		sprintf(savename, savegamename, slot);
 
 	length = FIL_ReadFile(savename, &savebuffer);
 	if (!length)
@@ -4291,7 +4340,7 @@ void G_LoadGame(UINT32 slot, INT16 mapoverride)
 	save_p = savebuffer;
 
 	memset(vcheck, 0, sizeof (vcheck));
-	sprintf(vcheck, "version %d", VERSION);
+	sprintf(vcheck, (marathonmode ? "back-up %d" : "version %d"), VERSION);
 	if (strcmp((const char *)save_p, (const char *)vcheck))
 	{
 #ifdef SAVEGAME_OTHERVERSIONS
@@ -4331,6 +4380,11 @@ void G_LoadGame(UINT32 slot, INT16 mapoverride)
 		memset(&savedata, 0, sizeof(savedata));
 		return;
 	}
+	if (marathonmode)
+	{
+		marathontime = READUINT32(save_p);
+		marathonmode |= READUINT8(save_p);
+	}
 
 	// done
 	Z_Free(savebuffer);
@@ -4359,13 +4413,12 @@ void G_SaveGame(UINT32 slot)
 	char savename[256] = "";
 	const char *backup;
 
-	sprintf(savename, savegamename, slot);
+	if (marathonmode)
+		strcpy(savename, liveeventbackup);
+	else
+		sprintf(savename, savegamename, slot);
 	backup = va("%s",savename);
 
-	// save during evaluation or credits? game's over, folks!
-	if (gamestate == GS_ENDING || gamestate == GS_CREDITS || gamestate == GS_EVALUATION)
-		gamecomplete = true;
-
 	gameaction = ga_nothing;
 	{
 		char name[VERSIONSIZE];
@@ -4379,10 +4432,15 @@ void G_SaveGame(UINT32 slot)
 		}
 
 		memset(name, 0, sizeof (name));
-		sprintf(name, "version %d", VERSION);
+		sprintf(name, (marathonmode ? "back-up %d" : "version %d"), VERSION);
 		WRITEMEM(save_p, name, VERSIONSIZE);
 
 		P_SaveGame();
+		if (marathonmode)
+		{
+			WRITEUINT32(save_p, marathontime);
+			WRITEUINT8(save_p, (marathonmode & ~MA_INIT));
+		}
 
 		length = save_p - savebuffer;
 		saved = FIL_WriteFile(backup, savebuffer, length);
@@ -4395,7 +4453,7 @@ void G_SaveGame(UINT32 slot)
 	if (cv_debug && saved)
 		CONS_Printf(M_GetText("Game saved.\n"));
 	else if (!saved)
-		CONS_Alert(CONS_ERROR, M_GetText("Error while writing to %s for save slot %u, base: %s\n"), backup, slot, savegamename);
+		CONS_Alert(CONS_ERROR, M_GetText("Error while writing to %s for save slot %u, base: %s\n"), backup, slot, (marathonmode ? liveeventbackup : savegamename));
 }
 
 #define BADSAVE goto cleanup;
@@ -4408,7 +4466,10 @@ void G_SaveGameOver(UINT32 slot, boolean modifylives)
 	char savename[255];
 	const char *backup;
 
-	sprintf(savename, savegamename, slot);
+	if (marathonmode)
+		strcpy(savename, liveeventbackup);
+	else
+		sprintf(savename, savegamename, slot);
 	backup = va("%s",savename);
 
 	length = FIL_ReadFile(savename, &savebuffer);
@@ -4427,7 +4488,7 @@ void G_SaveGameOver(UINT32 slot, boolean modifylives)
 		save_p = savebuffer;
 		// Version check
 		memset(vcheck, 0, sizeof (vcheck));
-		sprintf(vcheck, "version %d", VERSION);
+		sprintf(vcheck, (marathonmode ? "back-up %d" : "version %d"), VERSION);
 		if (strcmp((const char *)save_p, (const char *)vcheck)) BADSAVE
 		save_p += VERSIONSIZE;
 
@@ -4494,7 +4555,7 @@ cleanup:
 	if (cv_debug && saved)
 		CONS_Printf(M_GetText("Game saved.\n"));
 	else if (!saved)
-		CONS_Alert(CONS_ERROR, M_GetText("Error while writing to %s for save slot %u, base: %s\n"), backup, slot, savegamename);
+		CONS_Alert(CONS_ERROR, M_GetText("Error while writing to %s for save slot %u, base: %s\n"), backup, slot, (marathonmode ? liveeventbackup : savegamename));
 	Z_Free(savebuffer);
 	save_p = savebuffer = NULL;
 
@@ -4632,7 +4693,7 @@ void G_InitNew(UINT8 pultmode, const char *mapname, boolean resetplayer, boolean
 	automapactive = false;
 	imcontinuing = false;
 
-	if ((gametyperules & GTR_CUTSCENES) && !skipprecutscene && mapheaderinfo[gamemap-1]->precutscenenum && !modeattacking) // Start a custom cutscene.
+	if ((gametyperules & GTR_CUTSCENES) && !skipprecutscene && mapheaderinfo[gamemap-1]->precutscenenum && !modeattacking && !(marathonmode & MA_NOCUTSCENES)) // Start a custom cutscene.
 		F_StartCustomCutscene(mapheaderinfo[gamemap-1]->precutscenenum-1, true, resetplayer);
 	else
 		G_DoLoadLevel(resetplayer);
diff --git a/src/hardware/hw_draw.c b/src/hardware/hw_draw.c
index 5d58d4b203d149325ce9314493c97d97ffba3f67..3d20cd9b077ea9e0fe06328f00f9385a5da6b244 100644
--- a/src/hardware/hw_draw.c
+++ b/src/hardware/hw_draw.c
@@ -891,7 +891,11 @@ void HWR_DrawTutorialBack(UINT32 color, INT32 boxheight)
 {
 	FOutVector  v[4];
 	FSurfaceInfo Surf;
-	INT32 height = (boxheight * 4) + (boxheight/2)*5; // 4 lines of space plus gaps between and some leeway
+	INT32 height;
+	if (boxheight < 0)
+		height = -boxheight;
+	else
+		height = (boxheight * 4) + (boxheight/2)*5; // 4 lines of space plus gaps between and some leeway
 
 	// setup some neat-o translucency effect
 
diff --git a/src/lua_maplib.c b/src/lua_maplib.c
index ece42b8d3dec950b8c6a904eb6370715963f2cd7..a90b83c996d5a8d70f512241d2a8a34713bea401 100644
--- a/src/lua_maplib.c
+++ b/src/lua_maplib.c
@@ -2009,6 +2009,8 @@ static int mapheaderinfo_get(lua_State *L)
 		lua_pushinteger(L, header->typeoflevel);
 	else if (fastcmp(field,"nextlevel"))
 		lua_pushinteger(L, header->nextlevel);
+	else if (fastcmp(field,"marathonnext"))
+		lua_pushinteger(L, header->marathonnext);
 	else if (fastcmp(field,"keywords"))
 		lua_pushstring(L, header->keywords);
 	else if (fastcmp(field,"musname"))
diff --git a/src/lua_script.c b/src/lua_script.c
index 374294d6eb065ae21a226b0a314f69bc47f6aa34..a295256dae6437a9ab00e1626ee1e8d6b64f74bd 100644
--- a/src/lua_script.c
+++ b/src/lua_script.c
@@ -167,7 +167,10 @@ int LUA_PushGlobals(lua_State *L, const char *word)
 		lua_pushboolean(L, splitscreen);
 		return 1;
 	} else if (fastcmp(word,"gamecomplete")) {
-		lua_pushboolean(L, gamecomplete);
+		lua_pushboolean(L, (gamecomplete != 0));
+		return 1;
+	} else if (fastcmp(word,"marathonmode")) {
+		lua_pushinteger(L, marathonmode);
 		return 1;
 	} else if (fastcmp(word,"devparm")) {
 		lua_pushboolean(L, devparm);
@@ -197,6 +200,9 @@ int LUA_PushGlobals(lua_State *L, const char *word)
 	} else if (fastcmp(word,"spstage_start")) {
 		lua_pushinteger(L, spstage_start);
 		return 1;
+	} else if (fastcmp(word,"spmarathon_start")) {
+		lua_pushinteger(L, spmarathon_start);
+		return 1;
 	} else if (fastcmp(word,"sstage_start")) {
 		lua_pushinteger(L, sstage_start);
 		return 1;
diff --git a/src/m_menu.c b/src/m_menu.c
index e4cc189be8a4fff29b4a80f2c72c1e267c9839b1..61b41d75b89293f08f6697adb39d657e7d5c054f 100644
--- a/src/m_menu.c
+++ b/src/m_menu.c
@@ -182,6 +182,7 @@ static tic_t keydown = 0;
 
 static void M_GoBack(INT32 choice);
 static void M_StopMessage(INT32 choice);
+static boolean stopstopmessage = false;
 
 #ifndef NONET
 static void M_HandleServerPage(INT32 choice);
@@ -252,6 +253,7 @@ static void M_ConfirmTeamScramble(INT32 choice);
 static void M_ConfirmTeamChange(INT32 choice);
 static void M_SecretsMenu(INT32 choice);
 static void M_SetupChoosePlayer(INT32 choice);
+static UINT8 M_SetupChoosePlayerDirect(INT32 choice);
 static void M_QuitSRB2(INT32 choice);
 menu_t SP_MainDef, OP_MainDef;
 menu_t MISC_ScrambleTeamDef, MISC_ChangeTeamDef;
@@ -272,9 +274,14 @@ static void M_ModeAttackEndGame(INT32 choice);
 static void M_SetGuestReplay(INT32 choice);
 static void M_HandleChoosePlayerMenu(INT32 choice);
 static void M_ChoosePlayer(INT32 choice);
+static void M_MarathonLiveEventBackup(INT32 choice);
+static void M_Marathon(INT32 choice);
+static void M_HandleMarathonChoosePlayer(INT32 choice);
+static void M_StartMarathon(INT32 choice);
 menu_t SP_LevelStatsDef;
 static menu_t SP_TimeAttackDef, SP_ReplayDef, SP_GuestReplayDef, SP_GhostDef;
 static menu_t SP_NightsAttackDef, SP_NightsReplayDef, SP_NightsGuestReplayDef, SP_NightsGhostDef;
+static menu_t SP_MarathonDef;
 
 // Multiplayer
 static void M_SetupMultiPlayer(INT32 choice);
@@ -357,6 +364,7 @@ static void M_DrawLoad(void);
 static void M_DrawLevelStats(void);
 static void M_DrawTimeAttackMenu(void);
 static void M_DrawNightsAttackMenu(void);
+static void M_DrawMarathon(void);
 static void M_DrawSetupChoosePlayerMenu(void);
 static void M_DrawControlsDefMenu(void);
 static void M_DrawCameraOptionsMenu(void);
@@ -473,6 +481,13 @@ static consvar_t cv_dummylives = {"dummylives", "0", CV_HIDEN, liveslimit_cons_t
 static consvar_t cv_dummycontinues = {"dummycontinues", "0", CV_HIDEN, contlimit_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
 static consvar_t cv_dummymares = {"dummymares", "Overall", CV_HIDEN|CV_CALL, dummymares_cons_t, Dummymares_OnChange, 0, NULL, NULL, 0, 0, NULL};
 
+CV_PossibleValue_t marathon_cons_t[] = {{0, "Standard"}, {1, "Live Event Backup"}, {2, "Ultimate"}, {0, NULL}};
+CV_PossibleValue_t loadless_cons_t[] = {{0, "Realtime"}, {1, "In-game"}, {0, NULL}};
+
+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};
+
 // ==========================================================================
 // ORGANIZATION START.
 // ==========================================================================
@@ -743,9 +758,10 @@ static menuitem_t SR_EmblemHintMenu[] =
 // Single Player Main
 static menuitem_t SP_MainMenu[] =
 {
-	{IT_CALL | IT_STRING,                       NULL, "Start Game",    M_LoadGame,                 84},
-	{IT_SECRET,                                 NULL, "Record Attack", M_TimeAttack,               92},
-	{IT_SECRET,                                 NULL, "NiGHTS Mode",   M_NightsAttack,            100},
+	{IT_CALL | IT_STRING,                       NULL, "Start Game",    M_LoadGame,                 76},
+	{IT_SECRET,                                 NULL, "Record Attack", M_TimeAttack,               84},
+	{IT_SECRET,                                 NULL, "NiGHTS Mode",   M_NightsAttack,             92},
+	{IT_CALL | IT_STRING | IT_CALL_NOTMODIFIED, NULL, "Marathon Run",  M_Marathon,                100},
 	{IT_CALL | IT_STRING,                       NULL, "Tutorial",      M_StartTutorial,           108},
 	{IT_CALL | IT_STRING | IT_CALL_NOTMODIFIED, NULL, "Statistics",    M_Statistics,              116}
 };
@@ -755,6 +771,7 @@ enum
 	sploadgame,
 	sprecordattack,
 	spnightsmode,
+	spmarathon,
 	sptutorial,
 	spstatistics
 };
@@ -900,6 +917,25 @@ enum
 	nastart
 };
 
+// Marathon
+static menuitem_t SP_MarathonMenu[] =
+{
+	{IT_STRING|IT_KEYHANDLER,  NULL, "Character", M_HandleMarathonChoosePlayer,  90},
+	{IT_STRING|IT_CVAR,        NULL, "Category",  &cv_dummymarathon,            100},
+	{IT_STRING|IT_CVAR,        NULL, "Timer",     &cv_dummyloadless,            110},
+	{IT_STRING|IT_CVAR,        NULL, "Cutscenes", &cv_dummycutscenes,           120},
+	{IT_WHITESTRING|IT_CALL,   NULL, "Start",     M_StartMarathon,              130},
+};
+
+enum
+{
+	marathonplayer,
+	marathonultimate,
+	marathonloadless,
+	marathoncutscenes,
+	marathonstart
+};
+
 // Statistics
 static menuitem_t SP_LevelStatsMenu[] =
 {
@@ -1901,6 +1937,18 @@ static menu_t SP_NightsGhostDef =
 	NULL
 };
 
+static menu_t SP_MarathonDef =
+{
+	MTREE2(MN_SP_MAIN, MN_SP_MARATHON),
+	"M_ATTACK", // temporary
+	sizeof(SP_MarathonMenu)/sizeof(menuitem_t),
+	&MainDef,  // Doesn't matter.
+	SP_MarathonMenu,
+	M_DrawMarathon,
+	32, 40,
+	0,
+	NULL
+};
 
 menu_t SP_PlayerDef =
 {
@@ -2506,6 +2554,8 @@ void M_InitMenuPresTables(void)
 			strncpy(menupres[i].musname, "_recat", 7);
 		else if (i == MN_SP_NIGHTSATTACK)
 			strncpy(menupres[i].musname, "_nitat", 7);
+		else if (i == MN_SP_MARATHON)
+			strncpy(menupres[i].musname, "spec8", 6);
 		else if (i == MN_SP_PLAYER || i == MN_SR_PLAYER)
 			strncpy(menupres[i].musname, "_chsel", 7);
 		else if (i == MN_SR_SOUNDTEST)
@@ -3000,7 +3050,7 @@ static void M_GoBack(INT32 choice)
 			netgame = multiplayer = false;
 		}
 
-		if ((currentMenu->prevMenu == &MainDef) && (currentMenu == &SP_TimeAttackDef || currentMenu == &SP_NightsAttackDef))
+		if ((currentMenu->prevMenu == &MainDef) && (currentMenu == &SP_TimeAttackDef || currentMenu == &SP_NightsAttackDef || currentMenu == &SP_MarathonDef))
 		{
 			// D_StartTitle does its own wipe, since GS_TIMEATTACK is now a complete gamestate.
 
@@ -3385,11 +3435,14 @@ boolean M_Responder(event_t *ev)
 	{
 		if (currentMenu->menuitems[itemOn].alphaKey != MM_EVENTHANDLER)
 		{
-			if (ch == ' ' || ch == 'n' || ch == 'y' || ch == KEY_ESCAPE || ch == KEY_ENTER)
+			if (ch == ' ' || ch == 'n' || ch == 'y' || ch == KEY_ESCAPE || ch == KEY_ENTER || ch == KEY_DEL)
 			{
 				if (routine)
 					routine(ch);
-				M_StopMessage(0);
+				if (stopstopmessage)
+					stopstopmessage = false;
+				else
+					M_StopMessage(0);
 				noFurtherInput = true;
 				return true;
 			}
@@ -3639,7 +3692,7 @@ void M_StartControlPanel(void)
 		{
 			INT32 numlives = 2;
 
-			SPauseMenu[spause_pandora].status = (M_SecretUnlocked(SECRET_PANDORA)) ? (IT_STRING | IT_CALL) : (IT_DISABLED);
+			SPauseMenu[spause_pandora].status = (M_SecretUnlocked(SECRET_PANDORA) && !marathonmode) ? (IT_STRING | IT_CALL) : (IT_DISABLED);
 
 			if (&players[consoleplayer])
 			{
@@ -3657,10 +3710,10 @@ void M_StartControlPanel(void)
 		}
 
 		// We can always use level select though. :33
-		SPauseMenu[spause_levelselect].status = (gamecomplete) ? (IT_STRING | IT_CALL) : (IT_DISABLED);
+		SPauseMenu[spause_levelselect].status = (gamecomplete == 1) ? (IT_STRING | IT_CALL) : (IT_DISABLED);
 
 		// And emblem hints.
-		SPauseMenu[spause_hints].status = (M_SecretUnlocked(SECRET_EMBLEMHINTS)) ? (IT_STRING | IT_CALL) : (IT_DISABLED);
+		SPauseMenu[spause_hints].status = (M_SecretUnlocked(SECRET_EMBLEMHINTS) && !marathonmode) ? (IT_STRING | IT_CALL) : (IT_DISABLED);
 
 		// Shift up Pandora's Box if both pandora and levelselect are active
 		/*if (SPauseMenu[spause_pandora].status != (IT_DISABLED)
@@ -3841,6 +3894,9 @@ void M_Init(void)
 	CV_RegisterVar(&cv_dummylives);
 	CV_RegisterVar(&cv_dummycontinues);
 	CV_RegisterVar(&cv_dummymares);
+	CV_RegisterVar(&cv_dummymarathon);
+	CV_RegisterVar(&cv_dummyloadless);
+	CV_RegisterVar(&cv_dummycutscenes);
 
 	quitmsg[QUITMSG] = M_GetText("Eggman's tied explosives\nto your girlfriend, and\nwill activate them if\nyou press the 'Y' key!\nPress 'N' to save her!\n\n(Press 'Y' to quit)");
 	quitmsg[QUITMSG1] = M_GetText("What would Tails say if\nhe saw you quitting the game?\n\n(Press 'Y' to quit)");
@@ -6303,8 +6359,8 @@ static char *M_AddonsHeaderPath(void)
 
 static void M_AddonsClearName(INT32 choice)
 {
+	(void)choice;
 	CLEARNAME;
-	M_StopMessage(choice);
 }
 
 // returns whether to do message draw
@@ -6336,7 +6392,7 @@ static boolean M_AddonsRefresh(void)
 
 		if (message)
 		{
-			M_StartMessage(message,M_AddonsClearName,MM_EVENTHANDLER);
+			M_StartMessage(message,M_AddonsClearName,MM_NOTHING);
 			return true;
 		}
 
@@ -6841,6 +6897,11 @@ static void M_RetryResponse(INT32 ch)
 static void M_Retry(INT32 choice)
 {
 	(void)choice;
+	if (marathonmode)
+	{
+		M_RetryResponse(KEY_ENTER);
+		return;
+	}
 	M_StartMessage(M_GetText("Retry this act from the last starpost?\n\n(Press 'Y' to confirm)\n"),M_RetryResponse,MM_YESNO);
 }
 
@@ -7993,6 +8054,16 @@ static void M_SinglePlayerMenu(INT32 choice)
 
 	SP_MainMenu[sptutorial].status = tutorialmap ? IT_CALL|IT_STRING : IT_NOTHING|IT_DISABLED;
 
+	// If the FIRST stage immediately leads to the ending, or itself (which gets converted to the title screen in G_DoCompleted for marathonmode only), there's no point in having this option on the menu. You should use Record Attack in that circumstance, although if marathonnext is set this behaviour can be overridden if you make some weird mod that requires multiple playthroughs of the same map in sequence and has some in-level mechanism to break the cycle.
+	if (!M_SecretUnlocked(SECRET_RECORDATTACK) // also if record attack is locked
+		|| (mapheaderinfo[spmarathon_start-1]
+		&& !mapheaderinfo[spmarathon_start-1]->marathonnext
+		&& (mapheaderinfo[spmarathon_start-1]->nextlevel == spmarathon_start
+			|| mapheaderinfo[spmarathon_start-1]->nextlevel >= 1100)))
+		SP_MainMenu[spmarathon].status = IT_NOTHING|IT_DISABLED;
+	else
+		SP_MainMenu[spmarathon].status = IT_CALL|IT_STRING|IT_CALL_NOTMODIFIED;
+
 	M_SetupNextMenu(&SP_MainDef);
 }
 
@@ -8083,7 +8154,7 @@ static void M_StartTutorial(INT32 choice)
 	emeralds = 0;
 	memset(&luabanks, 0, sizeof(luabanks));
 	M_ClearMenus(true);
-	gamecomplete = false;
+	gamecomplete = 0;
 	cursaveslot = 0;
 	G_DeferedInitNew(false, G_BuildMapName(tutorialmap), 0, false, false);
 }
@@ -8846,7 +8917,7 @@ static void M_CacheCharacterSelect(void)
 {
 	INT32 i, skinnum;
 
-	for (i = 0; i < 32; i++)
+	for (i = 0; i < MAXSKINS; i++)
 	{
 		if (!description[i].used)
 			continue;
@@ -8858,7 +8929,7 @@ static void M_CacheCharacterSelect(void)
 	}
 }
 
-static void M_SetupChoosePlayer(INT32 choice)
+static UINT8 M_SetupChoosePlayerDirect(INT32 choice)
 {
 	INT32 skinnum;
 	UINT8 i;
@@ -8869,7 +8940,7 @@ static void M_SetupChoosePlayer(INT32 choice)
 
 	if (!mapheaderinfo[startmap-1] || mapheaderinfo[startmap-1]->forcecharacter[0] == '\0')
 	{
-		for (i = 0; i < 32; i++) // Handle charsels, availability, and unlocks.
+		for (i = 0; i < MAXSKINS; i++) // Handle charsels, availability, and unlocks.
 		{
 			if (description[i].used) // If the character's disabled through SOC, there's nothing we can do for it.
 			{
@@ -8877,7 +8948,7 @@ static void M_SetupChoosePlayer(INT32 choice)
 				if (and)
 				{
 					char firstskin[SKINNAMESIZE+1];
-					if (mapheaderinfo[startmap-1]->typeoflevel & TOL_NIGHTS) // skip tagteam characters for NiGHTS levels
+					if (mapheaderinfo[startmap-1] && mapheaderinfo[startmap-1]->typeoflevel & TOL_NIGHTS) // skip tagteam characters for NiGHTS levels
 						continue;
 					strncpy(firstskin, description[i].skinname, (and - description[i].skinname));
 					firstskin[(and - description[i].skinname)] = '\0';
@@ -8914,14 +8985,36 @@ static void M_SetupChoosePlayer(INT32 choice)
 
 	if (firstvalid == lastvalid) // We're being forced into a specific character, so might as well just skip it.
 	{
-		M_ChoosePlayer(firstvalid);
-		return;
+		return firstvalid;
 	}
 
 	// One last bit of order we can't do in the iteration above.
 	description[firstvalid].prev = lastvalid;
 	description[lastvalid].next = firstvalid;
 
+	if (!allowed)
+	{
+		char_on = firstvalid;
+		if (startchar > 0 && startchar < MAXSKINS)
+		{
+			INT16 workchar = startchar;
+			while (workchar--)
+				char_on = description[char_on].next;
+		}
+	}
+
+	return MAXSKINS;
+}
+
+static void M_SetupChoosePlayer(INT32 choice)
+{
+	UINT8 skinset = M_SetupChoosePlayerDirect(choice);
+	if (skinset != MAXSKINS)
+	{
+		M_ChoosePlayer(skinset);
+		return;
+	}
+
 	M_ChangeMenuMusic("_chsel", true);
 
 	/* the menus suck -James */
@@ -8936,16 +9029,6 @@ static void M_SetupChoosePlayer(INT32 choice)
 
 	SP_PlayerDef.prevMenu = currentMenu;
 	M_SetupNextMenu(&SP_PlayerDef);
-	if (!allowed)
-	{
-		char_on = firstvalid;
-		if (startchar > 0 && startchar < 32)
-		{
-			INT16 workchar = startchar;
-			while (workchar--)
-				char_on = description[char_on].next;
-		}
-	}
 
 	// finish scrolling the menu
 	char_scroll = 0;
@@ -9004,6 +9087,10 @@ static void M_HandleChoosePlayerMenu(INT32 choice)
 
 		case KEY_ENTER:
 			S_StartSound(NULL, sfx_menu1);
+			char_scroll = 0; // finish scrolling the menu
+			M_DrawSetupChoosePlayerMenu(); // draw the finally selected character one last time for the fadeout
+			// Is this a hack?
+			charseltimer = 0;
 			M_ChoosePlayer(char_on);
 			break;
 
@@ -9243,7 +9330,7 @@ static void M_DrawSetupChoosePlayerMenu(void)
 // Chose the player you want to use Tails 03-02-2002
 static void M_ChoosePlayer(INT32 choice)
 {
-	boolean ultmode = (ultimate_selectable && SP_PlayerDef.prevMenu == &SP_LoadDef && saveSlotSelected == NOSAVESLOT);
+	boolean ultmode = (currentMenu == &SP_MarathonDef) ? (cv_dummymarathon.value == 2) : (ultimate_selectable && SP_PlayerDef.prevMenu == &SP_LoadDef && saveSlotSelected == NOSAVESLOT);
 	UINT8 skinnum;
 
 	// skip this if forcecharacter or no characters available
@@ -9255,11 +9342,6 @@ static void M_ChoosePlayer(INT32 choice)
 	// M_SetupChoosePlayer didn't call us directly, that means we've been properly set up.
 	else
 	{
-		char_scroll = 0; // finish scrolling the menu
-		M_DrawSetupChoosePlayerMenu(); // draw the finally selected character one last time for the fadeout
-		// Is this a hack?
-		charseltimer = 0;
-
 		skinnum = description[choice].skinnum[0];
 
 		if ((botingame = (description[choice].skinnum[1] != -1))) {
@@ -9273,11 +9355,11 @@ static void M_ChoosePlayer(INT32 choice)
 
 	M_ClearMenus(true);
 
-	if (startmap != spstage_start)
+	if (!marathonmode && startmap != spstage_start)
 		cursaveslot = 0;
 
 	//lastmapsaved = 0;
-	gamecomplete = false;
+	gamecomplete = 0;
 
 	G_DeferedInitNew(ultmode, G_BuildMapName(startmap), skinnum, false, fromlevelselect);
 	COM_BufAddText("dummyconsvar 1\n"); // G_DeferedInitNew doesn't do this
@@ -10276,6 +10358,368 @@ static void M_ModeAttackEndGame(INT32 choice)
 	Nextmap_OnChange();
 }
 
+static void M_MarathonLiveEventBackup(INT32 choice)
+{
+	if (choice == 'y' || choice == KEY_ENTER)
+	{
+		marathonmode = MA_INIT;
+		G_LoadGame(MARATHONSLOT, 0);
+		cursaveslot = MARATHONSLOT;
+		if (!(marathonmode & MA_RUNNING))
+			marathonmode = 0;
+		return;
+	}
+
+	M_StopMessage(0);
+	stopstopmessage = true;
+
+	if (choice == KEY_DEL)
+	{
+		if (FIL_FileExists(liveeventbackup)) // just in case someone deleted it while we weren't looking.
+			remove(liveeventbackup);
+		BwehHehHe();
+		M_StartMessage("Live event backup erased.\n",M_Marathon,MM_NOTHING);
+		return;
+	}
+
+	M_Marathon(-1);
+}
+
+// Going to Marathon menu...
+static void M_Marathon(INT32 choice)
+{
+	UINT8 skinset;
+	INT32 mapnum = 0;
+
+	if (choice != -1 && FIL_FileExists(liveeventbackup))
+	{
+		M_StartMessage(\
+			"\x82Live event backup detected.\n\x80\
+			Do you want to resurrect the last run?\n\
+			(Fs in chat if we crashed on stream.)\n\
+			\n\
+			Press 'Y' or 'Enter' to resume,\n\
+			'Del' to delete, or any other\n\
+			key to continue to Marathon Run.",M_MarathonLiveEventBackup,MM_YESNO);
+		return;
+	}
+
+	fromlevelselect = false;
+
+	startmap = spmarathon_start;
+	CV_SetValue(&cv_newgametype, GT_COOP); // Graue 09-08-2004
+
+	skinset = M_SetupChoosePlayerDirect(-1);
+
+	SP_MarathonMenu[marathonplayer].status = (skinset == MAXSKINS) ? IT_KEYHANDLER|IT_STRING : IT_NOTHING|IT_DISABLED;
+
+	while (mapnum < NUMMAPS)
+	{
+		if (mapheaderinfo[mapnum])
+		{
+			if (mapheaderinfo[mapnum]->cutscenenum || mapheaderinfo[mapnum]->precutscenenum)
+				break;
+		}
+		mapnum++;
+	}
+
+	SP_MarathonMenu[marathoncutscenes].status = (mapnum < NUMMAPS) ? IT_CVAR|IT_STRING : IT_NOTHING|IT_DISABLED;
+
+	M_ChangeMenuMusic("spec8", true);
+
+	SP_MarathonDef.prevMenu = &MainDef;
+	G_SetGamestate(GS_TIMEATTACK); // do this before M_SetupNextMenu so that menu meta state knows that we're switching
+	titlemapinaction = TITLEMAP_OFF; // Nope don't give us HOMs please
+	M_SetupNextMenu(&SP_MarathonDef);
+	itemOn = marathonstart; // "Start" is selected.
+	recatkdrawtimer = 50-8;
+	char_scroll = 0;
+}
+
+static void M_HandleMarathonChoosePlayer(INT32 choice)
+{
+	INT32 selectval;
+
+	if (keydown > 1)
+		return;
+
+	switch (choice)
+	{
+		case KEY_DOWNARROW:
+			M_NextOpt();
+			break;
+		case KEY_UPARROW:
+			M_PrevOpt();
+			break;
+
+		case KEY_LEFTARROW:
+			if ((selectval = description[char_on].prev) == char_on)
+				return;
+			char_on = selectval;
+			break;
+		case KEY_RIGHTARROW:
+			if ((selectval = description[char_on].next) == char_on)
+				return;
+			char_on = selectval;
+			break;
+
+		case KEY_ESCAPE:
+			noFurtherInput = true;
+			M_GoBack(0);
+			return;
+
+		default:
+			return;
+	}
+	S_StartSound(NULL, sfx_menu1);
+}
+
+static void M_StartMarathon(INT32 choice)
+{
+	(void)choice;
+	marathontime = 0;
+	marathonmode = MA_RUNNING|MA_INIT;
+	if (cv_dummymarathon.value == 1)
+		cursaveslot = MARATHONSLOT;
+	if (!cv_dummycutscenes.value)
+		marathonmode |= MA_NOCUTSCENES;
+	if (cv_dummyloadless.value)
+		marathonmode |= MA_INGAME;
+	M_ChoosePlayer(char_on);
+}
+
+// Drawing function for Marathon menu
+void M_DrawMarathon(void)
+{
+	INT32 i, x, y, cursory = 0, cnt, soffset = 0, w;
+	UINT16 dispstatus;
+	consvar_t *cv;
+	const char *cvstring;
+	char *work;
+	angle_t fa;
+	INT32 dupz = (vid.dupx < vid.dupy ? vid.dupx : vid.dupy), xspan = (vid.width/dupz), yspan = (vid.height/dupz), diffx = (xspan - BASEVIDWIDTH)/2, diffy = (yspan - BASEVIDHEIGHT)/2, maxy = BASEVIDHEIGHT + diffy;
+
+	// lactozilla: the renderer changed so recache patches
+	if (needpatchrecache)
+		M_CacheCharacterSelect();
+
+	curbgxspeed = 0;
+	curbgyspeed = 18;
+
+	M_ChangeMenuMusic("spec8", true); // Eww, but needed for when user hits escape during demo playback
+
+	V_DrawFill(-diffx, -diffy, diffx+(BASEVIDWIDTH-190)/2, yspan, 158);
+	V_DrawFill((BASEVIDWIDTH-190)/2, -diffy, 190, yspan, 31);
+	V_DrawFill((BASEVIDWIDTH+190)/2, -diffy, diffx+(BASEVIDWIDTH-190)/2, yspan, 158);
+	//M_DrawRecordAttackForeground();
+	if (curfadevalue)
+		V_DrawFadeScreen(0xFF00, curfadevalue);
+
+	x = (((BASEVIDWIDTH-82)/2)+11)<<FRACBITS;
+	y = (((BASEVIDHEIGHT-82)/2)+12-10)<<FRACBITS;
+
+	cnt = (36*(recatkdrawtimer<<FRACBITS))/TICRATE;
+	fa = (FixedAngle(cnt)>>ANGLETOFINESHIFT) & FINEMASK;
+	y -= (10*FINECOSINE(fa));
+
+	recatkdrawtimer++;
+
+	soffset = cnt = (recatkdrawtimer%50);
+	if (!useBlackRock)
+	{
+		if (cnt > 8)
+			cnt = 8;
+		V_DrawFixedPatch(x+(6<<FRACBITS), y, FRACUNIT/2, (cnt&~1)<<(V_ALPHASHIFT-1), W_CachePatchName("RECCLOCK", PU_PATCH), NULL);
+	}
+	else if (cnt > 8)
+	{
+		cnt = 8;
+		V_DrawFixedPatch(x, y, FRACUNIT, V_TRANSLUCENT, W_CachePatchName("ENDEGRK5", PU_PATCH), NULL);
+	}
+	else
+	{
+		V_DrawFixedPatch(x, y, FRACUNIT, cnt<<V_ALPHASHIFT, W_CachePatchName("ROID0000", PU_PATCH), NULL);
+		V_DrawFixedPatch(x, y, FRACUNIT, V_TRANSLUCENT, W_CachePatchName("ENDEGRK5", PU_PATCH), NULL);
+		V_DrawFixedPatch(x, y, FRACUNIT, cnt<<V_ALPHASHIFT, W_CachePatchName("ENDEGRK0", PU_PATCH), NULL);
+	}
+
+	{
+		UINT8 col;
+		i = 0;
+		w = (((8-cnt)+1)/3)+1;
+		w *= w;
+		cursory = 0;
+		while (i < cnt)
+		{
+			i++;
+			col = 158+((cnt-i)/3);
+			if (col >= 160)
+				col = 253;
+			V_DrawFill(((BASEVIDWIDTH-190)/2)-cursory-w, -diffy, w, yspan, col);
+			V_DrawFill(((BASEVIDWIDTH+190)/2)+cursory,   -diffy, w, yspan, col);
+			cursory += w;
+			w *= 2;
+		}
+	}
+
+	w = char_scroll + (((8-cnt)*(8-cnt))<<(FRACBITS-5));
+	if (soffset == 50-1)
+		w += FRACUNIT/2;
+
+	{
+		patch_t *fg = W_CachePatchName("RECATKFG", PU_PATCH);
+		INT32 trans = V_60TRANS+((cnt&~3)<<(V_ALPHASHIFT-2));
+		INT32 height = (SHORT(fg->height)/2);
+		char patchname[7] = "CEMGx0";
+
+		dupz = (w*7)/6; //(w*42*120)/(360*6); -- I don't know why this works but I'm not going to complain.
+		dupz = ((dupz>>FRACBITS) % height);
+		y = height/2;
+		while (y+dupz >= -diffy)
+			y -= height;
+		while (y-2-dupz < maxy)
+		{
+			V_DrawFixedPatch(((BASEVIDWIDTH-190)<<(FRACBITS-1)), (y-2-dupz)<<FRACBITS, FRACUNIT/2, trans, fg, NULL);
+			V_DrawFixedPatch(((BASEVIDWIDTH+190)<<(FRACBITS-1)), (y+dupz)<<FRACBITS, FRACUNIT/2, trans|V_FLIP, fg, NULL);
+			y += height;
+		}
+
+		trans = V_40TRANS+((cnt&~1)<<(V_ALPHASHIFT-1));
+
+		for (i = 0; i < 7; ++i)
+		{
+			fa = (FixedAngle(w)>>ANGLETOFINESHIFT) & FINEMASK;
+			x = (BASEVIDWIDTH<<(FRACBITS-1)) + (60*FINESINE(fa));
+			y = ((BASEVIDHEIGHT+16-20)<<(FRACBITS-1)) - (60*FINECOSINE(fa));
+			w += (360<<FRACBITS)/7;
+
+			patchname[4] = 'A'+(char)i;
+			V_DrawFixedPatch(x, y, FRACUNIT, trans, W_CachePatchName(patchname, PU_PATCH), NULL);
+		}
+
+		height = 18; // prevents the need for the next line
+		//dupz = (w*height)/18;
+		dupz = ((w>>FRACBITS) % height);
+		y = dupz+(height/4);
+		x = 105+dupz;
+		while (y >= -diffy)
+		{
+			x -= height;
+			y -= height;
+		}
+		while (y-dupz < maxy && x < (xspan/2))
+		{
+			V_DrawFill((BASEVIDWIDTH/2)-x-height, -diffy, height, diffy+y+height, 153);
+			V_DrawFill((BASEVIDWIDTH/2)+x, (maxy-y)-height, height, height+y, 153);
+			y += height;
+			x += height;
+		}
+	}
+
+	if (!soffset)
+	{
+		char_scroll += (360<<FRACBITS)/42; // like a clock, ticking at 42bpm!
+		if (char_scroll >= 360<<FRACBITS)
+			char_scroll -= 360<<FRACBITS;
+		if (recatkdrawtimer > (10*TICRATE))
+			recatkdrawtimer -= (10*TICRATE);
+	}
+
+	//M_DrawMenuTitle();
+
+	// draw menu (everything else goes on top of it)
+	// Sadly we can't just use generic mode menus because we need some extra hacks
+	x = currentMenu->x;
+	y = currentMenu->y;
+
+	dispstatus = (currentMenu->menuitems[marathonplayer].status & IT_DISPLAY);
+	if (dispstatus == IT_STRING || dispstatus == IT_WHITESTRING)
+	{
+		soffset = 68;
+		if (description[char_on].charpic->width >= 256)
+			V_DrawTinyScaledPatch(224, 120, 0, description[char_on].charpic);
+		else
+			V_DrawSmallScaledPatch(224, 120, 0, description[char_on].charpic);
+	}
+	else
+		soffset = 0;
+
+	for (i = 0; i < currentMenu->numitems; ++i)
+	{
+		dispstatus = (currentMenu->menuitems[i].status & IT_DISPLAY);
+		if (dispstatus != IT_STRING && dispstatus != IT_WHITESTRING)
+			continue;
+
+		y = currentMenu->y+currentMenu->menuitems[i].alphaKey;
+		if (i == itemOn)
+			cursory = y;
+
+		V_DrawString(x, y, (dispstatus == IT_WHITESTRING) ? V_YELLOWMAP : 0 , currentMenu->menuitems[i].text);
+
+		cv = NULL;
+		cvstring = NULL;
+		work = NULL;
+		if ((currentMenu->menuitems[i].status & IT_TYPE) == IT_CVAR)
+		{
+			cv = (consvar_t *)currentMenu->menuitems[i].itemaction;
+			cvstring = cv->string;
+		}
+		else if (i == marathonplayer)
+		{
+			if (description[char_on].displayname[0])
+			{
+				work = Z_StrDup(description[char_on].displayname);
+				cnt = 0;
+				while (work[cnt])
+				{
+					if (work[cnt] == '\n')
+						work[cnt] = ' ';
+					cnt++;
+				}
+				cvstring = work;
+			}
+			else
+				cvstring = description[char_on].skinname;
+		}
+
+		// Cvar specific handling
+		if (cvstring)
+		{
+			INT32 flags = V_YELLOWMAP;
+			if (cv == &cv_dummymarathon && cv->value == 2) // ultimate_selectable
+				flags = V_REDMAP;
+
+			// Should see nothing but strings
+			if (cv == &cv_dummymarathon && cv->value == 1)
+			{
+				w = V_ThinStringWidth(cvstring, 0);
+				V_DrawThinString(BASEVIDWIDTH - x - soffset - w, y+1, flags, cvstring);
+			}
+			else
+			{
+				w = V_StringWidth(cvstring, 0);
+				V_DrawString(BASEVIDWIDTH - x - soffset - w, y, flags, cvstring);
+			}
+			if (i == itemOn)
+			{
+				V_DrawCharacter(BASEVIDWIDTH - x - soffset - 10 - w - (skullAnimCounter/5), y,
+					'\x1C' | V_YELLOWMAP, false);
+				V_DrawCharacter(BASEVIDWIDTH - x - soffset + 2 + (skullAnimCounter/5), y,
+					'\x1D' | V_YELLOWMAP, false);
+			}
+			if (work)
+				Z_Free(work);
+		}
+	}
+
+	// DRAW THE SKULL CURSOR
+	V_DrawScaledPatch(currentMenu->x - 24, cursory, 0, W_CachePatchName("M_CURSOR", PU_PATCH));
+	V_DrawString(currentMenu->x, cursory, V_YELLOWMAP, currentMenu->menuitems[itemOn].text);
+
+	// Draw press ESC to exit string on main record attack menu
+	V_DrawString(104-72, 180, V_TRANSLUCENT, M_GetText("Press ESC to exit"));
+}
+
 // ========
 // END GAME
 // ========
@@ -12755,6 +13199,7 @@ void M_QuitResponse(INT32 ch)
 	if (!(netgame || cv_debug))
 	{
 		S_ResetCaptions();
+		marathonmode = 0;
 
 		mrand = M_RandomKey(sizeof(quitsounds)/sizeof(INT32));
 		if (quitsounds[mrand]) S_StartSound(NULL, quitsounds[mrand]);
diff --git a/src/m_menu.h b/src/m_menu.h
index 73aff53e67df45fb246b8134a5cdb6823af444e6..221f1fd3f653a2d18eb9d013be8c5f104ae1e142 100644
--- a/src/m_menu.h
+++ b/src/m_menu.h
@@ -61,6 +61,8 @@ typedef enum
 	MN_SP_NIGHTS_REPLAY,
 	MN_SP_NIGHTS_GHOST,
 
+	MN_SP_MARATHON,
+
 	// Multiplayer
 	MN_MP_MAIN,
 	MN_MP_SPLITSCREEN, // SplitServer
@@ -417,6 +419,7 @@ extern INT16 char_on, startchar;
 
 #define MAXSAVEGAMES 31
 #define NOSAVESLOT 0 //slot where Play Without Saving appears
+#define MARATHONSLOT 420 // just has to be nonzero, but let's use one that'll show up as an obvious error if something goes wrong while not using our existing saves
 
 #define BwehHehHe() S_StartSound(NULL, sfx_bewar1+M_RandomKey(4)) // Bweh heh he
 
diff --git a/src/p_saveg.c b/src/p_saveg.c
index 85c4c4f757106bf427ce63bcdcf7aa954a0b7c35..b4d7ef83872291f4bccf077a94c6acbb55ee2363 100644
--- a/src/p_saveg.c
+++ b/src/p_saveg.c
@@ -3808,10 +3808,10 @@ static inline void P_UnArchiveSPGame(INT16 mapoverride)
 	if (mapoverride != 0)
 	{
 		gamemap = mapoverride;
-		gamecomplete = true;
+		gamecomplete = 1;
 	}
 	else
-		gamecomplete = false;
+		gamecomplete = 0;
 
 	// gamemap changed; we assume that its map header is always valid,
 	// so make it so
diff --git a/src/p_setup.c b/src/p_setup.c
index 84e89d7463abb5190b456d2c1d44e239f5c5cca1..ffb68b1b04f0713a50eb99a6f810ee6f6ad1ac20 100644
--- a/src/p_setup.c
+++ b/src/p_setup.c
@@ -344,6 +344,7 @@ static void P_ClearSingleMapHeaderInfo(INT16 i)
 	mapheaderinfo[num]->actnum = 0;
 	mapheaderinfo[num]->typeoflevel = 0;
 	mapheaderinfo[num]->nextlevel = (INT16)(i + 1);
+	mapheaderinfo[num]->marathonnext = 0;
 	mapheaderinfo[num]->startrings = 0;
 	mapheaderinfo[num]->sstimer = 90;
 	mapheaderinfo[num]->ssspheres = 1;
@@ -3308,7 +3309,7 @@ static boolean CanSaveLevel(INT32 mapnum)
 	// Any levels that have the savegame flag can save normally.
 	// If the game is complete for this save slot, then any level can save!
 	// On the other side of the spectrum, if lastmaploaded is 0, then the save file has only just been created and needs to save ASAP!
-	return (mapheaderinfo[mapnum-1]->levelflags & LF_SAVEGAME || gamecomplete || !lastmaploaded);
+	return (mapheaderinfo[mapnum-1]->levelflags & LF_SAVEGAME || (gamecomplete != 0) || marathonmode || !lastmaploaded);
 }
 
 static void P_RunSpecialStageWipe(void)
diff --git a/src/p_tick.c b/src/p_tick.c
index 55b545856c018600d831d8287932bfd5eaa64be7..1f1331856fd494c153bcf780d379843ae60f2f21 100644
--- a/src/p_tick.c
+++ b/src/p_tick.c
@@ -755,6 +755,9 @@ void P_PreTicker(INT32 frames)
 
 	postimgtype = postimgtype2 = postimg_none;
 
+	if (marathonmode & MA_INGAME)
+		marathonmode |= MA_INIT;
+
 	for (framecnt = 0; framecnt < frames; ++framecnt)
 	{
 		P_MapStart();
@@ -799,4 +802,7 @@ void P_PreTicker(INT32 frames)
 
 		P_MapEnd();
 	}
+
+	if (marathonmode & MA_INGAME)
+		marathonmode &= ~MA_INIT;
 }
diff --git a/src/screen.c b/src/screen.c
index e01d1a81a8160918010a61cc389646664a91cbb0..e7ff9e73555a35f146b78974f40d25eb4736299b 100644
--- a/src/screen.c
+++ b/src/screen.c
@@ -622,3 +622,51 @@ void SCR_ClosedCaptions(void)
 			va("%c [%s]", dot, (closedcaptions[i].s->caption[0] ? closedcaptions[i].s->caption : closedcaptions[i].s->name)));
 	}
 }
+
+void SCR_DisplayMarathonInfo(void)
+{
+	INT32 flags = V_SNAPTOBOTTOM;
+	static tic_t entertic, oldentertics = 0, antisplice[2] = {48,0};
+	const char *str;
+#if 0 // eh, this probably isn't going to be a problem
+	if (((signed)marathontime) < 0)
+	{
+		flags |= V_REDMAP;
+		str = "No waiting out the clock to submit a bogus time.";
+	}
+	else
+#endif
+	{
+		entertic = I_GetTime();
+		if (gamecomplete)
+			flags |= V_YELLOWMAP;
+		else if (marathonmode & MA_INGAME)
+			; // see also G_Ticker
+		else if (marathonmode & MA_INIT)
+			marathonmode &= ~MA_INIT;
+		else
+			marathontime += entertic - oldentertics;
+
+		// Create a sequence of primes such that their LCM is nice and big.
+#define PRIMEV1 13
+#define PRIMEV2 17 // I can't believe it! I'm on TV!
+		antisplice[0] += (entertic - oldentertics)*PRIMEV2;
+		antisplice[0] %= PRIMEV1*((vid.width/vid.dupx)+1);
+		antisplice[1] += (entertic - oldentertics)*PRIMEV1;
+		antisplice[1] %= PRIMEV1*((vid.width/vid.dupx)+1);
+		str = va("%i:%02i:%02i.%02i",
+			G_TicsToHours(marathontime),
+			G_TicsToMinutes(marathontime, false),
+			G_TicsToSeconds(marathontime),
+			G_TicsToCentiseconds(marathontime));
+		oldentertics = entertic;
+	}
+	V_DrawFill((antisplice[0]/PRIMEV1)-1, BASEVIDHEIGHT-8, 1, 8, V_SNAPTOBOTTOM|V_SNAPTOLEFT);
+	V_DrawFill((antisplice[0]/PRIMEV1),   BASEVIDHEIGHT-8, 1, 8, V_SNAPTOBOTTOM|V_SNAPTOLEFT|31);
+	V_DrawFill(BASEVIDWIDTH-((antisplice[1]/PRIMEV1)-1), BASEVIDHEIGHT-8, 1, 8, V_SNAPTOBOTTOM|V_SNAPTORIGHT);
+	V_DrawFill(BASEVIDWIDTH-((antisplice[1]/PRIMEV1)),   BASEVIDHEIGHT-8, 1, 8, V_SNAPTOBOTTOM|V_SNAPTORIGHT|31);
+#undef PRIMEV1
+#undef PRIMEV2
+	V_DrawPromptBack(-8, cons_backcolor.value);
+	V_DrawCenteredString(BASEVIDWIDTH/2, BASEVIDHEIGHT-8, flags, str);
+}
diff --git a/src/screen.h b/src/screen.h
index ddbd499bbd347d691c79e8ba0a4cbf416a300abd..91ec175f426cb53d723a2a0539c8611ed0cfaa17 100644
--- a/src/screen.h
+++ b/src/screen.h
@@ -206,5 +206,6 @@ FUNCMATH boolean SCR_IsAspectCorrect(INT32 width, INT32 height);
 void SCR_DisplayTicRate(void);
 void SCR_ClosedCaptions(void);
 void SCR_DisplayLocalPing(void);
+void SCR_DisplayMarathonInfo(void);
 #undef DNWH
 #endif //__SCREEN_H__
diff --git a/src/sdl/i_video.c b/src/sdl/i_video.c
index 99d2b27c5855b46aa0b607a2914f1489aac6149a..0a6e9c01c196ac226cd0ba0e33bb15937118781d 100644
--- a/src/sdl/i_video.c
+++ b/src/sdl/i_video.c
@@ -1200,6 +1200,9 @@ void I_FinishUpdate(void)
 	if (I_SkipFrame())
 		return;
 
+	if (marathonmode)
+		SCR_DisplayMarathonInfo();
+
 	// draw captions if enabled
 	if (cv_closedcaptioning.value)
 		SCR_ClosedCaptions();
diff --git a/src/st_stuff.c b/src/st_stuff.c
index 086e80291275cbc174dbe700d5b248e5a78a23a6..621acb4894e100fc06117c5c72071a9fc778cc20 100644
--- a/src/st_stuff.c
+++ b/src/st_stuff.c
@@ -763,7 +763,7 @@ static void ST_drawTime(void)
 		ST_DrawPatchFromHud(HUD_TIMECOLON, sbocolon, V_HUDTRANS); // Colon
 		ST_DrawPadNumFromHud(HUD_SECONDS, seconds, 2, V_HUDTRANS); // Seconds
 
-		if (cv_timetic.value == 1 || cv_timetic.value == 2 || modeattacking) // there's not enough room for tics in splitscreen, don't even bother trying!
+		if (cv_timetic.value == 1 || cv_timetic.value == 2 || modeattacking || marathonmode)
 		{
 			ST_DrawPatchFromHud(HUD_TIMETICCOLON, sboperiod, V_HUDTRANS); // Period
 			ST_DrawPadNumFromHud(HUD_TICS, tictrn, 2, V_HUDTRANS); // Tics
@@ -1450,7 +1450,7 @@ static void ST_drawPowerupHUD(void)
 // ---------
 
 	// Let's have a power-like icon to represent finishing the level!
-	if (stplyr->pflags & PF_FINISHED && cv_exitmove.value)
+	if (stplyr->pflags & PF_FINISHED && cv_exitmove.value && multiplayer)
 	{
 		finishoffs[q] = ICONSEP;
 		V_DrawSmallScaledPatch(offs, hudinfo[HUD_POWERUPS].y, V_PERPLAYER|hudinfo[HUD_POWERUPS].f|V_HUDTRANS, fnshico);
diff --git a/src/v_video.c b/src/v_video.c
index 5fd3020f65268ddda37e173e8b5966e85afaa8ce..b88c4838bc4c66a74ddff5c008dfdf0051da26cc 100644
--- a/src/v_video.c
+++ b/src/v_video.c
@@ -1870,7 +1870,10 @@ void V_DrawPromptBack(INT32 boxheight, INT32 color)
 
 	if (color >= 256 && color < 512)
 	{
-		boxheight = ((boxheight * 4) + (boxheight/2)*5);
+		if (boxheight < 0)
+			boxheight = -boxheight;
+		else // 4 lines of space plus gaps between and some leeway
+			boxheight = ((boxheight * 4) + (boxheight/2)*5);
 		V_DrawFill((BASEVIDWIDTH-(vid.width/vid.dupx))/2, BASEVIDHEIGHT-boxheight, (vid.width/vid.dupx),boxheight, (color-256)|V_SNAPTOBOTTOM);
 		return;
 	}
@@ -1917,8 +1920,11 @@ void V_DrawPromptBack(INT32 boxheight, INT32 color)
 
 	// heavily simplified -- we don't need to know x or y position,
 	// just the start and stop positions
-	deststop = screens[0] + vid.rowbytes * vid.height;
-	buf = deststop - vid.rowbytes * ((boxheight * 4) + (boxheight/2)*5); // 4 lines of space plus gaps between and some leeway
+	buf = deststop = screens[0] + vid.rowbytes * vid.height;
+	if (boxheight < 0)
+		buf += vid.rowbytes * boxheight;
+	else // 4 lines of space plus gaps between and some leeway
+		buf -= vid.rowbytes * ((boxheight * 4) + (boxheight/2)*5);
 	for (; buf < deststop; ++buf)
 		*buf = promptbgmap[*buf];
 }
diff --git a/src/win32/win_vid.c b/src/win32/win_vid.c
index 5fa2195866b08329f02c0057b1aaa6d9f384a977..0f1144a842718358a4c5694fee9bd46661749edf 100644
--- a/src/win32/win_vid.c
+++ b/src/win32/win_vid.c
@@ -366,6 +366,9 @@ void I_FinishUpdate(void)
 	if (I_SkipFrame())
 		return;
 
+	if (marathonmode)
+		SCR_DisplayMarathonInfo();
+
 	// draw captions if enabled
 	if (cv_closedcaptioning.value)
 		SCR_ClosedCaptions();
diff --git a/src/y_inter.c b/src/y_inter.c
index 2fe0de60519b7b965387dabd6f42912825d0a4f8..c32182518bfc15519cb5386503afb93660620a20 100644
--- a/src/y_inter.c
+++ b/src/y_inter.c
@@ -392,7 +392,7 @@ dontdrawbg:
 		if (gottoken) // first to be behind everything else
 			Y_IntermissionTokenDrawer();
 
-		if (!splitscreen)
+		if (!splitscreen)  // there's not enough room in splitscreen, don't even bother trying!
 		{
 			// draw score
 			ST_DrawPatchFromHud(HUD_SCORE, sboscore);
@@ -414,7 +414,7 @@ dontdrawbg:
 				ST_DrawPatchFromHud(HUD_TIMECOLON, sbocolon); // Colon
 				ST_DrawPadNumFromHud(HUD_SECONDS, seconds, 2); // Seconds
 
-				if (cv_timetic.value == 1 || cv_timetic.value == 2 || modeattacking) // there's not enough room for tics in splitscreen, don't even bother trying!
+				if (cv_timetic.value == 1 || cv_timetic.value == 2 || modeattacking || marathonmode)
 				{
 					ST_DrawPatchFromHud(HUD_TIMETICCOLON, sboperiod); // Period
 					ST_DrawPadNumFromHud(HUD_TICS, tictrn, 2); // Tics
@@ -1004,7 +1004,7 @@ void Y_Ticker(void)
 	{
 		INT32 i;
 		UINT32 oldscore = data.coop.score;
-		boolean skip = false;
+		boolean skip = (marathonmode) ? true : false;
 		boolean anybonuses = false;
 
 		if (!intertic) // first time only
@@ -1080,7 +1080,7 @@ void Y_Ticker(void)
 	{
 		INT32 i;
 		UINT32 oldscore = data.spec.score;
-		boolean skip = false, super = false, anybonuses = false;
+		boolean skip = (marathonmode) ? true : false, super = false, anybonuses = false;
 
 		if (!intertic) // first time only
 		{