diff --git a/core/sm_stringutil.cpp b/core/sm_stringutil.cpp
index 8b6b8363..d68ec552 100644
--- a/core/sm_stringutil.cpp
+++ b/core/sm_stringutil.cpp
@@ -910,3 +910,165 @@ char *sm_strdup(const char *str)
 	strcpy(ptr, str);
 	return ptr;
 }
+
+unsigned int UTIL_ReplaceAll(char *subject, size_t maxlength, const char *search, const char *replace)
+{
+	size_t searchLen = strlen(search);
+	size_t replaceLen = strlen(replace);
+
+	char *ptr = subject;
+	unsigned int total = 0;
+	while ((ptr = UTIL_ReplaceEx(ptr, maxlength, search, searchLen, replace, replaceLen)) != NULL)
+	{
+		total++;
+		if (*ptr == '\0')
+		{
+			break;
+		}
+	}
+
+	return total;
+}
+
+/**
+ * NOTE: Do not edit this for the love of god unless you have 
+ * read the test cases and understand the code behind each one.
+ * While I don't guarantee there aren't mistakes, I do guarantee
+ * that plugins will end up relying on tiny idiosyncracies of this
+ * function, just like they did with AMX Mod X.
+ *
+ * There are explicitly more cases than the AMX Mod X version because
+ * we're not doing a blind copy.  Each case is specifically optimized
+ * for what needs to be done.  Even better, we don't have to error on
+ * bad buffer sizes.  Instead, this function will smartly cut off the 
+ * string in a way that pushes old data out.
+ */
+char *UTIL_ReplaceEx(char *subject, size_t maxLen, const char *search, size_t searchLen, const char *replace, size_t replaceLen)
+{
+	char *ptr = subject;
+	size_t browsed = 0;
+	size_t textLen = strlen(subject);
+
+	/* It's not possible to search or replace */
+	if (searchLen > textLen || maxLen <= 1)
+	{
+		return NULL;
+	}
+
+	/* Subtract one off the maxlength so we can include the null terminator */
+	maxLen--;
+
+	while (*ptr != '\0' && (browsed <= textLen - searchLen))
+	{
+		/* See if we get a comparison */
+		if (strncmp(ptr, search, searchLen) == 0)
+		{
+			char *retPtr = NULL;
+			if (replaceLen > searchLen)
+			{
+				/* First, see if we have enough space to do this operation */
+				if (maxLen - textLen < replaceLen - searchLen)
+				{
+					/* First, see if the replacement length goes out of bounds. */
+					if (browsed + replaceLen >= maxLen)
+					{
+						/* EXAMPLE CASE:
+						 * Subject: AABBBCCC
+						 * Buffer : 12 bytes
+						 * Search : BBB
+						 * Replace: DDDDDDDDDD
+						 * OUTPUT : AADDDDDDDDD
+						 * POSITION:           ^
+						 */
+						/* If it does, we'll just bound the length and do a strcpy. */
+						replaceLen = maxLen - browsed;
+						/* Note, we add one to the final result for the null terminator */
+						strncopy(ptr, replace, replaceLen+1);
+					} else {
+						/* EXAMPLE CASE:
+						 * Subject: AABBBCCC
+						 * Buffer : 12 bytes
+						 * Search : BBB
+						 * Replace: DDDDDDD
+						 * OUTPUT : AADDDDDDDCC
+						 * POSITION:         ^
+						 */
+						/* We're going to have some bytes left over... */
+						size_t origBytesToCopy = (textLen - (browsed + searchLen)) + 1;
+						size_t realBytesToCopy = (maxLen - (browsed + replaceLen)) + 1;
+						char *moveFrom = ptr + searchLen + (origBytesToCopy - realBytesToCopy);
+						char *moveTo = ptr + replaceLen;
+
+						/* First, move our old data out of the way. */
+						memmove(moveTo, moveFrom, realBytesToCopy);
+
+						/* Now, do our replacement. */
+						memcpy(ptr, replace, replaceLen);
+					}
+				} else {
+					/* EXAMPLE CASE:
+					 * Subject: AABBBCCC
+					 * Buffer : 12 bytes
+					 * Search : BBB
+					 * Replace: DDDD
+					 * OUTPUT : AADDDDCCC
+					 * POSITION:      ^
+					 */
+					/* Yes, we have enough space.  Do a normal move operation. */
+					char *moveFrom = ptr + searchLen;
+					char *moveTo = ptr + replaceLen;
+
+					/* First move our old data out of the way. */
+					size_t bytesToCopy = (textLen - (browsed + searchLen)) + 1;
+					memmove(moveTo, moveFrom, bytesToCopy);
+
+					/* Now do our replacement. */
+					memcpy(ptr, replace, replaceLen);
+				}
+			} else if (replaceLen < searchLen) {
+				/* EXAMPLE CASE:
+				 * Subject: AABBBCCC
+				 * Buffer : 12 bytes
+				 * Search : BBB
+				 * Replace: D
+				 * OUTPUT : AADCCC
+				 * POSOTION:   ^
+				 */
+				/* If the replacement does not grow the string length, we do not
+				 * need to do any fancy checking at all.  Yay!
+				 */
+				char *moveFrom = ptr + searchLen;		/* Start after the search pointer */
+				char *moveTo = ptr + replaceLen;		/* Copy to where the replacement ends */
+
+				/* Copy our replacement in, if any */
+				if (replaceLen)
+				{
+					memcpy(ptr, replace, replaceLen);
+				}
+
+				/* Figure out how many bytes to move down, including null terminator */
+				size_t bytesToCopy = (textLen - (browsed + searchLen)) + 1;
+
+				/* Move the rest of the string down */
+				memmove(moveTo, moveFrom, bytesToCopy);
+			} else {
+				/* EXAMPLE CASE:
+				 * Subject: AABBBCCC
+				 * Buffer : 12 bytes
+				 * Search : BBB
+				 * Replace: DDD
+				 * OUTPUT : AADDDCCC
+				 * POSITION:     ^
+				 */
+				/* We don't have to move anything around, just do a straight copy */
+				memcpy(ptr, replace, replaceLen);
+			}
+
+			return ptr + replaceLen;
+		}
+		ptr++;
+		browsed++;
+	}
+
+	return NULL;
+}
diff --git a/core/sm_stringutil.h b/core/sm_stringutil.h
index 77cfbac9..b6498fc0 100644
--- a/core/sm_stringutil.h
+++ b/core/sm_stringutil.h
@@ -33,5 +33,7 @@ size_t UTIL_Format(char *buffer, size_t maxlength, const char *fmt, ...);
 size_t UTIL_FormatArgs(char *buffer, size_t maxlength, const char *fmt, va_list ap);
 char *sm_strdup(const char *str);
 size_t CorePlayerTranslate(int client, char *buffer, size_t maxlength, const char *phrase, void **params);
+unsigned int UTIL_ReplaceAll(char *subject, size_t maxlength, const char *search, const char *replace);
+char *UTIL_ReplaceEx(char *subject, size_t maxLen, const char *search, size_t searchLen, const char *replace, size_t replaceLen);
 
 #endif // _INCLUDE_SOURCEMOD_STRINGUTIL_H_
diff --git a/core/smn_string.cpp b/core/smn_string.cpp
index c85e9666..6b03c308 100644
--- a/core/smn_string.cpp
+++ b/core/smn_string.cpp
@@ -407,6 +407,43 @@ static cell_t IsCharLower(IPluginContext *pContext, const cell_t *params)
 	return islower(chr);
 }
 
+static cell_t ReplaceString(IPluginContext *pContext, const cell_t *params)
+{
+	char *text, *search, *replace;
+	size_t maxlength;
+	
+	pContext->LocalToString(params[1], &text);
+	pContext->LocalToString(params[3], &search);
+	pContext->LocalToString(params[4], &replace);
+	maxlength = (size_t)params[2];
+
+
+	return  UTIL_ReplaceAll(text, maxlength, search, replace);
+}
+
+static cell_t ReplaceStringEx(IPluginContext *pContext, const cell_t *params)
+{
+	char *text, *search, *replace;
+	size_t maxlength;
+
+	pContext->LocalToString(params[1], &text);
+	pContext->LocalToString(params[3], &search);
+	pContext->LocalToString(params[4], &replace);
+	maxlength = (size_t)params[2];
+
+	size_t searchLen = (params[5] == -1) ? strlen(search) : (size_t)params[5];
+	size_t replaceLen = (params[6] == -1) ? strlen(replace) : (size_t)params[6];
+
+	char *ptr = UTIL_ReplaceEx(text, maxlength, search, searchLen, replace, replaceLen);
+
+	if (ptr == NULL)
+	{
+		return -1;
+	}
+
+	return ptr - text;
+}
+
 static cell_t TrimString(IPluginContext *pContext, const cell_t *params)
 {
 	char *str;
@@ -444,6 +481,10 @@ static cell_t TrimString(IPluginContext *pContext, const cell_t *params)
 
 REGISTER_NATIVES(basicStrings)
 {
+	{"BreakString",			BreakString},
+	{"FloatToString",		sm_floattostr},
+	{"Format",				sm_format},
+	{"FormatEx",			sm_formatex},
 	{"GetCharBytes",		GetCharBytes},
 	{"IntToString",			sm_numtostr},
 	{"IsCharAlpha",			IsCharAlpha},
@@ -452,9 +493,10 @@ REGISTER_NATIVES(basicStrings)
 	{"IsCharNumeric",		IsCharNumeric},
 	{"IsCharSpace",			IsCharSpace},
 	{"IsCharUpper",			IsCharUpper},
+	{"ReplaceString",		ReplaceString},
+	{"ReplaceStringEx",		ReplaceStringEx},
 	{"strlen",				sm_strlen},
 	{"StrBreak",			BreakString},		/* Backwards compat shim */
-	{"BreakString",			BreakString},
 	{"StrContains",			sm_contain},
 	{"strcmp",				sm_strcmp},
 	{"StrCompare",			sm_strcmp},			/* Backwards compat shim */
@@ -463,10 +505,7 @@ REGISTER_NATIVES(basicStrings)
 	{"StrCopy",				sm_strcopy},		/* Backwards compat shim */
 	{"StringToInt",			sm_strconvint},
 	{"StringToFloat",		sm_strtofloat},
-	{"FloatToString",		sm_floattostr},
-	{"Format",				sm_format},
-	{"FormatEx",			sm_formatex},
-	{"VFormat",				sm_vformat},
 	{"TrimString",			TrimString},
+	{"VFormat",				sm_vformat},
 	{NULL,					NULL},
 };
diff --git a/plugins/include/string.inc b/plugins/include/string.inc
index c405e8ce..dc62e309 100644
--- a/plugins/include/string.inc
+++ b/plugins/include/string.inc
@@ -216,6 +216,36 @@ stock StrBreak(const String:source[], String:arg[], argLen)
 	return BreakString(source, arg, argLen);
 }
 
+/**
+ * Given a string, replaces all occurrences of a search string with a 
+ * replacement string.
+ *
+ * @param text			String to perform search and replacements on.
+ * @param maxlength		Maximum length of the string buffer.
+ * @param search		String to search for.
+ * @param replace		String to replace the search string with.
+ * @return				Number of replacements that were performed.
+ */
+native ReplaceString(String:text[], maxlength, const String:search[], const String:replace[]);
+
+/**
+ * Given a string, replaces the first occurrence of a search string with a 
+ * replacement string.
+ *
+ * @param text			String to perform search and replacements on.
+ * @param maxlength		Maximum length of the string buffer.
+ * @param search		String to search for.
+ * @param replace		String to replace the search string with.
+ * @param searchLen		If higher than -1, its value will be used instead of
+ *						a strlen() call on the search parameter.
+ * @param replaceLen	If higher than -1, its value will be used instead of
+ *						a strlen() call on the replace parameter.
+ * @return				Index into the buffer (relative to the start) from where
+ *						the last replacement ended, or -1 if no replacements were
+ *						made.
+ */
+native ReplaceStringEx(String:text[], maxlength, const String:search[], const String:replace[], searchLen=-1, replaceLen=-1);
+
 /** 
  * Returns the number of bytes a character is using.  This is
  * for multi-byte characters (UTF-8).  For normal ASCII characters,