/* Text file I/O module for the Pawn Abstract Machine * * Copyright (c) ITB CompuPhase, 2003-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: amxfile.c,v 1.7 2006/04/19 12:00:58 spookie Exp $ */ #if defined _UNICODE || defined __UNICODE__ || defined UNICODE # if !defined UNICODE /* for Windows */ # define UNICODE # endif # if !defined _UNICODE /* for C library */ # define _UNICODE # endif #endif #include #include #include #include #include #if defined __WIN32__ || defined _WIN32 || defined WIN32 || defined __MSDOS__ #include #include #endif #if defined __WIN32__ || defined _WIN32 || defined WIN32 || defined _Windows #include #endif #include "osdefs.h" #include "amx.h" #if !defined AMXFILE_VAR #define AMXFILE_VAR "AMXFILE" #elif AMXFILE_VAR=="" #undef AMXFILE_VAR #endif #if defined _UNICODE # include #elif !defined __T typedef char TCHAR; # define __T(string) string # define _tfopen fopen # define _tgetenv getenv # define _tfputs fputs # define _tcscat strcat # define _tcschr strchr # define _tcscpy strcpy # define _tcsdup strdup # define _tcslen strlen # define _tcspbrk strpbrk # define _tcsrchr strrchr #endif #if !defined UNUSED_PARAM #define UNUSED_PARAM(p) ((void)(p)) #endif enum filemode { io_read, /* file must exist */ io_write, /* creates a new file */ io_readwrite, /* file must exist */ io_append, /* file must exist, opened for writing only and seek to the end */ }; enum seek_whence { seek_start, seek_current, seek_end, }; /* This function only stores unpacked strings. UTF-8 is used for * Unicode, and packed strings can only store 7-bit and 8-bit * character sets (ASCII, Latin-1). */ static size_t fgets_cell(FILE *fp,cell *string,size_t max,int utf8mode) { size_t index; fpos_t pos; cell c; int follow,lastcr; cell lowmark; assert(sizeof(cell)>=4); assert(fp!=NULL); assert(string!=NULL); if (max<=0) return 0; /* get the position, in case we have to back up */ fgetpos(fp, &pos); index=0; follow=0; lowmark=0; lastcr=0; for ( ;; ) { assert(index0 && (c & 0xc0)==0x80) { /* leader code is active, combine with earlier code */ string[index]=(string[index] << 6) | ((unsigned char)c & 0x3f); if (--follow==0) { /* encoding a character in more bytes than is strictly needed, * is not really valid UTF-8; we are strict here to increase * the chance of heuristic dectection of non-UTF-8 text * (JAVA writes zero bytes as a 2-byte code UTF-8, which is invalid) */ if (string[index]=0xd800 && string[index]<=0xdfff || string[index]==0xfffe || string[index]==0xffff) utf8mode=0; index++; } /* if */ } else if (follow==0 && (c & 0x80)==0x80) { /* UTF-8 leader code */ if ((c & 0xe0)==0xc0) { /* 110xxxxx 10xxxxxx */ follow=1; lowmark=0x80; string[index]=c & 0x1f; } else if ((c & 0xf0)==0xe0) { /* 1110xxxx 10xxxxxx 10xxxxxx (16 bits, BMP plane) */ follow=2; lowmark=0x800; string[index]=c & 0x0f; } else if ((c & 0xf8)==0xf0) { /* 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx */ follow=3; lowmark=0x10000; string[index]=c & 0x07; } else if ((c & 0xfc)==0xf8) { /* 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx */ follow=4; lowmark=0x200000; string[index]=c & 0x03; } else if ((c & 0xfe)==0xfc) { /* 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx (31 bits) */ follow=5; lowmark=0x4000000; string[index]=c & 0x01; } else { /* this is invalid UTF-8 */ utf8mode=0; } /* if */ } else if (follow==0 && (c & 0x80)==0x00) { /* 0xxxxxxx (US-ASCII) */ string[index++]=c; if (c==__T('\n')) break; /* read newline, done */ } else { /* this is invalid UTF-8 */ utf8mode=0; } /* if */ if (!utf8mode) { /* UTF-8 mode was switched just off, which means that non-conforming * UTF-8 codes were found, which means in turn that the string is * probably not intended as UTF-8; start over again */ index=0; fsetpos(fp, &pos); } /* if */ } else { string[index++]=c; if (c==__T('\n')) { break; /* read newline, done */ } else if (lastcr) { ungetc(c,fp); /* carriage return was read, no newline follows */ break; } /* if */ lastcr=(c==__T('\r')); } /* if */ } /* for */ assert(index=4); assert(fp!=NULL); assert(string!=NULL); while (*string!=0) { if (utf8mode) { cell c=*string; if (c<0x80) { /* 0xxxxxxx */ fputc((unsigned char)c,fp); } else if (c<0x800) { /* 110xxxxx 10xxxxxx */ fputc((unsigned char)((c>>6) & 0x1f | 0xc0),fp); fputc((unsigned char)(c & 0x3f | 0x80),fp); } else if (c<0x10000) { /* 1110xxxx 10xxxxxx 10xxxxxx (16 bits, BMP plane) */ fputc((unsigned char)((c>>12) & 0x0f | 0xe0),fp); fputc((unsigned char)((c>>6) & 0x3f | 0x80),fp); fputc((unsigned char)(c & 0x3f | 0x80),fp); } else if (c<0x200000) { /* 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx */ fputc((unsigned char)((c>>18) & 0x07 | 0xf0),fp); fputc((unsigned char)((c>>12) & 0x3f | 0x80),fp); fputc((unsigned char)((c>>6) & 0x3f | 0x80),fp); fputc((unsigned char)(c & 0x3f | 0x80),fp); } else if (c<0x4000000) { /* 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx */ fputc((unsigned char)((c>>24) & 0x03 | 0xf8),fp); fputc((unsigned char)((c>>18) & 0x3f | 0x80),fp); fputc((unsigned char)((c>>12) & 0x3f | 0x80),fp); fputc((unsigned char)((c>>6) & 0x3f | 0x80),fp); fputc((unsigned char)(c & 0x3f | 0x80),fp); } else { /* 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx (31 bits) */ fputc((unsigned char)((c>>30) & 0x01 | 0xfc),fp); fputc((unsigned char)((c>>24) & 0x3f | 0x80),fp); fputc((unsigned char)((c>>18) & 0x3f | 0x80),fp); fputc((unsigned char)((c>>12) & 0x3f | 0x80),fp); fputc((unsigned char)((c>>6) & 0x3f | 0x80),fp); fputc((unsigned char)(c & 0x3f | 0x80),fp); } /* if */ } else { /* not UTF-8 mode */ fputc((unsigned char)*string,fp); } /* if */ string++; count++; } /* while */ return count; } static size_t fgets_char(FILE *fp, char *string, size_t max) { size_t index; int c,lastcr; index=0; lastcr=0; for ( ;; ) { assert(index=size) /* +1 because directory separator is appended */ return NULL; _tcscpy(dest,prefix); /* append a directory separator (if not already present) */ len=_tcslen(dest); if (len==0) return NULL; /* empty start directory is not allowed */ if (dest[len-1]!=__T(DIRSEP_CHAR) && dest[len-1]!=__T('/') && len+1=size) return NULL; _tcscat(dest,src); /* change forward slashes into proper directory separators */ #if DIRSEP_CHAR!='/' while ((ptr=_tcschr(dest,__T('/')))!=NULL) *ptr=__T(DIRSEP_CHAR); #endif return dest; #else if (_tcslen(src)>=size) return NULL; _tcscpy(dest,src); /* change forward slashes into proper directory separators */ #if DIRSEP_CHAR!='/' while ((ptr=_tcschr(dest,__T('/')))!=NULL) *ptr=__T(DIRSEP_CHAR); #endif return dest; #endif } /* File: fopen(const name[], filemode: mode) */ static cell AMX_NATIVE_CALL n_fopen(AMX *amx, cell *params) { TCHAR *attrib,*altattrib; TCHAR *name,fullname[_MAX_PATH]; FILE *f = NULL; altattrib=NULL; switch (params[2] & 0x7fff) { case io_read: attrib=__T("rb"); break; case io_write: attrib=__T("wb"); break; case io_readwrite: attrib=__T("r+b"); altattrib=__T("w+b"); break; case io_append: attrib=__T("ab"); break; default: return 0; } /* switch */ /* get the filename */ amx_StrParam(amx,params[1],name); if (name!=NULL && completename(fullname,name,sizeof fullname)!=NULL) { f=_tfopen(fullname,attrib); if (f==NULL && altattrib!=NULL) f=_tfopen(fullname,altattrib); } /* if */ return (cell)f; } /* fclose(File: handle) */ static cell AMX_NATIVE_CALL n_fclose(AMX *amx, cell *params) { UNUSED_PARAM(amx); return fclose((FILE*)params[1]) == 0; } /* fwrite(File: handle, const string[]) */ static cell AMX_NATIVE_CALL n_fwrite(AMX *amx, cell *params) { int r = 0; cell *cptr; char *str; int len; amx_GetAddr(amx,params[2],&cptr); amx_StrLen(cptr,&len); if (len==0) return 0; if ((ucell)*cptr>UNPACKEDMAX) { /* the string is packed, write it as an ASCII/ANSI string */ if ((str=(char*)alloca(len + 1))!=NULL) r=_tfputs(str,(FILE*)params[1]); } else { /* the string is unpacked, write it as UTF-8 */ r=fputs_cell((FILE*)params[1],cptr,1); } /* if */ return r; } /* fread(File: handle, string[], size=sizeof string, bool:pack=false) */ static cell AMX_NATIVE_CALL n_fread(AMX *amx, cell *params) { int chars,max; char *str; cell *cptr; max=(int)params[3]; if (max<=0) return 0; if (params[4]) max*=sizeof(cell); amx_GetAddr(amx,params[2],&cptr); str=(char *)alloca(max); if (str==NULL || cptr==NULL) { amx_RaiseError(amx, AMX_ERR_NATIVE); return 0; } /* if */ if (params[4]) { /* store as packed string, read an ASCII/ANSI string */ chars=fgets_char((FILE*)params[1],str,max); assert(chars0) // params[2]=-params[2]; break; default: return 0; } /* switch */ UNUSED_PARAM(amx); return lseek(fileno((FILE*)params[1]),params[2],whence); } /* bool: fexist(const name[]) */ static cell AMX_NATIVE_CALL n_fexist(AMX *amx, cell *params) { int r=1; TCHAR *name,fullname[_MAX_PATH]; amx_StrParam(amx,params[1],name); if (name!=NULL && completename(fullname,name,sizeof fullname)!=NULL) r=access(fullname,0); return r==0; } /* bool: fremove(const name[]) */ static cell AMX_NATIVE_CALL n_fremove(AMX *amx, cell *params) { int r=1; TCHAR *name,fullname[_MAX_PATH]; amx_StrParam(amx,params[1],name); if (name!=NULL && completename(fullname,name,sizeof fullname)!=NULL) r=remove(fullname); return r==0; } /* flength(File: handle) */ static cell AMX_NATIVE_CALL n_flength(AMX *amx, cell *params) { long l,c; int fn=fileno((FILE*)params[1]); c=lseek(fn,0,SEEK_CUR); /* save the current position */ l=lseek(fn,0,SEEK_END); /* return the file position at its end */ lseek(fn,c,SEEK_SET); /* restore the file pointer */ UNUSED_PARAM(amx); return l; } #if defined __cplusplus extern "C" #endif AMX_NATIVE_INFO file_Natives[] = { { "fopen", n_fopen }, { "fclose", n_fclose }, { "fwrite", n_fwrite }, { "fread", n_fread }, { "fputchar", n_fputchar }, { "fgetchar", n_fgetchar }, { "fblockwrite", n_fblockwrite }, { "fblockread", n_fblockread }, { "ftemp", n_ftemp }, { "fseek", n_fseek }, { "fexist", n_fexist }, { "flength", n_flength }, { "fremove", n_fremove }, { NULL, NULL } /* terminator */ }; int AMXEXPORT amx_FileInit(AMX *amx) { return amx_Register(amx, file_Natives, -1); } int AMXEXPORT amx_FileCleanup(AMX *amx) { UNUSED_PARAM(amx); return AMX_ERR_NONE; }