From 4e256b73b2c6d8a47c3bb1f46cf14fad4115764a Mon Sep 17 00:00:00 2001
From: toaster <rollerorbital@gmail.com>
Date: Sat, 24 Aug 2019 18:25:27 +0100
Subject: [PATCH 1/3] Lua save-banks!

* Array of 8 INT32's natively embedded into savedata (net and SP)!
* Initialised to zero whenever a new save (or equivalent) is started, otherwise untouched by the base game.
* Requires reservation to avoid clobber-conflicts.
    * Access via `reserveLuabanks()` - returns a read-write userdata.
    * Assign userdata to local variable or global rawset to use later.

Mostly for future SUGOIlikes, but I'm sure someone could figure out an unrelated usage eventually.
---
 src/d_main.c      |  1 +
 src/d_netcmd.c    |  4 +++
 src/doomstat.h    |  4 +++
 src/g_game.c      | 25 ++++++++++++++++-
 src/lua_baselib.c | 15 ++++++++++
 src/lua_infolib.c | 70 ++++++++++++++++++++++++++++++++++++++++++++++-
 src/lua_libs.h    |  2 ++
 src/m_menu.c      | 27 +++++++++++++++++-
 src/p_saveg.c     | 51 ++++++++++++++++++++++++++++++----
 9 files changed, 191 insertions(+), 8 deletions(-)

diff --git a/src/d_main.c b/src/d_main.c
index eaeae4b10..d99fb7494 100644
--- a/src/d_main.c
+++ b/src/d_main.c
@@ -716,6 +716,7 @@ void D_StartTitle(void)
 	botskin = 0;
 	cv_debug = 0;
 	emeralds = 0;
+	memset(&luabanks, 0, sizeof(luabanks));
 	lastmaploaded = 0;
 
 	// In case someone exits out at the same time they start a time attack run,
diff --git a/src/d_netcmd.c b/src/d_netcmd.c
index 3e82fc60c..c1183ebbe 100644
--- a/src/d_netcmd.c
+++ b/src/d_netcmd.c
@@ -1908,7 +1908,10 @@ static void Got_Mapcmd(UINT8 **cp, INT32 playernum)
 		precache = false;
 
 	if (resetplayer && !FLS)
+	{
 		emeralds = 0;
+		memset(&luabanks, 0, sizeof(luabanks));
+	}
 
 	if (modeattacking)
 	{
@@ -4103,6 +4106,7 @@ void Command_ExitGame_f(void)
 	botskin = 0;
 	cv_debug = 0;
 	emeralds = 0;
+	memset(&luabanks, 0, sizeof(luabanks));
 
 	if (dirmenu)
 		closefilemenu(true);
diff --git a/src/doomstat.h b/src/doomstat.h
index b6c376d1c..1bf67de61 100644
--- a/src/doomstat.h
+++ b/src/doomstat.h
@@ -418,6 +418,10 @@ extern UINT16 emeralds;
 #define EMERALD7 64
 #define ALL7EMERALDS(v) ((v & (EMERALD1|EMERALD2|EMERALD3|EMERALD4|EMERALD5|EMERALD6|EMERALD7)) == (EMERALD1|EMERALD2|EMERALD3|EMERALD4|EMERALD5|EMERALD6|EMERALD7))
 
+// yes, even in non HAVE_BLUA
+#define NUM_LUABANKS 8 // please only make this number go up between versions, never down. you'll break saves otherwise. also, must fit in UINT8
+extern INT32 luabanks[NUM_LUABANKS];
+
 extern INT32 nummaprings; //keep track of spawned rings/coins
 
 /** Time attack information, currently a very small structure.
diff --git a/src/g_game.c b/src/g_game.c
index e70241269..ec86c3d51 100644
--- a/src/g_game.c
+++ b/src/g_game.c
@@ -172,6 +172,7 @@ static boolean retrying = false;
 UINT8 stagefailed; // Used for GEMS BONUS? Also to see if you beat the stage.
 
 UINT16 emeralds;
+INT32 luabanks[NUM_LUABANKS]; // yes, even in non HAVE_BLUA
 UINT32 token; // Number of tokens collected in a level
 UINT32 tokenlist; // List of tokens collected
 boolean gottoken; // Did you get a token? Used for end of act
@@ -3778,7 +3779,29 @@ void G_SaveGameOver(UINT32 slot, boolean modifylives)
 
 		// File end marker check
 		CHECKPOS
-		if (READUINT8(save_p) != 0x1d) BADSAVE;
+		switch (READUINT8(save_p))
+		{
+			case 0xb7:
+				{
+					UINT8 i, banksinuse;
+					CHECKPOS
+					banksinuse = READUINT8(save_p);
+					CHECKPOS
+					if (banksinuse > NUM_LUABANKS)
+						BADSAVE
+					for (i = 0; i < banksinuse; i++)
+					{
+						(void)READINT32(save_p);
+						CHECKPOS
+					}
+					if (READUINT8(save_p) != 0x1d)
+						BADSAVE
+				}
+			case 0x1d:
+				break;
+			default:
+				BADSAVE
+		}
 
 		// done
 		saved = FIL_WriteFile(backup, savebuffer, length);
diff --git a/src/lua_baselib.c b/src/lua_baselib.c
index 3c136a436..a69e8a188 100644
--- a/src/lua_baselib.c
+++ b/src/lua_baselib.c
@@ -182,6 +182,8 @@ static const struct {
 	{META_CAMERA,       "camera_t"},
 
 	{META_ACTION,       "action"},
+
+	{META_LUABANKS,     "luabanks[]"},
 	{NULL,              NULL}
 };
 
@@ -228,6 +230,18 @@ static int lib_isPlayerAdmin(lua_State *L)
 	return 1;
 }
 
+static int lib_reserveLuabanks(lua_State *L)
+{
+	static boolean reserved = false;
+	if (!lua_lumploading)
+		return luaL_error(L, "luabanks[] cannot be reserved from within a hook or coroutine!");
+	if (reserved)
+		return luaL_error(L, "luabanks[] has already been reserved! Only one savedata-enabled mod at a time may use this feature.");
+	reserved = true;
+	LUA_PushUserdata(L, &luabanks, META_LUABANKS);
+	return 1;
+}
+
 // M_RANDOM
 //////////////
 
@@ -2736,6 +2750,7 @@ static luaL_Reg lib[] = {
 	{"chatprintf", lib_chatprintf},
 	{"userdataType", lib_userdataType},
 	{"IsPlayerAdmin", lib_isPlayerAdmin},
+	{"reserveLuabanks", lib_reserveLuabanks},
 
 	// m_random
 	{"P_RandomFixed",lib_pRandomFixed},
diff --git a/src/lua_infolib.c b/src/lua_infolib.c
index 77f37f8ec..8338fa5b9 100644
--- a/src/lua_infolib.c
+++ b/src/lua_infolib.c
@@ -18,6 +18,7 @@
 #include "p_mobj.h"
 #include "p_local.h"
 #include "z_zone.h"
+#include "doomstat.h" // luabanks[]
 
 #include "lua_script.h"
 #include "lua_libs.h"
@@ -146,7 +147,7 @@ static int lib_getSpr2default(lua_State *L)
 		return luaL_error(L, "spr2defaults[] invalid index");
 
 	if (i >= free_spr2)
-		return 0;
+		return luaL_error(L, "spr2defaults[] index %d out of range (%d - %d)", i, 0, free_spr2-1);
 
 	lua_pushinteger(L, spr2defaults[i]);
 	return 1;
@@ -1026,6 +1027,61 @@ static int sfxinfo_num(lua_State *L)
 	return 1;
 }
 
+//////////////
+// LUABANKS //
+//////////////
+
+static int lib_getluabanks(lua_State *L)
+{
+	UINT8 i;
+
+	lua_remove(L, 1); // don't care about luabanks[] dummy userdata.
+
+	if (lua_isnumber(L, 1))
+		i = lua_tonumber(L, 1);
+	else
+		return luaL_error(L, "luabanks[] invalid index");
+
+	if (i >= NUM_LUABANKS)
+		luaL_error(L, "luabanks[] index %d out of range (%d - %d)", i, 0, NUM_LUABANKS);
+
+	lua_pushinteger(L, luabanks[i]);
+	return 1;
+}
+
+static int lib_setluabanks(lua_State *L)
+{
+	UINT8 i;
+	INT32 j = 0;
+
+	if (hud_running)
+		return luaL_error(L, "Do not alter luabanks[] in HUD rendering code!");
+
+	lua_remove(L, 1); // don't care about luabanks[] dummy userdata.
+
+	if (lua_isnumber(L, 1))
+		i = lua_tonumber(L, 1);
+	else
+		return luaL_error(L, "luabanks[] invalid index");
+
+	if (i >= NUM_LUABANKS)
+		luaL_error(L, "luabanks[] index %d out of range (%d - %d)", i, 0, NUM_LUABANKS-1);
+
+	if (lua_isnumber(L, 2))
+		j = lua_tonumber(L, 2);
+	else
+		return luaL_error(L, "luabanks[] invalid set");
+
+	luabanks[i] = j;
+	return 0;
+}
+
+static int lib_luabankslen(lua_State *L)
+{
+	lua_pushinteger(L, NUM_LUABANKS);
+	return 1;
+}
+
 //////////////////////////////
 //
 // Now push all these functions into the Lua state!
@@ -1147,6 +1203,18 @@ int LUA_InfoLib(lua_State *L)
 	lua_pushvalue(L, -1);
 	lua_setglobal(L, "S_sfx");
 	lua_setglobal(L, "sfxinfo");
+
+	luaL_newmetatable(L, META_LUABANKS);
+		lua_pushcfunction(L, lib_getluabanks);
+		lua_setfield(L, -2, "__index");
+
+		lua_pushcfunction(L, lib_setluabanks);
+		lua_setfield(L, -2, "__newindex");
+
+		lua_pushcfunction(L, lib_luabankslen);
+		lua_setfield(L, -2, "__len");
+	lua_pop(L, 1);
+
 	return 0;
 }
 
diff --git a/src/lua_libs.h b/src/lua_libs.h
index 827c1d798..7609971ce 100644
--- a/src/lua_libs.h
+++ b/src/lua_libs.h
@@ -67,6 +67,8 @@ extern lua_State *gL;
 
 #define META_ACTION "ACTIONF_T*"
 
+#define META_LUABANKS "LUABANKS[]*"
+
 boolean luaL_checkboolean(lua_State *L, int narg);
 
 int LUA_EnumLib(lua_State *L);
diff --git a/src/m_menu.c b/src/m_menu.c
index 128b15a76..96efb569a 100644
--- a/src/m_menu.c
+++ b/src/m_menu.c
@@ -6885,6 +6885,7 @@ static void M_StartTutorial(INT32 choice)
 	tutorialmode = true; // turn on tutorial mode
 
 	emeralds = 0;
+	memset(&luabanks, 0, sizeof(luabanks));
 	M_ClearMenus(true);
 	gamecomplete = false;
 	cursaveslot = 0;
@@ -7293,7 +7294,29 @@ static void M_ReadSavegameInfo(UINT32 slot)
 
 	// File end marker check
 	CHECKPOS
-	if (READUINT8(save_p) != 0x1d) BADSAVE;
+	switch (READUINT8(save_p))
+	{
+		case 0xb7:
+			{
+				UINT8 i, banksinuse;
+				CHECKPOS
+				banksinuse = READUINT8(save_p);
+				CHECKPOS
+				if (banksinuse > NUM_LUABANKS)
+					BADSAVE
+				for (i = 0; i < banksinuse; i++)
+				{
+					(void)READINT32(save_p);
+					CHECKPOS
+				}
+				if (READUINT8(save_p) != 0x1d)
+					BADSAVE
+			}
+		case 0x1d:
+			break;
+		default:
+			BADSAVE
+	}
 
 	// done
 	Z_Free(savebuffer);
@@ -8495,6 +8518,7 @@ static void M_ChooseNightsAttack(INT32 choice)
 	char nameofdemo[256];
 	(void)choice;
 	emeralds = 0;
+	memset(&luabanks, 0, sizeof(luabanks));
 	M_ClearMenus(true);
 	modeattacking = ATTACKING_NIGHTS;
 
@@ -8519,6 +8543,7 @@ static void M_ChooseTimeAttack(INT32 choice)
 	char nameofdemo[256];
 	(void)choice;
 	emeralds = 0;
+	memset(&luabanks, 0, sizeof(luabanks));
 	M_ClearMenus(true);
 	modeattacking = ATTACKING_RECORD;
 
diff --git a/src/p_saveg.c b/src/p_saveg.c
index ea998b445..e03863bc2 100644
--- a/src/p_saveg.c
+++ b/src/p_saveg.c
@@ -4109,12 +4109,54 @@ static inline boolean P_NetUnArchiveMisc(void)
 	return true;
 }
 
+static inline void P_ArchiveLuabanksAndConsistency(void)
+{
+	UINT8 i, banksinuse = NUM_LUABANKS;
+
+	while (banksinuse && !luabanks[banksinuse-1])
+		banksinuse--; // get the last used bank
+
+	if (banksinuse)
+	{
+		WRITEUINT8(save_p, 0xb7); // luabanks marker
+		WRITEUINT8(save_p, banksinuse);
+		for (i = 0; i < banksinuse; i++)
+			WRITEINT32(save_p, luabanks[i]);
+	}
+
+	WRITEUINT8(save_p, 0x1d); // consistency marker
+}
+
+static inline boolean P_UnArchiveLuabanksAndConsistency(void)
+{
+	switch (READUINT8(save_p))
+	{
+		case 0xb7:
+			{
+				UINT8 i, banksinuse = READUINT8(save_p);
+				if (banksinuse > NUM_LUABANKS)
+					return false;
+				for (i = 0; i < banksinuse; i++)
+					luabanks[i] = READINT32(save_p);
+				if (READUINT8(save_p) != 0x1d)
+					return false;
+			}
+		case 0x1d:
+			break;
+		default:
+			return false;
+	}
+
+	return true;
+}
+
 void P_SaveGame(void)
 {
 	P_ArchiveMisc();
 	P_ArchivePlayer();
 
-	WRITEUINT8(save_p, 0x1d); // consistency marker
+	// yes, even in non HAVE_BLUA
+	P_ArchiveLuabanksAndConsistency();
 }
 
 void P_SaveNetGame(void)
@@ -4153,7 +4195,7 @@ void P_SaveNetGame(void)
 	LUA_Archive();
 #endif
 
-	WRITEUINT8(save_p, 0x1d); // consistency marker
+	P_ArchiveLuabanksAndConsistency();
 }
 
 boolean P_LoadGame(INT16 mapoverride)
@@ -4165,8 +4207,7 @@ boolean P_LoadGame(INT16 mapoverride)
 	P_UnArchiveSPGame(mapoverride);
 	P_UnArchivePlayer();
 
-	// Savegame end marker
-	if (READUINT8(save_p) != 0x1d)
+	if (!P_UnArchiveLuabanksAndConsistency())
 		return false;
 
 	// Only do this after confirming savegame is ok
@@ -4207,5 +4248,5 @@ boolean P_LoadNetGame(void)
 	// precipitation when loading a netgame save. Instead, precip has to be spawned here.
 	// This is done in P_NetUnArchiveSpecials now.
 
-	return READUINT8(save_p) == 0x1d;
+	return P_UnArchiveLuabanksAndConsistency();
 }

From b746ef66ac5abb7e4574e56e875f114564f24c87 Mon Sep 17 00:00:00 2001
From: toaster <rollerorbital@gmail.com>
Date: Sat, 24 Aug 2019 22:56:20 +0100
Subject: [PATCH 2/3] Fix range print for getter error (setter was caught ahead
 of time)

---
 src/lua_infolib.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/lua_infolib.c b/src/lua_infolib.c
index 8338fa5b9..8ef0bafcf 100644
--- a/src/lua_infolib.c
+++ b/src/lua_infolib.c
@@ -1043,7 +1043,7 @@ static int lib_getluabanks(lua_State *L)
 		return luaL_error(L, "luabanks[] invalid index");
 
 	if (i >= NUM_LUABANKS)
-		luaL_error(L, "luabanks[] index %d out of range (%d - %d)", i, 0, NUM_LUABANKS);
+		luaL_error(L, "luabanks[] index %d out of range (%d - %d)", i, 0, NUM_LUABANKS-1);
 
 	lua_pushinteger(L, luabanks[i]);
 	return 1;

From 323c7fa064d7851a3cb9cd3b9e2a9acaf6479acb Mon Sep 17 00:00:00 2001
From: toaster <rollerorbital@gmail.com>
Date: Wed, 11 Sep 2019 14:22:56 -0400
Subject: [PATCH 3/3] Increase maximum number of Luabanks to 16 on Steel's
 suggestion. (Using the Web IDE 'cuz I'm tired, so no new exe; luckily I made
 the code flexible to constant replacement!)

---
 src/doomstat.h | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/doomstat.h b/src/doomstat.h
index 1bf67de61..c12a6d007 100644
--- a/src/doomstat.h
+++ b/src/doomstat.h
@@ -419,7 +419,7 @@ extern UINT16 emeralds;
 #define ALL7EMERALDS(v) ((v & (EMERALD1|EMERALD2|EMERALD3|EMERALD4|EMERALD5|EMERALD6|EMERALD7)) == (EMERALD1|EMERALD2|EMERALD3|EMERALD4|EMERALD5|EMERALD6|EMERALD7))
 
 // yes, even in non HAVE_BLUA
-#define NUM_LUABANKS 8 // please only make this number go up between versions, never down. you'll break saves otherwise. also, must fit in UINT8
+#define NUM_LUABANKS 16 // please only make this number go up between versions, never down. you'll break saves otherwise. also, must fit in UINT8
 extern INT32 luabanks[NUM_LUABANKS];
 
 extern INT32 nummaprings; //keep track of spawned rings/coins