From f294db4ccf45f6274e65260dd6f9a2c5faa94313 Mon Sep 17 00:00:00 2001 From: Andrey Nazarov Date: Tue, 14 Aug 2007 20:18:08 +0000 Subject: Initial import of the new Q2PRO tree. --- source/files.c | 2733 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 2733 insertions(+) create mode 100644 source/files.c (limited to 'source/files.c') diff --git a/source/files.c b/source/files.c new file mode 100644 index 0000000..e33bc9e --- /dev/null +++ b/source/files.c @@ -0,0 +1,2733 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +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, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#include "com_local.h" +#ifdef USE_ZLIB +#include +#include "unzip.h" +#endif + +/* +============================================================================= + +QUAKE FILESYSTEM + +- transparently merged from several sources +- relative to the single virtual root +- case insensitive, at least at pakfiles level +- only '/' separators supported + +============================================================================= +*/ + +#define MAX_FILES_IN_PK2 0x4000 +#define MAX_FILE_HANDLES 8 + +#define FS_Malloc( size ) Z_TagMalloc( size, TAG_FILESYSTEM ) +#define FS_CopyString( string ) Z_TagCopyString( string, TAG_FILESYSTEM ) + +#ifdef _WIN32 +#define FS_strcmp Q_stricmp +#else +#define FS_strcmp strcmp +#endif + +#define MAX_READ 0x40000 // read in blocks of 256k +#define MAX_WRITE 0x40000 // write in blocks of 256k + + +// +// in memory +// + +typedef struct packfile_s { + char *name; + int filepos; + int filelen; + + struct packfile_s *hashNext; +} packfile_t; + +typedef struct pack_s { +#ifdef USE_ZLIB + unzFile zFile; +#endif + FILE *fp; + int numfiles; + packfile_t *files; + packfile_t **fileHash; + int hashSize; + char filename[1]; +} pack_t; + +typedef struct searchpath_s { + struct searchpath_s *next; + struct pack_s *pack; // only one of filename / pack will be used + char filename[1]; +} searchpath_t; + +typedef enum fsFileType_e { + FS_FREE, + FS_REAL, + FS_PAK, +#ifdef USE_ZLIB + FS_PK2, + FS_GZIP, +#endif + FS_BAD +} fsFileType_t; + +typedef struct fsFile_s { + char name[MAX_QPATH]; + char fullpath[MAX_OSPATH]; + fsFileType_t type; + uint32 mode; + FILE *fp; +#ifdef USE_ZLIB + void *zfp; +#endif + packfile_t *pak; + qboolean unique; + int length; +} fsFile_t; + +typedef struct fsLink_s { + char *target; + int targetLength, nameLength; + struct fsLink_s *next; + char name[1]; +} fsLink_t; + +static char fs_gamedir[MAX_OSPATH]; + +static cvar_t *fs_debug; +static cvar_t *fs_restrict_mask; + +static searchpath_t *fs_searchpaths; +static searchpath_t *fs_base_searchpaths; + +static fsLink_t *fs_links; + +static fsFile_t fs_files[MAX_FILE_HANDLES]; + +static qboolean fs_fileFromPak; + +static int fs_count_read, fs_count_strcmp, fs_count_open; + +cvar_t *fs_game; + +fsAPI_t fs; + +void FS_AddGameDirectory( const char *fmt, ... ) q_printf( 1, 2 ); + +/* + +All of Quake's data access is through a hierchal file system, +but the contents of the file system can be transparently merged from several sources. + +The "base directory" is the path to the directory holding the quake.exe and all game directories. +The sys_* files pass this to host_init in quakeparms_t->basedir. +This can be overridden with the "-basedir" command line parm to allow code debugging in a different directory. +The base directory is only used during filesystem initialization. + +The "game directory" is the first tree on the search path and directory that +all generated files (savegames, screenshots, demos, config files) will be saved to. +This can be overridden with the "-game" command line parameter. +The game directory can never be changed while quake is executing. +This is a precacution against having a malicious server instruct clients to write files over areas they shouldn't. + +*/ + +/* +================ +FS_DPrintf +================ +*/ +static void FS_DPrintf( char *format, ... ) { + va_list argptr; + char string[MAXPRINTMSG]; + + if( !fs_debug || !fs_debug->integer ) { + return; + } + + va_start( argptr, format ); + Q_vsnprintf( string, sizeof( string ), format, argptr ); + va_end( argptr ); + + Com_Printf( S_COLOR_CYAN "%s", string ); +} + +/* +================ +FS_AllocHandle +================ +*/ +static fsFile_t *FS_AllocHandle( fileHandle_t *f, const char *name ) { + fsFile_t *file; + int i; + + for( i = 0, file = fs_files; i < MAX_FILE_HANDLES; i++, file++ ) { + if( file->type == FS_FREE ) { + break; + } + } + + if( i == MAX_FILE_HANDLES ) { + Com_Error( ERR_FATAL, "FS_AllocHandle: none free" ); + } + + Q_strncpyz( file->name, name, sizeof( file->name ) ); + *f = i + 1; + return file; +} + +/* +================ +FS_FileForHandle +================ +*/ +static fsFile_t *FS_FileForHandle( fileHandle_t f ) { + fsFile_t *file; + + if( f <= 0 || f >= MAX_FILE_HANDLES + 1 ) { + Com_Error( ERR_FATAL, "FS_FileForHandle: invalid handle: %i", f ); + } + + file = &fs_files[f - 1]; + if( file->type == FS_FREE ) { + Com_Error( ERR_FATAL, "FS_FileForHandle: free file: %i", f ); + } + + if( file->type < FS_FREE || file->type >= FS_BAD ) { + Com_Error( ERR_FATAL, "FS_FileForHandle: invalid file type: %i", file->type ); + } + + return file; +} + +qboolean FS_ValidatePath( const char *s ) { + const char *start; + int back; + + // check for leading slash + // check for empty path + if( *s == '/' || *s == '\\' || *s == 0 ) { + return qfalse; + } + + back = 0; + start = s; + while( *s ) { + // check for ".." + if( *s == '.' && s[1] == '.' ) { + if( back > 1 ) { + return qfalse; + } + back++; + } + if( *s == '/' || *s == '\\' ) { + // check for two slashes in a row + // check for trailing slash + if( ( s[1] == '/' || s[1] == '\\' || s[1] == 0 ) ) { + return qfalse; + } + } + if( *s == ':' ) { + // check for "X:\" + if( s[1] == '\\' || s[1] == '/' ) { + return qfalse; + } + } + s++; + } + + // check length + if( s - start > MAX_QPATH ) { + return qfalse; + } + + return qtrue; +} + +/* +================ +FS_ConvertToSysPath +================ +*/ +static char *FS_ConvertToSysPath( char *path ) { + char *s; + + s = path; + while( *s ) { + if( *s == '/' || *s == '\\' ) { + *s = PATH_SEP_CHAR; + } + s++; + } + + return path; + +} + +/* +================ +FS_GetFileLength + +Gets current file length. +For GZIP files, returns uncompressed length +(very slow operation because it has to uncompress the whole file). +================ +*/ +int FS_GetFileLength( fileHandle_t f ) { + fsFile_t *file = FS_FileForHandle( f ); + int pos, length; + + if( ( file->mode & FS_MODE_MASK ) == FS_MODE_READ ) { +#ifdef USE_ZLIB + if( file->type == FS_GZIP ) { + goto gzipHack; + } +#endif + return file->length; + } + + switch( file->type ) { + case FS_REAL: + pos = ftell( file->fp ); + fseek( file->fp, 0, SEEK_END ); + length = ftell( file->fp ); + fseek( file->fp, pos, SEEK_SET ); + break; +#ifdef USE_ZLIB + case FS_GZIP: +gzipHack: + pos = gztell( file->zfp ); + gzseek( file->zfp, 0x7FFFFFFF, SEEK_SET ); + length = gztell( file->zfp ); + gzseek( file->zfp, pos, SEEK_SET ); + break; +#endif + default: + Com_Error( ERR_FATAL, "FS_GetFileLength: bad file type" ); + length = -1; + break; + } + + return length; +} + +int FS_GetFileLengthNoCache( fileHandle_t f ) { + fsFile_t *file = FS_FileForHandle( f ); + int pos, length; + + switch( file->type ) { + case FS_REAL: + pos = ftell( file->fp ); + fseek( file->fp, 0, SEEK_END ); + length = ftell( file->fp ); + fseek( file->fp, pos, SEEK_SET ); + break; + case FS_PAK: + length = file->length; + break; +#ifdef USE_ZLIB + case FS_GZIP: + pos = gztell( file->zfp ); + gzseek( file->zfp, 0x7FFFFFFF, SEEK_SET ); + length = gztell( file->zfp ); + gzseek( file->zfp, pos, SEEK_SET ); + break; + case FS_PK2: + length = file->length; + break; +#endif + default: + Com_Error( ERR_FATAL, "FS_GetFileLengthNoCache: bad file type" ); + length = -1; + break; + } + + return length; +} + +/* +================ +FS_GetFileName +================ +*/ +const char *FS_GetFileName( fileHandle_t f ) { + return ( FS_FileForHandle( f ) )->name; +} + +const char *FS_GetFileFullPath( fileHandle_t f ) { + return ( FS_FileForHandle( f ) )->fullpath; +} + +/* +============ +FS_CreatePath + +Creates any directories needed to store the given filename. +Expects a fully qualified path. +============ +*/ +void FS_CreatePath( const char *path ) { + char buffer[MAX_OSPATH]; + char *ofs; + + Q_strncpyz( buffer, path, sizeof( buffer ) ); + FS_ConvertToSysPath( buffer ); + + FS_DPrintf( "FS_CreatePath( '%s' )\n", buffer ); + + for( ofs = buffer + 1; *ofs; ofs++ ) { + if( *ofs == PATH_SEP_CHAR ) { + // create the directory + *ofs = 0; + Sys_Mkdir( buffer ); + *ofs = PATH_SEP_CHAR; + } + } +} + + +/* +============== +FS_FCloseFile +============== +*/ +void FS_FCloseFile( fileHandle_t f ) { + fsFile_t *file = FS_FileForHandle( f ); + + FS_DPrintf( "FS_FCloseFile( '%s' )\n", file->fullpath ); + + switch( file->type ) { + case FS_REAL: + fclose( file->fp ); + break; + case FS_PAK: + if( file->unique ) { + fclose( file->fp ); + } + break; +#ifdef USE_ZLIB + case FS_GZIP: + gzclose( file->zfp ); + break; + case FS_PK2: + unzCloseCurrentFile( file->zfp ); + if( file->unique ) { + unzClose( file->zfp ); + } + break; +#endif + default: + break; + } + + /* don't clear name and mode, so post-restart reopening works */ + file->type = FS_FREE; + file->fp = NULL; +#ifdef USE_ZLIB + file->zfp = NULL; +#endif + file->pak = NULL; + file->unique = qfalse; +} + +/* +============ +FS_FOpenFileWrite + +In case of GZIP files, returns *raw* (compressed) length! +============ +*/ +static int FS_FOpenFileWrite( fsFile_t *file ) { + FILE *fp; +#ifdef USE_ZLIB + gzFile zfp; + char *ext; +#endif + char *modeStr; + fsFileType_t type; + uint32 mode; + +#ifdef _WIN32 + /* allow writing into basedir on Windows */ + if( ( file->mode & FS_PATH_MASK ) == FS_PATH_BASE ) { + Com_sprintf( file->fullpath, sizeof( file->fullpath ), + "%s/" BASEGAME "/%s", sys_basedir->string, file->name ); + } else +#endif + { + Com_sprintf( file->fullpath, sizeof( file->fullpath ), + "%s/%s", fs_gamedir, file->name ); + } + + mode = file->mode & FS_MODE_MASK; + switch( mode ) { + case FS_MODE_APPEND: + modeStr = "ab"; + break; + case FS_MODE_WRITE: + modeStr = "wb"; + break; + case FS_MODE_RDWR: + modeStr = "r+b"; + break; + default: + Com_Error( ERR_FATAL, "FS_FOpenFileWrite( '%s' ): invalid mode mask", + file->fullpath ); + modeStr = NULL; + break; + } + + FS_ConvertToSysPath( file->fullpath ); + + FS_CreatePath( file->fullpath ); + + fp = fopen( file->fullpath, modeStr ); + + if( !fp ) { + FS_DPrintf( "FS_FOpenFileWrite: fopen( '%s', '%s' ) failed\n", + file->fullpath, modeStr ); + return -1; + } + + type = FS_REAL; +#ifdef USE_ZLIB + if( !( file->mode & FS_FLAG_RAW ) ) { + ext = COM_FileExtension( file->fullpath ); + if( !strcmp( ext, ".gz" ) ) { + zfp = gzdopen( fileno( fp ), modeStr ); + if( !zfp ) { + FS_DPrintf( "FS_FOpenFileWrite: gzopen( '%s', '%s' ) failed\n", + file->fullpath, modeStr ); + fclose( fp ); + return -1; + } + file->zfp = zfp; + type = FS_GZIP; + } + } +#endif + + FS_DPrintf( "FS_FOpenFileWrite( '%s' )\n", file->fullpath ); + + file->fp = fp; + file->type = type; + file->length = 0; + file->unique = qtrue; + + if( mode == FS_MODE_WRITE ) { + return 0; + } + + if( mode == FS_MODE_RDWR ) { + fseek( fp, 0, SEEK_END ); + } + + return ftell( fp ); +} + +static searchpath_t *FS_SearchPath( uint32 flags ) { + if( ( flags & FS_PATH_MASK ) == FS_PATH_BASE ) { + return fs_base_searchpaths; + } + return fs_searchpaths; +} + + +/* +=========== +FS_FOpenFileRead + +Finds the file in the search path. +Fills fsFile_t and returns file length. +In case of GZIP files, returns *raw* (compressed) length! +Used for streaming data out of either a pak file or +a seperate file. +=========== +*/ +static int FS_FOpenFileRead( fsFile_t *file, qboolean unique ) { + searchpath_t *search; + pack_t *pak; + uint32 hash; + packfile_t *entry; + FILE *fp; +#ifdef USE_ZLIB + void *zfp; + char *ext; +#endif + fsFileType_t type; + int pos, length; + + fs_fileFromPak = qfalse; + fs_count_read++; + +// +// search through the path, one element at a time +// + hash = Com_HashPath( file->name, 0 ); + + for( search = FS_SearchPath( file->mode ); search; search = search->next ) { + if( ( file->mode & FS_PATH_MASK ) == FS_PATH_GAME ) { + if( fs_searchpaths != fs_base_searchpaths && search == fs_base_searchpaths ) { + /* consider baseq2 a gamedir if no gamedir loaded */ + break; + } + } + + // is the element a pak file? + if( search->pack ) { + if( ( file->mode & FS_TYPE_MASK ) == FS_TYPE_REAL ) { + continue; + } + // look through all the pak file elements + pak = search->pack; + entry = pak->fileHash[ hash & ( pak->hashSize - 1 ) ]; + for( ; entry; entry = entry->hashNext ) { + fs_count_strcmp++; + if( !Q_stricmp( entry->name, file->name ) ) { + // found it! + fs_fileFromPak = qtrue; + + Com_sprintf( file->fullpath, sizeof( file->fullpath ), "%s/%s", pak->filename, file->name ); + + // open a new file on the pakfile +#ifdef USE_ZLIB + if( pak->zFile ) { + if( unique ) { + zfp = unzReOpen( pak->filename, pak->zFile ); + if( !zfp ) { + Com_Error( ERR_FATAL, "FS_FOpenFileRead: unzReOpen( '%s' ) failed", pak->filename ); + } + } else { + zfp = pak->zFile; + } + if( unzSetCurrentFileInfoPosition( zfp, entry->filepos ) == -1 ) { + Com_Error( ERR_FATAL, "FS_FOpenFileRead: unzSetCurrentFileInfoPosition( '%s/%s' ) failed", pak->filename, entry->name ); + } + if( unzOpenCurrentFile( zfp ) != UNZ_OK ) { + Com_Error( ERR_FATAL, "FS_FOpenFileRead: unzReOpen( '%s/%s' ) failed", pak->filename, entry->name ); + } + + file->zfp = zfp; + file->type = FS_PK2; + } else +#endif + { + if( unique ) { + fp = fopen( pak->filename, "rb" ); + if( !fp ) { + Com_Error( ERR_FATAL, "Couldn't reopen %s", pak->filename ); + } + } else { + fp = pak->fp; + } + + fseek( fp, entry->filepos, SEEK_SET ); + + file->fp = fp; + file->type = FS_PAK; + } + + length = entry->filelen; + file->pak = entry; + file->length = length; + file->unique = unique; + + FS_DPrintf( "FS_FOpenFileRead( '%s/%s' )\n", pak->filename, file->name ); + + return length; + } + } + } else { + if( ( file->mode & FS_TYPE_MASK ) == FS_TYPE_PAK ) { + continue; + } + // check a file in the directory tree + Com_sprintf( file->fullpath, sizeof( file->fullpath ), "%s/%s", + search->filename, file->name ); + + FS_ConvertToSysPath( file->fullpath ); + + fs_count_open++; + fp = fopen( file->fullpath, "rb" ); + if( !fp ) { + continue; + } + + type = FS_REAL; +#ifdef USE_ZLIB + if( !( file->mode & FS_FLAG_RAW ) ) { + ext = COM_FileExtension( file->fullpath ); + if( !strcmp( ext, ".gz" ) ) { + zfp = gzdopen( fileno( fp ), "rb" ); + if( !zfp ) { + Com_WPrintf( "gzopen( '%s', 'rb' ) failed, " + "not a GZIP file?\n", file->fullpath ); + fclose( fp ); + return -1; + } + file->zfp = zfp; + type = FS_GZIP; + } + } +#endif + + FS_DPrintf( "FS_FOpenFileRead( '%s' )\n", file->fullpath ); + + file->fp = fp; + file->type = type; + file->unique = qtrue; + + pos = ftell( fp ); + fseek( fp, 0, SEEK_END ); + length = ftell( fp ); + fseek( fp, pos, SEEK_SET ); + + file->length = length; + + return length; + } + } + + FS_DPrintf( "FS_FOpenFileRead( '%s' ): couldn't find\n", file->name ); + + return -1; +} + +/* +================= +FS_LastFileFromPak +================= +*/ +qboolean FS_LastFileFromPak( void ) { + return fs_fileFromPak; +} + + +/* +================= +FS_ReadFile + +Properly handles partial reads +================= +*/ +int FS_Read( void *buffer, int len, fileHandle_t hFile ) { + int block, remaining = len; + int read = 0; + byte *buf = (byte *)buffer; + fsFile_t *file = FS_FileForHandle( hFile ); + + // read in chunks for progress bar + while( remaining ) { + block = remaining; + if( block > MAX_READ ) + block = MAX_READ; + switch( file->type ) { + case FS_REAL: + case FS_PAK: + read = fread( buf, 1, block, file->fp ); + break; +#ifdef USE_ZLIB + case FS_GZIP: + read = gzread( file->zfp, buf, block ); + break; + case FS_PK2: + read = unzReadCurrentFile( file->zfp, buf, block ); + break; +#endif + default: + break; + } + if( read == 0 ) { + return len - remaining; + } + if( read == -1 ) { + Com_Error( ERR_FATAL, "FS_Read: -1 bytes read" ); + } + + remaining -= read; + buf += read; + } + + return len; +} + +/* +================= +FS_Write + +Properly handles partial writes +================= +*/ +int FS_Write( const void *buffer, int len, fileHandle_t hFile ) { + int block, remaining = len; + int write = 0; + byte *buf = (byte *)buffer; + fsFile_t *file = FS_FileForHandle( hFile ); + + // read in chunks for progress bar + while( remaining ) { + block = remaining; + if( block > MAX_WRITE ) + block = MAX_WRITE; + switch( file->type ) { + case FS_REAL: + write = fwrite( buf, 1, block, file->fp ); + break; +#ifdef USE_ZLIB + case FS_GZIP: + write = gzwrite( file->zfp, buf, block ); + break; +#endif + default: + Com_Error( ERR_FATAL, "FS_Write: illegal file type" ); + break; + } + if( write == 0 ) { + return len - remaining; + } + if( write == -1 ) { + Com_Error( ERR_FATAL, "FS_Write: -1 bytes written" ); + } + + remaining -= write; + buf += write; + } + + if( ( file->mode & FS_FLUSH_MASK ) == FS_FLUSH_SYNC ) { + switch( file->type ) { + case FS_REAL: + fflush( file->fp ); + break; +#ifdef USE_ZLIB + case FS_GZIP: + gzflush( file->zfp, Z_SYNC_FLUSH ); + break; +#endif + default: + break; + } + } + + return len; +} + +static char *FS_ExpandLinks( const char *filename ) { + static char buffer[MAX_QPATH]; + fsLink_t *l; + int length; + + length = strlen( filename ); + for( l = fs_links; l; l = l->next ) { + if( l->nameLength > length ) { + continue; + } + if( !Q_stricmpn( l->name, filename, l->nameLength ) ) { + if( l->targetLength + length - l->nameLength > MAX_QPATH - 1 ) { + FS_DPrintf( "FS_ExpandLinks( '%s' ): MAX_QPATH exceeded\n", + filename ); + return ( char * )filename; + } + memcpy( buffer, l->target, l->targetLength ); + strcpy( buffer + l->targetLength, filename + l->nameLength ); + FS_DPrintf( "FS_ExpandLinks( '%s' ) --> '%s'\n", filename, buffer ); + return buffer; + } + } + + return ( char * )filename; +} + +/* +============ +FS_FOpenFile +============ +*/ +int FS_FOpenFile( const char *filename, fileHandle_t *f, uint32 mode ) { + fsFile_t *file; + fileHandle_t hFile; + int ret = -1; + + if( !filename || !f ) { + Com_Error( ERR_FATAL, "FS_FOpenFile: NULL" ); + } + + *f = 0; + + if( !fs_searchpaths ) { + return -1; /* not yet initialized */ + } + + if( ( mode & FS_MODE_MASK ) == FS_MODE_READ ) { + filename = FS_ExpandLinks( filename ); + } + + if( !FS_ValidatePath( filename ) ) { + FS_DPrintf( "FS_FOpenFile: refusing invalid path: %s\n", filename ); + return -1; + } + + /* allocate new file handle */ + file = FS_AllocHandle( &hFile, filename ); + file->mode = mode; + + mode &= FS_MODE_MASK; + switch( mode ) { + case FS_MODE_READ: + ret = FS_FOpenFileRead( file, qtrue ); + break; + case FS_MODE_WRITE: + case FS_MODE_APPEND: + case FS_MODE_RDWR: + ret = FS_FOpenFileWrite( file ); + break; + default: + Com_Error( ERR_FATAL, "FS_FOpenFile: illegal mode: %u", mode ); + break; + } + + /* if succeeded, store file handle */ + if( ret != -1 ) { + *f = hFile; + } + + return ret; +} + + +/* +============ +FS_Tell +============ +*/ +int FS_Tell( fileHandle_t f ) { + fsFile_t *file = FS_FileForHandle( f ); + int length; + + switch( file->type ) { + case FS_REAL: + length = ftell( file->fp ); + break; + case FS_PAK: + length = ftell( file->fp ) - file->pak->filepos; + break; +#ifdef USE_ZLIB + case FS_GZIP: + length = gztell( file->zfp ); + break; + case FS_PK2: + length = unztell( file->zfp ); + break; +#endif + default: + length = -1; + break; + } + + return length; +} + +/* +============ +FS_RawTell +============ +*/ +int FS_RawTell( fileHandle_t f ) { + fsFile_t *file = FS_FileForHandle( f ); + int length; + + switch( file->type ) { + case FS_REAL: + length = ftell( file->fp ); + break; + case FS_PAK: + length = ftell( file->fp ) - file->pak->filepos; + break; +#ifdef USE_ZLIB + case FS_GZIP: + length = ftell( file->fp ); + break; + case FS_PK2: + length = unztell( file->zfp ); + break; +#endif + default: + length = -1; + break; + } + + return length; +} + +#define MAX_LOAD_BUFFER 0x100000 // 1 MiB + +// static buffer for small, stacked file loads and temp allocations +// the last allocation may be easily undone +static byte loadBuffer[MAX_LOAD_BUFFER]; +static byte *loadLast; +static int loadSaved; +static int loadInuse; +static int loadStack; + +// for statistics +static int loadCount; +static int loadCountStatic; + +// very simple one-file cache for *.bsp files +static byte *cachedBytes; +static char cachedPath[MAX_QPATH]; +static int cachedLength; +static int cachedInuse; + + +/* +============ +FS_LoadFile + +Filenames are relative to the quake search path +a null buffer will just return the file length without loading +============ +*/ +int FS_LoadFileEx( const char *path, void **buffer, uint32 flags ) { + fsFile_t *file; + fileHandle_t f; + byte *buf; + int length; + + if( !path ) { + Com_Error( ERR_FATAL, "FS_LoadFile: NULL" ); + } + + if( buffer ) { + *buffer = NULL; + } + + if( !fs_searchpaths ) { + return -1; /* not yet initialized */ + } + + path = FS_ExpandLinks( path ); + + if( !FS_ValidatePath( path ) ) { + FS_DPrintf( "FS_LoadFile: refusing invalid path: %s\n", path ); + return -1; + } + + if( buffer && ( flags & FS_FLAG_CACHE ) ) { + if( !Q_stricmp( cachedPath, path ) ) { + //Com_Printf( S_COLOR_MAGENTA"cached: %s\n", path ); + *buffer = cachedBytes; + cachedInuse++; + return cachedLength; + } + } + + /* allocate new file handle */ + file = FS_AllocHandle( &f, path ); + flags &= ~FS_MODE_MASK; + file->mode = flags | FS_MODE_READ; + + /* look for it in the filesystem or pack files */ + length = FS_FOpenFileRead( file, qfalse ); + if( length == -1 ) { + return -1; + } + + /* get real file length */ + length = FS_GetFileLength( f ); + + if( buffer ) { + if( !( flags & FS_FLAG_CACHE ) && loadInuse + length < MAX_LOAD_BUFFER && !( fs_restrict_mask->integer & 16 ) ) { + // Com_Printf(S_COLOR_MAGENTA"static: %s: %d\n",path,length); + buf = &loadBuffer[loadInuse]; + loadLast = buf; + loadSaved = loadInuse; + loadInuse += length + 1; + loadInuse = ( loadInuse + 3 ) & ~3; + loadStack++; + loadCountStatic++; + } else { +// Com_Printf(S_COLOR_MAGENTA"alloc: %s: %d\n",path,length); + buf = FS_Malloc( length + 1 ); + loadCount++; + } + *buffer = buf; + + FS_Read( buf, length, f ); + buf[length] = 0; + + if( flags & FS_FLAG_CACHE ) { + FS_FlushCache(); + cachedBytes = buf; + cachedLength = length; + strcpy( cachedPath, path ); + cachedInuse = 1; + } + } + + FS_FCloseFile( f ); + + return length; +} + +int FS_LoadFile( const char *path, void **buffer ) { + return FS_LoadFileEx( path, buffer, 0 ); +} + +void *FS_AllocTempMem( int length ) { + byte *buf; + + if( loadInuse + length <= MAX_LOAD_BUFFER && !( fs_restrict_mask->integer & 16 ) ) { + buf = &loadBuffer[loadInuse]; + loadLast = buf; + loadSaved = loadInuse; + loadInuse += length; + loadInuse = ( loadInuse + 3 ) & ~3; + loadStack++; + loadCountStatic++; + } else { + // Com_Printf(S_COLOR_MAGENTA"alloc %d\n",length); + buf = FS_Malloc( length ); + loadCount++; + } + return buf; +} + +/* +============= +FS_FreeFile +============= +*/ +void FS_FreeFile( void *buffer ) { + if( !buffer ) { + Com_Error( ERR_FATAL, "FS_FreeFile: NULL" ); + } + if( ( byte * )buffer == cachedBytes ) { + if( cachedInuse == 0 ) { + Com_Error( ERR_FATAL, "FS_FreeFile: cachedInuse is zero" ); + } + cachedInuse--; + return; + } + if( ( byte * )buffer >= loadBuffer && ( byte * )buffer < loadBuffer + MAX_LOAD_BUFFER ) { + if( loadStack == 0 ) { + Com_Error( ERR_FATAL, "FS_FreeFile: loadStack is zero" ); + } + loadStack--; + if( loadStack == 0 ) { + loadInuse = 0; + // Com_Printf(S_COLOR_MAGENTA"clear\n"); + } else if( buffer == loadLast ) { + loadInuse = loadSaved; + // Com_Printf(S_COLOR_MAGENTA"partial\n"); + } + } else { + Z_Free( buffer ); + } +} + +void FS_FlushCache( void ) { + if( cachedInuse ) { + Com_Error( ERR_FATAL, "FS_FlushCache: cachedInuse is nonzero" ); + } + if( !cachedBytes ) { + return; + } + Z_Free( cachedBytes ); + cachedBytes = NULL; + cachedLength = 0; + cachedPath[0] = 0; +} + +/* +============= +FS_CopyFile +============= +*/ +qboolean FS_CopyFile( const char *src, const char *dst ) { + fileHandle_t hSrc, hDst; + byte buffer[MAX_READ]; + int len; + int size; + + FS_DPrintf( "FS_CopyFile( '%s', '%s' )\n", src, dst ); + + size = FS_FOpenFile( src, &hSrc, FS_MODE_READ ); + if( !hSrc ) { + return qfalse; + } + + FS_FOpenFile( dst, &hDst, FS_MODE_WRITE ); + if( !hDst ) { + FS_FCloseFile( hSrc ); + return qfalse; + } + + while( size ) { + len = size; + if( len > sizeof( buffer ) ) { + len = sizeof( buffer ); + } + if( !( len = FS_Read( buffer, len, hSrc ) ) ) { + break; + } + FS_Write( buffer, len, hDst ); + size -= len; + } + + FS_FCloseFile( hSrc ); + FS_FCloseFile( hDst ); + + if( size ) { + return qfalse; + } + + return qtrue; +} + +/* +================ +FS_RemoveFile +================ +*/ +qboolean FS_RemoveFile( const char *filename ) { + char path[MAX_OSPATH]; + + if( !FS_ValidatePath( filename ) ) { + FS_DPrintf( "FS_RemoveFile: refusing invalid path: %s\n", filename ); + return qfalse; + } + + Com_sprintf( path, sizeof( path ), "%s/%s", fs_gamedir, filename ); + FS_ConvertToSysPath( path ); + + if( !Sys_RemoveFile( path ) ) { + return qfalse; + } + + return qtrue; +} + +/* +================ +FS_RemoveFile +================ +*/ +qboolean FS_RenameFile( const char *from, const char *to ) { + char frompath[MAX_OSPATH]; + char topath[MAX_OSPATH]; + + if( !FS_ValidatePath( from ) || !FS_ValidatePath( to ) ) { + FS_DPrintf( "FS_RenameFile: %s, %s: refusing invalid path\n", from, to ); + return qfalse; + } + + Com_sprintf( frompath, sizeof( frompath ), "%s/%s", fs_gamedir, from ); + Com_sprintf( topath, sizeof( topath ), "%s/%s", fs_gamedir, to ); + + FS_ConvertToSysPath( frompath ); + FS_ConvertToSysPath( topath ); + + if( !Sys_RenameFile( frompath, topath ) ) { + FS_DPrintf( "FS_RenameFile: Sys_RenameFile( '%s', '%s' ) failed\n", frompath, topath ); + return qfalse; + } + + return qtrue; +} + +/* +================ +FS_FPrintf +================ +*/ +void FS_FPrintf( fileHandle_t f, const char *format, ... ) { + va_list argptr; + char string[MAXPRINTMSG]; + int len; + + va_start( argptr, format ); + len = Q_vsnprintf( string, sizeof( string ), format, argptr ); + va_end( argptr ); + + FS_Write( string, len, f ); +} + +uint32 _Com_HashPath( const char *string, int hashSize ) { + uint32 hash; + uint32 c; + + hash = 0; + while( *string ) { + c = *string++; + if( c == '\\' ) { + c = '/'; + } else { + c = Q_tolower( c ); + } + hash = 127 * hash + c; + } + + hash = ( hash >> 20 ) ^ ( hash >> 10 ) ^ hash; + return hash & ( hashSize - 1 ); +} + +/* +================= +FS_LoadPakFile + +Takes an explicit (not game tree related) path to a pak file. + +Loads the header and directory, adding the files at the beginning +of the list so they override previous pack files. +================= +*/ +static pack_t *FS_LoadPakFile( const char *packfile ) { + dpackheader_t header; + int i; + packfile_t *file; + dpackfile_t *dfile; + int numpackfiles; + char *names; + int namesLength; + pack_t *pack; + FILE *packhandle; + dpackfile_t info[MAX_FILES_IN_PACK]; + int hashSize; + uint32 hash; + int len; + + packhandle = fopen( packfile, "rb" ); + if( !packhandle ) { + Com_WPrintf( "Couldn't open %s\n", packfile ); + return NULL; + } + + if( fread( &header, 1, sizeof( header ), packhandle ) != sizeof( header ) ) { + Com_WPrintf( "Reading header failed on %s\n", packfile ); + goto fail; + } + + if( LittleLong( header.ident ) != IDPAKHEADER ) { + Com_WPrintf( "%s is not a 'PACK' file\n", packfile ); + goto fail; + } + + header.dirlen = LittleLong( header.dirlen ); + if( header.dirlen % sizeof( dpackfile_t ) ) { + Com_WPrintf( "%s has bad directory length\n", packfile ); + goto fail; + } + + numpackfiles = header.dirlen / sizeof( dpackfile_t ); + if( numpackfiles < 1 ) { + Com_WPrintf( "%s has bad number of files: %i\n", packfile, numpackfiles ); + goto fail; + } + if( numpackfiles > MAX_FILES_IN_PACK ) { + Com_WPrintf( "%s has too many files: %i > %i\n", packfile, numpackfiles, MAX_FILES_IN_PACK ); + goto fail; + } + + header.dirofs = LittleLong( header.dirofs ); + if( fseek( packhandle, header.dirofs, SEEK_SET ) ) { + Com_WPrintf( "Seeking to directory failed on %s\n", packfile ); + goto fail; + } + if( fread( info, 1, header.dirlen, packhandle ) != header.dirlen ) { + Com_WPrintf( "Reading directory failed on %s\n", packfile ); + goto fail; + } + + namesLength = 0; + for( i = 0, dfile = info; i < numpackfiles; i++, dfile++ ) { + dfile->name[sizeof( dfile->name ) - 1] = 0; + namesLength += strlen( dfile->name ) + 1; + } + + hashSize = Q_CeilPowerOfTwo( numpackfiles ); + if( hashSize > 32 ) { + hashSize >>= 1; + } + + len = strlen( packfile ); + pack = FS_Malloc( sizeof( pack_t ) + + numpackfiles * sizeof( packfile_t ) + + hashSize * sizeof( packfile_t * ) + + namesLength + len ); + strcpy( pack->filename, packfile ); + pack->fp = packhandle; +#ifdef USE_ZLIB + pack->zFile = NULL; +#endif + pack->numfiles = numpackfiles; + pack->hashSize = hashSize; + pack->files = ( packfile_t * )( ( byte * )pack + sizeof( pack_t ) + len ); + pack->fileHash = ( packfile_t ** )( pack->files + numpackfiles ); + names = ( char * )( pack->fileHash + hashSize ); + memset( pack->fileHash, 0, hashSize * sizeof( packfile_t * ) ); + +// parse the directory + for( i = 0, file = pack->files, dfile = info; i < pack->numfiles; i++, file++, dfile++ ) { + len = strlen( dfile->name ) + 1; + file->name = names; + + strcpy( file->name, dfile->name ); + + file->filepos = LittleLong( dfile->filepos ); + file->filelen = LittleLong( dfile->filelen ); + + hash = Com_HashPath( file->name, hashSize ); + file->hashNext = pack->fileHash[hash]; + pack->fileHash[hash] = file; + + names += len; + } + + FS_DPrintf( "Added pakfile %s, %d files, %d hash table entries\n", + packfile, numpackfiles, hashSize ); + + return pack; + +fail: + fclose( packhandle ); + return NULL; +} + +#ifdef USE_ZLIB +/* +================= +FS_LoadZipFile +================= +*/ +static pack_t *FS_LoadZipFile( const char *packfile ) { + int i; + packfile_t *file; + char *names; + int numFiles; + pack_t *pack; + unzFile zFile; + unz_global_info zGlobalInfo; + unz_file_info zInfo; + char name[MAX_QPATH]; + int namesLength; + int hashSize; + uint32 hash; + int len; + + zFile = unzOpen( packfile ); + if( !zFile ) { + Com_WPrintf( "unzOpen() failed on %s\n", packfile ); + return NULL; + } + + if( unzGetGlobalInfo( zFile, &zGlobalInfo ) != UNZ_OK ) { + Com_WPrintf( "unzGetGlobalInfo() failed on %s\n", packfile ); + goto fail; + } + + numFiles = zGlobalInfo.number_entry; + if( numFiles > MAX_FILES_IN_PK2 ) { + Com_WPrintf( "%s has too many files, %i > %i\n", packfile, numFiles, MAX_FILES_IN_PK2 ); + goto fail; + } + + if( unzGoToFirstFile( zFile ) != UNZ_OK ) { + Com_WPrintf( "unzGoToFirstFile() failed on %s\n", packfile ); + goto fail; + } + + namesLength = 0; + for( i = 0; i < numFiles; i++ ) { + if( unzGetCurrentFileInfo( zFile, &zInfo, name, sizeof( name ), NULL, 0, NULL, 0 ) != UNZ_OK ) { + Com_WPrintf( "unzGetCurrentFileInfo() failed on %s\n", packfile ); + goto fail; + } + + namesLength += strlen( name ) + 1; + + if( i != numFiles - 1 && unzGoToNextFile( zFile ) != UNZ_OK ) { + Com_WPrintf( "unzGoToNextFile() failed on %s\n", packfile ); + + } + } + + hashSize = Q_CeilPowerOfTwo( numFiles ); + if( hashSize > 32 ) { + hashSize >>= 1; + } + + len = strlen( packfile ); + len = ( len + 3 ) & ~3; + pack = FS_Malloc( sizeof( pack_t ) + + numFiles * sizeof( packfile_t ) + + hashSize * sizeof( packfile_t * ) + + namesLength + len ); + strcpy( pack->filename, packfile ); + pack->zFile = zFile; + pack->fp = NULL; + pack->numfiles = numFiles; + pack->hashSize = hashSize; + pack->files = ( packfile_t * )( ( byte * )pack + sizeof( pack_t ) + len ); + pack->fileHash = ( packfile_t ** )( pack->files + numFiles ); + names = ( char * )( pack->fileHash + hashSize ); + memset( pack->fileHash, 0, hashSize * sizeof( packfile_t * ) ); + +// parse the directory + unzGoToFirstFile( zFile ); + + for( i = 0, file = pack->files; i < numFiles; i++, file++ ) { + unzGetCurrentFileInfo( zFile, &zInfo, name, sizeof( name ), NULL, 0, NULL, 0 ); + + len = strlen( name ) + 1; + file->name = names; + + strcpy( file->name, name ); + file->filepos = unzGetCurrentFileInfoPosition( zFile ); + file->filelen = zInfo.uncompressed_size; + + hash = Com_HashPath( file->name, hashSize ); + file->hashNext = pack->fileHash[hash]; + pack->fileHash[hash] = file; + + names += len; + + if( i != numFiles - 1 ) { + unzGoToNextFile( zFile ); + } + } + + FS_DPrintf( "Added zipfile '%s', %d files, %d hash table entries\n", + packfile, numFiles, hashSize ); + + return pack; + +fail: + unzClose( zFile ); + return NULL; +} +#endif + +static int pakcmp( const void *p1, const void *p2 ) { + const char *s1 = *( const char ** )p1; + const char *s2 = *( const char ** )p2; + + if( !strncmp( s1, "pak", 3 ) ) { + if( strncmp( s2, "pak", 3 ) ) { + return -1; + } + } else if( !strncmp( s2, "pak", 3 ) ) { + return 1; + } + + return strcmp( s1, s2 ); +} + +static void FS_LoadPackFiles( const char *extension, pack_t *(loadfunc)( const char * ) ) { + int i; + searchpath_t *search; + pack_t *pak; + char ** list; + int numFiles; + char path[MAX_OSPATH]; + + list = Sys_ListFiles( fs_gamedir, extension, FS_SEARCH_NOSORT, &numFiles ); + if( !list ) { + return; + } + qsort( list, numFiles, sizeof( list[0] ), pakcmp ); + for( i = 0; i < numFiles; i++ ) { + Com_sprintf( path, sizeof( path ), "%s/%s", fs_gamedir, list[i] ); + pak = (*loadfunc)( path ); + if( !pak ) + continue; + search = FS_Malloc( sizeof( searchpath_t ) ); + search->filename[0] = 0; + search->pack = pak; + search->next = fs_searchpaths; + fs_searchpaths = search; + } + + Sys_FreeFileList( list ); + +} + +/* +================ +FS_AddGameDirectory + +Sets fs_gamedir, adds the directory to the head of the path, +then loads and adds pak*.pak, then anything else in alphabethical order. +================ +*/ +void FS_AddGameDirectory( const char *fmt, ... ) { + va_list argptr; + searchpath_t *search; + int length; + + va_start( argptr, fmt ); + Q_vsnprintf( fs_gamedir, sizeof( fs_gamedir ), fmt, argptr ); + va_end( argptr ); + +#ifdef _WIN32 + Com_ReplaceSeparators( fs_gamedir, '/' ); +#endif + + // + // add the directory to the search path + // + if( !( fs_restrict_mask->integer & 1 ) ) { + length = strlen( fs_gamedir ); + search = FS_Malloc( sizeof( searchpath_t ) + length ); + search->pack = NULL; + strcpy( search->filename, fs_gamedir ); + search->next = fs_searchpaths; + fs_searchpaths = search; + } + + // + // add any pak files in the format *.pak + // + if( !( fs_restrict_mask->integer & 2 ) ) { + FS_LoadPackFiles( ".pak", FS_LoadPakFile ); + } + +#ifdef USE_ZLIB + // + // add any zip files in the format *.pk2 + // + if( !( fs_restrict_mask->integer & 4 ) ) { + FS_LoadPackFiles( ".pk2", FS_LoadZipFile ); + } +#endif +} + + +/* +================= +FS_GetModList +================= +*/ +#define MAX_LISTED_MODS 32 + +static void FS_GetModList( char **list, int *count ) { + char path[MAX_OSPATH]; + FILE *fp; + char **dirlist; + int numDirs; + int i; + + if( !( dirlist = Sys_ListFiles( sys_basedir->string, NULL, FS_SEARCHDIRS_ONLY, &numDirs ) ) ) { + return; + } + + for( i = 0; i < numDirs; i++ ) { + if( !strcmp( dirlist[i], BASEGAME ) ) { + continue; + } + +#ifdef _WIN32 + Com_sprintf( path, sizeof( path ), "%s/%s/gamex86.dll", sys_basedir->string, dirlist[i] ); +#else + Com_sprintf( path, sizeof( path ), "%s/%s/gamei386.so", sys_basedir->string, dirlist[i] ); +#endif + + if( !( fp = fopen( path, "rb" ) ) ) { + continue; + } + fclose( fp ); + + Com_sprintf( path, sizeof( path ), "%s/%s/description.txt", sys_basedir->string, dirlist[i] ); + + if( ( fp = fopen( path, "r" ) ) != NULL ) { + Q_strncpyz( path, va( "%s\n", dirlist[i] ), sizeof( path ) - MAX_QPATH ); + fgets( path + strlen( path ), MAX_QPATH, fp ); + fclose( fp ); + list[*count] = FS_CopyString( path ); + } else { + list[*count] = FS_CopyString( dirlist[i] ); + } + + if( (*count)++ == MAX_LISTED_MODS ) { + break; + } + } + + Sys_FreeFileList( dirlist ); +} + +/* +================= +FS_CopyExtraInfo +================= +*/ +char *FS_CopyExtraInfo( const char *name, const fsFileInfo_t *info ) { + char *out; + int length; + + if( !name ) { + return NULL; + } + + length = strlen( name ) + 1; + + out = FS_Malloc( sizeof( *info ) + length ); + strcpy( out, name ); + + memcpy( out + length, info, sizeof( *info ) ); + + return out; +} + +#if 0 +// foo*bar +// foobar +// fooblahbar +static qboolean FS_WildCmp_r( const char *filter, const char *string ) { + while( *filter && *filter != ';' ) { + if( *filter == '*' ) { + return FS_WildCmp_r( filter + 1, string ) || + ( *string && FS_WildCmp_r( filter, string + 1 ) ); + } + if( *filter == '[' ) { + filter++; + + continue; + } + if( *filter != '?' && Q_toupper( *filter ) != Q_toupper( *string ) ) { + return qfalse; + } + filter++; + string++; + } + + return !*string; +} +#endif + +static int FS_WildCmp_r( const char *filter, const char *string ) { + switch( *filter ) { + case '\0': + case ';': + return !*string; + + case '*': + return FS_WildCmp_r( filter + 1, string ) || (*string && FS_WildCmp_r( filter, string + 1 )); + + case '?': + return *string && FS_WildCmp_r( filter + 1, string + 1 ); + + default: + return ((*filter == *string) || (Q_toupper( *filter ) == Q_toupper( *string ))) && FS_WildCmp_r( filter + 1, string + 1 ); + } +} + +qboolean FS_WildCmp( const char *filter, const char *string ) { + do { + if( FS_WildCmp_r( filter, string ) ) { + return qtrue; + } + filter = strchr( filter, ';' ); + if( filter ) filter++; + } while( filter ); + + return qfalse; +} + +qboolean FS_ExtCmp( const char *ext, const char *string ) { + int c1, c2; + const char *s; + +rescan: + s = string; + do { + c1 = *ext++; + c2 = *s++; + + if( c1 == ';' ) { + if( c2 ) { + goto rescan; + } + return qtrue; + } + + c1 = Q_tolower( c1 ); + c2 = Q_tolower( c2 ); + if( c1 != c2 ) { + while( c1 ) { + c1 = *ext++; + if( c1 == ';' ) { + goto rescan; + } + } + return qfalse; + } + } while( c1 ); + + return qtrue; +} + + +/* +================= +FS_ListFiles +================= +*/ +char **FS_ListFiles( const char *path, const char *extension, + uint32 flags, int *numFiles ) +{ + searchpath_t *search; + char *listedFiles[MAX_LISTED_FILES]; + int count, total; + char buffer[MAX_QPATH]; + char **dirlist; + int numFilesInDir; + char **list; + int i, length; + char *name, *filename; + fsFileInfo_t info; + + if( flags & FS_SEARCH_BYFILTER ) { + if( !extension ) { + Com_Error( ERR_FATAL, "FS_ListFiles: NULL filter" ); + } + } + + if( !path ) { + path = ""; + } + + count = 0; + + if( numFiles ) { + *numFiles = 0; + } + + if( !strcmp( path, "$modlist" ) ) { + FS_GetModList( listedFiles, &count ); + } else { + switch( flags & FS_PATH_MASK ) { + case FS_PATH_BASE: + search = fs_base_searchpaths; + break; + default: + search = fs_searchpaths; + break; + } + + memset( &info, 0, sizeof( info ) ); + + for( ; search; search = search->next ) { + if( search->pack ) { + if( ( flags & FS_TYPE_MASK ) == FS_TYPE_REAL ) { + /* don't search in paks */ + continue; + } + + // TODO: add directory search support for pak files + if( ( flags & FS_SEARCHDIRS_MASK ) == FS_SEARCHDIRS_ONLY ) { + continue; + } + if( ( flags & FS_PATH_MASK ) == FS_PATH_GAME ) { + if( fs_searchpaths != fs_base_searchpaths && search == fs_base_searchpaths ) { + /* consider baseq2 a gamedir if no gamedir loaded */ + break; + } + } + if( flags & FS_SEARCH_BYFILTER ) { + for( i = 0; i < search->pack->numfiles; i++ ) { + name = search->pack->files[i].name; + + // check path + filename = name; + if( *path ) { + length = strlen( path ); + if( Q_stricmpn( name, path, length ) ) { + continue; + } + filename += length + 1; + } + + // check filter + if( !FS_WildCmp( extension, filename ) ) { + continue; + } + + // copy filename + if( count == MAX_LISTED_FILES ) { + break; + } + + if( !( flags & FS_SEARCH_SAVEPATH ) ) { + filename = COM_SkipPath( filename ); + } + if( flags & FS_SEARCH_EXTRAINFO ) { + info.fileSize = search->pack->files[i].filelen; + listedFiles[count++] = FS_CopyExtraInfo( filename, &info ); + } else { + listedFiles[count++] = FS_CopyString( filename ); + } + } + } else { + for( i = 0; i < search->pack->numfiles; i++ ) { + name = search->pack->files[i].name; + + // check path + if( *path ) { + COM_FilePath( name, buffer, sizeof( buffer ) ); + if( Q_stricmp( path, buffer ) ) { + continue; + } + } + + // check extension + if( extension && !FS_ExtCmp( extension, COM_FileExtension( name ) ) ) { + continue; + } + + // copy filename + if( count == MAX_LISTED_FILES ) { + break; + } + if( !( flags & FS_SEARCH_SAVEPATH ) ) { + name = COM_SkipPath( name ); + } + if( flags & FS_SEARCH_EXTRAINFO ) { + info.fileSize = search->pack->files[i].filelen; + listedFiles[count++] = FS_CopyExtraInfo( name, &info ); + } else { + listedFiles[count++] = FS_CopyString( name ); + } + } + } + } else { + if( ( flags & FS_TYPE_MASK ) == FS_TYPE_PAK ) { + /* don't search in OS filesystem */ + continue; + } + + Q_strncpyz( buffer, search->filename, sizeof( buffer ) ); + if( *path ) { + Q_strcat( buffer, sizeof( buffer ), "/" ); + Q_strcat( buffer, sizeof( buffer ), path ); + } + + if( flags & FS_SEARCH_BYFILTER ) { + dirlist = Sys_ListFiles( buffer, extension, flags|FS_SEARCH_NOSORT, &numFilesInDir ); + } else { + dirlist = Sys_ListFiles( buffer, extension, flags|FS_SEARCH_NOSORT, &numFilesInDir ); + } + + if( !dirlist ) { + continue; + } + + for( i = 0; i < numFilesInDir; i++ ) { + if( count == MAX_LISTED_FILES ) { + break; + } + name = dirlist[i]; + if( ( flags & FS_SEARCH_SAVEPATH ) && !( flags & FS_SEARCH_BYFILTER ) ) { + // skip search path + name += strlen( search->filename ) + 1; + } + if( flags & FS_SEARCH_EXTRAINFO ) { + listedFiles[count++] = FS_CopyExtraInfo( name, ( fsFileInfo_t * )( dirlist[i] + strlen( dirlist[i] ) + 1 ) ); + } else { + listedFiles[count++] = FS_CopyString( name ); + } + } + Sys_FreeFileList( dirlist ); + + } + if( count == MAX_LISTED_FILES ) { + break; + } + } + } + + if( !count ) { + return NULL; + } + + // sort alphabetically (ignoring FS_SEARCH_NOSORT) + qsort( listedFiles, count, sizeof( listedFiles[0] ), SortStrcmp ); + + // remove duplicates + total = 1; + for( i = 1; i < count; i++ ) { + if( !FS_strcmp( listedFiles[ i - 1 ], listedFiles[i] ) ) { + Z_Free( listedFiles[ i - 1 ] ); + listedFiles[i-1] = NULL; + } else { + total++; + } + } + + list = FS_Malloc( sizeof( char * ) * ( total + 1 ) ); + + total = 0; + for( i = 0; i < count; i++ ) { + if( listedFiles[i] ) { + list[total++] = listedFiles[i]; + } + } + list[total] = NULL; + + if( numFiles ) { + *numFiles = total; + } + + return list; + + +} + +/* +================= +FS_FreeFileList +================= +*/ +void FS_FreeFileList( char **list ) { + char **p; + + if( !list ) { + Com_Error( ERR_FATAL, "FS_FreeFileList: NULL" ); + } + + p = list; + while( *p ) { + Z_Free( *p++ ); + } + + Z_Free( list ); +} + +/* +================= +FS_CopyFile_f + +extract file from *.pak, *.pk2 or *.gz +================= +*/ +static void FS_CopyFile_f( void ) { + if( Cmd_Argc() < 2 ) { + Com_Printf( "Usage: %s \n", Cmd_Argv( 0 ) ); + return; + } + + if( FS_CopyFile( Cmd_Argv( 1 ), Cmd_Argv( 2 ) ) ) { + Com_Printf( "File copied successfully\n" ); + } else { + Com_Printf( "Failed to copy file\n" ); + } +} + +/* +============ +FS_FDir_f +============ +*/ +static void FS_FDir_f( void ) { + char **dirnames; + int ndirs = 0; + int i; + char *filter; + + if( Cmd_Argc() < 2 ) { + Com_Printf( "Usage: %s [fullPath]\n", Cmd_Argv( 0 ) ); + return; + } + + filter = Cmd_Argv( 1 ); + + i = FS_SEARCH_BYFILTER; + if( Cmd_Argc() > 2 ) { + i |= FS_SEARCH_SAVEPATH; + } + + if( ( dirnames = FS_ListFiles( NULL, filter, i, &ndirs ) ) != NULL ) { + for( i = 0; i < ndirs; i++ ) { + Com_Printf( "%s\n", dirnames[i] ); + } + FS_FreeFileList( dirnames ); + } + Com_Printf( "%i files listed\n", ndirs ); + +} + +/* +============ +FS_Dir_f +============ +*/ +static void FS_Dir_f( void ) { + char **dirnames; + int ndirs = 0; + int i; + char *ext; + + if( Cmd_Argc() < 2 ) { + Com_Printf( "Usage: %s [.extension]\n", Cmd_Argv( 0 ) ); + return; + } + if( Cmd_Argc() > 2 ) { + ext = Cmd_Argv( 2 ); + } else { + ext = NULL; + } + dirnames = FS_ListFiles( Cmd_Argv( 1 ), ext, 0, &ndirs ); + if( dirnames ) { + for( i = 0; i < ndirs; i++ ) { + Com_Printf( "%s\n", dirnames[i] ); + } + FS_FreeFileList( dirnames ); + } + Com_Printf( "%i files listed\n", ndirs ); + +} + +/* +============ +FS_WhereIs_f +============ +*/ +static void FS_WhereIs_f( void ) { + searchpath_t *search; + pack_t *pak; + packfile_t *entry; + uint32 hash; + char filename[MAX_QPATH]; + char fullpath[MAX_OSPATH]; + char *path; + fsFileInfo_t info; + int total; + + if( Cmd_Argc() < 2 ) { + Com_Printf( "Usage: %s \n", Cmd_Argv( 0 ) ); + return; + } + + Cmd_ArgvBuffer( 1, filename, sizeof( filename ) ); + Q_strlwr( filename ); + + path = FS_ExpandLinks( filename ); + if( path != filename ) { + Com_Printf( "%s linked to %s\n", filename, path ); + } + + hash = Com_HashPath( path, 0 ); + + total = 0; + for( search = fs_searchpaths; search; search = search->next ) { + // is the element a pak file? + if( search->pack ) { + // look through all the pak file elements + pak = search->pack; + entry = pak->fileHash[ hash & ( pak->hashSize - 1 ) ]; + for( ; entry; entry = entry->hashNext ) { + if( !Q_stricmp( entry->name, path ) ) { + Com_Printf( "%s/%s (%d bytes)\n", pak->filename, + path, entry->filelen ); + total++; + // return; + } + } + } else { + Com_sprintf( fullpath, sizeof( fullpath ), "%s/%s", + search->filename, path ); + FS_ConvertToSysPath( fullpath ); + if( Sys_GetFileInfo( fullpath, &info ) ) { + Com_Printf( "%s/%s (%d bytes)\n", search->filename, filename, + info.fileSize ); + total++; + // return; + } + } + + } + + if( total ) { + Com_Printf( "%d instances of %s\n", total, path ); + } else { + Com_Printf( "%s was not found\n", path ); + } +} + +/* +============ +FS_Path_f +============ +*/ +void FS_Path_f( void ) { + searchpath_t *s; + int numFilesInPAK = 0; +#ifdef USE_ZLIB + int numFilesInPK2 = 0; +#endif + + Com_Printf( "Current search path:\n" ); + for( s = fs_searchpaths; s; s = s->next ) { + if( s->pack ) { +#ifdef USE_ZLIB + if( s->pack->zFile ) { + numFilesInPK2 += s->pack->numfiles; + } else +#endif + { + numFilesInPAK += s->pack->numfiles; + } + } + + if( s->pack ) + Com_Printf( "%s (%i files)\n", s->pack->filename, s->pack->numfiles ); + else + Com_Printf( "%s\n", s->filename ); + } + + if( !( fs_restrict_mask->integer & 2 ) ) { + Com_Printf( "%i files in PAK files\n", numFilesInPAK ); + } + +#ifdef USE_ZLIB + if( !( fs_restrict_mask->integer & 4 ) ) { + Com_Printf( "%i files in PK2 files\n", numFilesInPK2 ); + } +#endif +} + +/* +================ +FS_Stats_f +================ +*/ +static void FS_Stats_f( void ) { + searchpath_t *path; + pack_t *pack, *maxpack = NULL; + packfile_t *file, *max = NULL; + int i; + int len, maxLen = 0; + int totalHashSize, totalLen; + + totalHashSize = totalLen = 0; + for( path = fs_searchpaths; path; path = path->next ) { + if( !( pack = path->pack ) ) { + continue; + } + for( i = 0; i < pack->hashSize; i++ ) { + if( !( file = pack->fileHash[i] ) ) { + continue; + } + len = 0; + for( ; file ; file = file->hashNext ) { + len++; + } + if( maxLen < len ) { + max = pack->fileHash[i]; + maxpack = pack; + maxLen = len; + } + totalLen += len; + totalHashSize++; + } + //totalHashSize += pack->hashSize; + } + + Com_Printf( "LoadFile counter: %d\n", loadCount ); + Com_Printf( "Static LoadFile counter: %d\n", loadCountStatic ); + Com_Printf( "Total calls to OpenFileRead: %d\n", fs_count_read ); + Com_Printf( "Total path comparsions: %d\n", fs_count_strcmp ); + Com_Printf( "Total calls to fopen: %d\n", fs_count_open ); + + if( !totalHashSize ) { + Com_Printf( "No stats to display\n" ); + return; + } + + Com_Printf( "Maximum hash bucket length is %d, average is %.2f\n", maxLen, ( float )totalLen / totalHashSize ); + if( max ) { + Com_Printf( "Dumping longest bucket (%s):\n", maxpack->filename ); + for( file = max; file ; file = file->hashNext ) { + Com_Printf( "%s\n", file->name ); + } + } +} + +static const char *FS_Link_g( const char *partial, int state ) { + static int length; + static fsLink_t *link; + char *name; + + if( !state ) { + length = strlen( partial ); + link = fs_links; + } + + while( link ) { + name = link->name; + link = link->next; + if( !Q_stricmpn( partial, name, length ) ) { + return name; + } + } + + return NULL; +} + +static void FS_UnLink_f( void ) { + fsLink_t *l, *next, **back; + char *name; + + if( Cmd_CheckParam( "h", "help" ) ) { +usage: + Com_Printf( "Usage: %s \n" + "Deletes the specified symbolic link.\n", + Cmd_Argv( 0 ) ); + return; + } + + if( Cmd_CheckParam( "a", "all" ) ) { + for( l = fs_links; l; l = next ) { + next = l->next; + Z_Free( l->target ); + Z_Free( l ); + } + fs_links = NULL; + return; + } + + if( Cmd_Argc() != 2 ) { + goto usage; + } + + name = Cmd_Argv( 1 ); + for( l = fs_links, back = &fs_links; l; l = l->next ) { + if( !Q_stricmp( l->name, name ) ) { + break; + } + back = &l->next; + } + + if( !l ) { + Com_Printf( "Symbolic link '%s' does not exist.\n", name ); + return; + } + + *back = l->next; + Z_Free( l->target ); + Z_Free( l ); +} + +static void FS_Link_f( void ) { + int argc, length, count; + fsLink_t *l; + char *name, *target; + + argc = Cmd_Argc(); + if( argc == 1 ) { + for( l = fs_links, count = 0; l; l = l->next, count++ ) { + Com_Printf( "%s --> %s\n", l->name, l->target ); + } + Com_Printf( "------------------\n" + "%d symbolic links listed.\n", count ); + return; + } + if( Cmd_CheckParam( "h", "help" ) || argc != 3 ) { + Com_Printf( "Usage: %s \n" + "Creates symbolic link to target with the specified name.\n" + "Virtual quake paths are accepted.\n" + "Links are effective only for reading.\n", + Cmd_Argv( 0 ) ); + return; + } + + name = Cmd_Argv( 1 ); + for( l = fs_links; l; l = l->next ) { + if( !Q_stricmp( l->name, name ) ) { + break; + } + } + + if( !l ) { + length = strlen( name ); + l = FS_Malloc( sizeof( *l ) + length ); + strcpy( l->name, name ); + l->nameLength = length; + l->next = fs_links; + fs_links = l; + } else { + Z_Free( l->target ); + } + + target = Cmd_Argv( 2 ); + l->target = FS_CopyString( target ); + l->targetLength = strlen( target ); +} + +/* +================ +FS_NextPath + +Allows enumerating all of the directories in the search path +================ +*/ +char *FS_NextPath( char *prevpath ) { + searchpath_t *s; + char *prev; + + prev = NULL; + for( s = fs_searchpaths; s; s = s->next ) { + if( s->pack ) + continue; + if( prevpath == prev ) + return s->filename; + prev = s->filename; + } + + return NULL; +} + + + +/* +============ +FS_Gamedir + +Called to find where to write a file (demos, savegames, etc) +============ +*/ +char *FS_Gamedir( void ) { + return fs_gamedir; +} + +static void FS_FreeSearchPath( searchpath_t *path ) { + pack_t *pak; + + if( ( pak = path->pack ) != NULL ) { +#ifdef USE_ZLIB + if( pak->zFile ) { + unzClose( pak->zFile ); + } else +#endif + { + fclose( pak->fp ); + } + Z_Free( pak ); + } + + Z_Free( path ); +} + +/* +================ +FS_Shutdown +================ +*/ +void FS_Shutdown( qboolean total ) { + searchpath_t *path, *next; + fsLink_t *l, *nextLink; + fsFile_t *file; + int i; + + if( !fs_searchpaths ) { + return; + } + + if( total ) { + /* close file handles */ + for( i = 0, file = fs_files; i < MAX_FILE_HANDLES; i++, file++ ) { + if( file->type != FS_FREE ) { + Com_WPrintf( "FS_Shutdown: closing handle %i: '%s'\n", + i + 1, file->name ); + FS_FCloseFile( i + 1 ); + } + } + + /* free symbolic links */ + for( l = fs_links; l; l = nextLink ) { + nextLink = l->next; + Z_Free( l->target ); + Z_Free( l ); + } + + fs_links = NULL; + } + + /* free search paths */ + for( path = fs_searchpaths; path; path = next ) { + next = path->next; + FS_FreeSearchPath( path ); + } + + fs_searchpaths = NULL; + + FS_FlushCache(); + + if( total ) { + Z_LeakTest( TAG_FILESYSTEM ); + } + + Cmd_RemoveCommand( "path" ); + Cmd_RemoveCommand( "fdir" ); + Cmd_RemoveCommand( "dir" ); + Cmd_RemoveCommand( "copyfile" ); + Cmd_RemoveCommand( "fs_stats" ); + Cmd_RemoveCommand( "link" ); + Cmd_RemoveCommand( "unlink" ); +} + +/* +================ +FS_DefaultGamedir +================ +*/ +static void FS_DefaultGamedir( void ) { + if( sys_homedir->string[0] ) { + FS_AddGameDirectory( "%s/"BASEGAME, sys_homedir->string ); + } else { + // already added as basedir + Com_sprintf( fs_gamedir, sizeof( fs_gamedir ), "%s/"BASEGAME, + sys_basedir->string ); + } + + Cvar_Set( "game", "" ); + Cvar_Set( "gamedir", "" ); +} + + +/* +================ +FS_SetupGamedir + +Sets the gamedir and path to a different directory. +================ +*/ +static void FS_SetupGamedir( void ) { + fs_game = Cvar_Get( "game", "", CVAR_LATCHED|CVAR_SERVERINFO ); + fs_game->subsystem = CVAR_SYSTEM_FILES; + + if( !fs_game->string[0] || !FS_strcmp( fs_game->string, BASEGAME ) ) { + FS_DefaultGamedir(); + return; + } + + if( !FS_ValidatePath( fs_game->string ) || + strchr( fs_game->string, '/' ) || + strchr( fs_game->string, '\\' ) ) + { + Com_WPrintf( "Gamedir should be a single filename, not a path.\n" ); + FS_DefaultGamedir(); + return; + } + + // this one is left for compatibility with server browsers, etc + Cvar_FullSet( "gamedir", fs_game->string, CVAR_ROM|CVAR_SERVERINFO, + CVAR_SET_DIRECT ); + + FS_AddGameDirectory( "%s/%s", sys_basedir->string, fs_game->string ); + + // home paths override system paths + if( sys_homedir->string[0] ) { + FS_AddGameDirectory( "%s/"BASEGAME, sys_homedir->string ); + FS_AddGameDirectory( "%s/%s", sys_homedir->string, fs_game->string ); + } +} + +qboolean FS_SafeToRestart( void ) { + fsFile_t *file; + int i; + + /* make sure no files are opened for reading */ + for( i = 0, file = fs_files; i < MAX_FILE_HANDLES; i++, file++ ) { + if( file->type == FS_FREE ) { + continue; + } + if( file->mode == FS_MODE_READ ) { + return qfalse; + } + } + + return qtrue; +} + +/* +================ +FS_Restart +================ +*/ +void FS_Restart( void ) { + fsFile_t *file; + int i; + fileHandle_t temp; + searchpath_t *path, *next; + + Com_Printf( "---------- FS_Restart ----------\n" ); + + /* temporary disable logfile */ + temp = com_logFile; + com_logFile = 0; + + /* make sure no files are opened for reading */ + for( i = 0, file = fs_files; i < MAX_FILE_HANDLES; i++, file++ ) { + if( file->type == FS_FREE ) { + continue; + } + if( file->mode == FS_MODE_READ ) { + Com_Error( ERR_FATAL, "FS_Restart: closing handle %i: %s", + i + 1, file->name ); + } + } + + if( fs_restrict_mask->latched_string ) { + /* perform full reset */ + FS_Shutdown( qfalse ); + FS_Init(); + } else { + /* just change gamedir */ + for( path = fs_searchpaths; path != fs_base_searchpaths; path = next ) { + next = path->next; + FS_FreeSearchPath( path ); + } + + fs_searchpaths = fs_base_searchpaths; + + FS_SetupGamedir(); + FS_Path_f(); + } + + /* re-enable logfile */ + com_logFile = temp; + + Com_Printf( "--------------------------------\n" ); +} + +/* +================ +FS_FillAPI +================ +*/ +void FS_FillAPI( fsAPI_t *api ) { + api->LoadFile = FS_LoadFile; + api->LoadFileEx = FS_LoadFileEx; + api->AllocTempMem = FS_AllocTempMem; + api->FreeFile = FS_FreeFile; + api->FOpenFile = FS_FOpenFile; + api->FCloseFile = FS_FCloseFile; + api->Tell = FS_Tell; + api->RawTell = FS_RawTell; + api->Read = FS_Read; + api->Write = FS_Write; + api->ListFiles = FS_ListFiles; + api->FreeFileList = FS_FreeFileList; +} + +static const cmdreg_t c_fs[] = { + { "path", FS_Path_f }, + { "fdir", FS_FDir_f }, + { "dir", FS_Dir_f }, + { "copyfile", FS_CopyFile_f }, + { "fs_stats", FS_Stats_f }, + { "whereis", FS_WhereIs_f }, + { "link", FS_Link_f, FS_Link_g }, + { "unlink", FS_UnLink_f, FS_Link_g }, + + { NULL } +}; + +/* +================ +FS_Init +================ +*/ +void FS_Init( void ) { + int startTime; + + startTime = Sys_Milliseconds(); + + Com_Printf( "---------- FS_Init ----------\n" ); + + Cmd_Register( c_fs ); + + fs_debug = Cvar_Get( "fs_debug", "0", 0 ); + fs_restrict_mask = Cvar_Get( "fs_restrict_mask", "4", CVAR_NOSET ); + fs_restrict_mask->subsystem = CVAR_SYSTEM_FILES; + + if( ( fs_restrict_mask->integer & 7 ) == 7 ) { + Com_WPrintf( "Invalid fs_restrict_mask value %d. " + "Falling back to default.\n", + fs_restrict_mask->integer ); + Cvar_SetInteger( "fs_restrict_mask", 4 ); + } + + // start up with baseq2 by default + FS_AddGameDirectory( "%s/"BASEGAME, sys_basedir->string ); + + fs_base_searchpaths = fs_searchpaths; + + // check for game override + FS_SetupGamedir(); + + FS_Path_f(); + + FS_FillAPI( &fs ); + + Com_DPrintf( "%i msec to init filesystem\n", + Sys_Milliseconds() - startTime ); + Com_Printf( "-----------------------------\n" ); +} + +/* +================ +FS_NeedRestart +================ +*/ +qboolean FS_NeedRestart( void ) { + if( fs_game->latched_string || fs_restrict_mask->latched_string ) { + return qtrue; + } + + return qfalse; +} + + +// TODO: remove it +int Developer_searchpath( int who ) { + if( !strcmp( fs_game->string, "xatrix" ) ) { + return 1; + } + + if( !strcmp( fs_game->string, "rogue" ) ) { + return 1; + } + + return 0; + + +} + + + + + -- cgit v1.2.3