diff --git a/src/d_netcmd.c b/src/d_netcmd.c
index 55833d0daeb81c421f57e3bb3eae68b808e035f8..246b509c2c4d206dc54ba3f7d24488b4a1c3c0d6 100644
--- a/src/d_netcmd.c
+++ b/src/d_netcmd.c
@@ -1719,25 +1719,6 @@ void D_MapChange(INT32 mapnum, INT32 newgametype, boolean pultmode, boolean rese
 	}
 }
 
-/*
-Return the number of times a series of keywords, delimited by spaces, matched.
-*/
-static int measurekeywords(const char *s, const char *q)
-{
-	int r = 0;
-	char *qp;
-	for (qp = strtok(va("%s", q), " ");
-			qp;
-			qp = strtok(0, " "))
-	{
-		if (strcasestr(s, qp))
-		{
-			r++;
-		}
-	}
-	return r;
-}
-
 /*
 Easy macro; declare parm_*id* and define acceptableargc; put in the parameter
 to match as a string as *name*. Set *argn* to the number of extra arguments
@@ -1768,16 +1749,10 @@ static void Command_Map_f(void)
 	boolean usemapcode = false;
 
 	INT32 newmapnum;
-	INT32 apromapnum = 0;
 
 	const char *mapname;
 	size_t      mapnamelen;
 	char   *realmapname = NULL;
-	char   *apromapname = NULL;
-
-	/* Keyword matching */
-	UINT8 *freq;
-	UINT8 freqc;
 
 	INT32 newgametype = gametype;
 
@@ -1877,65 +1852,7 @@ static void Command_Map_f(void)
 		}
 		else
 		{
-			freq = ZZ_Calloc(NUMMAPS * sizeof (UINT8));
-
-			for (i = 0, newmapnum = 1; i < NUMMAPS; ++i, ++newmapnum)
-				if (mapheaderinfo[i])
-			{
-				if (!( realmapname = G_BuildMapTitle(newmapnum) ))
-					continue;
-
-				/* Now that we found a perfect match no need to fucking guess. */
-				if (strnicmp(realmapname, mapname, mapnamelen) == 0)
-				{
-					Z_Free(apromapname);
-					break;
-				}
-
-				if (apromapnum == 0)
-				{
-					/* LEVEL 1--match keywords verbatim */
-					if (strcasestr(realmapname, mapname))
-					{
-						apromapnum = newmapnum;
-						apromapname = realmapname;
-						realmapname = 0;
-					}
-					else/* ...match individual keywords */
-					{
-						freq[i] += measurekeywords(realmapname, mapname);
-						freq[i] += measurekeywords(mapheaderinfo[i]->keyword,
-								mapname);
-					}
-				}
-
-				Z_Free(realmapname);/* leftover old name */
-			}
-
-			if (newmapnum == NUMMAPS+1)/* no perfect match--try a substring */
-			{
-				newmapnum = apromapnum;
-				realmapname = apromapname;
-			}
-
-			if (newmapnum == 0)/* calculate most queries met! */
-			{
-				freqc = 0;
-				for (i = 0; i < NUMMAPS; ++i)
-				{
-					if (freq[i] > freqc)
-					{
-						freqc = freq[i];
-						newmapnum = i + 1;
-					}
-				}
-				if (newmapnum)
-				{
-					realmapname = G_BuildMapTitle(newmapnum);
-				}
-			}
-
-			Z_Free(freq);
+			newmapnum = G_FindMap(mapname, &realmapname, NULL, NULL);
 		}
 	}
 
diff --git a/src/g_game.c b/src/g_game.c
index e2f43e4f26e13948b94b2cbfbf32e7b3f69e76e5..bc67ed792f59b2087cdde114193a1ed56209bfac 100644
--- a/src/g_game.c
+++ b/src/g_game.c
@@ -4040,6 +4040,190 @@ 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);
+}
+
+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);
+				measurekeywords(&freq[freqc],
+						&freq[freqc].keywhd, &freq[freqc].keywhc,
+						mapheaderinfo[i]->keyword, 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 198cbc396ccb5d76b1596d3ef7d45b89ee755d98..3aaaf95d52073dc461352ed62800218a59b798ae 100644
--- a/src/g_game.h
+++ b/src/g_game.h
@@ -107,6 +107,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);