/* String functions for the Pawn Abstract Machine * * Copyright (c) ITB CompuPhase, 2005-2011 * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy * of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. * * Version: $Id: amxstring.c 4523 2011-06-21 15:03:47Z thiadmer $ */ #include #include #include #if defined __WIN32__ || defined _WIN32 || defined WIN32 || defined __MSDOS__ #include #endif #include "osdefs.h" #include "amx.h" #if defined __WIN32__ || defined _WIN32 || defined WIN32 || defined _Windows #include #endif #define MAX_FORMATSTR 256 #define CHARBITS (8*sizeof(char)) #if defined _UNICODE # include #elif !defined __T typedef char TCHAR; # define __T(string) string # define _tcscat strcat # define _tcschr strchr # define _tcscpy strcpy # define _tcslen strlen #endif #include "amxcons.h" #if !defined isdigit # define isdigit(c) ((unsigned)((c)-'0')<10u) #endif #if !defined sizearray # define sizearray(a) (sizeof(a) / sizeof((a)[0])) #endif /* dest the destination buffer; the buffer must point to the start of a cell * source the source buffer, this must be aligned to a cell edge * len the number of characters (bytes) to copy * offs the offset in dest, in characters (bytes) */ static int amx_StrPack(cell *dest,cell *source,int len,int offs) { int i; if ((ucell)*source>UNPACKEDMAX && offs%sizeof(cell)==0) { /* source string is already packed and the destination is cell-aligned */ unsigned char* pdest=(unsigned char*)dest+offs; i=(len+sizeof(cell)-1)/sizeof(cell); memmove(pdest,source,i*sizeof(cell)); /* zero-terminate */ #if BYTE_ORDER==BIG_ENDIAN pdest+=len; for (i=len; i==len || i%sizeof(cell)!=0; i++) *pdest++='\0'; #else i=(len/sizeof(cell))*sizeof(cell); pdest+=i; len=(len==i) ? sizeof(cell) : sizeof(cell)-(len-i); assert(len>0 && len<=sizeof(cell)); for (i=0; iUNPACKEDMAX) { /* source string is packed, destination is not aligned */ cell mask,c; dest+=offs/sizeof(cell); /* increment whole number of cells */ offs%=sizeof(cell); /* get remainder */ mask=(~(ucell)0) >> (offs*CHARBITS); c=*dest & ~mask; for (i=0; i> (offs*CHARBITS)) & mask); c=(*source << ((sizeof(cell)-offs)*CHARBITS)) & ~mask; dest++; source++; } /* for */ /* set the zero byte in the last cell */ mask=(~(ucell)0) >> (((offs+len)%sizeof(cell))*CHARBITS); *(dest-1) &= ~mask; } else { /* source string is unpacked: pack string, from top-down */ cell c=0; if (offs!=0) { /* get the last cell in "dest" and mask of the characters that must be changed */ cell mask; dest+=offs/sizeof(cell); /* increment whole number of cells */ offs%=sizeof(cell); /* get remainder */ mask=(~(ucell)0) >> (offs*CHARBITS); c=(*dest & ~mask) >> ((sizeof(cell)-offs)*CHARBITS); } /* if */ /* for proper alignement, add the offset to both the starting and the ending * criterion (so that the number of iterations stays the same) */ assert(offs>=0 && offsUNPACKEDMAX) { /* unpack string, from bottom up (so string can be unpacked in place) */ cell c; int i; for (i=len-1; i>=0; i--) { c=source[i/sizeof(cell)] >> (sizeof(cell)-i%sizeof(cell)-1)*CHARBITS; dest[i]=c & UCHAR_MAX; } /* for */ dest[len]=0; /* zero-terminate */ } else { /* source string is already unpacked */ while (len-->0) *dest++=*source++; *dest=0; } /* if */ return AMX_ERR_NONE; } static unsigned char *packedptr(cell *string,int index) { unsigned char *ptr=(unsigned char *)(string+index/sizeof(cell)); #if BYTE_ORDER==BIG_ENDIAN ptr+=index & (sizeof(cell)-1); #else ptr+=(sizeof(cell)-1) - (index & (sizeof(cell)-1)); #endif return ptr; } static cell extractchar(cell *string,int index,int mklower) { cell c; if ((ucell)*string>UNPACKEDMAX) c=*packedptr(string,index); else c=string[index]; if (mklower) { #if defined __WIN32__ || defined _WIN32 || defined WIN32 c=(cell)CharLower((LPTSTR)c); #elif defined _Windows c=(cell)AnsiLower((LPSTR)c); #else if ((unsigned int)(c-'A')<26u) c+='a'-'A'; #endif } /* if */ return c; } /* strlen(const string[]) */ static cell AMX_NATIVE_CALL n_strlen(AMX *amx,const cell *params) { cell *cptr; int len = 0; (void)(amx); cptr=amx_Address(amx,params[1]); amx_StrLen(cptr,&len); return len; } /* strpack(dest[], const source[], maxlength=sizeof dest) */ static cell AMX_NATIVE_CALL n_strpack(AMX *amx,const cell *params) { cell *cdest,*csrc; int len,err; csrc=amx_Address(amx,params[2]); amx_StrLen(csrc,&len); if ((unsigned)len>params[3]*sizeof(cell)-1) len=params[3]*sizeof(cell)-1; cdest=amx_Address(amx,params[1]); err=amx_StrPack(cdest,csrc,len,0); if (err!=AMX_ERR_NONE) return amx_RaiseError(amx,err); return len; } /* strunpack(dest[], const source[], maxlength=sizeof dest) */ static cell AMX_NATIVE_CALL n_strunpack(AMX *amx,const cell *params) { cell *cdest,*csrc; int len,err; csrc=amx_Address(amx,params[2]); amx_StrLen(csrc,&len); assert(len>=0); if (len>=params[3]) len=params[3]-1; cdest=amx_Address(amx,params[1]); err=amx_StrUnpack(cdest,csrc,len); if (err!=AMX_ERR_NONE) return amx_RaiseError(amx,err); return len; } /* strcat(dest[], const source[], maxlength=sizeof dest) * packed/unpacked attribute is taken from dest[], or from source[] if dest[] * is an empty string. */ static cell AMX_NATIVE_CALL n_strcat(AMX *amx,const cell *params) { cell *cdest,*csrc; int len,len2; int packed,err; /* calculate number of cells needed for (packed) destination */ csrc=amx_Address(amx,params[2]); cdest=amx_Address(amx,params[1]); amx_StrLen(csrc,&len); amx_StrLen(cdest,&len2); packed=(*cdest==0) ? ((ucell)*csrc>UNPACKEDMAX) : ((ucell)*cdest>UNPACKEDMAX); if (packed) { if ((unsigned)(len+len2)>params[3]*sizeof(cell)-1) len=params[3]*sizeof(cell)-len2-1; } else { if (len+len2>params[3]-1) len=params[3]-len2-1; } /* if */ if (packed) { err=amx_StrPack(cdest,csrc,len,len2); } else { /* destination string must either be unpacked, or empty */ assert((ucell)*cdest<=UNPACKEDMAX || len2==0); err=amx_StrUnpack(cdest+len2,csrc,len); } /* if */ if (err!=AMX_ERR_NONE) return amx_RaiseError(amx,err); return len; } /* strcopy(dest[], const source[], maxlength=sizeof dest) * packed/unpacked attribute from source[] */ static cell AMX_NATIVE_CALL n_strcopy(AMX *amx,const cell *params) { cell *cdest,*csrc; int len,packed,err; /* calculate number of cells needed for (packed) destination */ csrc=amx_Address(amx,params[2]); cdest=amx_Address(amx,params[1]); amx_StrLen(csrc,&len); packed=(ucell)*csrc>UNPACKEDMAX; if (packed) { if ((unsigned)len>params[3]*sizeof(cell)-1) len=params[3]*sizeof(cell)-1; } else { if (len>params[3]-1) len=params[3]-1; } /* if */ if (packed) err=amx_StrPack(cdest,csrc,len,0); else err=amx_StrUnpack(cdest,csrc,len); if (err!=AMX_ERR_NONE) return amx_RaiseError(amx,err); return len; } static int compare(cell *cstr1,cell *cstr2,int ignorecase,int length,int offs1) { int index; cell c1=0,c2=0; for (index=0; indexc2) return 1; return 0; } /* strcmp(const string1[], const string2[], bool:ignorecase=false, length=cellmax) */ static cell AMX_NATIVE_CALL n_strcmp(AMX *amx,const cell *params) { cell *cstr1,*cstr2; int len1,len2,len; cell result; (void)(amx); cstr1=amx_Address(amx,params[1]); cstr2=amx_Address(amx,params[2]); /* get the maximum length to compare */ amx_StrLen(cstr1,&len1); amx_StrLen(cstr2,&len2); len=len1; if (len>len2) len=len2; if (len>params[4]) len=params[4]; if (len==0) return (params[4]==0) ? 0 : len1-len2; result=compare(cstr1,cstr2,params[3],len,0); if (result==0 && len!=params[4]) result=len1-len2; return result; } /* strfind(const string[], const sub[], bool:ignorecase=false, offset=0) */ static cell AMX_NATIVE_CALL n_strfind(AMX *amx,const cell *params) { cell *cstr,*csub; int lenstr,lensub,offs; cell c,f; (void)(amx); cstr=amx_Address(amx,params[1]); csub=amx_Address(amx,params[2]); /* get the maximum length to compare */ amx_StrLen(cstr,&lenstr); amx_StrLen(csub,&lensub); if (lensub==0) return -1; /* get the start character of the substring, for quicker searching */ f=extractchar(csub,0,params[3]); assert(f!=0); /* string length is already checked */ for (offs=(int)params[4]; offs+lensub<=lenstr; offs++) { /* find the initial character */ c=extractchar(csub,0,params[3]); assert(c!=0); /* string length is already checked */ if (c!=f) continue; if (compare(cstr,csub,params[3],lensub,offs)==0) return offs; } /* for */ return -1; } /* strmid(dest[], const source[], start, end, maxlength=sizeof dest) * packed/unpacked attribute is taken from source[] */ static cell AMX_NATIVE_CALL n_strmid(AMX *amx,const cell *params) { cell *cdest,*csrc; int len,err; int soffs,doffs; unsigned char *ptr; unsigned char c; int start=params[3]; int end=params[4]; /* calculate number of cells needed for (packed) destination */ csrc=amx_Address(amx,params[2]); cdest=amx_Address(amx,params[1]); amx_StrLen(csrc,&len); /* clamp the start/end parameters */ if (start<0) start=0; else if (start>len) start=len; if (endlen) end=len; len=end-start; if ((ucell)*csrc>UNPACKEDMAX) { if ((unsigned)len>params[5]*sizeof(cell)-1) len=params[5]*sizeof(cell)-1; } else { if (len>params[5]-1) len=params[5]-1; } /* if */ if ((ucell)*csrc>UNPACKEDMAX) { /* first align the source to a cell boundary */ for (doffs=0,soffs=start; (soffs & (sizeof(cell)-1))!=0 && len>0; soffs++,doffs++,len--) { ptr=packedptr(csrc,soffs); c=*ptr; ptr=packedptr(cdest,doffs); *ptr=c; } /* for */ if (len==0) { /* nothing left to do, zero-terminate */ ptr=packedptr(cdest,doffs); *ptr='\0'; err=AMX_ERR_NONE; } else { err=amx_StrPack(cdest,csrc+soffs/sizeof(cell),len,doffs); } /* if */ } else { err=amx_StrUnpack(cdest,csrc+start,len); } /* if */ if (err!=AMX_ERR_NONE) return amx_RaiseError(amx,err); return len; } /* strdel(string[], start, end) */ static cell AMX_NATIVE_CALL n_strdel(AMX *amx,const cell *params) { cell *cstr; int index,offs,length; unsigned char *ptr; unsigned char c; (void)(amx); /* calculate number of cells needed for (packed) destination */ cstr=amx_Address(amx,params[1]); amx_StrLen(cstr,&length); index=(int)params[2]; offs=(int)params[3]-index; if (index>=length || offs<=0) return 0; if (index+offs>length) offs=length-index; index--; /* prepare for increment in the top of the loop */ if (((ucell)*cstr>UNPACKEDMAX)) { do { index++; ptr=packedptr(cstr,index+offs); c=*ptr; ptr=packedptr(cstr,index); *ptr=c; } while (c!='\0'); if (index==0) *cstr=0; } else { do { index++; cstr[index]=cstr[index+offs]; } while (cstr[index]!=0); } /* if */ return 1; } /* strins(string[], const substr[], offset, maxlength=sizeof string) */ static cell AMX_NATIVE_CALL n_strins(AMX *amx,const cell *params) { cell *cstr,*csub; int index,lenstr,lensub,count; unsigned char *ptr; cell c; /* calculate number of cells needed for (packed) destination */ cstr=amx_Address(amx,params[1]); csub=amx_Address(amx,params[2]); amx_StrLen(cstr,&lenstr); amx_StrLen(csub,&lensub); index=(int)params[3]; if (index>lenstr) return amx_RaiseError(amx,AMX_ERR_NATIVE); if (*cstr==0) { /* current string is empty (and the insertion point is zero), just make a copy */ assert(index==0); if ((ucell)*csub>UNPACKEDMAX) amx_StrPack(cstr,csub,lensub,0); else amx_StrUnpack(cstr,csub,lensub); return 1; } /* if */ if (((ucell)*cstr>UNPACKEDMAX)) { /* make room for the new characters */ for (count=lenstr+lensub; count>index; count--) { ptr=packedptr(cstr,count-lensub); c=*ptr; ptr=packedptr(cstr,count); *ptr=(unsigned char)c; } /* for */ /* copy in the new characters */ for (count=0; countindex; count--) cstr[count]=cstr[count-lensub]; /* copy in the new characters */ for (count=0; count=2*sizeof(cell)) offset=params[2]; if (offset<0) offset=0; else if (offset>=len) offset=len-1; /* skip a number of cells */ if ((ucell)*cstr>UNPACKEDMAX) { /* packed string */ while (offset>=(int)sizeof(cell)) { cstr++; offset-=sizeof(cell); len-=sizeof(cell); } /* while */ } else { /* unpacked string, one character per cell */ while (offset>0) { cstr++; offset--; len--; } /* while */ } /* if */ amx_GetString(str,cstr,sizeof(TCHAR)>1,sizeof str); assert(offset<(int)sizeof(cell) && offset>=0); ptr=str+offset; result=0; while (*ptr!='\0' && *ptr<=' ') ptr++; /* skip whitespace */ if (*ptr=='-') { /* handle sign */ negate=1; ptr++; } else if (*ptr=='+') { ptr++; } /* if */ while (isdigit(*ptr)) { result=result*10 + (*ptr-'0'); ptr++; } /* while */ if (negate) result=-result; return result; } /* valstr(dest[], value, bool:pack=false) */ static cell AMX_NATIVE_CALL n_valstr(AMX *amx,const cell *params) { TCHAR str[50]; cell value,temp; cell *cstr; int len,result,negate=0; (void)(amx); /* find out how many digits are needed */ len=1; value=params[2]; if (value<0) { negate=1; len++; value=-value; } /* if */ for (temp=value; temp>=10; temp/=10) len++; assert(len<=sizearray(str)); /* put in the string */ result=len; str[len--]='\0'; while (len>=negate) { str[len--]=(char)((value % 10)+'0'); value/=10; } /* while */ if (negate) str[0]='-'; cstr=amx_Address(amx,params[1]); amx_SetString(cstr,str,params[3],sizeof(TCHAR)>1,sizearray(str)); return result; } /* ispacked(const string[]) */ static cell AMX_NATIVE_CALL n_ispacked(AMX *amx,const cell *params) { cell *cstr=amx_Address(amx,params[1]); (void)(amx); return *cstr>=UNPACKEDMAX; } /* single character decode and encode */ #define BITMASK 0x3f #define DEC(c) (((c) - ' ') & BITMASK) #define ENC(c) (char)(((c) & BITMASK) == 0 ? 0x60 : ((c) & BITMASK) + ' ') static int uudecode(unsigned char *target, char *source) { int len, retval; len = DEC(*source++); retval = len; while (len > 0) { if (len-- > 0) *target++ = (unsigned char)(( DEC(source[0]) << 2 ) | ( DEC(source[1]) >> 4 )); if (len-- > 0) *target++ = (unsigned char)(( DEC(source[1]) << 4 ) | ( DEC(source[2]) >> 2 )); if (len-- > 0) *target++ = (unsigned char)(( DEC(source[2]) << 6 ) | DEC(source[3]) ); source += 4; } /* while */ return retval; } static int uuencode(char *target, unsigned char *source, int length) { int split[4]={0,0,0,0}; if (length > BITMASK) return 0; /* can encode up to 64 bytes */ *target++ = ENC(length); while (length > 0) { split[0] = source[0] >> 2; /* split first byte to char. 0 & 1 */ split[1] = source[0] << 4; if (length > 1) { split[1] |= source[1] >> 4; /* split 2nd byte to char. 1 & 2 */ split[2] = source[1] << 2; if (length > 2) { split[2] |= source[2] >> 6; /* split 3th byte to char. 2 & 3 */ split[3] = source[2]; } /* if */ } /* if */ *target++ = ENC(split[0]); *target++ = ENC(split[1]); if (length > 1) *target++ = ENC(split[2]); if (length > 2) *target++ = ENC(split[3]); source += 3; length -= 3; } /* while */ *target = '\0'; /* end string */ return 1; } /* uudecode(dest[], const source[], maxlength=sizeof dest) * Returns the number of bytes (not cells) decoded; if the dest buffer is * too small, not all bytes are stored. * Always creates a (packed) array (not a string; the array is not * zero-terminated). * A buffer may be decoded "in-place"; the destination size is always smaller * than the source size. * Endian issues (for multi-byte values in the data stream) are not handled. */ static cell AMX_NATIVE_CALL n_uudecode(AMX *amx,const cell *params) { cell *cstr; unsigned char dst[BITMASK+2]; char src[BITMASK+BITMASK/3+2]; int len; size_t size; (void)(amx); /* get the source */ cstr=amx_Address(amx,params[2]); amx_GetString(src,cstr,0,sizeof src); /* decode */ len=uudecode(dst,src); /* store */ cstr=amx_Address(amx,params[1]); size=len; if (size>params[3]*sizeof(cell)) size=params[3]*sizeof(cell); memcpy(cstr,dst,size); return len; } /* uuencode(dest[], const source[], numbytes, maxlength=sizeof dest) * Returns the number of characters encoded, excluding the zero string * terminator; if the dest buffer is too small, not all bytes are stored. * Always creates a packed string. This string has a newline character at the * end. A buffer may be encoded "in-place" if the destination is large enough. * Endian issues (for multi-byte values in the data stream) are not handled. */ static cell AMX_NATIVE_CALL n_uuencode(AMX *amx,const cell *params) { cell *cstr; unsigned char src[BITMASK+2]; char dst[BITMASK+BITMASK/3+2]; (void)(amx); /* get the source */ cstr=amx_Address(amx,params[2]); amx_GetString((char *)src,cstr,0,sizeof src); /* encode (and check for errors) */ if (uuencode(dst,src,params[3])) { if (params[4]>0) { cstr=amx_Address(amx,params[1]); *cstr=0; } /* if */ return 0; } /* if */ /* always add a \n */ assert(strlen(dst)+1='0' && str[idx_src+1]<='9') p=str[idx_src+1]-'0'; else if (str[idx_src+1]>='A' && str[idx_src+1]<='F') p=str[idx_src+1]-'A'+10; else if (str[idx_src+1]>='a' && str[idx_src+1]<='f') p=str[idx_src+1]-'a'+10; else p=-1; if (p>=0) { if (str[idx_src+2]>='0' && str[idx_src+2]<='9') q=str[idx_src+2]-'0'; else if (str[idx_src+2]>='A' && str[idx_src+2]<='F') q=str[idx_src+2]-'A'+10; else if (str[idx_src+2]>='a' && str[idx_src+2]<='f') q=str[idx_src+2]-'a'+10; else q=-1; } /* if */ if (p>=0 && q >=0) { assert(p<=15 && q<=15); str[idx_dst]=(TCHAR)((p<<4) | q); idx_src+=3; } else { /* invalid '%xx' syntax, copy literal '%' */ str[idx_dst]=str[idx_src++]; } /* if */ } else { str[idx_dst]=str[idx_src++]; } /* if */ idx_dst++; } /* while */ str[idx_dst]='\0'; /* store */ cstr=amx_Address(amx,params[1]); amx_SetString(cstr,str,1,0,params[4]); /* store as packed ot unpacked */ return idx_dst; } #define INVALIDURI(c) ((c)<',' \ || (c)>'9' && (c)<'A' \ || (c)>'Z' && (c)<'_' \ || (c)>'_' && (c)<'a' \ || (c)>'z' && (unsigned)(c)<0xa1) #define TOHEX(c) (TCHAR)((c)<10 ? '0'+(c) : 'A'-10+(c)) /* urlencode(dest[], const source[], maxlength=sizeof dest, bool:pack=false) * Returns the number of characters encoded, excluding the zero string * terminator; if the dest buffer is too small, not all bytes are stored. * Always creates a packed string. This string has a newline character at the * end. A buffer may be encoded "in-place" if the destination is large enough. * Endian issues (for multi-byte values in the data stream) are not handled. */ static cell AMX_NATIVE_CALL n_urlencode(AMX *amx,const cell *params) { cell *cstr; int length,destlen,count,lastwidth; TCHAR *str; /* allocate memory and get the source */ (void)(amx); if ((length=(int)params[3])==0) return 0; if ((str = (TCHAR*)alloca(length * sizeof(TCHAR)))==NULL) return 0; cstr=amx_Address(amx,params[2]); amx_GetString((char*)str, cstr, sizeof(TCHAR)>1, length); /* run through the string and determine the new length */ destlen=1; /* space for the '\0' terminator */ lastwidth=0; for (count=0; str[count]!='\0' && destlenlength) { /* correct for overrun */ destlen-=lastwidth; count--; } /* if */ assert(destlen<=length); assert(count>=0); /* store string terminator */ assert(destlen>0); str[--destlen]='\0'; /* convert string from end to start */ while (--count>=0) { assert(destlen>count); if (INVALIDURI(str[count])) { str[--destlen]=TOHEX(str[count] & 0x0f); str[--destlen]=TOHEX((str[count] >> 4) & 0x0f); str[--destlen]='%'; } else { str[--destlen]=str[count]; } /* if */ } /* while */ assert(destlen==0); /* store the result */ cstr=amx_Address(amx,params[1]); amx_SetString(cstr,str,1,0,params[4]); /* store as packed ot unpacked */ return strlen(str); } /* memcpy(dest[], const source[], index=0, numbytes, maxlength=sizeof dest) * This function can align byte strings in cell arrays, or concatenate two * byte strings in two arrays. The parameter "index" is a byte offset; "numbytes" * is the number of bytes to copy. Parameter "maxlength", however, is in cells. * This function allows copying in-place, for aligning memory buffers. * Endian issues (for multi-byte values in the data stream) are not handled. */ static cell AMX_NATIVE_CALL n_memcpy(AMX *amx,const cell *params) { cell *cdest,*csrc; unsigned char *pdest,*psrc; (void)(amx); if (params[3]<0 || params[4]<0 || (params[3]+params[4])>params[5]*(int)sizeof(cell)) return 0; cdest=amx_Address(amx,params[1]); csrc=amx_Address(amx,params[2]); pdest=(unsigned char*)cdest+params[3]; psrc=(unsigned char*)csrc; memmove(pdest,psrc,params[4]); return 1; } #if !defined AMX_NOSTRFMT static int str_putstr(void *dest,const TCHAR *str) { if (_tcslen((TCHAR*)dest)+_tcslen(str)1,(int)params[2]); return 1; #endif } #if defined __cplusplus extern "C" #endif const AMX_NATIVE_INFO string_Natives[] = { { "ispacked", n_ispacked }, { "memcpy", n_memcpy }, { "strcat", n_strcat }, { "strcmp", n_strcmp }, { "strcopy", n_strcopy }, { "strdel", n_strdel }, { "strfind", n_strfind }, { "strformat", n_strformat }, { "strins", n_strins }, { "strlen", n_strlen }, { "strmid", n_strmid }, { "strpack", n_strpack }, { "strunpack", n_strunpack }, { "strval", n_strval }, { "uudecode", n_uudecode }, { "uuencode", n_uuencode }, { "urldecode", n_urldecode }, { "urlencode", n_urlencode }, { "valstr", n_valstr }, { NULL, NULL } /* terminator */ }; int AMXEXPORT AMXAPI amx_StringInit(AMX *amx) { return amx_Register(amx, string_Natives, -1); } int AMXEXPORT AMXAPI amx_StringCleanup(AMX *amx) { (void)amx; return AMX_ERR_NONE; }