diff --git a/src/r_defs.h b/src/r_defs.h
index 39e6765cd7af35d6478e690de8c48045d063b505..16c660b01938ed86669550f709f7b65336cfaebc 100644
--- a/src/r_defs.h
+++ b/src/r_defs.h
@@ -26,9 +26,6 @@
 
 #include "taglist.h"
 
-// Amount of colors in the palette
-#define NUM_PALETTE_ENTRIES 256
-
 //
 // ClipWallSegment
 // Clips the given range of columns
diff --git a/src/r_draw.c b/src/r_draw.c
index b87a8404ebc3ea5895425dd81b1c78dc6040fdfa..ff2e43df31d9291ed6ae07facf2da0c1beaaa4e1 100644
--- a/src/r_draw.c
+++ b/src/r_draw.c
@@ -125,49 +125,37 @@ UINT32 nflatxshift, nflatyshift, nflatshiftup, nflatmask;
 //                       TRANSLATION COLORMAP CODE
 // =========================================================================
 
-enum
-{
-	DEFAULT_TT_CACHE_INDEX,
-	BOSS_TT_CACHE_INDEX,
-	METALSONIC_TT_CACHE_INDEX,
-	ALLWHITE_TT_CACHE_INDEX,
-	RAINBOW_TT_CACHE_INDEX,
-	BLINK_TT_CACHE_INDEX,
-	DASHMODE_TT_CACHE_INDEX,
-
-	TT_CACHE_SIZE
-};
-
-static UINT8 **translationtablecache[TT_CACHE_SIZE] = {NULL};
-static UINT8 **skintranslationcache[NUM_PALETTE_ENTRIES] = {NULL};
+static colorcache_t **translationtablecache[TT_CACHE_SIZE] = {NULL};
 
 boolean skincolor_modified[MAXSKINCOLORS];
 
-static INT32 TranslationToCacheIndex(INT32 translation)
+static INT32 SkinToCacheIndex(INT32 translation)
 {
 	switch (translation)
 	{
+		case TC_DEFAULT:    return DEFAULT_TT_CACHE_INDEX;
 		case TC_BOSS:       return BOSS_TT_CACHE_INDEX;
 		case TC_METALSONIC: return METALSONIC_TT_CACHE_INDEX;
 		case TC_ALLWHITE:   return ALLWHITE_TT_CACHE_INDEX;
 		case TC_RAINBOW:    return RAINBOW_TT_CACHE_INDEX;
 		case TC_BLINK:      return BLINK_TT_CACHE_INDEX;
 		case TC_DASHMODE:   return DASHMODE_TT_CACHE_INDEX;
-		default:            return DEFAULT_TT_CACHE_INDEX;
+		default:            return translation;
 	}
 }
 
-static INT32 CacheIndexToTranslation(INT32 index)
+static INT32 CacheIndexToSkin(INT32 index)
 {
 	switch (index)
 	{
+		case DEFAULT_TT_CACHE_INDEX:    return TC_DEFAULT;
 		case BOSS_TT_CACHE_INDEX:       return TC_BOSS;
 		case METALSONIC_TT_CACHE_INDEX: return TC_METALSONIC;
 		case ALLWHITE_TT_CACHE_INDEX:   return TC_ALLWHITE;
 		case RAINBOW_TT_CACHE_INDEX:    return TC_RAINBOW;
 		case BLINK_TT_CACHE_INDEX:      return TC_BLINK;
 		case DASHMODE_TT_CACHE_INDEX:   return TC_DASHMODE;
-		default:                        return TC_DEFAULT;
+		default:                        return index;
 	}
 }
 
@@ -553,23 +541,22 @@ static void R_GenerateTranslationColormap(UINT8 *dest_colormap, INT32 translatio
 */
 UINT8* R_GetTranslationColormap(INT32 skinnum, skincolornum_t color, UINT8 flags)
 {
-	UINT8 ***cache = NULL;
-	INT32 index, starttranscolor;
-	UINT8 *ret;
+	colorcache_t *ret;
+	INT32 index = 0;
+	INT32 starttranscolor = DEFAULT_STARTTRANSCOLOR;
 
 	// Adjust if we want the default colormap
 	if (skinnum >= numskins)
 		I_Error("Invalid skin number %d", skinnum);
 	else if (skinnum >= 0)
 	{
-		cache = skintranslationcache;
-		starttranscolor = index = skins[skinnum]->starttranscolor;
+		index = skins[skinnum]->skinnum;
+		starttranscolor = skins[skinnum]->starttranscolor;
 	}
 	else if (skinnum <= TC_DEFAULT)
 	{
-		cache = translationtablecache;
-		starttranscolor = DEFAULT_STARTTRANSCOLOR;
-		index = TranslationToCacheIndex(skinnum);
+		// Do default translation
+		index = SkinToCacheIndex(skinnum);
 	}
 	else
 		I_Error("Invalid translation %d", skinnum);
@@ -577,41 +564,48 @@ UINT8* R_GetTranslationColormap(INT32 skinnum, skincolornum_t color, UINT8 flags
 	if (flags & GTC_CACHE)
 	{
 		// Allocate table for skin if necessary
-		if (!cache[index])
-			cache[index] = Z_Calloc(MAXSKINCOLORS * sizeof(UINT8**), PU_STATIC, NULL);
+		if (!translationtablecache[index])
+			translationtablecache[index] = Z_Calloc(MAXSKINCOLORS * sizeof(colorcache_t**), PU_STATIC, NULL);
 
 		// Get colormap
-		ret = cache[index][color];
+		ret = translationtablecache[index][color];
 
 		// Rebuild the cache if necessary
 		if (skincolor_modified[color])
 		{
-			INT32 i;
-
-			for (i = 0; i < TT_CACHE_SIZE; i++)
-				if (translationtablecache[i] && translationtablecache[i][color])
-					R_GenerateTranslationColormap(translationtablecache[i][color], CacheIndexToTranslation(i), color, starttranscolor);
-			for (i = 0; i < NUM_PALETTE_ENTRIES; i++)
-				if (skintranslationcache[i] && skintranslationcache[i][color])
-					R_GenerateTranslationColormap(skintranslationcache[i][color], 0, color, i);
-
+			// Moved up here so that R_UpdateTranslationRemaps doesn't cause a stack overflow,
+			// since in this situation, it will call R_GetTranslationColormap
 			skincolor_modified[color] = false;
+
+			for (unsigned i = 0; i < TT_CACHE_SIZE; i++)
+			{
+				if (translationtablecache[i])
+				{
+					colorcache_t *cache = translationtablecache[i][color];
+					if (cache)
+					{
+						R_GenerateTranslationColormap(cache->colors, CacheIndexToSkin(i), color, starttranscolor);
+						R_UpdateTranslationRemaps(color, i);
+					}
+				}
+			}
 		}
 	}
-	else ret = NULL;
+	else
+		ret = NULL;
 
 	// Generate the colormap if necessary
 	if (!ret)
 	{
-		ret = Z_MallocAlign(NUM_PALETTE_ENTRIES, (flags & GTC_CACHE) ? PU_LEVEL : PU_STATIC, NULL, 8);
-		R_GenerateTranslationColormap(ret, skinnum, color, starttranscolor);
+		ret = Z_Malloc(sizeof(colorcache_t), (flags & GTC_CACHE) ? PU_LEVEL : PU_STATIC, NULL);
+		R_GenerateTranslationColormap(ret->colors, skinnum, color, starttranscolor);
 
 		// Cache the colormap if desired
 		if (flags & GTC_CACHE)
-			cache[index][color] = ret;
+			translationtablecache[index][color] = ret;
 	}
 
-	return ret;
+	return ret->colors;
 }
 
 /**	\brief	Flushes cache of translation colormaps.
@@ -629,9 +623,6 @@ void R_FlushTranslationColormapCache(void)
 	for (i = 0; i < TT_CACHE_SIZE; i++)
 		if (translationtablecache[i])
 			memset(translationtablecache[i], 0, MAXSKINCOLORS * sizeof(UINT8**));
-	for (i = 0; i < NUM_PALETTE_ENTRIES; i++)
-		if (skintranslationcache[i])
-			memset(skintranslationcache[i], 0, MAXSKINCOLORS * sizeof(UINT8**));
 }
 
 UINT16 R_GetColorByName(const char *name)
diff --git a/src/r_draw.h b/src/r_draw.h
index 9cde3cf54f4421233ba517996ceee173067a42fe..29370015a1c46cb6405bf5b698d6c23425ee79e2 100644
--- a/src/r_draw.h
+++ b/src/r_draw.h
@@ -117,6 +117,27 @@ enum
 	TC_DEFAULT
 };
 
+// Amount of colors in the palette
+#define NUM_PALETTE_ENTRIES 256
+
+typedef struct colorcache_s
+{
+	UINT8 colors[NUM_PALETTE_ENTRIES];
+} colorcache_t;
+
+enum
+{
+	DEFAULT_TT_CACHE_INDEX = MAXSKINS,
+	BOSS_TT_CACHE_INDEX,
+	METALSONIC_TT_CACHE_INDEX,
+	ALLWHITE_TT_CACHE_INDEX,
+	RAINBOW_TT_CACHE_INDEX,
+	BLINK_TT_CACHE_INDEX,
+	DASHMODE_TT_CACHE_INDEX,
+
+	TT_CACHE_SIZE
+};
+
 // Custom player skin translation
 // Initialize color translation tables, for player rendering etc.
 UINT8* R_GetTranslationColormap(INT32 skinnum, skincolornum_t color, UINT8 flags);
diff --git a/src/r_translation.c b/src/r_translation.c
index 3799c87cd5c968d23c6c6f4da8a188f7a4e8c153..7e1e30d0cdec7afd2d810f6b8d1a6b26da0c42bb 100644
--- a/src/r_translation.c
+++ b/src/r_translation.c
@@ -1103,6 +1103,17 @@ remaptable_t *R_GetTranslationByID(int id)
 	return paletteremaps[id];
 }
 
+static void R_ApplyTranslationRemap(remaptable_t *tr, UINT8 *remap, skincolornum_t skincolor, INT32 skinnum)
+{
+	UINT8 *base_skincolor = R_GetTranslationColormap(skinnum, skincolor, GTC_CACHE);
+
+	for (unsigned i = 0; i < NUM_PALETTE_ENTRIES; i++)
+		remap[i] = base_skincolor[i];
+
+	for (unsigned i = 0; i < tr->num_sources; i++)
+		PaletteRemap_Apply(remap, &tr->sources[i]);
+}
+
 UINT8 *R_GetTranslationRemap(int id, skincolornum_t skincolor, INT32 skinnum)
 {
 	remaptable_t *tr = R_GetTranslationByID(id);
@@ -1112,25 +1123,39 @@ UINT8 *R_GetTranslationRemap(int id, skincolornum_t skincolor, INT32 skinnum)
 	if (!tr->num_sources || skincolor == SKINCOLOR_NONE)
 		return tr->remap;
 
-	if (!tr->skincolor_remap)
-		tr->skincolor_remap = Z_Calloc(NUM_PALETTE_ENTRIES * MAXSKINCOLORS, PU_LEVEL, &tr->skincolor_remap);
+	if (!tr->skincolor_remaps)
+		Z_Calloc(sizeof(*tr->skincolor_remaps) * TT_CACHE_SIZE, PU_LEVEL, &tr->skincolor_remaps);
 
-	if (!tr->skincolor_remap[skincolor])
+	if (!tr->skincolor_remaps[skinnum])
+		tr->skincolor_remaps[skinnum] = Z_Calloc(NUM_PALETTE_ENTRIES * MAXSKINCOLORS, PU_LEVEL, NULL);
+
+	colorcache_t *cache = tr->skincolor_remaps[skinnum][skincolor];
+	if (!cache)
 	{
-		UINT8 *remap = Z_Calloc(NUM_PALETTE_ENTRIES, PU_LEVEL, NULL);
+		cache = Z_Calloc(sizeof(colorcache_t), PU_LEVEL, NULL);
+
+		R_ApplyTranslationRemap(tr, cache->colors, skincolor, skinnum);
 
-		UINT8 *base_skincolor = R_GetTranslationColormap(skinnum, skincolor, GTC_CACHE);
+		tr->skincolor_remaps[skinnum][skincolor] = cache;
+	}
 
-		for (unsigned i = 0; i < NUM_PALETTE_ENTRIES; i++)
-			remap[i] = base_skincolor[i];
+	return cache->colors;
+}
 
-		for (unsigned i = 0; i < tr->num_sources; i++)
-			PaletteRemap_Apply(remap, &tr->sources[i]);
+static void R_UpdateTranslation(remaptable_t *tr, skincolornum_t skincolor, INT32 skinnum)
+{
+	if (!tr->num_sources || !tr->skincolor_remaps || !tr->skincolor_remaps[skinnum])
+		return;
 
-		tr->skincolor_remap[skincolor] = remap;
-	}
+	colorcache_t *cache = tr->skincolor_remaps[skinnum][skincolor];
+	if (cache)
+		R_ApplyTranslationRemap(tr, cache->colors, skincolor, skinnum);
+}
 
-	return tr->skincolor_remap[skincolor];
+void R_UpdateTranslationRemaps(skincolornum_t skincolor, INT32 skinnum)
+{
+	for (unsigned i = 0; i < numpaletteremaps; i++)
+		R_UpdateTranslation(paletteremaps[i], skincolor, skinnum);
 }
 
 boolean R_TranslationIsValid(int id)
diff --git a/src/r_translation.h b/src/r_translation.h
index 794eb063c3f8cd7dfdc86c9f8fa052cf9745cc67..1eb40233d12070708d9bda26bfa8b53792235352 100644
--- a/src/r_translation.h
+++ b/src/r_translation.h
@@ -15,6 +15,8 @@
 
 #include "doomdef.h"
 
+#include "r_draw.h"
+
 typedef enum
 {
 	REMAP_ADD_INDEXRANGE,
@@ -58,16 +60,16 @@ typedef struct
 
 typedef struct
 {
-	UINT8 remap[256];
+	UINT8 remap[NUM_PALETTE_ENTRIES];
 	unsigned num_entries;
 
 	paletteremap_t *sources;
 	unsigned num_sources;
 
-	// A typical remap is 256 bytes long, and there is currently a maximum of 1182 skincolors.
-	// This means allocating (1182 * 256) bytes, which equals 302592, or ~302kb of memory for every translation.
-	// So we allocate a list instead.
-	UINT8 **skincolor_remap;
+	// A typical remap is 256 bytes long, and there is currently a maximum of 1182 skincolors, and 263 possible color cache entries.
+	// This would mean allocating (1182 * 256 * 263) bytes, which equals 79581696 bytes, or ~79mb of memory for every remap.
+	// So instead a few lists are allocated.
+	colorcache_t ***skincolor_remaps;
 } remaptable_t;
 
 void PaletteRemap_Init(void);
@@ -85,6 +87,7 @@ const char *R_GetCustomTranslationName(unsigned id);
 unsigned R_NumCustomTranslations(void);
 remaptable_t *R_GetTranslationByID(int id);
 UINT8 *R_GetTranslationRemap(int id, skincolornum_t skincolor, INT32 skinnum);
+void R_UpdateTranslationRemaps(skincolornum_t skincolor, INT32 skinnum);
 boolean R_TranslationIsValid(int id);
 
 void R_ParseTrnslate(INT32 wadNum, UINT16 lumpnum);