3 * =============================================================================
5 * Copyright (C) 2004-2008 AlliedModders LLC. All rights reserved.
6 * =============================================================================
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.
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
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/>.
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>.
35 #include "sm_stringutil.h"
37 #include "PluginSys.h"
38 #include "Translator.h"
39 #include "PlayerManager.h"
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)
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); \
53 inline void ReorderTranslationParams(const Translation *pTrans, cell_t *params)
55 cell_t new_params[MAX_TRANSLATE_PARAMS];
56 for (unsigned int i = 0; i < pTrans->fmt_count; i++)
58 new_params[i] = params[pTrans->fmt_order[i]];
60 memcpy(params, new_params, pTrans->fmt_count * sizeof(cell_t));
63 size_t Translate(char *buffer,
75 CPlugin *pl = (CPlugin *)g_PluginSys.FindPluginByContext(pCtx->GetContext());
76 unsigned int max_params = 0;
77 IPhraseCollection *pPhrases;
79 pPhrases = pl->GetPhrases();
82 if (target == SOURCEMOD_SERVER_LANGUAGE)
84 langid = g_Translator.GetServerLanguage();
86 else if ((target >= 1) && (target <= g_Players.GetMaxClients()))
88 langid = g_Translator.GetClientLanguage(target);
92 pCtx->ThrowNativeErrorEx(SP_ERROR_PARAM, "Translation failed: invalid client index %d", target);
96 if (pPhrases->FindTranslation(key, langid, &pTrans) != Trans_Okay)
98 if (target != SOURCEMOD_SERVER_LANGUAGE && langid != g_Translator.GetServerLanguage())
100 target = SOURCEMOD_SERVER_LANGUAGE;
103 else if (langid != SOURCEMOD_LANGUAGE_ENGLISH)
105 if (pPhrases->FindTranslation(key, SOURCEMOD_LANGUAGE_ENGLISH, &pTrans) != Trans_Okay)
107 pCtx->ThrowNativeErrorEx(SP_ERROR_PARAM, "Language phrase \"%s\" not found", key);
113 pCtx->ThrowNativeErrorEx(SP_ERROR_PARAM, "Language phrase \"%s\" not found", key);
118 max_params = pTrans.fmt_count;
122 cell_t new_params[MAX_TRANSLATE_PARAMS];
124 /* Check if we're going to over the limit */
125 if ((*arg) + (max_params - 1) > (size_t)params[0])
127 pCtx->ThrowNativeErrorEx(SP_ERROR_PARAMS_MAX,
128 "Translation string formatted incorrectly - missing at least %d parameters",
129 ((*arg + (max_params - 1)) - params[0])
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().
137 memcpy(new_params, params, sizeof(cell_t) * (params[0] + 1));
138 ReorderTranslationParams(&pTrans, &new_params[*arg]);
140 return atcprintf(buffer, maxlen, pTrans.szPhrase, pCtx, new_params, arg);
144 return atcprintf(buffer, maxlen, pTrans.szPhrase, pCtx, params, arg);
152 void AddString(char **buf_p, size_t &maxlen, const char *string, int width, int prec)
156 static char nlstr[] = {'(','n','u','l','l',')','\0'};
168 for (size = 0; size < prec; size++)
170 if (string[size] == '\0')
178 while (string[size++]);
182 if (size > (int)maxlen)
195 while ((width-- > 0) && maxlen)
204 void AddFloat(char **buf_p, size_t &maxlen, double fval, int width, int prec, int flags)
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;
228 // compute whole-part digits count
229 digits = (int)log10(fval) + 1;
231 // Only print 0.something if 0 < fval < 1
237 // compute the field length
238 fieldlength = digits + prec + ((prec > 0) ? 1 : 0) + sign;
240 // minus sign BEFORE left padding if padding with zeros
241 if (sign && maxlen && (flags & ZEROPAD))
247 // right justify if required
248 if ((flags & LADJUST) == 0)
250 while ((fieldlength < width) && maxlen)
252 *buf++ = (flags & ZEROPAD) ? '0' : ' ';
258 // minus sign AFTER left padding if padding with spaces
259 if (sign && maxlen && !(flags & ZEROPAD))
265 // write the whole part
266 tmp = pow(10.0, digits-1);
267 while ((digits--) && maxlen)
269 if (++significant_digits > MAX_SIGNIFICANT_DIGITS)
275 val = (int)(fval / tmp);
283 // write the fraction part
290 tmp = pow(10.0, prec);
293 while (prec-- && maxlen)
295 if (++significant_digits > MAX_SIGNIFICANT_DIGITS)
302 val = (int)(fval / tmp);
309 // left justify if required
312 while ((fieldlength < width) && maxlen)
314 // right-padding only with spaces, ZEROPAD is ignored
321 // update parent's buffer pointer
325 void AddBinary(char **buf_p, size_t &maxlen, unsigned int val, int width, int flags)
336 text[digits++] = '1';
340 text[digits++] = '0';
347 if (!(flags & LADJUST))
349 while (digits < width && maxlen)
351 *buf++ = (flags & ZEROPAD) ? '0' : ' ';
357 while (digits-- && maxlen)
359 *buf++ = text[digits];
366 while (width-- && maxlen)
368 *buf++ = (flags & ZEROPAD) ? '0' : ' ';
376 void AddUInt(char **buf_p, size_t &maxlen, unsigned int val, int width, int flags)
385 text[digits++] = '0' + val % 10;
391 if (!(flags & LADJUST))
393 while (digits < width && maxlen)
395 *buf++ = (flags & ZEROPAD) ? '0' : ' ';
401 while (digits-- && maxlen)
403 *buf++ = text[digits];
410 while (width-- && maxlen)
412 *buf++ = (flags & ZEROPAD) ? '0' : ' ';
420 void AddInt(char **buf_p, size_t &maxlen, int val, int width, int flags)
426 unsigned int unsignedVal;
432 /* we want the unsigned version */
433 unsignedVal = abs(val);
442 text[digits++] = '0' + unsignedVal % 10;
444 } while (unsignedVal);
448 text[digits++] = '-';
453 if (!(flags & LADJUST))
455 while ((digits < width) && maxlen)
457 *buf++ = (flags & ZEROPAD) ? '0' : ' ';
463 while (digits-- && maxlen)
465 *buf++ = text[digits];
472 while (width-- && maxlen)
474 *buf++ = (flags & ZEROPAD) ? '0' : ' ';
482 void AddHex(char **buf_p, size_t &maxlen, unsigned int val, int width, int flags)
490 if (flags & UPPERDIGITS)
492 hexadjust = 'A' - '9' - 1;
496 hexadjust = 'a' - '9' - 1;
502 digit = ('0' + val % 16);
508 text[digits++] = digit;
514 if (!(flags & LADJUST))
516 while (digits < width && maxlen)
518 *buf++ = (flags & ZEROPAD) ? '0' : ' ';
524 while (digits-- && maxlen)
526 *buf++ = text[digits];
533 while (width-- && maxlen)
535 *buf++ = (flags & ZEROPAD) ? '0' : ' ';
543 bool gnprintf(char *buffer,
546 IPhraseCollection *pPhrases,
548 unsigned int numparams,
549 unsigned int &curparam,
551 const char **pFailPhrase)
553 if (!buffer || !maxlen)
555 if (pOutLength != NULL)
562 if (numparams > MAX_TRANSLATE_PARAMS)
564 if (pFailPhrase != NULL)
580 size_t llen = maxlen - 1;
587 // run through the format string until we hit a '%' or '\0'
588 for (ch = *fmt; llen && ((ch = *fmt) != '\0') && (ch != '%'); fmt++)
593 if ((ch == '\0') || (llen <= 0))
601 // reset formatting state
620 while(is_digit((ch = *fmt++)))
622 n = 10 * n + (ch - '0');
624 prec = (n < 0) ? -1 : n;
645 n = 10 * n + (ch - '0');
647 } while(is_digit(ch));
657 if (curparam >= numparams)
659 if (pFailPhrase != NULL)
665 char *c = (char *)params[curparam];
674 if (curparam >= numparams)
676 if (pFailPhrase != NULL)
682 int *value = (int *)params[curparam];
684 AddBinary(&buf_p, llen, *value, width, flags);
691 if (curparam >= numparams)
693 if (pFailPhrase != NULL)
699 int *value = (int *)params[curparam];
701 AddInt(&buf_p, llen, *value, width, flags);
707 if (curparam >= numparams)
709 if (pFailPhrase != NULL)
715 unsigned int *value = (unsigned int *)params[curparam];
717 AddUInt(&buf_p, llen, *value, width, flags);
723 if (curparam >= numparams)
725 if (pFailPhrase != NULL)
731 float *value = (float *)params[curparam];
733 AddFloat(&buf_p, llen, *value, width, prec, flags);
739 if (curparam >= numparams)
741 if (pFailPhrase != NULL)
747 const char *str = (const char *)params[curparam];
749 AddString(&buf_p, llen, str, width, prec);
760 unsigned int lang_id;
762 if (curparam >= numparams)
764 if (pFailPhrase != NULL)
770 key = (const char *)(params[curparam]);
775 if (curparam >= numparams)
777 if (pFailPhrase != NULL)
783 target = *((int *)(params[curparam]));
788 target = g_Translator.GetGlobalTarget();
792 if (target == SOURCEMOD_SERVER_LANGUAGE)
794 lang_id = g_Translator.GetServerLanguage();
796 else if (target >= 1 && target <= g_Players.GetMaxClients())
798 lang_id = g_Translator.GetClientLanguage(target);
802 lang_id = g_Translator.GetServerLanguage();
805 if (pPhrases == NULL)
807 if (pFailPhrase != NULL)
814 if (pPhrases->FindTranslation(key, lang_id, &trans) != Trans_Okay)
816 if (target != SOURCEMOD_SERVER_LANGUAGE && lang_id != g_Translator.GetServerLanguage())
818 target = SOURCEMOD_SERVER_LANGUAGE;
821 else if (lang_id != SOURCEMOD_LANGUAGE_ENGLISH)
823 if (pPhrases->FindTranslation(key, SOURCEMOD_LANGUAGE_ENGLISH, &trans) != Trans_Okay)
825 if (pFailPhrase != NULL)
834 if (pFailPhrase != NULL)
845 void *new_params[MAX_TRANSLATE_PARAMS];
847 if (curparam + trans.fmt_count > numparams)
849 if (pFailPhrase != NULL)
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++)
860 new_params[curparam + i] = const_cast<void *>(params[curparam + trans.fmt_order[i]]);
899 if (curparam >= numparams)
901 if (pFailPhrase != NULL)
907 unsigned int *value = (unsigned int *)params[curparam];
909 flags |= UPPERDIGITS;
910 AddHex(&buf_p, llen, *value, width, flags);
916 if (curparam >= numparams)
918 if (pFailPhrase != NULL)
924 unsigned int *value = (unsigned int *)params[curparam];
926 AddHex(&buf_p, llen, *value, width, flags);
966 if (pOutLength != NULL)
968 *pOutLength = (maxlen - llen - 1);
974 size_t atcprintf(char *buffer, size_t maxlen, const char *format, IPluginContext *pCtx, const cell_t *params, int *param)
976 if (!buffer || !maxlen)
982 int args = params[0];
991 size_t llen = maxlen - 1;
999 // run through the format string until we hit a '%' or '\0'
1000 for (ch = *fmt; llen && ((ch = *fmt) != '\0') && (ch != '%'); fmt++)
1005 if ((ch == '\0') || (llen <= 0))
1010 // skip over the '%'
1013 // reset formatting state
1032 while(is_digit((ch = *fmt++)))
1034 n = 10 * n + (ch - '0');
1036 prec = (n < 0) ? -1 : n;
1057 n = 10 * n + (ch - '0');
1059 } while(is_digit(ch));
1071 pCtx->LocalToString(params[arg], &c);
1081 pCtx->LocalToPhysAddr(params[arg], &value);
1082 AddBinary(&buf_p, llen, *value, width, flags);
1091 pCtx->LocalToPhysAddr(params[arg], &value);
1092 AddInt(&buf_p, llen, static_cast<int>(*value), width, flags);
1100 pCtx->LocalToPhysAddr(params[arg], &value);
1101 AddUInt(&buf_p, llen, static_cast<unsigned int>(*value), width, flags);
1109 pCtx->LocalToPhysAddr(params[arg], &value);
1110 AddFloat(&buf_p, llen, sp_ctof(*value), width, prec, flags);
1118 pCtx->LocalToPhysAddr(params[arg], &value);
1122 CPlayer *player = g_Players.GetPlayerByIndex(*value);
1123 if (!player || !player->IsConnected())
1125 return pCtx->ThrowNativeError("Client index %d is invalid", *value);
1127 const char *auth = player->GetAuthString();
1128 if (!auth || auth[0] == '\0')
1130 auth = "STEAM_ID_PENDING";
1132 int userid = engine->GetPlayerUserId(player->GetEdict());
1144 "Console<0><Console><Console>");
1146 AddString(&buf_p, llen, buffer, width, prec);
1154 pCtx->LocalToPhysAddr(params[arg], &value);
1156 const char *name = "Console";
1159 CPlayer *player = g_Players.GetPlayerByIndex(*value);
1160 if (!player || !player->IsConnected())
1162 return pCtx->ThrowNativeError("Client index %d is invalid", *value);
1164 name = player->GetName();
1166 AddString(&buf_p, llen, name, width, prec);
1175 if ((err=pCtx->LocalToString(params[arg], &str)) != SP_ERROR_NONE)
1177 pCtx->ThrowNativeErrorEx(err, "Could not deference string");
1180 AddString(&buf_p, llen, str, width, prec);
1191 pCtx->LocalToString(params[arg++], &key);
1192 pCtx->LocalToPhysAddr(params[arg++], &target);
1193 res = Translate(buf_p, llen, pCtx, key, *target, params, &arg, &error);
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);
1223 pCtx->LocalToPhysAddr(params[arg], &value);
1224 flags |= UPPERDIGITS;
1225 AddHex(&buf_p, llen, static_cast<unsigned int>(*value), width, flags);
1233 pCtx->LocalToPhysAddr(params[arg], &value);
1234 AddHex(&buf_p, llen, static_cast<unsigned int>(*value), width, flags);
1274 return (maxlen - llen - 1);
1277 const char *stristr(const char *str, const char *substr)
1281 return ((char *)str);
1284 char *needle = (char *)substr;
1285 char *prevloc = (char *)str;
1286 char *haystack = (char *)str;
1290 if (tolower(*haystack) == tolower(*needle))
1300 haystack = ++prevloc;
1301 needle = (char *)substr;
1308 unsigned int strncopy(char *dest, const char *src, size_t count)
1316 while ((*src) && (--count))
1322 return (dest - start);
1325 size_t UTIL_Format(char *buffer, size_t maxlength, const char *fmt, ...)
1329 size_t len = vsnprintf(buffer, maxlength, fmt, ap);
1332 if (len >= maxlength)
1334 buffer[maxlength - 1] = '\0';
1335 return (maxlength - 1);
1343 size_t UTIL_FormatArgs(char *buffer, size_t maxlength, const char *fmt, va_list ap)
1345 size_t len = vsnprintf(buffer, maxlength, fmt, ap);
1347 if (len >= maxlength)
1349 buffer[maxlength - 1] = '\0';
1350 return (maxlength - 1);
1358 char *sm_strdup(const char *str)
1360 char *ptr = new char[strlen(str)+1];
1365 unsigned int UTIL_ReplaceAll(char *subject, size_t maxlength, const char *search, const char *replace, bool caseSensitive)
1367 size_t searchLen = strlen(search);
1368 size_t replaceLen = strlen(replace);
1370 char *ptr = subject;
1371 unsigned int total = 0;
1372 while ((ptr = UTIL_ReplaceEx(ptr, maxlength, search, searchLen, replace, replaceLen, caseSensitive)) != NULL)
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.
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.
1397 char *UTIL_ReplaceEx(char *subject, size_t maxLen, const char *search, size_t searchLen, const char *replace, size_t replaceLen, bool caseSensitive)
1399 char *ptr = subject;
1401 size_t textLen = strlen(subject);
1403 /* It's not possible to search or replace */
1404 if (searchLen > textLen)
1409 /* Handle the case of one byte replacement.
1410 * It's only valid in one case.
1414 /* If the search matches and the replace length is 0,
1415 * we can just terminate the string and be done.
1417 if ((caseSensitive ? strcmp(subject, search) : strcasecmp(subject, search)) == 0 && replaceLen == 0)
1428 /* Subtract one off the maxlength so we can include the null terminator */
1431 while (*ptr != '\0' && (browsed <= textLen - searchLen))
1433 /* See if we get a comparison */
1434 if ((caseSensitive ? strncmp(ptr, search, searchLen) : strncasecmp(ptr, search, searchLen)) == 0)
1436 if (replaceLen > searchLen)
1438 /* First, see if we have enough space to do this operation */
1439 if (maxLen - textLen < replaceLen - searchLen)
1441 /* First, see if the replacement length goes out of bounds. */
1442 if (browsed + replaceLen >= maxLen)
1448 * Replace: DDDDDDDDDD
1449 * OUTPUT : AADDDDDDDDD
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);
1464 * OUTPUT : AADDDDDDDCC
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;
1473 /* First, move our old data out of the way. */
1474 memmove(moveTo, moveFrom, realBytesToCopy);
1476 /* Now, do our replacement. */
1477 memcpy(ptr, replace, replaceLen);
1487 * OUTPUT : AADDDDCCC
1490 /* Yes, we have enough space. Do a normal move operation. */
1491 char *moveFrom = ptr + searchLen;
1492 char *moveTo = ptr + replaceLen;
1494 /* First move our old data out of the way. */
1495 size_t bytesToCopy = (textLen - (browsed + searchLen)) + 1;
1496 memmove(moveTo, moveFrom, bytesToCopy);
1498 /* Now do our replacement. */
1499 memcpy(ptr, replace, replaceLen);
1502 else if (replaceLen < searchLen)
1512 /* If the replacement does not grow the string length, we do not
1513 * need to do any fancy checking at all. Yay!
1515 char *moveFrom = ptr + searchLen; /* Start after the search pointer */
1516 char *moveTo = ptr + replaceLen; /* Copy to where the replacement ends */
1518 /* Copy our replacement in, if any */
1521 memcpy(ptr, replace, replaceLen);
1524 /* Figure out how many bytes to move down, including null terminator */
1525 size_t bytesToCopy = (textLen - (browsed + searchLen)) + 1;
1527 /* Move the rest of the string down */
1528 memmove(moveTo, moveFrom, bytesToCopy);
1540 /* We don't have to move anything around, just do a straight copy */
1541 memcpy(ptr, replace, replaceLen);
1544 return ptr + replaceLen;
1553 char *UTIL_TrimWhitespace(char *str, size_t &len)
1555 char *end = str + len - 1;
1562 /* Iterate backwards through string until we reach first non-whitespace char */
1563 while (end >= str && textparsers->IsWhitespace(end))
1569 /* Replace first whitespace char (at the end) with null terminator.
1570 * If there is none, we're just replacing the null terminator.
1574 while (*str != '\0' && textparsers->IsWhitespace(str))
1583 size_t UTIL_DecodeHexString(unsigned char *buffer, size_t maxlength, const char *hexstr)
1586 size_t length = strlen(hexstr);
1588 for (size_t i = 0; i < length; i++)
1590 if (written >= maxlength)
1592 buffer[written++] = hexstr[i];
1593 if (hexstr[i] == '\\' && hexstr[i + 1] == 'x')
1595 if (i + 3 >= length)
1597 /* Get the hex part. */
1600 s_byte[0] = hexstr[i + 2];
1601 s_byte[1] = hexstr[i + 3];
1603 /* Read it as an integer */
1604 sscanf(s_byte, "%x", &r_byte);
1605 /* Save the value */
1606 buffer[written - 1] = r_byte;
1615 char *UTIL_ToLowerCase(const char *str)
1617 size_t len = strlen(str);
1618 char *buffer = new char[len + 1];
1619 for (size_t i = 0; i < len; i++)
1621 if (str[i] >= 'A' && str[i] <= 'Z')
1622 buffer[i] = tolower(str[i]);