Improve UTF-8 support in some natives (bug 6475) (#407)

* Compile as static library, update AMBuildScript and link to core

* Update VS project files to include the library

* Add UTF-8 Rewind library (v1.5.1) to third_party directory

* Update ACKNOWLEDGEMENTS.txt

* Move AMXX buffer in its own function

* Move constants from string.inc to string_const.inc and update project files

* Move stocks from string.inc to string_stocks.inc and update project files

* Improve UTF-8 support in containi() and update documentation

* Improve UTF-8 support in strcmp() and update documentation

* Improve UTF-8 support in strfind() and update documentation

Worth to be noted that this native with ignorecase set was not working properly. So broken that no one reported the issue.
This adds also a safety check for "pos" parameter to not go < 0.

* Improve UTF-8 support in strncmp() and update documentation

* Improve UTF-8 support in equali() and update documentation

* Add an option to some UTF-8 Rewind functions for avoiding invalid data to be replaced

By default it replaces any invalid byte or sequence of bytes by 0xFFFD (3 bytes). It can be problematic when the input buffer is not changed (from a plugin) and that some natives need to calculate a position from the converted string. With such replacement, the position is displaced due the final string length being larger.

This compiles the library as C++, because I added some silly param with a default default value which is not supported by C.

* Improve UTF-8 support in replace_string/ex() and update documentation

* Add is_string_category() and update documentation

* Update a little testsuite plugin (and fix linux compilation)

* Add mb_strotolower/upper() and update documentation

* Add mb_ucfirst() and update documentation

* Add mb_strtotile() and update documentation

* Improve UTF-8 support in get_players() and find_player() with name/case insenstive flags set

* Fix KliPPy's complain
This commit is contained in:
Vincent Herbet
2017-08-05 10:32:16 +02:00
committed by GitHub
parent 07c3d49cfa
commit ab854ec035
34 changed files with 20166 additions and 532 deletions

View File

@ -32,7 +32,7 @@ elif builder.target_platform == 'windows':
]
binary.compiler.linkflags += jit_objects
binary.compiler.linkflags += [AMXX.zlib.binary, AMXX.hashing.binary]
binary.compiler.linkflags += [AMXX.zlib.binary, AMXX.hashing.binary, AMXX.utf8rewind.binary]
if builder.target_platform == 'mac':
binary.compiler.postlink += [

View File

@ -2267,7 +2267,7 @@ static cell AMX_NATIVE_CALL get_players(AMX *amx, cell *params) /* 4 param */
{
if (flags & 64)
{
if (stristr(pPlayer->name.chars(), sptemp) == NULL)
if (utf8stristr(pPlayer->name.chars(), sptemp) == NULL)
continue;
}
else if (strstr(pPlayer->name.chars(), sptemp) == NULL)
@ -2301,7 +2301,7 @@ static cell AMX_NATIVE_CALL find_player(AMX *amx, cell *params) /* 1 param */
// Switch for the l flag
if (flags & 2048)
func = strcasecmp;
func = utf8strcasecmp;
else
func = strcmp;
@ -2327,7 +2327,7 @@ static cell AMX_NATIVE_CALL find_player(AMX *amx, cell *params) /* 1 param */
{
if (flags & 2048)
{
if (stristr(pPlayer->name.chars(), sptemp) == NULL)
if (utf8stristr(pPlayer->name.chars(), sptemp) == NULL)
continue;
}
else if (strstr(pPlayer->name.chars(), sptemp) == NULL)

View File

@ -113,6 +113,8 @@ extern AMX_NATIVE_INFO g_GameConfigNatives[];
#define SETCLIENTLISTENING (*g_engfuncs.pfnVoice_SetClientListening)
#define SETCLIENTMAXSPEED (*g_engfuncs.pfnSetClientMaxspeed)
#define MAX_BUFFER_LENGTH 16384
char* UTIL_SplitHudMessage(register const char *src);
int UTIL_ReadFlags(const char* c);
@ -130,11 +132,16 @@ void UTIL_TeamInfo(edict_t *pEntity, int playerIndex, const char *pszTeamName);
template <typename D> int UTIL_CheckValidChar(D *c);
template <typename D, typename S> unsigned int strncopy(D *dest, const S *src, size_t count);
unsigned int UTIL_GetUTF8CharBytes(const char *stream);
unsigned int UTIL_ReplaceAll(char *subject, size_t maxlength, const char *search, const char *replace, bool caseSensitive);
size_t UTIL_ReplaceAll(char *subject, size_t maxlength, const char *search, const char *replace, bool caseSensitive);
size_t UTIL_ReplaceAll(char *subject, size_t maxlength, const char *search, size_t searchLen, const char *replace, size_t replaceLen, bool caseSensitive);
char *UTIL_ReplaceEx(char *subject, size_t maxLen, const char *search, size_t searchLen, const char *replace, size_t replaceLen, bool caseSensitive);
void UTIL_TrimLeft(char *buffer);
void UTIL_TrimRight(char *buffer);
char* utf8stristr(const char *string1, const char *string2);
int utf8strncasecmp(const char *string1, const char *string2, size_t n);
int utf8strcasecmp(const char *string1, const char *string2);
#define GET_PLAYER_POINTER(e) (&g_players[ENTINDEX(e)])
//#define GET_PLAYER_POINTER(e) (&g_players[(((int)e-g_edict_point)/sizeof(edict_t))])
#define GET_PLAYER_POINTER_I(i) (&g_players[i])

View File

@ -60,8 +60,8 @@
</Midl>
<ClCompile>
<Optimization>Disabled</Optimization>
<AdditionalIncludeDirectories>..\;..\..\public;..\..\public\memtools;..\..\third_party;..\..\third_party\zlib;..\..\third_party\hashing;..\..\public\sdk;..\..\public\amtl;..\..\third_party;..\..\third_party\hashing;$(METAMOD)\metamod;$(HLSDK)\common;$(HLSDK)\engine;$(HLSDK)\dlls;$(HLSDK)\pm_shared;$(HLSDK)\public;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PreprocessorDefinitions>WIN32;_CRT_SECURE_NO_DEPRECATE;_CRT_SECURE_NO_WARNINGS;_CRT_NONSTDC_NO_DEPRECATE;_DEBUG;_WINDOWS;_USRDLL;amxmodx_EXPORTS;PAWN_CELL_SIZE=32;ASM32;JIT;_CRT_SECURE_NO_DEPRECATE;HAVE_STDINT_H;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalIncludeDirectories>..\;..\..\public;..\..\public\memtools;..\..\third_party;..\..\third_party\zlib;..\..\third_party\hashing;..\..\third_party\utf8rewind;..\..\public\sdk;..\..\public\amtl;..\..\third_party;..\..\third_party\hashing;$(METAMOD)\metamod;$(HLSDK)\common;$(HLSDK)\engine;$(HLSDK)\dlls;$(HLSDK)\pm_shared;$(HLSDK)\public;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PreprocessorDefinitions>WIN32;_CRT_SECURE_NO_DEPRECATE;_CRT_SECURE_NO_WARNINGS;_CRT_NONSTDC_NO_DEPRECATE;UTF8PROC_EXPORTS;_DEBUG;_WINDOWS;_USRDLL;amxmodx_EXPORTS;PAWN_CELL_SIZE=32;ASM32;JIT;_CRT_SECURE_NO_DEPRECATE;HAVE_STDINT_H;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
<StructMemberAlignment>4Bytes</StructMemberAlignment>
@ -110,8 +110,8 @@
<IntrinsicFunctions>true</IntrinsicFunctions>
<FavorSizeOrSpeed>Speed</FavorSizeOrSpeed>
<OmitFramePointers>true</OmitFramePointers>
<AdditionalIncludeDirectories>..\;..\..\public;..\..\public\memtools;..\..\third_party;..\..\third_party\zlib;..\..\third_party\hashing;..\..\third_party;..\..\third_party\hashing;..\..\public\sdk;..\..\public\amtl;..\..\third_party;..\..\third_party\hashing;$(METAMOD)\metamod;$(HLSDK)\common;$(HLSDK)\engine;$(HLSDK)\dlls;$(HLSDK)\pm_shared;$(HLSDK)\public;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PreprocessorDefinitions>WIN32;_CRT_SECURE_NO_DEPRECATE;_CRT_SECURE_NO_WARNINGS;_CRT_NONSTDC_NO_DEPRECATE;NDEBUG;_WINDOWS;_USRDLL;amxmodx_EXPORTS;JIT;ASM32;PAWN_CELL_SIZE=32;HAVE_STDINT_H;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalIncludeDirectories>..\;..\..\public;..\..\public\memtools;..\..\third_party;..\..\third_party\zlib;..\..\third_party\hashing;..\..\third_party\utf8rewind;..\..\third_party;..\..\third_party\hashing;..\..\public\sdk;..\..\public\amtl;..\..\third_party;..\..\third_party\hashing;$(METAMOD)\metamod;$(HLSDK)\common;$(HLSDK)\engine;$(HLSDK)\dlls;$(HLSDK)\pm_shared;$(HLSDK)\public;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PreprocessorDefinitions>WIN32;_CRT_SECURE_NO_DEPRECATE;_CRT_SECURE_NO_WARNINGS;_CRT_NONSTDC_NO_DEPRECATE;UTF8PROC_EXPORTS;NDEBUG;_WINDOWS;_USRDLL;amxmodx_EXPORTS;JIT;ASM32;PAWN_CELL_SIZE=32;HAVE_STDINT_H;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<IgnoreStandardIncludePath>false</IgnoreStandardIncludePath>
<StringPooling>true</StringPooling>
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
@ -180,6 +180,42 @@
<ObjectFileName Condition="'$(Configuration)|$(Platform)'=='JITDebug|Win32'">$(IntDir)hashing\</ObjectFileName>
<ObjectFileName Condition="'$(Configuration)|$(Platform)'=='JITRelease|Win32'">$(IntDir)hashing\</ObjectFileName>
</ClCompile>
<ClCompile Include="..\..\third_party\utf8rewind\internal\casemapping.c">
<CompileAs Condition="'$(Configuration)|$(Platform)'=='JITDebug|Win32'">CompileAsCpp</CompileAs>
<CompileAs Condition="'$(Configuration)|$(Platform)'=='JITRelease|Win32'">CompileAsCpp</CompileAs>
</ClCompile>
<ClCompile Include="..\..\third_party\utf8rewind\internal\codepoint.c">
<CompileAs Condition="'$(Configuration)|$(Platform)'=='JITDebug|Win32'">CompileAsCpp</CompileAs>
<CompileAs Condition="'$(Configuration)|$(Platform)'=='JITRelease|Win32'">CompileAsCpp</CompileAs>
</ClCompile>
<ClCompile Include="..\..\third_party\utf8rewind\internal\composition.c">
<CompileAs Condition="'$(Configuration)|$(Platform)'=='JITDebug|Win32'">CompileAsCpp</CompileAs>
<CompileAs Condition="'$(Configuration)|$(Platform)'=='JITRelease|Win32'">CompileAsCpp</CompileAs>
</ClCompile>
<ClCompile Include="..\..\third_party\utf8rewind\internal\database.c">
<CompileAs Condition="'$(Configuration)|$(Platform)'=='JITDebug|Win32'">CompileAsCpp</CompileAs>
<CompileAs Condition="'$(Configuration)|$(Platform)'=='JITRelease|Win32'">CompileAsCpp</CompileAs>
</ClCompile>
<ClCompile Include="..\..\third_party\utf8rewind\internal\decomposition.c">
<CompileAs Condition="'$(Configuration)|$(Platform)'=='JITDebug|Win32'">CompileAsCpp</CompileAs>
<CompileAs Condition="'$(Configuration)|$(Platform)'=='JITRelease|Win32'">CompileAsCpp</CompileAs>
</ClCompile>
<ClCompile Include="..\..\third_party\utf8rewind\internal\seeking.c">
<CompileAs Condition="'$(Configuration)|$(Platform)'=='JITDebug|Win32'">CompileAsCpp</CompileAs>
<CompileAs Condition="'$(Configuration)|$(Platform)'=='JITRelease|Win32'">CompileAsCpp</CompileAs>
</ClCompile>
<ClCompile Include="..\..\third_party\utf8rewind\internal\streaming.c">
<CompileAs Condition="'$(Configuration)|$(Platform)'=='JITDebug|Win32'">CompileAsCpp</CompileAs>
<CompileAs Condition="'$(Configuration)|$(Platform)'=='JITRelease|Win32'">CompileAsCpp</CompileAs>
</ClCompile>
<ClCompile Include="..\..\third_party\utf8rewind\unicodedatabase.c">
<CompileAs Condition="'$(Configuration)|$(Platform)'=='JITDebug|Win32'">CompileAsCpp</CompileAs>
<CompileAs Condition="'$(Configuration)|$(Platform)'=='JITRelease|Win32'">CompileAsCpp</CompileAs>
</ClCompile>
<ClCompile Include="..\..\third_party\utf8rewind\utf8rewind.c">
<CompileAs Condition="'$(Configuration)|$(Platform)'=='JITDebug|Win32'">CompileAsCpp</CompileAs>
<CompileAs Condition="'$(Configuration)|$(Platform)'=='JITRelease|Win32'">CompileAsCpp</CompileAs>
</ClCompile>
<ClCompile Include="..\..\third_party\zlib\adler32.c">
<ObjectFileName Condition="'$(Configuration)|$(Platform)'=='JITDebug|Win32'">$(IntDir)zlib\</ObjectFileName>
<ObjectFileName Condition="'$(Configuration)|$(Platform)'=='JITRelease|Win32'">$(IntDir)zlib\</ObjectFileName>
@ -326,6 +362,16 @@
<ClInclude Include="..\..\third_party\hashing\hashers\sha256.h" />
<ClInclude Include="..\..\third_party\hashing\hashers\sha3.h" />
<ClInclude Include="..\..\third_party\hashing\hashing.h" />
<ClInclude Include="..\..\third_party\utf8rewind\internal\base.h" />
<ClInclude Include="..\..\third_party\utf8rewind\internal\casemapping.h" />
<ClInclude Include="..\..\third_party\utf8rewind\internal\codepoint.h" />
<ClInclude Include="..\..\third_party\utf8rewind\internal\composition.h" />
<ClInclude Include="..\..\third_party\utf8rewind\internal\database.h" />
<ClInclude Include="..\..\third_party\utf8rewind\internal\decomposition.h" />
<ClInclude Include="..\..\third_party\utf8rewind\internal\seeking.h" />
<ClInclude Include="..\..\third_party\utf8rewind\internal\streaming.h" />
<ClInclude Include="..\..\third_party\utf8rewind\unicodedatabase.h" />
<ClInclude Include="..\..\third_party\utf8rewind\utf8rewind.h" />
<ClInclude Include="..\..\third_party\zlib\crc32.h" />
<ClInclude Include="..\..\third_party\zlib\deflate.h" />
<ClInclude Include="..\..\third_party\zlib\gzguts.h" />
@ -390,6 +436,8 @@
<None Include="..\..\plugins\include\cvars.inc" />
<None Include="..\..\plugins\include\datapack.inc" />
<None Include="..\..\plugins\include\gameconfig.inc" />
<None Include="..\..\plugins\include\string_const.inc" />
<None Include="..\..\plugins\include\string_stocks.inc" />
<None Include="..\..\plugins\include\textparse_ini.inc" />
<None Include="..\..\plugins\include\textparse_smc.inc" />
<None Include="..\amxdefn.asm" />

View File

@ -54,6 +54,12 @@
</Filter>
<Filter Include="ReSDK\engine">
<UniqueIdentifier>{04fab577-6f56-40d0-8f69-7ce1b8bf3bb9}</UniqueIdentifier>
</Filter>
<Filter Include="Third Party\UTF8Rewind">
<UniqueIdentifier>{270f3524-564f-4154-bb35-242a6faac09e}</UniqueIdentifier>
</Filter>
<Filter Include="Third Party\UTF8Rewind\internal">
<UniqueIdentifier>{295b670a-1aa3-4b80-bbf6-4ba422672274}</UniqueIdentifier>
</Filter>
</ItemGroup>
<ItemGroup>
@ -303,6 +309,33 @@
<ClCompile Include="..\..\public\resdk\mod_rehlds_api.cpp">
<Filter>ReSDK</Filter>
</ClCompile>
<ClCompile Include="..\..\third_party\utf8rewind\unicodedatabase.c">
<Filter>Third Party\UTF8Rewind</Filter>
</ClCompile>
<ClCompile Include="..\..\third_party\utf8rewind\utf8rewind.c">
<Filter>Third Party\UTF8Rewind</Filter>
</ClCompile>
<ClCompile Include="..\..\third_party\utf8rewind\internal\casemapping.c">
<Filter>Third Party\UTF8Rewind\internal</Filter>
</ClCompile>
<ClCompile Include="..\..\third_party\utf8rewind\internal\codepoint.c">
<Filter>Third Party\UTF8Rewind\internal</Filter>
</ClCompile>
<ClCompile Include="..\..\third_party\utf8rewind\internal\composition.c">
<Filter>Third Party\UTF8Rewind\internal</Filter>
</ClCompile>
<ClCompile Include="..\..\third_party\utf8rewind\internal\database.c">
<Filter>Third Party\UTF8Rewind\internal</Filter>
</ClCompile>
<ClCompile Include="..\..\third_party\utf8rewind\internal\decomposition.c">
<Filter>Third Party\UTF8Rewind\internal</Filter>
</ClCompile>
<ClCompile Include="..\..\third_party\utf8rewind\internal\seeking.c">
<Filter>Third Party\UTF8Rewind\internal</Filter>
</ClCompile>
<ClCompile Include="..\..\third_party\utf8rewind\internal\streaming.c">
<Filter>Third Party\UTF8Rewind\internal</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\amx.h">
@ -506,8 +539,35 @@
<ClInclude Include="..\CoreConfig.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\CFrameAction.h">
<Filter>Header Files</Filter>
<ClInclude Include="..\..\third_party\utf8rewind\unicodedatabase.h">
<Filter>Third Party\UTF8Rewind</Filter>
</ClInclude>
<ClInclude Include="..\..\third_party\utf8rewind\internal\base.h">
<Filter>Third Party\UTF8Rewind\internal</Filter>
</ClInclude>
<ClInclude Include="..\..\third_party\utf8rewind\internal\casemapping.h">
<Filter>Third Party\UTF8Rewind\internal</Filter>
</ClInclude>
<ClInclude Include="..\..\third_party\utf8rewind\internal\codepoint.h">
<Filter>Third Party\UTF8Rewind\internal</Filter>
</ClInclude>
<ClInclude Include="..\..\third_party\utf8rewind\internal\composition.h">
<Filter>Third Party\UTF8Rewind\internal</Filter>
</ClInclude>
<ClInclude Include="..\..\third_party\utf8rewind\internal\database.h">
<Filter>Third Party\UTF8Rewind\internal</Filter>
</ClInclude>
<ClInclude Include="..\..\third_party\utf8rewind\internal\decomposition.h">
<Filter>Third Party\UTF8Rewind\internal</Filter>
</ClInclude>
<ClInclude Include="..\..\third_party\utf8rewind\internal\seeking.h">
<Filter>Third Party\UTF8Rewind\internal</Filter>
</ClInclude>
<ClInclude Include="..\..\third_party\utf8rewind\internal\streaming.h">
<Filter>Third Party\UTF8Rewind\internal</Filter>
</ClInclude>
<ClInclude Include="..\..\third_party\utf8rewind\utf8rewind.h">
<Filter>Third Party\UTF8Rewind</Filter>
</ClInclude>
<ClInclude Include="..\..\public\resdk\common\hookchains.h">
<Filter>ReSDK\common</Filter>
@ -521,6 +581,9 @@
<ClInclude Include="..\..\public\resdk\mod_rehlds_api.h">
<Filter>ReSDK</Filter>
</ClInclude>
<ClInclude Include="..\CFrameAction.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="..\version.rc">
@ -624,6 +687,12 @@
<None Include="..\..\plugins\include\cstrike_const.inc">
<Filter>Pawn Includes</Filter>
</None>
<None Include="..\..\plugins\include\string_const.inc">
<Filter>Pawn Includes</Filter>
</None>
<None Include="..\..\plugins\include\string_stocks.inc">
<Filter>Pawn Includes</Filter>
</None>
</ItemGroup>
<ItemGroup>
<Object Include="..\Jit\helpers-x86.obj">

View File

@ -11,6 +11,7 @@
#include "amxmodx.h"
#include "format.h"
#include "binlog.h"
#include <utf8rewind.h>
const char* stristr(const char* str, const char* substr)
{
@ -169,11 +170,17 @@ extern "C" size_t get_amxstring_r(AMX *amx, cell amx_addr, char *destination, in
return dest - start;
}
char *get_amxbuffer(int id)
{
static char buffer[4][MAX_BUFFER_LENGTH];
return buffer[id];
}
char *get_amxstring(AMX *amx, cell amx_addr, int id, int& len)
{
static char buffer[4][16384];
len = get_amxstring_r(amx, amx_addr, buffer[id], sizeof(buffer[id]) - 1);
return buffer[id];
auto buffer = get_amxbuffer(id);
len = get_amxstring_r(amx, amx_addr, buffer, MAX_BUFFER_LENGTH - 1);
return buffer;
}
char *get_amxstring_null(AMX *amx, cell amx_addr, int id, int& len)
@ -302,58 +309,68 @@ static cell AMX_NATIVE_CALL replace(AMX *amx, cell *params) /* 4 param */
return 0;
}
// native replace_string(text[], maxlength, const search[], const replace[], bool:caseSensitive = true);
static cell AMX_NATIVE_CALL replace_string(AMX *amx, cell *params)
{
int len;
size_t maxlength = (size_t)params[2];
enum args { arg_count, arg_text, arg_maxlength, arg_search, arg_replace, arg_casesensitive };
char *text = get_amxstring(amx, params[1], 0, len);
const char *search = get_amxstring(amx, params[3], 1, len);
const char *replace = get_amxstring(amx, params[4], 2, len);
auto textLength = 0;
auto searchLength = 0;
auto replaceLength = 0;
bool caseSensitive = params[5] ? true : false;
auto text = get_amxstring(amx, params[arg_text] , 0, textLength);
auto search = get_amxstring(amx, params[arg_search] , 1, searchLength);
auto replace = get_amxstring(amx, params[arg_replace], 2, replaceLength);
if (search[0] == '\0')
auto textMaxLength = params[arg_maxlength];
auto caseSensitive = params[arg_casesensitive] != 0;
if (!*search)
{
LogError(amx, AMX_ERR_NATIVE, "Cannot replace searches of empty strings.");
return -1;
}
int count = UTIL_ReplaceAll(text, maxlength + 1, search, replace, caseSensitive); // + EOS
auto count = UTIL_ReplaceAll(text, textMaxLength + 1, search, searchLength, replace, replaceLength, caseSensitive); // + EOS
set_amxstring(amx, params[1], text, maxlength);
set_amxstring(amx, params[arg_text], text, textMaxLength);
return count;
}
// native replace_stringex(text[], maxlength, const search[], const replace[], searchLen = -1, replaceLen = -1, bool:caseSensitive = true);
static cell AMX_NATIVE_CALL replace_stringex(AMX *amx, cell *params)
{
int len;
size_t maxlength = (size_t)params[2];
enum args { arg_count, arg_text, arg_maxlength, arg_search, arg_replace, arg_searchlen, arg_replacelen, arg_casesensitive };
char *text = get_amxstring(amx, params[1], 0, len);
const char *search = get_amxstring(amx, params[3], 1, len);
const char *replace = get_amxstring(amx, params[4], 2, len);
auto textLength = 0;
auto searchLength = 0;
auto replaceLength = 0;
size_t searchLen = (params[5] == -1) ? strlen(search) : (size_t)params[5];
size_t replaceLen = (params[6] == -1) ? strlen(replace) : (size_t)params[6];
auto text = get_amxstring(amx, params[arg_text] , 0, textLength);
auto search = get_amxstring(amx, params[arg_search] , 1, searchLength);
auto replace = get_amxstring(amx, params[arg_replace], 2, replaceLength);
bool caseSensitive = params[7] ? true : false;
auto textMaxLength = params[arg_maxlength];
auto caseSensitive = params[arg_casesensitive] != 0;
if (searchLen == 0)
if (params[arg_searchlen] != -1) { searchLength = params[arg_searchlen]; }
if (params[arg_replacelen] != -1) { replaceLength = params[arg_replacelen]; }
if (searchLength <= 0)
{
LogError(amx, AMX_ERR_NATIVE, "Cannot replace searches of empty strings.");
return -1;
}
char *ptr = UTIL_ReplaceEx(text, maxlength + 1, search, searchLen, replace, replaceLen, caseSensitive); // + EOS
auto ptr = UTIL_ReplaceEx(text, textMaxLength + 1, search, searchLength, replace, replaceLength, caseSensitive); // + EOS
if (!ptr)
{
return -1;
}
set_amxstring(amx, params[1], text, maxlength);
set_amxstring(amx, params[arg_text], text, textMaxLength);
return ptr - text;
}
@ -382,27 +399,36 @@ static cell AMX_NATIVE_CALL contain(AMX *amx, cell *params) /* 2 param */
return -1;
}
static cell AMX_NATIVE_CALL containi(AMX *amx, cell *params) /* 2 param */
// native containi(const source[], const string[]);
static cell AMX_NATIVE_CALL containi(AMX *amx, cell *params)
{
register cell *a = get_amxaddr(amx, params[2]);
register cell *b = get_amxaddr(amx, params[1]);
register cell *c = b;
cell* str = b;
cell* substr = a;
while (*c)
enum args { arg_count, arg_source, arg_search };
auto sourceLength = 0;
auto searchLength = 0;
auto source = get_amxstring(amx, params[arg_source], 0, sourceLength);
auto search = get_amxstring(amx, params[arg_search], 1, searchLength);
if (sourceLength && searchLength)
{
if (tolower(*c) == tolower(*a))
auto sourceFolded = get_amxbuffer(2);
auto searchFolded = get_amxbuffer(3);
sourceLength = utf8casefold(source, sourceLength, sourceFolded, MAX_BUFFER_LENGTH - 1, UTF8_LOCALE_DEFAULT, nullptr, TRUE);
searchLength = utf8casefold(search, searchLength, searchFolded, MAX_BUFFER_LENGTH - 1, UTF8_LOCALE_DEFAULT, nullptr, TRUE);
sourceFolded[sourceLength] = '\0';
searchFolded[searchLength] = '\0';
auto result = strstr(sourceFolded, searchFolded);
if (result)
{
c++;
if (!*++a)
return b - str;
} else {
c = ++b;
a = substr;
return result - sourceFolded;
}
}
return -1;
}
@ -609,30 +635,34 @@ static cell AMX_NATIVE_CALL equal(AMX *amx, cell *params) /* 3 param */
return ret ? 0 : 1;
}
static cell AMX_NATIVE_CALL equali(AMX *amx, cell *params) /* 3 param */
// native equali(const a[], const b[], c = 0);
static cell AMX_NATIVE_CALL equali(AMX *amx, cell *params)
{
cell *a = get_amxaddr(amx, params[1]);
cell *b = get_amxaddr(amx, params[2]);
int f, l, c = params[3];
if (c)
{
do
{
f = tolower(*a++);
l = tolower(*b++);
} while (--c && l && f && f == l);
return (f - l) ? 0 : 1;
}
enum args { arg_count, arg_string1, arg_string2, arg_numbytes };
do
auto string1Length = 0;
auto string2Length = 0;
auto string1 = get_amxstring(amx, params[arg_string1], 0, string1Length);
auto string2 = get_amxstring(amx, params[arg_string2], 1, string2Length);
auto string1Folded = get_amxbuffer(2);
auto string2Folded = get_amxbuffer(3);
string1Length = utf8casefold(string1, string1Length, string1Folded, MAX_BUFFER_LENGTH - 1, UTF8_LOCALE_DEFAULT, nullptr, TRUE);
string2Length = utf8casefold(string2, string2Length, string2Folded, MAX_BUFFER_LENGTH - 1, UTF8_LOCALE_DEFAULT, nullptr, TRUE);
string2Folded[string1Length] = '\0';
string1Folded[string2Length] = '\0';
if (params[arg_numbytes] > 0)
{
f = tolower(*a++);
l = tolower(*b++);
} while (f && f == l);
return (f - l) ? 0 : 1;
return static_cast<cell>(strncmp(string1Folded, string2Folded, params[arg_numbytes]) == 0);
}
else
{
return static_cast<cell>(strcmp(string1Folded, string2Folded) == 0);
}
}
static cell g_cpbuf[4096];
@ -730,6 +760,29 @@ static cell AMX_NATIVE_CALL strtolower(AMX *amx, cell *params) /* 1 param */
return cptr - begin;
}
// native mb_strtolower(source[], maxlength = 0);
static cell AMX_NATIVE_CALL mb_strtolower(AMX *amx, cell *params)
{
enum args { arg_count, arg_string, arg_maxlength };
auto sourceLength = 0;
auto source = get_amxstring(amx, params[arg_string], 0, sourceLength);
auto outputMaxLength = params[arg_maxlength];
if (outputMaxLength <= 0)
{
outputMaxLength = sourceLength;
}
auto output = get_amxbuffer(1);
auto outputLength = utf8tolower(source, sourceLength, output, MAX_BUFFER_LENGTH - 1, UTF8_LOCALE_DEFAULT, nullptr, TRUE);
output[outputLength] = '\0';
return set_amxstring_utf8(amx, params[arg_string], output, outputLength, outputMaxLength);
}
static cell AMX_NATIVE_CALL strtoupper(AMX *amx, cell *params) /* 1 param */
{
cell *cptr = get_amxaddr(amx, params[1]);
@ -744,6 +797,29 @@ static cell AMX_NATIVE_CALL strtoupper(AMX *amx, cell *params) /* 1 param */
return cptr - begin;
}
// native mb_strtoupper(source[], maxlength = 0);
static cell AMX_NATIVE_CALL mb_strtoupper(AMX *amx, cell *params)
{
enum args { arg_count, arg_string, arg_maxlength };
auto sourceLength = 0;
auto source = get_amxstring(amx, params[arg_string], 0, sourceLength);
auto outputMaxLength = params[arg_maxlength];
if (outputMaxLength <= 0)
{
outputMaxLength = sourceLength;
}
auto output = get_amxbuffer(1);
auto outputLength = utf8toupper(source, sourceLength, output, MAX_BUFFER_LENGTH - 1, UTF8_LOCALE_DEFAULT, nullptr, TRUE);
output[outputLength] = '\0';
return set_amxstring_utf8(amx, params[arg_string], output, outputLength, outputMaxLength);
}
int fo_numargs(AMX *amx)
{
unsigned char *data = amx->base + (int)((AMX_HEADER *)amx->base)->dat;
@ -1241,6 +1317,46 @@ static cell AMX_NATIVE_CALL amx_ucfirst(AMX *amx, cell *params)
return 1;
}
// native mb_ucfirst(string[], maxlength = 0);
static cell AMX_NATIVE_CALL mb_ucfirst(AMX *amx, cell *params)
{
enum args { arg_count, arg_string, arg_maxlength };
auto sourceLength = 0;
auto source = get_amxstring(amx, params[arg_string], 0, sourceLength);
auto outputMaxLength = params[arg_maxlength];
if (outputMaxLength <= 0)
{
outputMaxLength = sourceLength;
}
// Retrieves the first character length in bytes.
auto firstChLength = utf8seek(source, sourceLength, source, 1, SEEK_CUR) - source;
if (firstChLength)
{
char output[8] = {};
auto outputLength = utf8toupper(source, firstChLength, output, MAX_BUFFER_LENGTH - 1, UTF8_LOCALE_DEFAULT, nullptr, TRUE);
// The converted character is either larger or smaller in bytes.
if (firstChLength != outputLength)
{
// Calculates the new string length and makes sure we don't go over the buffer size (fairly unlikely).
sourceLength = ke::Min<int>(sourceLength + (outputLength - firstChLength), outputMaxLength);
// Move data forward or backward minus the first character (whathever its size).
memmove(source + outputLength, source + firstChLength, (sourceLength - outputLength) * sizeof(char));
}
// Copy the new character at the start of the string.
memcpy(source, output, outputLength);
}
return set_amxstring_utf8(amx, params[arg_string], source, sourceLength, outputMaxLength);
}
static cell AMX_NATIVE_CALL amx_strlen(AMX *amx, cell *params)
{
int len;
@ -1292,62 +1408,105 @@ static cell AMX_NATIVE_CALL n_strcat(AMX *amx, cell *params)
return params[3] - num;
}
// native strcmp(const string1[], const string2[], bool:ignorecase = false);
static cell AMX_NATIVE_CALL n_strcmp(AMX *amx, cell *params)
{
int len;
char *str1 = get_amxstring(amx, params[1], 0, len);
char *str2 = get_amxstring(amx, params[2], 1, len);
enum args { arg_count, arg_string1, arg_string2, arg_ignorecase };
if (params[3])
return stricmp(str1, str2);
else
return strcmp(str1, str2);
}
auto string1Length = 0;
auto string2Length = 0;
static cell AMX_NATIVE_CALL n_strncmp(AMX *amx, cell *params)
{
int len;
char *str1 = get_amxstring(amx, params[1], 0, len);
char *str2 = get_amxstring(amx, params[2], 1, len);
auto string1 = get_amxstring(amx, params[arg_string1], 0, string1Length);
auto string2 = get_amxstring(amx, params[arg_string2], 1, string2Length);
if (params[4])
return strncasecmp(str1, str2, (size_t)params[3]);
else
return strncmp(str1, str2, (size_t)params[3]);
}
static cell AMX_NATIVE_CALL n_strfind(AMX *amx, cell *params)
{
int len;
char *str = get_amxstring(amx, params[1], 0, len);
int sublen;
char *sub = get_amxstring(amx, params[2], 1, sublen);
bool igcase = params[3] ? true : false;
if (igcase)
if (params[arg_ignorecase] != 0)
{
for (int i = 0; i < len; i++)
{
if (str[i] & (1<<5))
str[i] &= ~(1<<5);
}
for (int i = 0; i < sublen; i++)
{
if (str[i] & (1<<5))
str[i] &= ~(1<<5);
}
auto string1Folded = get_amxbuffer(2);
auto string2Folded = get_amxbuffer(3);
string1Length = utf8casefold(string1, string1Length, string1Folded, MAX_BUFFER_LENGTH - 1, UTF8_LOCALE_DEFAULT, nullptr, TRUE);
string2Length = utf8casefold(string2, string2Length, string2Folded, MAX_BUFFER_LENGTH - 1, UTF8_LOCALE_DEFAULT, nullptr, TRUE);
string2Folded[string1Length] = '\0';
string1Folded[string2Length] = '\0';
string1 = string1Folded;
string2 = string2Folded;
}
if (params[4] > len)
return -1;
return strcmp(string1, string2);
}
char *find = strstr(str + params[4], sub);
// native strncmp(const string1[], const string2[], num, bool:ignorecase = false);
static cell AMX_NATIVE_CALL n_strncmp(AMX *amx, cell *params)
{
enum args { arg_count, arg_string1, arg_string2, arg_numbytes, arg_ignorecase };
auto string1Length = 0;
auto string2Length = 0;
auto string1 = get_amxstring(amx, params[arg_string1], 0, string1Length);
auto string2 = get_amxstring(amx, params[arg_string2], 1, string2Length);
if (params[arg_ignorecase] != 0)
{
auto string1Folded = get_amxbuffer(2);
auto string2Folded = get_amxbuffer(3);
string1Length = utf8casefold(string1, string1Length, string1Folded, MAX_BUFFER_LENGTH - 1, UTF8_LOCALE_DEFAULT, nullptr, TRUE);
string2Length = utf8casefold(string2, string2Length, string2Folded, MAX_BUFFER_LENGTH - 1, UTF8_LOCALE_DEFAULT, nullptr, TRUE);
string2Folded[string1Length] = '\0';
string1Folded[string2Length] = '\0';
string1 = string1Folded;
string2 = string2Folded;
}
return strncmp(string1, string2, params[arg_numbytes]);
}
// native strfind(const string[], const sub[], bool:ignorecase = false, pos = 0);
static cell AMX_NATIVE_CALL n_strfind(AMX *amx, cell *params)
{
enum args { arg_count, arg_source, arg_search, arg_ignorecase, arg_startpos };
auto sourceLength = 0;
auto searchLength = 0;
auto source = get_amxstring(amx, params[arg_source], 0, sourceLength);
auto search = get_amxstring(amx, params[arg_search], 1, searchLength);
if (params[arg_ignorecase] != 0)
{
auto sourceFolded = get_amxbuffer(2);
auto searchFolded = get_amxbuffer(3);
sourceLength = utf8casefold(source, sourceLength, sourceFolded, MAX_BUFFER_LENGTH - 1, UTF8_LOCALE_DEFAULT, nullptr, TRUE);
searchLength = utf8casefold(search, searchLength, searchFolded, MAX_BUFFER_LENGTH - 1, UTF8_LOCALE_DEFAULT, nullptr, TRUE);
sourceFolded[sourceLength] = '\0';
searchFolded[searchLength] = '\0';
source = sourceFolded;
search = searchFolded;
}
auto position = params[arg_startpos];
if (position < 0 || position > sourceLength)
{
return -1;
}
auto find = strstr(source + position, search);
if (!find)
{
return -1;
}
return (find - str);
return (find - source);
}
static cell AMX_NATIVE_CALL vformat(AMX *amx, cell *params)
@ -1424,6 +1583,60 @@ static cell AMX_NATIVE_CALL fmt(AMX *amx, cell *params)
return 1;
};
// native mb_strtotitle(source[], maxlength = 0);
static cell AMX_NATIVE_CALL mb_strtotitle(AMX *amx, cell *params)
{
enum args { arg_count, arg_string, arg_maxlength };
auto sourceLength = 0;
auto source = get_amxstring(amx, params[arg_string], 0, sourceLength);
auto outputMaxLength = params[arg_maxlength];
if (outputMaxLength <= 0)
{
outputMaxLength = sourceLength;
}
auto output = get_amxbuffer(1);
auto outputLength = utf8totitle(source, sourceLength, output, MAX_BUFFER_LENGTH - 1, UTF8_LOCALE_DEFAULT, nullptr, TRUE);
output[outputLength] = '\0';
return set_amxstring_utf8(amx, params[arg_string], output, outputLength, outputMaxLength);
}
// native bool:is_string_category(const input[], input_size, flags, &output_size = 0);
static cell AMX_NATIVE_CALL is_string_category(AMX *amx, cell *params)
{
enum args { arg_count, arg_input, arg_inputsize, arg_flags, arg_outputsize };
auto inputLength = 0;
auto input = get_amxstring(amx, params[arg_input], 0, inputLength);
auto inputMaxLength = ke::Min(params[arg_inputsize], inputLength);
auto outputSize = get_amxaddr(amx, params[arg_outputsize]);
// User wants to check only one character whatever its size.
if (inputMaxLength <= 1)
{
// Gets the character length.
inputMaxLength = utf8seek(input, inputLength, input, 1, SEEK_CUR) - input;
// Truncated character.
if (inputMaxLength > inputLength)
{
*outputSize = 0;
return FALSE;
}
}
// Checks input with the given flags.
*outputSize = utf8iscategory(input, inputMaxLength, params[arg_flags]);
// If function consumed input, then it's a success.
return static_cast<cell>(*outputSize == inputMaxLength);
}
AMX_NATIVE_INFO string_Natives[] =
{
@ -1445,7 +1658,12 @@ AMX_NATIVE_INFO string_Natives[] =
{"is_char_upper", is_char_upper},
{"is_char_lower", is_char_lower},
{"is_char_mb", is_char_mb},
{"is_string_category", is_string_category },
{"get_char_bytes", get_char_bytes},
{"mb_strtotitle", mb_strtotitle},
{"mb_strtolower", mb_strtolower},
{"mb_strtoupper", mb_strtoupper},
{"mb_ucfirst", mb_ucfirst},
{"num_to_str", numtostr},
{"numtostr", numtostr},
{"parse", parse},

View File

@ -9,6 +9,7 @@
#include <time.h>
#include "amxmodx.h"
#include <utf8rewind.h>
int UTIL_ReadFlags(const char* c)
{
@ -454,11 +455,38 @@ int UTIL_CheckValidChar(D *c)
return 0;
}
unsigned int UTIL_ReplaceAll(char *subject, size_t maxlength, const char *search, const char *replace, bool caseSensitive)
{
size_t searchLen = strlen(search);
size_t replaceLen = strlen(replace);
static char OutputBuffer1[MAX_BUFFER_LENGTH];
static char OutputBuffer2[MAX_BUFFER_LENGTH];
char* utf8stristr(const char *string1, const char *string2)
{
auto string1Length = utf8casefold(string1, strlen(string1), OutputBuffer1, MAX_BUFFER_LENGTH - 1, UTF8_LOCALE_DEFAULT, nullptr, TRUE);
auto string2Length = utf8casefold(string2, strlen(string2), OutputBuffer2, MAX_BUFFER_LENGTH - 1, UTF8_LOCALE_DEFAULT, nullptr, TRUE);
OutputBuffer1[string1Length] = '\0';
OutputBuffer2[string2Length] = '\0';
return strstr(OutputBuffer1, OutputBuffer2);
}
int utf8strncasecmp(const char *string1, const char *string2, size_t n)
{
auto string1Length = utf8casefold(string1, strlen(string1), OutputBuffer1, MAX_BUFFER_LENGTH - 1, UTF8_LOCALE_DEFAULT, nullptr, TRUE);
auto string2Length = utf8casefold(string2, strlen(string2), OutputBuffer2, MAX_BUFFER_LENGTH - 1, UTF8_LOCALE_DEFAULT, nullptr, TRUE);
OutputBuffer1[string1Length] = '\0';
OutputBuffer2[string2Length] = '\0';
return n != 0 ? strncmp(OutputBuffer1, OutputBuffer2, n) : strcmp(OutputBuffer1, OutputBuffer2);
}
int utf8strcasecmp(const char *string1, const char *string2)
{
return utf8strncasecmp(string1, string2, 0);
}
size_t UTIL_ReplaceAll(char *subject, size_t maxlength, const char *search, size_t searchLen, const char *replace, size_t replaceLen, bool caseSensitive)
{
char *newptr, *ptr = subject;
unsigned int total = 0;
while ((newptr = UTIL_ReplaceEx(ptr, maxlength, search, searchLen, replace, replaceLen, caseSensitive)) != NULL)
@ -476,6 +504,11 @@ unsigned int UTIL_ReplaceAll(char *subject, size_t maxlength, const char *search
return total;
}
size_t UTIL_ReplaceAll(char *subject, size_t maxlength, const char *search, const char *replace, bool caseSensitive)
{
return UTIL_ReplaceAll(subject, maxlength, search, strlen(search), replace, strlen(replace), caseSensitive);
}
template unsigned int strncopy<char, char>(char *, const char *, size_t);
template unsigned int strncopy<char, cell>(char *, const cell *, size_t);
template unsigned int strncopy<cell, char>(cell *, const char *, size_t);
@ -534,7 +567,7 @@ char *UTIL_ReplaceEx(char *subject, size_t maxLen, const char *search, size_t se
/* If the search matches and the replace length is 0,
* we can just terminate the string and be done.
*/
if ((caseSensitive ? strcmp(subject, search) : strcasecmp(subject, search)) == 0 && replaceLen == 0)
if ((caseSensitive ? strcmp(subject, search) : utf8strcasecmp(subject, search)) == 0 && replaceLen == 0)
{
*subject = '\0';
return subject;
@ -551,7 +584,7 @@ char *UTIL_ReplaceEx(char *subject, size_t maxLen, const char *search, size_t se
while (*ptr != '\0' && (browsed <= textLen - searchLen))
{
/* See if we get a comparison */
if ((caseSensitive ? strncmp(ptr, search, searchLen) : strncasecmp(ptr, search, searchLen)) == 0)
if ((caseSensitive ? strncmp(ptr, search, searchLen) : utf8strncasecmp(ptr, search, searchLen)) == 0)
{
if (replaceLen > searchLen)
{