From f93b1b8d7b5e6f4b73e73513e74f6e84c377dfaa Mon Sep 17 00:00:00 2001
From: Jaime Ita Passos <jp6781615@gmail.com>
Date: Mon, 19 Apr 2021 04:01:49 -0300
Subject: [PATCH] Raise the skin limit to 256.

---
 src/d_clisrv.c          |   6 +-
 src/d_main.c            |   7 +-
 src/d_netcmd.c          |  18 +++--
 src/d_player.h          |   2 +-
 src/deh_soc.c           |  31 ++++-----
 src/dehacked.c          |   2 +-
 src/doomdef.h           |   3 +-
 src/g_game.c            |   2 +-
 src/hardware/hw_md2.c   |  16 +++--
 src/hardware/hw_md2.h   |   2 +-
 src/hardware/hw_model.c |   3 +-
 src/lua_playerlib.c     |   3 +-
 src/m_cond.c            |   3 +-
 src/m_menu.c            | 141 ++++++++++++++++++++++------------------
 src/m_menu.h            |   7 +-
 src/p_saveg.c           |   4 +-
 src/r_picformats.c      |   9 ++-
 src/r_skins.c           |  53 ++++++++-------
 src/r_skins.h           |   5 +-
 src/sounds.h            |   4 +-
 src/st_stuff.c          |  15 ++++-
 src/st_stuff.h          |   6 +-
 22 files changed, 194 insertions(+), 148 deletions(-)

diff --git a/src/d_clisrv.c b/src/d_clisrv.c
index 7c2dec6a11..f1c24fe5aa 100644
--- a/src/d_clisrv.c
+++ b/src/d_clisrv.c
@@ -2463,6 +2463,8 @@ void CL_ClearPlayer(INT32 playernum)
 {
 	if (players[playernum].mo)
 		P_RemoveMobj(players[playernum].mo);
+	if (players[playernum].availabilities)
+		Z_Free(players[playernum].availabilities);
 	memset(&players[playernum], 0, sizeof (player_t));
 	memset(playeraddress[playernum], 0, sizeof(*playeraddress));
 }
@@ -4486,9 +4488,9 @@ static INT16 Consistancy(void)
 		{
 			if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
 				continue;
-	
+
 			mo = (mobj_t *)th;
-	
+
 			if (mo->flags & (MF_SPECIAL | MF_SOLID | MF_PUSHABLE | MF_BOSS | MF_MISSILE | MF_SPRING | MF_MONITOR | MF_FIRE | MF_ENEMY | MF_PAIN | MF_STICKY))
 			{
 				ret -= mo->type;
diff --git a/src/d_main.c b/src/d_main.c
index 23a2c0133c..e9d236a9b7 100644
--- a/src/d_main.c
+++ b/src/d_main.c
@@ -813,7 +813,8 @@ void D_StartTitle(void)
 	for (i = 0; i < MAXPLAYERS; i++)
 		CL_ClearPlayer(i);
 
-	players[consoleplayer].availabilities = players[1].availabilities = R_GetSkinAvailabilities(); // players[1] is supposed to be for 2p
+	R_GetSkinAvailabilities(&players[consoleplayer].availabilities);
+	R_GetSkinAvailabilities(&players[1].availabilities); // players[1] is supposed to be for 2p
 
 	splitscreen = false;
 	SplitScreen_OnChange();
@@ -1216,10 +1217,6 @@ void D_SRB2Main(void)
 	// Make backups of some SOCcable tables.
 	P_BackupTables();
 
-	// Setup character tables
-	// Have to be done here before files are loaded
-	M_InitCharacterTables();
-
 	mainwads = 3; // doesn't include music.dta
 #ifdef USE_PATCH_DTA
 	mainwads++;
diff --git a/src/d_netcmd.c b/src/d_netcmd.c
index 09f9d46514..14349d559f 100644
--- a/src/d_netcmd.c
+++ b/src/d_netcmd.c
@@ -1235,7 +1235,7 @@ static void SendNameAndColor(void)
 	&& !strcmp(cv_skin.string, skins[players[consoleplayer].skin].name))
 		return;
 
-	players[consoleplayer].availabilities = R_GetSkinAvailabilities();
+	R_GetSkinAvailabilities(&players[consoleplayer].availabilities);
 
 	// We'll handle it later if we're not playing.
 	if (!Playing())
@@ -1319,9 +1319,9 @@ static void SendNameAndColor(void)
 
 	// Finally write out the complete packet and send it off.
 	WRITESTRINGN(p, cv_playername.zstring, MAXPLAYERNAME);
-	WRITEUINT32(p, (UINT32)players[consoleplayer].availabilities);
 	WRITEUINT16(p, (UINT16)cv_playercolor.value);
 	WRITEUINT8(p, (UINT8)cv_skin.value);
+	WRITEMEM(p, players[consoleplayer].availabilities, sizeof(bitarray_t) * numskins);
 	SendNetXCmd(XD_NAMEANDCOLOR, buf, p - buf);
 }
 
@@ -1363,7 +1363,7 @@ static void SendNameAndColor2(void)
 		}
 	}
 
-	players[secondplaya].availabilities = R_GetSkinAvailabilities();
+	R_GetSkinAvailabilities(&players[secondplaya].availabilities);
 
 	// We'll handle it later if we're not playing.
 	if (!Playing())
@@ -1455,10 +1455,13 @@ static void Got_NameAndColor(UINT8 **cp, INT32 playernum)
 #endif
 
 	READSTRINGN(*cp, name, MAXPLAYERNAME);
-	p->availabilities = READUINT32(*cp);
 	color = READUINT16(*cp);
 	skin = READUINT8(*cp);
 
+	if (p->availabilities == NULL)
+		p->availabilities = Z_Malloc(sizeof(bitarray_t) * numskins, PU_STATIC, NULL);
+	READMEM(*cp, p->availabilities, sizeof(bitarray_t) * numskins);
+
 	// set name
 	if (player_name_changes[playernum] < MAXNAMECHANGES)
 	{
@@ -1491,9 +1494,9 @@ static void Got_NameAndColor(UINT8 **cp, INT32 playernum)
 			kick = true;
 
 		// availabilities
-		for (s = 0; s < MAXSKINS; s++)
+		for (s = 0; s < numskins; s++)
 		{
-			if (!skins[s].availability && (p->availabilities & (1 << s)))
+			if (!skins[s].availability && in_bit_array(p->availabilities, s))
 			{
 				kick = true;
 				break;
@@ -4278,7 +4281,8 @@ void Command_ExitGame_f(void)
 	for (i = 0; i < MAXPLAYERS; i++)
 		CL_ClearPlayer(i);
 
-	players[consoleplayer].availabilities = players[1].availabilities = R_GetSkinAvailabilities(); // players[1] is supposed to be for 2p
+	R_GetSkinAvailabilities(&players[consoleplayer].availabilities);
+	R_GetSkinAvailabilities(&players[1].availabilities); // players[1] is supposed to be for 2p
 
 	splitscreen = false;
 	SplitScreen_OnChange();
diff --git a/src/d_player.h b/src/d_player.h
index 2e7afed882..d90dd79ffa 100644
--- a/src/d_player.h
+++ b/src/d_player.h
@@ -379,7 +379,7 @@ typedef struct player_s
 	UINT16 skincolor;
 
 	INT32 skin;
-	UINT32 availabilities;
+	bitarray_t *availabilities;
 
 	UINT32 score; // player score
 	fixed_t dashspeed; // dashing speed
diff --git a/src/deh_soc.c b/src/deh_soc.c
index 5b12ea1b0b..046ed885aa 100644
--- a/src/deh_soc.c
+++ b/src/deh_soc.c
@@ -162,23 +162,20 @@ void clear_levels(void)
 
 static boolean findFreeSlot(INT32 *num)
 {
-	// Send the character select entry to a free slot.
-	while (*num < MAXSKINS && (description[*num].used))
-		*num = *num+1;
+	if (description)
+	{
+		// Send the character select entry to a free slot.
+		while (*num < numdescriptions && (description[*num].used))
+			(*num)++;
+	}
 
-	// No more free slots. :(
-	if (*num >= MAXSKINS)
+	// No more free slots.
+	if (*num >= MAXCHARACTERSLOTS)
 		return false;
+	else if (*num >= numdescriptions)
+		M_InitCharacterTables((*num) + 1);
 
-	// Redesign your logo. (See M_DrawSetupChoosePlayerMenu in m_menu.c...)
-	description[*num].picname[0] = '\0';
-	description[*num].nametag[0] = '\0';
-	description[*num].displayname[0] = '\0';
-	description[*num].oppositecolor = SKINCOLOR_NONE;
-	description[*num].tagtextcolor = SKINCOLOR_NONE;
-	description[*num].tagoutlinecolor = SKINCOLOR_NONE;
-
-	// Found one! ^_^
+	// Found one!
 	return (description[*num].used = true);
 }
 
@@ -891,7 +888,7 @@ void readspriteinfo(MYFILE *f, INT32 num, boolean sprite2)
 	INT32 value;
 #endif
 	char *lastline;
-	INT32 skinnumbers[MAXSKINS];
+	INT32 *skinnumbers = NULL;
 	INT32 foundskins = 0;
 
 	// allocate a spriteinfo
@@ -980,6 +977,8 @@ void readspriteinfo(MYFILE *f, INT32 num, boolean sprite2)
 					break;
 				}
 
+				if (skinnumbers == NULL)
+					skinnumbers = Z_Malloc(sizeof(INT32) * numskins, PU_STATIC, NULL);
 				skinnumbers[foundskins] = skinnum;
 				foundskins++;
 			}
@@ -1043,6 +1042,8 @@ void readspriteinfo(MYFILE *f, INT32 num, boolean sprite2)
 
 	Z_Free(s);
 	Z_Free(info);
+	if (skinnumbers)
+		Z_Free(skinnumbers);
 }
 
 void readsprite2(MYFILE *f, INT32 num)
diff --git a/src/dehacked.c b/src/dehacked.c
index c2ea28d27c..8d3c5725f7 100644
--- a/src/dehacked.c
+++ b/src/dehacked.c
@@ -241,7 +241,7 @@ static void DEH_LoadDehackedFile(MYFILE *f, boolean mainfile)
 				i = 0;
 			if (fastcmp(word, "CHARACTER"))
 			{
-				if (i >= 0 && i < 32)
+				if (i >= 0 && i < MAXCHARACTERSLOTS)
 					readPlayer(f, i);
 				else
 				{
diff --git a/src/doomdef.h b/src/doomdef.h
index 52abc95972..e7831f6897 100644
--- a/src/doomdef.h
+++ b/src/doomdef.h
@@ -220,7 +220,8 @@ extern char logfilename[1024];
 // NOTE: it needs more than this to increase the number of players...
 
 #define MAXPLAYERS 32
-#define MAXSKINS 32
+#define MAXSKINS 256
+#define MAXCHARACTERSLOTS (MAXSKINS * 2)
 #define PLAYERSMASK (MAXPLAYERS-1)
 #define MAXPLAYERNAME 21
 
diff --git a/src/g_game.c b/src/g_game.c
index 13423ce77d..7f48556ff1 100644
--- a/src/g_game.c
+++ b/src/g_game.c
@@ -2486,7 +2486,7 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps)
 	UINT8 mare;
 	UINT16 skincolor;
 	INT32 skin;
-	UINT32 availabilities;
+	bitarray_t *availabilities;
 	tic_t jointime;
 	tic_t quittime;
 	boolean spectator;
diff --git a/src/hardware/hw_md2.c b/src/hardware/hw_md2.c
index 5caf344f75..cba322b2a7 100644
--- a/src/hardware/hw_md2.c
+++ b/src/hardware/hw_md2.c
@@ -71,7 +71,7 @@
 #endif
 
 md2_t md2_models[NUMSPRITES];
-md2_t md2_playermodels[MAXSKINS];
+md2_t *md2_playermodels = NULL;
 
 
 /*
@@ -490,7 +490,11 @@ void HWR_InitModels(void)
 	size_t prefixlen;
 
 	CONS_Printf("HWR_InitModels()...\n");
-	for (s = 0; s < MAXSKINS; s++)
+
+	if (numskins && md2_playermodels == NULL)
+		md2_playermodels = Z_Malloc(sizeof(md2_t), PU_STATIC, NULL);
+
+	for (s = 0; s < numskins; s++)
 	{
 		md2_playermodels[s].scale = -1.0f;
 		md2_playermodels[s].model = NULL;
@@ -501,6 +505,7 @@ void HWR_InitModels(void)
 		md2_playermodels[s].notfound = true;
 		md2_playermodels[s].error = false;
 	}
+
 	for (i = 0; i < NUMSPRITES; i++)
 	{
 		md2_models[i].scale = -1.0f;
@@ -561,7 +566,7 @@ void HWR_InitModels(void)
 
 addskinmodel:
 		// add player model
-		for (s = 0; s < MAXSKINS; s++)
+		for (s = 0; s < numskins; s++)
 		{
 			if (stricmp(skinname, skins[s].name) == 0)
 			{
@@ -581,7 +586,7 @@ modelfound:
 	fclose(f);
 }
 
-void HWR_AddPlayerModel(int skin) // For skins that were added after startup
+void HWR_AddPlayerModel(INT32 skin) // For skins that were added after startup
 {
 	FILE *f;
 	char name[24], filename[32];
@@ -608,6 +613,9 @@ void HWR_AddPlayerModel(int skin) // For skins that were added after startup
 		}
 	}
 
+	// realloc player models table
+	md2_playermodels = Z_Realloc(md2_playermodels, sizeof(md2_t) * numskins, PU_STATIC, NULL);
+
 	// length of the player model prefix
 	prefixlen = strlen(PLAYERMODELPREFIX);
 
diff --git a/src/hardware/hw_md2.h b/src/hardware/hw_md2.h
index 0f4d2c7bc9..42787115a9 100644
--- a/src/hardware/hw_md2.h
+++ b/src/hardware/hw_md2.h
@@ -37,7 +37,7 @@ typedef struct
 } md2_t;
 
 extern md2_t md2_models[NUMSPRITES];
-extern md2_t md2_playermodels[MAXSKINS];
+extern md2_t *md2_playermodels;
 
 void HWR_InitModels(void);
 void HWR_AddPlayerModel(INT32 skin);
diff --git a/src/hardware/hw_model.c b/src/hardware/hw_model.c
index 4ed03744bf..86e2a0c704 100644
--- a/src/hardware/hw_model.c
+++ b/src/hardware/hw_model.c
@@ -10,6 +10,7 @@
 #include "../doomdef.h"
 #include "../doomtype.h"
 #include "../info.h"
+#include "../r_skins.h"
 #include "../z_zone.h"
 #include "hw_model.h"
 #include "hw_md2load.h"
@@ -238,7 +239,7 @@ void HWR_ReloadModels(void)
 	size_t i;
 	INT32 s;
 
-	for (s = 0; s < MAXSKINS; s++)
+	for (s = 0; s < numskins; s++)
 	{
 		if (md2_playermodels[s].model)
 			LoadModelSprite2(md2_playermodels[s].model);
diff --git a/src/lua_playerlib.c b/src/lua_playerlib.c
index 0eb54808fb..b05e21ca9f 100644
--- a/src/lua_playerlib.c
+++ b/src/lua_playerlib.c
@@ -16,6 +16,7 @@
 #include "d_player.h"
 #include "g_game.h"
 #include "p_local.h"
+#include "r_skins.h"
 
 #include "lua_script.h"
 #include "lua_libs.h"
@@ -161,7 +162,7 @@ static int player_get(lua_State *L)
 	else if (fastcmp(field,"skin"))
 		lua_pushinteger(L, plr->skin);
 	else if (fastcmp(field,"availabilities"))
-		lua_pushinteger(L, plr->availabilities);
+		lua_pushinteger(L, R_GetAvailabilitiesBits(plr->availabilities));
 	else if (fastcmp(field,"score"))
 		lua_pushinteger(L, plr->score);
 	else if (fastcmp(field,"dashspeed"))
diff --git a/src/m_cond.c b/src/m_cond.c
index 36fcd7cf29..44ac943730 100644
--- a/src/m_cond.c
+++ b/src/m_cond.c
@@ -285,7 +285,8 @@ void M_SilentUpdateUnlockablesAndEmblems(void)
 		unlockables[i].unlocked = M_Achieved(unlockables[i].conditionset - 1);
 	}
 
-	players[consoleplayer].availabilities = players[1].availabilities = R_GetSkinAvailabilities(); // players[1] is supposed to be for 2p
+	R_GetSkinAvailabilities(&players[consoleplayer].availabilities);
+	R_GetSkinAvailabilities(&players[1].availabilities); // players[1] is supposed to be for 2p
 }
 
 // Emblem unlocking shit
diff --git a/src/m_menu.c b/src/m_menu.c
index 0fca39801b..0c5c706a8e 100644
--- a/src/m_menu.c
+++ b/src/m_menu.c
@@ -126,7 +126,9 @@ M_waiting_mode_t m_waiting_mode = M_NOT_WAITING;
 const char *quitmsg[NUM_QUITMESSAGES];
 
 // Stuff for customizing the player select screen Tails 09-22-2003
-description_t description[MAXSKINS];
+description_t *description = NULL;
+INT32 numdescriptions = 0;
+
 INT16 char_on = -1, startchar = 0;
 static char *char_notes = NULL;
 
@@ -255,7 +257,7 @@ static void M_ConfirmTeamScramble(INT32 choice);
 static void M_ConfirmTeamChange(INT32 choice);
 static void M_SecretsMenu(INT32 choice);
 static void M_SetupChoosePlayer(INT32 choice);
-static UINT8 M_SetupChoosePlayerDirect(INT32 choice);
+static UINT16 M_SetupChoosePlayerDirect(INT32 choice);
 static void M_QuitSRB2(INT32 choice);
 menu_t SP_MainDef, OP_MainDef;
 menu_t MISC_ScrambleTeamDef, MISC_ChangeTeamDef;
@@ -3970,24 +3972,32 @@ void M_Init(void)
 #endif
 }
 
-void M_InitCharacterTables(void)
+static void M_InitCharacterDescription(INT32 i)
 {
-	UINT8 i;
-
 	// Setup description table
-	for (i = 0; i < MAXSKINS; i++)
-	{
-		description[i].used = false;
-		strcpy(description[i].notes, "???");
-		strcpy(description[i].picname, "");
-		strcpy(description[i].nametag, "");
-		strcpy(description[i].skinname, "");
-		strcpy(description[i].displayname, "");
-		description[i].prev = description[i].next = 0;
-		description[i].charpic = NULL;
-		description[i].namepic = NULL;
-		description[i].oppositecolor = description[i].tagtextcolor = description[i].tagoutlinecolor = 0;
-	}
+	description_t *desc = &description[i];
+	desc->picname[0] = '\0';
+	desc->nametag[0] = '\0';
+	desc->skinname[0] = '\0';
+	desc->displayname[0] = '\0';
+	desc->prev = desc->next = 0;
+	desc->charpic = NULL;
+	desc->namepic = NULL;
+	desc->oppositecolor = SKINCOLOR_NONE;
+	desc->tagtextcolor = SKINCOLOR_NONE;
+	desc->tagoutlinecolor = SKINCOLOR_NONE;
+	strcpy(desc->notes, "???");
+}
+
+void M_InitCharacterTables(INT32 num)
+{
+	INT32 i = numdescriptions;
+
+	description = Z_Realloc(description, sizeof(description_t) * num, PU_STATIC, NULL);
+	numdescriptions = num;
+
+	for (; i < numdescriptions; i++)
+		M_InitCharacterDescription(i);
 }
 
 // ==========================================================================
@@ -4981,7 +4991,7 @@ static void M_PatchSkinNameTable(void)
 
 	for (j = 0; j < MAXSKINS; j++)
 	{
-		if (skins[j].name[0] != '\0' && R_SkinUsable(-1, j))
+		if (j < numskins && skins[j].name[0] != '\0' && R_SkinUsable(-1, j))
 		{
 			skins_cons_t[j].strvalue = skins[j].realname;
 			skins_cons_t[j].value = j+1;
@@ -8961,64 +8971,67 @@ static void M_CacheCharacterSelectEntry(INT32 i, INT32 skinnum)
 		description[i].namepic = W_CachePatchName(description[i].nametag, PU_PATCH);
 }
 
-static UINT8 M_SetupChoosePlayerDirect(INT32 choice)
+static UINT16 M_SetupChoosePlayerDirect(INT32 choice)
 {
 	INT32 skinnum;
-	UINT8 i;
-	UINT8 firstvalid = 255, lastvalid = 255;
+	UINT16 i;
+	UINT16 firstvalid = 65535, lastvalid = 65535;
 	boolean allowed = false;
-	char *and;
 	(void)choice;
 
 	if (!mapheaderinfo[startmap-1] || mapheaderinfo[startmap-1]->forcecharacter[0] == '\0')
 	{
-		for (i = 0; i < MAXSKINS; i++) // Handle charsels, availability, and unlocks.
+		for (i = 0; i < numdescriptions; i++) // Handle charsels, availability, and unlocks.
 		{
-			if (description[i].used) // If the character's disabled through SOC, there's nothing we can do for it.
+			char *and;
+
+			// If the character's disabled through SOC, there's nothing we can do for it.
+			if (!description[i].used)
+				continue;
+
+			and = strchr(description[i].skinname, '&');
+
+			if (and)
 			{
-				and = strchr(description[i].skinname, '&');
-				if (and)
-				{
-					char firstskin[SKINNAMESIZE+1];
-					if (mapheaderinfo[startmap-1] && mapheaderinfo[startmap-1]->typeoflevel & TOL_NIGHTS) // skip tagteam characters for NiGHTS levels
-						continue;
-					strncpy(firstskin, description[i].skinname, (and - description[i].skinname));
-					firstskin[(and - description[i].skinname)] = '\0';
-					description[i].skinnum[0] = R_SkinAvailable(firstskin);
-					description[i].skinnum[1] = R_SkinAvailable(and+1);
-				}
+				char firstskin[SKINNAMESIZE+1];
+				if (mapheaderinfo[startmap-1] && mapheaderinfo[startmap-1]->typeoflevel & TOL_NIGHTS) // skip tagteam characters for NiGHTS levels
+					continue;
+				strncpy(firstskin, description[i].skinname, (and - description[i].skinname));
+				firstskin[(and - description[i].skinname)] = '\0';
+				description[i].skinnum[0] = R_SkinAvailable(firstskin);
+				description[i].skinnum[1] = R_SkinAvailable(and+1);
+			}
+			else
+			{
+				description[i].skinnum[0] = R_SkinAvailable(description[i].skinname);
+				description[i].skinnum[1] = -1;
+			}
+
+			skinnum = description[i].skinnum[0];
+
+			if ((skinnum != -1) && (R_SkinUsable(-1, skinnum)))
+			{
+				// Handling order.
+				if (firstvalid == 65535)
+					firstvalid = i;
 				else
 				{
-					description[i].skinnum[0] = R_SkinAvailable(description[i].skinname);
-					description[i].skinnum[1] = -1;
+					description[i].prev = lastvalid;
+					description[lastvalid].next = i;
 				}
-				skinnum = description[i].skinnum[0];
-				if ((skinnum != -1) && (R_SkinUsable(-1, skinnum)))
-				{
-					// Handling order.
-					if (firstvalid == 255)
-						firstvalid = i;
-					else
-					{
-						description[i].prev = lastvalid;
-						description[lastvalid].next = i;
-					}
-					lastvalid = i;
+				lastvalid = i;
 
-					if (i == char_on)
-						allowed = true;
+				if (i == char_on)
+					allowed = true;
 
-					M_CacheCharacterSelectEntry(i, skinnum);
-				}
-				// else -- Technically, character select icons without corresponding skins get bundled away behind this too. Sucks to be them.
+				M_CacheCharacterSelectEntry(i, skinnum);
 			}
+			// else -- Technically, character select icons without corresponding skins get bundled away behind this too. Sucks to be them.
 		}
 	}
 
 	if (firstvalid == lastvalid) // We're being forced into a specific character, so might as well just skip it.
-	{
 		return firstvalid;
-	}
 
 	// One last bit of order we can't do in the iteration above.
 	description[firstvalid].prev = lastvalid;
@@ -9027,7 +9040,7 @@ static UINT8 M_SetupChoosePlayerDirect(INT32 choice)
 	if (!allowed)
 	{
 		char_on = firstvalid;
-		if (startchar > 0 && startchar < MAXSKINS)
+		if (startchar > 0 && startchar < numdescriptions)
 		{
 			INT16 workchar = startchar;
 			while (workchar--)
@@ -9035,13 +9048,13 @@ static UINT8 M_SetupChoosePlayerDirect(INT32 choice)
 		}
 	}
 
-	return MAXSKINS;
+	return MAXCHARACTERSLOTS;
 }
 
 static void M_SetupChoosePlayer(INT32 choice)
 {
-	UINT8 skinset = M_SetupChoosePlayerDirect(choice);
-	if (skinset != MAXSKINS)
+	UINT16 skinset = M_SetupChoosePlayerDirect(choice);
+	if (skinset != MAXCHARACTERSLOTS)
 	{
 		M_ChoosePlayer(skinset);
 		return;
@@ -9362,7 +9375,7 @@ static void M_ChoosePlayer(INT32 choice)
 	UINT8 skinnum;
 
 	// skip this if forcecharacter or no characters available
-	if (choice == 255)
+	if (choice == 65535)
 	{
 		skinnum = botskin = 0;
 		botingame = false;
@@ -10431,7 +10444,7 @@ static void M_MarathonLiveEventBackup(INT32 choice)
 // Going to Marathon menu...
 static void M_Marathon(INT32 choice)
 {
-	UINT8 skinset;
+	UINT16 skinset;
 	INT32 mapnum = 0;
 
 	if (choice != -1 && FIL_FileExists(liveeventbackup))
@@ -10454,7 +10467,7 @@ static void M_Marathon(INT32 choice)
 
 	skinset = M_SetupChoosePlayerDirect(-1);
 
-	SP_MarathonMenu[marathonplayer].status = (skinset == MAXSKINS) ? IT_KEYHANDLER|IT_STRING : IT_NOTHING|IT_DISABLED;
+	SP_MarathonMenu[marathonplayer].status = (skinset == MAXCHARACTERSLOTS) ? IT_KEYHANDLER|IT_STRING : IT_NOTHING|IT_DISABLED;
 
 	while (mapnum < NUMMAPS)
 	{
diff --git a/src/m_menu.h b/src/m_menu.h
index 0465128ef7..018d5c7e78 100644
--- a/src/m_menu.h
+++ b/src/m_menu.h
@@ -200,8 +200,8 @@ void M_Drawer(void);
 // Called by D_SRB2Main, loads the config file.
 void M_Init(void);
 
-// Called by D_SRB2Main also, sets up the playermenu and description tables.
-void M_InitCharacterTables(void);
+// Called by deh_soc.c, sets up the playermenu and description tables.
+void M_InitCharacterTables(INT32 num);
 
 // Called by intro code to force menu up upon a keypress,
 // does nothing if menu is already up.
@@ -431,7 +431,8 @@ typedef struct
 	INT32 gamemap;
 } saveinfo_t;
 
-extern description_t description[MAXSKINS];
+extern description_t *description;
+extern INT32 numdescriptions;
 
 extern consvar_t cv_showfocuslost;
 extern consvar_t cv_newgametype, cv_nextmap, cv_chooseskin, cv_serversort;
diff --git a/src/p_saveg.c b/src/p_saveg.c
index 03229e740b..80bdfa09e8 100644
--- a/src/p_saveg.c
+++ b/src/p_saveg.c
@@ -139,7 +139,7 @@ static void P_NetArchivePlayers(void)
 
 		WRITEUINT8(save_p, players[i].skincolor);
 		WRITEINT32(save_p, players[i].skin);
-		WRITEUINT32(save_p, players[i].availabilities);
+		WRITEMEM(save_p, players[i].availabilities, sizeof(bitarray_t) * numskins);
 		WRITEUINT32(save_p, players[i].score);
 		WRITEFIXED(save_p, players[i].dashspeed);
 		WRITESINT8(save_p, players[i].lives);
@@ -353,7 +353,7 @@ static void P_NetUnArchivePlayers(void)
 
 		players[i].skincolor = READUINT8(save_p);
 		players[i].skin = READINT32(save_p);
-		players[i].availabilities = READUINT32(save_p);
+		READMEM(save_p, players[i].availabilities, sizeof(bitarray_t) * numskins);
 		players[i].score = READUINT32(save_p);
 		players[i].dashspeed = READFIXED(save_p); // dashing speed
 		players[i].lives = READSINT8(save_p);
diff --git a/src/r_picformats.c b/src/r_picformats.c
index f87362c76e..ff2b9f15a1 100644
--- a/src/r_picformats.c
+++ b/src/r_picformats.c
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 2005-2009 by Andrey "entryway" Budko.
-// Copyright (C) 2018-2020 by Jaime "Lactozilla" Passos.
+// Copyright (C) 2018-2021 by Jaime "Lactozilla" Passos.
 // Copyright (C) 2019-2020 by Sonic Team Junior.
 //
 // This program is free software distributed under the
@@ -1494,7 +1494,7 @@ static void R_ParseSpriteInfo(boolean spr2)
 	spritenum_t sprnum = NUMSPRITES;
 	playersprite_t spr2num = NUMPLAYERSPRITES;
 	INT32 i;
-	INT32 skinnumbers[MAXSKINS];
+	INT32 *skinnumbers = NULL;
 	INT32 foundskins = 0;
 
 	// Sprite name
@@ -1592,6 +1592,8 @@ static void R_ParseSpriteInfo(boolean spr2)
 				if (skinnum == -1)
 					I_Error("Error parsing SPRTINFO lump: Unknown skin \"%s\"", skinName);
 
+				if (skinnumbers == NULL)
+					skinnumbers = Z_Malloc(sizeof(INT32) * numskins, PU_STATIC, NULL);
 				skinnumbers[foundskins] = skinnum;
 				foundskins++;
 			}
@@ -1630,8 +1632,11 @@ static void R_ParseSpriteInfo(boolean spr2)
 	{
 		I_Error("Error parsing SPRTINFO lump: Expected \"{\" for sprite \"%s\", got \"%s\"",newSpriteName,sprinfoToken);
 	}
+
 	Z_Free(sprinfoToken);
 	Z_Free(info);
+	if (skinnumbers)
+		Z_Free(skinnumbers);
 }
 
 //
diff --git a/src/r_skins.c b/src/r_skins.c
index 6f150f234e..bfbc3e18f7 100644
--- a/src/r_skins.c
+++ b/src/r_skins.c
@@ -18,6 +18,7 @@
 #include "st_stuff.h"
 #include "w_wad.h"
 #include "z_zone.h"
+#include "m_menu.h"
 #include "m_misc.h"
 #include "info.h" // spr2names
 #include "i_video.h" // rendermode
@@ -32,13 +33,7 @@
 #endif
 
 INT32 numskins = 0;
-skin_t skins[MAXSKINS];
-
-// FIXTHIS: don't work because it must be inistilised before the config load
-//#define SKINVALUES
-#ifdef SKINVALUES
-CV_PossibleValue_t skin_cons_t[MAXSKINS+1];
-#endif
+skin_t *skins = NULL;
 
 //
 // P_GetSkinSprite2
@@ -160,30 +155,37 @@ static void Sk_SetDefaultValue(skin_t *skin)
 //
 void R_InitSkins(void)
 {
-#ifdef SKINVALUES
-	INT32 i;
+	// no default skin!
+	numskins = 0;
+}
 
-	for (i = 0; i <= MAXSKINS; i++)
+void R_GetSkinAvailabilities(bitarray_t **response)
+{
+	INT32 s;
+
+	if (*response == NULL)
+		(*response) = Z_Calloc(sizeof(bitarray_t) * numskins, PU_STATIC, NULL);
+
+	for (s = 0; s < numskins; s++)
 	{
-		skin_cons_t[i].value = 0;
-		skin_cons_t[i].strvalue = NULL;
+		if (skins[s].availability && unlockables[skins[s].availability - 1].unlocked)
+			set_bit_array((*response), s);
+		else
+			unset_bit_array((*response), s);
 	}
-#endif
-
-	// no default skin!
-	numskins = 0;
 }
 
-UINT32 R_GetSkinAvailabilities(void)
+UINT32 R_GetAvailabilitiesBits(bitarray_t *availabilities)
 {
 	INT32 s;
 	UINT32 response = 0;
 
-	for (s = 0; s < MAXSKINS; s++)
+	for (s = 0; s < min(numskins, 32); s++)
 	{
-		if (skins[s].availability && unlockables[skins[s].availability - 1].unlocked)
+		if (in_bit_array(availabilities, s))
 			response |= (1 << s);
 	}
+
 	return response;
 }
 
@@ -193,7 +195,7 @@ boolean R_SkinUsable(INT32 playernum, INT32 skinnum)
 {
 	return ((skinnum == -1) // Simplifies things elsewhere, since there's already plenty of checks for less-than-0...
 		|| (!skins[skinnum].availability)
-		|| (((netgame || multiplayer) && playernum != -1) ? (players[playernum].availabilities & (1 << skinnum)) : (unlockables[skins[skinnum].availability - 1].unlocked))
+		|| (((netgame || multiplayer) && playernum != -1) ? (in_bit_array(players[playernum].availabilities, skinnum)) : (unlockables[skins[skinnum].availability - 1].unlocked))
 		|| (modeattacking) // If you have someone else's run you might as well take a look
 		|| (Playing() && (R_SkinAvailable(mapheaderinfo[gamemap-1]->forcecharacter) == skinnum)) // Force 1.
 		|| (netgame && (cv_forceskin.value == skinnum)) // Force 2.
@@ -594,6 +596,7 @@ void R_AddSkins(UINT16 wadnum)
 		buf2[size] = '\0';
 
 		// set defaults
+		skins = Z_Realloc(skins, sizeof(skin_t) * (numskins + 1), PU_STATIC, NULL);
 		skin = &skins[numskins];
 		Sk_SetDefaultValue(skin);
 		skin->wadnum = wadnum;
@@ -695,17 +698,13 @@ next_token:
 
 		if (!skin->availability) // Safe to print...
 			CONS_Printf(M_GetText("Added skin '%s'\n"), skin->name);
-#ifdef SKINVALUES
-		skin_cons_t[numskins].value = numskins;
-		skin_cons_t[numskins].strvalue = skin->name;
-#endif
+
+		numskins++;
 
 #ifdef HWRENDER
 		if (rendermode == render_opengl)
-			HWR_AddPlayerModel(numskins);
+			HWR_AddPlayerModel(numskins-1);
 #endif
-
-		numskins++;
 	}
 	return;
 }
diff --git a/src/r_skins.h b/src/r_skins.h
index fbbb38743d..f2e76afcb4 100644
--- a/src/r_skins.h
+++ b/src/r_skins.h
@@ -86,7 +86,7 @@ typedef struct
 
 /// Externs
 extern INT32 numskins;
-extern skin_t skins[MAXSKINS];
+extern skin_t *skins;
 
 /// Function prototypes
 void R_InitSkins(void);
@@ -94,7 +94,8 @@ void R_InitSkins(void);
 void SetPlayerSkin(INT32 playernum,const char *skinname);
 void SetPlayerSkinByNum(INT32 playernum,INT32 skinnum); // Tails 03-16-2002
 boolean R_SkinUsable(INT32 playernum, INT32 skinnum);
-UINT32 R_GetSkinAvailabilities(void);
+void R_GetSkinAvailabilities(bitarray_t **response);
+UINT32 R_GetAvailabilitiesBits(bitarray_t *availabilities);
 INT32 R_SkinAvailable(const char *name);
 void R_PatchSkins(UINT16 wadnum);
 void R_AddSkins(UINT16 wadnum);
diff --git a/src/sounds.h b/src/sounds.h
index e49dd2f3e3..7a2e3bc25f 100644
--- a/src/sounds.h
+++ b/src/sounds.h
@@ -43,7 +43,7 @@ typedef enum
 
 // free sfx for S_AddSoundFx()
 #define NUMSFXFREESLOTS 1600 // Matches SOC Editor.
-#define NUMSKINSFXSLOTS (MAXSKINS*NUMSKINSOUNDS)
+#define NUMSKINSFXSLOTS (128*NUMSKINSOUNDS)
 
 //
 // SoundFX struct.
@@ -874,7 +874,7 @@ typedef enum
 	// free slots for S_AddSoundFx() at run-time --------------------
 	sfx_freeslot0,
 	//
-	// ... 60 free sounds here ...
+	// ... 1600 free sounds here ...
 	//
 	sfx_lastfreeslot = sfx_freeslot0 + NUMSFXFREESLOTS-1,
 	// end of freeslots ---------------------------------------------
diff --git a/src/st_stuff.c b/src/st_stuff.c
index a1fbbec03a..cd4582f6d4 100644
--- a/src/st_stuff.c
+++ b/src/st_stuff.c
@@ -50,8 +50,8 @@ UINT16 objectsdrawn = 0;
 // STATUS BAR DATA
 //
 
-patch_t *faceprefix[MAXSKINS]; // face status patches
-patch_t *superprefix[MAXSKINS]; // super face status patches
+patch_t **faceprefix; // face status patches
+patch_t **superprefix; // super face status patches
 
 // ------------------------------------------
 //             status bar overlay
@@ -368,6 +368,17 @@ void ST_ReloadSkinFaceGraphics(void)
 {
 	INT32 i;
 
+	if (faceprefix)
+		Z_Free(faceprefix);
+	if (superprefix)
+		Z_Free(superprefix);
+
+	if (!numskins)
+		return;
+
+	faceprefix = Z_Malloc(sizeof(patch_t *) * numskins, PU_STATIC, NULL);
+	superprefix = Z_Malloc(sizeof(patch_t *) * numskins, PU_STATIC, NULL);
+
 	for (i = 0; i < numskins; i++)
 		ST_LoadFaceGraphics(i);
 }
diff --git a/src/st_stuff.h b/src/st_stuff.h
index 4ea307d2b4..c31651cd5e 100644
--- a/src/st_stuff.h
+++ b/src/st_stuff.h
@@ -42,7 +42,7 @@ void ST_UnloadGraphics(void);
 void ST_LoadGraphics(void);
 
 // face load graphics, called when skin changes
-void ST_LoadFaceGraphics(INT32 playernum);
+void ST_LoadFaceGraphics(INT32 skinnum);
 void ST_ReloadSkinFaceGraphics(void);
 
 void ST_doPaletteStuff(void);
@@ -76,8 +76,8 @@ extern patch_t *sboscore;
 extern patch_t *sbotime;
 extern patch_t *sbocolon;
 extern patch_t *sboperiod;
-extern patch_t *faceprefix[MAXSKINS]; // face status patches
-extern patch_t *superprefix[MAXSKINS]; // super face status patches
+extern patch_t **faceprefix; // face status patches
+extern patch_t **superprefix; // super face status patches
 extern patch_t *livesback;
 extern patch_t *stlivex;
 extern patch_t *ngradeletters[7];
-- 
GitLab