343 lines
		
	
	
		
			9.1 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			343 lines
		
	
	
		
			9.1 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /**
 | |
|  * vim: set ts=4 sw=4 tw=99 et :
 | |
|  * =============================================================================
 | |
|  * SourceMod
 | |
|  * Copyright (C) 2004-2009 AlliedModders LLC.  All rights reserved.
 | |
|  * =============================================================================
 | |
|  *
 | |
|  * This program is free software; you can redistribute it and/or modify it under
 | |
|  * the terms of the GNU General Public License, version 3.0, as published by the
 | |
|  * Free Software Foundation.
 | |
|  * 
 | |
|  * This program is distributed in the hope that it will be useful, but WITHOUT
 | |
|  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 | |
|  * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
 | |
|  * details.
 | |
|  *
 | |
|  * You should have received a copy of the GNU General Public License along with
 | |
|  * this program.  If not, see <http://www.gnu.org/licenses/>.
 | |
|  *
 | |
|  * As a special exception, AlliedModders LLC gives you permission to link the
 | |
|  * code of this program (as well as its derivative works) to "Half-Life 2," the
 | |
|  * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software
 | |
|  * by the Valve Corporation.  You must obey the GNU General Public License in
 | |
|  * all respects for all other code used.  Additionally, AlliedModders LLC grants
 | |
|  * this exception to all derivative works.  AlliedModders LLC defines further
 | |
|  * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007),
 | |
|  * or <http://www.sourcemod.net/license.php>.
 | |
|  *
 | |
|  * Version: $Id$
 | |
|  */
 | |
| 
 | |
| #include <string.h>
 | |
| #include <stdio.h>
 | |
| #include <ctype.h>
 | |
| #include <sm_platform.h>
 | |
| #include "stringutil.h"
 | |
| 
 | |
| // We're in logic so we don't have this from the SDK.
 | |
| #ifndef MIN
 | |
| #define MIN( a, b ) ( ( ( a ) < ( b ) ) ? ( a ) : ( b ) )
 | |
| #endif
 | |
| 
 | |
| const char *stristr(const char *str, const char *substr)
 | |
| {
 | |
| 	if (!*substr)
 | |
| 	{
 | |
| 		return ((char *)str);
 | |
| 	}
 | |
| 
 | |
| 	char *needle = (char *)substr;
 | |
| 	char *prevloc = (char *)str;
 | |
| 	char *haystack = (char *)str;
 | |
| 
 | |
| 	while (*haystack)
 | |
| 	{
 | |
| 		if (tolower(*haystack) == tolower(*needle))
 | |
| 		{
 | |
| 			haystack++;
 | |
| 			if (!*++needle)
 | |
| 			{
 | |
| 				return prevloc;
 | |
| 			}
 | |
| 		}
 | |
| 		else
 | |
| 		{
 | |
| 			haystack = ++prevloc;
 | |
| 			needle = (char *)substr;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return NULL;
 | |
| }
 | |
| 
 | |
| unsigned int strncopy(char *dest, const char *src, size_t count)
 | |
| {
 | |
| 	if (!count)
 | |
| 	{
 | |
| 		return 0;
 | |
| 	}
 | |
| 
 | |
| 	char *start = dest;
 | |
| 	while ((*src) && (--count))
 | |
| 	{
 | |
| 		*dest++ = *src++;
 | |
| 	}
 | |
| 	*dest = '\0';
 | |
| 
 | |
| 	return (dest - start);
 | |
| }
 | |
| 
 | |
| unsigned int UTIL_ReplaceAll(char *subject, size_t maxlength, const char *search, const char *replace, bool caseSensitive)
 | |
| {
 | |
| 	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, caseSensitive)) != 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 idiosyncrasies 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, bool caseSensitive)
 | |
| {
 | |
| 	char *ptr = subject;
 | |
| 	size_t browsed = 0;
 | |
| 	size_t textLen = strlen(subject);
 | |
| 
 | |
| 	/* It's not possible to search or replace */
 | |
| 	if (searchLen > textLen)
 | |
| 	{
 | |
| 		return NULL;
 | |
| 	}
 | |
| 
 | |
| 	/* Handle the case of one byte replacement.
 | |
| 	 * It's only valid in one case.
 | |
| 	 */
 | |
| 	if (maxLen == 1)
 | |
| 	{
 | |
| 		/* If the search matches and the replace length is 0, 
 | |
| 		 * we can just terminate the string and be done.
 | |
| 		 */
 | |
| 		if ((caseSensitive ? strcmp(subject, search) : strcasecmp(subject, search)) == 0 && replaceLen == 0)
 | |
| 		{
 | |
| 			*subject = '\0';
 | |
| 			return subject;
 | |
| 		}
 | |
| 		else
 | |
| 		{
 | |
| 			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 ((caseSensitive ? strncmp(ptr, search, searchLen) : strncasecmp(ptr, search, searchLen)) == 0)
 | |
| 		{
 | |
| 			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
 | |
| 				 * POSITION:   ^
 | |
| 				 */
 | |
| 				/* 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;
 | |
| }
 | |
| 
 | |
| size_t UTIL_DecodeHexString(unsigned char *buffer, size_t maxlength, const char *hexstr)
 | |
| {
 | |
| 	size_t written = 0;
 | |
| 	size_t length = strlen(hexstr);
 | |
| 
 | |
| 	for (size_t i = 0; i < length; i++)
 | |
| 	{
 | |
| 		if (written >= maxlength)
 | |
| 			break;
 | |
| 		buffer[written++] = hexstr[i];
 | |
| 		if (hexstr[i] == '\\' && hexstr[i + 1] == 'x')
 | |
| 		{
 | |
| 			if (i + 3 >= length)
 | |
| 				continue;
 | |
| 			/* Get the hex part. */
 | |
| 			char s_byte[3];
 | |
| 			int r_byte;
 | |
| 			s_byte[0] = hexstr[i + 2];
 | |
| 			s_byte[1] = hexstr[i + 3];
 | |
| 			s_byte[2] = '\0';
 | |
| 			/* Read it as an integer */
 | |
| 			sscanf(s_byte, "%x", &r_byte);
 | |
| 			/* Save the value */
 | |
| 			buffer[written - 1] = r_byte;
 | |
| 			/* Adjust index */
 | |
| 			i += 3;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return written;
 | |
| }
 | |
| 
 | |
| #define PATHSEPARATOR(c) ((c) == '\\' || (c) == '/')
 | |
| 
 | |
| void UTIL_StripExtension(const char *in, char *out, int outSize)
 | |
| {
 | |
| 	// Find the last dot. If it's followed by a dot or a slash, then it's part of a 
 | |
| 	// directory specifier like ../../somedir/./blah.
 | |
| 
 | |
| 	// scan backward for '.'
 | |
| 	int end = strlen(in) - 1;
 | |
| 	while (end > 0 && in[end] != '.' && !PATHSEPARATOR(in[end]))
 | |
| 	{
 | |
| 		--end;
 | |
| 	}
 | |
| 
 | |
| 	if (end > 0 && !PATHSEPARATOR(in[end]) && end < outSize)
 | |
| 	{
 | |
| 		int nChars = MIN(end, outSize-1);
 | |
| 		if (out != in)
 | |
| 		{
 | |
| 			memcpy(out, in, nChars);
 | |
| 		}
 | |
| 		out[nChars] = 0;
 | |
| 	}
 | |
| 	else
 | |
| 	{
 | |
| 		// nothing found
 | |
| 		if (out != in)
 | |
| 		{
 | |
| 			strncopy(out, in, outSize);
 | |
| 		}
 | |
| 	}
 | |
| }
 |