683 lines
15 KiB
C++
683 lines
15 KiB
C++
/**
|
|
* vim: set ts=4 sw=4 tw=99 noet :
|
|
* =============================================================================
|
|
* SourceMod
|
|
* Copyright (C) 2004-2008 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 <http://www.gnu.org/licenses/>.
|
|
*
|
|
* 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 <http://www.sourcemod.net/license.php>.
|
|
*
|
|
* Version: $Id$
|
|
*/
|
|
|
|
#include "common_logic.h"
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <ITextParsers.h>
|
|
#include <ctype.h>
|
|
#include "stringutil.h"
|
|
|
|
inline const char *_strstr(const char *str, const char *substr)
|
|
{
|
|
#ifdef PLATFORM_WINDOWS
|
|
return strstr(str, substr);
|
|
#elif defined PLATFORM_LINUX || defined PLATFORM_APPLE
|
|
return (const char *)strstr(str, substr);
|
|
#endif
|
|
}
|
|
|
|
/*********************************************
|
|
* *
|
|
* STRING MANIPULATION NATIVE IMPLEMENTATIONS *
|
|
* *
|
|
*********************************************/
|
|
|
|
static cell_t sm_strlen(IPluginContext *pCtx, const cell_t *params)
|
|
{
|
|
char *str;
|
|
pCtx->LocalToString(params[1], &str);
|
|
|
|
return strlen(str);
|
|
}
|
|
|
|
static cell_t sm_contain(IPluginContext *pCtx, const cell_t *params)
|
|
{
|
|
typedef const char *(*STRSEARCH)(const char *, const char *);
|
|
STRSEARCH func;
|
|
char *str, *substr;
|
|
|
|
pCtx->LocalToString(params[1], &str);
|
|
pCtx->LocalToString(params[2], &substr);
|
|
|
|
func = (params[3]) ? _strstr : stristr;
|
|
const char *pos = func(str, substr);
|
|
if (pos)
|
|
{
|
|
return (pos - str);
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
static cell_t sm_strcmp(IPluginContext *pCtx, const cell_t *params)
|
|
{
|
|
typedef int (*STRCOMPARE)(const char *, const char *);
|
|
STRCOMPARE func;
|
|
char *str1, *str2;
|
|
|
|
pCtx->LocalToString(params[1], &str1);
|
|
pCtx->LocalToString(params[2], &str2);
|
|
|
|
func = (params[3]) ? strcmp : stricmp;
|
|
|
|
return (func(str1, str2));
|
|
}
|
|
|
|
static cell_t sm_strncmp(IPluginContext *pCtx, const cell_t *params)
|
|
{
|
|
char *str1, *str2;
|
|
|
|
pCtx->LocalToString(params[1], &str1);
|
|
pCtx->LocalToString(params[2], &str2);
|
|
|
|
if (params[4])
|
|
{
|
|
return strncmp(str1, str2, (size_t)params[3]);
|
|
} else {
|
|
return strncasecmp(str1, str2, (size_t)params[3]);
|
|
}
|
|
}
|
|
|
|
static cell_t sm_strcopy(IPluginContext *pCtx, const cell_t *params)
|
|
{
|
|
char *dest, *src;
|
|
|
|
pCtx->LocalToString(params[1], &dest);
|
|
pCtx->LocalToString(params[3], &src);
|
|
|
|
return strncopy(dest, src, params[2]);
|
|
}
|
|
|
|
static cell_t sm_strconvint(IPluginContext *pCtx, const cell_t *params)
|
|
{
|
|
char *str, *dummy;
|
|
pCtx->LocalToString(params[1], &str);
|
|
|
|
return static_cast<cell_t>(strtoul(str, &dummy, params[2]));
|
|
}
|
|
|
|
static cell_t StringToIntEx(IPluginContext *pCtx, const cell_t *params)
|
|
{
|
|
char *str, *dummy = NULL;
|
|
cell_t *addr;
|
|
pCtx->LocalToString(params[1], &str);
|
|
pCtx->LocalToPhysAddr(params[2], &addr);
|
|
|
|
*addr = static_cast<cell_t>(strtoul(str, &dummy, params[3]));
|
|
|
|
return dummy - str;
|
|
}
|
|
|
|
static cell_t sm_numtostr(IPluginContext *pCtx, const cell_t *params)
|
|
{
|
|
char *str;
|
|
pCtx->LocalToString(params[2], &str);
|
|
size_t res = smcore.Format(str, params[3], "%d", params[1]);
|
|
|
|
return static_cast<cell_t>(res);
|
|
}
|
|
|
|
static cell_t sm_strtofloat(IPluginContext *pCtx, const cell_t *params)
|
|
{
|
|
char *str, *dummy;
|
|
pCtx->LocalToString(params[1], &str);
|
|
|
|
float val = (float)strtod(str, &dummy);
|
|
|
|
return sp_ftoc(val);
|
|
}
|
|
|
|
static cell_t StringToFloatEx(IPluginContext *pCtx, const cell_t *params)
|
|
{
|
|
char *str, *dummy = NULL;
|
|
cell_t *addr;
|
|
pCtx->LocalToString(params[1], &str);
|
|
pCtx->LocalToPhysAddr(params[2], &addr);
|
|
|
|
float val = (float)strtod(str, &dummy);
|
|
|
|
*addr = sp_ftoc(val);
|
|
|
|
return dummy - str;
|
|
}
|
|
|
|
static cell_t sm_floattostr(IPluginContext *pCtx, const cell_t *params)
|
|
{
|
|
char *str;
|
|
pCtx->LocalToString(params[2], &str);
|
|
size_t res = smcore.Format(str, params[3], "%f", sp_ctof(params[1]));
|
|
|
|
return static_cast<cell_t>(res);
|
|
}
|
|
|
|
static cell_t sm_formatex(IPluginContext *pCtx, const cell_t *params)
|
|
{
|
|
char *buf, *fmt;
|
|
size_t res;
|
|
int arg = 4;
|
|
|
|
pCtx->LocalToString(params[1], &buf);
|
|
pCtx->LocalToString(params[3], &fmt);
|
|
res = smcore.atcprintf(buf, static_cast<size_t>(params[2]), fmt, pCtx, params, &arg);
|
|
|
|
return static_cast<cell_t>(res);
|
|
}
|
|
|
|
class StaticCharBuf
|
|
{
|
|
char *buffer;
|
|
size_t max_size;
|
|
public:
|
|
StaticCharBuf() : buffer(NULL), max_size(0)
|
|
{
|
|
}
|
|
~StaticCharBuf()
|
|
{
|
|
free(buffer);
|
|
}
|
|
char* GetWithSize(size_t len)
|
|
{
|
|
if (len > max_size)
|
|
{
|
|
buffer = (char *)realloc(buffer, len);
|
|
max_size = len;
|
|
}
|
|
return buffer;
|
|
}
|
|
};
|
|
|
|
static char g_formatbuf[2048];
|
|
static StaticCharBuf g_extrabuf;
|
|
static cell_t sm_format(IPluginContext *pCtx, const cell_t *params)
|
|
{
|
|
char *buf, *fmt, *destbuf;
|
|
cell_t start_addr, end_addr, maxparam;
|
|
size_t res, maxlen;
|
|
int arg = 4;
|
|
bool copy = false;
|
|
char *__copy_buf;
|
|
|
|
pCtx->LocalToString(params[1], &destbuf);
|
|
pCtx->LocalToString(params[3], &fmt);
|
|
|
|
maxlen = static_cast<size_t>(params[2]);
|
|
start_addr = params[1];
|
|
end_addr = params[1] + maxlen;
|
|
maxparam = params[0];
|
|
|
|
for (cell_t i=3; i<=maxparam; i++)
|
|
{
|
|
if ((params[i] >= start_addr) && (params[i] <= end_addr))
|
|
{
|
|
copy = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (copy)
|
|
{
|
|
if (maxlen > sizeof(g_formatbuf))
|
|
{
|
|
__copy_buf = g_extrabuf.GetWithSize(maxlen);
|
|
}
|
|
else
|
|
{
|
|
__copy_buf = g_formatbuf;
|
|
}
|
|
}
|
|
|
|
buf = (copy) ? __copy_buf : destbuf;
|
|
res = smcore.atcprintf(buf, maxlen, fmt, pCtx, params, &arg);
|
|
|
|
if (copy)
|
|
{
|
|
memcpy(destbuf, __copy_buf, res+1);
|
|
}
|
|
|
|
return static_cast<cell_t>(res);
|
|
}
|
|
|
|
static cell_t sm_vformat(IPluginContext *pContext, const cell_t *params)
|
|
{
|
|
int vargPos = static_cast<int>(params[4]);
|
|
|
|
/* Get the parent parameter array */
|
|
cell_t *local_params = pContext->GetLocalParams();
|
|
|
|
cell_t max = local_params[0];
|
|
if (vargPos > (int)max + 1)
|
|
{
|
|
return pContext->ThrowNativeError("Argument index is invalid: %d", vargPos);
|
|
}
|
|
|
|
cell_t addr_start = params[1];
|
|
cell_t addr_end = addr_start + params[2];
|
|
bool copy = false;
|
|
for (int i=vargPos; i<=max; i++)
|
|
{
|
|
/* Does this clip bounds? */
|
|
if ((local_params[i] >= addr_start)
|
|
&& (local_params[i] <= addr_end))
|
|
{
|
|
copy = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Get destination info */
|
|
char *format, *destination;
|
|
size_t maxlen = static_cast<size_t>(params[2]);
|
|
|
|
if (copy)
|
|
{
|
|
destination = g_formatbuf;
|
|
} else {
|
|
pContext->LocalToString(params[1], &destination);
|
|
}
|
|
|
|
pContext->LocalToString(params[3], &format);
|
|
|
|
size_t total = smcore.atcprintf(destination, maxlen, format, pContext, local_params, &vargPos);
|
|
|
|
/* Perform copy-on-write if we need to */
|
|
if (copy)
|
|
{
|
|
pContext->StringToLocal(params[1], maxlen, g_formatbuf);
|
|
}
|
|
|
|
return total;
|
|
}
|
|
|
|
/* :TODO: make this UTF8 safe */
|
|
static cell_t BreakString(IPluginContext *pContext, const cell_t *params)
|
|
{
|
|
const char *input;
|
|
char *out;
|
|
size_t outMax;
|
|
|
|
/* Get parameters */
|
|
pContext->LocalToString(params[1], (char **)&input);
|
|
pContext->LocalToString(params[2], &out);
|
|
outMax = params[3];
|
|
|
|
const char *inptr = input;
|
|
/* Eat up whitespace */
|
|
while (*inptr != '\0' && textparsers->IsWhitespace(inptr))
|
|
{
|
|
inptr++;
|
|
}
|
|
|
|
if (*inptr == '\0')
|
|
{
|
|
if (outMax)
|
|
{
|
|
*out = '\0';
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
const char *start, *end = NULL;
|
|
|
|
bool quoted = (*inptr == '"');
|
|
if (quoted)
|
|
{
|
|
inptr++;
|
|
start = inptr;
|
|
/* Read input until we reach a quote. */
|
|
while (*inptr != '\0' && *inptr != '"')
|
|
{
|
|
/* Update the end point, increment the stream. */
|
|
end = inptr++;
|
|
}
|
|
/* Read one more token if we reached an end quote */
|
|
if (*inptr == '"')
|
|
{
|
|
inptr++;
|
|
}
|
|
} else {
|
|
start = inptr;
|
|
/* Read input until we reach a space */
|
|
while (*inptr != '\0' && !textparsers->IsWhitespace(inptr))
|
|
{
|
|
/* Update the end point, increment the stream. */
|
|
end = inptr++;
|
|
}
|
|
}
|
|
|
|
/* Copy the string we found, if necessary */
|
|
if (end == NULL)
|
|
{
|
|
if (outMax)
|
|
{
|
|
*out = '\0';
|
|
}
|
|
} else if (outMax) {
|
|
char *outptr = out;
|
|
outMax--;
|
|
for (const char *ptr=start;
|
|
(ptr <= end) && ((unsigned)(outptr - out) < (outMax));
|
|
ptr++, outptr++)
|
|
{
|
|
*outptr = *ptr;
|
|
}
|
|
*outptr = '\0';
|
|
}
|
|
|
|
/* Consume more of the string until we reach non-whitespace */
|
|
while (*inptr != '\0' && textparsers->IsWhitespace(inptr))
|
|
{
|
|
inptr++;
|
|
}
|
|
|
|
if (*inptr == '\0')
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
return inptr - input;
|
|
}
|
|
|
|
static cell_t GetCharBytes(IPluginContext *pContext, const cell_t *params)
|
|
{
|
|
char *str;
|
|
pContext->LocalToString(params[1], &str);
|
|
|
|
return _GetUTF8CharBytes(str);
|
|
};
|
|
|
|
static cell_t IsCharAlpha(IPluginContext *pContext, const cell_t *params)
|
|
{
|
|
char chr = params[1];
|
|
|
|
if (_GetUTF8CharBytes(&chr) != 1)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
return isalpha(chr);
|
|
}
|
|
|
|
static cell_t IsCharNumeric(IPluginContext *pContext, const cell_t *params)
|
|
{
|
|
char chr = params[1];
|
|
|
|
if (_GetUTF8CharBytes(&chr) != 1)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
return isdigit(chr);
|
|
}
|
|
|
|
static cell_t IsCharSpace(IPluginContext *pContext, const cell_t *params)
|
|
{
|
|
char chr = params[1];
|
|
|
|
if (_GetUTF8CharBytes(&chr) != 1)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
return isspace(chr);
|
|
}
|
|
|
|
static cell_t IsCharMB(IPluginContext *pContext, const cell_t *params)
|
|
{
|
|
char chr = params[1];
|
|
|
|
unsigned int bytes = _GetUTF8CharBytes(&chr);
|
|
if (bytes == 1)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
return bytes;
|
|
}
|
|
|
|
static cell_t IsCharUpper(IPluginContext *pContext, const cell_t *params)
|
|
{
|
|
char chr = params[1];
|
|
|
|
if (_GetUTF8CharBytes(&chr) != 1)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
return isupper(chr);
|
|
}
|
|
|
|
static cell_t IsCharLower(IPluginContext *pContext, const cell_t *params)
|
|
{
|
|
char chr = params[1];
|
|
|
|
if (_GetUTF8CharBytes(&chr) != 1)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
return islower(chr);
|
|
}
|
|
|
|
static cell_t ReplaceString(IPluginContext *pContext, const cell_t *params)
|
|
{
|
|
char *text, *search, *replace;
|
|
size_t maxlength;
|
|
bool caseSensitive;
|
|
|
|
pContext->LocalToString(params[1], &text);
|
|
pContext->LocalToString(params[3], &search);
|
|
pContext->LocalToString(params[4], &replace);
|
|
maxlength = (size_t)params[2];
|
|
|
|
/* Account for old binary plugins that won't pass the last parameter */
|
|
if (params[0] == 5)
|
|
{
|
|
caseSensitive = (bool)params[5];
|
|
}
|
|
else
|
|
{
|
|
caseSensitive = true;
|
|
}
|
|
|
|
if (search[0] == '\0')
|
|
{
|
|
return pContext->ThrowNativeError("Cannot replace searches of empty strings");
|
|
}
|
|
|
|
return UTIL_ReplaceAll(text, maxlength, search, replace, caseSensitive);
|
|
}
|
|
|
|
static cell_t ReplaceStringEx(IPluginContext *pContext, const cell_t *params)
|
|
{
|
|
char *text, *search, *replace;
|
|
size_t maxlength;
|
|
bool caseSensitive;
|
|
|
|
pContext->LocalToString(params[1], &text);
|
|
pContext->LocalToString(params[3], &search);
|
|
pContext->LocalToString(params[4], &replace);
|
|
maxlength = (size_t)params[2];
|
|
|
|
size_t searchLen = (params[5] == -1) ? strlen(search) : (size_t)params[5];
|
|
size_t replaceLen = (params[6] == -1) ? strlen(replace) : (size_t)params[6];
|
|
|
|
/* Account for old binary plugins that won't pass the last parameter */
|
|
if (params[0] == 7)
|
|
{
|
|
caseSensitive = (bool)params[7];
|
|
}
|
|
else
|
|
{
|
|
caseSensitive = true;
|
|
}
|
|
|
|
if (searchLen == 0)
|
|
{
|
|
return pContext->ThrowNativeError("Cannot replace searches of empty strings");
|
|
}
|
|
|
|
char *ptr = UTIL_ReplaceEx(text, maxlength, search, searchLen, replace, replaceLen, caseSensitive);
|
|
|
|
if (ptr == NULL)
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
return ptr - text;
|
|
}
|
|
|
|
static cell_t TrimString(IPluginContext *pContext, const cell_t *params)
|
|
{
|
|
char *str;
|
|
pContext->LocalToString(params[1], &str);
|
|
|
|
size_t chars = strlen(str);
|
|
|
|
if (chars == 0)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
char *end = str + chars - 1;
|
|
|
|
/* Iterate backwards through string until we reach first non-whitespace char */
|
|
while (end >= str && textparsers->IsWhitespace(end))
|
|
{
|
|
end--;
|
|
}
|
|
|
|
/* Replace first whitespace char (at the end) with null terminator */
|
|
*(end + 1) = '\0';
|
|
|
|
/* Iterate forwards through string until first non-whitespace char is reached */
|
|
while (textparsers->IsWhitespace(str))
|
|
{
|
|
str++;
|
|
}
|
|
|
|
size_t bytes;
|
|
pContext->StringToLocalUTF8(params[1], chars + 1, str, &bytes);
|
|
|
|
return bytes;
|
|
}
|
|
|
|
static cell_t SplitString(IPluginContext *pContext, const cell_t *params)
|
|
{
|
|
char *text, *split;
|
|
|
|
pContext->LocalToString(params[1], &text);
|
|
pContext->LocalToString(params[2], &split);
|
|
|
|
size_t maxLen = (size_t)params[4];
|
|
size_t textLen = strlen(text);
|
|
size_t splitLen = strlen(split);
|
|
|
|
if (splitLen > textLen)
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
/**
|
|
* Note that it's <= ... you could also just add 1,
|
|
* but this is a bit nicer
|
|
*/
|
|
for (size_t i=0; i<=textLen - splitLen; i++)
|
|
{
|
|
if (strncmp(&text[i], split, splitLen) == 0)
|
|
{
|
|
/* Split hereeeee */
|
|
if (i >= maxLen)
|
|
{
|
|
pContext->StringToLocalUTF8(params[3], maxLen, text, NULL);
|
|
} else {
|
|
pContext->StringToLocalUTF8(params[3], i+1, text, NULL);
|
|
}
|
|
return (cell_t)(i + splitLen);
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
static cell_t StripQuotes(IPluginContext *pContext, const cell_t *params)
|
|
{
|
|
char *text;
|
|
size_t length;
|
|
|
|
pContext->LocalToString(params[1], &text);
|
|
length = strlen(text);
|
|
|
|
if (text[0] == '"' && text[length-1] == '"')
|
|
{
|
|
/* Null-terminate */
|
|
text[--length] = '\0';
|
|
/* Move the remaining bytes (including null terminator) down by one */
|
|
memmove(&text[0], &text[1], length);
|
|
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
REGISTER_NATIVES(basicStrings)
|
|
{
|
|
{"BreakString", BreakString},
|
|
{"FloatToString", sm_floattostr},
|
|
{"Format", sm_format},
|
|
{"FormatEx", sm_formatex},
|
|
{"GetCharBytes", GetCharBytes},
|
|
{"IntToString", sm_numtostr},
|
|
{"IsCharAlpha", IsCharAlpha},
|
|
{"IsCharLower", IsCharLower},
|
|
{"IsCharMB", IsCharMB},
|
|
{"IsCharNumeric", IsCharNumeric},
|
|
{"IsCharSpace", IsCharSpace},
|
|
{"IsCharUpper", IsCharUpper},
|
|
{"ReplaceString", ReplaceString},
|
|
{"ReplaceStringEx", ReplaceStringEx},
|
|
{"SplitString", SplitString},
|
|
{"strlen", sm_strlen},
|
|
{"StrContains", sm_contain},
|
|
{"strcmp", sm_strcmp},
|
|
{"strncmp", sm_strncmp},
|
|
{"strcopy", sm_strcopy},
|
|
{"StringToInt", sm_strconvint},
|
|
{"StringToIntEx", StringToIntEx},
|
|
{"StringToFloat", sm_strtofloat},
|
|
{"StringToFloatEx", StringToFloatEx},
|
|
{"StripQuotes", StripQuotes},
|
|
{"TrimString", TrimString},
|
|
{"VFormat", sm_vformat},
|
|
{NULL, NULL},
|
|
};
|