3 * ================================================================
5 * Copyright (C) 2004-2007 AlliedModders LLC. All rights reserved.
6 * ================================================================
8 * This program is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU General Public License,
10 * version 3.0, as published by the Free Software Foundation.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program. If not, see <http://www.gnu.org/licenses/>.
20 * As a special exception, AlliedModders LLC gives you permission to
21 * link the code of this program (as well as its derivative works) to
22 * "Half-Life 2," the "Source Engine," the "SourcePawn JIT," and any
23 * Game MODs that run on software by the Valve Corporation. You must
24 * obey the GNU General Public License in all respects for all other
25 * code used. Additionally, AlliedModders LLC grants this exception
26 * to all derivative works. AlliedModders LLC defines further
27 * exceptions, found in LICENSE.txt (as of this writing, version
28 * JULY-31-2007), or <http://www.sourcemod.net/license.php>.
36 #include "sm_stringutil.h"
38 #include "PluginSys.h"
39 #include "Translator.h"
40 #include "PlayerManager.h"
42 #define LADJUST 0x00000004 /* left adjustment */
43 #define ZEROPAD 0x00000080 /* zero (as opposed to blank) pad */
44 #define UPPERDIGITS 0x00000200 /* make alpha digits uppercase */
45 #define to_digit(c) ((c) - '0')
46 #define is_digit(c) ((unsigned)to_digit(c) <= 9)
48 #define CHECK_ARGS(x) \
49 if ((arg+x) > args) { \
50 pCtx->ThrowNativeErrorEx(SP_ERROR_PARAM, "String formatted incorrectly - parameter %d (total %d)", arg, args); \
54 size_t CorePlayerTranslate(int client, char *buffer, size_t maxlength, const char *phrase, void **params)
59 err = g_pCorePhrases->GetTranslation(phrase, g_Translator.GetServerLanguage(), &pTrans);
60 if (err != Trans_Okay && g_Translator.GetServerLanguage() != CORELANG_ENGLISH)
62 err = g_pCorePhrases->GetTranslation(phrase, CORELANG_ENGLISH, &pTrans);
65 if (err != Trans_Okay)
67 return UTIL_Format(buffer, maxlength, "%s", phrase);
70 return g_Translator.Translate(buffer, maxlength, params, &pTrans);
73 inline bool TryTranslation(CPlugin *pl, const char *key, unsigned int langid, unsigned int langcount, Translation *pTrans)
75 TransError err = Trans_BadLanguage;
78 for (size_t i=0; i<langcount && err!=Trans_Okay; i++)
80 phrfl = g_Translator.GetFileByIndex(pl->GetLangFileByIndex(i));
81 err = phrfl->GetTranslation(key, langid, pTrans);
84 return (err == Trans_Okay) ? true : false;
87 size_t Translate(char *buffer, size_t maxlen, IPluginContext *pCtx, const char *key, cell_t target, const cell_t *params, int *arg, bool *error)
92 CPlugin *pl = (CPlugin *)g_PluginSys.FindPluginByContext(pCtx->GetContext());
93 size_t langcount = pl->GetLangFileCount();
94 void *new_params[MAX_TRANSLATE_PARAMS];
95 unsigned int max_params = 0;
98 if (target == LANG_SERVER)
100 langid = g_Translator.GetServerLanguage();
101 } else if ((target >= 1) && (target <= g_Players.GetMaxClients())) {
102 langid = g_Translator.GetClientLanguage(target);
104 pCtx->ThrowNativeErrorEx(SP_ERROR_PARAM, "Translation failed: invalid client index %d", target);
108 if (!TryTranslation(pl, key, langid, langcount, &pTrans))
110 if (target != LANG_SERVER && langid != g_Translator.GetServerLanguage())
112 target = LANG_SERVER;
114 } else if (langid != LANGUAGE_ENGLISH) {
115 if (!TryTranslation(pl, key, LANGUAGE_ENGLISH, langcount, &pTrans))
117 pCtx->ThrowNativeErrorEx(SP_ERROR_PARAM, "Language phrase \"%s\" not found", key);
121 pCtx->ThrowNativeErrorEx(SP_ERROR_PARAM, "Language phrase \"%s\" not found", key);
126 max_params = pTrans.fmt_count;
130 /* Check if we're going to over the limit */
131 if ((*arg) + (max_params - 1) > (size_t)params[0])
133 pCtx->ThrowNativeErrorEx(SP_ERROR_PARAMS_MAX,
134 "Translation string formatted incorrectly - missing at least %d parameters",
135 ((*arg + (max_params - 1)) - params[0])
140 /* Translate the parameters to raw pointers */
141 for (size_t i=0; i<max_params; i++)
143 pCtx->LocalToPhysAddr(params[*arg], reinterpret_cast<cell_t **>(&new_params[i]));
148 return g_Translator.Translate(buffer, maxlen, new_params, &pTrans);
154 //:TODO: review this code before we choose a license
156 void AddString(char **buf_p, size_t &maxlen, const char *string, int width, int prec)
160 static char nlstr[] = {'(','n','u','l','l',')','\0'};
172 for (size = 0; size < prec; size++)
174 if (string[size] == '\0')
180 while (string[size++]);
184 if (size > (int)maxlen)
197 while ((width-- > 0) && maxlen)
206 void AddFloat(char **buf_p, size_t &maxlen, double fval, int width, int prec)
221 // write the float number
226 text[digits++] = '0' + val % 10;
232 text[digits++] = '-';
237 while ((digits < width) && maxlen)
244 while ((digits--) && maxlen)
246 *buf++ = text[digits];
256 // write the fraction
258 while (digits < prec)
263 text[digits++] = '0' + val % 10;
266 if ((digits > 0) && maxlen)
271 for (prec = 0; maxlen && (prec < digits); prec++)
280 void AddUInt(char **buf_p, size_t &maxlen, unsigned int val, int width, int flags)
289 text[digits++] = '0' + val % 10;
295 if (!(flags & LADJUST))
297 while (digits < width && maxlen)
299 *buf++ = (flags & ZEROPAD) ? '0' : ' ';
305 while (digits-- && maxlen)
307 *buf++ = text[digits];
314 while (width-- && maxlen)
316 *buf++ = (flags & ZEROPAD) ? '0' : ' ';
324 void AddInt(char **buf_p, size_t &maxlen, int val, int width, int flags)
330 unsigned int unsignedVal;
336 /* we want the unsigned version */
337 unsignedVal = abs(val);
343 text[digits++] = '0' + unsignedVal % 10;
345 } while (unsignedVal);
349 text[digits++] = '-';
354 if (!(flags & LADJUST))
356 while ((digits < width) && maxlen)
358 *buf++ = (flags & ZEROPAD) ? '0' : ' ';
364 while (digits-- && maxlen)
366 *buf++ = text[digits];
373 while (width-- && maxlen)
375 *buf++ = (flags & ZEROPAD) ? '0' : ' ';
383 void AddHex(char **buf_p, size_t &maxlen, unsigned int val, int width, int flags)
391 if (flags & UPPERDIGITS)
393 hexadjust = 'A' - '9' - 1;
395 hexadjust = 'a' - '9' - 1;
401 digit = ('0' + val % 16);
407 text[digits++] = digit;
413 if (!(flags & LADJUST))
415 while (digits < width && maxlen)
417 *buf++ = (flags & ZEROPAD) ? '0' : ' ';
423 while (digits-- && maxlen)
425 *buf++ = text[digits];
432 while (width-- && maxlen)
434 *buf++ = (flags & ZEROPAD) ? '0' : ' ';
442 size_t gnprintf(char *buffer, size_t maxlen, const char *format, void **args)
444 if (!buffer || !maxlen)
458 size_t llen = maxlen - 1;
465 // run through the format string until we hit a '%' or '\0'
466 for (ch = *fmt; llen && ((ch = *fmt) != '\0') && (ch != '%'); fmt++)
471 if ((ch == '\0') || (llen <= 0))
479 // reset formatting state
498 while(is_digit((ch = *fmt++)))
500 n = 10 * n + (ch - '0');
502 prec = (n < 0) ? -1 : n;
523 n = 10 * n + (ch - '0');
525 } while(is_digit(ch));
535 char *c = (char *)args[arg];
544 int *value = (int *)args[arg];
545 AddInt(&buf_p, llen, *value, width, flags);
551 unsigned int *value = (unsigned int *)args[arg];
552 AddUInt(&buf_p, llen, *value, width, flags);
558 float *value = (float *)args[arg];
559 AddFloat(&buf_p, llen, *value, width, prec);
565 const char *str = (const char *)args[arg];
566 AddString(&buf_p, llen, str, width, prec);
572 unsigned int *value = (unsigned int *)args[arg];
573 flags |= UPPERDIGITS;
574 AddHex(&buf_p, llen, *value, width, flags);
580 unsigned int *value = (unsigned int *)args[arg];
581 AddHex(&buf_p, llen, *value, width, flags);
621 return (maxlen - llen - 1);
624 size_t atcprintf(char *buffer, size_t maxlen, const char *format, IPluginContext *pCtx, const cell_t *params, int *param)
626 if (!buffer || !maxlen)
632 int args = params[0];
641 size_t llen = maxlen - 1;
649 // run through the format string until we hit a '%' or '\0'
650 for (ch = *fmt; llen && ((ch = *fmt) != '\0') && (ch != '%'); fmt++)
655 if ((ch == '\0') || (llen <= 0))
663 // reset formatting state
682 while(is_digit((ch = *fmt++)))
684 n = 10 * n + (ch - '0');
686 prec = (n < 0) ? -1 : n;
707 n = 10 * n + (ch - '0');
709 } while(is_digit(ch));
721 pCtx->LocalToString(params[arg], &c);
732 pCtx->LocalToPhysAddr(params[arg], &value);
733 AddInt(&buf_p, llen, static_cast<int>(*value), width, flags);
741 pCtx->LocalToPhysAddr(params[arg], &value);
742 AddUInt(&buf_p, llen, static_cast<unsigned int>(*value), width, flags);
750 pCtx->LocalToPhysAddr(params[arg], &value);
751 AddFloat(&buf_p, llen, sp_ctof(*value), width, prec);
759 pCtx->LocalToPhysAddr(params[arg], &value);
763 CPlayer *player = g_Players.GetPlayerByIndex(*value);
764 if (!player || !player->IsConnected())
766 return pCtx->ThrowNativeError("Client index %d is invalid", *value);
768 const char *auth = player->GetAuthString();
769 if (!auth || auth[0] == '\0')
771 auth = "STEAM_ID_PENDING";
773 int userid = engine->GetPlayerUserId(player->GetEdict());
783 "Console<0><Console><Console>");
785 AddString(&buf_p, llen, buffer, width, prec);
794 if ((err=pCtx->LocalToString(params[arg], &str)) != SP_ERROR_NONE)
796 pCtx->ThrowNativeErrorEx(err, "Could not deference string");
799 AddString(&buf_p, llen, str, width, prec);
810 pCtx->LocalToString(params[arg++], &key);
811 pCtx->LocalToPhysAddr(params[arg++], &target);
812 res = Translate(buf_p, llen, pCtx, key, *target, params, &arg, &error);
827 cell_t target = g_SourceMod.GetGlobalTarget();
828 pCtx->LocalToString(params[arg++], &key);
829 res = Translate(buf_p, llen, pCtx, key, target, params, &arg, &error);
842 pCtx->LocalToPhysAddr(params[arg], &value);
843 flags |= UPPERDIGITS;
844 AddHex(&buf_p, llen, static_cast<unsigned int>(*value), width, flags);
852 pCtx->LocalToPhysAddr(params[arg], &value);
853 AddHex(&buf_p, llen, static_cast<unsigned int>(*value), width, flags);
893 return (maxlen - llen - 1);
896 const char *stristr(const char *str, const char *substr)
900 return ((char *)str);
903 char *needle = (char *)substr;
904 char *prevloc = (char *)str;
905 char *haystack = (char *)str;
909 if (tolower(*haystack) == tolower(*needle))
917 haystack = ++prevloc;
918 needle = (char *)substr;
925 unsigned int strncopy(char *dest, const char *src, size_t count)
933 while ((*src) && (--count))
939 return (dest - start);
942 size_t UTIL_Format(char *buffer, size_t maxlength, const char *fmt, ...)
946 size_t len = vsnprintf(buffer, maxlength, fmt, ap);
949 if (len >= maxlength)
951 buffer[maxlength - 1] = '\0';
952 return (maxlength - 1);
958 size_t UTIL_FormatArgs(char *buffer, size_t maxlength, const char *fmt, va_list ap)
960 size_t len = vsnprintf(buffer, maxlength, fmt, ap);
962 if (len >= maxlength)
964 buffer[maxlength - 1] = '\0';
965 return (maxlength - 1);
971 char *sm_strdup(const char *str)
973 char *ptr = new char[strlen(str)+1];
978 unsigned int UTIL_ReplaceAll(char *subject, size_t maxlength, const char *search, const char *replace)
980 size_t searchLen = strlen(search);
981 size_t replaceLen = strlen(replace);
984 unsigned int total = 0;
985 while ((ptr = UTIL_ReplaceEx(ptr, maxlength, search, searchLen, replace, replaceLen)) != NULL)
998 * NOTE: Do not edit this for the love of god unless you have
999 * read the test cases and understand the code behind each one.
1000 * While I don't guarantee there aren't mistakes, I do guarantee
1001 * that plugins will end up relying on tiny idiosyncracies of this
1002 * function, just like they did with AMX Mod X.
1004 * There are explicitly more cases than the AMX Mod X version because
1005 * we're not doing a blind copy. Each case is specifically optimized
1006 * for what needs to be done. Even better, we don't have to error on
1007 * bad buffer sizes. Instead, this function will smartly cut off the
1008 * string in a way that pushes old data out.
1010 char *UTIL_ReplaceEx(char *subject, size_t maxLen, const char *search, size_t searchLen, const char *replace, size_t replaceLen)
1012 char *ptr = subject;
1014 size_t textLen = strlen(subject);
1016 /* It's not possible to search or replace */
1017 if (searchLen > textLen || maxLen <= 1)
1022 /* Subtract one off the maxlength so we can include the null terminator */
1025 while (*ptr != '\0' && (browsed <= textLen - searchLen))
1027 /* See if we get a comparison */
1028 if (strncmp(ptr, search, searchLen) == 0)
1030 if (replaceLen > searchLen)
1032 /* First, see if we have enough space to do this operation */
1033 if (maxLen - textLen < replaceLen - searchLen)
1035 /* First, see if the replacement length goes out of bounds. */
1036 if (browsed + replaceLen >= maxLen)
1042 * Replace: DDDDDDDDDD
1043 * OUTPUT : AADDDDDDDDD
1046 /* If it does, we'll just bound the length and do a strcpy. */
1047 replaceLen = maxLen - browsed;
1048 /* Note, we add one to the final result for the null terminator */
1049 strncopy(ptr, replace, replaceLen+1);
1056 * OUTPUT : AADDDDDDDCC
1059 /* We're going to have some bytes left over... */
1060 size_t origBytesToCopy = (textLen - (browsed + searchLen)) + 1;
1061 size_t realBytesToCopy = (maxLen - (browsed + replaceLen)) + 1;
1062 char *moveFrom = ptr + searchLen + (origBytesToCopy - realBytesToCopy);
1063 char *moveTo = ptr + replaceLen;
1065 /* First, move our old data out of the way. */
1066 memmove(moveTo, moveFrom, realBytesToCopy);
1068 /* Now, do our replacement. */
1069 memcpy(ptr, replace, replaceLen);
1077 * OUTPUT : AADDDDCCC
1080 /* Yes, we have enough space. Do a normal move operation. */
1081 char *moveFrom = ptr + searchLen;
1082 char *moveTo = ptr + replaceLen;
1084 /* First move our old data out of the way. */
1085 size_t bytesToCopy = (textLen - (browsed + searchLen)) + 1;
1086 memmove(moveTo, moveFrom, bytesToCopy);
1088 /* Now do our replacement. */
1089 memcpy(ptr, replace, replaceLen);
1091 } else if (replaceLen < searchLen) {
1100 /* If the replacement does not grow the string length, we do not
1101 * need to do any fancy checking at all. Yay!
1103 char *moveFrom = ptr + searchLen; /* Start after the search pointer */
1104 char *moveTo = ptr + replaceLen; /* Copy to where the replacement ends */
1106 /* Copy our replacement in, if any */
1109 memcpy(ptr, replace, replaceLen);
1112 /* Figure out how many bytes to move down, including null terminator */
1113 size_t bytesToCopy = (textLen - (browsed + searchLen)) + 1;
1115 /* Move the rest of the string down */
1116 memmove(moveTo, moveFrom, bytesToCopy);
1126 /* We don't have to move anything around, just do a straight copy */
1127 memcpy(ptr, replace, replaceLen);
1130 return ptr + replaceLen;