diff --git a/src/m_menu.c b/src/m_menu.c
index f057f5c38d0679bbf21ee87ff520f5c8921aac58..fa4a9e068fc101a8e098b4821c158971e5bef158 100644
--- a/src/m_menu.c
+++ b/src/m_menu.c
@@ -140,6 +140,7 @@ static char *char_notes = NULL;
 
 boolean menuactive = false;
 boolean fromlevelselect = false;
+tic_t shieldbuttonprompt = 0; // Show a prompt about the new Shield button for old configs // TODO: 2.3: Remove
 
 typedef enum
 {
@@ -3148,6 +3149,7 @@ static void Command_Manual_f(void)
 	if (modeattacking)
 		return;
 	M_StartControlPanel();
+	if (shieldbuttonprompt) return; // TODO: 2.3: Delete this line
 	currentMenu = &MISC_HelpDef;
 	itemOn = 0;
 }
@@ -3327,6 +3329,7 @@ boolean M_Responder(event_t *ev)
 				if (modeattacking)
 					return true;
 				M_StartControlPanel();
+				if (shieldbuttonprompt) return true; // TODO: 2.3: Delete this line
 				M_Options(0);
 				// Uncomment the below if you want the menu to reset to the top each time like before. M_SetupNextMenu will fix it automatically.
 				//OP_SoundOptionsDef.lastOn = 0;
@@ -3337,6 +3340,7 @@ boolean M_Responder(event_t *ev)
 				if (modeattacking)
 					return true;
 				M_StartControlPanel();
+				if (shieldbuttonprompt) return true; // TODO: 2.3: Delete this line
 				M_Options(0);
 				M_VideoModeMenu(0);
 				return true;
@@ -3348,6 +3352,7 @@ boolean M_Responder(event_t *ev)
 				if (modeattacking)
 					return true;
 				M_StartControlPanel();
+				if (shieldbuttonprompt) return true; // TODO: 2.3: Delete this line
 				M_Options(0);
 				M_SetupNextMenu(&OP_MainDef);
 				return true;
@@ -3618,6 +3623,27 @@ void M_Drawer(void)
 	}
 }
 
+// Handle the "Do you want to assign Shield Ability now?" pop-up for old configs // TODO: 2.3: Remove
+static void M_ShieldButtonResponse(INT32 ch)
+{
+	if (I_GetTime() <= shieldbuttonprompt) // Don't mash past the pop-up by accident!
+	{
+		stopstopmessage = true;
+		return;
+	}
+
+	S_StartSound(NULL, sfx_menu1);
+	noFurtherInput = true;
+	shieldbuttonprompt = 0;
+
+	if (ch == 'y' || ch == KEY_ENTER)
+	{
+		stopstopmessage = true; // Stop MessageDef from calling M_SetupNextMenu automatically
+		OP_ChangeControlsDef.lastOn = 8; // Highlight Shield Ability in the controls menu
+		M_Setup1PControlsMenu(ch); // Set up P1's controls menu and call M_SetupNextMenu
+	}
+}
+
 //
 // M_StartControlPanel
 //
@@ -3648,6 +3674,17 @@ void M_StartControlPanel(void)
 
 		currentMenu = &MainDef;
 		itemOn = singleplr;
+
+		if (shieldbuttonprompt) // For old configs, show a pop-up about the new Shield button // TODO: 2.3: Remove
+		{
+			S_StartSound(NULL, sfx_menu1);
+			shieldbuttonprompt = I_GetTime() + TICRATE; // Don't mash past the pop-up by accident!
+
+			M_StartMessage(M_GetText("Welcome back! Since you last played,\nSpin has been split into separate\n\"Spin\" and \"Shield Ability\" controls."
+			"\n\nDo you want to \x82""assign Shield Ability now\x80""?"
+			"\n\n\nPress \x82""ENTER\x80"" or the \x83""A button\x80"" to accept\nPress \x82""ESC\x80"" or the \x85""B button\x80"" to skip\n"),
+				M_ShieldButtonResponse, MM_YESNO);
+		}
 	}
 	else if (modeattacking)
 	{
diff --git a/src/m_menu.h b/src/m_menu.h
index 99a5b6de4266be02b1cba0e98a26addac5524548..ea130a6072741ca996939ee6be0ccbe5c9cd0e40 100644
--- a/src/m_menu.h
+++ b/src/m_menu.h
@@ -176,6 +176,7 @@ typedef struct
 extern menupres_t menupres[NUMMENUTYPES];
 extern UINT32 prevMenuId;
 extern UINT32 activeMenuId;
+extern tic_t shieldbuttonprompt; // Show a prompt about the new Shield button for old configs // TODO: 2.3: Remove
 
 void M_InitMenuPresTables(void);
 UINT8 M_GetYoungestChildMenu(void);
diff --git a/src/m_misc.c b/src/m_misc.c
index 55c5485a149a2b2c43e48fab973c5ad898091036..eebd896abb480e51b11edf7e0238976a8663bc0d 100644
--- a/src/m_misc.c
+++ b/src/m_misc.c
@@ -560,6 +560,11 @@ void M_FirstLoadConfig(void)
 	COM_BufInsertText(va("exec \"%s\"\n", configfile));
 	// no COM_BufExecute() needed; that does it right away
 
+	// For configs loaded at startup only, check for pre-Shield-button configs // TODO: 2.3: Remove
+	if (GETMAJOREXECVERSION(cv_execversion.value) < 55 // Pre-v2.2.14 configs
+	&& cv_execversion.value != 25) // Make sure that the config exists, too
+		shieldbuttonprompt = 1;
+
 	// don't filter anymore vars and don't let this convsvar be changed
 	COM_BufInsertText(va("%s \"%d\"\n", cv_execversion.name, EXECVERSION));
 	CV_ToggleExecVersion(false);