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