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