diff --git a/src/command.c b/src/command.c
index dbdaceab5560f21aa2229d9e2ecaaef69b97c224..cb6803b571cc70a37c015caa91a430be265396ee 100644
--- a/src/command.c
+++ b/src/command.c
@@ -823,8 +823,13 @@ static void COM_Help_f(void)
 					if (!stricmp(cvar->PossibleValue[MINVAL].strvalue, "MIN"))
 					{
 						if (floatmode)
-							CONS_Printf("  range from %f to %f\n", FIXED_TO_FLOAT(cvar->PossibleValue[MINVAL].value),
-								FIXED_TO_FLOAT(cvar->PossibleValue[MAXVAL].value));
+						{
+							float fu = FIXED_TO_FLOAT(cvar->PossibleValue[MINVAL].value);
+							float ck = FIXED_TO_FLOAT(cvar->PossibleValue[MAXVAL].value);
+							CONS_Printf("  range from %ld%s to %ld%s\n",
+									(long)fu, M_Ftrim(fu),
+									(long)ck, M_Ftrim(ck));
+						}
 						else
 							CONS_Printf("  range from %d to %d\n", cvar->PossibleValue[MINVAL].value,
 								cvar->PossibleValue[MAXVAL].value);
@@ -973,7 +978,10 @@ static void COM_Add_f(void)
 	}
 
 	if (( cvar->flags & CV_FLOAT ))
-		CV_Set(cvar, va("%f", FIXED_TO_FLOAT (cvar->value) + atof(COM_Argv(2))));
+	{
+		float n =FIXED_TO_FLOAT (cvar->value) + atof(COM_Argv(2));
+		CV_Set(cvar, va("%ld%s", (long)n, M_Ftrim(n)));
+	}
 	else
 		CV_AddValue(cvar, atoi(COM_Argv(2)));
 }
diff --git a/src/d_netcmd.c b/src/d_netcmd.c
index 3204d3227f4ecb9543df2dfaecbf7451e283f74c..bac297f8e10a4183d30347ec4068afa5e7e37b05 100644
--- a/src/d_netcmd.c
+++ b/src/d_netcmd.c
@@ -677,6 +677,7 @@ void D_RegisterClientCommands(void)
 	// GIF variables
 	CV_RegisterVar(&cv_gif_optimize);
 	CV_RegisterVar(&cv_gif_downscale);
+	CV_RegisterVar(&cv_gif_localcolortable);
 
 #ifdef WALLSPLATS
 	CV_RegisterVar(&cv_splats);
diff --git a/src/dehacked.c b/src/dehacked.c
index 6093d6fd6e92250d0103f3dbd2fa781544bd3101..4d114727627d672eaa1adce3f4c21e272fcaaacf 100644
--- a/src/dehacked.c
+++ b/src/dehacked.c
@@ -585,21 +585,30 @@ static void readfreeslots(MYFILE *f)
 					continue;
 				// Copy in the spr2 name and increment free_spr2.
 				if (free_spr2 < NUMPLAYERSPRITES) {
-					CONS_Printf("Sprite SPR2_%s allocated.\n",word);
 					strncpy(spr2names[free_spr2],word,4);
 					spr2defaults[free_spr2] = 0;
 					spr2names[free_spr2++][4] = 0;
 				} else
-					CONS_Alert(CONS_WARNING, "Ran out of free SPR2 slots!\n");
+					deh_warning("Ran out of free SPR2 slots!\n");
 			}
 			else if (fastcmp(type, "TOL"))
 			{
-				if (lastcustomtol > 31)
-					CONS_Alert(CONS_WARNING, "Ran out of free typeoflevel slots!\n");
+				// Search if we already have a typeoflevel by that name...
+				for (i = 0; TYPEOFLEVEL[i].name; i++)
+					if (fastcmp(word, TYPEOFLEVEL[i].name))
+						break;
+
+				// We found it? Then don't allocate another one.
+				if (TYPEOFLEVEL[i].name)
+					continue;
+
+				// We don't, so freeslot it.
+				if (lastcustomtol == (UINT32)MAXTOL) // Unless you have way too many, since they're flags.
+					deh_warning("Ran out of free typeoflevel slots!\n");
 				else
 				{
-					G_AddTOL((1<<lastcustomtol), word);
-					lastcustomtol++;
+					G_AddTOL(lastcustomtol, word);
+					lastcustomtol <<= 1;
 				}
 			}
 			else
@@ -1105,38 +1114,7 @@ static void readsprite2(MYFILE *f, INT32 num)
 	Z_Free(s);
 }
 
-INT32 numtolinfo = NUMBASETOL;
-UINT32 lastcustomtol = 13;
-
-tolinfo_t TYPEOFLEVEL[NUMMAXTOL] = {
-	{"SOLO",TOL_SP},
-	{"SP",TOL_SP},
-	{"SINGLEPLAYER",TOL_SP},
-	{"SINGLE",TOL_SP},
-
-	{"COOP",TOL_COOP},
-	{"CO-OP",TOL_COOP},
-
-	{"COMPETITION",TOL_COMPETITION},
-	{"RACE",TOL_RACE},
-
-	{"MATCH",TOL_MATCH},
-	{"TAG",TOL_TAG},
-	{"CTF",TOL_CTF},
-
-	{"2D",TOL_2D},
-	{"MARIO",TOL_MARIO},
-	{"NIGHTS",TOL_NIGHTS},
-	{"OLDBRAK",TOL_ERZ3},
-
-	{"XMAS",TOL_XMAS},
-	{"CHRISTMAS",TOL_XMAS},
-	{"WINTER",TOL_XMAS},
-
-	{NULL, 0}
-};
-
-// copypasted from readPlayer :sleep:
+// copypasted from readPlayer :]
 static const char *const GAMETYPERULE_LIST[];
 static void readgametype(MYFILE *f, char *gtname)
 {
@@ -10468,16 +10446,23 @@ static inline int lib_freeslot(lua_State *L)
 		}
 		else if (fastcmp(type, "TOL"))
 		{
-			if (lastcustomtol > 31)
-				CONS_Alert(CONS_WARNING, "Ran out of free typeoflevel slots!\n");
-			else
-			{
-				UINT32 newtol = (1<<lastcustomtol);
-				CONS_Printf("TypeOfLevel TOL_%s allocated.\n",word);
-				G_AddTOL(newtol, word);
-				lua_pushinteger(L, newtol);
-				lastcustomtol++;
-				r++;
+			// Search if we already have a typeoflevel by that name...
+			int i;
+			for (i = 0; TYPEOFLEVEL[i].name; i++)
+				if (fastcmp(word, TYPEOFLEVEL[i].name))
+					break;
+
+			// We don't, so allocate a new one.
+			if (TYPEOFLEVEL[i].name == NULL) {
+				if (lastcustomtol == (UINT32)MAXTOL) // Unless you have way too many, since they're flags.
+					CONS_Alert(CONS_WARNING, "Ran out of free typeoflevel slots!\n");
+				else {
+					CONS_Printf("TypeOfLevel TOL_%s allocated.\n",word);
+					G_AddTOL(lastcustomtol, word);
+					lua_pushinteger(L, lastcustomtol);
+					lastcustomtol <<= 1;
+					r++;
+				}
 			}
 		}
 		Z_Free(s);
diff --git a/src/doomstat.h b/src/doomstat.h
index c7c12632ab1dcf45c8ab5040ccb5095c91d799dc..cf02e438900c7996ed5a058139f5b6bfc278b8e2 100644
--- a/src/doomstat.h
+++ b/src/doomstat.h
@@ -437,6 +437,7 @@ extern const char *Gametype_ConstantNames[NUMGAMETYPES];
 extern INT32 pointlimits[NUMGAMETYPES];
 extern INT32 timelimits[NUMGAMETYPES];
 
+// TypeOfLevel things
 enum TypeOfLevel
 {
 	TOL_SP          = 0x01, ///< Single Player
@@ -461,16 +462,16 @@ enum TypeOfLevel
 	TOL_XMAS   = 0x1000, ///< Christmas NiGHTS
 };
 
-#define NUMBASETOL 18
-#define NUMMAXTOL (18 + NUMGAMETYPEFREESLOTS)
+#define MAXTOL             (1<<31)
+#define NUMBASETOLNAMES    (19)
+#define NUMTOLNAMES        (NUMBASETOLNAMES + NUMGAMETYPEFREESLOTS)
 
 typedef struct
 {
 	const char *name;
 	UINT32 flag;
 } tolinfo_t;
-extern tolinfo_t TYPEOFLEVEL[NUMMAXTOL];
-extern INT32 numtolinfo;
+extern tolinfo_t TYPEOFLEVEL[NUMTOLNAMES];
 extern UINT32 lastcustomtol;
 
 extern tic_t totalplaytime;
diff --git a/src/g_game.c b/src/g_game.c
index 3f45988b15d1521c8cc28cb92781503b496fa0f9..68f548fbf8e4d6fa3101a1ea255358931eea1d3f 100644
--- a/src/g_game.c
+++ b/src/g_game.c
@@ -3384,6 +3384,36 @@ UINT32 gametypetol[NUMGAMETYPES] =
 	TOL_CTF, // CTF
 };
 
+tolinfo_t TYPEOFLEVEL[NUMTOLNAMES] = {
+	{"SOLO",TOL_SP},
+	{"SP",TOL_SP},
+	{"SINGLEPLAYER",TOL_SP},
+	{"SINGLE",TOL_SP},
+
+	{"COOP",TOL_COOP},
+	{"CO-OP",TOL_COOP},
+
+	{"COMPETITION",TOL_COMPETITION},
+	{"RACE",TOL_RACE},
+
+	{"MATCH",TOL_MATCH},
+	{"TAG",TOL_TAG},
+	{"CTF",TOL_CTF},
+
+	{"2D",TOL_2D},
+	{"MARIO",TOL_MARIO},
+	{"NIGHTS",TOL_NIGHTS},
+	{"OLDBRAK",TOL_ERZ3},
+
+	{"XMAS",TOL_XMAS},
+	{"CHRISTMAS",TOL_XMAS},
+	{"WINTER",TOL_XMAS},
+
+	{NULL, 0}
+};
+
+UINT32 lastcustomtol = (TOL_XMAS<<1);
+
 //
 // G_AddTOL
 //
@@ -3391,16 +3421,16 @@ UINT32 gametypetol[NUMGAMETYPES] =
 //
 void G_AddTOL(UINT32 newtol, const char *tolname)
 {
-	TYPEOFLEVEL[numtolinfo].name = Z_StrDup(tolname);
-	TYPEOFLEVEL[numtolinfo].flag = newtol;
-	numtolinfo++;
+	INT32 i;
+	for (i = 0; TYPEOFLEVEL[i].name; i++)
+		;
 
-	TYPEOFLEVEL[numtolinfo].name = NULL;
-	TYPEOFLEVEL[numtolinfo].flag = 0;
+	TYPEOFLEVEL[i].name = Z_StrDup(tolname);
+	TYPEOFLEVEL[i].flag = newtol;
 }
 
 //
-// G_AddTOL
+// G_AddGametypeTOL
 //
 // Assigns a type of level to a gametype.
 //
diff --git a/src/hardware/hw_md2.c b/src/hardware/hw_md2.c
index a107a6d051f9064f2e436cd7d5c434d59ebcc9f6..7c49beb528ba6f0e1ddf8c3c398646cb586d453b 100644
--- a/src/hardware/hw_md2.c
+++ b/src/hardware/hw_md2.c
@@ -476,8 +476,9 @@ void HWR_InitModels(void)
 	size_t i;
 	INT32 s;
 	FILE *f;
-	char name[18], filename[32];
+	char name[24], filename[32];
 	float scale, offset;
+	size_t prefixlen;
 
 	CONS_Printf("HWR_InitModels()...\n");
 	for (s = 0; s < MAXSKINS; s++)
@@ -509,46 +510,54 @@ void HWR_InitModels(void)
 		nomd2s = true;
 		return;
 	}
-	while (fscanf(f, "%19s %31s %f %f", name, filename, &scale, &offset) == 4)
+
+	// length of the player model prefix
+	prefixlen = strlen(PLAYERMODELPREFIX);
+
+	while (fscanf(f, "%25s %31s %f %f", name, filename, &scale, &offset) == 4)
 	{
-		if (stricmp(name, "PLAY") == 0)
+		char *skinname = name;
+		size_t len = strlen(name);
+
+		// check for the player model prefix.
+		if (!strnicmp(name, PLAYERMODELPREFIX, prefixlen) && (len > prefixlen))
 		{
-			CONS_Printf("Model for sprite PLAY detected in models.dat, use a player skin instead!\n");
-			continue;
+			skinname += prefixlen;
+			goto addskinmodel;
 		}
 
-		for (i = 0; i < NUMSPRITES; i++)
+		// add sprite model
+		if (len == 4) // must be 4 characters long exactly. otherwise it's not a sprite name.
 		{
-			if (stricmp(name, sprnames[i]) == 0)
+			for (i = 0; i < NUMSPRITES; i++)
 			{
-				//if (stricmp(name, "PLAY") == 0)
-					//continue;
-
-				//CONS_Debug(DBG_RENDER, "  Found: %s %s %f %f\n", name, filename, scale, offset);
-				md2_models[i].scale = scale;
-				md2_models[i].offset = offset;
-				md2_models[i].notfound = false;
-				strcpy(md2_models[i].filename, filename);
-				goto md2found;
+				if (stricmp(name, sprnames[i]) == 0)
+				{
+					md2_models[i].scale = scale;
+					md2_models[i].offset = offset;
+					md2_models[i].notfound = false;
+					strcpy(md2_models[i].filename, filename);
+					goto modelfound;
+				}
 			}
 		}
 
+addskinmodel:
+		// add player model
 		for (s = 0; s < MAXSKINS; s++)
 		{
-			if (stricmp(name, skins[s].name) == 0)
+			if (stricmp(skinname, skins[s].name) == 0)
 			{
-				//CONS_Printf("  Found: %s %s %f %f\n", name, filename, scale, offset);
 				md2_playermodels[s].skin = s;
 				md2_playermodels[s].scale = scale;
 				md2_playermodels[s].offset = offset;
 				md2_playermodels[s].notfound = false;
 				strcpy(md2_playermodels[s].filename, filename);
-				goto md2found;
+				goto modelfound;
 			}
 		}
-		// no sprite/player skin name found?!?
-		//CONS_Printf("Unknown sprite/player skin %s detected in models.dat\n", name);
-md2found:
+
+modelfound:
 		// move on to next line...
 		continue;
 	}
@@ -558,8 +567,9 @@ md2found:
 void HWR_AddPlayerModel(int skin) // For skins that were added after startup
 {
 	FILE *f;
-	char name[18], filename[32];
+	char name[24], filename[32];
 	float scale, offset;
+	size_t prefixlen;
 
 	if (nomd2s)
 		return;
@@ -577,32 +587,42 @@ void HWR_AddPlayerModel(int skin) // For skins that were added after startup
 		return;
 	}
 
-	// Check for any model that match the names of player skins!
-	while (fscanf(f, "%19s %31s %f %f", name, filename, &scale, &offset) == 4)
+	// length of the player model prefix
+	prefixlen = strlen(PLAYERMODELPREFIX);
+
+	// Check for any models that match the names of player skins!
+	while (fscanf(f, "%25s %31s %f %f", name, filename, &scale, &offset) == 4)
 	{
-		if (stricmp(name, skins[skin].name) == 0)
+		char *skinname = name;
+		size_t len = strlen(name);
+
+		// ignore the player model prefix.
+		if (!strnicmp(name, PLAYERMODELPREFIX, prefixlen) && (len > prefixlen))
+			skinname += prefixlen;
+
+		if (stricmp(skinname, skins[skin].name) == 0)
 		{
 			md2_playermodels[skin].skin = skin;
 			md2_playermodels[skin].scale = scale;
 			md2_playermodels[skin].offset = offset;
 			md2_playermodels[skin].notfound = false;
 			strcpy(md2_playermodels[skin].filename, filename);
-			goto playermd2found;
+			goto playermodelfound;
 		}
 	}
 
-	//CONS_Printf("Model for player skin %s not found\n", skins[skin].name);
 	md2_playermodels[skin].notfound = true;
-playermd2found:
+playermodelfound:
 	fclose(f);
 }
 
 void HWR_AddSpriteModel(size_t spritenum) // For sprites that were added after startup
 {
 	FILE *f;
-	// name[18] is used to check for names in the models.dat file that match with sprites or player skins
+	// name[24] is used to check for names in the models.dat file that match with sprites or player skins
 	// sprite names are always 4 characters long, and names is for player skins can be up to 19 characters long
-	char name[18], filename[32];
+	// PLAYERMODELPREFIX is 6 characters long
+	char name[24], filename[32];
 	float scale, offset;
 
 	if (nomd2s)
@@ -622,22 +642,30 @@ void HWR_AddSpriteModel(size_t spritenum) // For sprites that were added after s
 		return;
 	}
 
-	// Check for any MD2s that match the names of sprite names!
-	while (fscanf(f, "%19s %31s %f %f", name, filename, &scale, &offset) == 4)
+	// Check for any models that match the names of sprite names!
+	while (fscanf(f, "%25s %31s %f %f", name, filename, &scale, &offset) == 4)
 	{
+		// length of the sprite name
+		size_t len = strlen(name);
+		if (len != 4) // must be 4 characters long exactly. otherwise it's not a sprite name.
+			continue;
+
+		// check for the player model prefix.
+		if (!strnicmp(name, PLAYERMODELPREFIX, strlen(PLAYERMODELPREFIX)))
+			continue; // that's not a sprite...
+
 		if (stricmp(name, sprnames[spritenum]) == 0)
 		{
 			md2_models[spritenum].scale = scale;
 			md2_models[spritenum].offset = offset;
 			md2_models[spritenum].notfound = false;
 			strcpy(md2_models[spritenum].filename, filename);
-			goto spritemd2found;
+			goto spritemodelfound;
 		}
 	}
 
-	//CONS_Printf("MD2 for sprite %s not found\n", sprnames[spritenum]);
 	md2_models[spritenum].notfound = true;
-spritemd2found:
+spritemodelfound:
 	fclose(f);
 }
 
diff --git a/src/hardware/hw_md2.h b/src/hardware/hw_md2.h
index 6f5985a44ea480226a49da31da82ae902c9650a0..3bbe300534c5af1b12a5b4a9772f7802a57bb271 100644
--- a/src/hardware/hw_md2.h
+++ b/src/hardware/hw_md2.h
@@ -49,4 +49,6 @@ void HWR_AddPlayerModel(INT32 skin);
 void HWR_AddSpriteModel(size_t spritenum);
 boolean HWR_DrawModel(gr_vissprite_t *spr);
 
+#define PLAYERMODELPREFIX "PLAYER"
+
 #endif // _HW_MD2_H_
diff --git a/src/m_anigif.c b/src/m_anigif.c
index f062bc826a6aca115a8bc6a1fe0612cc540535e3..faa8f29e16b6a9b9d0545692e94e8f0cda334105 100644
--- a/src/m_anigif.c
+++ b/src/m_anigif.c
@@ -19,6 +19,7 @@
 #include "v_video.h"
 #include "i_video.h"
 #include "m_misc.h"
+#include "st_stuff.h" // st_palette
 
 #ifdef HWRENDER
 #include "hardware/hw_main.h"
@@ -29,11 +30,17 @@
 
 consvar_t cv_gif_optimize = {"gif_optimize", "On", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
 consvar_t cv_gif_downscale =  {"gif_downscale", "On", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_gif_localcolortable =  {"gif_localcolortable", "On", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
 
 #ifdef HAVE_ANIGIF
 static boolean gif_optimize = false; // So nobody can do something dumb
 static boolean gif_downscale = false; // like changing cvars mid output
-static RGBA_t *gif_palette = NULL;
+
+// Palette handling
+static boolean gif_localcolortable = false;
+static boolean gif_colorprofile = false;
+static RGBA_t *gif_headerpalette = NULL;
+static RGBA_t *gif_framepalette = NULL;
 
 static FILE *gif_out = NULL;
 static INT32 gif_frames = 0;
@@ -393,16 +400,47 @@ const UINT8 gifhead_nsid[19] = {0x21,0xFF,0x0B, // extension block + size
 	0x4E,0x45,0x54,0x53,0x43,0x41,0x50,0x45,0x32,0x2E,0x30, // NETSCAPE2.0
 	0x03,0x01,0xFF,0xFF,0x00}; // sub-block, repetitions
 
+
+//
+// GIF_getpalette
+// determine the palette for the current frame.
+//
+static RGBA_t *GIF_getpalette(size_t palnum)
+{
+	// In hardware mode, always returns the local palette
+#ifdef HWRENDER
+	if (rendermode == render_opengl)
+		return pLocalPalette;
+	else
+#endif
+		return (gif_colorprofile ? &pLocalPalette[palnum*256] : &pMasterPalette[palnum*256]);
+}
+
+//
+// GIF_palwrite
+// writes the gif palette.
+// used both for the header and local color tables.
+//
+static UINT8 *GIF_palwrite(UINT8 *p, RGBA_t *pal)
+{
+	INT32 i;
+	for (i = 0; i < 256; i++)
+	{
+		WRITEUINT8(p, pal[i].s.red);
+		WRITEUINT8(p, pal[i].s.green);
+		WRITEUINT8(p, pal[i].s.blue);
+	}
+	return p;
+}
+
 //
 // GIF_headwrite
 // writes the gif header to the currently open output file.
-// NOTE that this code does not accomodate for palette changes.
 //
 static void GIF_headwrite(void)
 {
 	UINT8 *gifhead = Z_Malloc(800, PU_STATIC, NULL);
 	UINT8 *p = gifhead;
-	INT32 i;
 	UINT16 rwidth, rheight;
 
 	if (!gif_out)
@@ -423,24 +461,17 @@ static void GIF_headwrite(void)
 		rwidth = vid.width;
 		rheight = vid.height;
 	}
+
 	WRITEUINT16(p, rwidth);
 	WRITEUINT16(p, rheight);
 
 	// colors, aspect, etc
-	WRITEUINT8(p, 0xF7);
+	WRITEUINT8(p, 0xF7); // (0xF7 = 1111 0111)
 	WRITEUINT8(p, 0x00);
 	WRITEUINT8(p, 0x00);
 
 	// write color table
-	{
-		RGBA_t *pal = gif_palette;
-		for (i = 0; i < 256; i++)
-		{
-			WRITEUINT8(p, pal[i].s.red);
-			WRITEUINT8(p, pal[i].s.green);
-			WRITEUINT8(p, pal[i].s.blue);
-		}
-	}
+	p = GIF_palwrite(p, gif_headerpalette);
 
 	// write extension block
 	WRITEMEM(p, gifhead_nsid, sizeof(gifhead_nsid));
@@ -468,7 +499,7 @@ static void hwrconvert(void)
 	INT32 x, y;
 	size_t i = 0;
 
-	InitColorLUT(gif_palette);
+	InitColorLUT(gif_framepalette);
 
 	for (y = 0; y < vid.height; y++)
 	{
@@ -494,6 +525,7 @@ static void GIF_framewrite(void)
 	UINT8 *p;
 	UINT8 *movie_screen = screens[2];
 	INT32 blitx, blity, blitw, blith;
+	boolean palchanged;
 
 	if (!gifframe_data)
 		gifframe_data = Z_Malloc(gifframe_size, PU_STATIC, NULL);
@@ -502,8 +534,18 @@ static void GIF_framewrite(void)
 	if (!gif_out)
 		return;
 
+	// Lactozilla: Compare the header's palette with the current frame's palette and see if it changed.
+	if (gif_localcolortable)
+	{
+		gif_framepalette = GIF_getpalette(max(st_palette, 0));
+		palchanged = memcmp(gif_headerpalette, gif_framepalette, sizeof(RGBA_t) * 256);
+	}
+	else
+		palchanged = false;
+
 	// Compare image data (for optimizing GIF)
-	if (gif_optimize && gif_frames > 0)
+	// If the palette has changed, the entire frame is considered to be different.
+	if (gif_optimize && gif_frames > 0 && (!palchanged))
 	{
 		// before blit movie_screen points to last frame, cur_screen points to this frame
 		UINT8 *cur_screen = screens[0];
@@ -566,7 +608,20 @@ static void GIF_framewrite(void)
 		WRITEUINT16(p, (UINT16)(blity / scrbuf_downscaleamt));
 		WRITEUINT16(p, (UINT16)(blitw / scrbuf_downscaleamt));
 		WRITEUINT16(p, (UINT16)(blith / scrbuf_downscaleamt));
-		WRITEUINT8(p, 0); // no local table of colors
+
+		if (!gif_localcolortable)
+			WRITEUINT8(p, 0); // no local table of colors
+		else
+		{
+			if (palchanged)
+			{
+				// The palettes are different, so write the Local Color Table!
+				WRITEUINT8(p, 0x87); // (0x87 = 1000 0111)
+				p = GIF_palwrite(p, gif_framepalette);
+			}
+			else
+				WRITEUINT8(p, 0); // They are equal, no Local Color Table needed.
+		}
 
 		scrbuf_pos = movie_screen + blitx + (blity * vid.width);
 		scrbuf_writeend = scrbuf_pos + (blitw - 1) + ((blith - 1) * vid.width);
@@ -630,15 +685,9 @@ INT32 GIF_open(const char *filename)
 
 	gif_optimize = (!!cv_gif_optimize.value);
 	gif_downscale = (!!cv_gif_downscale.value);
-
-	// GIF color table
-	// In hardware mode, forces the local palette
-#ifdef HWRENDER
-	if (rendermode == render_opengl)
-		gif_palette = pLocalPalette;
-	else
-#endif
-		gif_palette = ((cv_screenshot_colorprofile.value) ? pLocalPalette : pMasterPalette);
+	gif_localcolortable = (!!cv_gif_localcolortable.value);
+	gif_colorprofile = (!!cv_screenshot_colorprofile.value);
+	gif_headerpalette = GIF_getpalette(0);
 
 	GIF_headwrite();
 	gif_frames = 0;
diff --git a/src/m_anigif.h b/src/m_anigif.h
index 9bdf2cc7f22701c6a2a35eff4580ba299379a565..9eb6faa75e0748a71cf1d6890adbac0fe41a43ba 100644
--- a/src/m_anigif.h
+++ b/src/m_anigif.h
@@ -27,6 +27,6 @@ void GIF_frame(void);
 INT32 GIF_close(void);
 #endif
 
-extern consvar_t cv_gif_optimize, cv_gif_downscale;
+extern consvar_t cv_gif_optimize, cv_gif_downscale, cv_gif_localcolortable;
 
 #endif
diff --git a/src/m_argv.c b/src/m_argv.c
index 935f4b9e3f2786b4a369af8b9b06a949c0961036..129706127087254d82362e75bc5169b858736cc2 100644
--- a/src/m_argv.c
+++ b/src/m_argv.c
@@ -92,36 +92,21 @@ const char *M_GetNextParm(void)
 void M_PushSpecialParameters(void)
 {
 	INT32 i;
-	char s[256];
-	boolean onetime = false;
-
 	for (i = 1; i < myargc; i++)
 	{
 		if (myargv[i][0] == '+')
 		{
-			strcpy(s, &myargv[i][1]);
+			COM_BufAddText(&myargv[i][1]);
 			i++;
 
 			// get the parameters of the command too
 			for (; i < myargc && myargv[i][0] != '+' && myargv[i][0] != '-'; i++)
 			{
-				strcat(s, " ");
-				if (!onetime)
-				{
-					strcat(s, "\"");
-					onetime = true;
-				}
-				strcat(s, myargv[i]);
-			}
-			if (onetime)
-			{
-				strcat(s, "\"");
-				onetime = false;
+				COM_BufAddText(va(" \"%s\"", myargv[i]));
 			}
-			strcat(s, "\n");
 
 			// push it
-			COM_BufAddText(s);
+			COM_BufAddText("\n");
 			i--;
 		}
 	}
diff --git a/src/m_menu.c b/src/m_menu.c
index cd8a9e3b2a4bb9af6203f51da7c7d27701a0992f..0d81baa4606247780a20102fd19369f7b27e1714 100644
--- a/src/m_menu.c
+++ b/src/m_menu.c
@@ -325,6 +325,7 @@ menu_t OP_DataOptionsDef, OP_ScreenshotOptionsDef, OP_EraseDataDef;
 menu_t OP_ServerOptionsDef;
 menu_t OP_MonitorToggleDef;
 static void M_ScreenshotOptions(INT32 choice);
+static void M_SetupScreenshotMenu(void);
 static void M_EraseData(INT32 choice);
 
 static void M_Addons(INT32 choice);
@@ -366,7 +367,6 @@ static void M_DrawMonitorToggles(void);
 static void M_OGL_DrawFogMenu(void);
 #endif
 #ifndef NONET
-static void M_DrawScreenshotMenu(void);
 static void M_DrawConnectMenu(void);
 static void M_DrawMPMainMenu(void);
 static void M_DrawRoomMenu(void);
@@ -1517,6 +1517,7 @@ static menuitem_t OP_ScreenshotOptionsMenu[] =
 
 	{IT_STRING|IT_CVAR, NULL, "Region Optimizing", &cv_gif_optimize,              95},
 	{IT_STRING|IT_CVAR, NULL, "Downscaling",       &cv_gif_downscale,             100},
+	{IT_STRING|IT_CVAR, NULL, "Local Color Table", &cv_gif_localcolortable,       105},
 
 	{IT_STRING|IT_CVAR, NULL, "Memory Level",      &cv_zlib_memorya,              95},
 	{IT_STRING|IT_CVAR, NULL, "Compression Level", &cv_zlib_levela,               100},
@@ -1527,13 +1528,14 @@ static menuitem_t OP_ScreenshotOptionsMenu[] =
 enum
 {
 	op_screenshot_colorprofile = 1,
+	op_screenshot_storagelocation = 3,
 	op_screenshot_folder = 4,
 	op_movie_folder = 11,
 	op_screenshot_capture = 12,
 	op_screenshot_gif_start = 13,
-	op_screenshot_gif_end = 14,
-	op_screenshot_apng_start = 15,
-	op_screenshot_apng_end = 18,
+	op_screenshot_gif_end = 15,
+	op_screenshot_apng_start = 16,
+	op_screenshot_apng_end = 19,
 };
 
 static menuitem_t OP_EraseDataMenu[] =
@@ -3031,7 +3033,8 @@ static void M_ChangeCvar(INT32 choice)
 			|| !(currentMenu->menuitems[itemOn].status & IT_CV_INTEGERSTEP))
 		{
 			char s[20];
-			sprintf(s,"%f",FIXED_TO_FLOAT(cv->value)+(choice)*(1.0f/16.0f));
+			float n = FIXED_TO_FLOAT(cv->value)+(choice)*(1.0f/16.0f);
+			sprintf(s,"%ld%s",(long)n,M_Ftrim(n));
 			CV_Set(cv,s);
 		}
 		else
@@ -3788,6 +3791,9 @@ void M_Ticker(void)
 		if (--vidm_testingmode == 0)
 			setmodeneeded = vidm_previousmode + 1;
 	}
+
+	if (currentMenu == &OP_ScreenshotOptionsDef)
+		M_SetupScreenshotMenu();
 }
 
 //
@@ -11422,9 +11428,27 @@ static void M_ScreenshotOptions(INT32 choice)
 	Screenshot_option_Onchange();
 	Moviemode_mode_Onchange();
 
+	M_SetupScreenshotMenu();
 	M_SetupNextMenu(&OP_ScreenshotOptionsDef);
 }
 
+static void M_SetupScreenshotMenu(void)
+{
+	menuitem_t *item = &OP_ScreenshotOptionsMenu[op_screenshot_colorprofile];
+
+#ifdef HWRENDER
+	// Hide some options based on render mode
+	if (rendermode == render_opengl)
+	{
+		item->status = IT_GRAYEDOUT;
+		if ((currentMenu == &OP_ScreenshotOptionsDef) && (itemOn == op_screenshot_colorprofile)) // Can't select that
+			itemOn = op_screenshot_storagelocation;
+	}
+	else
+#endif
+		item->status = (IT_STRING | IT_CVAR);
+}
+
 // =============
 // JOYSTICK MENU
 // =============
@@ -12367,6 +12391,15 @@ static void M_HandleVideoMode(INT32 ch)
 static void M_DrawScreenshotMenu(void)
 {
 	M_DrawGenericScrollMenu();
+#ifdef HWRENDER
+	if ((rendermode == render_opengl) && (itemOn < 7)) // where it starts to go offscreen; change this number if you change the layout of the screenshot menu
+	{
+		INT32 y = currentMenu->y+currentMenu->menuitems[op_screenshot_colorprofile].alphaKey*2;
+		if (itemOn == 6)
+			y -= 10;
+		V_DrawRightAlignedString(BASEVIDWIDTH - currentMenu->x, y, V_REDMAP, "Yes");
+	}
+#endif
 }
 
 // ===============
diff --git a/src/m_misc.c b/src/m_misc.c
index 3026f66548c88bf33d06e64701c891e69db25d20..3dfeef81ec941129cadbdfd145394e1ad49f8452 100644
--- a/src/m_misc.c
+++ b/src/m_misc.c
@@ -2612,3 +2612,20 @@ int M_JumpWordReverse(const char *line, int offset)
 		offset--;
 	return offset;
 }
+
+const char * M_Ftrim (double f)
+{
+	static char dig[9];/* "0." + 6 digits (6 is printf's default) */
+	int i;
+	/* I know I said it's the default, but just in case... */
+	sprintf(dig, "%.6g", modf(f, &f));
+	if (dig[0])
+	{
+		for (i = strlen(dig); dig[i] == '0'; --i)
+			;
+		dig[i + 1] = '\0';
+		return &dig[1];/* skip the 0 */
+	}
+	else
+		return "";
+}
diff --git a/src/m_misc.h b/src/m_misc.h
index 3fb9f031a460f007e4d539837905072547b367fd..ad12517c18782b63793effbfd4a009d2bda4e9b0 100644
--- a/src/m_misc.h
+++ b/src/m_misc.h
@@ -109,6 +109,12 @@ int M_JumpWord (const char *s);
 /* E.g. cursor = M_JumpWordReverse(line, cursor); */
 int M_JumpWordReverse (const char *line, int offset);
 
+/*
+Return dot and then the fractional part of a float, without
+trailing zeros, or "" if the fractional part is zero.
+*/
+const char * M_Ftrim (double);
+
 // counting bits, for weapon ammo code, usually
 FUNCMATH UINT8 M_CountBits(UINT32 num, UINT8 size);