core/sm_stringutil.cpp
author David Anderson <dvander@alliedmods.net>
Sun Feb 08 15:40:48 2009 -0500 (2009-02-08)
changeset 2571 5c995a647735
parent 2515 09a125a571ff
child 2637 ed7b03e3bf16
permissions -rw-r--r--
Fixed documentation typos in dbi.inc (bug 3587).
     1 /**
     2  * vim: set ts=4 :
     3  * =============================================================================
     4  * SourceMod
     5  * Copyright (C) 2004-2008 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 inline void ReorderTranslationParams(const Translation *pTrans, cell_t *params)
    54 {
    55 	cell_t new_params[MAX_TRANSLATE_PARAMS];
    56 	for (unsigned int i = 0; i < pTrans->fmt_count; i++)
    57 	{
    58 		new_params[i] = params[pTrans->fmt_order[i]];
    59 	}
    60 	memcpy(params, new_params, pTrans->fmt_count * sizeof(cell_t));
    61 }
    62 
    63 size_t Translate(char *buffer,
    64 				 size_t maxlen,
    65 				 IPluginContext *pCtx,
    66 				 const char *key,
    67 				 cell_t target,
    68 				 const cell_t *params,
    69 				 int *arg,
    70 				 bool *error)
    71 {
    72 	unsigned int langid;
    73 	*error = false;
    74 	Translation pTrans;
    75 	CPlugin *pl = (CPlugin *)g_PluginSys.FindPluginByContext(pCtx->GetContext());
    76 	unsigned int max_params = 0;
    77 	IPhraseCollection *pPhrases;
    78 
    79 	pPhrases = pl->GetPhrases();
    80 
    81 try_serverlang:
    82 	if (target == SOURCEMOD_SERVER_LANGUAGE)
    83 	{
    84 		langid = g_Translator.GetServerLanguage();
    85  	}
    86 	else if ((target >= 1) && (target <= g_Players.GetMaxClients()))
    87 	{
    88 		langid = g_Translator.GetClientLanguage(target);
    89 	}
    90 	else
    91 	{
    92 		pCtx->ThrowNativeErrorEx(SP_ERROR_PARAM, "Translation failed: invalid client index %d", target);
    93 		goto error_out;
    94 	}
    95 
    96 	if (pPhrases->FindTranslation(key, langid, &pTrans) != Trans_Okay)
    97 	{
    98 		if (target != SOURCEMOD_SERVER_LANGUAGE && langid != g_Translator.GetServerLanguage())
    99 		{
   100 			target = SOURCEMOD_SERVER_LANGUAGE;
   101 			goto try_serverlang;
   102 		}
   103 		else if (langid != SOURCEMOD_LANGUAGE_ENGLISH)
   104 		{
   105 			if (pPhrases->FindTranslation(key, SOURCEMOD_LANGUAGE_ENGLISH, &pTrans) != Trans_Okay)
   106 			{
   107 				pCtx->ThrowNativeErrorEx(SP_ERROR_PARAM, "Language phrase \"%s\" not found", key);
   108 				goto error_out;
   109 			}
   110 		}
   111 		else
   112 		{
   113 			pCtx->ThrowNativeErrorEx(SP_ERROR_PARAM, "Language phrase \"%s\" not found", key);
   114 			goto error_out;
   115 		}
   116 	}
   117 
   118 	max_params = pTrans.fmt_count;
   119 
   120 	if (max_params)
   121 	{
   122 		cell_t new_params[MAX_TRANSLATE_PARAMS];
   123 
   124 		/* Check if we're going to over the limit */
   125 		if ((*arg) + (max_params - 1) > (size_t)params[0])
   126 		{
   127 			pCtx->ThrowNativeErrorEx(SP_ERROR_PARAMS_MAX, 
   128 				"Translation string formatted incorrectly - missing at least %d parameters", 
   129 				((*arg + (max_params - 1)) - params[0])
   130 				);
   131 			goto error_out;
   132 		}
   133 
   134 		/* If we need to re-order the parameters, do so with a temporary array.
   135 		 * Otherwise, we could run into trouble with continual formats, a la ShowActivity().
   136 		 */
   137 		memcpy(new_params, params, sizeof(cell_t) * (params[0] + 1));
   138 		ReorderTranslationParams(&pTrans, &new_params[*arg]);
   139 
   140 		return atcprintf(buffer, maxlen, pTrans.szPhrase, pCtx, new_params, arg);
   141 	}
   142 	else
   143 	{
   144 		return atcprintf(buffer, maxlen, pTrans.szPhrase, pCtx, params, arg);
   145 	}
   146 	
   147 error_out:
   148 	*error = true;
   149 	return 0;
   150 }
   151 
   152 void AddString(char **buf_p, size_t &maxlen, const char *string, int width, int prec)
   153 {
   154 	int size = 0;
   155 	char *buf;
   156 	static char nlstr[] = {'(','n','u','l','l',')','\0'};
   157 
   158 	buf = *buf_p;
   159 
   160 	if (string == NULL)
   161 	{
   162 		string = nlstr;
   163 		prec = -1;
   164 	}
   165 
   166 	if (prec >= 0)
   167 	{
   168 		for (size = 0; size < prec; size++) 
   169 		{
   170 			if (string[size] == '\0')
   171 			{
   172 				break;
   173 			}
   174 		}
   175 	}
   176 	else
   177 	{
   178 		while (string[size++]);
   179 		size--;
   180 	}
   181 
   182 	if (size > (int)maxlen)
   183 	{
   184 		size = maxlen;
   185 	}
   186 
   187 	maxlen -= size;
   188 	width -= size;
   189 
   190 	while (size--)
   191 	{
   192 		*buf++ = *string++;
   193 	}
   194 
   195 	while ((width-- > 0) && maxlen)
   196 	{
   197 		*buf++ = ' ';
   198 		maxlen--;
   199 	}
   200 
   201 	*buf_p = buf;
   202 }
   203 
   204 void AddFloat(char **buf_p, size_t &maxlen, double fval, int width, int prec, int flags)
   205 {
   206 	int digits;					// non-fraction part digits
   207 	double tmp;					// temporary
   208 	char *buf = *buf_p;			// output buffer pointer
   209 	int val;					// temporary
   210 	int sign = 0;				// 0: positive, 1: negative
   211 	int fieldlength;			// for padding
   212 	int significant_digits = 0;	// number of significant digits written
   213 	const int MAX_SIGNIFICANT_DIGITS = 16;
   214 
   215 	// default precision
   216 	if (prec < 0)
   217 	{
   218 		prec = 6;
   219 	}
   220 
   221 	// get the sign
   222 	if (fval < 0)
   223 	{
   224 		fval = -fval;
   225 		sign = 1;
   226 	}
   227 
   228 	// compute whole-part digits count
   229 	digits = (int)log10(fval) + 1;
   230 
   231 	// Only print 0.something if 0 < fval < 1
   232 	if (digits < 1)
   233 	{
   234 		digits = 1;
   235 	}
   236 
   237 	// compute the field length
   238 	fieldlength = digits + prec + ((prec > 0) ? 1 : 0) + sign;
   239 
   240 	// minus sign BEFORE left padding if padding with zeros
   241 	if (sign && maxlen && (flags & ZEROPAD))
   242 	{
   243 		*buf++ = '-';
   244 		maxlen--;
   245 	}
   246 
   247 	// right justify if required
   248 	if ((flags & LADJUST) == 0)
   249 	{
   250 		while ((fieldlength < width) && maxlen)
   251 		{
   252 			*buf++ = (flags & ZEROPAD) ? '0' : ' ';
   253 			width--;
   254 			maxlen--;
   255 		}
   256 	}
   257 
   258 	// minus sign AFTER left padding if padding with spaces
   259 	if (sign && maxlen && !(flags & ZEROPAD))
   260 	{
   261 		*buf++ = '-';
   262 		maxlen--;
   263 	}
   264 
   265 	// write the whole part
   266 	tmp = pow(10.0, digits-1);
   267 	while ((digits--) && maxlen)
   268 	{
   269 		if (++significant_digits > MAX_SIGNIFICANT_DIGITS)
   270 		{
   271 			*buf++ = '0';
   272 		}
   273 		else
   274 		{
   275 			val = (int)(fval / tmp);
   276 			*buf++ = '0' + val;
   277 			fval -= val * tmp;
   278 			tmp *= 0.1;
   279 		}
   280 		maxlen--;
   281 	}
   282 
   283 	// write the fraction part
   284 	if (maxlen && prec)
   285 	{
   286 		*buf++ = '.';
   287 		maxlen--;
   288 	}
   289 
   290 	tmp = pow(10.0, prec);
   291 
   292 	fval *= tmp;
   293 	while (prec-- && maxlen)
   294 	{
   295 		if (++significant_digits > MAX_SIGNIFICANT_DIGITS)
   296 		{
   297 			*buf++ = '0';
   298 		}
   299 		else
   300 		{
   301 			tmp *= 0.1;
   302 			val = (int)(fval / tmp);
   303 			*buf++ = '0' + val;
   304 			fval -= val * tmp;
   305 		}
   306 		maxlen--;
   307 	}
   308 
   309 	// left justify if required
   310 	if (flags & LADJUST)
   311 	{
   312 		while ((fieldlength < width) && maxlen)
   313 		{
   314 			// right-padding only with spaces, ZEROPAD is ignored
   315 			*buf++ = ' ';
   316 			width--;
   317 			maxlen--;
   318 		}
   319 	}
   320 
   321 	// update parent's buffer pointer
   322 	*buf_p = buf;
   323 }
   324 
   325 void AddBinary(char **buf_p, size_t &maxlen, unsigned int val, int width, int flags)
   326 {
   327 	char text[32];
   328 	int digits;
   329 	char *buf;
   330 
   331 	digits = 0;
   332 	do
   333 	{
   334 		if (val & 1)
   335 		{
   336 			text[digits++] = '1';
   337 		}
   338 		else
   339 		{
   340 			text[digits++] = '0';
   341 		}
   342 		val >>= 1;
   343 	} while (val);
   344 
   345 	buf = *buf_p;
   346 
   347 	if (!(flags & LADJUST))
   348 	{
   349 		while (digits < width && maxlen)
   350 		{
   351 			*buf++ = (flags & ZEROPAD) ? '0' : ' ';
   352 			width--;
   353 			maxlen--;
   354 		}
   355 	}
   356 
   357 	while (digits-- && maxlen)
   358 	{
   359 		*buf++ = text[digits];
   360 		width--;
   361 		maxlen--;
   362 	}
   363 
   364 	if (flags & LADJUST)
   365 	{
   366 		while (width-- && maxlen)
   367 		{
   368 			*buf++ = (flags & ZEROPAD) ? '0' : ' ';
   369 			maxlen--;
   370 		}
   371 	}
   372 
   373 	*buf_p = buf;
   374 }
   375 
   376 void AddUInt(char **buf_p, size_t &maxlen, unsigned int val, int width, int flags)
   377 {
   378 	char text[32];
   379 	int digits;
   380 	char *buf;
   381 
   382 	digits = 0;
   383 	do
   384 	{
   385 		text[digits++] = '0' + val % 10;
   386 		val /= 10;
   387 	} while (val);
   388 
   389 	buf = *buf_p;
   390 
   391 	if (!(flags & LADJUST))
   392 	{
   393 		while (digits < width && maxlen)
   394 		{
   395 			*buf++ = (flags & ZEROPAD) ? '0' : ' ';
   396 			width--;
   397 			maxlen--;
   398 		}
   399 	}
   400 
   401 	while (digits-- && maxlen)
   402 	{
   403 		*buf++ = text[digits];
   404 		width--;
   405 		maxlen--;
   406 	}
   407 
   408 	if (flags & LADJUST)
   409 	{
   410 		while (width-- && maxlen)
   411 		{
   412 			*buf++ = (flags & ZEROPAD) ? '0' : ' ';
   413 			maxlen--;
   414 		}
   415 	}
   416 
   417 	*buf_p = buf;
   418 }
   419 
   420 void AddInt(char **buf_p, size_t &maxlen, int val, int width, int flags)
   421 {
   422 	char text[32];
   423 	int digits;
   424 	int signedVal;
   425 	char *buf;
   426 	unsigned int unsignedVal;
   427 
   428 	digits = 0;
   429 	signedVal = val;
   430 	if (val < 0)
   431 	{
   432 		/* we want the unsigned version */
   433 		unsignedVal = abs(val);
   434 	}
   435 	else
   436 	{
   437 		unsignedVal = val;
   438 	}
   439 
   440 	do
   441 	{
   442 		text[digits++] = '0' + unsignedVal % 10;
   443 		unsignedVal /= 10;
   444 	} while (unsignedVal);
   445 
   446 	if (signedVal < 0)
   447 	{
   448 		text[digits++] = '-';
   449 	}
   450 
   451 	buf = *buf_p;
   452 
   453 	if (!(flags & LADJUST))
   454 	{
   455 		while ((digits < width) && maxlen)
   456 		{
   457 			*buf++ = (flags & ZEROPAD) ? '0' : ' ';
   458 			width--;
   459 			maxlen--;
   460 		}
   461 	}
   462 
   463 	while (digits-- && maxlen)
   464 	{
   465 		*buf++ = text[digits];
   466 		width--;
   467 		maxlen--;
   468 	}
   469 
   470 	if (flags & LADJUST)
   471 	{
   472 		while (width-- && maxlen)
   473 		{
   474 			*buf++ = (flags & ZEROPAD) ? '0' : ' ';
   475 			maxlen--;
   476 		}
   477 	}
   478 
   479 	*buf_p = buf;
   480 }
   481 
   482 void AddHex(char **buf_p, size_t &maxlen, unsigned int val, int width, int flags)
   483 {
   484 	char text[32];
   485 	int digits;
   486 	char *buf;
   487 	char digit;
   488 	int hexadjust;
   489 
   490 	if (flags & UPPERDIGITS)
   491 	{
   492 		hexadjust = 'A' - '9' - 1;
   493 	}
   494 	else
   495 	{
   496 		hexadjust = 'a' - '9' - 1;
   497 	}
   498 
   499 	digits = 0;
   500 	do 
   501 	{
   502 		digit = ('0' + val % 16);
   503 		if (digit > '9')
   504 		{
   505 			digit += hexadjust;
   506 		}
   507 
   508 		text[digits++] = digit;
   509 		val /= 16;
   510 	} while(val);
   511 
   512 	buf = *buf_p;
   513 
   514 	if (!(flags & LADJUST))
   515 	{
   516 		while (digits < width && maxlen)
   517 		{
   518 			*buf++ = (flags & ZEROPAD) ? '0' : ' ';
   519 			width--;
   520 			maxlen--;
   521 		}
   522 	}
   523 
   524 	while (digits-- && maxlen)
   525 	{
   526 		*buf++ = text[digits];
   527 		width--;
   528 		maxlen--;
   529 	}
   530 
   531 	if (flags & LADJUST)
   532 	{
   533 		while (width-- && maxlen)
   534 		{
   535 			*buf++ = (flags & ZEROPAD) ? '0' : ' ';
   536 			maxlen--;
   537 		}
   538 	}
   539 
   540 	*buf_p = buf;
   541 }
   542 
   543 bool gnprintf(char *buffer,
   544 			  size_t maxlen,
   545 			  const char *format,
   546 			  IPhraseCollection *pPhrases,
   547 			  void **params,
   548 			  unsigned int numparams,
   549 			  unsigned int &curparam,
   550 			  size_t *pOutLength,
   551 			  const char **pFailPhrase)
   552 {
   553 	if (!buffer || !maxlen)
   554 	{
   555 		if (pOutLength != NULL)
   556 		{
   557 			*pOutLength = 0;
   558 		}
   559 		return true;
   560 	}
   561 
   562 	if (numparams > MAX_TRANSLATE_PARAMS)
   563 	{
   564 		if (pFailPhrase != NULL)
   565 		{
   566 			*pFailPhrase = NULL;
   567 		}
   568 		return false;
   569 	}
   570 
   571 	int arg = 0;
   572 	char *buf_p;
   573 	char ch;
   574 	int flags;
   575 	int width;
   576 	int prec;
   577 	int n;
   578 	char sign;
   579 	const char *fmt;
   580 	size_t llen = maxlen - 1;
   581 
   582 	buf_p = buffer;
   583 	fmt = format;
   584 
   585 	while (true)
   586 	{
   587 		// run through the format string until we hit a '%' or '\0'
   588 		for (ch = *fmt; llen && ((ch = *fmt) != '\0') && (ch != '%'); fmt++)
   589 		{
   590 			*buf_p++ = ch;
   591 			llen--;
   592 		}
   593 		if ((ch == '\0') || (llen <= 0))
   594 		{
   595 			goto done;
   596 		}
   597 
   598 		// skip over the '%'
   599 		fmt++;
   600 
   601 		// reset formatting state
   602 		flags = 0;
   603 		width = 0;
   604 		prec = -1;
   605 		sign = '\0';
   606 
   607 rflag:
   608 		ch = *fmt++;
   609 reswitch:
   610 		switch(ch)
   611 		{
   612 		case '-':
   613 			{
   614 				flags |= LADJUST;
   615 				goto rflag;
   616 			}
   617 		case '.':
   618 			{
   619 				n = 0;
   620 				while(is_digit((ch = *fmt++)))
   621 				{
   622 					n = 10 * n + (ch - '0');
   623 				}
   624 				prec = (n < 0) ? -1 : n;
   625 				goto reswitch;
   626 			}
   627 		case '0':
   628 			{
   629 				flags |= ZEROPAD;
   630 				goto rflag;
   631 			}
   632 		case '1':
   633 		case '2':
   634 		case '3':
   635 		case '4':
   636 		case '5':
   637 		case '6':
   638 		case '7':
   639 		case '8':
   640 		case '9':
   641 			{
   642 				n = 0;
   643 				do
   644 				{
   645 					n = 10 * n + (ch - '0');
   646 					ch = *fmt++;
   647 				} while(is_digit(ch));
   648 				width = n;
   649 				goto reswitch;
   650 			}
   651 		case 'c':
   652 			{
   653 				if (!llen)
   654 				{
   655 					goto done;
   656 				}
   657 				if (curparam >= numparams)
   658 				{
   659 					if (pFailPhrase != NULL)
   660 					{
   661 						*pFailPhrase = NULL;
   662 					}
   663 					return false;
   664 				}
   665 				char *c = (char *)params[curparam];
   666 				curparam++;
   667 				*buf_p++ = *c;
   668 				llen--;
   669 				arg++;
   670 				break;
   671 			}
   672 		case 'b':
   673 			{
   674 				if (curparam >= numparams)
   675 				{
   676 					if (pFailPhrase != NULL)
   677 					{
   678 						*pFailPhrase = NULL;
   679 					}
   680 					return false;
   681 				}
   682 				int *value = (int *)params[curparam];
   683 				curparam++;
   684 				AddBinary(&buf_p, llen, *value, width, flags);
   685 				arg++;
   686 				break;
   687 			}
   688 		case 'd':
   689 		case 'i':
   690 			{
   691 				if (curparam >= numparams)
   692 				{
   693 					if (pFailPhrase != NULL)
   694 					{
   695 						*pFailPhrase = NULL;
   696 					}
   697 					return false;
   698 				}
   699 				int *value = (int *)params[curparam];
   700 				curparam++;
   701 				AddInt(&buf_p, llen, *value, width, flags);
   702 				arg++;
   703 				break;
   704 			}
   705 		case 'u':
   706 			{
   707 				if (curparam >= numparams)
   708 				{
   709 					if (pFailPhrase != NULL)
   710 					{
   711 						*pFailPhrase = NULL;
   712 					}
   713 					return false;
   714 				}
   715 				unsigned int *value = (unsigned int *)params[curparam];
   716 				curparam++;
   717 				AddUInt(&buf_p, llen, *value, width, flags);
   718 				arg++;
   719 				break;
   720 			}
   721 		case 'f':
   722 			{
   723 				if (curparam >= numparams)
   724 				{
   725 					if (pFailPhrase != NULL)
   726 					{
   727 						*pFailPhrase = NULL;
   728 					}
   729 					return false;
   730 				}
   731 				float *value = (float *)params[curparam];
   732 				curparam++;
   733 				AddFloat(&buf_p, llen, *value, width, prec, flags);
   734 				arg++;
   735 				break;
   736 			}
   737 		case 's':
   738 			{
   739 				if (curparam >= numparams)
   740 				{
   741 					if (pFailPhrase != NULL)
   742 					{
   743 						*pFailPhrase = NULL;
   744 					}
   745 					return false;
   746 				}
   747 				const char *str = (const char *)params[curparam];
   748 				curparam++;
   749 				AddString(&buf_p, llen, str, width, prec);
   750 				arg++;
   751 				break;
   752 			}
   753 		case 'T':
   754 		case 't':
   755 			{
   756 				int target;
   757 				const char *key;
   758 				size_t out_length;
   759 				Translation trans;
   760 				unsigned int lang_id;
   761 
   762 				if (curparam >= numparams)
   763 				{
   764 					if (pFailPhrase != NULL)
   765 					{
   766 						*pFailPhrase = NULL;
   767 					}
   768 					return false;
   769 				}
   770 				key = (const char *)(params[curparam]);
   771 				curparam++;
   772 
   773 				if (ch == 'T')
   774 				{
   775 					if (curparam >= numparams)
   776 					{
   777 						if (pFailPhrase != NULL)
   778 						{
   779 							*pFailPhrase = NULL;
   780 						}
   781 						return false;
   782 					}
   783 					target = *((int *)(params[curparam]));
   784 					curparam++;
   785 				}
   786 				else
   787 				{
   788 					target = g_Translator.GetGlobalTarget();
   789 				}
   790 
   791 try_again:
   792 				if (target == SOURCEMOD_SERVER_LANGUAGE)
   793 				{
   794 					lang_id = g_Translator.GetServerLanguage();
   795 				}
   796 				else if (target >= 1 && target <= g_Players.GetMaxClients())
   797 				{
   798 					lang_id = g_Translator.GetClientLanguage(target);
   799 				}
   800 				else
   801 				{
   802 					lang_id = g_Translator.GetServerLanguage();
   803 				}
   804 
   805 				if (pPhrases == NULL)
   806 				{
   807 					if (pFailPhrase != NULL)
   808 					{
   809 						*pFailPhrase = key;
   810 					}
   811 					return false;
   812 				}
   813 
   814 				if (pPhrases->FindTranslation(key, lang_id, &trans) != Trans_Okay)
   815 				{
   816 					if (target != SOURCEMOD_SERVER_LANGUAGE && lang_id != g_Translator.GetServerLanguage())
   817 					{
   818 						target = SOURCEMOD_SERVER_LANGUAGE;
   819 						goto try_again;
   820 					}
   821 					else if (lang_id != SOURCEMOD_LANGUAGE_ENGLISH)
   822 					{
   823 						if (pPhrases->FindTranslation(key, SOURCEMOD_LANGUAGE_ENGLISH, &trans) != Trans_Okay)
   824 						{
   825 							if (pFailPhrase != NULL)
   826 							{
   827 								*pFailPhrase = key;
   828 							}
   829 							return false;
   830 						}
   831 					}
   832 					else
   833 					{
   834 						if (pFailPhrase != NULL)
   835 						{
   836 							*pFailPhrase = key;
   837 						}
   838 						return false;
   839 					}
   840 				}
   841 
   842 				if (trans.fmt_count)
   843 				{
   844 					unsigned int i;
   845 					void *new_params[MAX_TRANSLATE_PARAMS];
   846 
   847 					if (curparam + trans.fmt_count > numparams)
   848 					{
   849 						if (pFailPhrase != NULL)
   850 						{
   851 							*pFailPhrase = NULL;
   852 						}
   853 						return false;
   854 					}
   855 
   856 					/* Copy the array and re-order the stack */
   857 					memcpy(new_params, params, sizeof(void *) * numparams);
   858 					for (i = 0; i < trans.fmt_count; i++)
   859 					{
   860 						new_params[i] = const_cast<void *>(params[curparam + trans.fmt_order[i]]);
   861 					}
   862 
   863 					if (!gnprintf(buf_p,
   864 							llen,
   865 							trans.szPhrase,
   866 							pPhrases,
   867 							new_params,
   868 							numparams,
   869 							curparam,
   870 							&out_length,
   871 							pFailPhrase))
   872 					{
   873 						return false;
   874 					}
   875 				}
   876 				else
   877 				{
   878 					if (!gnprintf(buf_p,
   879 						llen,
   880 						trans.szPhrase,
   881 						pPhrases,
   882 						params,
   883 						numparams,
   884 						curparam,
   885 						&out_length,
   886 						pFailPhrase))
   887 					{
   888 						return false;
   889 					}
   890 				}
   891 
   892 				buf_p += out_length;
   893 				llen -= out_length;
   894 
   895 				break;
   896 			}
   897 		case 'X':
   898 			{
   899 				if (curparam >= numparams)
   900 				{
   901 					if (pFailPhrase != NULL)
   902 					{
   903 						*pFailPhrase = NULL;
   904 					}
   905 					return false;
   906 				}
   907 				unsigned int *value = (unsigned int *)params[curparam];
   908 				curparam++;
   909 				flags |= UPPERDIGITS;
   910 				AddHex(&buf_p, llen, *value, width, flags);
   911 				arg++;
   912 				break;
   913 			}
   914 		case 'x':
   915 			{
   916 				if (curparam >= numparams)
   917 				{
   918 					if (pFailPhrase != NULL)
   919 					{
   920 						*pFailPhrase = NULL;
   921 					}
   922 					return false;
   923 				}
   924 				unsigned int *value = (unsigned int *)params[curparam];
   925 				curparam++;
   926 				AddHex(&buf_p, llen, *value, width, flags);
   927 				arg++;
   928 				break;
   929 			}
   930 		case '%':
   931 			{
   932 				if (!llen)
   933 				{
   934 					goto done;
   935 				}
   936 				*buf_p++ = ch;
   937 				llen--;
   938 				break;
   939 			}
   940 		case '\0':
   941 			{
   942 				if (!llen)
   943 				{
   944 					goto done;
   945 				}
   946 				*buf_p++ = '%';
   947 				llen--;
   948 				goto done;
   949 			}
   950 		default:
   951 			{
   952 				if (!llen)
   953 				{
   954 					goto done;
   955 				}
   956 				*buf_p++ = ch;
   957 				llen--;
   958 				break;
   959 			}
   960 		}
   961 	}
   962 
   963 done:
   964 	*buf_p = '\0';
   965 
   966 	if (pOutLength != NULL)
   967 	{
   968 		*pOutLength = (maxlen - llen - 1);
   969 	}
   970 
   971 	return true;
   972 }
   973 
   974 size_t atcprintf(char *buffer, size_t maxlen, const char *format, IPluginContext *pCtx, const cell_t *params, int *param)
   975 {
   976 	if (!buffer || !maxlen)
   977 	{
   978 		return 0;
   979 	}
   980 
   981 	int arg;
   982 	int args = params[0];
   983 	char *buf_p;
   984 	char ch;
   985 	int flags;
   986 	int width;
   987 	int prec;
   988 	int n;
   989 	char sign;
   990 	const char *fmt;
   991 	size_t llen = maxlen - 1;
   992 
   993 	buf_p = buffer;
   994 	arg = *param;
   995 	fmt = format;
   996 
   997 	while (true)
   998 	{
   999 		// run through the format string until we hit a '%' or '\0'
  1000 		for (ch = *fmt; llen && ((ch = *fmt) != '\0') && (ch != '%'); fmt++)
  1001 		{
  1002 			*buf_p++ = ch;
  1003 			llen--;
  1004 		}
  1005 		if ((ch == '\0') || (llen <= 0))
  1006 		{
  1007 			goto done;
  1008 		}
  1009 
  1010 		// skip over the '%'
  1011 		fmt++;
  1012 
  1013 		// reset formatting state
  1014 		flags = 0;
  1015 		width = 0;
  1016 		prec = -1;
  1017 		sign = '\0';
  1018 
  1019 rflag:
  1020 		ch = *fmt++;
  1021 reswitch:
  1022 		switch(ch)
  1023 		{
  1024 		case '-':
  1025 			{
  1026 				flags |= LADJUST;
  1027 				goto rflag;
  1028 			}
  1029 		case '.':
  1030 			{
  1031 				n = 0;
  1032 				while(is_digit((ch = *fmt++)))
  1033 				{
  1034 					n = 10 * n + (ch - '0');
  1035 				}
  1036 				prec = (n < 0) ? -1 : n;
  1037 				goto reswitch;
  1038 			}
  1039 		case '0':
  1040 			{
  1041 				flags |= ZEROPAD;
  1042 				goto rflag;
  1043 			}
  1044 		case '1':
  1045 		case '2':
  1046 		case '3':
  1047 		case '4':
  1048 		case '5':
  1049 		case '6':
  1050 		case '7':
  1051 		case '8':
  1052 		case '9':
  1053 			{
  1054 				n = 0;
  1055 				do
  1056 				{
  1057 					n = 10 * n + (ch - '0');
  1058 					ch = *fmt++;
  1059 				} while(is_digit(ch));
  1060 				width = n;
  1061 				goto reswitch;
  1062 			}
  1063 		case 'c':
  1064 			{
  1065 				CHECK_ARGS(0);
  1066 				if (!llen)
  1067 				{
  1068 					goto done;
  1069 				}
  1070 				char *c;
  1071 				pCtx->LocalToString(params[arg], &c);
  1072 				*buf_p++ = *c;
  1073 				llen--;
  1074 				arg++;
  1075 				break;
  1076 			}
  1077 		case 'b':
  1078 			{
  1079 				CHECK_ARGS(0);
  1080 				cell_t *value;
  1081 				pCtx->LocalToPhysAddr(params[arg], &value);
  1082 				AddBinary(&buf_p, llen, *value, width, flags);
  1083 				arg++;
  1084 				break;
  1085 			}
  1086 		case 'd':
  1087 		case 'i':
  1088 			{
  1089 				CHECK_ARGS(0);
  1090 				cell_t *value;
  1091 				pCtx->LocalToPhysAddr(params[arg], &value);
  1092 				AddInt(&buf_p, llen, static_cast<int>(*value), width, flags);
  1093 				arg++;
  1094 				break;
  1095 			}
  1096 		case 'u':
  1097 			{
  1098 				CHECK_ARGS(0);
  1099 				cell_t *value;
  1100 				pCtx->LocalToPhysAddr(params[arg], &value);
  1101 				AddUInt(&buf_p, llen, static_cast<unsigned int>(*value), width, flags);
  1102 				arg++;
  1103 				break;
  1104 			}
  1105 		case 'f':
  1106 			{
  1107 				CHECK_ARGS(0);
  1108 				cell_t *value;
  1109 				pCtx->LocalToPhysAddr(params[arg], &value);
  1110 				AddFloat(&buf_p, llen, sp_ctof(*value), width, prec, flags);
  1111 				arg++;
  1112 				break;
  1113 			}
  1114 		case 'L':
  1115 			{
  1116 				CHECK_ARGS(0);
  1117 				cell_t *value;
  1118 				pCtx->LocalToPhysAddr(params[arg], &value);
  1119 				char buffer[255];
  1120 				if (*value)
  1121 				{
  1122 					CPlayer *player = g_Players.GetPlayerByIndex(*value);
  1123 					if (!player || !player->IsConnected())
  1124 					{
  1125 						return pCtx->ThrowNativeError("Client index %d is invalid", *value);
  1126 					}
  1127 					const char *auth = player->GetAuthString();
  1128 					if (!auth || auth[0] == '\0')
  1129 					{
  1130 						auth = "STEAM_ID_PENDING";
  1131 					}
  1132 					int userid = engine->GetPlayerUserId(player->GetEdict());
  1133 					UTIL_Format(buffer, 
  1134 						sizeof(buffer), 
  1135 						"%s<%d><%s><>", 
  1136 						player->GetName(),
  1137 						userid,
  1138 						auth);
  1139 				}
  1140 				else
  1141 				{
  1142 					UTIL_Format(buffer,
  1143 						sizeof(buffer),
  1144 						"Console<0><Console><Console>");
  1145 				}
  1146 				AddString(&buf_p, llen, buffer, width, prec);
  1147 				arg++;
  1148 				break;
  1149 			}
  1150 		case 'N':
  1151 			{
  1152 				CHECK_ARGS(0);
  1153 				cell_t *value;
  1154 				pCtx->LocalToPhysAddr(params[arg], &value);
  1155 
  1156 				const char *name = "Console";
  1157 				if (*value)
  1158 				{
  1159 					CPlayer *player = g_Players.GetPlayerByIndex(*value);
  1160 					if (!player || !player->IsConnected())
  1161 					{
  1162 						return pCtx->ThrowNativeError("Client index %d is invalid", *value);
  1163 					}
  1164 					name = player->GetName();
  1165 				}
  1166 				AddString(&buf_p, llen, name, width, prec);
  1167 				arg++;
  1168 				break;
  1169 			}
  1170 		case 's':
  1171 			{
  1172 				CHECK_ARGS(0);
  1173 				char *str;
  1174 				int err;
  1175 				if ((err=pCtx->LocalToString(params[arg], &str)) != SP_ERROR_NONE)
  1176 				{
  1177 					pCtx->ThrowNativeErrorEx(err, "Could not deference string");
  1178 					return 0;
  1179 				}
  1180 				AddString(&buf_p, llen, str, width, prec);
  1181 				arg++;
  1182 				break;
  1183 			}
  1184 		case 'T':
  1185 			{
  1186 				CHECK_ARGS(1);
  1187 				char *key;
  1188 				bool error;
  1189 				size_t res;
  1190 				cell_t *target;
  1191 				pCtx->LocalToString(params[arg++], &key);
  1192 				pCtx->LocalToPhysAddr(params[arg++], &target);
  1193 				res = Translate(buf_p, llen, pCtx, key, *target, params, &arg, &error);
  1194 				if (error)
  1195 				{
  1196 					return 0;
  1197 				}
  1198 				buf_p += res;
  1199 				llen -= res;
  1200 				break;
  1201 			}
  1202 		case 't':
  1203 			{
  1204 				CHECK_ARGS(0);
  1205 				char *key;
  1206 				bool error;
  1207 				size_t res;
  1208 				cell_t target = g_SourceMod.GetGlobalTarget();
  1209 				pCtx->LocalToString(params[arg++], &key);
  1210 				res = Translate(buf_p, llen, pCtx, key, target, params, &arg, &error);
  1211 				if (error)
  1212 				{
  1213 					return 0;
  1214 				}
  1215 				buf_p += res;
  1216 				llen -= res;
  1217 				break;
  1218 			}
  1219 		case 'X':
  1220 			{
  1221 				CHECK_ARGS(0);
  1222 				cell_t *value;
  1223 				pCtx->LocalToPhysAddr(params[arg], &value);
  1224 				flags |= UPPERDIGITS;
  1225 				AddHex(&buf_p, llen, static_cast<unsigned int>(*value), width, flags);
  1226 				arg++;
  1227 				break;
  1228 			}
  1229 		case 'x':
  1230 			{
  1231 				CHECK_ARGS(0);
  1232 				cell_t *value;
  1233 				pCtx->LocalToPhysAddr(params[arg], &value);
  1234 				AddHex(&buf_p, llen, static_cast<unsigned int>(*value), width, flags);
  1235 				arg++;
  1236 				break;
  1237 			}
  1238 		case '%':
  1239 			{
  1240 				if (!llen)
  1241 				{
  1242 					goto done;
  1243 				}
  1244 				*buf_p++ = ch;
  1245 				llen--;
  1246 				break;
  1247 			}
  1248 		case '\0':
  1249 			{
  1250 				if (!llen)
  1251 				{
  1252 					goto done;
  1253 				}
  1254 				*buf_p++ = '%';
  1255 				llen--;
  1256 				goto done;
  1257 			}
  1258 		default:
  1259 			{
  1260 				if (!llen)
  1261 				{
  1262 					goto done;
  1263 				}
  1264 				*buf_p++ = ch;
  1265 				llen--;
  1266 				break;
  1267 			}
  1268 		}
  1269 	}
  1270 
  1271 done:
  1272 	*buf_p = '\0';
  1273 	*param = arg;
  1274 	return (maxlen - llen - 1);
  1275 }
  1276 
  1277 const char *stristr(const char *str, const char *substr)
  1278 {
  1279 	if (!*substr)
  1280 	{
  1281 		return ((char *)str);
  1282 	}
  1283 
  1284 	char *needle = (char *)substr;
  1285 	char *prevloc = (char *)str;
  1286 	char *haystack = (char *)str;
  1287 
  1288 	while (*haystack)
  1289 	{
  1290 		if (tolower(*haystack) == tolower(*needle))
  1291 		{
  1292 			haystack++;
  1293 			if (!*++needle)
  1294 			{
  1295 				return prevloc;
  1296 			}
  1297 		}
  1298 		else
  1299 		{
  1300 			haystack = ++prevloc;
  1301 			needle = (char *)substr;
  1302 		}
  1303 	}
  1304 
  1305 	return NULL;
  1306 }
  1307 
  1308 unsigned int strncopy(char *dest, const char *src, size_t count)
  1309 {
  1310 	if (!count)
  1311 	{
  1312 		return 0;
  1313 	}
  1314 
  1315 	char *start = dest;
  1316 	while ((*src) && (--count))
  1317 	{
  1318 		*dest++ = *src++;
  1319 	}
  1320 	*dest = '\0';
  1321 
  1322 	return (dest - start);
  1323 }
  1324 
  1325 size_t UTIL_Format(char *buffer, size_t maxlength, const char *fmt, ...)
  1326 {
  1327 	va_list ap;
  1328 	va_start(ap, fmt);
  1329 	size_t len = vsnprintf(buffer, maxlength, fmt, ap);
  1330 	va_end(ap);
  1331 
  1332 	if (len >= maxlength)
  1333 	{
  1334 		buffer[maxlength - 1] = '\0';
  1335 		return (maxlength - 1);
  1336 	}
  1337 	else
  1338 	{
  1339 		return len;
  1340 	}
  1341 }
  1342 
  1343 size_t UTIL_FormatArgs(char *buffer, size_t maxlength, const char *fmt, va_list ap)
  1344 {
  1345 	size_t len = vsnprintf(buffer, maxlength, fmt, ap);
  1346 
  1347 	if (len >= maxlength)
  1348 	{
  1349 		buffer[maxlength - 1] = '\0';
  1350 		return (maxlength - 1);
  1351 	}
  1352 	else
  1353 	{
  1354 		return len;
  1355 	}
  1356 }
  1357 
  1358 char *sm_strdup(const char *str)
  1359 {
  1360 	char *ptr = new char[strlen(str)+1];
  1361 	strcpy(ptr, str);
  1362 	return ptr;
  1363 }
  1364 
  1365 unsigned int UTIL_ReplaceAll(char *subject, size_t maxlength, const char *search, const char *replace)
  1366 {
  1367 	size_t searchLen = strlen(search);
  1368 	size_t replaceLen = strlen(replace);
  1369 
  1370 	char *ptr = subject;
  1371 	unsigned int total = 0;
  1372 	while ((ptr = UTIL_ReplaceEx(ptr, maxlength, search, searchLen, replace, replaceLen)) != NULL)
  1373 	{
  1374 		total++;
  1375 		if (*ptr == '\0')
  1376 		{
  1377 			break;
  1378 		}
  1379 	}
  1380 
  1381 	return total;
  1382 }
  1383 
  1384 /**
  1385  * NOTE: Do not edit this for the love of god unless you have 
  1386  * read the test cases and understand the code behind each one.
  1387  * While I don't guarantee there aren't mistakes, I do guarantee
  1388  * that plugins will end up relying on tiny idiosyncrasies of this
  1389  * function, just like they did with AMX Mod X.
  1390  *
  1391  * There are explicitly more cases than the AMX Mod X version because
  1392  * we're not doing a blind copy.  Each case is specifically optimized
  1393  * for what needs to be done.  Even better, we don't have to error on
  1394  * bad buffer sizes.  Instead, this function will smartly cut off the 
  1395  * string in a way that pushes old data out.
  1396  */
  1397 char *UTIL_ReplaceEx(char *subject, size_t maxLen, const char *search, size_t searchLen, const char *replace, size_t replaceLen)
  1398 {
  1399 	char *ptr = subject;
  1400 	size_t browsed = 0;
  1401 	size_t textLen = strlen(subject);
  1402 
  1403 	/* It's not possible to search or replace */
  1404 	if (searchLen > textLen)
  1405 	{
  1406 		return NULL;
  1407 	}
  1408 
  1409 	/* Handle the case of one byte replacement.
  1410 	 * It's only valid in one case.
  1411 	 */
  1412 	if (maxLen == 1)
  1413 	{
  1414 		/* If the search matches and the replace length is 0, 
  1415 		 * we can just terminate the string and be done.
  1416 		 */
  1417 		if (strcmp(subject, search) == 0 && replaceLen == 0)
  1418 		{
  1419 			*subject = '\0';
  1420 			return subject;
  1421 		}
  1422 		else
  1423 		{
  1424 			return NULL;
  1425 		}
  1426 	}
  1427 
  1428 	/* Subtract one off the maxlength so we can include the null terminator */
  1429 	maxLen--;
  1430 
  1431 	while (*ptr != '\0' && (browsed <= textLen - searchLen))
  1432 	{
  1433 		/* See if we get a comparison */
  1434 		if (strncmp(ptr, search, searchLen) == 0)
  1435 		{
  1436 			if (replaceLen > searchLen)
  1437 			{
  1438 				/* First, see if we have enough space to do this operation */
  1439 				if (maxLen - textLen < replaceLen - searchLen)
  1440 				{
  1441 					/* First, see if the replacement length goes out of bounds. */
  1442 					if (browsed + replaceLen >= maxLen)
  1443 					{
  1444 						/* EXAMPLE CASE:
  1445 						 * Subject: AABBBCCC
  1446 						 * Buffer : 12 bytes
  1447 						 * Search : BBB
  1448 						 * Replace: DDDDDDDDDD
  1449 						 * OUTPUT : AADDDDDDDDD
  1450 						 * POSITION:           ^
  1451 						 */
  1452 						/* If it does, we'll just bound the length and do a strcpy. */
  1453 						replaceLen = maxLen - browsed;
  1454 						/* Note, we add one to the final result for the null terminator */
  1455 						strncopy(ptr, replace, replaceLen+1);
  1456 					}
  1457 					else
  1458 					{
  1459 						/* EXAMPLE CASE:
  1460 						 * Subject: AABBBCCC
  1461 						 * Buffer : 12 bytes
  1462 						 * Search : BBB
  1463 						 * Replace: DDDDDDD
  1464 						 * OUTPUT : AADDDDDDDCC
  1465 						 * POSITION:         ^
  1466 						 */
  1467 						/* We're going to have some bytes left over... */
  1468 						size_t origBytesToCopy = (textLen - (browsed + searchLen)) + 1;
  1469 						size_t realBytesToCopy = (maxLen - (browsed + replaceLen)) + 1;
  1470 						char *moveFrom = ptr + searchLen + (origBytesToCopy - realBytesToCopy);
  1471 						char *moveTo = ptr + replaceLen;
  1472 
  1473 						/* First, move our old data out of the way. */
  1474 						memmove(moveTo, moveFrom, realBytesToCopy);
  1475 
  1476 						/* Now, do our replacement. */
  1477 						memcpy(ptr, replace, replaceLen);
  1478 					}
  1479 				}
  1480 				else
  1481 				{
  1482 					/* EXAMPLE CASE:
  1483 					 * Subject: AABBBCCC
  1484 					 * Buffer : 12 bytes
  1485 					 * Search : BBB
  1486 					 * Replace: DDDD
  1487 					 * OUTPUT : AADDDDCCC
  1488 					 * POSITION:      ^
  1489 					 */
  1490 					/* Yes, we have enough space.  Do a normal move operation. */
  1491 					char *moveFrom = ptr + searchLen;
  1492 					char *moveTo = ptr + replaceLen;
  1493 
  1494 					/* First move our old data out of the way. */
  1495 					size_t bytesToCopy = (textLen - (browsed + searchLen)) + 1;
  1496 					memmove(moveTo, moveFrom, bytesToCopy);
  1497 
  1498 					/* Now do our replacement. */
  1499 					memcpy(ptr, replace, replaceLen);
  1500 				}
  1501 			}
  1502 			else if (replaceLen < searchLen)
  1503 			{
  1504 				/* EXAMPLE CASE:
  1505 				 * Subject: AABBBCCC
  1506 				 * Buffer : 12 bytes
  1507 				 * Search : BBB
  1508 				 * Replace: D
  1509 				 * OUTPUT : AADCCC
  1510 				 * POSOTION:   ^
  1511 				 */
  1512 				/* If the replacement does not grow the string length, we do not
  1513 				 * need to do any fancy checking at all.  Yay!
  1514 				 */
  1515 				char *moveFrom = ptr + searchLen;		/* Start after the search pointer */
  1516 				char *moveTo = ptr + replaceLen;		/* Copy to where the replacement ends */
  1517 
  1518 				/* Copy our replacement in, if any */
  1519 				if (replaceLen)
  1520 				{
  1521 					memcpy(ptr, replace, replaceLen);
  1522 				}
  1523 
  1524 				/* Figure out how many bytes to move down, including null terminator */
  1525 				size_t bytesToCopy = (textLen - (browsed + searchLen)) + 1;
  1526 
  1527 				/* Move the rest of the string down */
  1528 				memmove(moveTo, moveFrom, bytesToCopy);
  1529 			}
  1530 			else
  1531 			{
  1532 				/* EXAMPLE CASE:
  1533 				 * Subject: AABBBCCC
  1534 				 * Buffer : 12 bytes
  1535 				 * Search : BBB
  1536 				 * Replace: DDD
  1537 				 * OUTPUT : AADDDCCC
  1538 				 * POSITION:     ^
  1539 				 */
  1540 				/* We don't have to move anything around, just do a straight copy */
  1541 				memcpy(ptr, replace, replaceLen);
  1542 			}
  1543 
  1544 			return ptr + replaceLen;
  1545 		}
  1546 		ptr++;
  1547 		browsed++;
  1548 	}
  1549 
  1550 	return NULL;
  1551 }
  1552 
  1553 char *UTIL_TrimWhitespace(char *str, size_t &len)
  1554 {
  1555 	char *end = str + len - 1;
  1556 
  1557 	if (!len)
  1558 	{
  1559 		return str;
  1560 	}
  1561 
  1562 	/* Iterate backwards through string until we reach first non-whitespace char */
  1563 	while (end >= str && textparsers->IsWhitespace(end))
  1564 	{
  1565 		end--;
  1566 		len--;
  1567 	}
  1568 
  1569 	/* Replace first whitespace char (at the end) with null terminator.
  1570 	 * If there is none, we're just replacing the null terminator. 
  1571 	 */
  1572 	*(end + 1) = '\0';
  1573 
  1574 	while (*str != '\0' && textparsers->IsWhitespace(str))
  1575 	{
  1576 		str++;
  1577 		len--;
  1578 	}
  1579 
  1580 	return str;
  1581 }
  1582