core/sm_stringutil.cpp
author David Anderson <dvander@alliedmods.net>
Sun Sep 30 02:16:37 2007 +0000 (2007-09-30)
changeset 1505 d063f3dee725
parent 1420 0591a57b25e7
child 1620 fd5c786ca8e5
permissions -rw-r--r--
fixed a bug where UTIL_ReplaceAll() did not work on a buffer size of 1 even if the other inputs were valid
     1 /**
     2  * vim: set ts=4 :
     3  * =============================================================================
     4  * SourceMod
     5  * Copyright (C) 2004-2007 AlliedModders LLC.  All rights reserved.
     6  * =============================================================================
     7  *
     8  * This program is free software; you can redistribute it and/or modify it under
     9  * the terms of the GNU General Public License, version 3.0, as published by the
    10  * Free Software Foundation.
    11  * 
    12  * This program is distributed in the hope that it will be useful, but WITHOUT
    13  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
    14  * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
    15  * details.
    16  *
    17  * You should have received a copy of the GNU General Public License along with
    18  * this program.  If not, see <http://www.gnu.org/licenses/>.
    19  *
    20  * As a special exception, AlliedModders LLC gives you permission to link the
    21  * code of this program (as well as its derivative works) to "Half-Life 2," the
    22  * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software
    23  * by the Valve Corporation.  You must obey the GNU General Public License in
    24  * all respects for all other code used.  Additionally, AlliedModders LLC grants
    25  * this exception to all derivative works.  AlliedModders LLC defines further
    26  * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007),
    27  * or <http://www.sourcemod.net/license.php>.
    28  *
    29  * Version: $Id$
    30  */
    31 
    32 #include <stdlib.h>
    33 #include <ctype.h>
    34 #include <stdarg.h>
    35 #include "sm_stringutil.h"
    36 #include "Logger.h"
    37 #include "PluginSys.h"
    38 #include "Translator.h"
    39 #include "PlayerManager.h"
    40 
    41 #define LADJUST			0x00000004		/* left adjustment */
    42 #define ZEROPAD			0x00000080		/* zero (as opposed to blank) pad */
    43 #define UPPERDIGITS		0x00000200		/* make alpha digits uppercase */
    44 #define to_digit(c)		((c) - '0')
    45 #define is_digit(c)		((unsigned)to_digit(c) <= 9)
    46 
    47 #define CHECK_ARGS(x) \
    48 	if ((arg+x) > args) { \
    49 		pCtx->ThrowNativeErrorEx(SP_ERROR_PARAM, "String formatted incorrectly - parameter %d (total %d)", arg, args); \
    50 		return 0; \
    51 	}
    52 
    53 size_t CorePlayerTranslate(int client, char *buffer, size_t maxlength, const char *phrase, void **params)
    54 {
    55 	Translation pTrans;
    56 	TransError err;
    57 
    58 	err = g_pCorePhrases->GetTranslation(phrase, g_Translator.GetClientLanguage(client), &pTrans);
    59 	if (err != Trans_Okay)
    60 	{
    61 		err = g_pCorePhrases->GetTranslation(phrase, g_Translator.GetServerLanguage(), &pTrans);
    62 		if (err != Trans_Okay && g_Translator.GetServerLanguage() != CORELANG_ENGLISH)
    63 		{
    64 			err = g_pCorePhrases->GetTranslation(phrase, CORELANG_ENGLISH, &pTrans);
    65 		}
    66 	}
    67 
    68 	if (err != Trans_Okay)
    69 	{
    70 		return UTIL_Format(buffer, maxlength, "%s", phrase);
    71 	}
    72 
    73 	return g_Translator.Translate(buffer, maxlength, params, &pTrans);
    74 }
    75 
    76 inline bool TryTranslation(CPlugin *pl, const char *key, unsigned int langid, unsigned int langcount, Translation *pTrans)
    77 {
    78 	TransError err = Trans_BadLanguage;
    79 	CPhraseFile *phrfl;
    80 
    81 	for (size_t i=0; i<langcount && err!=Trans_Okay; i++)
    82 	{
    83 		phrfl = g_Translator.GetFileByIndex(pl->GetLangFileByIndex(i));
    84 		err = phrfl->GetTranslation(key, langid, pTrans);
    85 	}
    86 
    87 	return (err == Trans_Okay) ? true : false;
    88 }
    89 
    90 size_t Translate(char *buffer, size_t maxlen, IPluginContext *pCtx, const char *key, cell_t target, const cell_t *params, int *arg, bool *error)
    91 {
    92 	unsigned int langid;
    93 	*error = false;
    94 	Translation pTrans;
    95 	CPlugin *pl = (CPlugin *)g_PluginSys.FindPluginByContext(pCtx->GetContext());
    96 	size_t langcount = pl->GetLangFileCount();
    97 	void *new_params[MAX_TRANSLATE_PARAMS];
    98 	unsigned int max_params = 0;
    99 
   100 try_serverlang:
   101 	if (target == LANG_SERVER)
   102 	{
   103 		langid = g_Translator.GetServerLanguage();
   104  	} else if ((target >= 1) && (target <= g_Players.GetMaxClients())) {
   105 		langid = g_Translator.GetClientLanguage(target);
   106 	} else {
   107 		pCtx->ThrowNativeErrorEx(SP_ERROR_PARAM, "Translation failed: invalid client index %d", target);
   108 		goto error_out;
   109 	}
   110 
   111 	if (!TryTranslation(pl, key, langid, langcount, &pTrans))
   112 	{
   113 		if (target != LANG_SERVER && langid != g_Translator.GetServerLanguage())
   114 		{
   115 			target = LANG_SERVER;
   116 			goto try_serverlang;
   117 		} else if (langid != LANGUAGE_ENGLISH) {
   118 			if (!TryTranslation(pl, key, LANGUAGE_ENGLISH, langcount, &pTrans))
   119 			{
   120 				pCtx->ThrowNativeErrorEx(SP_ERROR_PARAM, "Language phrase \"%s\" not found", key);
   121 				goto error_out;
   122 			}
   123 		} else {
   124 			pCtx->ThrowNativeErrorEx(SP_ERROR_PARAM, "Language phrase \"%s\" not found", key);
   125 			goto error_out;
   126 		}
   127 	}
   128 
   129 	max_params = pTrans.fmt_count;
   130 
   131 	if (max_params)
   132 	{
   133 		/* Check if we're going to over the limit */
   134 		if ((*arg) + (max_params - 1) > (size_t)params[0])
   135 		{
   136 			pCtx->ThrowNativeErrorEx(SP_ERROR_PARAMS_MAX, 
   137 				"Translation string formatted incorrectly - missing at least %d parameters", 
   138 				((*arg + (max_params - 1)) - params[0])
   139 				);
   140 			goto error_out;
   141 		}
   142 
   143 		/* Translate the parameters to raw pointers */
   144 		for (size_t i=0; i<max_params; i++)
   145 		{
   146 			pCtx->LocalToPhysAddr(params[*arg], reinterpret_cast<cell_t **>(&new_params[i]));
   147 			(*arg)++;
   148 		}
   149 	}
   150 
   151 	return g_Translator.Translate(buffer, maxlen, new_params, &pTrans);
   152 error_out:
   153 	*error = true;
   154 	return 0;
   155 }
   156 
   157 //:TODO: review this code before we choose a license
   158 
   159 void AddString(char **buf_p, size_t &maxlen, const char *string, int width, int prec)
   160 {
   161 	int size = 0;
   162 	char *buf;
   163 	static char nlstr[] = {'(','n','u','l','l',')','\0'};
   164 
   165 	buf = *buf_p;
   166 
   167 	if (string == NULL)
   168 	{
   169 		string = nlstr;
   170 		prec = -1;
   171 	}
   172 
   173 	if (prec >= 0)
   174 	{
   175 		for (size = 0; size < prec; size++) 
   176 		{
   177 			if (string[size] == '\0')
   178 			{
   179 				break;
   180 			}
   181 		}
   182 	} else {
   183 		while (string[size++]);
   184 		size--;
   185 	}
   186 
   187 	if (size > (int)maxlen)
   188 	{
   189 		size = maxlen;
   190 	}
   191 
   192 	maxlen -= size;
   193 	width -= size;
   194 
   195 	while (size--)
   196 	{
   197 		*buf++ = *string++;
   198 	}
   199 
   200 	while ((width-- > 0) && maxlen)
   201 	{
   202 		*buf++ = ' ';
   203 		maxlen--;
   204 	}
   205 
   206 	*buf_p = buf;
   207 }
   208 
   209 void AddFloat(char **buf_p, size_t &maxlen, double fval, int width, int prec)
   210 {
   211 	char text[32];
   212 	int digits;
   213 	double signedVal;
   214 	char *buf;
   215 	int val;
   216 
   217 	// get the sign
   218 	signedVal = fval;
   219 	if (fval < 0)
   220 	{
   221 		fval = -fval;
   222 	}
   223 
   224 	// write the float number
   225 	digits = 0;
   226 	val = (int)fval;
   227 	do
   228 	{
   229 		text[digits++] = '0' + val % 10;
   230 		val /= 10;
   231 	} while (val);
   232 
   233 	if (signedVal < 0)
   234 	{
   235 		text[digits++] = '-';
   236 	}
   237 
   238 	buf = *buf_p;
   239 
   240 	while ((digits < width) && maxlen)
   241 	{
   242 		*buf++ = ' ';
   243 		width--;
   244 		maxlen--;
   245 	}
   246 
   247 	while ((digits--) && maxlen)
   248 	{
   249 		*buf++ = text[digits];
   250 		maxlen--;
   251 	}
   252 
   253 	*buf_p = buf;
   254 
   255 	if (prec < 0)
   256 	{
   257 		prec = 6;
   258 	}
   259 	// write the fraction
   260 	digits = 0;
   261 	while (digits < prec)
   262 	{
   263 		fval -= (int)fval;
   264 		fval *= 10.0;
   265 		val = (int)fval;
   266 		text[digits++] = '0' + val % 10;
   267 	}
   268 
   269 	if ((digits > 0) && maxlen)
   270 	{
   271 		buf = *buf_p;
   272 		*buf++ = '.';
   273 		maxlen--;
   274 		for (prec = 0; maxlen && (prec < digits); prec++)
   275 		{
   276 			*buf++ = text[prec];
   277 			maxlen--;
   278 		}
   279 		*buf_p = buf;
   280 	}
   281 }
   282 
   283 void AddUInt(char **buf_p, size_t &maxlen, unsigned int val, int width, int flags)
   284 {
   285 	char text[32];
   286 	int digits;
   287 	char *buf;
   288 
   289 	digits = 0;
   290 	do
   291 	{
   292 		text[digits++] = '0' + val % 10;
   293 		val /= 10;
   294 	} while (val);
   295 
   296 	buf = *buf_p;
   297 
   298 	if (!(flags & LADJUST))
   299 	{
   300 		while (digits < width && maxlen)
   301 		{
   302 			*buf++ = (flags & ZEROPAD) ? '0' : ' ';
   303 			width--;
   304 			maxlen--;
   305 		}
   306 	}
   307 
   308 	while (digits-- && maxlen)
   309 	{
   310 		*buf++ = text[digits];
   311 		width--;
   312 		maxlen--;
   313 	}
   314 
   315 	if (flags & LADJUST)
   316 	{
   317 		while (width-- && maxlen)
   318 		{
   319 			*buf++ = (flags & ZEROPAD) ? '0' : ' ';
   320 			maxlen--;
   321 		}
   322 	}
   323 
   324 	*buf_p = buf;
   325 }
   326 
   327 void AddInt(char **buf_p, size_t &maxlen, int val, int width, int flags)
   328 {
   329 	char text[32];
   330 	int digits;
   331 	int signedVal;
   332 	char *buf;
   333 	unsigned int unsignedVal;
   334 
   335 	digits = 0;
   336 	signedVal = val;
   337 	if (val < 0)
   338 	{
   339 		/* we want the unsigned version */
   340 		unsignedVal = abs(val);
   341 	} else {
   342 		unsignedVal = val;
   343 	}
   344 	do
   345 	{
   346 		text[digits++] = '0' + unsignedVal % 10;
   347 		unsignedVal /= 10;
   348 	} while (unsignedVal);
   349 
   350 	if (signedVal < 0)
   351 	{
   352 		text[digits++] = '-';
   353 	}
   354 
   355 	buf = *buf_p;
   356 
   357 	if (!(flags & LADJUST))
   358 	{
   359 		while ((digits < width) && maxlen)
   360 		{
   361 			*buf++ = (flags & ZEROPAD) ? '0' : ' ';
   362 			width--;
   363 			maxlen--;
   364 		}
   365 	}
   366 
   367 	while (digits-- && maxlen)
   368 	{
   369 		*buf++ = text[digits];
   370 		width--;
   371 		maxlen--;
   372 	}
   373 
   374 	if (flags & LADJUST)
   375 	{
   376 		while (width-- && maxlen)
   377 		{
   378 			*buf++ = (flags & ZEROPAD) ? '0' : ' ';
   379 			maxlen--;
   380 		}
   381 	}
   382 
   383 	*buf_p = buf;
   384 }
   385 
   386 void AddHex(char **buf_p, size_t &maxlen, unsigned int val, int width, int flags)
   387 {
   388 	char text[32];
   389 	int digits;
   390 	char *buf;
   391 	char digit;
   392 	int hexadjust;
   393 
   394 	if (flags & UPPERDIGITS)
   395 	{
   396 		hexadjust = 'A' - '9' - 1;
   397 	} else {
   398 		hexadjust = 'a' - '9' - 1;
   399 	}
   400 
   401 	digits = 0;
   402 	do 
   403 	{
   404 		digit = ('0' + val % 16);
   405 		if (digit > '9')
   406 		{
   407 			digit += hexadjust;
   408 		}
   409 
   410 		text[digits++] = digit;
   411 		val /= 16;
   412 	} while(val);
   413 
   414 	buf = *buf_p;
   415 
   416 	if (!(flags & LADJUST))
   417 	{
   418 		while (digits < width && maxlen)
   419 		{
   420 			*buf++ = (flags & ZEROPAD) ? '0' : ' ';
   421 			width--;
   422 			maxlen--;
   423 		}
   424 	}
   425 
   426 	while (digits-- && maxlen)
   427 	{
   428 		*buf++ = text[digits];
   429 		width--;
   430 		maxlen--;
   431 	}
   432 
   433 	if (flags & LADJUST)
   434 	{
   435 		while (width-- && maxlen)
   436 		{
   437 			*buf++ = (flags & ZEROPAD) ? '0' : ' ';
   438 			maxlen--;
   439 		}
   440 	}
   441 
   442 	*buf_p = buf;
   443 }
   444 
   445 size_t gnprintf(char *buffer, size_t maxlen, const char *format, void **args)
   446 {
   447 	if (!buffer || !maxlen)
   448 	{
   449 		return 0;
   450 	}
   451 
   452 	int arg = 0;
   453 	char *buf_p;
   454 	char ch;
   455 	int flags;
   456 	int width;
   457 	int prec;
   458 	int n;
   459 	char sign;
   460 	const char *fmt;
   461 	size_t llen = maxlen - 1;
   462 
   463 	buf_p = buffer;
   464 	fmt = format;
   465 
   466 	while (true)
   467 	{
   468 		// run through the format string until we hit a '%' or '\0'
   469 		for (ch = *fmt; llen && ((ch = *fmt) != '\0') && (ch != '%'); fmt++)
   470 		{
   471 			*buf_p++ = ch;
   472 			llen--;
   473 		}
   474 		if ((ch == '\0') || (llen <= 0))
   475 		{
   476 			goto done;
   477 		}
   478 
   479 		// skip over the '%'
   480 		fmt++;
   481 
   482 		// reset formatting state
   483 		flags = 0;
   484 		width = 0;
   485 		prec = -1;
   486 		sign = '\0';
   487 
   488 rflag:
   489 		ch = *fmt++;
   490 reswitch:
   491 		switch(ch)
   492 		{
   493 		case '-':
   494 			{
   495 				flags |= LADJUST;
   496 				goto rflag;
   497 			}
   498 		case '.':
   499 			{
   500 				n = 0;
   501 				while(is_digit((ch = *fmt++)))
   502 				{
   503 					n = 10 * n + (ch - '0');
   504 				}
   505 				prec = (n < 0) ? -1 : n;
   506 				goto reswitch;
   507 			}
   508 		case '0':
   509 			{
   510 				flags |= ZEROPAD;
   511 				goto rflag;
   512 			}
   513 		case '1':
   514 		case '2':
   515 		case '3':
   516 		case '4':
   517 		case '5':
   518 		case '6':
   519 		case '7':
   520 		case '8':
   521 		case '9':
   522 			{
   523 				n = 0;
   524 				do
   525 				{
   526 					n = 10 * n + (ch - '0');
   527 					ch = *fmt++;
   528 				} while(is_digit(ch));
   529 				width = n;
   530 				goto reswitch;
   531 			}
   532 		case 'c':
   533 			{
   534 				if (!llen)
   535 				{
   536 					goto done;
   537 				}
   538 				char *c = (char *)args[arg];
   539 				*buf_p++ = *c;
   540 				llen--;
   541 				arg++;
   542 				break;
   543 			}
   544 		case 'd':
   545 		case 'i':
   546 			{
   547 				int *value = (int *)args[arg];
   548 				AddInt(&buf_p, llen, *value, width, flags);
   549 				arg++;
   550 				break;
   551 			}
   552 		case 'u':
   553 			{
   554 				unsigned int *value = (unsigned int *)args[arg];
   555 				AddUInt(&buf_p, llen, *value, width, flags);
   556 				arg++;
   557 				break;
   558 			}
   559 		case 'f':
   560 			{
   561 				float *value = (float *)args[arg];
   562 				AddFloat(&buf_p, llen, *value, width, prec);
   563 				arg++;
   564 				break;
   565 			}
   566 		case 's':
   567 			{
   568 				const char *str = (const char *)args[arg];
   569 				AddString(&buf_p, llen, str, width, prec);
   570 				arg++;
   571 				break;
   572 			}
   573 		case 'X':
   574 			{
   575 				unsigned int *value = (unsigned int *)args[arg];
   576 				flags |= UPPERDIGITS;
   577 				AddHex(&buf_p, llen, *value, width, flags);
   578 				arg++;
   579 				break;
   580 			}
   581 		case 'x':
   582 			{
   583 				unsigned int *value = (unsigned int *)args[arg];
   584 				AddHex(&buf_p, llen, *value, width, flags);
   585 				arg++;
   586 				break;
   587 			}
   588 		case '%':
   589 			{
   590 				if (!llen)
   591 				{
   592 					goto done;
   593 				}
   594 				*buf_p++ = ch;
   595 				llen--;
   596 				break;
   597 			}
   598 		case '\0':
   599 			{
   600 				if (!llen)
   601 				{
   602 					goto done;
   603 				}
   604 				*buf_p++ = '%';
   605 				llen--;
   606 				goto done;
   607 			}
   608 		default:
   609 			{
   610 				if (!llen)
   611 				{
   612 					goto done;
   613 				}
   614 				*buf_p++ = ch;
   615 				llen--;
   616 				break;
   617 			}
   618 		}
   619 	}
   620 
   621 done:
   622 	*buf_p = '\0';
   623 
   624 	return (maxlen - llen - 1);
   625 }
   626 
   627 size_t atcprintf(char *buffer, size_t maxlen, const char *format, IPluginContext *pCtx, const cell_t *params, int *param)
   628 {
   629 	if (!buffer || !maxlen)
   630 	{
   631 		return 0;
   632 	}
   633 
   634 	int arg;
   635 	int args = params[0];
   636 	char *buf_p;
   637 	char ch;
   638 	int flags;
   639 	int width;
   640 	int prec;
   641 	int n;
   642 	char sign;
   643 	const char *fmt;
   644 	size_t llen = maxlen - 1;
   645 
   646 	buf_p = buffer;
   647 	arg = *param;
   648 	fmt = format;
   649 
   650 	while (true)
   651 	{
   652 		// run through the format string until we hit a '%' or '\0'
   653 		for (ch = *fmt; llen && ((ch = *fmt) != '\0') && (ch != '%'); fmt++)
   654 		{
   655 			*buf_p++ = ch;
   656 			llen--;
   657 		}
   658 		if ((ch == '\0') || (llen <= 0))
   659 		{
   660 			goto done;
   661 		}
   662 
   663 		// skip over the '%'
   664 		fmt++;
   665 
   666 		// reset formatting state
   667 		flags = 0;
   668 		width = 0;
   669 		prec = -1;
   670 		sign = '\0';
   671 
   672 rflag:
   673 		ch = *fmt++;
   674 reswitch:
   675 		switch(ch)
   676 		{
   677 		case '-':
   678 			{
   679 				flags |= LADJUST;
   680 				goto rflag;
   681 			}
   682 		case '.':
   683 			{
   684 				n = 0;
   685 				while(is_digit((ch = *fmt++)))
   686 				{
   687 					n = 10 * n + (ch - '0');
   688 				}
   689 				prec = (n < 0) ? -1 : n;
   690 				goto reswitch;
   691 			}
   692 		case '0':
   693 			{
   694 				flags |= ZEROPAD;
   695 				goto rflag;
   696 			}
   697 		case '1':
   698 		case '2':
   699 		case '3':
   700 		case '4':
   701 		case '5':
   702 		case '6':
   703 		case '7':
   704 		case '8':
   705 		case '9':
   706 			{
   707 				n = 0;
   708 				do
   709 				{
   710 					n = 10 * n + (ch - '0');
   711 					ch = *fmt++;
   712 				} while(is_digit(ch));
   713 				width = n;
   714 				goto reswitch;
   715 			}
   716 		case 'c':
   717 			{
   718 				CHECK_ARGS(0);
   719 				if (!llen)
   720 				{
   721 					goto done;
   722 				}
   723 				char *c;
   724 				pCtx->LocalToString(params[arg], &c);
   725 				*buf_p++ = *c;
   726 				llen--;
   727 				arg++;
   728 				break;
   729 			}
   730 		case 'd':
   731 		case 'i':
   732 			{
   733 				CHECK_ARGS(0);
   734 				cell_t *value;
   735 				pCtx->LocalToPhysAddr(params[arg], &value);
   736 				AddInt(&buf_p, llen, static_cast<int>(*value), width, flags);
   737 				arg++;
   738 				break;
   739 			}
   740 		case 'u':
   741 			{
   742 				CHECK_ARGS(0);
   743 				cell_t *value;
   744 				pCtx->LocalToPhysAddr(params[arg], &value);
   745 				AddUInt(&buf_p, llen, static_cast<unsigned int>(*value), width, flags);
   746 				arg++;
   747 				break;
   748 			}
   749 		case 'f':
   750 			{
   751 				CHECK_ARGS(0);
   752 				cell_t *value;
   753 				pCtx->LocalToPhysAddr(params[arg], &value);
   754 				AddFloat(&buf_p, llen, sp_ctof(*value), width, prec);
   755 				arg++;
   756 				break;
   757 			}
   758 		case 'L':
   759 			{
   760 				CHECK_ARGS(0);
   761 				cell_t *value;
   762 				pCtx->LocalToPhysAddr(params[arg], &value);
   763 				char buffer[255];
   764 				if (*value)
   765 				{
   766 					CPlayer *player = g_Players.GetPlayerByIndex(*value);
   767 					if (!player || !player->IsConnected())
   768 					{
   769 						return pCtx->ThrowNativeError("Client index %d is invalid", *value);
   770 					}
   771 					const char *auth = player->GetAuthString();
   772 					if (!auth || auth[0] == '\0')
   773 					{
   774 						auth = "STEAM_ID_PENDING";
   775 					}
   776 					int userid = engine->GetPlayerUserId(player->GetEdict());
   777 					UTIL_Format(buffer, 
   778 						sizeof(buffer), 
   779 						"%s<%d><%s><>", 
   780 						player->GetName(),
   781 						userid,
   782 						auth);
   783 				} else {
   784 					UTIL_Format(buffer,
   785 						sizeof(buffer),
   786 						"Console<0><Console><Console>");
   787 				}
   788 				AddString(&buf_p, llen, buffer, width, prec);
   789 				arg++;
   790 				break;
   791 			}
   792 		case 'N':
   793 			{
   794 				CHECK_ARGS(0);
   795 				cell_t *value;
   796 				pCtx->LocalToPhysAddr(params[arg], &value);
   797 
   798 				const char *name = "Console";
   799 				if (*value)
   800 				{
   801 					CPlayer *player = g_Players.GetPlayerByIndex(*value);
   802 					if (!player || !player->IsConnected())
   803 					{
   804 						return pCtx->ThrowNativeError("Client index %d is invalid", *value);
   805 					}
   806 					name = player->GetName();
   807 				}
   808 				AddString(&buf_p, llen, name, width, prec);
   809 				arg++;
   810 				break;
   811 			}
   812 		case 's':
   813 			{
   814 				CHECK_ARGS(0);
   815 				char *str;
   816 				int err;
   817 				if ((err=pCtx->LocalToString(params[arg], &str)) != SP_ERROR_NONE)
   818 				{
   819 					pCtx->ThrowNativeErrorEx(err, "Could not deference string");
   820 					return 0;
   821 				}
   822 				AddString(&buf_p, llen, str, width, prec);
   823 				arg++;
   824 				break;
   825 			}
   826 		case 'T':
   827 			{
   828 				CHECK_ARGS(1);
   829 				char *key;
   830 				bool error;
   831 				size_t res;
   832 				cell_t *target;
   833 				pCtx->LocalToString(params[arg++], &key);
   834 				pCtx->LocalToPhysAddr(params[arg++], &target);
   835 				res = Translate(buf_p, llen, pCtx, key, *target, params, &arg, &error);
   836 				if (error)
   837 				{
   838 					return 0;
   839 				}
   840 				buf_p += res;
   841 				llen -= res;
   842 				break;
   843 			}
   844 		case 't':
   845 			{
   846 				CHECK_ARGS(0);
   847 				char *key;
   848 				bool error;
   849 				size_t res;
   850 				cell_t target = g_SourceMod.GetGlobalTarget();
   851 				pCtx->LocalToString(params[arg++], &key);
   852 				res = Translate(buf_p, llen, pCtx, key, target, params, &arg, &error);
   853 				if (error)
   854 				{
   855 					return 0;
   856 				}
   857 				buf_p += res;
   858 				llen -= res;
   859 				break;
   860 			}
   861 		case 'X':
   862 			{
   863 				CHECK_ARGS(0);
   864 				cell_t *value;
   865 				pCtx->LocalToPhysAddr(params[arg], &value);
   866 				flags |= UPPERDIGITS;
   867 				AddHex(&buf_p, llen, static_cast<unsigned int>(*value), width, flags);
   868 				arg++;
   869 				break;
   870 			}
   871 		case 'x':
   872 			{
   873 				CHECK_ARGS(0);
   874 				cell_t *value;
   875 				pCtx->LocalToPhysAddr(params[arg], &value);
   876 				AddHex(&buf_p, llen, static_cast<unsigned int>(*value), width, flags);
   877 				arg++;
   878 				break;
   879 			}
   880 		case '%':
   881 			{
   882 				if (!llen)
   883 				{
   884 					goto done;
   885 				}
   886 				*buf_p++ = ch;
   887 				llen--;
   888 				break;
   889 			}
   890 		case '\0':
   891 			{
   892 				if (!llen)
   893 				{
   894 					goto done;
   895 				}
   896 				*buf_p++ = '%';
   897 				llen--;
   898 				goto done;
   899 			}
   900 		default:
   901 			{
   902 				if (!llen)
   903 				{
   904 					goto done;
   905 				}
   906 				*buf_p++ = ch;
   907 				llen--;
   908 				break;
   909 			}
   910 		}
   911 	}
   912 
   913 done:
   914 	*buf_p = '\0';
   915 	*param = arg;
   916 	return (maxlen - llen - 1);
   917 }
   918 
   919 const char *stristr(const char *str, const char *substr)
   920 {
   921 	if (!*substr)
   922 	{
   923 		return ((char *)str);
   924 	}
   925 
   926 	char *needle = (char *)substr;
   927 	char *prevloc = (char *)str;
   928 	char *haystack = (char *)str;
   929 
   930 	while (*haystack)
   931 	{
   932 		if (tolower(*haystack) == tolower(*needle))
   933 		{
   934 			haystack++;
   935 			if (!*++needle)
   936 			{
   937 				return prevloc;
   938 			}
   939 		} else {
   940 			haystack = ++prevloc;
   941 			needle = (char *)substr;
   942 		}
   943 	}
   944 
   945 	return NULL;
   946 }
   947 
   948 unsigned int strncopy(char *dest, const char *src, size_t count)
   949 {
   950 	if (!count)
   951 	{
   952 		return 0;
   953 	}
   954 
   955 	char *start = dest;
   956 	while ((*src) && (--count))
   957 	{
   958 		*dest++ = *src++;
   959 	}
   960 	*dest = '\0';
   961 
   962 	return (dest - start);
   963 }
   964 
   965 size_t UTIL_Format(char *buffer, size_t maxlength, const char *fmt, ...)
   966 {
   967 	va_list ap;
   968 	va_start(ap, fmt);
   969 	size_t len = vsnprintf(buffer, maxlength, fmt, ap);
   970 	va_end(ap);
   971 
   972 	if (len >= maxlength)
   973 	{
   974 		buffer[maxlength - 1] = '\0';
   975 		return (maxlength - 1);
   976 	} else {
   977 		return len;
   978 	}
   979 }
   980 
   981 size_t UTIL_FormatArgs(char *buffer, size_t maxlength, const char *fmt, va_list ap)
   982 {
   983 	size_t len = vsnprintf(buffer, maxlength, fmt, ap);
   984 
   985 	if (len >= maxlength)
   986 	{
   987 		buffer[maxlength - 1] = '\0';
   988 		return (maxlength - 1);
   989 	} else {
   990 		return len;
   991 	}
   992 }
   993 
   994 char *sm_strdup(const char *str)
   995 {
   996 	char *ptr = new char[strlen(str)+1];
   997 	strcpy(ptr, str);
   998 	return ptr;
   999 }
  1000 
  1001 unsigned int UTIL_ReplaceAll(char *subject, size_t maxlength, const char *search, const char *replace)
  1002 {
  1003 	size_t searchLen = strlen(search);
  1004 	size_t replaceLen = strlen(replace);
  1005 
  1006 	char *ptr = subject;
  1007 	unsigned int total = 0;
  1008 	while ((ptr = UTIL_ReplaceEx(ptr, maxlength, search, searchLen, replace, replaceLen)) != NULL)
  1009 	{
  1010 		total++;
  1011 		if (*ptr == '\0')
  1012 		{
  1013 			break;
  1014 		}
  1015 	}
  1016 
  1017 	return total;
  1018 }
  1019 
  1020 /**
  1021  * NOTE: Do not edit this for the love of god unless you have 
  1022  * read the test cases and understand the code behind each one.
  1023  * While I don't guarantee there aren't mistakes, I do guarantee
  1024  * that plugins will end up relying on tiny idiosyncracies of this
  1025  * function, just like they did with AMX Mod X.
  1026  *
  1027  * There are explicitly more cases than the AMX Mod X version because
  1028  * we're not doing a blind copy.  Each case is specifically optimized
  1029  * for what needs to be done.  Even better, we don't have to error on
  1030  * bad buffer sizes.  Instead, this function will smartly cut off the 
  1031  * string in a way that pushes old data out.
  1032  */
  1033 char *UTIL_ReplaceEx(char *subject, size_t maxLen, const char *search, size_t searchLen, const char *replace, size_t replaceLen)
  1034 {
  1035 	char *ptr = subject;
  1036 	size_t browsed = 0;
  1037 	size_t textLen = strlen(subject);
  1038 
  1039 	/* It's not possible to search or replace */
  1040 	if (searchLen > textLen)
  1041 	{
  1042 		return NULL;
  1043 	}
  1044 
  1045 	/* Handle the case of one byte replacement.
  1046 	 * It's only valid in one case.
  1047 	 */
  1048 	if (maxLen == 1)
  1049 	{
  1050 		/* If the search matches and the replace length is 0, 
  1051 		 * we can just terminate the string and be done.
  1052 		 */
  1053 		if (strcmp(subject, search) == 0 && replaceLen == 0)
  1054 		{
  1055 			*subject = '\0';
  1056 			return subject;
  1057 		}
  1058 		else
  1059 		{
  1060 			return NULL;
  1061 		}
  1062 	}
  1063 
  1064 	/* Subtract one off the maxlength so we can include the null terminator */
  1065 	maxLen--;
  1066 
  1067 	while (*ptr != '\0' && (browsed <= textLen - searchLen))
  1068 	{
  1069 		/* See if we get a comparison */
  1070 		if (strncmp(ptr, search, searchLen) == 0)
  1071 		{
  1072 			if (replaceLen > searchLen)
  1073 			{
  1074 				/* First, see if we have enough space to do this operation */
  1075 				if (maxLen - textLen < replaceLen - searchLen)
  1076 				{
  1077 					/* First, see if the replacement length goes out of bounds. */
  1078 					if (browsed + replaceLen >= maxLen)
  1079 					{
  1080 						/* EXAMPLE CASE:
  1081 						 * Subject: AABBBCCC
  1082 						 * Buffer : 12 bytes
  1083 						 * Search : BBB
  1084 						 * Replace: DDDDDDDDDD
  1085 						 * OUTPUT : AADDDDDDDDD
  1086 						 * POSITION:           ^
  1087 						 */
  1088 						/* If it does, we'll just bound the length and do a strcpy. */
  1089 						replaceLen = maxLen - browsed;
  1090 						/* Note, we add one to the final result for the null terminator */
  1091 						strncopy(ptr, replace, replaceLen+1);
  1092 					} else {
  1093 						/* EXAMPLE CASE:
  1094 						 * Subject: AABBBCCC
  1095 						 * Buffer : 12 bytes
  1096 						 * Search : BBB
  1097 						 * Replace: DDDDDDD
  1098 						 * OUTPUT : AADDDDDDDCC
  1099 						 * POSITION:         ^
  1100 						 */
  1101 						/* We're going to have some bytes left over... */
  1102 						size_t origBytesToCopy = (textLen - (browsed + searchLen)) + 1;
  1103 						size_t realBytesToCopy = (maxLen - (browsed + replaceLen)) + 1;
  1104 						char *moveFrom = ptr + searchLen + (origBytesToCopy - realBytesToCopy);
  1105 						char *moveTo = ptr + replaceLen;
  1106 
  1107 						/* First, move our old data out of the way. */
  1108 						memmove(moveTo, moveFrom, realBytesToCopy);
  1109 
  1110 						/* Now, do our replacement. */
  1111 						memcpy(ptr, replace, replaceLen);
  1112 					}
  1113 				} else {
  1114 					/* EXAMPLE CASE:
  1115 					 * Subject: AABBBCCC
  1116 					 * Buffer : 12 bytes
  1117 					 * Search : BBB
  1118 					 * Replace: DDDD
  1119 					 * OUTPUT : AADDDDCCC
  1120 					 * POSITION:      ^
  1121 					 */
  1122 					/* Yes, we have enough space.  Do a normal move operation. */
  1123 					char *moveFrom = ptr + searchLen;
  1124 					char *moveTo = ptr + replaceLen;
  1125 
  1126 					/* First move our old data out of the way. */
  1127 					size_t bytesToCopy = (textLen - (browsed + searchLen)) + 1;
  1128 					memmove(moveTo, moveFrom, bytesToCopy);
  1129 
  1130 					/* Now do our replacement. */
  1131 					memcpy(ptr, replace, replaceLen);
  1132 				}
  1133 			} else if (replaceLen < searchLen) {
  1134 				/* EXAMPLE CASE:
  1135 				 * Subject: AABBBCCC
  1136 				 * Buffer : 12 bytes
  1137 				 * Search : BBB
  1138 				 * Replace: D
  1139 				 * OUTPUT : AADCCC
  1140 				 * POSOTION:   ^
  1141 				 */
  1142 				/* If the replacement does not grow the string length, we do not
  1143 				 * need to do any fancy checking at all.  Yay!
  1144 				 */
  1145 				char *moveFrom = ptr + searchLen;		/* Start after the search pointer */
  1146 				char *moveTo = ptr + replaceLen;		/* Copy to where the replacement ends */
  1147 
  1148 				/* Copy our replacement in, if any */
  1149 				if (replaceLen)
  1150 				{
  1151 					memcpy(ptr, replace, replaceLen);
  1152 				}
  1153 
  1154 				/* Figure out how many bytes to move down, including null terminator */
  1155 				size_t bytesToCopy = (textLen - (browsed + searchLen)) + 1;
  1156 
  1157 				/* Move the rest of the string down */
  1158 				memmove(moveTo, moveFrom, bytesToCopy);
  1159 			} else {
  1160 				/* EXAMPLE CASE:
  1161 				 * Subject: AABBBCCC
  1162 				 * Buffer : 12 bytes
  1163 				 * Search : BBB
  1164 				 * Replace: DDD
  1165 				 * OUTPUT : AADDDCCC
  1166 				 * POSITION:     ^
  1167 				 */
  1168 				/* We don't have to move anything around, just do a straight copy */
  1169 				memcpy(ptr, replace, replaceLen);
  1170 			}
  1171 
  1172 			return ptr + replaceLen;
  1173 		}
  1174 		ptr++;
  1175 		browsed++;
  1176 	}
  1177 
  1178 	return NULL;
  1179 }