diff --git a/src/d_netcmd.c b/src/d_netcmd.c
index 7da0aa605ecb4f2100364d0fdcda18da6a0ff744..3fb204aeaf1fcee88aa7334e9d7dd76c1d3b7ed6 100644
--- a/src/d_netcmd.c
+++ b/src/d_netcmd.c
@@ -1736,88 +1736,297 @@ void D_MapChange(INT32 mapnum, INT32 newgametype, boolean pultmode, boolean rese
 	}
 }
 
+enum
+{
+	MAP_COMMAND_FORCE_OPTION,
+	MAP_COMMAND_GAMETYPE_OPTION,
+	MAP_COMMAND_NORESETPLAYERS_OPTION,
+
+	NUM_MAP_COMMAND_OPTIONS
+};
+
+static size_t CheckOptions(
+		int            num_options,
+		size_t       *first_argumentp,
+		size_t       *user_options,
+		const char ***option_names,
+		int          *option_num_arguments
+)
+{
+	int    arguments_used;
+	size_t first_argument;
+
+	int i;
+	const char **pp;
+	const char  *name;
+	size_t n;
+
+	arguments_used = 0;
+	first_argument = COM_Argc();
+
+	for (i = 0; i < num_options; ++i)
+	{
+		pp = option_names[i];
+		name = *pp;
+		do
+		{
+			if (( n = COM_CheckParm(name) ))
+			{
+				user_options[i] = n;
+				arguments_used += 1 + option_num_arguments[i];
+				if (n < first_argument)
+					first_argument = n;
+			}
+		}
+		while (( name = *++pp )) ;
+	}
+
+	(*first_argumentp) = first_argument;
+
+	return arguments_used;
+}
+
+static char *
+ConcatCommandArgv (int start, int end)
+{
+	char *final;
+
+	size_t size;
+
+	int i;
+	char *p;
+
+	size = 0;
+
+	for (i = start; i < end; ++i)
+	{
+		/*
+		one space after each argument, but terminating
+		character on final argument
+		*/
+		size += strlen(COM_Argv(i)) + 1;
+	}
+
+	final = ZZ_Alloc(size);
+	p = final;
+
+	--end;/* handle the final argument separately */
+	for (i = start; i < end; ++i)
+	{
+		p += sprintf(p, "%s ", COM_Argv(i));
+	}
+	/* at this point "end" is actually the last argument's position */
+	strcpy(p, COM_Argv(end));
+
+	return final;
+}
+
+//
 // Warp to map code.
 // Called either from map <mapname> console command, or idclev cheat.
 //
+// Largely rewritten by James.
+//
 static void Command_Map_f(void)
 {
-	const char *mapname;
-	size_t i;
-	INT32 newmapnum;
+	const char  *force_option_names[] =
+	{
+		"-force",
+		"-f",
+		NULL
+	};
+	const char  *gametype_option_names[] =
+	{
+		"-gametype",
+		"-g",
+		"-gt",
+		NULL
+	};
+	const char  *noresetplayers_option_names[] =
+	{
+		"-noresetplayers",
+		NULL
+	};
+	const char **option_names[] =
+	{
+		force_option_names,
+		gametype_option_names,
+		noresetplayers_option_names,
+	};
+	int         option_num_arguments[] =
+	{
+		0,/* -force */
+		1,/* -gametype */
+		0,/* -noresetplayers */
+	};
+
+	size_t acceptableargc;/* (this includes the command name itself!) */
+	size_t first_argument;
+
+	size_t      user_options         [NUM_MAP_COMMAND_OPTIONS] = {0};
+
+	const char *arg_gametype;
 	boolean newresetplayers;
+
+	boolean mustmodifygame;
+	boolean usemapcode = false;
+
+	INT32 newmapnum;
+
+	char   *    mapname;
+	size_t      mapnamelen;
+	char   *realmapname = NULL;
+
 	INT32 newgametype = gametype;
 
-	// max length of command: map map03 -gametype coop -noresetplayers -force
-	//                         1    2       3       4         5           6
-	// = 8 arg max
-	if (COM_Argc() < 2 || COM_Argc() > 8)
+	INT32 d;
+	char *p;
+
+	if (client && !IsPlayerAdmin(consoleplayer))
 	{
-		CONS_Printf(M_GetText("map <mapname> [-gametype <type> [-force]: warp to map\n"));
+		CONS_Printf(M_GetText("Only the server or a remote admin can use this.\n"));
 		return;
 	}
 
-	if (client && !IsPlayerAdmin(consoleplayer))
+	/* map name + options */
+	acceptableargc = 2 + CheckOptions(NUM_MAP_COMMAND_OPTIONS,
+			&first_argument,
+			user_options, option_names, option_num_arguments);
+
+	newresetplayers = !user_options[MAP_COMMAND_NORESETPLAYERS_OPTION];
+
+	mustmodifygame =
+		!( netgame     || multiplayer ) &&
+		(!modifiedgame || savemoddata );
+
+	if (mustmodifygame && !user_options[MAP_COMMAND_FORCE_OPTION])
 	{
-		CONS_Printf(M_GetText("Only the server or a remote admin can use this.\n"));
+		/* May want to be more descriptive? */
+		CONS_Printf(M_GetText("Sorry, level change disabled in single player.\n"));
 		return;
 	}
 
-	// internal wad lump always: map command doesn't support external files as in doom legacy
-	if (W_CheckNumForName(COM_Argv(1)) == LUMPERROR)
+	if (!newresetplayers && !cv_debug)
 	{
-		CONS_Alert(CONS_ERROR, M_GetText("Internal game level '%s' not found\n"), COM_Argv(1));
+		CONS_Printf(M_GetText("DEVMODE must be enabled.\n"));
 		return;
 	}
 
-	if (!(netgame || multiplayer) && (!modifiedgame || savemoddata))
+	if (user_options[MAP_COMMAND_GAMETYPE_OPTION] && !multiplayer)
 	{
-		if (COM_CheckParm("-force"))
-			G_SetGameModified(false);
-		else
+		CONS_Printf(M_GetText("You can't switch gametypes in single player!\n"));
+		return;
+	}
+
+	/* If the first argument is an option, you fucked up. */
+	if (COM_Argc() < acceptableargc || first_argument == 1)
+	{
+		/* I'm going over the fucking lines and I DON'T CAREEEEE */
+		CONS_Printf("map <name / [MAP]code / number> [-gametype <type>] [-force]:\n");
+		CONS_Printf(M_GetText(
+					"Warp to a map, by its name, two character code, with optional \"MAP\" prefix, or by its number (though why would you).\n"
+					"All parameters are case-insensitive.\n"
+					"* \"-force\" may be shortened to \"-f\".\n"
+					"* \"-gametype\" may be shortened to \"-g\" or \"-gt\".\n"));
+		return;
+	}
+
+	mapname = ConcatCommandArgv(1, first_argument);
+	mapnamelen = strlen(mapname);
+
+	if (mapnamelen == 2)/* maybe two digit code */
+	{
+		if (( newmapnum = M_MapNumber(mapname[0], mapname[1]) ))
+			usemapcode = true;
+	}
+	else if (mapnamelen == 5 && strnicmp(mapname, "MAP", 3) == 0)
+	{
+		if (( newmapnum = M_MapNumber(mapname[3], mapname[4]) ) == 0)
 		{
-			CONS_Printf(M_GetText("Sorry, level change disabled in single player.\n"));
+			CONS_Alert(CONS_ERROR, M_GetText("Invalid map code '%s'.\n"), mapname);
+			Z_Free(mapname);
 			return;
 		}
+		usemapcode = true;
 	}
 
-	newresetplayers = !COM_CheckParm("-noresetplayers");
+	if (!usemapcode)
+	{
+		/* Now detect map number in base 10, which no one asked for. */
+		newmapnum = strtol(mapname, &p, 10);
+		if (*p == '\0')/* we got it */
+		{
+			if (newmapnum < 1 || newmapnum > NUMMAPS)
+			{
+				CONS_Alert(CONS_ERROR, M_GetText("Invalid map number %d.\n"), newmapnum);
+				Z_Free(mapname);
+				return;
+			}
+			usemapcode = true;
+		}
+		else
+		{
+			newmapnum = G_FindMap(mapname, &realmapname, NULL, NULL);
+		}
+	}
 
-	if (!newresetplayers && !cv_debug)
+	if (newmapnum == 0 || !mapheaderinfo[newmapnum-1])
 	{
-		CONS_Printf(M_GetText("DEVMODE must be enabled.\n"));
+		CONS_Alert(CONS_ERROR, M_GetText("Could not find any map described as '%s'.\n"), mapname);
+		Z_Free(mapname);
 		return;
 	}
 
-	mapname = COM_Argv(1);
-	if (strlen(mapname) != 5
-	|| (newmapnum = M_MapNumber(mapname[3], mapname[4])) == 0)
+	if (usemapcode)
 	{
-		CONS_Alert(CONS_ERROR, M_GetText("Invalid level name %s\n"), mapname);
-		return;
+		realmapname = G_BuildMapTitle(newmapnum);
 	}
 
-	// Ultimate Mode only in SP via menu
-	if (netgame || multiplayer)
-		ultimatemode = false;
+	if (mustmodifygame && user_options[MAP_COMMAND_FORCE_OPTION])
+	{
+		G_SetGameModified(false);
+	}
 
 	// new gametype value
 	// use current one by default
-	i = COM_CheckParm("-gametype");
-	if (i)
+	if (user_options[MAP_COMMAND_GAMETYPE_OPTION])
 	{
-		if (!multiplayer)
-		{
-			CONS_Printf(M_GetText("You can't switch gametypes in single player!\n"));
-			return;
-		}
+		arg_gametype = COM_Argv(user_options[MAP_COMMAND_GAMETYPE_OPTION] + 1);
 
-		newgametype = G_GetGametypeByName(COM_Argv(i+1));
+		newgametype = G_GetGametypeByName(arg_gametype);
 
 		if (newgametype == -1) // reached end of the list with no match
 		{
-			INT32 j = atoi(COM_Argv(i+1)); // assume they gave us a gametype number, which is okay too
-			if (j >= 0 && j < NUMGAMETYPES)
-				newgametype = (INT16)j;
+			d = atoi(arg_gametype);
+			// assume they gave us a gametype number, which is okay too
+			if (d >= 0 && d < NUMGAMETYPES)
+				newgametype = d;
+		}
+	}
+
+	// don't use a gametype the map doesn't support
+	if (cv_debug || user_options[MAP_COMMAND_FORCE_OPTION] || cv_skipmapcheck.value)
+		fromlevelselect = false; // The player wants us to trek on anyway.  Do so.
+	// G_TOLFlag handles both multiplayer gametype and ignores it for !multiplayer
+	else
+	{
+		if (!(
+					mapheaderinfo[newmapnum-1] &&
+					mapheaderinfo[newmapnum-1]->typeoflevel & G_TOLFlag(newgametype)
+		))
+		{
+			CONS_Alert(CONS_WARNING, M_GetText("%s (%s) doesn't support %s mode!\n(Use -force to override)\n"), realmapname, G_BuildMapName(newmapnum),
+				(multiplayer ? gametype_cons_t[newgametype].strvalue : "Single Player"));
+			Z_Free(realmapname);
+			Z_Free(mapname);
+			return;
+		}
+		else
+		{
+			fromlevelselect =
+				( netgame || multiplayer ) &&
+				newgametype == gametype    &&
+				newgametype == GT_COOP;
 		}
 	}
 
@@ -1828,31 +2037,14 @@ static void Command_Map_f(void)
 	if (!dedicated && M_MapLocked(newmapnum))
 	{
 		CONS_Alert(CONS_NOTICE, M_GetText("You need to unlock this level before you can warp to it!\n"));
+		Z_Free(realmapname);
+		Z_Free(mapname);
 		return;
 	}
 
-	// don't use a gametype the map doesn't support
-	if (cv_debug || COM_CheckParm("-force") || cv_skipmapcheck.value)
-		fromlevelselect = false; // The player wants us to trek on anyway.  Do so.
-	// G_TOLFlag handles both multiplayer gametype and ignores it for !multiplayer
-	// Alternatively, bail if the map header is completely missing anyway.
-	else if (!mapheaderinfo[newmapnum-1]
-	 || !(mapheaderinfo[newmapnum-1]->typeoflevel & G_TOLFlag(newgametype)))
-	{
-		char gametypestring[32] = "Single Player";
-
-		if (multiplayer)
-		{
-			if (newgametype >= 0 && newgametype < NUMGAMETYPES
-			&& Gametype_Names[newgametype])
-				strcpy(gametypestring, Gametype_Names[newgametype]);
-		}
-
-		CONS_Alert(CONS_WARNING, M_GetText("%s doesn't support %s mode!\n(Use -force to override)\n"), mapname, gametypestring);
-		return;
-	}
-	else
-		fromlevelselect = ((netgame || multiplayer) && ((gametype == newgametype) && (newgametype == GT_COOP)));
+	// Ultimate Mode only in SP via menu
+	if (netgame || multiplayer)
+		ultimatemode = false;
 
 	if (tutorialmode && tutorialgcs)
 	{
@@ -1865,7 +2057,10 @@ static void Command_Map_f(void)
 	tutorialmode = false; // warping takes us out of tutorial mode
 
 	D_MapChange(newmapnum, newgametype, false, newresetplayers, 0, false, fromlevelselect);
+
+	Z_Free(realmapname);
 }
+#undef CHECKPARM
 
 /** Receives a map command and changes the map.
   *
diff --git a/src/doomdef.h b/src/doomdef.h
index 676c86e0d68b8c844c6edf042804f3a68589b048..b58d59259426dd5dea316e87572ca47e86d1ed1c 100644
--- a/src/doomdef.h
+++ b/src/doomdef.h
@@ -495,6 +495,7 @@ extern boolean capslock;
 
 // if we ever make our alloc stuff...
 #define ZZ_Alloc(x) Z_Malloc(x, PU_STATIC, NULL)
+#define ZZ_Calloc(x) Z_Calloc(x, PU_STATIC, NULL)
 
 // i_system.c, replace getchar() once the keyboard has been appropriated
 INT32 I_GetKey(void);
diff --git a/src/doomtype.h b/src/doomtype.h
index 5f60e21b51cc6aec62c1a3427a897c47d649816d..746463cd296e624602165ef1006f7b451e010bbc 100644
--- a/src/doomtype.h
+++ b/src/doomtype.h
@@ -115,6 +115,9 @@ typedef long ssize_t;
 	#define strnicmp(x,y,n) strncasecmp(x,y,n)
 #endif
 
+char *strcasestr(const char *in, const char *what);
+#define stristr strcasestr
+
 #if defined (macintosh) //|| defined (__APPLE__) //skip all boolean/Boolean crap
 	#define true 1
 	#define false 0
diff --git a/src/g_game.c b/src/g_game.c
index 18d0cdfe849027a034deae84f95c760294534e70..9677e81c9fe417e9e705a06fb29492e1d8122231 100644
--- a/src/g_game.c
+++ b/src/g_game.c
@@ -4047,6 +4047,187 @@ char *G_BuildMapTitle(INT32 mapnum)
 	return title;
 }
 
+static void measurekeywords(mapsearchfreq_t *fr,
+		struct searchdim **dimp, UINT8 *cuntp,
+		const char *s, const char *q, boolean wanttable)
+{
+	char *qp;
+	char *sp;
+	if (wanttable)
+		(*dimp) = Z_Realloc((*dimp), 255 * sizeof (struct searchdim),
+				PU_STATIC, NULL);
+	for (qp = strtok(va("%s", q), " ");
+			qp && fr->total < 255;
+			qp = strtok(0, " "))
+	{
+		if (( sp = strcasestr(s, qp) ))
+		{
+			if (wanttable)
+			{
+				(*dimp)[(*cuntp)].pos = sp - s;
+				(*dimp)[(*cuntp)].siz = strlen(qp);
+			}
+			(*cuntp)++;
+			fr->total++;
+		}
+	}
+	if (wanttable)
+		(*dimp) = Z_Realloc((*dimp), (*cuntp) * sizeof (struct searchdim),
+				PU_STATIC, NULL);
+}
+
+static void writesimplefreq(mapsearchfreq_t *fr, INT32 *frc,
+		INT32 mapnum, UINT8 pos, UINT8 siz)
+{
+	fr[(*frc)].mapnum = mapnum;
+	fr[(*frc)].matchd = ZZ_Alloc(sizeof (struct searchdim));
+	fr[(*frc)].matchd[0].pos = pos;
+	fr[(*frc)].matchd[0].siz = siz;
+	fr[(*frc)].matchc = 1;
+	fr[(*frc)].total = 1;
+	(*frc)++;
+}
+
+INT32 G_FindMap(const char *mapname, char **foundmapnamep,
+		mapsearchfreq_t **freqp, INT32 *freqcp)
+{
+	INT32 newmapnum = 0;
+	INT32 mapnum;
+	INT32 apromapnum = 0;
+
+	size_t      mapnamelen;
+	char   *realmapname = NULL;
+	char   *newmapname = NULL;
+	char   *apromapname = NULL;
+	char   *aprop = NULL;
+
+	mapsearchfreq_t *freq;
+	boolean wanttable;
+	INT32 freqc;
+	UINT8 frequ;
+
+	INT32 i;
+
+	mapnamelen = strlen(mapname);
+
+	/* Count available maps; how ugly. */
+	for (i = 0, freqc = 0; i < NUMMAPS; ++i)
+	{
+		if (mapheaderinfo[i])
+			freqc++;
+	}
+
+	freq = ZZ_Calloc(freqc * sizeof (mapsearchfreq_t));
+
+	wanttable = !!( freqp );
+
+	freqc = 0;
+	for (i = 0, mapnum = 1; i < NUMMAPS; ++i, ++mapnum)
+		if (mapheaderinfo[i])
+	{
+		if (!( realmapname = G_BuildMapTitle(mapnum) ))
+			continue;
+
+		aprop = realmapname;
+
+		/* Now that we found a perfect match no need to fucking guess. */
+		if (strnicmp(realmapname, mapname, mapnamelen) == 0)
+		{
+			if (wanttable)
+			{
+				writesimplefreq(freq, &freqc, mapnum, 0, mapnamelen);
+			}
+			if (newmapnum == 0)
+			{
+				newmapnum = mapnum;
+				newmapname = realmapname;
+				realmapname = 0;
+				Z_Free(apromapname);
+				if (!wanttable)
+					break;
+			}
+		}
+		else
+		if (apromapnum == 0 || wanttable)
+		{
+			/* LEVEL 1--match keywords verbatim */
+			if (( aprop = strcasestr(realmapname, mapname) ))
+			{
+				if (wanttable)
+				{
+					writesimplefreq(freq, &freqc,
+							mapnum, aprop - realmapname, mapnamelen);
+				}
+				if (apromapnum == 0)
+				{
+					apromapnum = mapnum;
+					apromapname = realmapname;
+					realmapname = 0;
+				}
+			}
+			else/* ...match individual keywords */
+			{
+				freq[freqc].mapnum = mapnum;
+				measurekeywords(&freq[freqc],
+						&freq[freqc].matchd, &freq[freqc].matchc,
+						realmapname, mapname, wanttable);
+				if (freq[freqc].total)
+					freqc++;
+			}
+		}
+
+		Z_Free(realmapname);/* leftover old name */
+	}
+
+	if (newmapnum == 0)/* no perfect match--try a substring */
+	{
+		newmapnum = apromapnum;
+		newmapname = apromapname;
+	}
+
+	if (newmapnum == 0)/* calculate most queries met! */
+	{
+		frequ = 0;
+		for (i = 0; i < freqc; ++i)
+		{
+			if (freq[i].total > frequ)
+			{
+				frequ = freq[i].total;
+				newmapnum = freq[i].mapnum;
+			}
+		}
+		if (newmapnum)
+		{
+			newmapname = G_BuildMapTitle(newmapnum);
+		}
+	}
+
+	if (freqp)
+		(*freqp) = freq;
+	else
+		Z_Free(freq);
+
+	if (freqcp)
+		(*freqcp) = freqc;
+
+	if (foundmapnamep)
+		(*foundmapnamep) = newmapname;
+	else
+		Z_Free(newmapname);
+
+	return newmapnum;
+}
+
+void G_FreeMapSearch(mapsearchfreq_t *freq, INT32 freqc)
+{
+	INT32 i;
+	for (i = 0; i < freqc; ++i)
+	{
+		Z_Free(freq[i].matchd);
+	}
+	Z_Free(freq);
+}
+
 //
 // DEMO RECORDING
 //
diff --git a/src/g_game.h b/src/g_game.h
index 22abae17dea2c66d116fc109fe08101161d3c646..2489ff09707a4a2717318320bd3eac47c213dfb7 100644
--- a/src/g_game.h
+++ b/src/g_game.h
@@ -108,6 +108,27 @@ void G_InitNew(UINT8 pultmode, const char *mapname, boolean resetplayer,
 	boolean skipprecutscene, boolean FLS);
 char *G_BuildMapTitle(INT32 mapnum);
 
+struct searchdim
+{
+	UINT8 pos;
+	UINT8 siz;
+};
+
+typedef struct
+{
+	INT16  mapnum;
+	UINT8  matchc;
+	struct searchdim *matchd;/* offset that a pattern was matched */
+	UINT8  keywhc;
+	struct searchdim *keywhd;/* ...in KEYWORD */
+	UINT8  total;/* total hits */
+}
+mapsearchfreq_t;
+
+INT32 G_FindMap(const char *query, char **foundmapnamep,
+		mapsearchfreq_t **freqp, INT32 *freqc);
+void G_FreeMapSearch(mapsearchfreq_t *freq, INT32 freqc);
+
 // XMOD spawning
 mapthing_t *G_FindCTFStart(INT32 playernum);
 mapthing_t *G_FindMatchStart(INT32 playernum);
diff --git a/src/strcasestr.c b/src/strcasestr.c
new file mode 100644
index 0000000000000000000000000000000000000000..2077dc3ffa322e96c38c66536199cafebd8c0447
--- /dev/null
+++ b/src/strcasestr.c
@@ -0,0 +1,110 @@
+/*
+strcasestr -- case insensitive substring searching function.
+*/
+/*
+Copyright 2019 James R.
+All rights reserved.
+
+Redistribution and use in source forms, with or without modification, is
+permitted provided that the following condition is met:
+
+1. Redistributions of source code must retain the above copyright notice, this
+   condition and the following disclaimer.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+#define SWAP( a, b ) \
+(\
+		(a) ^= (b),\
+		(b) ^= (a),\
+		(a) ^= (b)\
+)
+
+static inline int
+trycmp (char **pp, char *cp,
+		const char *q, size_t qn)
+{
+	char *p;
+	p = (*pp);
+	if (strncasecmp(p, q, qn) == 0)
+		return 0;
+	(*pp) = strchr(&p[1], (*cp));
+	return 1;
+}
+
+static inline void
+swapp (char ***ppap, char ***ppbp, char **cpap, char **cpbp)
+{
+	SWAP(*(intptr_t *)ppap, *(intptr_t *)ppbp);
+	SWAP(*(intptr_t *)cpap, *(intptr_t *)cpbp);
+}
+
+char *
+strcasestr (const char *s, const char *q)
+{
+	size_t  qn;
+
+	char    uc;
+	char    lc;
+
+	char   *up;
+	char   *lp;
+
+	char **ppa;
+	char **ppb;
+
+	char  *cpa;
+	char  *cpb;
+
+	uc = toupper(*q);
+	lc = tolower(*q);
+
+	up = strchr(s, uc);
+	lp = strchr(s, lc);
+
+	if (!( (intptr_t)up|(intptr_t)lp ))
+		return 0;
+
+	if (!lp || up < lp)
+	{
+		ppa = &up;
+		ppb = &lp;
+
+		cpa = &uc;
+		cpb = &lc;
+	}
+	else
+	{
+		ppa = &lp;
+		ppb = &up;
+
+		cpa = &lc;
+		cpb = &uc;
+	}
+
+	qn = strlen(q);
+
+	for (;;)
+	{
+		if (trycmp(ppa, cpa, q, qn) == 0)
+			return (*ppa);
+
+		if (!( (intptr_t)up|(intptr_t)lp ))
+			break;
+
+		if (!(*ppa) || ( (*ppb) && (*ppb) < (*ppa) ))
+			swapp(&ppa, &ppb, &cpa, &cpb);
+	}
+
+	return 0;
+}
diff --git a/src/string.c b/src/string.c
index 2a03e87296711f437b9bf4e61a0217b3e37bcf81..c415e5245a85acf2d57940bc53f7ed4891d46671 100644
--- a/src/string.c
+++ b/src/string.c
@@ -2,6 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 2006      by Graue.
 // Copyright (C) 2006-2018 by Sonic Team Junior.
+// Copyright (C) 2019      by James R.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -50,3 +51,5 @@ size_t strlcpy(char *dst, const char *src, size_t siz)
 }
 
 #endif
+
+#include "strcasestr.c"