/* String functions for the Pawn Abstract Machine * * Copyright (c) ITB CompuPhase, 2005 * * This software is provided "as-is", without any express or implied warranty. * In no event will the authors be held liable for any damages arising from * the use of this software. * * Permission is granted to anyone to use this software for any purpose, * including commercial applications, and to alter it and redistribute it * freely, subject to the following restrictions: * * 1. The origin of this software must not be misrepresented; you must not * claim that you wrote the original software. If you use this software in * a product, an acknowledgment in the product documentation would be * appreciated but is not required. * 2. Altered source versions must be plainly marked as such, and must not be * misrepresented as being the original software. * 3. This notice may not be removed or altered from any source distribution. * * Version: $Id: amxstring.c 3363 2005-07-23 09:03:29Z thiadmer $ */ #include #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 CHARBITS (8*sizeof(char)) /* 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 */ /* add the final cell, but only if there is not already a terminating zero * in dest */ if ((*(dest-1) & 0xff)!=0) { c &= ~0xff; /* force a terminating zero, for security */ *dest=c; } /* if */ } 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 c=tolower((int)c); #endif } /* if */ return c; } static int verify_addr(AMX *amx,cell addr) { int err; cell *cdest; err=amx_GetAddr(amx,addr,&cdest); if (err!=AMX_ERR_NONE) amx_RaiseError(amx,err); return err; } /* strlen(const string[]) */ static cell AMX_NATIVE_CALL n_strlen(AMX *amx,cell *params) { cell *cptr; int len = 0; if (amx_GetAddr(amx,params[1],&cptr)==AMX_ERR_NONE) amx_StrLen(cptr,&len); return len; } /* strpack(dest[], const source[], maxlength=sizeof dest) */ static cell AMX_NATIVE_CALL n_strpack(AMX *amx,cell *params) { cell *cdest,*csrc; int len,needed,err; size_t lastaddr; /* calculate number of cells needed for (packed) destination */ amx_GetAddr(amx,params[2],&csrc); amx_StrLen(csrc,&len); if ((unsigned)len>params[3]*sizeof(cell)-1) len=params[3]*sizeof(cell)-1; needed=(len+sizeof(cell))/sizeof(cell); /* # of cells needed */ assert(needed>0); lastaddr=(size_t)(params[1]+sizeof(cell)*needed-1); if (verify_addr(amx,(cell)lastaddr)!=AMX_ERR_NONE) return amx_RaiseError(amx,AMX_ERR_NATIVE); amx_GetAddr(amx,params[1],&cdest); 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,cell *params) { cell *cdest,*csrc; int len,err; size_t lastaddr; /* calculate number of cells needed for (unpacked) destination */ amx_GetAddr(amx,params[2],&csrc); amx_StrLen(csrc,&len); assert(len>=0); if (len>=params[3]) len=params[3]-1; lastaddr=(size_t)(params[1]+sizeof(cell)*(len+1)-1); if (verify_addr(amx,(cell)lastaddr)!=AMX_ERR_NONE) return amx_RaiseError(amx,AMX_ERR_NATIVE); amx_GetAddr(amx,params[1],&cdest); 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,cell *params) { cell *cdest,*csrc; int len,len2,needed; int packed,err; size_t lastaddr; /* calculate number of cells needed for (packed) destination */ amx_GetAddr(amx,params[2],&csrc); amx_GetAddr(amx,params[1],&cdest); 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; needed=(len+len2+sizeof(cell))/sizeof(cell); /* # of cells needed */ assert(needed>0); lastaddr=(size_t)(params[1]+sizeof(cell)*needed-1); } else { if (len+len2>params[3]-1) len=params[3]-len2-1; lastaddr=(size_t)(params[1]+sizeof(cell)*(len+len2+1)-1); } /* if */ if (verify_addr(amx,(cell)lastaddr)!=AMX_ERR_NONE) return amx_RaiseError(amx,AMX_ERR_NATIVE); 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; } 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,cell *params) { cell *cstr1,*cstr2; int len1,len2,len; cell result; amx_GetAddr(amx,params[1],&cstr1); amx_GetAddr(amx,params[2],&cstr2); /* 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 0; 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,cell *params) { cell *cstr,*csub; int lenstr,lensub,offs; cell c,f; amx_GetAddr(amx,params[1],&cstr); amx_GetAddr(amx,params[2],&csub); /* 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,cell *params) { cell *cdest,*csrc; int len,needed,err; int soffs,doffs; size_t lastaddr; unsigned char *ptr; unsigned char c; /* calculate number of cells needed for (packed) destination */ amx_GetAddr(amx,params[2],&csrc); amx_GetAddr(amx,params[1],&cdest); amx_StrLen(csrc,&len); /* clamp the start/end parameters */ if (params[3]<0) params[3]=0; else if (params[3]>len) params[3]=len; if (params[4]len) params[4]=len; else if (params[3]>params[4]) params[3]=params[4]; len=params[4]-params[3]; if ((ucell)*csrc>UNPACKEDMAX) { if ((unsigned)len>params[5]*sizeof(cell)-1) len=params[5]*sizeof(cell)-1; needed=(len+sizeof(cell))/sizeof(cell); /* # of cells needed */ assert(needed>0); lastaddr=(size_t)(params[1]+sizeof(cell)*needed-1); } else { if (len>params[5]-1) len=params[5]-1; lastaddr=(size_t)(params[1]+sizeof(cell)*(len+1)-1); } /* if */ if (verify_addr(amx,(cell)lastaddr)!=AMX_ERR_NONE) return amx_RaiseError(amx,AMX_ERR_NATIVE); if ((ucell)*csrc>UNPACKEDMAX) { /* first align the source to a cell boundary */ for (doffs=0,soffs=(int)params[3]; (soffs & (sizeof(cell)-1))!=0 && len>1; soffs++,doffs++,len--) { ptr=packedptr(csrc,soffs); c=*ptr; ptr=packedptr(cdest,doffs); *ptr=c; } /* for */ err=amx_StrPack(cdest,csrc+soffs/sizeof(cell),len,doffs); } else { err=amx_StrUnpack(cdest,csrc+params[3],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,cell *params) { cell *cstr; int index,offs,length; unsigned char *ptr; unsigned char c; /* calculate number of cells needed for (packed) destination */ amx_GetAddr(amx,params[1],&cstr); 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,cell *params) { cell *cstr,*csub; int index,lenstr,lensub,count; int needed; size_t lastaddr; unsigned char *ptr; cell c; /* calculate number of cells needed for (packed) destination */ amx_GetAddr(amx,params[1],&cstr); amx_GetAddr(amx,params[2],&csub); amx_StrLen(cstr,&lenstr); amx_StrLen(csub,&lensub); index=(int)params[3]; if (index>lenstr) return amx_RaiseError(amx,AMX_ERR_NATIVE); if (((ucell)*cstr>UNPACKEDMAX)) { needed=(lenstr+lensub+sizeof(cell))/sizeof(cell); /* # of cells needed */ assert(needed>0); lastaddr=(size_t)(params[1]+sizeof(cell)*needed-1); } else { lastaddr=(size_t)(params[1]+sizeof(cell)*(lenstr+lensub+1)-1); } /* if */ if (verify_addr(amx,(cell)lastaddr)!=AMX_ERR_NONE) 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=50) { //amx_RaiseError(amx,AMX_ERR_NATIVE); return 0; } /* if */ amx_GetString(str,cstr,0,sizeof str); ptr=str; 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,cell *params) { char str[50]; cell value,mult; cell *cstr; int len,result,negate=0; /* find out how many digits are needed */ mult=10; len=1; value=params[2]; if (value<0) { negate=1; len++; value=-value; } /* if */ while (value>=mult) { len++; mult*=10; } /* while */ /* 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]='-'; amx_GetAddr(amx,params[1],&cstr); amx_SetString(cstr,str,params[3],0,UNLIMITED); return result; } /* ispacked(const string[]) */ static cell AMX_NATIVE_CALL n_ispacked(AMX *amx,cell *params) { cell *cstr; amx_GetAddr(amx,params[1],&cstr); 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]; 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,cell *params) { cell *cstr; unsigned char dst[BITMASK+2]; char src[BITMASK+BITMASK/3+2]; int len; size_t size; /* get the source */ amx_GetAddr(amx,params[2],&cstr); amx_GetString(src,cstr,0,sizeof src); /* decode */ len=uudecode(dst,src); /* store */ amx_GetAddr(amx,params[1],&cstr); 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,cell *params) { cell *cstr; unsigned char src[BITMASK+2]; char dst[BITMASK+BITMASK/3+2]; /* get the source */ amx_GetAddr(amx,params[2],&cstr); /* encode (and check for errors) */ if (uuencode(dst,src,params[3])) { if (params[4]>0) { amx_GetAddr(amx,params[1],&cstr); *cstr=0; } /* if */ return 0; } /* if */ /* always add a \n */ assert(strlen(dst)+1params[5]*(int)sizeof(cell)) return 0; amx_GetAddr(amx,params[1],&cdest); amx_GetAddr(amx,params[2],&csrc); pdest=(unsigned char*)cdest+params[3]; psrc=(unsigned char*)csrc; memmove(pdest,psrc,params[4]); return 1; } #if defined __cplusplus extern "C" #endif const AMX_NATIVE_INFO string_Natives[] = { { "ispacked", n_ispacked }, { "memcpy", n_memcpy }, { "strcat", n_strcat }, { "strcmp", n_strcmp }, { "strdel", n_strdel }, { "strfind", n_strfind }, { "strins", n_strins }, { "strlen", n_strlen }, { "strmid", n_strmid }, { "strpack", n_strpack }, { "strunpack", n_strunpack }, { "strval", n_strval }, { "uudecode", n_uudecode }, { "uuencode", n_uuencode }, { "valstr", n_valstr }, { NULL, NULL } /* terminator */ }; int AMXEXPORT amx_StringInit(AMX *amx) { return amx_Register(amx, string_Natives, -1); } int AMXEXPORT amx_StringCleanup(AMX *amx) { (void)amx; return AMX_ERR_NONE; }