diff --git a/src/command.c b/src/command.c
index d41a551538fa222c85b0919829987f36c9810846..07c084aaa4affdbf28b3c39b82a2fed0f4d7a53a 100644
--- a/src/command.c
+++ b/src/command.c
@@ -2147,14 +2147,14 @@ void CV_AddValue(consvar_t *var, INT32 increment)
 					if(increment > 0) // Going up!
 					{
 						newvalue++;
-						if (newvalue == NUMMAPS)
+						if (newvalue == numgamemaps)
 							newvalue = 0;
 					}
 					else // Going down!
 					{
 						newvalue--;
 						if (newvalue == -1)
-							newvalue = NUMMAPS-1;
+							newvalue = numgamemaps-1;
 					}
 
 					if (newvalue == oldvalue)
@@ -2162,7 +2162,6 @@ void CV_AddValue(consvar_t *var, INT32 increment)
 
 					if(!mapheaderinfo[newvalue])
 						continue; // Don't allocate the header.  That just makes memory usage skyrocket.
-
 				} while (newvalue != oldvalue && !M_CanShowLevelInList(newvalue, gt));
 
 				var->value = newvalue + 1;
diff --git a/src/d_main.c b/src/d_main.c
index bc821cf71f19982ba725b9ee6a7b198baf8fcf75..8489155433f1c9c7ca20b6268c80062eaeb021d6 100644
--- a/src/d_main.c
+++ b/src/d_main.c
@@ -950,13 +950,7 @@ void D_StartTitle(void)
 
 			if (server)
 			{
-				char mapname[6];
-
-				strlcpy(mapname, G_BuildMapName(spstage_start), sizeof (mapname));
-				strlwr(mapname);
-				mapname[5] = '\0';
-
-				COM_BufAddText(va("map %s\n", mapname));
+				COM_BufAddText(va("map %s\n", G_BuildMapName(spstage_start)));
 			}
 		}
 
@@ -1375,6 +1369,8 @@ void D_SRB2Main(void)
 	CONS_Printf("Z_Init(): Init zone memory allocation daemon. \n");
 	Z_Init();
 
+	G_InitMaps();
+
 	clientGamedata = M_NewGameDataStruct();
 	serverGamedata = M_NewGameDataStruct();
 
@@ -1692,7 +1688,7 @@ void D_SRB2Main(void)
 	{
 		pstartmap = bootmap;
 
-		if (pstartmap < 1 || pstartmap > NUMMAPS)
+		if (pstartmap < 1 || pstartmap > numgamemaps)
 			I_Error("Cannot warp to map %d (out of range)\n", pstartmap);
 		else
 		{
@@ -1739,7 +1735,7 @@ void D_SRB2Main(void)
 		if (server && !M_CheckParm("+map"))
 		{
 			// Prevent warping to nonexistent levels
-			if (W_CheckNumForName(G_BuildMapName(pstartmap)) == LUMPERROR)
+			if (!G_MapFileExists(G_BuildMapName(pstartmap)))
 				I_Error("Could not warp to %s (map not found)\n", G_BuildMapName(pstartmap));
 			else
 			{
diff --git a/src/deh_soc.c b/src/deh_soc.c
index 6162034de636e54ec8d35f5292720564de8de32d..0e904dda40dd010fa6677f5b56aa6ab290213e87 100644
--- a/src/deh_soc.c
+++ b/src/deh_soc.c
@@ -168,7 +168,7 @@ void clear_levels(void)
 
 	// This is potentially dangerous but if we're resetting these headers,
 	// we may as well try to save some memory, right?
-	for (i = 0; i < NUMMAPS; ++i)
+	for (i = 0; i < numgamemaps; ++i)
 	{
 		if (!mapheaderinfo[i] || i == (tutorialmap-1))
 			continue;
@@ -1338,6 +1338,36 @@ void readgametype(MYFILE *f, char *gtname)
 	CONS_Printf("Added gametype %s\n", Gametype_Names[newgtidx]);
 }
 
+static INT32 ParseNextLevelName(const char *name)
+{
+	if      (fastcmp(name, "TITLE"))      return MAP_TITLE;
+	else if (fastcmp(name, "EVALUATION")) return MAP_EVALUATION;
+	else if (fastcmp(name, "CREDITS"))    return MAP_CREDITS;
+	else if (fastcmp(name, "ENDING"))     return MAP_ENDING;
+	else
+	{
+		// Support using the actual map name,
+		// i.e., Nextlevel = AB, Nextlevel = FZ, etc.
+
+		// Convert to map number
+		return G_GetMapNumber(name);
+	}
+}
+
+static INT32 ConvertLevelHeaderMapNum(INT32 mapnum)
+{
+	if (mapnum == 1100)
+		return MAP_TITLE;
+	else if (mapnum == 1101)
+		return MAP_EVALUATION;
+	else if (mapnum == 1102)
+		return MAP_CREDITS;
+	else if (mapnum == 1103)
+		return MAP_ENDING;
+	else
+		return mapnum;
+}
+
 void readlevelheader(MYFILE *f, INT32 num)
 {
 	char *s = Z_Malloc(MAXLINELEN, PU_STATIC, NULL);
@@ -1554,33 +1584,17 @@ void readlevelheader(MYFILE *f, INT32 num)
 			}
 			else if (fastcmp(word, "NEXTLEVEL"))
 			{
-				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., Nextlevel = AB, Nextlevel = FZ, etc.
-
-				// Convert to map number
-				if (word2[0] >= 'A' && word2[0] <= 'Z' && word2[2] == '\0')
-					i = M_MapNumber(word2[0], word2[1]);
+				i = ConvertLevelHeaderMapNum(i);
+				if (i == 0)
+					i = ParseNextLevelName(word2);
 
 				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]);
+				i = ConvertLevelHeaderMapNum(i);
+				if (i == 0)
+					i = ParseNextLevelName(word2);
 
 				mapheaderinfo[num-1]->marathonnext = (INT16)i;
 			}
@@ -1995,8 +2009,7 @@ static void readcutscenescene(MYFILE *f, INT32 num, INT32 scenenum)
 			}
 			else if (fastcmp(word, "MUSIC"))
 			{
-				strncpy(cutscenes[num]->scene[scenenum].musswitch, word2, 7);
-				cutscenes[num]->scene[scenenum].musswitch[6] = 0;
+				strlcpy(cutscenes[num]->scene[scenenum].musswitch, word2, MAX_MUSIC_NAME+1);
 			}
 			else if (fastcmp(word, "MUSICTRACK"))
 			{
@@ -2257,8 +2270,7 @@ static void readtextpromptpage(MYFILE *f, INT32 num, INT32 pagenum)
 			}
 			else if (fastcmp(word, "MUSIC"))
 			{
-				strncpy(textprompts[num]->page[pagenum].musswitch, word2, 7);
-				textprompts[num]->page[pagenum].musswitch[6] = 0;
+				strlcpy(textprompts[num]->page[pagenum].musswitch, word2, MAX_MUSIC_NAME+1);
 			}
 			else if (fastcmp(word, "MUSICTRACK"))
 			{
@@ -2581,8 +2593,7 @@ void readmenu(MYFILE *f, INT32 num)
 			}
 			else if (fastcmp(word, "MUSIC"))
 			{
-				strncpy(menupres[num].musname, word2, 7);
-				menupres[num].musname[6] = 0;
+				strlcpy(menupres[num].musname, word2, MAX_MUSIC_NAME+1);
 				titlechanged = true;
 			}
 			else if (fastcmp(word, "MUSICTRACK"))
@@ -2996,12 +3007,12 @@ void reademblemdata(MYFILE *f, INT32 num)
 				emblemlocations[num-1].tag = (INT16)value;
 			else if (fastcmp(word, "MAPNUM"))
 			{
-				// 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]);
+				if (!value)
+				{
+					value = G_GetMapNumber(word2);
+					if (!value)
+						value = get_number(word2);
+				}
 
 				emblemlocations[num-1].level = (INT16)value;
 			}
@@ -3243,6 +3254,12 @@ void readunlockable(MYFILE *f, INT32 num)
 					// Convert to map number
 					if (word2[0] >= 'A' && word2[0] <= 'Z')
 						i = M_MapNumber(word2[0], word2[1]);
+					else if (unlockables[num].type == SECRET_WARP)
+					{
+						INT16 mapnum = G_GetMapNumber(word2);
+						if (mapnum)
+							i = mapnum;
+					}
 
 					unlockables[num].variable = (INT16)i;
 				}
@@ -3316,15 +3333,14 @@ static void readcondition(UINT8 set, UINT32 id, char *word2)
 		PARAMCHECK(1);
 		ty = UC_MAPVISITED + offset;
 
-		// Convert to map number if it appears to be one
-		if (params[1][0] >= 'A' && params[1][0] <= 'Z')
-			re = M_MapNumber(params[1][0], params[1][1]);
-		else
+		// Convert to map number
+		re = G_GetMapNumber(params[1]);
+		if (!re)
 			re = atoi(params[1]);
 
-		if (re <= 0 || re > NUMMAPS)
+		if (re <= 0 || re > numgamemaps)
 		{
-			deh_warning("Level number %d out of range (1 - %d)", re, NUMMAPS);
+			deh_warning("Level number %d out of range (1 - %d)", re, numgamemaps);
 			return;
 		}
 	}
@@ -3336,15 +3352,14 @@ static void readcondition(UINT8 set, UINT32 id, char *word2)
 		ty = UC_MAPSCORE + offset;
 		re = atoi(params[2]);
 
-		// Convert to map number if it appears to be one
-		if (params[1][0] >= 'A' && params[1][0] <= 'Z')
-			x1 = (INT16)M_MapNumber(params[1][0], params[1][1]);
-		else
+		// Convert to map number
+		x1 = G_GetMapNumber(params[1]);
+		if (!x1)
 			x1 = (INT16)atoi(params[1]);
 
-		if (x1 <= 0 || x1 > NUMMAPS)
+		if (x1 <= 0 || x1 > numgamemaps)
 		{
-			deh_warning("Level number %d out of range (1 - %d)", re, NUMMAPS);
+			deh_warning("Level number %d out of range (1 - %d)", re, numgamemaps);
 			return;
 		}
 	}
@@ -3371,15 +3386,14 @@ static void readcondition(UINT8 set, UINT32 id, char *word2)
 		else
 			re = atoi(params[i]);
 
-		// Convert to map number if it appears to be one
-		if (params[1][0] >= 'A' && params[1][0] <= 'Z')
-			x1 = (INT16)M_MapNumber(params[1][0], params[1][1]);
-		else
+		// Convert to map number
+		x1 = G_GetMapNumber(params[1]);
+		if (!x1)
 			x1 = (INT16)atoi(params[1]);
 
-		if (x1 <= 0 || x1 > NUMMAPS)
+		if (x1 <= 0 || x1 > numgamemaps)
 		{
-			deh_warning("Level number %d out of range (1 - %d)", re, NUMMAPS);
+			deh_warning("Level number %d out of range (1 - %d)", re, numgamemaps);
 			return;
 		}
 
@@ -3589,10 +3603,12 @@ void readmaincfg(MYFILE *f)
 				// 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);
+				if (!value)
+				{
+					value = G_GetMapNumber(word2);
+					if (!value)
+						value = get_number(word2);
+				}
 
 				spstage_start = spmarathon_start = (INT16)value;
 			}
@@ -3602,10 +3618,12 @@ void readmaincfg(MYFILE *f)
 				// 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);
+				if (!value)
+				{
+					value = G_GetMapNumber(word2);
+					if (!value)
+						value = get_number(word2);
+				}
 
 				spmarathon_start = (INT16)value;
 			}
@@ -3615,10 +3633,12 @@ void readmaincfg(MYFILE *f)
 				// 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);
+				if (!value)
+				{
+					value = G_GetMapNumber(word2);
+					if (!value)
+						value = get_number(word2);
+				}
 
 				sstage_start = (INT16)value;
 				sstage_end = (INT16)(sstage_start+7); // 7 special stages total plus one weirdo
@@ -3629,10 +3649,12 @@ void readmaincfg(MYFILE *f)
 				// 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);
+				if (!value)
+				{
+					value = G_GetMapNumber(word2);
+					if (!value)
+						value = get_number(word2);
+				}
 
 				smpstage_start = (INT16)value;
 				smpstage_end = (INT16)(smpstage_start+6); // 7 special stages total
@@ -3723,10 +3745,12 @@ void readmaincfg(MYFILE *f)
 				// 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);
+				if (!value)
+				{
+					value = G_GetMapNumber(word2);
+					if (!value)
+						value = get_number(word2);
+				}
 
 				titlemap = (INT16)value;
 				titlechanged = true;
@@ -3895,10 +3919,12 @@ void readmaincfg(MYFILE *f)
 				// 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);
+				if (!value)
+				{
+					value = G_GetMapNumber(word2);
+					if (!value)
+						value = get_number(word2);
+				}
 
 				bootmap = (INT16)value;
 				//titlechanged = true;
@@ -3914,10 +3940,12 @@ void readmaincfg(MYFILE *f)
 				// 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);
+				if (!value)
+				{
+					value = G_GetMapNumber(word2);
+					if (!value)
+						value = get_number(word2);
+				}
 
 				tutorialmap = (INT16)value;
 			}
diff --git a/src/dehacked.c b/src/dehacked.c
index fd2a701715e17ce7dab0b6c829147113844972fe..142459fece3b3ccfaf78898df495a3dff42921b0 100644
--- a/src/dehacked.c
+++ b/src/dehacked.c
@@ -364,18 +364,18 @@ static void DEH_LoadDehackedFile(MYFILE *f, boolean mainfile)
 				}
 				else if (fastcmp(word, "LEVEL"))
 				{
-					// Support using the actual map name,
-					// i.e., Level AB, Level FZ, etc.
-
-					// Convert to map number
-					if (word2[0] >= 'A' && word2[0] <= 'Z')
-						i = M_MapNumber(word2[0], word2[1]);
+					if (!isdigit(word2[0]))
+					{
+						INT16 mapnum = G_GetMapNumber(word2);
+						if (mapnum != 0)
+							i = mapnum;
+					}
 
-					if (i > 0 && i <= NUMMAPS)
+					if (i > 0 && i <= numgamemaps)
 						readlevelheader(f, i);
 					else
 					{
-						deh_warning("Level number %d out of range (1 - %d)", i, NUMMAPS);
+						deh_warning("Level number %d out of range (1 - %d)", i, numgamemaps);
 						ignorelines(f);
 					}
 				}
diff --git a/src/doomdata.h b/src/doomdata.h
index 276e03297b6f0d453ad0d0c65fc8bd9196d9680c..33051da656b9237a4ae11c3fb43398422dd4cec6 100644
--- a/src/doomdata.h
+++ b/src/doomdata.h
@@ -223,6 +223,15 @@ typedef struct
 
 #define ZSHIFT 4
 
-#define NUMMAPS 1035
+#define MAXMAPS 16386
+
+#define MAX_MAP_NAME_SIZE 256 // This is an arbitrary limit to prevent exceedingly long map names.
+
+#define MAP_TITLE (MAXMAPS)
+#define MAP_EVALUATION (MAXMAPS+2)
+#define MAP_CREDITS (MAXMAPS+3)
+#define MAP_ENDING (MAXMAPS+4)
+
+#define NUMBASEMAPS 1035 // MAP01 to MAPZZ
 
 #endif // __DOOMDATA__
diff --git a/src/doomdef.h b/src/doomdef.h
index 4e08b11bfb0116c2f60fffc33582d6c9a56f3b27..2626e8f54d6e4e7e27b44227ee83fda3a24ddeb6 100644
--- a/src/doomdef.h
+++ b/src/doomdef.h
@@ -451,6 +451,8 @@ extern skincolor_t skincolors[MAXSKINCOLORS];
 
 #define MUSICRATE 1000 // sound timing is calculated by milliseconds
 
+#define MAX_MUSIC_NAME 64
+
 #define RING_DIST 512*FRACUNIT // how close you need to be to a ring to attract it
 
 #define PUSHACCEL (2*FRACUNIT) // Acceleration for MF2_SLIDEPUSH items.
diff --git a/src/doomstat.h b/src/doomstat.h
index 6a2d6acf00f816804c1ec8830b69c9848deadcbe..3141e7cc4446098ed3b5aed16bd4237969e7c90c 100644
--- a/src/doomstat.h
+++ b/src/doomstat.h
@@ -31,7 +31,7 @@
 
 // Selected by user.
 extern INT16 gamemap;
-extern char mapmusname[7];
+extern char mapmusname[MAX_MUSIC_NAME+1];
 extern UINT16 mapmusflags;
 extern UINT32 mapmusposition;
 #define MUSIC_TRACKMASK   0x0FFF // ----************
@@ -79,6 +79,7 @@ extern boolean usedCheats;
 extern boolean disableSpeedAdjust; // Don't alter the duration of player states if true
 extern boolean imcontinuing; // Temporary flag while continuing
 extern boolean metalrecording;
+extern boolean replacedcurrentmap;
 
 #define ATTACKING_NONE   0
 #define ATTACKING_RECORD 1
@@ -175,7 +176,7 @@ typedef struct
 	UINT16 textxpos;
 	UINT16 textypos;
 
-	char   musswitch[7];
+	char   musswitch[MAX_MUSIC_NAME+1];
 	UINT16 musswitchflags;
 	UINT32 musswitchposition;
 
@@ -215,7 +216,7 @@ typedef struct
 	UINT16 ycoord[MAX_PROMPT_PICS]; // gfx
 	UINT16 picduration[MAX_PROMPT_PICS];
 
-	char   musswitch[7];
+	char   musswitch[MAX_MUSIC_NAME+1];
 	UINT16 musswitchflags;
 	UINT8 musicloop;
 
@@ -277,6 +278,16 @@ extern struct quake
 	fixed_t radius, intensity;
 } quake;
 
+typedef struct
+{
+	UINT32 hash;
+	char *name;
+	char *thumbnail;
+	char *thumbnail_wide;
+	char *music;
+	char *metal_replay;
+} gamemap_t;
+
 // NiGHTS grades
 typedef struct
 {
@@ -303,7 +314,7 @@ typedef struct
 	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.
+	char musname[MAX_MUSIC_NAME+1]; ///< 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.
 	UINT32 muspos;    ///< Music position to jump to.
 	char forcecharacter[17];  ///< (SKINNAMESIZE+1) Skin to switch to or "" to disable.
@@ -351,9 +362,9 @@ typedef struct
 
 	// Music stuff.
 	UINT32 musinterfadeout;  ///< Fade out level music on intermission screen in milliseconds
-	char musintername[7];    ///< Intermission screen music.
+	char musintername[MAX_MUSIC_NAME+1]; ///< Intermission screen music.
 
-	char muspostbossname[7];    ///< Post-bossdeath music.
+	char muspostbossname[MAX_MUSIC_NAME+1]; ///< Post-bossdeath music.
 	UINT16 muspostbosstrack;    ///< Post-bossdeath track.
 	UINT32 muspostbosspos;      ///< Post-bossdeath position
 	UINT32 muspostbossfadein;   ///< Post-bossdeath fade-in milliseconds.
@@ -388,7 +399,11 @@ typedef struct
 #define LF2_NOVISITNEEDED 16 ///< Available in time attack/nights mode without visiting the level
 #define LF2_WIDEICON      32 ///< If you're in a circumstance where it fits, use a wide map icon
 
-extern mapheader_t* mapheaderinfo[NUMMAPS];
+extern mapheader_t* mapheaderinfo[MAXMAPS];
+
+extern gamemap_t gamemaps[MAXMAPS];
+
+extern UINT16 numgamemaps;
 
 // Gametypes
 #define NUMGAMETYPEFREESLOTS 128
diff --git a/src/f_finale.c b/src/f_finale.c
index 5dc18115c9b07f37e3e36db788ed88489e011ef3..12be1393c4a7040950f58a951695ad2552f44f81 100644
--- a/src/f_finale.c
+++ b/src/f_finale.c
@@ -3914,7 +3914,7 @@ void F_EndCutScene(void)
 			F_StartGameEvaluation();
 		else if (cutnum == introtoplay-1)
 			D_StartTitle();
-		else if (nextmap < 1100-1)
+		else if (!G_IsGameEndMap(nextmap+1))
 			G_NextLevel();
 		else
 			G_EndGame();
diff --git a/src/g_demo.c b/src/g_demo.c
index f39efad8e68fe81a37eed65925dc8c1ae394dae3..3df703a19187cb287be10196d97a3a396e4418f1 100644
--- a/src/g_demo.c
+++ b/src/g_demo.c
@@ -46,7 +46,7 @@ boolean nodrawers; // for comparative timing purposes
 boolean noblit; // for comparative timing purposes
 tic_t demostarttime; // for comparative timing purposes
 
-static char demoname[64];
+static char demoname[512];
 boolean demorecording;
 boolean demoplayback;
 boolean titledemo; // Title Screen demo can be cancelled by any key
@@ -1405,15 +1405,14 @@ void G_WriteMetalTic(mobj_t *metal)
 //
 void G_RecordDemo(const char *name)
 {
-	INT32 maxsize;
+	strlcpy(demoname, name, sizeof(demoname));
 
-	strcpy(demoname, name);
-	strcat(demoname, ".lmp");
-	maxsize = 1024*1024;
+	FIL_ForceExtension(demoname, ".lmp");
+
+	INT32 maxsize = 1024*1024;
 	if (M_CheckParm("-maxdemo") && M_IsNextParm())
 		maxsize = atoi(M_GetNextParm()) * 1024;
-//	if (demobuffer)
-//		free(demobuffer);
+
 	demo_p = NULL;
 	demobuffer = malloc(maxsize);
 	demoend = demobuffer + maxsize;
@@ -2654,7 +2653,7 @@ void G_DoPlayMetal(void)
 	thinker_t *th;
 
 	// it's an internal demo
-	if ((l = W_CheckNumForName(va("%sMS",G_BuildMapName(gamemap)))) == LUMPERROR)
+	if ((l = W_CheckNumForName(G_GetMapMetalSonicReplay(gamemap))) == LUMPERROR)
 	{
 		CONS_Alert(CONS_WARNING, M_GetText("No bot recording for this map.\n"));
 		return;
@@ -2780,7 +2779,7 @@ ATTRNORETURN void FUNCNORETURN G_StopMetalRecording(boolean kill)
 	{
 		WRITEUINT8(demo_p, (kill) ? METALDEATH : DEMOMARKER); // add the demo end (or metal death) marker
 		WriteDemoChecksum();
-		sprintf(demoname, "%sMS.LMP", G_BuildMapName(gamemap));
+		snprintf(demoname, sizeof(demoname), "%s.lmp", G_GetMapMetalSonicReplay(gamemap));
 		saved = FIL_WriteFile(va(pandf, srb2home, demoname), demobuffer, demo_p - demobuffer); // finally output the file.
 	}
 	free(demobuffer);
diff --git a/src/g_game.c b/src/g_game.c
index 121672fa7995fb55c355472bf43571a9e277377c..28e7646a8b7fd1b5b0ad437848364e41be39ae28 100644
--- a/src/g_game.c
+++ b/src/g_game.c
@@ -78,7 +78,7 @@ static void G_DoStartContinue(void);
 static void G_DoContinued(void);
 static void G_DoWorldDone(void);
 
-char   mapmusname[7]; // Music name
+char   mapmusname[MAX_MUSIC_NAME+1]; // Music name
 UINT16 mapmusflags; // Track and reset bit
 UINT32 mapmusposition; // Position to jump to
 
@@ -105,6 +105,7 @@ UINT8 paused;
 UINT8 modeattacking = ATTACKING_NONE;
 boolean disableSpeedAdjust = false;
 boolean imcontinuing = false;
+boolean replacedcurrentmap = false;
 boolean runemeraldmanager = false;
 UINT16 emeraldspawndelay = 60*TICRATE;
 
@@ -172,7 +173,10 @@ mapthing_t *bflagpoint;
 struct quake quake;
 
 // Map Header Information
-mapheader_t* mapheaderinfo[NUMMAPS] = {NULL};
+mapheader_t* mapheaderinfo[MAXMAPS] = {NULL};
+
+gamemap_t gamemaps[MAXMAPS];
+UINT16 numgamemaps = 0;
 
 static boolean exitgame = false;
 static boolean retrying = false;
@@ -463,7 +467,7 @@ void G_AllocNightsRecordData(INT16 i, gamedata_t *data)
 void G_ClearRecords(gamedata_t *data)
 {
 	INT16 i;
-	for (i = 0; i < NUMMAPS; ++i)
+	for (i = 0; i < numgamemaps; ++i)
 	{
 		if (data->mainrecords[i])
 		{
@@ -571,7 +575,8 @@ static void G_SetMainRecords(gamedata_t *data, player_t *player)
 
 	if (modeattacking)
 	{
-		const size_t glen = strlen(srb2home)+1+strlen("replay")+1+strlen(timeattackfolder)+1+strlen("MAPXX")+1;
+		const char *mapname = G_BuildMapName(gamemap);
+		const size_t glen = strlen(srb2home)+1+strlen("replay")+1+strlen(timeattackfolder)+1+strlen(mapname)+1;
 		char *gpath;
 		char lastdemo[256], bestdemo[256];
 
@@ -587,7 +592,7 @@ static void G_SetMainRecords(gamedata_t *data, player_t *player)
 		if ((gpath = malloc(glen)) == NULL)
 			I_Error("Out of memory for replay filepath\n");
 
-		sprintf(gpath,"%s"PATHSEP"replay"PATHSEP"%s"PATHSEP"%s", srb2home, timeattackfolder, G_BuildMapName(gamemap));
+		sprintf(gpath,"%s"PATHSEP"replay"PATHSEP"%s"PATHSEP"%s", srb2home, timeattackfolder, mapname);
 		snprintf(lastdemo, 255, "%s-%s-last.lmp", gpath, skins[cv_chooseskin.value-1].name);
 
 		if (FIL_FileExists(lastdemo))
@@ -709,7 +714,8 @@ static void G_SetNightsRecords(gamedata_t *data, player_t *player)
 
 	if (modeattacking)
 	{
-		const size_t glen = strlen(srb2home)+1+strlen("replay")+1+strlen(timeattackfolder)+1+strlen("MAPXX")+1;
+		const char *mapname = G_BuildMapName(gamemap);
+		const size_t glen = strlen(srb2home)+1+strlen("replay")+1+strlen(timeattackfolder)+1+strlen(mapname)+1;
 		char *gpath;
 		char lastdemo[256], bestdemo[256];
 
@@ -725,7 +731,7 @@ static void G_SetNightsRecords(gamedata_t *data, player_t *player)
 		if ((gpath = malloc(glen)) == NULL)
 			I_Error("Out of memory for replay filepath\n");
 
-		sprintf(gpath,"%s"PATHSEP"replay"PATHSEP"%s"PATHSEP"%s", srb2home, timeattackfolder, G_BuildMapName(gamemap));
+		sprintf(gpath,"%s"PATHSEP"replay"PATHSEP"%s"PATHSEP"%s", srb2home, timeattackfolder, mapname);
 		snprintf(lastdemo, 255, "%s-%s-last.lmp", gpath, skins[cv_chooseskin.value-1].name);
 
 		if (FIL_FileExists(lastdemo))
@@ -803,6 +809,20 @@ void G_SetUsedCheats(boolean silent)
 		Command_ExitGame_f();
 }
 
+/** Gets a game map name from a map number.
+  *
+  * \param map Map number.
+  * \return The desired map name.
+  * \sa M_MapNumber
+  */
+const char *G_BuildMapName(INT32 map)
+{
+	if (map > 0 && map <= numgamemaps)
+		return gamemaps[map - 1].name;
+
+	return "UNKNOWN";
+}
+
 /** Builds an original game map name from a map number.
   * The complexity is due to MAPA0-MAPZZ.
   *
@@ -810,12 +830,12 @@ void G_SetUsedCheats(boolean silent)
   * \return Pointer to a static buffer containing the desired map name.
   * \sa M_MapNumber
   */
-const char *G_BuildMapName(INT32 map)
+const char *G_BuildClassicMapName(INT32 map)
 {
 	static char mapname[10] = "MAPXX"; // internal map name (wad resource name)
 
 	I_Assert(map > 0);
-	I_Assert(map <= NUMMAPS);
+	I_Assert(map <= NUMBASEMAPS);
 
 	if (map < 100)
 		sprintf(&mapname[3], "%.2d", map);
@@ -832,6 +852,158 @@ const char *G_BuildMapName(INT32 map)
 	return mapname;
 }
 
+void G_InitMaps(void)
+{
+	for (UINT16 i = 0; i < NUMBASEMAPS; i++)
+	{
+		const char *name = G_BuildClassicMapName(i + 1);
+		G_AddMap(name);
+	}
+}
+
+UINT16 G_GetMapNumber(const char *name)
+{
+	size_t name_len = strlen(name);
+
+	// Special case
+	if (name_len == 2 && name[0] >= 'A' && name[0] <= 'Z')
+	{
+		return M_MapNumber(name[0], name[1]);
+	}
+
+	UINT32 hash = quickncasehash(name, name_len);
+
+	for (UINT16 i = 0; i < numgamemaps; i++)
+	{
+		if (hash == gamemaps[i].hash && stricmp(gamemaps[i].name, name) == 0)
+			return i + 1;
+	}
+
+	return 0;
+}
+
+UINT16 G_AddMap(const char *name)
+{
+	if (numgamemaps == MAXMAPS)
+		return 0;
+
+	UINT16 mapnum = G_GetMapNumber(name);
+	if (mapnum != 0)
+	{
+		// That map already exists, silly.
+		return mapnum;
+	}
+
+	size_t name_len = strlen(name);
+	if (name_len > MAX_MAP_NAME_SIZE)
+		return 0;
+
+	gamemaps[numgamemaps].hash = quickncasehash(name, name_len);
+	gamemaps[numgamemaps].name = Z_StrDup(name);
+
+	strupr(gamemaps[numgamemaps].name);
+
+	numgamemaps++;
+
+	CONS_Debug(DBG_SETUP, "Added map %d (%s)\n", numgamemaps, name);
+
+	return numgamemaps;
+}
+
+boolean G_MapFileExists(const char *name)
+{
+	return W_CheckNumForLongName(name) != LUMPERROR;
+}
+
+boolean G_IsValidMapName(const char *name)
+{
+	if (name[0] == '\0' || !isalpha(name[0]))
+		return false;
+
+	return true;
+}
+
+static char *BuildCombinedMapString(INT16 map, const char *newfmt, const char *oldfmt)
+{
+	const char *mapname = gamemaps[map].name;
+	const char *fmt = (map < NUMBASEMAPS) ? oldfmt : newfmt;
+
+	size_t len = strlen(mapname) + strlen(fmt) + 1;
+
+	char *text = Z_Malloc(len, PU_STATIC, NULL);
+
+	snprintf(text, len, fmt, mapname);
+
+	return text;
+}
+
+const char *G_GetMapThumbnail(INT16 map)
+{
+	map--;
+
+	if (map < 0 || map >= numgamemaps)
+		return "";
+
+	// This is done lazily -- it's not created until it's needed.
+	if (gamemaps[map].thumbnail == NULL)
+		gamemaps[map].thumbnail = BuildCombinedMapString(map, "%s_PIC", "%sP");
+
+	return gamemaps[map].thumbnail;
+}
+
+const char *G_GetMapThumbnailWide(INT16 map)
+{
+	map--;
+
+	if (map < 0 || map >= numgamemaps)
+		return "";
+
+	if (gamemaps[map].thumbnail_wide == NULL)
+		gamemaps[map].thumbnail_wide = BuildCombinedMapString(map, "%s_WIDEPIC", "%sW");
+
+	return gamemaps[map].thumbnail_wide;
+}
+
+const char *G_GetDefaultMapMusic(INT16 map)
+{
+	map--;
+
+	if (map < 0 || map >= numgamemaps)
+		return "";
+
+	if (gamemaps[map].music == NULL)
+		gamemaps[map].music = BuildCombinedMapString(map, "%s", "%sM");
+
+	return gamemaps[map].music;
+}
+
+const char *G_GetMapMetalSonicReplay(INT16 map)
+{
+	map--;
+
+	if (map < 0 || map >= numgamemaps)
+		return "";
+
+	if (gamemaps[map].metal_replay == NULL)
+		gamemaps[map].metal_replay = BuildCombinedMapString(map, "%s_METALREPLAY", "%sMS");
+
+	return gamemaps[map].metal_replay;
+}
+
+boolean G_IsGameEndMap(INT16 mapnum)
+{
+	switch (mapnum)
+	{
+		case MAP_TITLE:
+		case MAP_EVALUATION:
+		case MAP_CREDITS:
+		case MAP_ENDING:
+			return true;
+		default:
+			return false;
+	}
+}
+
 /** Clips the console player's mouse aiming to the current view.
   * Used whenever the player view is changed manually.
   *
@@ -1853,7 +2025,7 @@ void G_DoLoadLevel(boolean resetplayer)
 	// cleanup
 	if (titlemapinaction == TITLEMAP_LOADING)
 	{
-		if (W_CheckNumForName(G_BuildMapName(gamemap)) == LUMPERROR)
+		if (!G_MapFileExists(G_BuildMapName(gamemap)))
 		{
 			titlemap = 0; // let's not infinite recursion ok
 			Command_ExitGame_f();
@@ -2770,8 +2942,7 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps)
 	{
 		if (mapmusflags & MUSIC_RELOADRESET)
 		{
-			strncpy(mapmusname, mapheaderinfo[gamemap-1]->musname, 7);
-			mapmusname[6] = 0;
+			strlcpy(mapmusname, mapheaderinfo[gamemap-1]->musname, MAX_MUSIC_NAME+1);
 			mapmusflags = (mapheaderinfo[gamemap-1]->mustrack & MUSIC_TRACKMASK);
 			mapmusposition = mapheaderinfo[gamemap-1]->muspos;
 		}
@@ -3847,12 +4018,12 @@ UINT32 G_TOLFlag(INT32 pgametype)
   */
 static INT16 RandMap(UINT32 tolflags, INT16 pprevmap)
 {
-	INT16 *okmaps = Z_Malloc(NUMMAPS * sizeof(INT16), PU_STATIC, NULL);
+	INT16 *okmaps = Z_Malloc(numgamemaps * sizeof(INT16), PU_STATIC, NULL);
 	INT32 numokmaps = 0;
 	INT16 ix;
 
 	// Find all the maps that are ok and and put them in an array.
-	for (ix = 0; ix < NUMMAPS; ix++)
+	for (ix = 0; ix < numgamemaps; ix++)
 		if (mapheaderinfo[ix] && (mapheaderinfo[ix]->typeoflevel & tolflags) == tolflags
 		 && ix != pprevmap // Don't pick the same map.
 		 && (!M_MapLocked(ix+1, serverGamedata)) // Don't pick locked maps.
@@ -4060,13 +4231,11 @@ static void G_DoCompleted(void)
 	// for instance).
 	if (!spec || nextmapoverride)
 	{
-		if (nextmap >= 0 && nextmap < NUMMAPS)
+		if (nextmap >= 0 && nextmap < numgamemaps)
 		{
 			INT16 cm = nextmap;
 			UINT32 tolflag = G_TOLFlag(gametype_to_use);
-			UINT8 visitedmap[(NUMMAPS+7)/8];
-
-			memset(visitedmap, 0, sizeof (visitedmap));
+			UINT8 *visitedmap = Z_Calloc(((numgamemaps+7)/8) * sizeof(UINT8), PU_STATIC, NULL);
 
 			while (!mapheaderinfo[cm] || !(mapheaderinfo[cm]->typeoflevel & tolflag))
 			{
@@ -4078,12 +4247,12 @@ static void G_DoCompleted(void)
 				else
 					cm = (INT16)(mapheaderinfo[cm]->nextlevel-1);
 
-				if (cm >= NUMMAPS || cm < 0) // out of range (either 1100ish or error)
+				if (cm >= numgamemaps || cm < 0) // out of range (either 1100ish or error)
 				{
 					cm = nextmap; //Start the loop again so that the error checking below is executed.
 
 					//Make sure the map actually exists before you try to go to it!
-					if ((W_CheckNumForName(G_BuildMapName(cm + 1)) == LUMPERROR))
+					if (!G_MapFileExists(G_BuildMapName(cm + 1)))
 					{
 						CONS_Alert(CONS_ERROR, M_GetText("Next map given (MAP %d) doesn't exist! Reverting to MAP01.\n"), cm+1);
 						cm = 0;
@@ -4100,14 +4269,17 @@ static void G_DoCompleted(void)
 					break;
 				}
 			}
+
+			Z_Free(visitedmap);
+
 			nextmap = cm;
 		}
 
 		// wrap around in race
-		if (nextmap >= 1100-1 && nextmap <= 1102-1 && !(gametyperules & GTR_CAMPAIGN))
+		if (G_IsGameEndMap(nextmap+1) && !(gametyperules & GTR_CAMPAIGN))
 			nextmap = (INT16)(spstage_start-1);
 
-		if (nextmap < 0 || (nextmap >= NUMMAPS && nextmap < 1100-1) || nextmap > 1103-1)
+		if (nextmap < 0 || (nextmap >= numgamemaps && !G_IsGameEndMap(nextmap+1)))
 			I_Error("Followed map %d to invalid map %d\n", prevmap + 1, nextmap + 1);
 
 		if (!spec)
@@ -4149,7 +4321,7 @@ static void G_DoCompleted(void)
 	// We are committed to this map now.
 	// We may as well allocate its header if it doesn't exist
 	// (That is, if it's a real map)
-	if (nextmap < NUMMAPS && !mapheaderinfo[nextmap])
+	if (nextmap < numgamemaps && !mapheaderinfo[nextmap])
 		P_AllocMapHeader(nextmap);
 
 	Y_DetermineIntermissionType();
@@ -4195,7 +4367,7 @@ void G_AfterIntermission(void)
 	}
 	else
 	{
-		if (nextmap < 1100-1)
+		if (!G_IsGameEndMap(nextmap+1))
 			G_NextLevel();
 		else
 			G_EndGame();
@@ -4315,17 +4487,17 @@ void G_EndGame(void)
 	// Only do evaluation and credits in coop games.
 	if (gametyperules & GTR_CUTSCENES)
 	{
-		if (nextmap == 1103-1) // end game with ending
+		if (nextmap+1 == MAP_ENDING) // end game with ending
 		{
 			F_StartEnding();
 			return;
 		}
-		if (nextmap == 1102-1) // end game with credits
+		else if (nextmap+1 == MAP_CREDITS) // end game with credits
 		{
 			F_StartCredits();
 			return;
 		}
-		if (nextmap == 1101-1) // end game with evaluation
+		else if (nextmap+1 == MAP_EVALUATION) // end game with evaluation
 		{
 			F_StartGameEvaluation();
 			return;
@@ -4356,6 +4528,62 @@ void G_LoadGameSettings(void)
 
 #define GAMEDATA_ID 0x86E4A27C // Change every major version, as usual
 #define COMPAT_GAMEDATA_ID 0xFCAFE211 // TODO: 2.3: Delete
+#define EXTRA_DATA_MARKER 0x71B9F853 // TODO: 2.3: Refactor all related code, then delete this
+
+static boolean ReadMapVisited(gamedata_t *data, UINT16 i)
+{
+	if ((data->mapvisited[i] = READUINT8(save_p)) > MV_MAX)
+		return false;
+
+	return true;
+}
+
+static boolean ReadMainRecords(gamedata_t *data, UINT16 i)
+{
+	UINT32 recscore = READUINT32(save_p);
+	tic_t rectime  = (tic_t)READUINT32(save_p);
+	UINT16 recrings = READUINT16(save_p);
+	save_p++; // compat
+
+	if (recrings > 10000 || recscore > MAXSCORE)
+		return false;
+
+	if (recscore || rectime || recrings)
+	{
+		G_AllocMainRecordData((INT16)i, data);
+		data->mainrecords[i]->score = recscore;
+		data->mainrecords[i]->time = rectime;
+		data->mainrecords[i]->rings = recrings;
+	}
+
+	return true;
+}
+
+static boolean ReadNightsRecords(gamedata_t *data, UINT16 i)
+{
+	UINT8 recmares;
+
+	if ((recmares = READUINT8(save_p)) == 0)
+		return true;
+
+	G_AllocNightsRecordData((INT16)i, data);
+
+	for (INT32 curmare = 0; curmare < (recmares+1); ++curmare)
+	{
+		data->nightsrecords[i]->score[curmare] = READUINT32(save_p);
+		data->nightsrecords[i]->grade[curmare] = READUINT8(save_p);
+		data->nightsrecords[i]->time[curmare] = (tic_t)READUINT32(save_p);
+
+		if (data->nightsrecords[i]->grade[curmare] > GRADE_S)
+		{
+			return false;
+		}
+	}
+
+	data->nightsrecords[i]->nummares = recmares;
+
+	return true;
+}
 
 // G_LoadGameData
 // Loads the main data file, which stores information such as emblems found, etc.
@@ -4367,14 +4595,6 @@ void G_LoadGameData(gamedata_t *data)
 	UINT32 versionID;
 	UINT8 rtemp;
 
-	//For records
-	UINT32 recscore;
-	tic_t  rectime;
-	UINT16 recrings;
-
-	UINT8 recmares;
-	INT32 curmare;
-
 	// Stop saving, until we successfully load it again.
 	data->loaded = false;
 
@@ -4482,9 +4702,12 @@ void G_LoadGameData(gamedata_t *data)
 	}
 
 	// TODO put another cipher on these things? meh, I don't care...
-	for (i = 0; i < NUMMAPS; i++)
-		if ((data->mapvisited[i] = READUINT8(save_p)) > MV_MAX)
+	// Read map visited flags
+	for (i = 0; i < NUMBASEMAPS; i++)
+	{
+		if (!ReadMapVisited(data, i))
 			goto datacorrupt;
+	}
 
 	// To save space, use one bit per collected/achieved/unlocked flag
 	for (i = 0; i < max_emblems;)
@@ -4521,47 +4744,57 @@ void G_LoadGameData(gamedata_t *data)
 	data->timesBeatenUltimate = READUINT32(save_p);
 
 	// Main records
-	for (i = 0; i < NUMMAPS; ++i)
+	for (i = 0; i < NUMBASEMAPS; ++i)
 	{
-		recscore = READUINT32(save_p);
-		rectime  = (tic_t)READUINT32(save_p);
-		recrings = READUINT16(save_p);
-		save_p++; // compat
-
-		if (recrings > 10000 || recscore > MAXSCORE)
+		if (!ReadMainRecords(data, i))
 			goto datacorrupt;
-
-		if (recscore || rectime || recrings)
-		{
-			G_AllocMainRecordData((INT16)i, data);
-			data->mainrecords[i]->score = recscore;
-			data->mainrecords[i]->time = rectime;
-			data->mainrecords[i]->rings = recrings;
-		}
 	}
 
 	// Nights records
-	for (i = 0; i < NUMMAPS; ++i)
+	for (i = 0; i < NUMBASEMAPS; ++i)
 	{
-		if ((recmares = READUINT8(save_p)) == 0)
-			continue;
+		if (!ReadNightsRecords(data, i))
+			goto datacorrupt;
+	}
+
+#ifdef EXTRA_DATA_MARKER
+	// Read extra data
+	if (save_p < savebuffer+length && (length - (save_p-savebuffer)) >= 4)
+	{
+		UINT32 marker = READUINT32(save_p);
+		if (marker != EXTRA_DATA_MARKER)
+			goto datacorrupt;
+
+		UINT8 extraID = READUINT8(save_p);
+		if (extraID != 0x00)
+			goto datacorrupt;
 
-		G_AllocNightsRecordData((INT16)i, data);
+		UINT16 nummaps = READUINT16(save_p);
+		if (nummaps >= MAXMAPS)
+			goto datacorrupt;
 
-		for (curmare = 0; curmare < (recmares+1); ++curmare)
+		// Read map visited flags
+		for (i = NUMBASEMAPS; i < nummaps; i++)
 		{
-			data->nightsrecords[i]->score[curmare] = READUINT32(save_p);
-			data->nightsrecords[i]->grade[curmare] = READUINT8(save_p);
-			data->nightsrecords[i]->time[curmare] = (tic_t)READUINT32(save_p);
+			if (!ReadMapVisited(data, i))
+				goto datacorrupt;
+		}
 
-			if (data->nightsrecords[i]->grade[curmare] > GRADE_S)
-			{
+		// Main records
+		for (i = NUMBASEMAPS; i < nummaps; i++)
+		{
+			if (!ReadMainRecords(data, i))
 				goto datacorrupt;
-			}
 		}
 
-		data->nightsrecords[i]->nummares = recmares;
+		// Nights records
+		for (i = NUMBASEMAPS; i < nummaps; i++)
+		{
+			if (!ReadNightsRecords(data, i))
+				goto datacorrupt;
+		}
 	}
+#endif
 
 	// done
 	Z_Free(savebuffer);
@@ -4593,6 +4826,41 @@ void G_LoadGameData(gamedata_t *data)
 	}
 }
 
+static void WriteMainRecords(gamedata_t *data, UINT16 i)
+{
+	if (data->mainrecords[i])
+	{
+		WRITEUINT32(save_p, data->mainrecords[i]->score);
+		WRITEUINT32(save_p, data->mainrecords[i]->time);
+		WRITEUINT16(save_p, data->mainrecords[i]->rings);
+	}
+	else
+	{
+		WRITEUINT32(save_p, 0);
+		WRITEUINT32(save_p, 0);
+		WRITEUINT16(save_p, 0);
+	}
+	WRITEUINT8(save_p, 0); // compat
+}
+
+static void WriteNightsRecords(gamedata_t *data, UINT16 i)
+{
+	if (!data->nightsrecords[i] || !data->nightsrecords[i]->nummares)
+	{
+		WRITEUINT8(save_p, 0);
+		return;
+	}
+
+	WRITEUINT8(save_p, data->nightsrecords[i]->nummares);
+
+	for (INT32 curmare = 0; curmare < (data->nightsrecords[i]->nummares + 1); ++curmare)
+	{
+		WRITEUINT32(save_p, data->nightsrecords[i]->score[curmare]);
+		WRITEUINT8(save_p, data->nightsrecords[i]->grade[curmare]);
+		WRITEUINT32(save_p, data->nightsrecords[i]->time[curmare]);
+	}
+}
+
 // G_SaveGameData
 // Saves the main data file, which stores information such as emblems found, etc.
 void G_SaveGameData(gamedata_t *data)
@@ -4601,8 +4869,6 @@ void G_SaveGameData(gamedata_t *data)
 	INT32 i, j;
 	UINT8 btemp;
 
-	INT32 curmare;
-
 	if (!data->loaded)
 		return; // If never loaded (-nodata), don't save
 
@@ -4628,7 +4894,7 @@ void G_SaveGameData(gamedata_t *data)
 	WRITEUINT32(save_p, quickncasehash(timeattackfolder, sizeof timeattackfolder));
 
 	// TODO put another cipher on these things? meh, I don't care...
-	for (i = 0; i < NUMMAPS; i++)
+	for (i = 0; i < NUMBASEMAPS; i++)
 		WRITEUINT8(save_p, (data->mapvisited[i] & MV_MAX));
 
 	// To save space, use one bit per collected/achieved/unlocked flag
@@ -4670,41 +4936,35 @@ void G_SaveGameData(gamedata_t *data)
 	WRITEUINT32(save_p, data->timesBeatenUltimate);
 
 	// Main records
-	for (i = 0; i < NUMMAPS; i++)
-	{
-		if (data->mainrecords[i])
-		{
-			WRITEUINT32(save_p, data->mainrecords[i]->score);
-			WRITEUINT32(save_p, data->mainrecords[i]->time);
-			WRITEUINT16(save_p, data->mainrecords[i]->rings);
-		}
-		else
-		{
-			WRITEUINT32(save_p, 0);
-			WRITEUINT32(save_p, 0);
-			WRITEUINT16(save_p, 0);
-		}
-		WRITEUINT8(save_p, 0); // compat
-	}
+	for (i = 0; i < NUMBASEMAPS; i++)
+		WriteMainRecords(data, i);
 
 	// NiGHTS records
-	for (i = 0; i < NUMMAPS; i++)
+	for (i = 0; i < NUMBASEMAPS; i++)
+		WriteNightsRecords(data, i);
+
+#ifdef EXTRA_DATA_MARKER
+	if (numgamemaps > NUMBASEMAPS)
 	{
-		if (!data->nightsrecords[i] || !data->nightsrecords[i]->nummares)
-		{
-			WRITEUINT8(save_p, 0);
-			continue;
-		}
+		// Write extra data
+		WRITEUINT32(save_p, EXTRA_DATA_MARKER);
+		WRITEUINT8(save_p, 0x00);
 
-		WRITEUINT8(save_p, data->nightsrecords[i]->nummares);
+		WRITEUINT16(save_p, numgamemaps);
 
-		for (curmare = 0; curmare < (data->nightsrecords[i]->nummares + 1); ++curmare)
-		{
-			WRITEUINT32(save_p, data->nightsrecords[i]->score[curmare]);
-			WRITEUINT8(save_p, data->nightsrecords[i]->grade[curmare]);
-			WRITEUINT32(save_p, data->nightsrecords[i]->time[curmare]);
-		}
+		// Write map visited flags
+		for (i = NUMBASEMAPS; i < numgamemaps; i++)
+			WRITEUINT8(save_p, (data->mapvisited[i] & MV_MAX));
+
+		// Main records
+		for (i = NUMBASEMAPS; i < numgamemaps; i++)
+			WriteMainRecords(data, i);
+
+		// NiGHTS records
+		for (i = NUMBASEMAPS; i < numgamemaps; i++)
+			WriteNightsRecords(data, i);
 	}
+#endif
 
 	length = save_p - savebuffer;
 
@@ -5019,7 +5279,7 @@ void G_DeferedInitNew(boolean pultmode, const char *mapname, INT32 character, bo
 	SetPlayerSkinByNum(consoleplayer, character);
 
 	if (mapname)
-		D_MapChange(M_MapNumber(mapname[3], mapname[4]), gametype, pultmode, true, 1, false, FLS);
+		D_MapChange(G_GetMapNumber(mapname), gametype, pultmode, true, 1, false, FLS);
 }
 
 //
@@ -5100,18 +5360,18 @@ void G_InitNew(UINT8 pultmode, const char *mapname, boolean resetplayer, boolean
 	// (Looks a bit silly, but it works.)
 	boolean reset_skin = netgame && mapheaderinfo[gamemap-1] && mapheaderinfo[gamemap-1]->forcecharacter[0] != '\0';
 
+	gamemap = (INT16)G_GetMapNumber(mapname); // get xx out of MAPxx
+
 	// internal game map
 	// well this check is useless because it is done before (d_netcmd.c::command_map_f)
 	// but in case of for demos....
-	if (W_CheckNumForName(mapname) == LUMPERROR)
+	if (gamemap == 0)
 	{
 		I_Error("Internal game map '%s' not found\n", mapname);
 		Command_ExitGame_f();
 		return;
 	}
 
-	gamemap = (INT16)M_MapNumber(mapname[3], mapname[4]); // get xx out of MAPxx
-
 	// gamemap changed; we assume that its map header is always valid,
 	// so make it so
 	if(!mapheaderinfo[gamemap-1])
@@ -5162,7 +5422,6 @@ void G_InitNew(UINT8 pultmode, const char *mapname, boolean resetplayer, boolean
 	}
 }
 
-
 char *G_BuildMapTitle(INT32 mapnum)
 {
 	char *title = NULL;
@@ -5259,7 +5518,7 @@ INT32 G_FindMap(const char *mapname, char **foundmapnamep,
 	mapnamelen = strlen(mapname);
 
 	/* Count available maps; how ugly. */
-	for (i = 0, freqc = 0; i < NUMMAPS; ++i)
+	for (i = 0, freqc = 0; i < numgamemaps; ++i)
 	{
 		if (mapheaderinfo[i])
 			freqc++;
@@ -5270,7 +5529,7 @@ INT32 G_FindMap(const char *mapname, char **foundmapnamep,
 	wanttable = !!( freqp );
 
 	freqc = 0;
-	for (i = 0, mapnum = 1; i < NUMMAPS; ++i, ++mapnum)
+	for (i = 0, mapnum = 1; i < numgamemaps; ++i, ++mapnum)
 		if (mapheaderinfo[i])
 	{
 		if (!( realmapname = G_BuildMapTitle(mapnum) ))
@@ -5397,7 +5656,7 @@ INT32 G_FindMapByNameOrCode(const char *mapname, char **realmapnamep)
 		{
 			usemapcode = true;
 			newmapnum = mapheaderinfo[gamemap-1]->nextlevel;
-			if (newmapnum < 1 || newmapnum > NUMMAPS)
+			if (newmapnum < 1 || newmapnum > numgamemaps)
 			{
 				CONS_Alert(CONS_ERROR, M_GetText("NextLevel (%d) is not a valid map.\n"), newmapnum);
 				return 0;
@@ -5414,6 +5673,11 @@ INT32 G_FindMapByNameOrCode(const char *mapname, char **realmapnamep)
 		if (( newmapnum = M_MapNumber(mapname[3], mapname[4]) ))
 			usemapcode = true;
 	}
+	else
+	{
+		if (( newmapnum = G_GetMapNumber(mapname) ))
+			usemapcode = true;
+	}
 
 	if (!usemapcode)
 	{
@@ -5421,7 +5685,7 @@ INT32 G_FindMapByNameOrCode(const char *mapname, char **realmapnamep)
 		newmapnum = strtol(mapname, &p, 10);
 		if (*p == '\0')/* we got it */
 		{
-			if (newmapnum < 1 || newmapnum > NUMMAPS)
+			if (newmapnum < 1 || newmapnum > numgamemaps)
 			{
 				CONS_Alert(CONS_ERROR, M_GetText("Invalid map number %d.\n"), newmapnum);
 				return 0;
@@ -5437,7 +5701,7 @@ INT32 G_FindMapByNameOrCode(const char *mapname, char **realmapnamep)
 	if (usemapcode)
 	{
 		/* we can't check mapheaderinfo for this hahahaha */
-		if (W_CheckNumForName(G_BuildMapName(newmapnum)) == LUMPERROR)
+		if (!G_MapFileExists(G_BuildMapName(newmapnum)))
 			return 0;
 
 		if (realmapnamep)
diff --git a/src/g_game.h b/src/g_game.h
index 2612224a1252266346c352aa0294fee28047fe15..6e59b2cd2473b416512d5775c7b5f4bb9e01ee33 100644
--- a/src/g_game.h
+++ b/src/g_game.h
@@ -113,8 +113,16 @@ INT32 Joy2Axis(joyaxis_e axissel);
 #define SLOWTURNTICS (6)
 
 // build an internal map name MAPxx from map number
+const char *G_BuildClassicMapName(INT32 map);
+
 const char *G_BuildMapName(INT32 map);
 
+void G_InitMaps(void);
+UINT16 G_GetMapNumber(const char *name);
+UINT16 G_AddMap(const char *name);
+boolean G_MapFileExists(const char *name);
+boolean G_IsValidMapName(const char *name);
+
 extern INT16 ticcmd_oldangleturn[2];
 extern boolean ticcmd_centerviewdown[2]; // For simple controls, lock the camera behind the player
 extern mobj_t *ticcmd_ztargetfocus[2]; // Locking onto an object?
@@ -141,6 +149,11 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps);
 void G_InitNew(UINT8 pultmode, const char *mapname, boolean resetplayer,
 	boolean skipprecutscene, boolean FLS);
 char *G_BuildMapTitle(INT32 mapnum);
+const char *G_GetMapThumbnail(INT16 map);
+const char *G_GetMapThumbnailWide(INT16 map);
+const char *G_GetDefaultMapMusic(INT16 map);
+const char *G_GetMapMetalSonicReplay(INT16 map);
+boolean G_IsGameEndMap(INT16 mapnum);
 
 struct searchdim
 {
diff --git a/src/lua_baselib.c b/src/lua_baselib.c
index 9c723353b33ab6057fea4fc5b24de2499e607c50..87afc772c04a6c7fabce14de17ae4ad87a1d90df 100644
--- a/src/lua_baselib.c
+++ b/src/lua_baselib.c
@@ -1368,7 +1368,7 @@ static int lib_pPlayJingleMusic(lua_State *L)
 {
 	player_t *player = NULL;
 	const char *musnamearg = luaL_checkstring(L, 2);
-	char musname[7], *p = musname;
+	char musname[MAX_MUSIC_NAME+1], *p = musname;
 	UINT16 musflags = luaL_optinteger(L, 3, 0);
 	boolean looping = lua_opttrueboolean(L, 4);
 	jingletype_t jingletype = luaL_optinteger(L, 5, JT_OTHER);
@@ -1383,8 +1383,7 @@ static int lib_pPlayJingleMusic(lua_State *L)
 	if (jingletype >= NUMJINGLES)
 		return luaL_error(L, "jingletype %d out of range (0 - %d)", jingletype, NUMJINGLES-1);
 
-	musname[6] = '\0';
-	strncpy(musname, musnamearg, 6);
+	strlcpy(musname, musnamearg, MAX_MUSIC_NAME+1);
 
 	while (*p) {
 		*p = tolower(*p);
@@ -3828,10 +3827,34 @@ static int lib_gSetUsedCheats(lua_State *L)
 	return 0;
 }
 
+static int GetMapNameOrNumber(lua_State *L, int idx)
+{
+	if (lua_type(L, idx) == LUA_TSTRING)
+	{
+		const char *mapname = luaL_checkstring(L, idx);
+		INT16 mapnum = G_GetMapNumber(mapname);
+		if (mapnum == 0)
+		{
+			return luaL_error(L,
+					"%s is not a valid game map.",
+					mapname
+			);
+		}
+		return mapnum;
+	}
+	else
+		return luaL_checkinteger(L, idx);
+}
+
 static int Lcheckmapnumber (lua_State *L, int idx, const char *fun)
 {
 	if (ISINLEVEL)
-		return luaL_optinteger(L, idx, gamemap);
+	{
+		if (!lua_isnoneornil(L, idx))
+			return GetMapNameOrNumber(L, idx);
+		else
+			return gamemap;
+	}
 	else
 	{
 		if (lua_isnoneornil(L, idx))
@@ -3842,14 +3865,21 @@ static int Lcheckmapnumber (lua_State *L, int idx, const char *fun)
 			);
 		}
 		else
-			return luaL_checkinteger(L, idx);
+			return GetMapNameOrNumber(L, idx);
 	}
 }
 
 static int lib_gBuildMapName(lua_State *L)
 {
 	INT32 map = Lcheckmapnumber(L, 1, "G_BuildMapName");
-	//HUDSAFE
+	if (map < 1 || map > numgamemaps)
+	{
+		return luaL_error(L,
+				"map number %d out of range (1 - %d)",
+				map,
+				numgamemaps
+		);
+	}
 	lua_pushstring(L, G_BuildMapName(map));
 	return 1;
 }
@@ -3858,12 +3888,12 @@ static int lib_gBuildMapTitle(lua_State *L)
 {
 	INT32 map = Lcheckmapnumber(L, 1, "G_BuildMapTitle");
 	char *name;
-	if (map < 1 || map > NUMMAPS)
+	if (map < 1 || map > numgamemaps)
 	{
 		return luaL_error(L,
 				"map number %d out of range (1 - %d)",
 				map,
-				NUMMAPS
+				numgamemaps
 		);
 	}
 	name = G_BuildMapTitle(map);
@@ -3983,6 +4013,36 @@ static int lib_gFindMapByNameOrCode(lua_State *L)
 		return 1;
 }
 
+static int lib_gGetMapThumbnail(lua_State *L)
+{
+	INT32 map = Lcheckmapnumber(L, 1, "G_GetMapThumbnail");
+	if (map < 1 || map > numgamemaps)
+	{
+		return luaL_error(L,
+				"map number %d out of range (1 - %d)",
+				map,
+				numgamemaps
+		);
+	}
+	lua_pushstring(L, G_GetMapThumbnail(map));
+	return 1;
+}
+
+static int lib_gGetMapThumbnailWide(lua_State *L)
+{
+	INT32 map = Lcheckmapnumber(L, 1, "G_GetMapThumbnailWide");
+	if (map < 1 || map > numgamemaps)
+	{
+		return luaL_error(L,
+				"map number %d out of range (1 - %d)",
+				map,
+				numgamemaps
+		);
+	}
+	lua_pushstring(L, G_GetMapThumbnailWide(map));
+	return 1;
+}
+
 static int lib_gDoReborn(lua_State *L)
 {
 	INT32 playernum = luaL_checkinteger(L, 1);
@@ -4016,7 +4076,19 @@ static int lib_gSetCustomExitVars(lua_State *L)
 
 	if (n >= 1)
 	{
-		nextmapoverride = (INT16)luaL_optinteger(L, 1, 0);
+		if (!lua_isnoneornil(L, 1))
+		{
+			INT16 mapnum = GetMapNameOrNumber(L, 1);
+			if (mapnum < 1 || mapnum > numgamemaps)
+			{
+				return luaL_error(L,
+						"map number %d out of range (1 - %d)",
+						mapnum,
+						numgamemaps
+				);
+			}
+			nextmapoverride = mapnum;
+		}
 		skipstats = (INT16)luaL_optinteger(L, 2, 0);
 		nextgametype = (INT16)luaL_optinteger(L, 3, -1);
 	}
@@ -4044,9 +4116,22 @@ static int lib_gExitLevel(lua_State *L)
 
 static int lib_gIsSpecialStage(lua_State *L)
 {
-	INT32 mapnum = luaL_optinteger(L, 1, gamemap);
-	//HUDSAFE
+	INT32 mapnum;
 	INLEVEL
+	if (!lua_isnoneornil(L, 1))
+	{
+		mapnum = GetMapNameOrNumber(L, 1);
+		if (mapnum < 1 || mapnum > numgamemaps)
+		{
+			return luaL_error(L,
+					"map number %d out of range (1 - %d)",
+					mapnum,
+					numgamemaps
+			);
+		}
+	}
+	else
+		mapnum = gamemap;
 	lua_pushboolean(L, G_IsSpecialStage(mapnum));
 	return 1;
 }
@@ -4451,6 +4536,8 @@ static luaL_Reg lib[] = {
 	{"G_BuildMapTitle",lib_gBuildMapTitle},
 	{"G_FindMap",lib_gFindMap},
 	{"G_FindMapByNameOrCode",lib_gFindMapByNameOrCode},
+	{"G_GetMapThumbnail",lib_gGetMapThumbnail},
+	{"G_GetMapThumbnailWide",lib_gGetMapThumbnailWide},
 	{"G_DoReborn",lib_gDoReborn},
 	{"G_SetCustomExitVars",lib_gSetCustomExitVars},
 	{"G_EnoughPlayersFinished",lib_gEnoughPlayersFinished},
diff --git a/src/lua_hooklib.c b/src/lua_hooklib.c
index 38815a06cfa4558b8ad5659bcbe1cdba5705ca03..358bc82af425e193a25a08fdf93e92622f00dd69 100644
--- a/src/lua_hooklib.c
+++ b/src/lua_hooklib.c
@@ -1067,16 +1067,16 @@ static void update_music_name(struct MusicChange *musicchange)
 	size_t length;
 	const char * new = lua_tolstring(gL, -6, &length);
 
-	if (length < 7)
+	if (length <= MAX_MUSIC_NAME)
 	{
 		strcpy(musicchange->newname, new);
 		lua_pushvalue(gL, -6);/* may as well keep it for next call */
 	}
 	else
 	{
-		memcpy(musicchange->newname, new, 6);
-		musicchange->newname[6] = '\0';
-		lua_pushlstring(gL, new, 6);
+		memcpy(musicchange->newname, new, MAX_MUSIC_NAME);
+		musicchange->newname[MAX_MUSIC_NAME] = '\0';
+		lua_pushlstring(gL, new, MAX_MUSIC_NAME);
 	}
 
 	lua_replace(gL, -7);
diff --git a/src/lua_maplib.c b/src/lua_maplib.c
index 0c4ba6fd3e24451d4b3144264bb5c357eb3eea35..d5cb0c2f054f10fe21c2dd8651d412bd80bd292a 100644
--- a/src/lua_maplib.c
+++ b/src/lua_maplib.c
@@ -2685,32 +2685,22 @@ static int vector3_get(lua_State *L)
 static int lib_getMapheaderinfo(lua_State *L)
 {
 	// i -> mapheaderinfo[i-1]
-
-	//int field;
 	lua_settop(L, 2);
 	lua_remove(L, 1); // dummy userdata table is unused.
 	if (lua_isnumber(L, 1))
 	{
 		size_t i = lua_tointeger(L, 1)-1;
-		if (i >= NUMMAPS)
+		if (i >= numgamemaps)
 			return 0;
 		LUA_PushUserdata(L, mapheaderinfo[i], META_MAPHEADER);
-		//CONS_Printf(mapheaderinfo[i]->lvlttl);
-		return 1;
-	}/*
-	field = luaL_checkoption(L, 1, NULL, array_opt);
-	switch(field)
-	{
-	case 0: // iterate
-		lua_pushcfunction(L, lib_iterateSubsectors);
 		return 1;
-	}*/
+	}
 	return 0;
 }
 
 static int lib_nummapheaders(lua_State *L)
 {
-	lua_pushinteger(L, NUMMAPS);
+	lua_pushinteger(L, numgamemaps);
 	return 1;
 }
 
diff --git a/src/m_cond.c b/src/m_cond.c
index 706a1b5106f487e4ed285880304a87335f7f716e..7fea86c65700126f81134134a3ff3253065b23c8 100644
--- a/src/m_cond.c
+++ b/src/m_cond.c
@@ -78,7 +78,7 @@ void M_CopyGameData(gamedata_t *dest, gamedata_t *src)
 	memcpy(dest->mapvisited, src->mapvisited, sizeof(dest->mapvisited));
 
 	// Main records
-	for (i = 0; i < NUMMAPS; ++i)
+	for (i = 0; i < MAXMAPS; ++i)
 	{
 		if (!src->mainrecords[i])
 			continue;
@@ -90,7 +90,7 @@ void M_CopyGameData(gamedata_t *dest, gamedata_t *src)
 	}
 
 	// Nights records
-	for (i = 0; i < NUMMAPS; ++i)
+	for (i = 0; i < MAXMAPS; ++i)
 	{
 		if (!src->nightsrecords[i] || !src->nightsrecords[i]->nummares)
 			continue;
@@ -574,7 +574,7 @@ UINT8 M_GotHighEnoughScore(INT32 tscore, gamedata_t *data)
 	INT32 mscore = 0;
 	INT32 i;
 
-	for (i = 0; i < NUMMAPS; ++i)
+	for (i = 0; i < numgamemaps; ++i)
 	{
 		if (!mapheaderinfo[i] || !(mapheaderinfo[i]->menuflags & LF2_RECORDATTACK))
 			continue;
@@ -592,7 +592,7 @@ UINT8 M_GotLowEnoughTime(INT32 tictime, gamedata_t *data)
 	INT32 curtics = 0;
 	INT32 i;
 
-	for (i = 0; i < NUMMAPS; ++i)
+	for (i = 0; i < numgamemaps; ++i)
 	{
 		if (!mapheaderinfo[i] || !(mapheaderinfo[i]->menuflags & LF2_RECORDATTACK))
 			continue;
@@ -610,7 +610,7 @@ UINT8 M_GotHighEnoughRings(INT32 trings, gamedata_t *data)
 	INT32 mrings = 0;
 	INT32 i;
 
-	for (i = 0; i < NUMMAPS; ++i)
+	for (i = 0; i < numgamemaps; ++i)
 	{
 		if (!mapheaderinfo[i] || !(mapheaderinfo[i]->menuflags & LF2_RECORDATTACK))
 			continue;
diff --git a/src/m_cond.h b/src/m_cond.h
index 2491a384c02aa5f34ba47933eba689811ac599d0..2efc6c530cb467b4bc2abd36da48951e56edf2a2 100644
--- a/src/m_cond.h
+++ b/src/m_cond.h
@@ -203,9 +203,9 @@ typedef struct
 	boolean unlocked[MAXUNLOCKABLES];
 
 	// TIME ATTACK DATA
-	recorddata_t *mainrecords[NUMMAPS];
-	nightsdata_t *nightsrecords[NUMMAPS];
-	UINT8 mapvisited[NUMMAPS];
+	recorddata_t *mainrecords[MAXMAPS];
+	nightsdata_t *nightsrecords[MAXMAPS];
+	UINT8 mapvisited[MAXMAPS];
 
 	// # OF TIMES THE GAME HAS BEEN BEATEN
 	UINT32 timesBeaten;
diff --git a/src/m_menu.c b/src/m_menu.c
index 9aae59445382270c6d45445dfb113425fe52eee6..7dc5056ac221ecad45199ff23e87f87241d68e83 100644
--- a/src/m_menu.c
+++ b/src/m_menu.c
@@ -414,7 +414,7 @@ consvar_t cv_showfocuslost = CVAR_INIT ("showfocuslost", "Yes", CV_SAVE, CV_YesN
 
 static CV_PossibleValue_t map_cons_t[] = {
 	{1,"MIN"},
-	{NUMMAPS, "MAX"},
+	{MAXMAPS, "MAX"},
 	{0,NULL}
 };
 consvar_t cv_nextmap = CVAR_INIT ("nextmap", "1", CV_HIDEN|CV_CALL, map_cons_t, Nextmap_OnChange);
@@ -2230,9 +2230,9 @@ void Nextmap_OnChange(void)
 {
 	gamedata_t *data = clientGamedata;
 	char *leveltitle;
-	char tabase[256];
+	char tabase[4096];
 #ifdef OLDNREPLAYNAME
-	char tabaseold[256];
+	char tabaseold[4096];
 #endif
 	short i;
 	boolean active;
@@ -2498,13 +2498,13 @@ void M_InitMenuPresTables(void)
 			menupres[i].muslooping = true;
 		}
 		if (i == MN_SP_TIMEATTACK)
-			strncpy(menupres[i].musname, "_recat", 7);
+			strlcpy(menupres[i].musname, "_recat", MAX_MUSIC_NAME+1);
 		else if (i == MN_SP_NIGHTSATTACK)
-			strncpy(menupres[i].musname, "_nitat", 7);
+			strlcpy(menupres[i].musname, "_nitat", MAX_MUSIC_NAME+1);
 		else if (i == MN_SP_MARATHON)
-			strncpy(menupres[i].musname, "spec8", 6);
+			strlcpy(menupres[i].musname, "spec8", MAX_MUSIC_NAME+1);
 		else if (i == MN_SP_PLAYER || i == MN_SR_PLAYER)
-			strncpy(menupres[i].musname, "_chsel", 7);
+			strlcpy(menupres[i].musname, "_chsel", MAX_MUSIC_NAME+1);
 		else if (i == MN_SR_SOUNDTEST)
 		{
 			*menupres[i].musname = '\0';
@@ -2530,7 +2530,7 @@ typedef boolean (*menutree_iterator)(UINT32, INT32, INT32 *, void **, boolean fr
 // a single input. Maybe someday use this struct program-wide.
 typedef struct
 {
-	char musname[7];
+	char musname[MAX_MUSIC_NAME+1];
 	UINT16 mustrack;
 	boolean muslooping;
 } menupresmusic_t;
@@ -2741,8 +2741,7 @@ void M_ChangeMenuMusic(const char *defaultmusname, boolean defaultmuslooping)
 	if (!defaultmusname)
 		defaultmusname = "";
 
-	strncpy(defaultmusic.musname, defaultmusname, 7);
-	defaultmusic.musname[6] = 0;
+	strlcpy(defaultmusic.musname, defaultmusname, MAX_MUSIC_NAME+1);
 	defaultmusic.mustrack = 0;
 	defaultmusic.muslooping = defaultmuslooping;
 
@@ -5091,7 +5090,7 @@ static INT32 M_CountLevelsToShowOnPlatter(INT32 gt)
 {
 	INT32 mapnum, count = 0;
 
-	for (mapnum = 0; mapnum < NUMMAPS; mapnum++)
+	for (mapnum = 0; mapnum < numgamemaps; mapnum++)
 		if (M_CanShowLevelOnPlatter(mapnum, gt))
 			count++;
 
@@ -5126,7 +5125,7 @@ static boolean M_GametypeHasLevels(INT32 gt)
 {
 	INT32 mapnum;
 
-	for (mapnum = 0; mapnum < NUMMAPS; mapnum++)
+	for (mapnum = 0; mapnum < numgamemaps; mapnum++)
 		if (M_CanShowLevelOnPlatter(mapnum, gt))
 			return true;
 
@@ -5138,11 +5137,9 @@ static INT32 M_CountRowsToShowOnPlatter(INT32 gt)
 	INT32 col = 0, rows = 0;
 	INT32 mapIterate = 0;
 	INT32 headingIterate = 0;
-	boolean mapAddedAlready[NUMMAPS];
+	boolean *mapAddedAlready = Z_Calloc(numgamemaps*sizeof(boolean), PU_STATIC, NULL);
 
-	memset(mapAddedAlready, 0, sizeof mapAddedAlready);
-
-	for (mapIterate = 0; mapIterate < NUMMAPS; mapIterate++)
+	for (mapIterate = 0; mapIterate < numgamemaps; mapIterate++)
 	{
 		boolean forceNewRow = true;
 
@@ -5158,7 +5155,7 @@ static INT32 M_CountRowsToShowOnPlatter(INT32 gt)
 			continue;
 		}
 
-		for (headingIterate = mapIterate; headingIterate < NUMMAPS; headingIterate++)
+		for (headingIterate = mapIterate; headingIterate < numgamemaps; headingIterate++)
 		{
 			boolean wide = false;
 
@@ -5205,6 +5202,8 @@ static INT32 M_CountRowsToShowOnPlatter(INT32 gt)
 		rows++;
 	}
 
+	Z_Free(mapAddedAlready);
+
 	return rows;
 }
 
@@ -5236,11 +5235,13 @@ static boolean M_PrepareLevelPlatter(INT32 gt, boolean nextmappick)
 	INT32 col = 0, row = 0, startrow = 0;
 	INT32 mapIterate = 0; // First level of map loop -- find starting points for select headings
 	INT32 headingIterate = 0; // Second level of map loop -- finding maps that match mapIterate's heading.
-	boolean mapAddedAlready[NUMMAPS];
+	boolean *mapAddedAlready;
 
 	if (!numrows)
 		return false;
 
+	mapAddedAlready = Z_Calloc(numgamemaps*sizeof(boolean), PU_STATIC, NULL);
+
 	if (levelselect.rows)
 		Z_Free(levelselect.rows);
 	levelselect.rows = NULL;
@@ -5253,8 +5254,6 @@ static boolean M_PrepareLevelPlatter(INT32 gt, boolean nextmappick)
 	// done here so lsrow and lscol can be set if cv_nextmap is on the platter
 	lsrow = lscol = lshli = lsoffs[0] = lsoffs[1] = 0;
 
-	memset(mapAddedAlready, 0, sizeof mapAddedAlready);
-
 	if (levellistmode == LLM_CREATESERVER)
 	{
 		sprintf(levelselect.rows[0].header, "Gametype");
@@ -5266,7 +5265,7 @@ static boolean M_PrepareLevelPlatter(INT32 gt, boolean nextmappick)
 		char_notes = NULL;
 	}
 
-	for (mapIterate = 0; mapIterate < NUMMAPS; mapIterate++)
+	for (mapIterate = 0; mapIterate < numgamemaps; mapIterate++)
 	{
 		INT32 headerRow = -1;
 		boolean anyAvailable = false;
@@ -5284,7 +5283,7 @@ static boolean M_PrepareLevelPlatter(INT32 gt, boolean nextmappick)
 			continue;
 		}
 
-		for (headingIterate = mapIterate; headingIterate < NUMMAPS; headingIterate++)
+		for (headingIterate = mapIterate; headingIterate < numgamemaps; headingIterate++)
 		{
 			UINT8 actnum = 0;
 			boolean headingisname = false;
@@ -5435,6 +5434,8 @@ static boolean M_PrepareLevelPlatter(INT32 gt, boolean nextmappick)
 	}
 #endif
 
+	Z_Free(mapAddedAlready);
+
 	M_CacheLevelPlatter();
 
 	return true;
@@ -5670,8 +5671,9 @@ static void M_DrawLevelPlatterWideMap(UINT8 row, UINT8 col, INT32 x, INT32 y, bo
 	}
 	else
 	{
-		if (W_CheckNumForName(va("%sW", G_BuildMapName(map))) != LUMPERROR)
-			patch = W_CachePatchName(va("%sW", G_BuildMapName(map)), PU_PATCH);
+		const char *thumbnail = G_GetMapThumbnailWide(map);
+		if (W_CheckNumForLongName(thumbnail) != LUMPERROR)
+			patch = W_CachePatchLongName(thumbnail, PU_PATCH);
 		else
 			patch = levselp[1][2]; // don't static to indicate that it's just a normal level
 
@@ -5701,8 +5703,9 @@ static void M_DrawLevelPlatterMap(UINT8 row, UINT8 col, INT32 x, INT32 y, boolea
 	}
 	else
 	{
-		if (W_CheckNumForName(va("%sP", G_BuildMapName(map))) != LUMPERROR)
-			patch = W_CachePatchName(va("%sP", G_BuildMapName(map)), PU_PATCH);
+		const char *thumbnail = G_GetMapThumbnail(map);
+		if (W_CheckNumForLongName(thumbnail) != LUMPERROR)
+			patch = W_CachePatchLongName(thumbnail, PU_PATCH);
 		else
 			patch = levselp[0][2]; // don't static to indicate that it's just a normal level
 
@@ -6025,7 +6028,7 @@ static INT32 M_GetFirstLevelInList(INT32 gt)
 {
 	INT32 mapnum;
 
-	for (mapnum = 0; mapnum < NUMMAPS; mapnum++)
+	for (mapnum = 0; mapnum < numgamemaps; mapnum++)
 		if (M_CanShowLevelInList(mapnum, gt))
 			return mapnum + 1;
 
@@ -6905,12 +6908,9 @@ static boolean M_ExitPandorasBox(void)
 
 static void M_ChangeLevel(INT32 choice)
 {
-	char mapname[6];
-	(void)choice;
+	const char *mapname = G_BuildMapName(cv_nextmap.value);
 
-	strlcpy(mapname, G_BuildMapName(cv_nextmap.value), sizeof (mapname));
-	strlwr(mapname);
-	mapname[5] = '\0';
+	(void)choice;
 
 	M_ClearMenus(true);
 	COM_BufAddText(va("map %s -gametype \"%s\"\n", mapname, cv_newgametype.string));
@@ -7075,7 +7075,7 @@ static void M_LevelSelectWarp(INT32 choice)
 {
 	(void)choice;
 
-	if (W_CheckNumForName(G_BuildMapName(cv_nextmap.value)) == LUMPERROR)
+	if (!G_MapFileExists(G_BuildMapName(cv_nextmap.value)))
 	{
 		CONS_Alert(CONS_WARNING, "Internal game map '%s' not found\n", G_BuildMapName(cv_nextmap.value));
 		return;
@@ -8438,7 +8438,7 @@ static void M_DrawLoadGameData(void)
 #ifdef PERFECTSAVE // disabled on request
 			else if ((savegameinfo[savetodraw].skinnum == 1)
 			&& (savegameinfo[savetodraw].lives == 99)
-			&& (savegameinfo[savetodraw].gamemap & 8192)
+			&& (savegameinfo[savetodraw].flags & SAVE_GAME_COMPLETE_BIT)
 			&& (savegameinfo[savetodraw].numgameovers == 0)
 			&& (savegameinfo[savetodraw].numemeralds == ((1<<7) - 1))) // perfect save
 			{
@@ -8494,11 +8494,11 @@ static void M_DrawLoadGameData(void)
 			else
 			{
 				patch_t *patch;
-				if (savegameinfo[savetodraw].gamemap & 8192)
+				if (savegameinfo[savetodraw].flags & SAVE_GAME_COMPLETE_BIT)
 					patch = savselp[3];
 				else
 				{
-					lumpnum_t lumpnum = W_CheckNumForName(va("%sP", G_BuildMapName((savegameinfo[savetodraw].gamemap) & 8191)));
+					lumpnum_t lumpnum = W_CheckNumForLongName(G_GetMapThumbnail(savegameinfo[savetodraw].gamemap));
 					if (lumpnum != LUMPERROR)
 						patch = W_CachePatchNum(lumpnum, PU_PATCH);
 					else
@@ -8513,7 +8513,7 @@ static void M_DrawLoadGameData(void)
 				V_DrawRightAlignedThinString(x + 79, y, V_GRAYMAP, "NEW GAME");
 			else if (savegameinfo[savetodraw].lives == -666)
 				V_DrawRightAlignedThinString(x + 79, y, V_REDMAP, "CAN'T LOAD!");
-			else if (savegameinfo[savetodraw].gamemap & 8192)
+			else if (savegameinfo[savetodraw].flags & SAVE_GAME_COMPLETE_BIT)
 				V_DrawRightAlignedThinString(x + 79, y, V_GREENMAP, "CLEAR!");
 			else
 				V_DrawRightAlignedThinString(x + 79, y, V_YELLOWMAP, savegameinfo[savetodraw].levelname);
@@ -8731,7 +8731,7 @@ static void M_LoadSelect(INT32 choice)
 		// This slot is empty, so start a new game here.
 		M_NewGame();
 	}
-	else if (savegameinfo[saveSlotSelected-1].gamemap & 8192) // Completed
+	else if (savegameinfo[saveSlotSelected-1].flags & SAVE_GAME_COMPLETE_BIT) // Completed
 		M_LoadGameLevelSelect(0);
 	else
 		G_LoadGame((UINT32)saveSlotSelected, 0);
@@ -8752,6 +8752,7 @@ static void M_ReadSavegameInfo(UINT32 slot)
 	UINT8 *end_p; // buffer end point, don't read past here
 	UINT8 *sav_p;
 	INT32 fake; // Dummy variable
+	INT16 mapnum;
 	char temp[sizeof(timeattackfolder)];
 	char vcheck[VERSIONSIZE];
 #ifdef NEWSKINSAVES
@@ -8786,19 +8787,44 @@ static void M_ReadSavegameInfo(UINT32 slot)
 	CHECKPOS
 	fake = READINT16(sav_p);
 
-	if (((fake-1) & 8191) >= NUMMAPS) BADSAVE
+#ifdef NEWMAPSAVES
+	if (fake == NEWMAPSAVES)
+	{
+		char mapname[MAX_MAP_NAME_SIZE+1];
+
+		READSTRINGN(sav_p, mapname, MAX_MAP_NAME_SIZE);
+
+		savegameinfo[slot].flags = READUINT8(sav_p);
 
-	if(!mapheaderinfo[(fake-1) & 8191])
+		mapnum = G_GetMapNumber(mapname);
+		if (mapnum == 0)
+			BADSAVE
+	}
+	else
+#endif
+	{
+		if (((fake-1) & 8191) >= NUMBASEMAPS) BADSAVE
+
+		mapnum = (fake-1) & 8191;
+		mapnum++;
+
+		if (fake & 8192)
+			savegameinfo[slot].flags = SAVE_GAME_COMPLETE_BIT;
+		else
+			savegameinfo[slot].flags = 0;
+	}
+
+	if(!mapheaderinfo[mapnum-1])
 		savegameinfo[slot].levelname[0] = '\0';
-	else if (V_ThinStringWidth(mapheaderinfo[(fake-1) & 8191]->lvlttl, 0) <= 78)
-		strlcpy(savegameinfo[slot].levelname, mapheaderinfo[(fake-1) & 8191]->lvlttl, 22);
+	else if (V_ThinStringWidth(mapheaderinfo[mapnum-1]->lvlttl, 0) <= 78)
+		strlcpy(savegameinfo[slot].levelname, mapheaderinfo[mapnum-1]->lvlttl, 22);
 	else
 	{
-		strlcpy(savegameinfo[slot].levelname, mapheaderinfo[(fake-1) & 8191]->lvlttl, 15);
+		strlcpy(savegameinfo[slot].levelname, mapheaderinfo[mapnum-1]->lvlttl, 15);
 		strcat(savegameinfo[slot].levelname, "...");
 	}
 
-	savegameinfo[slot].gamemap = fake;
+	savegameinfo[slot].gamemap = mapnum;
 
 	CHECKPOS
 	savegameinfo[slot].numemeralds = READUINT16(sav_p)-357; // emeralds
@@ -9608,7 +9634,8 @@ static void M_ChoosePlayer(INT32 choice)
 
 static INT32 statsLocation;
 static INT32 statsMax;
-static INT16 statsMapList[NUMMAPS+1];
+static INT16 *statsMapList = NULL;
+static size_t statsMapLength = 0;
 
 static void M_Statistics(INT32 choice)
 {
@@ -9616,9 +9643,17 @@ static void M_Statistics(INT32 choice)
 
 	(void)choice;
 
-	memset(statsMapList, 0, sizeof(statsMapList));
+	size_t num_maps = (size_t)(numgamemaps+1);
+
+	if (statsMapLength != num_maps)
+	{
+		statsMapLength = num_maps;
+		statsMapList = Z_Realloc(statsMapList, statsMapLength*sizeof(INT16), PU_STATIC, NULL);
+	}
+
+	memset(statsMapList, 0, statsMapLength*sizeof(INT16));
 
-	for (i = 0; i < NUMMAPS; i++)
+	for (i = 0; i < numgamemaps; i++)
 	{
 		if (!mapheaderinfo[i] || mapheaderinfo[i]->lvlttl[0] == '\0')
 			continue;
@@ -9754,7 +9789,7 @@ static void M_DrawLevelStats(void)
 	                         G_TicsToMinutes(data->totalplaytime, false),
 	                         G_TicsToSeconds(data->totalplaytime)));
 
-	for (i = 0; i < NUMMAPS; i++)
+	for (i = 0; i < numgamemaps; i++)
 	{
 		boolean mapunfinished = false;
 
@@ -9965,10 +10000,10 @@ void M_DrawTimeAttackMenu(void)
 		M_DrawLevelPlatterHeader(32-lsheadingheight/2, cv_nextmap.string, true, false);
 
 		//  A 160x100 image of the level as entry MAPxxP
-		lumpnum = W_CheckNumForName(va("%sP", G_BuildMapName(cv_nextmap.value)));
+		lumpnum = W_CheckNumForLongName(G_GetMapThumbnail(cv_nextmap.value));
 
 		if (lumpnum != LUMPERROR)
-			PictureOfLevel = W_CachePatchName(va("%sP", G_BuildMapName(cv_nextmap.value)), PU_PATCH);
+			PictureOfLevel = W_CachePatchLongName(G_GetMapThumbnail(cv_nextmap.value), PU_PATCH);
 		else
 			PictureOfLevel = W_CachePatchName("BLANKLVL", PU_PATCH);
 
@@ -10229,10 +10264,10 @@ void M_DrawNightsAttackMenu(void)
 		M_DrawLevelPlatterHeader(32-lsheadingheight/2, cv_nextmap.string, true, false);
 
 		//  A 160x100 image of the level as entry MAPxxP
-		lumpnum = W_CheckNumForName(va("%sP", G_BuildMapName(cv_nextmap.value)));
+		lumpnum = W_CheckNumForLongName(G_GetMapThumbnail(cv_nextmap.value));
 
 		if (lumpnum != LUMPERROR)
-			PictureOfLevel = W_CachePatchName(va("%sP", G_BuildMapName(cv_nextmap.value)), PU_PATCH);
+			PictureOfLevel = W_CachePatchLongName(G_GetMapThumbnail(cv_nextmap.value), PU_PATCH);
 		else
 			PictureOfLevel = W_CachePatchName("BLANKLVL", PU_PATCH);
 
@@ -10366,7 +10401,8 @@ static void M_NightsAttack(INT32 choice)
 static void M_ChooseNightsAttack(INT32 choice)
 {
 	char *gpath;
-	const size_t glen = strlen("replay")+1+strlen(timeattackfolder)+1+strlen("MAPXX")+1;
+	const char *mapname = G_BuildMapName(cv_nextmap.value);
+	const size_t glen = strlen("replay")+1+strlen(timeattackfolder)+1+strlen(mapname)+1;
 	char nameofdemo[256];
 	(void)choice;
 	emeralds = 0;
@@ -10380,7 +10416,7 @@ static void M_ChooseNightsAttack(INT32 choice)
 	if ((gpath = malloc(glen)) == NULL)
 		I_Error("Out of memory for replay filepath\n");
 
-	sprintf(gpath,"replay"PATHSEP"%s"PATHSEP"%s", timeattackfolder, G_BuildMapName(cv_nextmap.value));
+	sprintf(gpath,"replay"PATHSEP"%s"PATHSEP"%s", timeattackfolder, mapname);
 	snprintf(nameofdemo, sizeof nameofdemo, "%s-%s-last", gpath, skins[cv_chooseskin.value-1].name);
 
 	if (!cv_autorecord.value)
@@ -10395,7 +10431,8 @@ static void M_ChooseNightsAttack(INT32 choice)
 static void M_ChooseTimeAttack(INT32 choice)
 {
 	char *gpath;
-	const size_t glen = strlen("replay")+1+strlen(timeattackfolder)+1+strlen("MAPXX")+1;
+	const char *mapname = G_BuildMapName(cv_nextmap.value);
+	const size_t glen = strlen("replay")+1+strlen(timeattackfolder)+1+strlen(mapname)+1;
 	char nameofdemo[256];
 	(void)choice;
 	emeralds = 0;
@@ -10409,7 +10446,7 @@ static void M_ChooseTimeAttack(INT32 choice)
 	if ((gpath = malloc(glen)) == NULL)
 		I_Error("Out of memory for replay filepath\n");
 
-	sprintf(gpath,"replay"PATHSEP"%s"PATHSEP"%s", timeattackfolder, G_BuildMapName(cv_nextmap.value));
+	sprintf(gpath,"replay"PATHSEP"%s"PATHSEP"%s", timeattackfolder, mapname);
 	snprintf(nameofdemo, sizeof nameofdemo, "%s-%s-last", gpath, skins[cv_chooseskin.value-1].name);
 
 	if (!cv_autorecord.value)
@@ -10741,7 +10778,7 @@ static void M_Marathon(INT32 choice)
 
 	SP_MarathonMenu[marathonplayer].status = (skinset == MAXSKINS) ? IT_KEYHANDLER|IT_STRING : IT_NOTHING|IT_DISABLED;
 
-	while (mapnum < NUMMAPS)
+	while (mapnum < numgamemaps)
 	{
 		if (mapheaderinfo[mapnum])
 		{
@@ -10751,7 +10788,7 @@ static void M_Marathon(INT32 choice)
 		mapnum++;
 	}
 
-	SP_MarathonMenu[marathoncutscenes].status = (mapnum < NUMMAPS) ? IT_CVAR|IT_STRING : IT_NOTHING|IT_DISABLED;
+	SP_MarathonMenu[marathoncutscenes].status = (mapnum < numgamemaps) ? IT_CVAR|IT_STRING : IT_NOTHING|IT_DISABLED;
 
 	M_ChangeMenuMusic("spec8", true);
 
@@ -11650,10 +11687,10 @@ static void M_DrawServerMenu(void)
 		M_DrawLevelPlatterHeader(currentMenu->y + imgheight - 10 - lsheadingheight/2, (const char *)headerstr, true, false);
 
 		//  A 160x100 image of the level as entry MAPxxP
-		lumpnum = W_CheckNumForName(va("%sP", G_BuildMapName(cv_nextmap.value)));
+		lumpnum = W_CheckNumForLongName(G_GetMapThumbnail(cv_nextmap.value));
 
 		if (lumpnum != LUMPERROR)
-			PictureOfLevel = W_CachePatchName(va("%sP", G_BuildMapName(cv_nextmap.value)), PU_PATCH);
+			PictureOfLevel = W_CachePatchLongName(G_GetMapThumbnail(cv_nextmap.value), PU_PATCH);
 		else
 			PictureOfLevel = W_CachePatchName("BLANKLVL", PU_PATCH);
 
diff --git a/src/m_menu.h b/src/m_menu.h
index b8fe3b808928b81c86bf3c09731e557f4b113616..ed026a65df9ef612236245b3cb63c994466533cb 100644
--- a/src/m_menu.h
+++ b/src/m_menu.h
@@ -159,7 +159,7 @@ typedef struct
 	INT16 ttloop; // # frame to loop; -1 means dont loop
 	UINT16 tttics; // # of tics per frame
 
-	char musname[7]; ///< Music track to play. "" for no music.
+	char musname[MAX_MUSIC_NAME+1]; ///< 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.
 	boolean muslooping; ///< Loop the music
 	boolean musstop; ///< Don't play any music
@@ -429,6 +429,7 @@ typedef struct
 	INT32 lives;
 	INT32 continuescore;
 	INT32 gamemap;
+	UINT8 flags;
 } saveinfo_t;
 
 extern description_t description[MAXSKINS];
diff --git a/src/m_misc.c b/src/m_misc.c
index ce332910dc8ccf78a8c751adfc96c28c3721aea4..ed5e1ebcdaa0e2fc64b084a742a879d6c360b579 100644
--- a/src/m_misc.c
+++ b/src/m_misc.c
@@ -2387,6 +2387,30 @@ boolean M_IsStringEmpty(const char *s)
 	return true;
 }
 
+const char *M_GetFilenameFromPath(const char *path)
+{
+	const char *slash = strrchr(path, '/');
+	if (slash)
+		return slash + 1;
+	return path;
+}
+
+const char *M_GetExtensionFromFilename(const char *filename)
+{
+	const char *dot = strrchr(filename, '.');
+	if (dot)
+		return dot + 1;
+	return NULL;
+}
+
+const char *M_CheckFilenameExtension(const char *filename, const char *ext)
+{
+	const char *dot = strrchr(filename, '.');
+	if (dot && (strstr(dot, ext) || strstr(dot + 1, ext)))
+		return dot + 1;
+	return NULL;
+}
+
 // Rounds off floating numbers and checks for 0 - 255 bounds
 int M_RoundUp(double number)
 {
diff --git a/src/m_misc.h b/src/m_misc.h
index 753991e70465c075fa874ce7662d6aa6603e7b6a..7e6f18d4cd598490422c3b73d2698c3c6c5c00ae 100644
--- a/src/m_misc.h
+++ b/src/m_misc.h
@@ -109,6 +109,10 @@ const char * M_Ftrim (double);
 // Returns true if the string is empty.
 boolean M_IsStringEmpty(const char *s);
 
+const char *M_GetFilenameFromPath(const char *path);
+const char *M_GetExtensionFromFilename(const char *filename);
+const char *M_CheckFilenameExtension(const char *filename, const char *ext);
+
 // counting bits, for weapon ammo code, usually
 FUNCMATH UINT8 M_CountBits(UINT32 num, UINT8 size);
 
diff --git a/src/netcode/d_netcmd.c b/src/netcode/d_netcmd.c
index ef1ef9aeb1971d1a63b41944e59c161d8e05f676..a6b7b804d26bc222efe8a65b71deb5679b0680af 100644
--- a/src/netcode/d_netcmd.c
+++ b/src/netcode/d_netcmd.c
@@ -1720,7 +1720,7 @@ void D_MapChange(INT32 mapnum, INT32 newgametype, boolean pultmode, boolean rese
 	{
 		UINT8 flags = 0;
 		const char *mapname = G_BuildMapName(mapnum);
-		I_Assert(W_CheckNumForName(mapname) != LUMPERROR);
+		I_Assert(G_MapFileExists(mapname) == true);
 		buf_p = buf;
 		if (pultmode)
 			flags |= 1;
@@ -2022,8 +2022,8 @@ static void Command_Map_f(void)
 static void Got_Mapcmd(UINT8 **cp, INT32 playernum)
 {
 	char mapname[MAX_WADPATH+1];
-	UINT8 flags;
-	INT32 resetplayer = 1, lastgametype;
+	UINT8 flags, newgametype;
+	INT32 resetplayer = 1, lastgametype = gametype;
 	UINT8 skipprecutscene, FLS;
 	INT16 mapnumber;
 
@@ -2039,6 +2039,12 @@ static void Got_Mapcmd(UINT8 **cp, INT32 playernum)
 		chmappending--;
 
 	flags = READUINT8(*cp);
+	newgametype = READUINT8(*cp);
+	READSTRINGN(*cp, mapname, MAX_WADPATH);
+
+	mapnumber = G_GetMapNumber(mapname);
+	if (!mapnumber) // Not valid???
+		return;
 
 	ultimatemode = ((flags & 1) != 0);
 	if (netgame || multiplayer)
@@ -2046,13 +2052,8 @@ static void Got_Mapcmd(UINT8 **cp, INT32 playernum)
 
 	resetplayer = ((flags & (1<<1)) == 0);
 
-	lastgametype = gametype;
-	gametype = READUINT8(*cp);
-
-	if (gametype < 0 || gametype >= gametypecount)
-		gametype = lastgametype;
-	else
-		G_SetGametype(gametype);
+	if (newgametype < gametypecount)
+		G_SetGametype(newgametype);
 
 	if (gametype != lastgametype)
 		D_GameTypeChanged(lastgametype); // emulate consvar_t behavior for gametype
@@ -2061,8 +2062,6 @@ static void Got_Mapcmd(UINT8 **cp, INT32 playernum)
 
 	FLS = ((flags & (1<<3)) != 0);
 
-	READSTRINGN(*cp, mapname, MAX_WADPATH);
-
 	if (netgame)
 		P_SetRandSeed(READUINT32(*cp));
 
@@ -2082,7 +2081,6 @@ static void Got_Mapcmd(UINT8 **cp, INT32 playernum)
 		players[0].skincolor = skins[players[0].skin].prefcolor;
 	}
 
-	mapnumber = M_MapNumber(mapname[3], mapname[4]);
 	LUA_HookInt(mapnumber, HOOK(MapChange));
 
 	G_InitNew(ultimatemode, mapname, resetplayer, skipprecutscene, FLS);
diff --git a/src/netcode/server_connection.c b/src/netcode/server_connection.c
index bfbe30a08ed89aba1af4bb5ef6f2a51448d00c1c..2805824b53adff558e50b49f12ca7531a26a6557 100644
--- a/src/netcode/server_connection.c
+++ b/src/netcode/server_connection.c
@@ -121,7 +121,7 @@ static void SV_SendServerInfo(INT32 node, tic_t servertime)
 	netbuffer->u.serverinfo.flags = (dedicated ? SV_DEDICATED : 0);
 	strncpy(netbuffer->u.serverinfo.servername, cv_servername.string,
 		MAXSERVERNAME);
-	strncpy(netbuffer->u.serverinfo.mapname, G_BuildMapName(gamemap), 7);
+	strncpy(netbuffer->u.serverinfo.mapname, G_BuildMapName(gamemap), 7); // [mapnames] FIXME
 
 	M_Memcpy(netbuffer->u.serverinfo.mapmd5, mapmd5, 16);
 
diff --git a/src/p_enemy.c b/src/p_enemy.c
index 819f0dc1d992f1251def75c6a82eb687d7c5e69c..a7492f0533d896824e766804db9b6548cf3214e6 100644
--- a/src/p_enemy.c
+++ b/src/p_enemy.c
@@ -4035,11 +4035,10 @@ static void P_DoBossVictory(mobj_t *mo)
 			// Touching the egg trap button calls P_DoPlayerExit, which calls P_RestoreMusic.
 			// So just park ourselves in the mapmus variables.
 			// But don't change the mapmus variables if they were modified from their level header values (e.g., TUNES).
-			boolean changed = strnicmp(mapheaderinfo[gamemap-1]->musname, S_MusicName(), 7);
-			if (!strnicmp(mapheaderinfo[gamemap-1]->musname, mapmusname, 7))
+			boolean changed = strnicmp(mapheaderinfo[gamemap-1]->musname, S_MusicName(), MAX_MUSIC_NAME);
+			if (!strnicmp(mapheaderinfo[gamemap-1]->musname, mapmusname, MAX_MUSIC_NAME))
 			{
-				strncpy(mapmusname, mapheaderinfo[gamemap-1]->muspostbossname, 7);
-				mapmusname[6] = 0;
+				strlcpy(mapmusname, mapheaderinfo[gamemap-1]->muspostbossname, MAX_MUSIC_NAME+1);
 				mapmusflags = (mapheaderinfo[gamemap-1]->muspostbosstrack & MUSIC_TRACKMASK) | MUSIC_RELOADRESET;
 				mapmusposition = mapheaderinfo[gamemap-1]->muspostbosspos;
 			}
diff --git a/src/p_local.h b/src/p_local.h
index 84a0aace09b83b4a6f5bc71552ccb910a486ae43..1d53938b6d5c0220ac90c3dc5738c12ca09d2c87 100644
--- a/src/p_local.h
+++ b/src/p_local.h
@@ -258,7 +258,7 @@ typedef enum
 
 typedef struct
 {
-	char musname[7];
+	char musname[MAX_MUSIC_NAME+1];
 	boolean looping;
 } jingle_t;
 
diff --git a/src/p_saveg.c b/src/p_saveg.c
index 8c74ebef4815cd535c16436b682efcdd769b5f42..f6c9134b2fe6e9461b05519b716dfe7e26cc387a 100644
--- a/src/p_saveg.c
+++ b/src/p_saveg.c
@@ -4355,24 +4355,59 @@ static void P_NetUnArchiveSpecials(void)
 // =======================================================================
 //          Misc
 // =======================================================================
-static inline void P_ArchiveMisc(INT16 mapnum)
+static void P_ArchiveMisc(INT16 mapnum)
 {
-	//lastmapsaved = mapnum;
 	lastmaploaded = mapnum;
 
-	if (gamecomplete)
-		mapnum |= 8192;
+#ifdef NEWMAPSAVES
+	if (mapnum >= NUMBASEMAPS)
+	{
+		WRITEINT16(save_p, NEWMAPSAVES);
+		WRITESTRINGN(save_p, G_BuildMapName(mapnum), MAX_MAP_NAME_SIZE);
+
+		UINT8 flags = 0;
+		if (gamecomplete)
+			flags |= SAVE_GAME_COMPLETE_BIT;
+
+		WRITEUINT8(save_p, flags);
+	}
+	else
+#endif
+	{
+		if (gamecomplete)
+			mapnum |= 8192;
+
+		WRITEINT16(save_p, mapnum);
+	}
 
-	WRITEINT16(save_p, mapnum);
 	WRITEUINT16(save_p, emeralds+357);
 	WRITESTRINGN(save_p, timeattackfolder, sizeof(timeattackfolder));
 }
 
-static inline void P_UnArchiveSPGame(INT16 mapoverride)
+static void P_UnArchiveSPGame(INT16 mapoverride)
 {
 	char testname[sizeof(timeattackfolder)];
 
-	gamemap = READINT16(save_p);
+	INT16 mapnum = READINT16(save_p);
+
+#ifdef NEWMAPSAVES
+	if (mapnum == NEWMAPSAVES)
+	{
+		char mapname[MAX_MAP_NAME_SIZE+1];
+
+		READSTRINGN(save_p, mapname, MAX_MAP_NAME_SIZE);
+		READUINT8(save_p); // flags
+
+		mapnum = G_GetMapNumber(mapname);
+		if (mapnum == 0)
+		{
+			// If not valid, just load MAP01 instead.
+			mapnum = 1;
+		}
+	}
+#endif
+
+	gamemap = mapnum;
 
 	if (mapoverride != 0)
 	{
@@ -4387,7 +4422,6 @@ static inline void P_UnArchiveSPGame(INT16 mapoverride)
 	if(!mapheaderinfo[gamemap-1])
 		P_AllocMapHeader(gamemap-1);
 
-	//lastmapsaved = gamemap;
 	lastmaploaded = gamemap;
 
 	tokenlist = 0;
@@ -4612,7 +4646,7 @@ static inline void P_NetArchiveEmblems(void)
 	WRITEUINT32(save_p, data->totalplaytime);
 
 	// TODO put another cipher on these things? meh, I don't care...
-	for (i = 0; i < NUMMAPS; i++)
+	for (i = 0; i < numgamemaps; i++)
 		WRITEUINT8(save_p, (data->mapvisited[i] & MV_MAX));
 
 	// To save space, use one bit per collected/achieved/unlocked flag
@@ -4654,7 +4688,7 @@ static inline void P_NetArchiveEmblems(void)
 	WRITEUINT32(save_p, data->timesBeatenUltimate);
 
 	// Main records
-	for (i = 0; i < NUMMAPS; i++)
+	for (i = 0; i < numgamemaps; i++)
 	{
 		if (data->mainrecords[i])
 		{
@@ -4671,7 +4705,7 @@ static inline void P_NetArchiveEmblems(void)
 	}
 
 	// NiGHTS records
-	for (i = 0; i < NUMMAPS; i++)
+	for (i = 0; i < numgamemaps; i++)
 	{
 		if (!data->nightsrecords[i] || !data->nightsrecords[i]->nummares)
 		{
@@ -4747,7 +4781,7 @@ static inline void P_NetUnArchiveEmblems(void)
 	data->totalplaytime = READUINT32(save_p);
 
 	// TODO put another cipher on these things? meh, I don't care...
-	for (i = 0; i < NUMMAPS; i++)
+	for (i = 0; i < numgamemaps; i++)
 		if ((data->mapvisited[i] = READUINT8(save_p)) > MV_MAX)
 			I_Error("Bad $$$.sav dearchiving Emblems (invalid visit flags)");
 
@@ -4786,7 +4820,7 @@ static inline void P_NetUnArchiveEmblems(void)
 	data->timesBeatenUltimate = READUINT32(save_p);
 
 	// Main records
-	for (i = 0; i < NUMMAPS; ++i)
+	for (i = 0; i < numgamemaps; ++i)
 	{
 		recscore = READUINT32(save_p);
 		rectime  = (tic_t)READUINT32(save_p);
@@ -4805,7 +4839,7 @@ static inline void P_NetUnArchiveEmblems(void)
 	}
 
 	// Nights records
-	for (i = 0; i < NUMMAPS; ++i)
+	for (i = 0; i < numgamemaps; ++i)
 	{
 		if ((recmares = READUINT8(save_p)) == 0)
 			continue;
diff --git a/src/p_saveg.h b/src/p_saveg.h
index 545008e7efc6af656fe94864a4ebcf8ea28e5f27..7325980050aa21993042459041e4dbb9ec022c3a 100644
--- a/src/p_saveg.h
+++ b/src/p_saveg.h
@@ -18,7 +18,11 @@
 #pragma interface
 #endif
 
-#define NEWSKINSAVES (INT16_MAX) // TODO: 2.3: Delete (Purely for backwards compatibility)
+#define SAVE_GAME_COMPLETE_BIT 0x01
+
+// TODO: 2.3: Delete both of these (purely for backwards compatibility)
+#define NEWSKINSAVES (INT16_MAX)
+#define NEWMAPSAVES (INT16_MAX)
 
 // Persistent storage/archiving.
 // These are the load / save game routines.
diff --git a/src/p_setup.c b/src/p_setup.c
index 6f5ec17dcd59814ddae0032911704c3c9e4f6b16..40be9babab87d4c715874202df838634eefffd25 100644
--- a/src/p_setup.c
+++ b/src/p_setup.c
@@ -360,8 +360,7 @@ static void P_ClearSingleMapHeaderInfo(INT16 i)
 	mapheaderinfo[num]->ssspheres = 1;
 	mapheaderinfo[num]->gravity = FRACUNIT/2;
 	mapheaderinfo[num]->keywords[0] = '\0';
-	snprintf(mapheaderinfo[num]->musname, 7, "%sM", G_BuildMapName(i));
-	mapheaderinfo[num]->musname[6] = 0;
+	snprintf(mapheaderinfo[num]->musname, MAX_MUSIC_NAME+1, "%s", G_GetDefaultMapMusic(i));
 	mapheaderinfo[num]->mustrack = 0;
 	mapheaderinfo[num]->muspos = 0;
 	mapheaderinfo[num]->musinterfadeout = 0;
@@ -7293,14 +7292,15 @@ static void P_ResetSpawnpoints(void)
 
 static void P_LoadRecordGhosts(void)
 {
-	const size_t glen = strlen(srb2home)+1+strlen("replay")+1+strlen(timeattackfolder)+1+strlen("MAPXX")+1;
+	const char *mapname = G_BuildMapName(gamemap);
+	const size_t glen = strlen(srb2home)+1+strlen("replay")+1+strlen(timeattackfolder)+1+strlen(mapname)+1;
 	char *gpath = malloc(glen);
 	INT32 i;
 
 	if (!gpath)
 		return;
 
-	sprintf(gpath,"%s"PATHSEP"replay"PATHSEP"%s"PATHSEP"%s", srb2home, timeattackfolder, G_BuildMapName(gamemap));
+	sprintf(gpath,"%s"PATHSEP"replay"PATHSEP"%s"PATHSEP"%s", srb2home, timeattackfolder, mapname);
 
 	// Best Score ghost
 	if (cv_ghost_bestscore.value)
@@ -7363,14 +7363,15 @@ static void P_LoadRecordGhosts(void)
 
 static void P_LoadNightsGhosts(void)
 {
-	const size_t glen = strlen(srb2home)+1+strlen("replay")+1+strlen(timeattackfolder)+1+strlen("MAPXX")+1;
+	const char *mapname = G_BuildMapName(gamemap);
+	const size_t glen = strlen(srb2home)+1+strlen("replay")+1+strlen(timeattackfolder)+1+strlen(mapname)+1;
 	char *gpath = malloc(glen);
 	INT32 i;
 
 	if (!gpath)
 		return;
 
-	sprintf(gpath,"%s"PATHSEP"replay"PATHSEP"%s"PATHSEP"%s", srb2home, timeattackfolder, G_BuildMapName(gamemap));
+	sprintf(gpath,"%s"PATHSEP"replay"PATHSEP"%s"PATHSEP"%s", srb2home, timeattackfolder, mapname);
 
 	// Best Score ghost
 	if (cv_ghost_bestscore.value)
@@ -7544,7 +7545,7 @@ static void P_RunSpecialStageWipe(void)
 	// Fade music! Time it to S3KAF: 0.25 seconds is snappy.
 	if (RESETMUSIC ||
 		strnicmp(S_MusicName(),
-		(mapmusflags & MUSIC_RELOADRESET) ? mapheaderinfo[gamemap - 1]->musname : mapmusname, 7))
+		(mapmusflags & MUSIC_RELOADRESET) ? mapheaderinfo[gamemap - 1]->musname : mapmusname, MAX_MUSIC_NAME))
 		S_FadeOutStopMusic(MUSICRATE/4); //FixedMul(FixedDiv(F_GetWipeLength(wipedefs[wipe_speclevel_towhite])*NEWTICRATERATIO, NEWTICRATE), MUSICRATE)
 
 	F_WipeStartScreen();
@@ -7812,7 +7813,7 @@ boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate)
 	// But don't halt the music! S_Start will take care of that. This dodges a MIDI crash bug.
 	if (!(reloadinggamestate || titlemapinaction) && (RESETMUSIC ||
 		strnicmp(S_MusicName(),
-			(mapmusflags & MUSIC_RELOADRESET) ? mapheaderinfo[gamemap-1]->musname : mapmusname, 7)))
+			(mapmusflags & MUSIC_RELOADRESET) ? mapheaderinfo[gamemap-1]->musname : mapmusname, MAX_MUSIC_NAME)))
 	{
 		S_FadeMusic(0, FixedMul(
 			FixedDiv((F_GetWipeLength(wipedefs[wipe_level_toblack])-2)*NEWTICRATERATIO, NEWTICRATE), MUSICRATE));
@@ -8118,6 +8119,80 @@ static lumpinfo_t* FindFolder(const char *folName, UINT16 *start, UINT16 *end, l
 	return lumpinfo;
 }
 
+void P_LoadMapsFromFile(UINT16 wadnum, boolean is_pwad)
+{
+	boolean mapsadded = false;
+
+	lumpinfo_t *lumpinfo = wadfiles[wadnum]->lumpinfo;
+
+	INT16 num;
+	const char *name;
+
+	if (W_FileHasFolders(wadfiles[wadnum]))
+	{
+		UINT16 posStart = W_CheckNumForFolderStartPK3("Maps/", wadnum, 0);
+		if (posStart == INT16_MAX)
+			return;
+
+		UINT16 posEnd = W_CheckNumForFolderEndPK3("Maps/", wadnum, posStart);
+
+		lumpinfo += posStart;
+
+		for (; posStart < posEnd; posStart++, lumpinfo++)
+		{
+			name = M_GetFilenameFromPath(lumpinfo->fullname); // Full lump name, with its extension
+
+			if (!M_CheckFilenameExtension(name, "wad"))
+			{
+				// Extension must be .wad
+				continue;
+			}
+
+			name = lumpinfo->longname; // Name without the extension
+
+			num = G_AddMap(name);
+
+			if (num == 0)
+			{
+				CONS_Alert(CONS_ERROR, "Too many maps loaded!\n");
+				break;
+			}
+			//If you replaced the map you're on, end the level when done.
+			else if (num == gamemap)
+				replacedcurrentmap = true;
+
+			if (is_pwad)
+				CONS_Printf("%s\n", name);
+			mapsadded = true;
+		}
+	}
+	else
+	{
+		UINT16 numlumps = wadfiles[wadnum]->numlumps;
+		for (size_t i = 0; i < numlumps; i++, lumpinfo++)
+		{
+			name = lumpinfo->name;
+			if (name[0] == 'M' && name[1] == 'A' && name[2] == 'P') // Ignore the headers
+			{
+				if (name[5]!='\0')
+					continue;
+				num = (INT16)M_MapNumber(name[3], name[4]);
+
+				//If you replaced the map you're on, end the level when done.
+				if (num == gamemap)
+					replacedcurrentmap = true;
+
+				if (is_pwad)
+					CONS_Printf("%s\n", name);
+				mapsadded = true;
+			}
+		}
+	}
+
+	if (!mapsadded && is_pwad)
+		CONS_Printf(M_GetText("No maps added\n"));
+}
+
 //
 // Add a wadfile to the active wad files,
 // replace sounds, musics, patches, textures, sprites and maps
@@ -8131,8 +8206,6 @@ static boolean P_LoadAddon(UINT16 numlumps)
 	lumpinfo_t *lumpinfo;
 
 	//boolean texturechange = false; ///\todo Useless; broken when back-frontporting PK3 changes?
-	boolean mapsadded = false;
-	boolean replacedcurrentmap = false;
 
 	// Vars to help us with the position start and amount of each resource type.
 	// Useful for PK3s since they use folders.
@@ -8272,32 +8345,7 @@ static boolean P_LoadAddon(UINT16 numlumps)
 	//
 	S_LoadMusicDefs(wadnum);
 
-	//
-	// search for maps
-	//
-	lumpinfo = wadfiles[wadnum]->lumpinfo;
-	for (i = 0; i < numlumps; i++, lumpinfo++)
-	{
-		name = lumpinfo->name;
-		if (name[0] == 'M' && name[1] == 'A' && name[2] == 'P') // Ignore the headers
-		{
-			INT16 num;
-			if (name[5]!='\0')
-				continue;
-			num = (INT16)M_MapNumber(name[3], name[4]);
-
-			//If you replaced the map you're on, end the level when done.
-			if (num == gamemap)
-				replacedcurrentmap = true;
-
-			CONS_Printf("%s\n", name);
-			mapsadded = true;
-		}
-	}
-	if (!mapsadded)
-		CONS_Printf(M_GetText("No maps added\n"));
-
-	R_LoadSpriteInfoLumps(wadnum, numlumps);
+	R_LoadSpriteInfoLumps(wadnum);
 
 #ifdef HWRENDER
 	HWR_ReloadModels();
@@ -8311,11 +8359,16 @@ static boolean P_LoadAddon(UINT16 numlumps)
 	if (modifiedgame && (cursaveslot > 0))
 		cursaveslot = 0;
 
-	if (replacedcurrentmap && gamestate == GS_LEVEL && (netgame || multiplayer))
+	if (replacedcurrentmap)
 	{
-		CONS_Printf(M_GetText("Current map %d replaced by added file, ending the level to ensure consistency.\n"), gamemap);
-		if (server)
-			D_SendExitLevel(false);
+		replacedcurrentmap = false;
+
+		if (gamestate == GS_LEVEL && (netgame || multiplayer))
+		{
+			CONS_Printf(M_GetText("Current map %d replaced by added file, ending the level to ensure consistency.\n"), gamemap);
+			if (server)
+				D_SendExitLevel(false);
+		}
 	}
 
 	return true;
diff --git a/src/p_setup.h b/src/p_setup.h
index c6f4f741c01c56fecfc5b0fa0fb267fb1c975b16..23c6d4b1c8883675e6100e4664ad3197097d40d4 100644
--- a/src/p_setup.h
+++ b/src/p_setup.h
@@ -107,6 +107,7 @@ boolean P_AddFolder(const char *folderpath);
 boolean P_RunSOC(const char *socfilename);
 void P_LoadSoundsRange(UINT16 wadnum, UINT16 first, UINT16 num);
 void P_LoadMusicsRange(UINT16 wadnum, UINT16 first, UINT16 num);
+void P_LoadMapsFromFile(UINT16 wadnum, boolean is_pwad);
 //void P_WriteThings(void);
 size_t P_PrecacheLevelFlats(void);
 void P_AllocMapHeader(INT16 i);
diff --git a/src/p_spec.c b/src/p_spec.c
index 131a58d20c4a5b3d7f5800b2b3a2e4db93a229c7..594a11e7d3736960e22c099b28169af2ef8763e0 100644
--- a/src/p_spec.c
+++ b/src/p_spec.c
@@ -2522,7 +2522,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 			// console player only unless TMM_ALLPLAYERS is set
 			if ((line->args[0] & TMM_ALLPLAYERS) || (mo && mo->player && P_IsLocalPlayer(mo->player)) || titlemapinaction)
 			{
-				boolean musicsame = (!line->stringargs[0] || !line->stringargs[0][0] || !strnicmp(line->stringargs[0], S_MusicName(), 7));
+				boolean musicsame = (!line->stringargs[0] || !line->stringargs[0][0] || !strnicmp(line->stringargs[0], S_MusicName(), MAX_MUSIC_NAME));
 				UINT16 tracknum = (UINT16)max(line->args[6], 0);
 				INT32 position = (INT32)max(line->args[1], 0);
 				UINT32 prefadems = (UINT32)max(line->args[2], 0);
@@ -2565,8 +2565,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 						strcpy(mapmusname, "");
 					else
 					{
-						strncpy(mapmusname, line->stringargs[0], 7);
-						mapmusname[6] = 0;
+						strlcpy(mapmusname, line->stringargs[0], MAX_MUSIC_NAME+1);
 					}
 
 					mapmusflags = tracknum & MUSIC_TRACKMASK;
diff --git a/src/p_user.c b/src/p_user.c
index 0c21a1cc15fdcf310276222879c0fb04e232a01e..d7a4de627a19262db9281bde3b96ff7d5523eb84 100644
--- a/src/p_user.c
+++ b/src/p_user.c
@@ -1576,13 +1576,8 @@ void P_PlayJingle(player_t *player, jingletype_t jingletype)
 	UINT16 musflags = 0;
 	boolean looping = jingleinfo[jingletype].looping;
 
-	char newmusic[7];
-	strncpy(newmusic, musname, 7);
-#ifdef HAVE_LUA_MUSICPLUS
-	if(LUAh_MusicJingle(jingletype, newmusic, &musflags, &looping))
-		return;
-#endif
-	newmusic[6] = 0;
+	char newmusic[MAX_MUSIC_NAME];
+	strlcpy(newmusic, musname, MAX_MUSIC_NAME);
 
 	P_PlayJingleMusic(player, newmusic, musflags, looping, jingletype);
 }
diff --git a/src/r_picformats.c b/src/r_picformats.c
index 3e817f4a06199bb47c38f198c6ceb6cd447a6452..8f577e426102afc7de9022b497251a6a2318d5ea 100644
--- a/src/r_picformats.c
+++ b/src/r_picformats.c
@@ -1678,13 +1678,13 @@ void R_ParseSPRTINFOLump(UINT16 wadNum, UINT16 lumpNum)
 //
 // Load and read every SPRTINFO lump from the specified file.
 //
-void R_LoadSpriteInfoLumps(UINT16 wadnum, UINT16 numlumps)
+void R_LoadSpriteInfoLumps(UINT16 wadnum)
 {
 	lumpinfo_t *lumpinfo = wadfiles[wadnum]->lumpinfo;
 	UINT16 i;
 	char *name;
 
-	for (i = 0; i < numlumps; i++, lumpinfo++)
+	for (i = 0; i < wadfiles[wadnum]->numlumps; i++, lumpinfo++)
 	{
 		name = lumpinfo->name;
 		// Load SPRTINFO and SPR_ lumps as SpriteInfo
diff --git a/src/r_picformats.h b/src/r_picformats.h
index 4050a1b71dc01e8a60cbb14cbe017716c61038c3..c06d2d55ec0bfb8178f45caab6a01e21603c7898 100644
--- a/src/r_picformats.h
+++ b/src/r_picformats.h
@@ -122,7 +122,7 @@ boolean Picture_PNGDimensions(UINT8 *png, INT32 *width, INT32 *height, INT16 *to
 
 // SpriteInfo
 extern spriteinfo_t spriteinfo[NUMSPRITES];
-void R_LoadSpriteInfoLumps(UINT16 wadnum, UINT16 numlumps);
+void R_LoadSpriteInfoLumps(UINT16 wadnum);
 void R_ParseSPRTINFOLump(UINT16 wadNum, UINT16 lumpNum);
 
 #endif // __R_PICFORMATS__
diff --git a/src/r_things.c b/src/r_things.c
index 557e1681439cd13d6bd871f7c33a789a0d2ba2be..d3a5d960c63ebef9c7a0146914cd7991902a3e6d 100644
--- a/src/r_things.c
+++ b/src/r_things.c
@@ -583,7 +583,7 @@ void R_InitSprites(void)
 	{
 		R_AddSkins((UINT16)i, true);
 		R_PatchSkins((UINT16)i, true);
-		R_LoadSpriteInfoLumps(i, wadfiles[i]->numlumps);
+		R_LoadSpriteInfoLumps(i);
 	}
 	ST_ReloadSkinFaceGraphics();
 
diff --git a/src/s_sound.c b/src/s_sound.c
index a579292ff3aa791176b389d686901799c47c89a1..7b7268566a8ae362da65b1db3284ee520ba62365 100644
--- a/src/s_sound.c
+++ b/src/s_sound.c
@@ -1352,12 +1352,12 @@ void S_InitSfxChannels(INT32 sfxVolume)
 /// Music
 /// ------------------------
 
-static char      music_name[7]; // up to 6-character name
+static char      music_name[MAX_MUSIC_NAME+1];
 static void      *music_data;
 static UINT16    music_flags;
 static boolean   music_looping;
 
-static char      queue_name[7];
+static char      queue_name[MAX_MUSIC_NAME+1];
 static UINT16    queue_flags;
 static boolean   queue_looping;
 static UINT32    queue_position;
@@ -1568,9 +1568,8 @@ ReadMusicDefFields (UINT16 wadnum, int line, boolean fields, char *stoken,
 			} else if (!stricmp(stoken, "soundtestpage")) {
 				def->soundtestpage = (UINT8)i;
 			} else if (!stricmp(stoken, "soundtestcond")) {
-				// Convert to map number
-				if (textline[0] >= 'A' && textline[0] <= 'Z' && textline[2] == '\0')
-					i = M_MapNumber(textline[0], textline[1]);
+				if (!i)
+					i = G_GetMapNumber(textline);
 				def->soundtestcond = (INT16)i;
 			} else if (!stricmp(stoken, "stoppingtime")) {
 				double stoppingtime = atof(textline)*TICRATE;
@@ -1778,8 +1777,8 @@ const char *S_MusicName(void)
 boolean S_MusicExists(const char *mname, boolean checkMIDI, boolean checkDigi)
 {
 	return (
-		(checkDigi ? W_CheckNumForName(va("O_%s", mname)) != LUMPERROR : false)
-		|| (checkMIDI ? W_CheckNumForName(va("D_%s", mname)) != LUMPERROR : false)
+		(checkDigi ? W_CheckNumForLongName(va("O_%s", mname)) != LUMPERROR : false)
+		|| (checkMIDI ? W_CheckNumForLongName(va("D_%s", mname)) != LUMPERROR : false)
 	);
 }
 
@@ -1826,7 +1825,7 @@ UINT32 S_GetMusicPosition(void)
 /// In this section: mazmazz doesn't know how to do dynamic arrays or struct pointers!
 /// ------------------------
 
-char music_stack_nextmusname[7];
+char music_stack_nextmusname[MAX_MUSIC_NAME+1];
 boolean music_stack_noposition = false;
 UINT32 music_stack_fadeout = 0;
 UINT32 music_stack_fadein = 0;
@@ -1910,7 +1909,7 @@ static void S_AddMusicStackEntry(const char *mname, UINT16 mflags, boolean loopi
 	if (!music_stacks)
 	{
 		music_stacks = Z_Calloc(sizeof (*mst), PU_MUSIC, NULL);
-		strncpy(music_stacks->musname, (status == JT_MASTER ? mname : (S_CheckQueue() ? queue_name : mapmusname)), 7);
+		strlcpy(music_stacks->musname, (status == JT_MASTER ? mname : (S_CheckQueue() ? queue_name : mapmusname)), MAX_MUSIC_NAME+1);
 		music_stacks->musflags = (status == JT_MASTER ? mflags : (S_CheckQueue() ? queue_flags : mapmusflags));
 		music_stacks->looping = (status == JT_MASTER ? looping : (S_CheckQueue() ? queue_looping : true));
 		music_stacks->position = (status == JT_MASTER ? position : (S_CheckQueue() ? queue_position : S_GetMusicPosition()));
@@ -1928,8 +1927,7 @@ static void S_AddMusicStackEntry(const char *mname, UINT16 mflags, boolean loopi
 
 	// create our new entry
 	new_mst = Z_Calloc(sizeof (*new_mst), PU_MUSIC, NULL);
-	strncpy(new_mst->musname, mname, 7);
-	new_mst->musname[6] = 0;
+	strlcpy(new_mst->musname, mname, MAX_MUSIC_NAME+1);
 	new_mst->musflags = mflags;
 	new_mst->looping = looping;
 	new_mst->position = position;
@@ -2033,13 +2031,13 @@ boolean S_RecallMusic(UINT16 status, boolean fromfirst)
 	if (result)
 	{
 		*entry = *result;
-		strncpy(entry->musname, result->musname, 7);
+		strlcpy(entry->musname, result->musname, MAX_MUSIC_NAME+1);
 	}
 
 	// no result, just grab mapmusname
 	if (!result || !entry->musname[0] || ((status == JT_MASTER || (music_stacks ? !music_stacks->status : false)) && !entry->status))
 	{
-		strncpy(entry->musname, mapmusname, 7);
+		strlcpy(entry->musname, mapmusname, MAX_MUSIC_NAME+1);
 		entry->musflags = mapmusflags;
 		entry->looping = true;
 		entry->position = mapmusposition;
@@ -2051,10 +2049,10 @@ boolean S_RecallMusic(UINT16 status, boolean fromfirst)
 
 	if (entry->status == JT_MASTER)
 	{
-		mapmuschanged = strnicmp(entry->musname, mapmusname, 7);
+		mapmuschanged = strnicmp(entry->musname, mapmusname, MAX_MUSIC_NAME);
 		if (mapmuschanged)
 		{
-			strncpy(entry->musname, mapmusname, 7);
+			strlcpy(entry->musname, mapmusname, MAX_MUSIC_NAME+1);
 			entry->musflags = mapmusflags;
 			entry->looping = true;
 			entry->position = mapmusposition;
@@ -2071,7 +2069,7 @@ boolean S_RecallMusic(UINT16 status, boolean fromfirst)
 		return false;
 	}
 
-	if (strncmp(entry->musname, S_MusicName(), 7) || // don't restart music if we're already playing it
+	if (strncmp(entry->musname, S_MusicName(), MAX_MUSIC_NAME) || // don't restart music if we're already playing it
 		(midipref != currentmidi && S_PrefAvailable(midipref, entry->musname))) // but do if the user's preference has changed
 	{
 		if (music_stack_fadeout)
@@ -2122,9 +2120,9 @@ static lumpnum_t S_GetMusicLumpNum(const char *mname)
 	boolean midipref = cv_musicpref.value;
 
 	if (S_PrefAvailable(midipref, mname))
-		return W_GetNumForName(va(midipref ? "d_%s":"o_%s", mname));
+		return W_GetNumForLongName(va(midipref ? "D_%s":"O_%s", mname));
 	else if (S_PrefAvailable(!midipref, mname))
-		return W_GetNumForName(va(midipref ? "o_%s":"d_%s", mname));
+		return W_GetNumForLongName(va(midipref ? "O_%s":"D_%s", mname));
 	else
 		return LUMPERROR;
 }
@@ -2141,18 +2139,16 @@ static boolean S_LoadMusic(const char *mname)
 
 	if (mlumpnum == LUMPERROR)
 	{
-		CONS_Alert(CONS_ERROR, "Music %.6s could not be loaded: lump not found!\n", mname);
+		CONS_Alert(CONS_ERROR, "Music %s could not be loaded: lump not found!\n", mname);
 		return false;
 	}
 
 	// load & register it
 	mdata = W_CacheLumpNum(mlumpnum, PU_MUSIC);
 
-
 	if (I_LoadSong(mdata, W_LumpLength(mlumpnum)))
 	{
-		strncpy(music_name, mname, 7);
-		music_name[6] = 0;
+		strlcpy(music_name, mname, MAX_MUSIC_NAME+1);
 		music_data = mdata;
 		return true;
 	}
@@ -2213,7 +2209,7 @@ static boolean S_PlayMusic(boolean looping, UINT32 fadeinms)
 
 static void S_QueueMusic(const char *mmusic, UINT16 mflags, boolean looping, UINT32 position, UINT32 fadeinms)
 {
-	strncpy(queue_name, mmusic, 7);
+	strlcpy(queue_name, mmusic, MAX_MUSIC_NAME+1);
 	queue_flags = mflags;
 	queue_looping = looping;
 	queue_position = position;
@@ -2238,7 +2234,7 @@ static void S_ChangeMusicToQueue(void)
 
 void S_ChangeMusicEx(const char *mmusic, UINT16 mflags, boolean looping, UINT32 position, UINT32 prefadems, UINT32 fadeinms)
 {
-	char newmusic[7];
+	char newmusic[MAX_MUSIC_NAME+1];
 
 	struct MusicChange hook_param = {
 		newmusic,
@@ -2255,10 +2251,9 @@ void S_ChangeMusicEx(const char *mmusic, UINT16 mflags, boolean looping, UINT32
 	if (S_MusicDisabled())
 		return;
 
-	strncpy(newmusic, mmusic, 7);
+	strlcpy(newmusic, mmusic, MAX_MUSIC_NAME+1);
 	if (LUA_HookMusicChange(music_name, &hook_param))
 		return;
-	newmusic[6] = 0;
 
 	// No Music (empty string)
 	if (newmusic[0] == 0)
@@ -2278,7 +2273,7 @@ void S_ChangeMusicEx(const char *mmusic, UINT16 mflags, boolean looping, UINT32
 		I_FadeSong(0, prefadems, S_ChangeMusicToQueue);
 		return;
 	}
-	else if (strnicmp(music_name, newmusic, 6) || (mflags & MUSIC_FORCERESET) ||
+	else if (strnicmp(music_name, newmusic, MAX_MUSIC_NAME) || (mflags & MUSIC_FORCERESET) ||
 		(midipref != currentmidi && S_PrefAvailable(midipref, newmusic)))
 	{
 		CONS_Debug(DBG_DETAILED, "Now playing song %s\n", newmusic);
@@ -2303,7 +2298,7 @@ void S_ChangeMusicEx(const char *mmusic, UINT16 mflags, boolean looping, UINT32
 	{
 		I_SetSongPosition(position);
 		I_FadeSong(100, fadeinms, NULL);
-}
+	}
 	else // reset volume to 100 with same music
 	{
 		I_StopFadingSong();
@@ -2433,8 +2428,7 @@ void S_StartEx(boolean reset)
 {
 	if (mapmusflags & MUSIC_RELOADRESET)
 	{
-		strncpy(mapmusname, mapheaderinfo[gamemap-1]->musname, 7);
-		mapmusname[6] = 0;
+		strlcpy(mapmusname, mapheaderinfo[gamemap-1]->musname, MAX_MUSIC_NAME+1);
 		mapmusflags = (mapheaderinfo[gamemap-1]->mustrack & MUSIC_TRACKMASK);
 		mapmusposition = mapheaderinfo[gamemap-1]->muspos;
 	}
@@ -2487,18 +2481,17 @@ static void Command_Tunes_f(void)
 		track = mapheaderinfo[gamemap-1]->mustrack;
 	}
 
-	if (strlen(tunearg) > 6) // This is automatic -- just show the error just in case
-		CONS_Alert(CONS_NOTICE, M_GetText("Music name too long - truncated to six characters.\n"));
+	if (strlen(tunearg) > MAX_MUSIC_NAME) // This is automatic -- just show the error just in case
+		CONS_Alert(CONS_NOTICE, M_GetText("Music name too long - truncated to %d characters.\n"), MAX_MUSIC_NAME);
 
 	if (argc > 2)
 		track = (UINT16)atoi(COM_Argv(2))-1;
 
-	strncpy(mapmusname, tunearg, 7);
+	strlcpy(mapmusname, tunearg, MAX_MUSIC_NAME+1);
 
 	if (argc > 4)
 		position = (UINT32)atoi(COM_Argv(4));
 
-	mapmusname[6] = 0;
 	mapmusflags = (track & MUSIC_TRACKMASK);
 	mapmusposition = position;
 
diff --git a/src/s_sound.h b/src/s_sound.h
index 288859c8d233dc9696118ef1d93655f98be514b9..66cb80ff88e4044eb94641f006584d5db2ae3ed2 100644
--- a/src/s_sound.h
+++ b/src/s_sound.h
@@ -182,7 +182,7 @@ boolean S_SpeedMusic(float speed);
 // Music definitions
 typedef struct musicdef_s
 {
-	char name[7];
+	char name[MAX_MUSIC_NAME+1];
 	char title[32];
 	char alttitle[64];
 	char authors[256];
@@ -238,7 +238,7 @@ UINT32 S_GetMusicPosition(void);
 
 typedef struct musicstack_s
 {
-	char musname[7];
+	char musname[MAX_MUSIC_NAME+1];
 	UINT16 musflags;
 	boolean looping;
 	UINT32 position;
@@ -251,7 +251,7 @@ typedef struct musicstack_s
     struct musicstack_s *next;
 } musicstack_t;
 
-extern char music_stack_nextmusname[7];
+extern char music_stack_nextmusname[MAX_MUSIC_NAME+1];
 extern boolean music_stack_noposition;
 extern UINT32 music_stack_fadeout;
 extern UINT32 music_stack_fadein;
diff --git a/src/w_wad.c b/src/w_wad.c
index 051469ef57285b28003b43569de8e625856ce5e8..13749f630078f27382a456d52b522eda1798f892 100644
--- a/src/w_wad.c
+++ b/src/w_wad.c
@@ -979,6 +979,9 @@ UINT16 W_InitFile(const char *filename, boolean mainfile, boolean startup)
 	// Read shaders from file
 	W_ReadFileShaders(wadfile);
 
+	// Load maps from file
+	P_LoadMapsFromFile(numwadfiles - 1, !startup);
+
 	// TODO: HACK ALERT - Load Lua & SOC stuff right here. I feel like this should be out of this place, but... Let's stick with this for now.
 	switch (wadfile->type)
 	{