diff --git a/src/deh_soc.c b/src/deh_soc.c
index df11a3e6df59ecdba4b7dc74078b071bce405b88..343beb3012676b93256af35c0bd57ff6ba7e9076 100644
--- a/src/deh_soc.c
+++ b/src/deh_soc.c
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2024 by Sonic Team Junior.
+// Copyright (C) 1999-2025 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -286,6 +286,7 @@ void readPlayer(MYFILE *f, INT32 num)
 				}
 				if (playertext)
 				{
+					// PLAYERTEXT is really weird, so this doesn't use deh_strlcpy.
 					strlcpy(description[num].notes, playertext, NOTE_SIZE);
 					strlcat(description[num].notes,
 						myhashfgets(playertext, NOTE_SIZE, f), NOTE_SIZE);
@@ -324,7 +325,8 @@ void readPlayer(MYFILE *f, INT32 num)
 			if (fastcmp(word, "PICNAME"))
 			{
 				SLOTFOUND
-				strncpy(description[num].picname, word2, sizeof(description[num].picname)-1);
+				deh_strlcpy(description[num].picname, word2, sizeof description[num].picname,
+					va("Character %d: picname", num));
 			}
 			else if (fastcmp(word, "DISPLAYNAME"))
 			{
@@ -345,7 +347,8 @@ void readPlayer(MYFILE *f, INT32 num)
 					cur = strchr(cur, '#');
 				}
 
-				strlcpy(description[num].displayname, stringvalue, sizeof description[num].displayname);
+				deh_strlcpy(description[num].displayname, stringvalue, sizeof description[num].displayname,
+					va("Character %d: displayname", num));
 			}
 			else if (fastcmp(word, "OPPOSITECOLOR") || fastcmp(word, "OPPOSITECOLOUR"))
 			{
@@ -355,7 +358,8 @@ void readPlayer(MYFILE *f, INT32 num)
 			else if (fastcmp(word, "NAMETAG") || fastcmp(word, "TAGNAME"))
 			{
 				SLOTFOUND
-				strncpy(description[num].nametag, word2, sizeof(description[num].nametag)-1);
+				deh_strlcpy(description[num].nametag, word2, sizeof description[num].nametag,
+					va("Character %d: nametag", num));
 			}
 			else if (fastcmp(word, "TAGTEXTCOLOR") || fastcmp(word, "TAGTEXTCOLOUR"))
 			{
@@ -387,7 +391,8 @@ void readPlayer(MYFILE *f, INT32 num)
 			{
 				// Send to free slot.
 				SLOTFOUND
-				strlcpy(description[num].skinname, word2, sizeof description[num].skinname);
+				deh_strlcpy(description[num].skinname, word2, sizeof description[num].skinname,
+					va("Character %d: skinname", num));
 				strlwr(description[num].skinname);
 			}
 			else if (!failure)
@@ -1196,6 +1201,7 @@ void readgametype(MYFILE *f, char *gtname)
 				}
 				if (descr)
 				{
+					// DESCRIPTION is really weird, so this doesn't use deh_strlcpy.
 					strlcpy(gtdescription, descr, sizeof (gtdescription));
 					strlcat(gtdescription,
 						myhashfgets(descr, sizeof (gtdescription), f),
@@ -1402,7 +1408,7 @@ void readlevelheader(MYFILE *f, INT32 num)
 			{
 				deh_strlcpy(mapheaderinfo[num-1]->lvlttl, word2,
 					sizeof(mapheaderinfo[num-1]->lvlttl), va("Level header %d: levelname", num));
-				strlcpy(mapheaderinfo[num-1]->selectheading, word2, sizeof(mapheaderinfo[num-1]->selectheading)); // not deh_ so only complains once
+				strlcpy(mapheaderinfo[num-1]->selectheading, word2, sizeof(mapheaderinfo[num-1]->selectheading)); // not deh_strlcpy so only complains once
 				continue;
 			}
 			// CHEAP HACK: move this over here for lowercase subtitles
@@ -1445,10 +1451,10 @@ void readlevelheader(MYFILE *f, INT32 num)
 				// Newly allocated
 				modoption = &mapheaderinfo[num-1]->customopts[j];
 
-				strncpy(modoption->option, word,  31);
-				modoption->option[31] = '\0';
-				strncpy(modoption->value,  word2, 255);
-				modoption->value[255] = '\0';
+				deh_strlcpy(modoption->option, word, sizeof(modoption->option),
+					va("Level header %d: custom option %d key", num, j));
+				deh_strlcpy(modoption->value, word2, sizeof(modoption->value),
+					va("Level header %d: custom option %d value", num, j));
 				continue;
 			}
 
@@ -1626,7 +1632,7 @@ void readlevelheader(MYFILE *f, INT32 num)
 			else if (fastcmp(word, "KEYWORDS"))
 			{
 				deh_strlcpy(mapheaderinfo[num-1]->keywords, word2,
-						sizeof(mapheaderinfo[num-1]->keywords), va("Level header %d: keywords", num));
+					sizeof(mapheaderinfo[num-1]->keywords), va("Level header %d: keywords", num));
 			}
 			else if (fastcmp(word, "MUSIC"))
 			{
@@ -1675,7 +1681,8 @@ void readlevelheader(MYFILE *f, INT32 num)
 			}
 			else if (fastcmp(word, "FORCECHARACTER"))
 			{
-				strlcpy(mapheaderinfo[num-1]->forcecharacter, word2, SKINNAMESIZE+1);
+				deh_strlcpy(mapheaderinfo[num-1]->forcecharacter, word2, sizeof mapheaderinfo[num-1]->forcecharacter,
+					va("Level header %d: forcecharacter", num));
 				strlwr(mapheaderinfo[num-1]->forcecharacter); // skin names are lowercase
 			}
 			else if (fastcmp(word, "WEATHER"))
@@ -1683,7 +1690,10 @@ void readlevelheader(MYFILE *f, INT32 num)
 			else if (fastcmp(word, "SKYNUM"))
 				mapheaderinfo[num-1]->skynum = (INT16)i;
 			else if (fastcmp(word, "INTERSCREEN"))
-				strncpy(mapheaderinfo[num-1]->interscreen, word2, sizeof(mapheaderinfo[num-1]->interscreen)-1);
+			{
+				deh_strlcpy(mapheaderinfo[num-1]->interscreen, word2, sizeof mapheaderinfo[num-1]->interscreen,
+					va("Level header %d: interscreen", num));
+			}
 			else if (fastcmp(word, "PRECUTSCENENUM"))
 				mapheaderinfo[num-1]->precutscenenum = (UINT8)i;
 			else if (fastcmp(word, "CUTSCENENUM"))
@@ -1985,14 +1995,17 @@ static void readcutscenescene(MYFILE *f, INT32 num, INT32 scenenum)
 				picid = (UINT8)atoi(word + 3);
 				if (picid > 8 || picid == 0)
 				{
-					deh_warning("CutSceneScene %d: unknown word '%s'", num, word);
+					deh_warning("Cutscene %d, scene %d: pic number %d out of range (1 - %d)",
+						num + 1, scenenum + 1, picid, 8);
 					continue;
 				}
 				--picid;
 
 				if (fastcmp(word+4, "NAME"))
 				{
-					strncpy(cutscenes[num]->scene[scenenum].picname[picid], word2, 8);
+					deh_strlcpy(cutscenes[num]->scene[scenenum].picname[picid], word2,
+						sizeof cutscenes[num]->scene[scenenum].picname[picid],
+						va("Cutscene %d, scene %d, pic %d: name", num + 1, scenenum + 1, picid + 1));
 				}
 				else if (fastcmp(word+4, "HIRES"))
 				{
@@ -2011,12 +2024,13 @@ static void readcutscenescene(MYFILE *f, INT32 num, INT32 scenenum)
 					cutscenes[num]->scene[scenenum].ycoord[picid] = usi;
 				}
 				else
-					deh_warning("CutSceneScene %d: unknown word '%s'", num, word);
+					deh_warning("Cutscene %d, scene %d: unknown word '%s'", num + 1, scenenum + 1, word);
 			}
 			else if (fastcmp(word, "MUSIC"))
 			{
-				strncpy(cutscenes[num]->scene[scenenum].musswitch, word2, 7);
-				cutscenes[num]->scene[scenenum].musswitch[6] = 0;
+				deh_strlcpy(cutscenes[num]->scene[scenenum].musswitch, word2,
+					sizeof cutscenes[num]->scene[scenenum].musswitch,
+					va("Cutscene %d, scene %d: music", num + 1, scenenum + 1));
 			}
 			else if (fastcmp(word, "MUSICTRACK"))
 			{
@@ -2051,7 +2065,7 @@ static void readcutscenescene(MYFILE *f, INT32 num, INT32 scenenum)
 				cutscenes[num]->scene[scenenum].fadecolor = (UINT8)i;
 			}
 			else
-				deh_warning("CutSceneScene %d: unknown word '%s'", num, word);
+				deh_warning("Cutscene %d, scene %d: unknown word '%s'", num + 1, scenenum + 1, word);
 		}
 	} while (!myfeof(f)); // finish when the line is empty
 
@@ -2109,11 +2123,10 @@ void readcutscene(MYFILE *f, INT32 num)
 					readcutscenescene(f, num, value - 1);
 				}
 				else
-					deh_warning("Scene number %d out of range (1 - 128)", value);
-
+					deh_warning("Cutscene %d: scene number %d out of range (1 - 128)", num + 1, value);
 			}
 			else
-				deh_warning("Cutscene %d: unknown word '%s', Scene <num> expected.", num, word);
+				deh_warning("Cutscene %d: unknown word '%s', Scene <num> expected.", num + 1, word);
 		}
 	} while (!myfeof(f)); // finish when the line is empty
 
@@ -2234,7 +2247,8 @@ static void readtextpromptpage(MYFILE *f, INT32 num, INT32 pagenum)
 
 					for (picid = 0; picid < MAX_PROMPT_PICS; picid++)
 					{
-						strncpy(textprompts[num]->page[pagenum].picname[picid], textprompts[num]->page[metapagenum].picname[picid], 8);
+						// Doesn't use deh_strlcpy because it's not copying input.
+						strlcpy(textprompts[num]->page[pagenum].picname[picid], textprompts[num]->page[metapagenum].picname[picid], sizeof textprompts[num]->page[pagenum].picname[picid]);
 						textprompts[num]->page[pagenum].pichires[picid] = textprompts[num]->page[metapagenum].pichires[picid];
 						textprompts[num]->page[pagenum].picduration[picid] = textprompts[num]->page[metapagenum].picduration[picid];
 						textprompts[num]->page[pagenum].xcoord[picid] = textprompts[num]->page[metapagenum].xcoord[picid];
@@ -2247,14 +2261,17 @@ static void readtextpromptpage(MYFILE *f, INT32 num, INT32 pagenum)
 				picid = (UINT8)atoi(word + 3);
 				if (picid > MAX_PROMPT_PICS || picid == 0)
 				{
-					deh_warning("textpromptscene %d: unknown word '%s'", num, word);
+					deh_warning("Text prompt %d, page %d: pic number %d out of range (1 - %d)",
+						num + 1, pagenum + 1, picid, MAX_PROMPT_PICS);
 					continue;
 				}
 				--picid;
 
 				if (fastcmp(word+4, "NAME"))
 				{
-					strncpy(textprompts[num]->page[pagenum].picname[picid], word2, 8);
+					deh_strlcpy(textprompts[num]->page[pagenum].picname[picid], word2,
+						sizeof textprompts[num]->page[pagenum].picname[picid],
+						va("Text prompt %d, page %d, pic %d: name", num + 1, pagenum + 1, picid + 1));
 				}
 				else if (fastcmp(word+4, "HIRES"))
 				{
@@ -2273,12 +2290,16 @@ static void readtextpromptpage(MYFILE *f, INT32 num, INT32 pagenum)
 					textprompts[num]->page[pagenum].ycoord[picid] = usi;
 				}
 				else
-					deh_warning("textpromptscene %d: unknown word '%s'", num, word);
+				{
+					deh_warning("Text prompt %d, page %d: unknown word '%s'",
+						num + 1, pagenum + 1, word);
+				}
 			}
 			else if (fastcmp(word, "MUSIC"))
 			{
-				strncpy(textprompts[num]->page[pagenum].musswitch, word2, 7);
-				textprompts[num]->page[pagenum].musswitch[6] = 0;
+				deh_strlcpy(textprompts[num]->page[pagenum].musswitch, word2,
+					sizeof textprompts[num]->page[pagenum].musswitch,
+					va("Text prompt %d, page %d: music", num + 1, pagenum + 1));
 			}
 			else if (fastcmp(word, "MUSICTRACK"))
 			{
@@ -2293,30 +2314,35 @@ static void readtextpromptpage(MYFILE *f, INT32 num, INT32 pagenum)
 			{
 				if (*word2 != '\0')
 				{
-					INT32 j;
+					size_t j;
 
 					// HACK: Add yellow control char now
 					// so the drawing function doesn't call it repeatedly
-					char name[34];
+					char name[32 + 2];
 					name[0] = '\x82'; // color yellow
-					name[1] = 0;
-					strncat(name, word2, 32);
-					name[33] = 0;
+
+					// So that we still get a warning.
+					deh_strlcpy(name + 1, word2, (sizeof(name)) - 1,
+						va("Text prompt %d, page %d: name", num + 1, pagenum + 1));
 
 					// Replace _ with ' '
-					for (j = 0; j < 32 && name[j]; j++)
+					for (j = 1; j < sizeof(name) && name[j]; j++)
 					{
 						if (name[j] == '_')
 							name[j] = ' ';
 					}
 
-					strncpy(textprompts[num]->page[pagenum].name, name, sizeof(textprompts[num]->page[pagenum].name));
+					strlcpy(textprompts[num]->page[pagenum].name, name, sizeof(textprompts[num]->page[pagenum].name));
 				}
 				else
 					*textprompts[num]->page[pagenum].name = '\0';
 			}
 			else if (fastcmp(word, "ICON"))
-				strncpy(textprompts[num]->page[pagenum].iconname, word2, 8);
+			{
+				deh_strlcpy(textprompts[num]->page[pagenum].iconname, word2,
+					sizeof textprompts[num]->page[pagenum].iconname,
+					va("Text prompt %d, page %d: icon", num + 1, pagenum + 1));
+			}
 			else if (fastcmp(word, "ICONALIGN"))
 				textprompts[num]->page[pagenum].rightside = (i || word2[0] == 'R');
 			else if (fastcmp(word, "ICONFLIP"))
@@ -2383,8 +2409,9 @@ static void readtextpromptpage(MYFILE *f, INT32 num, INT32 pagenum)
 				{
 					UINT8 metapagenum = usi - 1;
 
-					strncpy(textprompts[num]->page[pagenum].name, textprompts[num]->page[metapagenum].name, 32);
-					strncpy(textprompts[num]->page[pagenum].iconname, textprompts[num]->page[metapagenum].iconname, 8);
+					// Doesn't use deh_strlcpy because it's not copying input.
+					strlcpy(textprompts[num]->page[pagenum].name, textprompts[num]->page[metapagenum].name, sizeof textprompts[num]->page[pagenum].name);
+					strlcpy(textprompts[num]->page[pagenum].iconname, textprompts[num]->page[metapagenum].iconname, sizeof textprompts[num]->page[pagenum].iconname);
 					textprompts[num]->page[pagenum].rightside = textprompts[num]->page[metapagenum].rightside;
 					textprompts[num]->page[pagenum].iconflip = textprompts[num]->page[metapagenum].iconflip;
 					textprompts[num]->page[pagenum].lines = textprompts[num]->page[metapagenum].lines;
@@ -2399,17 +2426,25 @@ static void readtextpromptpage(MYFILE *f, INT32 num, INT32 pagenum)
 				}
 			}
 			else if (fastcmp(word, "TAG"))
-				strncpy(textprompts[num]->page[pagenum].tag, word2, 33);
+			{
+				deh_strlcpy(textprompts[num]->page[pagenum].tag, word2,
+					sizeof textprompts[num]->page[pagenum].tag,
+					va("Text prompt %d, page %d: tag", num + 1, pagenum + 1));
+			}
 			else if (fastcmp(word, "NEXTPROMPT"))
 				textprompts[num]->page[pagenum].nextprompt = usi;
 			else if (fastcmp(word, "NEXTPAGE"))
 				textprompts[num]->page[pagenum].nextpage = usi;
 			else if (fastcmp(word, "NEXTTAG"))
-				strncpy(textprompts[num]->page[pagenum].nexttag, word2, 33);
+			{
+				deh_strlcpy(textprompts[num]->page[pagenum].nexttag, word2,
+					sizeof textprompts[num]->page[pagenum].nexttag,
+					va("Text prompt %d, page %d: nexttag", num + 1, pagenum + 1));
+			}
 			else if (fastcmp(word, "TIMETONEXT"))
 				textprompts[num]->page[pagenum].timetonext = get_number(word2);
 			else
-				deh_warning("PromptPage %d: unknown word '%s'", num, word);
+				deh_warning("Text prompt %d, page %d: unknown word '%s'", num + 1, pagenum + 1, word);
 		}
 	} while (!myfeof(f)); // finish when the line is empty
 
@@ -2469,11 +2504,11 @@ void readtextprompt(MYFILE *f, INT32 num)
 					readtextpromptpage(f, num, value - 1);
 				}
 				else
-					deh_warning("Page number %d out of range (1 - %d)", value, MAX_PAGES);
+					deh_warning("Prompt %d: page number %d out of range (1 - %d)", num + 1, value, MAX_PAGES);
 
 			}
 			else
-				deh_warning("Prompt %d: unknown word '%s', Page <num> expected.", num, word);
+				deh_warning("Prompt %d: unknown word '%s', Page <num> expected.", num + 1, word);
 		}
 	} while (!myfeof(f)); // finish when the line is empty
 
@@ -2522,7 +2557,8 @@ void readmenu(MYFILE *f, INT32 num)
 
 			if (fastcmp(word, "BACKGROUNDNAME"))
 			{
-				strncpy(menupres[num].bgname, word2, 8);
+				deh_strlcpy(menupres[num].bgname, word2,
+					sizeof menupres[num].bgname, va("Menu %d: backgroundname", num));
 				titlechanged = true;
 			}
 			else if (fastcmp(word, "HIDEBACKGROUND"))
@@ -2565,7 +2601,8 @@ void readmenu(MYFILE *f, INT32 num)
 			}
 			else if (fastcmp(word, "TITLEPICSNAME"))
 			{
-				strncpy(menupres[num].ttname, word2, 9);
+				deh_strlcpy(menupres[num].ttname, word2,
+					sizeof menupres[num].ttname, va("Menu %d: titlepicsname", num));
 				titlechanged = true;
 			}
 			else if (fastcmp(word, "TITLEPICSX"))
@@ -2601,8 +2638,8 @@ void readmenu(MYFILE *f, INT32 num)
 			}
 			else if (fastcmp(word, "MUSIC"))
 			{
-				strncpy(menupres[num].musname, word2, 7);
-				menupres[num].musname[6] = 0;
+				deh_strlcpy(menupres[num].musname, word2,
+					sizeof menupres[num].musname, va("Menu %d: music", num));
 				titlechanged = true;
 			}
 			else if (fastcmp(word, "MUSICTRACK"))
@@ -3590,9 +3627,7 @@ void readmaincfg(MYFILE *f)
 					lumpnum_t lumpnum;
 					char newname[9];
 
-					strncpy(newname, word2, 8);
-
-					newname[8] = '\0';
+					deh_strlcpy(newname, word2, sizeof newname, va("Maincfg: execcfg"));
 
 					lumpnum = W_CheckNumForName(newname);
 
@@ -3800,7 +3835,7 @@ void readmaincfg(MYFILE *f)
 			}
 			else if (fastcmp(word, "TITLEPICSNAME"))
 			{
-				strncpy(ttname, word2, sizeof(ttname)-1);
+				deh_strlcpy(ttname, word2, sizeof ttname, va("Maincfg: titlepicsname"));
 				titlechanged = true;
 			}
 			else if (fastcmp(word, "TITLEPICSX"))
@@ -3910,7 +3945,7 @@ void readmaincfg(MYFILE *f)
 			}
 			else if (fastcmp(word, "CUSTOMVERSION"))
 			{
-				strlcpy(customversionstring, word2, sizeof (customversionstring));
+				deh_strlcpy(customversionstring, word2, sizeof customversionstring, va("Maincfg: customversion"));
 				//titlechanged = true;
 			}
 			else if (fastcmp(word, "BOOTMAP"))
diff --git a/src/doomstat.h b/src/doomstat.h
index 5246349deef6f859bbb6f34ff8f078b3c15fe500..4abc9d21a060bd6f42055a11801fcd3e6fca7a05 100644
--- a/src/doomstat.h
+++ b/src/doomstat.h
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2024 by Sonic Team Junior.
+// Copyright (C) 1999-2025 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -209,7 +209,7 @@ typedef struct
 	UINT8 picmode; // sequence mode after displaying last pic, 0 = persist, 1 = loop, 2 = destroy
 	UINT8 pictoloop; // if picmode == loop, which pic to loop to?
 	UINT8 pictostart; // initial pic number to show
-	char picname[MAX_PROMPT_PICS][8];
+	char picname[MAX_PROMPT_PICS][9];
 	UINT8 pichires[MAX_PROMPT_PICS];
 	UINT16 xcoord[MAX_PROMPT_PICS]; // gfx
 	UINT16 ycoord[MAX_PROMPT_PICS]; // gfx
@@ -221,7 +221,7 @@ typedef struct
 
 	char tag[33]; // page tag
 	char name[34]; // narrator name, extra char for color
-	char iconname[8]; // narrator icon lump
+	char iconname[9]; // narrator icon lump
 	boolean rightside; // narrator side, false = left, true = right
 	boolean iconflip; // narrator flip icon horizontally
 	UINT8 hidehud; // hide hud, 0 = show all, 1 = hide depending on prompt position (top/bottom), 2 = hide all
diff --git a/src/m_menu.h b/src/m_menu.h
index cfe811d0be536b82ec3aeec7f0ab920a55a3d6c1..58c55734185bc5a0b0556654e9d4bcd67ba2c898 100644
--- a/src/m_menu.h
+++ b/src/m_menu.h
@@ -3,7 +3,7 @@
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
 // Copyright (C) 2011-2016 by Matthew "Kaito Sinclaire" Walsh.
-// Copyright (C) 1999-2024 by Sonic Team Junior.
+// Copyright (C) 1999-2025 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -143,7 +143,7 @@ typedef enum
 
 typedef struct
 {
-	char bgname[8]; // name for background gfx lump; lays over titlemap if this is set
+	char bgname[9]; // name for background gfx lump; lays over titlemap if this is set
 	SINT8 fadestrength;  // darken background when displaying this menu, strength 0-31 or -1 for undefined
 	INT32 bgcolor; // fill color, overrides bg name. -1 means follow bg name rules.
 	INT32 titlescrollxspeed; // background gfx scroll per menu; inherits global setting
@@ -371,7 +371,7 @@ typedef struct
 {
 	boolean used;
 	char notes[441];
-	char picname[8];
+	char picname[9];
 	char skinname[SKINNAMESIZE*2+2]; // skin&skin\0
 	patch_t *charpic;
 	UINT8 prev;
@@ -379,7 +379,7 @@ typedef struct
 	char displayname[SKINNAMESIZE+1];
 	INT16 skinnum[2];
 	UINT16 oppositecolor;
-	char nametag[8+1];
+	char nametag[9];
 	patch_t *namepic;
 	UINT16 tagtextcolor;
 	UINT16 tagoutlinecolor;