// vim: set ts=4 sw=4 tw=99 noet : // ============================================================================= // SourceMod // Copyright (C) 2004-2015 AlliedModders LLC. All rights reserved. // ============================================================================= // // This program is free software; you can redistribute it and/or modify it under // the terms of the GNU General Public License, version 3.0, as published by the // Free Software Foundation. // // This program is distributed in the hope that it will be useful, but WITHOUT // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS // FOR A PARTICULAR PURPOSE. See the GNU General Public License for more // details. // // You should have received a copy of the GNU General Public License along with // this program. If not, see . // // As a special exception, AlliedModders LLC gives you permission to link the // code of this program (as well as its derivative works) to "Half-Life 2," the // "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software // by the Valve Corporation. You must obey the GNU General Public License in // all respects for all other code used. Additionally, AlliedModders LLC grants // this exception to all derivative works. AlliedModders LLC defines further // exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), // or . #include "common_logic.h" #include "Translator.h" #include "sprintf.h" #include #include #include #include #include using namespace SourceMod; #define LADJUST 0x00000004 /* left adjustment */ #define ZEROPAD 0x00000080 /* zero (as opposed to blank) pad */ #define UPPERDIGITS 0x00000200 /* make alpha digits uppercase */ #define to_digit(c) ((c) - '0') #define is_digit(c) ((unsigned)to_digit(c) <= 9) #define CHECK_ARGS(x) \ if ((arg+x) > args) { \ pCtx->ThrowNativeErrorEx(SP_ERROR_PARAM, "String formatted incorrectly - parameter %d (total %d)", arg, args); \ return 0; \ } inline void ReorderTranslationParams(const Translation *pTrans, cell_t *params) { cell_t new_params[MAX_TRANSLATE_PARAMS]; for (unsigned int i = 0; i < pTrans->fmt_count; i++) { new_params[i] = params[pTrans->fmt_order[i]]; } memcpy(params, new_params, pTrans->fmt_count * sizeof(cell_t)); } size_t Translate(char *buffer, size_t maxlen, IPluginContext *pCtx, const char *key, cell_t target, const cell_t *params, int *arg, bool *error) { unsigned int langid; *error = false; Translation pTrans; IPlugin *pl = scripts->FindPluginByContext(pCtx->GetContext()); unsigned int max_params = 0; IPhraseCollection *pPhrases; pPhrases = pl->GetPhrases(); try_serverlang: if (target == SOURCEMOD_SERVER_LANGUAGE) { langid = g_Translator.GetServerLanguage(); } else if ((target >= 1) && (target <= bridge->MaxClients())) { langid = g_Translator.GetClientLanguage(target); } else { pCtx->ThrowNativeErrorEx(SP_ERROR_PARAM, "Translation failed: invalid client index %d", target); goto error_out; } if (pPhrases->FindTranslation(key, langid, &pTrans) != Trans_Okay) { if (target != SOURCEMOD_SERVER_LANGUAGE && langid != g_Translator.GetServerLanguage()) { target = SOURCEMOD_SERVER_LANGUAGE; goto try_serverlang; } else if (langid != SOURCEMOD_LANGUAGE_ENGLISH) { if (pPhrases->FindTranslation(key, SOURCEMOD_LANGUAGE_ENGLISH, &pTrans) != Trans_Okay) { pCtx->ThrowNativeErrorEx(SP_ERROR_PARAM, "Language phrase \"%s\" not found", key); goto error_out; } } else { pCtx->ThrowNativeErrorEx(SP_ERROR_PARAM, "Language phrase \"%s\" not found", key); goto error_out; } } max_params = pTrans.fmt_count; if (max_params) { cell_t new_params[MAX_TRANSLATE_PARAMS]; /* Check if we're going to over the limit */ if ((*arg) + (max_params - 1) > (size_t)params[0]) { pCtx->ThrowNativeErrorEx(SP_ERROR_PARAMS_MAX, "Translation string formatted incorrectly - missing at least %d parameters", ((*arg + (max_params - 1)) - params[0]) ); goto error_out; } /* If we need to re-order the parameters, do so with a temporary array. * Otherwise, we could run into trouble with continual formats, a la ShowActivity(). */ memcpy(new_params, params, sizeof(cell_t) * (params[0] + 1)); ReorderTranslationParams(&pTrans, &new_params[*arg]); return atcprintf(buffer, maxlen, pTrans.szPhrase, pCtx, new_params, arg); } else { return atcprintf(buffer, maxlen, pTrans.szPhrase, pCtx, params, arg); } error_out: *error = true; return 0; } void AddString(char **buf_p, size_t &maxlen, const char *string, int width, int prec) { int size = 0; char *buf; static char nlstr[] = {'(','n','u','l','l',')','\0'}; buf = *buf_p; if (string == NULL) { string = nlstr; prec = -1; } if (prec >= 0) { for (size = 0; size < prec; size++) { if (string[size] == '\0') { break; } } } else { while (string[size++]); size--; } if (size > (int)maxlen) { size = maxlen; } maxlen -= size; width -= size; while (size--) { *buf++ = *string++; } while ((width-- > 0) && maxlen) { *buf++ = ' '; maxlen--; } *buf_p = buf; } void AddFloat(char **buf_p, size_t &maxlen, double fval, int width, int prec, int flags) { int digits; // non-fraction part digits double tmp; // temporary char *buf = *buf_p; // output buffer pointer int val; // temporary int sign = 0; // 0: positive, 1: negative int fieldlength; // for padding int significant_digits = 0; // number of significant digits written const int MAX_SIGNIFICANT_DIGITS = 16; if (ke::IsNaN(fval)) { AddString(buf_p, maxlen, "NaN", width, prec); return; } // default precision if (prec < 0) { prec = 6; } // get the sign if (fval < 0) { fval = -fval; sign = 1; } // compute whole-part digits count digits = (int)log10(fval) + 1; // Only print 0.something if 0 < fval < 1 if (digits < 1) { digits = 1; } // compute the field length fieldlength = digits + prec + ((prec > 0) ? 1 : 0) + sign; // minus sign BEFORE left padding if padding with zeros if (sign && maxlen && (flags & ZEROPAD)) { *buf++ = '-'; maxlen--; } // right justify if required if ((flags & LADJUST) == 0) { while ((fieldlength < width) && maxlen) { *buf++ = (flags & ZEROPAD) ? '0' : ' '; width--; maxlen--; } } // minus sign AFTER left padding if padding with spaces if (sign && maxlen && !(flags & ZEROPAD)) { *buf++ = '-'; maxlen--; } // write the whole part tmp = pow(10.0, digits-1); while ((digits--) && maxlen) { if (++significant_digits > MAX_SIGNIFICANT_DIGITS) { *buf++ = '0'; } else { val = (int)(fval / tmp); *buf++ = '0' + val; fval -= val * tmp; tmp *= 0.1; } maxlen--; } // write the fraction part if (maxlen && prec) { *buf++ = '.'; maxlen--; } tmp = pow(10.0, prec); fval *= tmp; while (prec-- && maxlen) { if (++significant_digits > MAX_SIGNIFICANT_DIGITS) { *buf++ = '0'; } else { tmp *= 0.1; val = (int)(fval / tmp); *buf++ = '0' + val; fval -= val * tmp; } maxlen--; } // left justify if required if (flags & LADJUST) { while ((fieldlength < width) && maxlen) { // right-padding only with spaces, ZEROPAD is ignored *buf++ = ' '; width--; maxlen--; } } // update parent's buffer pointer *buf_p = buf; } void AddBinary(char **buf_p, size_t &maxlen, unsigned int val, int width, int flags) { char text[32]; int digits; char *buf; digits = 0; do { if (val & 1) { text[digits++] = '1'; } else { text[digits++] = '0'; } val >>= 1; } while (val); buf = *buf_p; if (!(flags & LADJUST)) { while (digits < width && maxlen) { *buf++ = (flags & ZEROPAD) ? '0' : ' '; width--; maxlen--; } } while (digits-- && maxlen) { *buf++ = text[digits]; width--; maxlen--; } if (flags & LADJUST) { while (width-- && maxlen) { *buf++ = (flags & ZEROPAD) ? '0' : ' '; maxlen--; } } *buf_p = buf; } void AddUInt(char **buf_p, size_t &maxlen, unsigned int val, int width, int flags) { char text[32]; int digits; char *buf; digits = 0; do { text[digits++] = '0' + val % 10; val /= 10; } while (val); buf = *buf_p; if (!(flags & LADJUST)) { while (digits < width && maxlen) { *buf++ = (flags & ZEROPAD) ? '0' : ' '; width--; maxlen--; } } while (digits-- && maxlen) { *buf++ = text[digits]; width--; maxlen--; } if (flags & LADJUST) { while (width-- && maxlen) { *buf++ = (flags & ZEROPAD) ? '0' : ' '; maxlen--; } } *buf_p = buf; } void AddInt(char **buf_p, size_t &maxlen, int val, int width, int flags) { char text[32]; int digits; int signedVal; char *buf; unsigned int unsignedVal; digits = 0; signedVal = val; if (val < 0) { /* we want the unsigned version */ unsignedVal = abs(val); } else { unsignedVal = val; } do { text[digits++] = '0' + unsignedVal % 10; unsignedVal /= 10; } while (unsignedVal); if (signedVal < 0) { text[digits++] = '-'; } buf = *buf_p; if (!(flags & LADJUST)) { while ((digits < width) && maxlen) { *buf++ = (flags & ZEROPAD) ? '0' : ' '; width--; maxlen--; } } while (digits-- && maxlen) { *buf++ = text[digits]; width--; maxlen--; } if (flags & LADJUST) { while (width-- && maxlen) { *buf++ = (flags & ZEROPAD) ? '0' : ' '; maxlen--; } } *buf_p = buf; } void AddHex(char **buf_p, size_t &maxlen, unsigned int val, int width, int flags) { char text[32]; int digits; char *buf; char digit; int hexadjust; if (flags & UPPERDIGITS) { hexadjust = 'A' - '9' - 1; } else { hexadjust = 'a' - '9' - 1; } digits = 0; do { digit = ('0' + val % 16); if (digit > '9') { digit += hexadjust; } text[digits++] = digit; val /= 16; } while(val); buf = *buf_p; if (!(flags & LADJUST)) { while (digits < width && maxlen) { *buf++ = (flags & ZEROPAD) ? '0' : ' '; width--; maxlen--; } } while (digits-- && maxlen) { *buf++ = text[digits]; width--; maxlen--; } if (flags & LADJUST) { while (width-- && maxlen) { *buf++ = (flags & ZEROPAD) ? '0' : ' '; maxlen--; } } *buf_p = buf; } bool gnprintf(char *buffer, size_t maxlen, const char *format, IPhraseCollection *pPhrases, void **params, unsigned int numparams, unsigned int &curparam, size_t *pOutLength, const char **pFailPhrase) { if (!buffer || !maxlen) { if (pOutLength != NULL) { *pOutLength = 0; } return true; } if (numparams > MAX_TRANSLATE_PARAMS) { if (pFailPhrase != NULL) { *pFailPhrase = NULL; } return false; } int arg = 0; char *buf_p; char ch; int flags; int width; int prec; int n; char sign; const char *fmt; size_t llen = maxlen - 1; buf_p = buffer; fmt = format; while (true) { // run through the format string until we hit a '%' or '\0' for (ch = *fmt; llen && ((ch = *fmt) != '\0') && (ch != '%'); fmt++) { *buf_p++ = ch; llen--; } if ((ch == '\0') || (llen <= 0)) { goto done; } // skip over the '%' fmt++; // reset formatting state flags = 0; width = 0; prec = -1; sign = '\0'; rflag: ch = *fmt++; reswitch: switch(ch) { case '-': { flags |= LADJUST; goto rflag; } case '.': { n = 0; while(is_digit((ch = *fmt++))) { n = 10 * n + (ch - '0'); } prec = (n < 0) ? -1 : n; goto reswitch; } case '0': { flags |= ZEROPAD; goto rflag; } case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': { n = 0; do { n = 10 * n + (ch - '0'); ch = *fmt++; } while(is_digit(ch)); width = n; goto reswitch; } case 'c': { if (!llen) { goto done; } if (curparam >= numparams) { if (pFailPhrase != NULL) { *pFailPhrase = NULL; } return false; } char *c = (char *)params[curparam]; curparam++; *buf_p++ = *c; llen--; arg++; break; } case 'b': { if (curparam >= numparams) { if (pFailPhrase != NULL) { *pFailPhrase = NULL; } return false; } int *value = (int *)params[curparam]; curparam++; AddBinary(&buf_p, llen, *value, width, flags); arg++; break; } case 'd': case 'i': { if (curparam >= numparams) { if (pFailPhrase != NULL) { *pFailPhrase = NULL; } return false; } int *value = (int *)params[curparam]; curparam++; AddInt(&buf_p, llen, *value, width, flags); arg++; break; } case 'u': { if (curparam >= numparams) { if (pFailPhrase != NULL) { *pFailPhrase = NULL; } return false; } unsigned int *value = (unsigned int *)params[curparam]; curparam++; AddUInt(&buf_p, llen, *value, width, flags); arg++; break; } case 'f': { if (curparam >= numparams) { if (pFailPhrase != NULL) { *pFailPhrase = NULL; } return false; } float *value = (float *)params[curparam]; curparam++; AddFloat(&buf_p, llen, *value, width, prec, flags); arg++; break; } case 's': { if (curparam >= numparams) { if (pFailPhrase != NULL) { *pFailPhrase = NULL; } return false; } const char *str = (const char *)params[curparam]; curparam++; AddString(&buf_p, llen, str, width, prec); arg++; break; } case 'T': case 't': { int target; const char *key; size_t out_length; Translation trans; unsigned int lang_id; if (curparam >= numparams) { if (pFailPhrase != NULL) { *pFailPhrase = NULL; } return false; } key = (const char *)(params[curparam]); curparam++; if (ch == 'T') { if (curparam >= numparams) { if (pFailPhrase != NULL) { *pFailPhrase = NULL; } return false; } target = *((int *)(params[curparam])); curparam++; } else { target = g_Translator.GetGlobalTarget(); } try_again: if (target == SOURCEMOD_SERVER_LANGUAGE) { lang_id = g_Translator.GetServerLanguage(); } else if (target >= 1 && target <= bridge->MaxClients()) { lang_id = g_Translator.GetClientLanguage(target); } else { lang_id = g_Translator.GetServerLanguage(); } if (pPhrases == NULL) { if (pFailPhrase != NULL) { *pFailPhrase = key; } return false; } if (pPhrases->FindTranslation(key, lang_id, &trans) != Trans_Okay) { if (target != SOURCEMOD_SERVER_LANGUAGE && lang_id != g_Translator.GetServerLanguage()) { target = SOURCEMOD_SERVER_LANGUAGE; goto try_again; } else if (lang_id != SOURCEMOD_LANGUAGE_ENGLISH) { if (pPhrases->FindTranslation(key, SOURCEMOD_LANGUAGE_ENGLISH, &trans) != Trans_Okay) { if (pFailPhrase != NULL) { *pFailPhrase = key; } return false; } } else { if (pFailPhrase != NULL) { *pFailPhrase = key; } return false; } } if (trans.fmt_count) { unsigned int i; void *new_params[MAX_TRANSLATE_PARAMS]; if (curparam + trans.fmt_count > numparams) { if (pFailPhrase != NULL) { *pFailPhrase = NULL; } return false; } /* Copy the array and re-order the stack */ memcpy(new_params, params, sizeof(void *) * numparams); for (i = 0; i < trans.fmt_count; i++) { new_params[curparam + i] = const_cast(params[curparam + trans.fmt_order[i]]); } if (!gnprintf(buf_p, llen, trans.szPhrase, pPhrases, new_params, numparams, curparam, &out_length, pFailPhrase)) { return false; } } else { if (!gnprintf(buf_p, llen, trans.szPhrase, pPhrases, params, numparams, curparam, &out_length, pFailPhrase)) { return false; } } buf_p += out_length; llen -= out_length; break; } case 'X': { if (curparam >= numparams) { if (pFailPhrase != NULL) { *pFailPhrase = NULL; } return false; } unsigned int *value = (unsigned int *)params[curparam]; curparam++; flags |= UPPERDIGITS; AddHex(&buf_p, llen, *value, width, flags); arg++; break; } case 'x': { if (curparam >= numparams) { if (pFailPhrase != NULL) { *pFailPhrase = NULL; } return false; } unsigned int *value = (unsigned int *)params[curparam]; curparam++; AddHex(&buf_p, llen, *value, width, flags); arg++; break; } case '%': { if (!llen) { goto done; } *buf_p++ = ch; llen--; break; } case '\0': { if (!llen) { goto done; } *buf_p++ = '%'; llen--; goto done; } default: { if (!llen) { goto done; } *buf_p++ = ch; llen--; break; } } } done: *buf_p = '\0'; if (pOutLength != NULL) { *pOutLength = (maxlen - llen - 1); } return true; } size_t atcprintf(char *buffer, size_t maxlen, const char *format, IPluginContext *pCtx, const cell_t *params, int *param) { if (!buffer || !maxlen) { return 0; } int arg; int args = params[0]; char *buf_p; char ch; int flags; int width; int prec; int n; char sign; const char *fmt; size_t llen = maxlen - 1; buf_p = buffer; arg = *param; fmt = format; while (true) { // run through the format string until we hit a '%' or '\0' for (ch = *fmt; llen && ((ch = *fmt) != '\0') && (ch != '%'); fmt++) { *buf_p++ = ch; llen--; } if ((ch == '\0') || (llen <= 0)) { goto done; } // skip over the '%' fmt++; // reset formatting state flags = 0; width = 0; prec = -1; sign = '\0'; rflag: ch = *fmt++; reswitch: switch(ch) { case '-': { flags |= LADJUST; goto rflag; } case '.': { n = 0; while(is_digit((ch = *fmt++))) { n = 10 * n + (ch - '0'); } prec = (n < 0) ? -1 : n; goto reswitch; } case '0': { flags |= ZEROPAD; goto rflag; } case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': { n = 0; do { n = 10 * n + (ch - '0'); ch = *fmt++; } while(is_digit(ch)); width = n; goto reswitch; } case 'c': { CHECK_ARGS(0); if (!llen) { goto done; } char *c; pCtx->LocalToString(params[arg], &c); *buf_p++ = *c; llen--; arg++; break; } case 'b': { CHECK_ARGS(0); cell_t *value; pCtx->LocalToPhysAddr(params[arg], &value); AddBinary(&buf_p, llen, *value, width, flags); arg++; break; } case 'd': case 'i': { CHECK_ARGS(0); cell_t *value; pCtx->LocalToPhysAddr(params[arg], &value); AddInt(&buf_p, llen, static_cast(*value), width, flags); arg++; break; } case 'u': { CHECK_ARGS(0); cell_t *value; pCtx->LocalToPhysAddr(params[arg], &value); AddUInt(&buf_p, llen, static_cast(*value), width, flags); arg++; break; } case 'f': { CHECK_ARGS(0); cell_t *value; pCtx->LocalToPhysAddr(params[arg], &value); AddFloat(&buf_p, llen, sp_ctof(*value), width, prec, flags); arg++; break; } case 'L': { CHECK_ARGS(0); cell_t *value; pCtx->LocalToPhysAddr(params[arg], &value); char buffer[255]; if (*value) { const char *name; const char *auth; int userid; if (!bridge->DescribePlayer(*value, &name, &auth, &userid)) return pCtx->ThrowNativeError("Client index %d is invalid", *value); ke::SafeSprintf(buffer, sizeof(buffer), "%s<%d><%s><>", name, userid, auth); } else { ke::SafeSprintf(buffer, sizeof(buffer), "Console<0>"); } AddString(&buf_p, llen, buffer, width, prec); arg++; break; } case 'N': { CHECK_ARGS(0); cell_t *value; pCtx->LocalToPhysAddr(params[arg], &value); const char *name = "Console"; if (*value) { if (!bridge->DescribePlayer(*value, &name, nullptr, nullptr)) return pCtx->ThrowNativeError("Client index %d is invalid", *value); } AddString(&buf_p, llen, name, width, prec); arg++; break; } case 's': { CHECK_ARGS(0); char *str; pCtx->LocalToString(params[arg], &str); AddString(&buf_p, llen, str, width, prec); arg++; break; } case 'T': { CHECK_ARGS(1); char *key; bool error; size_t res; cell_t *target; pCtx->LocalToString(params[arg++], &key); pCtx->LocalToPhysAddr(params[arg++], &target); res = Translate(buf_p, llen, pCtx, key, *target, params, &arg, &error); if (error) { return 0; } buf_p += res; llen -= res; break; } case 't': { CHECK_ARGS(0); char *key; bool error; size_t res; cell_t target = bridge->GetGlobalTarget(); pCtx->LocalToString(params[arg++], &key); res = Translate(buf_p, llen, pCtx, key, target, params, &arg, &error); if (error) { return 0; } buf_p += res; llen -= res; break; } case 'X': { CHECK_ARGS(0); cell_t *value; pCtx->LocalToPhysAddr(params[arg], &value); flags |= UPPERDIGITS; AddHex(&buf_p, llen, static_cast(*value), width, flags); arg++; break; } case 'x': { CHECK_ARGS(0); cell_t *value; pCtx->LocalToPhysAddr(params[arg], &value); AddHex(&buf_p, llen, static_cast(*value), width, flags); arg++; break; } case '%': { if (!llen) { goto done; } *buf_p++ = ch; llen--; break; } case '\0': { if (!llen) { goto done; } *buf_p++ = '%'; llen--; goto done; } default: { if (!llen) { goto done; } *buf_p++ = ch; llen--; break; } } } done: *buf_p = '\0'; *param = arg; return (maxlen - llen - 1); }