diff --git a/src/d_main.c b/src/d_main.c
index 9fa506bee472054d13148d3a9ce6a961f83d4b47..7b62435883689fbfd9ff3da2c3d0ea538156b753 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 d0296e7fcbf4d7b80970c6ea2eeee6f57f250817..4e3cdaaacadb51a22371006acfbdd06824ed6e48 100644
--- a/src/d_netcmd.c
+++ b/src/d_netcmd.c
@@ -1916,7 +1916,10 @@ static void Got_Mapcmd(UINT8 **cp, INT32 playernum)
 		precache = false;
 
 	if (resetplayer && !FLS)
+	{
 		emeralds = 0;
+		memset(&luabanks, 0, sizeof(luabanks));
+	}
 
 	if (modeattacking)
 	{
@@ -4111,6 +4114,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 a70a122a6f3b0ec0d3628f17bc844f1f019b27bb..57e37f72dc98dfbcbecdb225292e6bcc10c71db7 100644
--- a/src/doomstat.h
+++ b/src/doomstat.h
@@ -420,6 +420,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 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
 
 /** Time attack information, currently a very small structure.
diff --git a/src/g_game.c b/src/g_game.c
index 55154c3e6d47248ce13afa8f9a5e0641eac3e207..80bd8bc3e38e1d9c7e6f7113c286f34b3eb00a34 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
@@ -3788,7 +3789,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 3c136a43695b66fdafb3ae2cb29f8ef3ff18dbe5..a69e8a188d0030a2893ce4e4eac8237130edafc9 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 77f37f8ecc90b26b3f43894ac2f29a3e93f4f9ec..8ef0bafcf3801a306234d5898bd1c228aa6d4fd1 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-1);
+
+	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 827c1d7987713ae0440fb31665226c839748fa71..7609971ce65bae48f1595c2737a817dd03e20f60 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 b32c0a65d84ed31e29978638f34b5e2746bb06a4..9ed6d02c1fbe4a8ae64cd87b6a9c53b890abc25d 100644
--- a/src/m_menu.c
+++ b/src/m_menu.c
@@ -6895,6 +6895,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;
@@ -7303,7 +7304,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);
@@ -8505,6 +8528,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;
 
@@ -8529,6 +8553,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 12f14e99da1d2c42131b099961a90b052547bc49..69c942236df283f5825f2ce8a1f8b081daa12d61 100644
--- a/src/p_saveg.c
+++ b/src/p_saveg.c
@@ -4119,12 +4119,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)
@@ -4163,7 +4205,7 @@ void P_SaveNetGame(void)
 	LUA_Archive();
 #endif
 
-	WRITEUINT8(save_p, 0x1d); // consistency marker
+	P_ArchiveLuabanksAndConsistency();
 }
 
 boolean P_LoadGame(INT16 mapoverride)
@@ -4175,8 +4217,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
@@ -4217,5 +4258,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();
 }