diff options
author | Andrey Nazarov <skuller@skuller.net> | 2010-09-15 22:41:27 +0400 |
---|---|---|
committer | Andrey Nazarov <skuller@skuller.net> | 2010-09-15 22:41:27 +0400 |
commit | cd8d25aa0b96b48e1a6d0edf9893afe9cbf796c1 (patch) | |
tree | 322d2ed47d1ab4c7afdd5e70f34829f252877872 /source/files.c | |
parent | de41ad148d857184ead919fa488fd58cec5b1864 (diff) |
Renamed source tree subdirectory into ‘src’, moved ‘asm’ subdirectory there and renamed it into ‘i386’.
Diffstat (limited to 'source/files.c')
-rw-r--r-- | source/files.c | 3051 |
1 files changed, 0 insertions, 3051 deletions
diff --git a/source/files.c b/source/files.c deleted file mode 100644 index 36aeda1..0000000 --- a/source/files.c +++ /dev/null @@ -1,3051 +0,0 @@ -/* -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" -#include "files.h" -#include "sys_public.h" -#include "cl_public.h" -#include "d_pak.h" -#if USE_ZLIB -#include <zlib.h> -#endif -#ifndef _GNU_SOURCE -#include <sys/types.h> -#include <sys/stat.h> -#include <fcntl.h> -#endif - -/* -============================================================================= - -QUAKE FILESYSTEM - -- transparently merged from several sources -- relative to the single virtual root -- case insensitive at pakfiles level, - but may be case sensitive at host OS level -- uses / as path separators internally - -============================================================================= -*/ - -#define MAX_FILE_HANDLES 8 - -// macros for dealing portably with files at OS level -#ifdef _WIN32 -#define FS_strcmp Q_strcasecmp -#define FS_strncmp Q_strncasecmp -#else -#define FS_strcmp strcmp -#define FS_strncmp strncmp -#endif - -#define MAX_READ 0x40000 // read in blocks of 256k -#define MAX_WRITE 0x40000 // write in blocks of 256k - -#if USE_ZLIB -#define ZIP_MAXFILES 0x4000 // 16k files, rather arbitrary -#define ZIP_BUFSIZE 0x10000 // inflate in blocks of 64k - -#define ZIP_BUFREADCOMMENT 1024 -#define ZIP_SIZELOCALHEADER 30 -#define ZIP_SIZECENTRALHEADER 20 -#define ZIP_SIZECENTRALDIRITEM 46 - -#define ZIP_LOCALHEADERMAGIC 0x04034b50 -#define ZIP_CENTRALHEADERMAGIC 0x02014b50 -#define ZIP_ENDHEADERMAGIC 0x06054b50 -#endif - -#ifdef _DEBUG -#define FS_DPrintf(...) \ - if( fs_debug && fs_debug->integer ) \ - Com_LPrintf( PRINT_DEVELOPER, __VA_ARGS__ ) -#else -#define FS_DPrintf(...) -#endif - -// -// in memory -// - -typedef enum { - FS_FREE, - FS_REAL, - FS_PAK, -#if USE_ZLIB - FS_ZIP, - FS_GZ, -#endif - FS_BAD -} filetype_t; - -#if USE_ZLIB -typedef struct { - z_stream stream; - size_t rest_in; - size_t rest_out; - qerror_t error; - byte buffer[ZIP_BUFSIZE]; -} zipstream_t; -#endif - -typedef struct packfile_s { - char *name; - size_t filepos; - size_t filelen; -#if USE_ZLIB - size_t complen; - unsigned compmtd; // compression method, 0 (stored) or Z_DEFLATED - qboolean coherent; // true if local file header has been checked -#endif - - struct packfile_s *hash_next; -} packfile_t; - -typedef struct { - filetype_t type; // FS_PAK or FS_ZIP - unsigned refcount; // for tracking pack users - FILE *fp; - unsigned num_files; - packfile_t *files; - packfile_t **file_hash; - unsigned hash_size; - char *names; - char *filename; -} pack_t; - -typedef struct searchpath_s { - struct searchpath_s *next; - unsigned mode; - pack_t *pack; // only one of filename / pack will be used - char filename[1]; -} searchpath_t; - -typedef struct { - filetype_t type; - unsigned mode; - FILE *fp; -#if USE_ZLIB - void *zfp; // gzFile for FS_GZ or zipstream_t for FS_ZIP -#endif - packfile_t *entry; // pack entry this handle is tied to - pack_t *pack; // points to the pack entry is from - qboolean unique; - size_t length; -} file_t; - -typedef struct symlink_s { - struct symlink_s *next; - size_t targlen; - size_t namelen; - char *target; - char name[1]; -} symlink_t; - -// these point to user home directory -char fs_gamedir[MAX_OSPATH]; -//static char fs_basedir[MAX_OSPATH]; - -static searchpath_t *fs_searchpaths; -static searchpath_t *fs_base_searchpaths; - -static symlink_t *fs_links; - -static file_t fs_files[MAX_FILE_HANDLES]; - -#ifdef _DEBUG -static int fs_count_read, fs_count_strcmp, fs_count_open; -#endif - -#ifdef _DEBUG -static cvar_t *fs_debug; -#endif - -cvar_t *fs_game; - -#if USE_ZLIB -// local stream used for all file loads -static zipstream_t fs_zipstream; - -static void open_zip_file( file_t *file ); -static void close_zip_file( file_t *file ); -static ssize_t tell_zip_file( file_t *file ); -static ssize_t read_zip_file( file_t *file, void *buf, size_t len ); -#endif - -// for tracking users of pack_t instance -// allows FS to be restarted while reading something from pack -static pack_t *pack_get( pack_t *pack ); -static void pack_put( pack_t *pack ); - -/* - -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 all game directories. -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. - -*/ - -/* -================ -FS_pathcmp - -Portably compares quake paths -================ -*/ -int FS_pathcmp( const char *s1, const char *s2 ) { - int c1, c2; - - do { - c1 = *s1++; - c2 = *s2++; - - if( c1 != c2 ) { - c1 = c1 == '\\' ? '/' : Q_tolower( c1 ); - c2 = c2 == '\\' ? '/' : Q_tolower( c2 ); - if( c1 < c2 ) - return -1; - if( c1 > c2 ) - return 1; /* strings not equal */ - } - } while( c1 ); - - return 0; /* strings are equal */ -} - -int FS_pathcmpn( const char *s1, const char *s2, size_t n ) { - int c1, c2; - - do { - c1 = *s1++; - c2 = *s2++; - - if( !n-- ) - return 0; /* strings are equal until end point */ - - if( c1 != c2 ) { - c1 = c1 == '\\' ? '/' : Q_tolower( c1 ); - c2 = c2 == '\\' ? '/' : Q_tolower( c2 ); - if( c1 < c2 ) - return -1; - if( c1 > c2 ) - return 1; /* strings not equal */ - } - } while( c1 ); - - return 0; /* strings are equal */ -} - -#ifdef _WIN32 -/* -================ -FS_ReplaceSeparators -================ -*/ -char *FS_ReplaceSeparators( char *s, int separator ) { - char *p; - - p = s; - while( *p ) { - if( *p == '/' || *p == '\\' ) { - *p = separator; - } - p++; - } - - return s; -} -#endif - -// ============================================================================= - -static file_t *alloc_handle( qhandle_t *f ) { - file_t *file; - int i; - - for( i = 0, file = fs_files; i < MAX_FILE_HANDLES; i++, file++ ) { - if( file->type == FS_FREE ) { - *f = i + 1; - return file; - } - } - - return NULL; -} - -static file_t *file_for_handle( qhandle_t f ) { - file_t *file; - - if( f <= 0 || f >= MAX_FILE_HANDLES + 1 ) { - Com_Error( ERR_FATAL, "%s: bad handle", __func__ ); - } - - file = &fs_files[f - 1]; - if( file->type <= FS_FREE || file->type >= FS_BAD ) { - Com_Error( ERR_FATAL, "%s: bad file type", __func__ ); - } - - return file; -} - -static qerror_t validate_path( const char *s ) { - const char *start; - - // check for leading slash - // check for empty path - if( *s == '/' || *s == '\\' /*|| *s == 0*/ ) { - return Q_ERR_INVALID_PATH; - } - - start = s; - while( *s ) { - // check for high bit - if( *s & 128 ) { - return Q_ERR_UNCLEAN_PATH; - } - // check for ".." - if( *s == '.' && s[1] == '.' ) { - return Q_ERR_INVALID_PATH; - } - if( *s == '/' || *s == '\\' ) { - // check for two slashes in a row - // check for trailing slash - if( ( s[1] == '/' || s[1] == '\\' || s[1] == 0 ) ) { - return Q_ERR_INVALID_PATH; - } - } -#ifdef _WIN32 - if( *s == ':' ) { - // check for "X:\" - if( s[1] == '\\' || s[1] == '/' ) { - return Q_ERR_INVALID_PATH; - } - } -#endif - s++; - } - - // check length - if( s - start > MAX_OSPATH ) { - return Q_ERR_NAMETOOLONG; - } - - return Q_ERR_SUCCESS; -} - -/* -================ -FS_GetFileLength - -Returns: -- current length for files opened for writing. -- cached length for files opened for reading. -- error for gzip-compressed files. -================ -*/ -ssize_t FS_GetFileLength( qhandle_t f ) { - file_t *file = file_for_handle( f ); - file_info_t info; - qerror_t ret; - - switch( file->type ) { - case FS_REAL: - ret = Sys_GetFileInfo( file->fp, &info ); - if( ret ) { - return ret; - } - return info.size; - case FS_PAK: -#if USE_ZLIB - case FS_ZIP: -#endif - return file->length; - default: - return Q_ERR_NOSYS; - } -} - -/* -============ -FS_Tell -============ -*/ -ssize_t FS_Tell( qhandle_t f ) { - file_t *file = file_for_handle( f ); - long ret; - - switch( file->type ) { - case FS_REAL: - ret = ftell( file->fp ); - if( ret == -1 ) { - return Q_ERR(errno); - } - return ret; - case FS_PAK: - ret = ftell( file->fp ); - if( ret == -1 ) { - return Q_ERR(errno); - } - if( ret < file->entry->filepos || - ret > file->entry->filepos + - file->entry->filelen ) - { - return Q_ERR_SPIPE; - } - return ret; -#if USE_ZLIB - case FS_ZIP: - return tell_zip_file( file ); -#endif - default: - return Q_ERR_NOSYS; - } -} - -/* -============ -FS_Seek -============ -*/ -qerror_t FS_Seek( qhandle_t f, size_t offset ) { - file_t *file = file_for_handle( f ); - - if( offset > LONG_MAX ) { - return Q_ERR_INVAL; - } - - switch( file->type ) { - case FS_REAL: - //case FS_PAK: - if( fseek( file->fp, (long)offset, SEEK_CUR ) == -1 ) { - return Q_ERR(errno); - } - return Q_ERR_SUCCESS; -#if USE_ZLIB - case FS_GZ: - if( gzseek( file->zfp, (long)offset, SEEK_CUR ) == -1 ) { - return Q_ERR(errno); - } - return Q_ERR_SUCCESS; -#endif - default: - return Q_ERR_NOSYS; - } -} - - -/* -============ -FS_CreatePath - -Creates any directories needed to store the given filename. -Expects a fully qualified quake path (i.e. with / separators). -============ -*/ -qerror_t FS_CreatePath( char *path ) { - char *ofs; - int ret; - - if( !*path ) { - return Q_ERR_INVAL; - } - - for( ofs = path + 1; *ofs; ofs++ ) { - if( *ofs == '/' ) { - // create the directory - *ofs = 0; - ret = Q_mkdir( path ); - *ofs = '/'; - if( ret == -1 && errno != EEXIST ) { - return Q_ERR(errno); - } - } - } - - return Q_ERR_SUCCESS; -} - -/* -============ -FS_FilterFile - -Turns FS_REAL file into FS_GZIP by reopening it through GZIP. -File position is reset to the beginning of file. -============ -*/ -qerror_t FS_FilterFile( qhandle_t f ) { -#if USE_ZLIB - file_t *file = file_for_handle( f ); - unsigned mode; - char *modeStr; - void *zfp; - - switch( file->type ) { - case FS_GZ: - return Q_ERR_SUCCESS; - case FS_REAL: - break; - default: - return Q_ERR_NOSYS; - } - - mode = file->mode & FS_MODE_MASK; - switch( mode ) { - case FS_MODE_READ: - modeStr = "rb"; - break; - case FS_MODE_WRITE: - modeStr = "wb"; - break; - default: - return qfalse; - } - - if( fseek( file->fp, 0, SEEK_SET ) == -1 ) { - return Q_ERR(errno); - } - - zfp = gzdopen( fileno( file->fp ), modeStr ); - if( !zfp ) { - return Q_ERR_FAILURE; - } - - file->zfp = zfp; - file->type = FS_GZ; - return Q_ERR_SUCCESS; -#else - return Q_ERR_NOSYS; -#endif -} - - -/* -============== -FS_FCloseFile -============== -*/ -void FS_FCloseFile( qhandle_t f ) { - file_t *file = file_for_handle( f ); - - FS_DPrintf( "%s: %u\n", __func__, f ); - - switch( file->type ) { - case FS_REAL: - fclose( file->fp ); - break; - case FS_PAK: - if( file->unique ) { - fclose( file->fp ); - pack_put( file->pack ); - } - break; -#if USE_ZLIB - case FS_GZ: - gzclose( file->zfp ); - break; - case FS_ZIP: - if( file->unique ) { - close_zip_file( file ); - pack_put( file->pack ); - } - break; -#endif - default: - break; - } - - memset( file, 0, sizeof( *file ) ); -} - -static inline FILE *fopen_hack( const char *path, const char *mode ) { -#ifndef _GNU_SOURCE - if( !strcmp( mode, "wxb" ) ) { -#ifdef _WIN32 - int fd = _open( path, _O_WRONLY | _O_CREAT | _O_EXCL | _O_BINARY, - _S_IREAD | _S_IWRITE ); - if( fd == -1 ) { - return NULL; - } - return _fdopen( fd, "wb" ); -#else - int fd = open( path, O_WRONLY | O_CREAT | O_EXCL, - S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH ); - if( fd == -1 ) { - return NULL; - } - return fdopen( fd, "wb" ); -#endif - } -#endif // _GNU_SOURCE - - return fopen( path, mode ); -} - -static ssize_t open_file_write( file_t *file, const char *name ) { - char fullpath[MAX_OSPATH]; - FILE *fp; - char *modeStr; - unsigned mode; - size_t len; - long pos; - qerror_t ret; - - ret = validate_path( name ); - if( ret ) { - return ret; - } - - if( ( file->mode & FS_PATH_MASK ) == FS_PATH_BASE ) { - if( sys_homedir->string[0] ) { - len = Q_concat( fullpath, sizeof( fullpath ), - sys_homedir->string, "/" BASEGAME "/", name, NULL ); - } else { - len = Q_concat( fullpath, sizeof( fullpath ), - sys_basedir->string, "/" BASEGAME "/", name, NULL ); - } - } else { - len = Q_concat( fullpath, sizeof( fullpath ), - fs_gamedir, "/", name, NULL ); - } - if( len >= sizeof( fullpath ) ) { - return Q_ERR_NAMETOOLONG; - } - - mode = file->mode & FS_MODE_MASK; - switch( mode ) { - case FS_MODE_APPEND: - modeStr = "ab"; - break; - case FS_MODE_WRITE: - if( file->mode & FS_FLAG_EXCL ) { - modeStr = "wxb"; - } else { - modeStr = "wb"; - } - break; - case FS_MODE_RDWR: - // this mode is only used by client downloading code - // similar to FS_MODE_APPEND, but does not create - // the file if it does not exist - modeStr = "r+b"; - break; - default: - return Q_ERR_INVAL; - } - - ret = FS_CreatePath( fullpath ); - if( ret ) { - return ret; - } - - fp = fopen_hack( fullpath, modeStr ); - if( !fp ) { - return Q_ERR(errno); - } - -#ifdef __unix__ - // check if this is a regular file - ret = Sys_GetFileInfo( fp, NULL ); - if( ret ) { - goto fail2; - } -#endif - - if( mode == FS_MODE_RDWR ) { - // seek to the end of file for appending - if( fseek( fp, 0, SEEK_END ) == -1 ) { - goto fail1; - } - } - - // return current position (non-zero for appending modes) - pos = ftell( fp ); - if( pos == -1 ) { - goto fail1; - } - - FS_DPrintf( "%s: %s: succeeded\n", __func__, fullpath ); - - file->fp = fp; - file->type = FS_REAL; - file->length = 0; - file->unique = qtrue; - - return pos; - -fail1: - ret = Q_ERR(errno); -#ifdef __unix__ -fail2: -#endif - fclose( fp ); - return ret; -} - -// functions that report errors for partial reads/writes -static inline ssize_t read_block( void *buf, size_t size, FILE *fp ) { - size_t result = fread( buf, 1, size, fp ); - return result == size ? result : ferror(fp) ? Q_ERR(errno) : result; -} - -static inline ssize_t write_block( void *buf, size_t size, FILE *fp ) { - size_t result = fwrite( buf, 1, size, fp ); - return result == size ? result : ferror(fp) ? Q_ERR(errno) : result; -} - -#if USE_ZLIB - -static qerror_t check_header_coherency( FILE *fp, packfile_t *entry ) { - unsigned flags, comp_mtd; - size_t comp_len, file_len; - size_t name_size, xtra_size; - byte header[ZIP_SIZELOCALHEADER]; - size_t ofs; - - if( fseek( fp, (long)entry->filepos, SEEK_SET ) == -1 ) - return Q_ERR(errno); - if( fread( header, 1, sizeof( header ), fp ) != sizeof( header ) ) - return ferror( fp ) ? Q_ERR(errno) : Q_ERR_UNEXPECTED_EOF; - - // check the magic - if( LittleLongMem( &header[0] ) != ZIP_LOCALHEADERMAGIC ) - return Q_ERR_NOT_COHERENT; - - flags = LittleShortMem( &header[6] ); - comp_mtd = LittleShortMem( &header[8] ); - comp_len = LittleLongMem( &header[18] ); - file_len = LittleLongMem( &header[22] ); - name_size = LittleShortMem( &header[26] ); - xtra_size = LittleShortMem( &header[28] ); - - if( comp_mtd != entry->compmtd ) - return Q_ERR_NOT_COHERENT; - - // bit 3 tells that file lengths were not known - // at the time local header was written, so don't check them - if( ( flags & 8 ) == 0 ) { - if( comp_len != entry->complen ) - return Q_ERR_NOT_COHERENT; - if( file_len != entry->filelen ) - return Q_ERR_NOT_COHERENT; - } - - ofs = ZIP_SIZELOCALHEADER + name_size + xtra_size; - if( entry->filepos > LONG_MAX - ofs ) { - return Q_ERR_SPIPE; - } - - entry->filepos += ofs; - entry->coherent = qtrue; - return Q_ERR_SUCCESS; -} - -static voidpf FS_zalloc OF(( voidpf opaque, uInt items, uInt size )) { - return FS_Malloc( items * size ); -} - -static void FS_zfree OF(( voidpf opaque, voidpf address )) { - Z_Free( address ); -} - -static void open_zip_file( file_t *file ) { - packfile_t *entry = file->entry; - zipstream_t *s; - - if( file->unique ) { - s = FS_Malloc( sizeof( *s ) ); - memset( &s->stream, 0, sizeof( s->stream ) ); - } else { - s = &fs_zipstream; - } - - if( entry->compmtd ) { - z_streamp z = &s->stream; - - if( z->state ) { - // already initialized, just reset - inflateReset( z ); - } else { - z->zalloc = FS_zalloc; - z->zfree = FS_zfree; - if( inflateInit2( z, -MAX_WBITS ) != Z_OK ) { - Com_Error( ERR_FATAL, "%s: inflateInit2() failed", __func__ ); - } - } - - z->avail_in = z->avail_out = 0; - z->total_in = z->total_out = 0; - z->next_in = z->next_out = NULL; - } - - s->rest_in = entry->complen; - s->rest_out = entry->filelen; - s->error = Q_ERR_SUCCESS; - - file->zfp = s; -} - -// only called for unique handles -static void close_zip_file( file_t *file ) { - zipstream_t *s = file->zfp; - - inflateEnd( &s->stream ); - Z_Free( s ); - - fclose( file->fp ); -} - -static ssize_t tell_zip_file( file_t *file ) { - zipstream_t *s = file->zfp; - - if( !file->entry->compmtd ) { - return file->entry->filelen - s->rest_in; - } - return s->stream.total_out; -} - -static ssize_t read_zip_file( file_t *file, void *buf, size_t len ) { - zipstream_t *s = file->zfp; - z_streamp z = &s->stream; - size_t block; - ssize_t result; - int ret; - - // can't continue after error - if( s->error ) { - return s->error; - } - - if( len > s->rest_out ) { - len = s->rest_out; - } - - if( !file->entry->compmtd ) { - if( len > s->rest_in ) { - len = s->rest_in; - } - if( !len ) { - return 0; - } - - result = read_block( buf, len, file->fp ); - if( result <= 0 ) { - s->error = result ? result : Q_ERR_UNEXPECTED_EOF; - return s->error; - } - - s->rest_in -= result; - s->rest_out -= result; - return result; - } - - z->next_out = buf; - z->avail_out = (uInt)len; - - while( z->avail_out ) { - if( !z->avail_in ) { - if( !s->rest_in ) { - break; - } - - // fill in the temp buffer - block = ZIP_BUFSIZE; - if( block > s->rest_in ) { - block = s->rest_in; - } - - result = read_block( s->buffer, block, file->fp ); - if( result <= 0 ) { - s->error = result ? result : Q_ERR_UNEXPECTED_EOF; - return s->error; - } - - s->rest_in -= result; - z->next_in = s->buffer; - z->avail_in = result; - } - //if(z->total_out>1024*128)return Q_ERR(EIO); - - ret = inflate( z, Z_SYNC_FLUSH ); - if( ret == Z_STREAM_END ) { - break; - } - if( ret != Z_OK ) { - s->error = Q_ERR_INFLATE_FAILED; - //Com_Printf("%s\n",z->msg ); - break; - } - } - - len -= z->avail_out; - s->rest_out -= len; - - if( s->error && len == 0 ) { - return s->error; - } - - return len; -} - -#endif - -// open a new file on the pakfile -static ssize_t open_from_pak( file_t *file, pack_t *pack, packfile_t *entry, qboolean unique ) { - FILE *fp; - int ret; - - if( unique ) { - fp = fopen( pack->filename, "rb" ); - if( !fp ) { - return Q_ERR(errno); - } - } else { - fp = pack->fp; - clearerr( fp ); - } - -#if USE_ZLIB - if( pack->type == FS_ZIP && !entry->coherent ) { - ret = check_header_coherency( fp, entry ); - if( ret ) { - goto fail; - } - } -#endif - - if( fseek( fp, (long)entry->filepos, SEEK_SET ) == -1 ) { - ret = Q_ERR(errno); - goto fail; - } - - file->fp = fp; - file->type = pack->type; - file->entry = entry; - file->pack = pack; - file->length = entry->filelen; - file->unique = unique; - -#if USE_ZLIB - if( pack->type == FS_ZIP ) { - open_zip_file( file ); - } -#endif - - if( unique ) { - // reference source pak - pack_get( pack ); - } - - FS_DPrintf( "%s: %s/%s: succeeded\n", - __func__, pack->filename, entry->name ); - - return entry->filelen; - -fail: - if( unique ) { - fclose( fp ); - } - return ret; -} - -// Finds the file in the search path. -// Fills file_t and returns file length. -// Used for streaming data out of either a pak file or a seperate file. -static ssize_t open_file_read( file_t *file, const char *name, qboolean unique ) { - char fullpath[MAX_OSPATH]; - searchpath_t *search; - pack_t *pak; - unsigned hash; - packfile_t *entry; - FILE *fp; - file_info_t info; - int ret = Q_ERR_SUCCESS, valid = -1; - size_t len; - -#ifdef _DEBUG - fs_count_read++; -#endif - -// -// search through the path, one element at a time -// - hash = Com_HashPath( name, 0 ); - - for( search = fs_searchpaths; search; search = search->next ) { - if( file->mode & FS_PATH_MASK ) { - if( ( file->mode & search->mode & FS_PATH_MASK ) == 0 ) { - continue; - } - } - - // 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->file_hash[ hash & ( pak->hash_size - 1 ) ]; - for( ; entry; entry = entry->hash_next ) { -#ifdef _DEBUG - fs_count_strcmp++; -#endif - if( !FS_pathcmp( entry->name, name ) ) { - // found it! - return open_from_pak( file, pak, entry, unique ); - } - } - } else { - if( ( file->mode & FS_TYPE_MASK ) == FS_TYPE_PAK ) { - continue; - } - if( valid == -1 ) { - ret = validate_path( name ); - if( ret ) { - valid = 0; - } - } - if( valid == 0 ) { - continue; - } - // check a file in the directory tree - len = Q_concat( fullpath, sizeof( fullpath ), - search->filename, "/", name, NULL ); - if( len >= sizeof( fullpath ) ) { - return Q_ERR_NAMETOOLONG; - } - -#ifdef _DEBUG - fs_count_open++; -#endif - fp = fopen( fullpath, "rb" ); - if( !fp ) { - if( errno == ENOENT ) { - continue; - } - return Q_ERR(errno); - } - - ret = Sys_GetFileInfo( fp, &info ); - if( ret ) { - fclose( fp ); - return ret; - } - - file->fp = fp; - file->type = FS_REAL; - file->unique = qtrue; - file->length = info.size; - - FS_DPrintf( "%s: %s: succeeded\n", __func__, fullpath ); - - return info.size; - } - } - - FS_DPrintf( "%s: %s: failed\n", __func__, name ); - - return ret == Q_ERR_SUCCESS ? Q_ERR_NOENT : ret; -} - -/* -================= -FS_ReadFile - -Properly handles partial reads -================= -*/ -ssize_t FS_Read( void *buffer, size_t len, qhandle_t f ) { - size_t block, remaining = len; - ssize_t read = 0; - byte *buf = (byte *)buffer; - file_t *file = file_for_handle( f ); - - if( len > SSIZE_MAX ) { - return Q_ERR_INVAL; - } - - // 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 = read_block( buf, block, file->fp ); - if( read < 0 ) { - return read; - } - break; -#if USE_ZLIB - case FS_GZ: - read = gzread( file->zfp, buf, block ); - if( read < 0 ) { - return Q_ERR_INFLATE_FAILED; - } - break; - case FS_ZIP: - read = read_zip_file( file, buf, block ); - if( read < 0 ) { - return read; - } - break; -#endif - default: - break; - } - if( read == 0 ) { - return len - remaining; - } - - remaining -= read; - buf += read; - } - - return len; -} - -ssize_t FS_ReadLine( qhandle_t f, char *buffer, size_t size ) { - file_t *file = file_for_handle( f ); - char *s; - size_t len; - - if( file->type != FS_REAL ) { - return Q_ERR_NOSYS; - } - do { - s = fgets( buffer, size, file->fp ); - if( !s ) { - return ferror( file->fp ) ? Q_ERR(errno) : 0; - } - len = strlen( s ); - } while( len < 2 ); - - s[ len - 1 ] = 0; - return len - 1; -} - -void FS_Flush( qhandle_t f ) { - file_t *file = file_for_handle( f ); - - switch( file->type ) { - case FS_REAL: - fflush( file->fp ); - break; -#if USE_ZLIB - case FS_GZ: - gzflush( file->zfp, Z_SYNC_FLUSH ); - break; -#endif - default: - break; - } -} - -/* -================= -FS_Write - -Properly handles partial writes -================= -*/ -ssize_t FS_Write( const void *buffer, size_t len, qhandle_t f ) { - size_t block, remaining = len; - ssize_t write = 0; - byte *buf = (byte *)buffer; - file_t *file = file_for_handle( f ); - - if( len > SSIZE_MAX ) { - return Q_ERR_INVAL; - } - - // read in chunks for progress bar - while( remaining ) { - block = remaining; - if( block > MAX_WRITE ) - block = MAX_WRITE; - switch( file->type ) { - case FS_REAL: - write = write_block( buf, block, file->fp ); - if( write < 0 ) { - return write; - } - break; -#if USE_ZLIB - case FS_GZ: - write = gzwrite( file->zfp, buf, block ); - if( write < 0 ) { - return Q_ERR_DEFLATE_FAILED; - } - break; -#endif - default: - Com_Error( ERR_FATAL, "%s: bad file type", __func__ ); - } - if( write == 0 ) { - return len - remaining; - } - - remaining -= write; - buf += write; - } - - if( ( file->mode & FS_FLUSH_MASK ) == FS_FLUSH_SYNC ) { - switch( file->type ) { - case FS_REAL: - fflush( file->fp ); - break; -#if USE_ZLIB - case FS_GZ: - gzflush( file->zfp, Z_SYNC_FLUSH ); - break; -#endif - default: - break; - } - } - - return len; -} - -static char *expand_links( const char *filename ) { - static char buffer[MAX_OSPATH]; - symlink_t *link; - size_t len; - - len = strlen( filename ); - for( link = fs_links; link; link = link->next ) { - if( link->namelen > len ) { - continue; - } - if( !FS_pathcmpn( link->name, filename, link->namelen ) ) { - if( link->targlen + len - link->namelen >= MAX_OSPATH ) { - FS_DPrintf( "%s: %s: MAX_OSPATH exceeded\n", __func__, filename ); - return ( char * )filename; - } - memcpy( buffer, link->target, link->targlen ); - memcpy( buffer + link->targlen, filename + link->namelen, - len - link->namelen + 1 ); - FS_DPrintf( "%s: %s --> %s\n", __func__, filename, buffer ); - return buffer; - } - } - - return ( char * )filename; -} - -/* -============ -FS_FOpenFile -============ -*/ -ssize_t FS_FOpenFile( const char *name, qhandle_t *f, unsigned mode ) { - file_t *file; - qhandle_t handle; - ssize_t ret; - - if( !name || !f ) { - Com_Error( ERR_FATAL, "%s: NULL", __func__ ); - } - - *f = 0; - - if( !fs_searchpaths ) { - return Q_ERR_AGAIN; // not yet initialized - } - - if( *name == '/' ) { - name++; - } - - if( ( mode & FS_MODE_MASK ) == FS_MODE_READ ) { - name = expand_links( name ); - } - - // allocate new file handle - file = alloc_handle( &handle ); - if( !file ) { - return Q_ERR_MFILE; - } - - file->mode = mode; - - if( ( mode & FS_MODE_MASK ) == FS_MODE_READ ) { - ret = open_file_read( file, name, qtrue ); - } else { - ret = open_file_write( file, name ); - } - - if( ret >= 0 ) { - *f = handle; - } - - return ret; -} - -/* -============ -FS_EasyOpenFile - -Helper function for various console commands. Concatenates -the arguments, checks for path buffer overflow, and attempts -to open the file, printing an error message in case of failure. -============ -*/ -qhandle_t FS_EasyOpenFile( char *buf, size_t size, unsigned mode, - const char *dir, const char *name, const char *ext ) -{ - size_t len; - qhandle_t f; - qerror_t ret; - char *gz = NULL; - - if( mode & FS_FLAG_GZIP ) { - gz = ".gz"; - } - - // TODO: don't append the extension if name already has it - - len = Q_concat( buf, size, dir, name, ext, gz, NULL ); - if( len >= size ) { - ret = Q_ERR_NAMETOOLONG; - goto fail1; - } - - ret = FS_FOpenFile( buf, &f, mode ); - if( !f ) { - goto fail1; - } - - if( mode & FS_FLAG_GZIP ) { - ret = FS_FilterFile( f ); - if( ret ) { - goto fail2; - } - } - - return f; - -fail2: - FS_FCloseFile( f ); -fail1: - Com_EPrintf( "Couldn't open %s for writing: %s\n", buf, Q_ErrorString( ret ) ); - return 0; -} - -/* -============ -FS_LoadFile - -opens non-unique file handle as an optimization -a NULL buffer will just return the file length without loading -============ -*/ -ssize_t FS_LoadFileEx( const char *path, void **buffer, unsigned flags ) { - file_t *file; - qhandle_t f; - byte *buf; - ssize_t len, read; - - if( !path ) { - Com_Error( ERR_FATAL, "%s: NULL", __func__ ); - } - - if( buffer ) { - *buffer = NULL; - } - - if( !fs_searchpaths ) { - return Q_ERR_AGAIN; // not yet initialized - } - - if( *path == '/' ) { - path++; - } - - path = expand_links( path ); - - // allocate new file handle - file = alloc_handle( &f ); - if( !file ) { - return Q_ERR_MFILE; - } - - file->mode = ( flags & ~FS_MODE_MASK ) | FS_MODE_READ; - - // look for it in the filesystem or pack files - len = open_file_read( file, path, qfalse ); - if( len < 0 ) { - return len; - } - - // NULL buffer just checks for file existence - if( !buffer ) { - goto done; - } - - // sanity check file size - if( len > MAX_LOADFILE ) { - len = Q_ERR_FBIG; - goto done; - } - - // allocate chunk of memory, +1 for NUL - buf = FS_Malloc( len + 1 ); - - // read entire file - read = FS_Read( buf, len, f ); - if( read != len ) { - len = read < 0 ? read : Q_ERR_UNEXPECTED_EOF; - Z_Free( buf ); - goto done; - } - - *buffer = buf; - buf[len] = 0; - -done: - FS_FCloseFile( f ); - return len; -} - -ssize_t FS_LoadFile( const char *path, void **buffer ) { - return FS_LoadFileEx( path, buffer, 0 ); -} - -void *FS_AllocTempMem( size_t len ) { - return FS_Malloc( len ); -} - -void FS_FreeFile( void *buf ) { - Z_Free( buf ); -} - -/* -================ -FS_WriteFile -================ -*/ -qerror_t FS_WriteFile( const char *path, const void *data, size_t len ) { - qhandle_t f; - ssize_t write; - qerror_t ret; - - ret = FS_FOpenFile( path, &f, FS_MODE_WRITE ); - if( !f ) { - return ret; - } - - write = FS_Write( data, len, f ); - if( write != len ) { - ret = write < 0 ? write : Q_ERR_FAILURE; - } - - FS_FCloseFile( f ); - return ret; -} - -#if USE_CLIENT - -/* -================ -FS_RenameFile -================ -*/ -qerror_t FS_RenameFile( const char *from, const char *to ) { - char frompath[MAX_OSPATH]; - char topath[MAX_OSPATH]; - size_t len; - int ret; - - if( *from == '/' ) { - from++; - } - ret = validate_path( from ); - if( ret ) { - return ret; - } - len = Q_concat( frompath, sizeof( frompath ), fs_gamedir, "/", from, NULL ); - if( len >= sizeof( frompath ) ) { - return Q_ERR_NAMETOOLONG; - } - - if( *to == '/' ) { - to++; - } - ret = validate_path( to ); - if( ret ) { - return ret; - } - len = Q_concat( topath, sizeof( topath ), fs_gamedir, "/", to, NULL ); - if( len >= sizeof( topath ) ) { - return Q_ERR_NAMETOOLONG; - } - - if( rename( frompath, topath ) ) { - return Q_ERR(errno); - } - - return Q_ERR_SUCCESS; -} - -#endif // USE_CLIENT - -/* -================ -FS_FPrintf -================ -*/ -ssize_t FS_FPrintf( qhandle_t f, const char *format, ... ) { - va_list argptr; - char string[MAXPRINTMSG]; - size_t len; - - va_start( argptr, format ); - len = Q_vsnprintf( string, sizeof( string ), format, argptr ); - va_end( argptr ); - - if( len >= sizeof( string ) ) { - return Q_ERR_STRING_TRUNCATED; - } - - return FS_Write( string, len, f ); -} - -// references pack_t instance -static pack_t *pack_get( pack_t *pack ) { - pack->refcount++; - return pack; -} - -// dereferences pack_t instance -static void pack_put( pack_t *pack ) { - if( !pack ) { - return; - } - if( !pack->refcount ) { - Com_Error( ERR_FATAL, "%s: refcount already zero", __func__ ); - } - if( !--pack->refcount ) { - FS_DPrintf( "Freeing packfile %s\n", pack->filename ); - fclose( pack->fp ); - Z_Free( pack ); - } -} - -// allocates pack_t instance along with filenames and hashes in one chunk of memory -static pack_t *pack_alloc( FILE *fp, filetype_t type, const char *name, - unsigned num_files, size_t names_len ) -{ - pack_t *pack; - unsigned hash_size; - size_t len; - - hash_size = Q_CeilPowerOfTwo( num_files / 3 ); - - len = strlen( name ) + 1; - pack = FS_Malloc( sizeof( pack_t ) + - num_files * sizeof( packfile_t ) + - hash_size * sizeof( packfile_t * ) + - len + names_len ); - pack->type = type; - pack->refcount = 0; - pack->fp = fp; - pack->num_files = num_files; - pack->hash_size = hash_size; - pack->files = ( packfile_t * )( pack + 1 ); - pack->file_hash = ( packfile_t ** )( pack->files + num_files ); - pack->filename = ( char * )( pack->file_hash + hash_size ); - pack->names = pack->filename + len; - memcpy( pack->filename, name, len ); - memset( pack->file_hash, 0, hash_size * sizeof( packfile_t * ) ); - - return pack; -} - -// Loads the header and directory, adding the files at the beginning -// of the list so they override previous pack files. -static pack_t *load_pak_file( const char *packfile ) { - dpackheader_t header; - int i; - packfile_t *file; - dpackfile_t *dfile; - unsigned num_files; - char *name; - size_t names_len; - pack_t *pack; - FILE *fp; - dpackfile_t info[MAX_FILES_IN_PACK]; - unsigned hash; - size_t len; - - fp = fopen( packfile, "rb" ); - if( !fp ) { - Com_Printf( "Couldn't open %s: %s\n", packfile, strerror( errno ) ); - return NULL; - } - - if( fread( &header, 1, sizeof( header ), fp ) != sizeof( header ) ) { - Com_Printf( "Reading header failed on %s\n", packfile ); - goto fail; - } - - if( LittleLong( header.ident ) != IDPAKHEADER ) { - Com_Printf( "%s is not a 'PACK' file\n", packfile ); - goto fail; - } - - header.dirlen = LittleLong( header.dirlen ); - if( header.dirlen > LONG_MAX || header.dirlen % sizeof( dpackfile_t ) ) { - Com_Printf( "%s has bad directory length\n", packfile ); - goto fail; - } - - num_files = header.dirlen / sizeof( dpackfile_t ); - if( num_files < 1 ) { - Com_Printf( "%s has no files\n", packfile ); - goto fail; - } - if( num_files > MAX_FILES_IN_PACK ) { - Com_Printf( "%s has too many files: %u > %u\n", packfile, num_files, MAX_FILES_IN_PACK ); - goto fail; - } - - header.dirofs = LittleLong( header.dirofs ); - if( header.dirofs > LONG_MAX - header.dirlen ) { - Com_Printf( "%s has bad directory offset\n", packfile ); - goto fail; - } - if( fseek( fp, (long)header.dirofs, SEEK_SET ) ) { - Com_Printf( "Seeking to directory failed on %s\n", packfile ); - goto fail; - } - if( fread( info, 1, header.dirlen, fp ) != header.dirlen ) { - Com_Printf( "Reading directory failed on %s\n", packfile ); - goto fail; - } - - names_len = 0; - for( i = 0, dfile = info; i < num_files; i++, dfile++ ) { - dfile->filepos = LittleLong( dfile->filepos ); - dfile->filelen = LittleLong( dfile->filelen ); - if( dfile->filelen > LONG_MAX || dfile->filepos > LONG_MAX - dfile->filelen ) { - Com_Printf( "%s has bad directory structure\n", packfile ); - goto fail; - } - dfile->name[sizeof( dfile->name ) - 1] = 0; - names_len += strlen( dfile->name ) + 1; - } - -// allocate the pack - pack = pack_alloc( fp, FS_PAK, packfile, num_files, names_len ); - -// parse the directory - name = pack->names; - for( i = 0, file = pack->files, dfile = info; i < pack->num_files; i++, file++, dfile++ ) { - len = strlen( dfile->name ) + 1; - - file->name = memcpy( name, dfile->name, len ); - name += len; - - file->filepos = dfile->filepos; - file->filelen = dfile->filelen; - - hash = Com_HashPath( file->name, pack->hash_size ); - file->hash_next = pack->file_hash[hash]; - pack->file_hash[hash] = file; - } - - FS_DPrintf( "%s: %u files, %u hash\n", - packfile, num_files, pack->hash_size ); - - return pack; - -fail: - fclose( fp ); - return NULL; -} - -#if USE_ZLIB - -// Locate the central directory of a zipfile (at the end, just before the global comment) -static size_t search_central_header( FILE *fp ) { - size_t file_size, back_read; - size_t max_back = 0xffff; // maximum size of global comment - byte buf[ZIP_BUFREADCOMMENT + 4]; - long ret; - - if( fseek( fp, 0, SEEK_END ) == -1 ) - return 0; - - ret = ftell( fp ); - if( ret == -1 ) - return 0; - file_size = (size_t)ret; - if( max_back > file_size ) - max_back = file_size; - - back_read = 4; - while( back_read < max_back ) { - size_t i, read_size, read_pos; - - if( back_read + ZIP_BUFREADCOMMENT > max_back ) - back_read = max_back; - else - back_read += ZIP_BUFREADCOMMENT; - - read_pos = file_size - back_read; - - read_size = back_read; - if( read_size > ZIP_BUFREADCOMMENT + 4 ) - read_size = ZIP_BUFREADCOMMENT + 4; - - if( fseek( fp, (long)read_pos, SEEK_SET ) == -1 ) - break; - if( fread( buf, 1, read_size, fp ) != read_size ) - break; - - i = read_size - 4; - do { - // check the magic - if( LittleLongMem( buf + i ) == ZIP_ENDHEADERMAGIC ) - return read_pos + i; - } while( i-- ); - } - - return 0; -} - -// Get Info about the current file in the zipfile, with internal only info -static size_t get_file_info( FILE *fp, size_t pos, packfile_t *file, size_t *len ) { - size_t name_size, xtra_size, comm_size; - size_t comp_len, file_len, file_pos; - unsigned comp_mtd; - byte header[ZIP_SIZECENTRALDIRITEM]; // we can't use a struct here because of packing - - *len = 0; - - if( pos > LONG_MAX ) - return 0; - if( fseek( fp, (long)pos, SEEK_SET ) == -1 ) - return 0; - if( fread( header, 1, sizeof( header ), fp ) != sizeof( header ) ) - return 0; - - // check the magic - if( LittleLongMem( &header[0] ) != ZIP_CENTRALHEADERMAGIC ) - return 0; - - comp_mtd = LittleShortMem( &header[10] ); - //if( crc ) - // *crc = LittleLongMem( &header[16] ); - comp_len = LittleLongMem( &header[20] ); - file_len = LittleLongMem( &header[24] ); - name_size = LittleShortMem( &header[28] ); - xtra_size = LittleShortMem( &header[30] ); - comm_size = LittleShortMem( &header[32] ); - file_pos = LittleLongMem( &header[42] ); - - if( file_len > LONG_MAX ) - return 0; - if( comp_len > LONG_MAX || file_pos > LONG_MAX - comp_len ) - return 0; - - if( !file_len || !comp_len ) { - goto skip; // skip directories and empty files - } - if( !comp_mtd ) { - if( file_len != comp_len ) { - FS_DPrintf( "%s: skipping file stored with file_len != comp_len\n", __func__ ); - goto skip; - } - } else if( comp_mtd != Z_DEFLATED ) { - FS_DPrintf( "%s: skipping file compressed with unknown method\n", __func__ ); - goto skip; - } - if( !name_size ) { - FS_DPrintf( "%s: skipping file with empty name\n", __func__ ); - goto skip; - } - if( name_size >= MAX_QPATH ) { - FS_DPrintf( "%s: skipping file with oversize name\n", __func__ ); - goto skip; - } - - // fill in the info - if( file ) { - file->compmtd = comp_mtd; - file->complen = comp_len; - file->filelen = file_len; - file->filepos = file_pos; - if( fread( file->name, 1, name_size, fp ) != name_size ) - return 0; - file->name[name_size] = 0; - } - - *len = name_size + 1; - -skip: - return ZIP_SIZECENTRALDIRITEM + name_size + xtra_size + comm_size; -} - -static pack_t *load_zip_file( const char *packfile ) { - int i; - packfile_t *file; - char *name; - size_t names_len; - unsigned num_disk, num_disk_cd, num_files, num_files_cd; - size_t header_pos, central_ofs, central_size, central_end; - size_t extra_bytes, ofs; - pack_t *pack; - FILE *fp; - byte header[ZIP_SIZECENTRALHEADER]; - unsigned hash; - size_t len; - - fp = fopen( packfile, "rb" ); - if( !fp ) { - Com_Printf( "Couldn't open %s: %s\n", packfile, strerror( errno ) ); - return NULL; - } - - header_pos = search_central_header( fp ); - if( !header_pos ) { - Com_Printf( "No central header found in %s\n", packfile ); - goto fail2; - } - if( fseek( fp, (long)header_pos, SEEK_SET ) == -1 ) { - Com_Printf( "Couldn't seek to central header in %s\n", packfile ); - goto fail2; - } - if( fread( header, 1, sizeof( header ), fp ) != sizeof( header ) ) { - Com_Printf( "Reading central header failed on %s\n", packfile ); - goto fail2; - } - - num_disk = LittleShortMem( &header[4] ); - num_disk_cd = LittleShortMem( &header[6] ); - num_files = LittleShortMem( &header[8] ); - num_files_cd = LittleShortMem( &header[10] ); - if( num_files_cd != num_files || num_disk_cd != 0 || num_disk != 0 ) { - Com_Printf( "%s is an unsupported multi-part archive\n", packfile ); - goto fail2; - } - if( num_files < 1 ) { - Com_Printf( "%s has no files\n", packfile ); - goto fail2; - } - if( num_files > ZIP_MAXFILES ) { - Com_Printf( "%s has too many files: %u > %u\n", packfile, num_files, ZIP_MAXFILES ); - goto fail2; - } - - central_size = LittleLongMem( &header[12] ); - central_ofs = LittleLongMem( &header[16] ); - central_end = central_ofs + central_size; - if( central_end > header_pos || central_end < central_ofs ) { - Com_Printf( "%s has bad central directory offset\n", packfile ); - goto fail2; - } - -// non-zero for sfx? - extra_bytes = header_pos - central_end; - if( extra_bytes ) { - Com_Printf( "%s has %"PRIz" extra bytes at the beginning, funny sfx archive?\n", - packfile, extra_bytes ); - } - -// parse the directory - num_files = 0; - names_len = 0; - header_pos = central_ofs + extra_bytes; - for( i = 0; i < num_files_cd; i++ ) { - ofs = get_file_info( fp, header_pos, NULL, &len ); - if( !ofs ) { - Com_Printf( "%s has bad central directory structure\n", packfile ); - goto fail2; - } - header_pos += ofs; - - if( len ) { - names_len += len; - num_files++; - } - } - - if( !num_files ) { - Com_Printf( "%s has no valid files\n", packfile ); - goto fail2; - } - -// allocate the pack - pack = pack_alloc( fp, FS_ZIP, packfile, num_files, names_len ); - -// parse the directory - name = pack->names; - file = pack->files; - num_files = 0; - header_pos = central_ofs + extra_bytes; - for( i = 0; i < num_files_cd; i++ ) { - file->name = name; - ofs = get_file_info( fp, header_pos, file, &len ); - if( !ofs ) { - Com_EPrintf( "Error re-reading central directory in %s\n", packfile ); - goto fail1; - } - header_pos += ofs; - - if( len ) { - // fix absolute position - file->filepos += extra_bytes; - file->coherent = qfalse; - - hash = Com_HashPath( file->name, pack->hash_size ); - file->hash_next = pack->file_hash[hash]; - pack->file_hash[hash] = file; - - file++; - name += len; - if( ++num_files == pack->num_files ) { - break; - } - } - } - - FS_DPrintf( "%s: %u files, %u skipped, %u hash\n", - packfile, num_files, num_files_cd - num_files, pack->hash_size ); - - return pack; - -fail1: - Z_Free( pack ); -fail2: - fclose( fp ); - return NULL; -} -#endif - -// this is complicated as we need pakXX.pak loaded first, -// sorted in numerical order, then the rest of the paks in -// alphabetical order, e.g. pak0.pak, pak2.pak, pak17.pak, abc.pak... -static int QDECL pakcmp( const void *p1, const void *p2 ) { - char *s1 = *( char ** )p1; - char *s2 = *( char ** )p2; - - if( !FS_strncmp( s1, "pak", 3 ) ) { - if( !FS_strncmp( s2, "pak", 3 ) ) { - int n1 = strtoul( s1 + 3, &s1, 10 ); - int n2 = strtoul( s2 + 3, &s2, 10 ); - if( n1 > n2 ) { - return 1; - } - if( n1 < n2 ) { - return -1; - } - goto alphacmp; - } - return -1; - } - if( !FS_strncmp( s2, "pak", 3 ) ) { - return 1; - } - -alphacmp: - return FS_strcmp( s1, s2 ); -} - -static void load_pack_files( unsigned mode, const char *ext, pack_t *(loadfunc)( const char * ) ) { - int i; - searchpath_t *search; - pack_t *pack; - void **list; - int num_files; - char path[MAX_OSPATH]; - size_t len; - - list = Sys_ListFiles( fs_gamedir, ext, FS_SEARCH_NOSORT, 0, &num_files ); - if( !list ) { - return; - } - - qsort( list, num_files, sizeof( list[0] ), pakcmp ); - - for( i = 0; i < num_files; i++ ) { - len = Q_concat( path, sizeof( path ), fs_gamedir, "/", list[i], NULL ); - if( len >= sizeof( path ) ) { - Com_EPrintf( "%s: refusing oversize path\n", __func__ ); - continue; - } - pack = (*loadfunc)( path ); - if( !pack ) - continue; - search = FS_Malloc( sizeof( searchpath_t ) ); - search->mode = mode; - search->filename[0] = 0; - search->pack = pack_get( pack ); - search->next = fs_searchpaths; - fs_searchpaths = search; - } - - FS_FreeList( list ); -} - -// Sets fs_gamedir, adds the directory to the head of the path, -// then loads and adds pak*.pak, then anything else in alphabethical order. -static void q_printf( 2, 3 ) add_game_dir( unsigned mode, const char *fmt, ... ) { - va_list argptr; - searchpath_t *search; - size_t len; - //qerror_t ret; - - va_start( argptr, fmt ); - len = Q_vsnprintf( fs_gamedir, sizeof( fs_gamedir ), fmt, argptr ); - va_end( argptr ); - - if( len >= sizeof( fs_gamedir ) ) { - Com_EPrintf( "%s: refusing oversize path\n", __func__ ); - return; - } - -#ifdef _WIN32 - FS_ReplaceSeparators( fs_gamedir, '/' ); -#elif 0 - // check if this path exists and IS a directory - ret = Sys_GetPathInfo( fs_gamedir, NULL ); - if( Q_ERRNO(ret) != EISDIR ) { - Com_DPrintf( "Not adding %s: %s\n", fs_gamedir, Q_ErrorString( ret ) ); - return; - } -#endif - - // add the directory to the search path - search = FS_Malloc( sizeof( searchpath_t ) + len ); - search->mode = mode; - search->pack = NULL; - memcpy( search->filename, fs_gamedir, len + 1 ); - search->next = fs_searchpaths; - fs_searchpaths = search; - - // add any pak files in the format *.pak - load_pack_files( mode, ".pak", load_pak_file ); - -#if USE_ZLIB - // add any zip files in the format *.pkz - load_pack_files( mode, ".pkz", load_zip_file ); -#endif -} - -/* -================= -FS_CopyInfo -================= -*/ -file_info_t *FS_CopyInfo( const char *name, size_t size, time_t ctime, time_t mtime ) { - file_info_t *out; - size_t len; - - if( !name ) { - return NULL; - } - - len = strlen( name ); - out = FS_Mallocz( sizeof( *out ) + len ); - out->size = size; - out->ctime = ctime; - out->mtime = mtime; - memcpy( out->name, name, len + 1 ); - - return out; -} - -void **FS_CopyList( void **list, int count ) { - void **out; - int i; - - out = FS_Malloc( sizeof( void * ) * ( count + 1 ) ); - for( i = 0; i < count; i++ ) { - out[i] = list[i]; - } - out[i] = NULL; - - 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 *name ) { - int c1, c2; - const char *e, *n, *l; - - if( !name[0] || !ext[0] ) { - return qfalse; - } - - for( l = name; l[1]; l++ ) - ; - - for( e = ext; e[1]; e++ ) - ; - -rescan: - n = l; - do { - c1 = *e--; - c2 = *n--; - - if( c1 == ';' ) { - break; // matched - } - - if( c1 != c2 ) { - c1 = Q_tolower( c1 ); - c2 = Q_tolower( c2 ); - if( c1 != c2 ) { - while( e > ext ) { - c1 = *e--; - if( c1 == ';' ) { - goto rescan; - } - } - return qfalse; - } - } - if( n < name ) { - return qfalse; - } - } while( e >= ext ); - - return qtrue; -} - -static int infocmp( const void *p1, const void *p2 ) { - file_info_t *n1 = *( file_info_t ** )p1; - file_info_t *n2 = *( file_info_t ** )p2; - - return FS_pathcmp( n1->name, n2->name ); -} - -static int alphacmp( const void *p1, const void *p2 ) { - char *s1 = *( char ** )p1; - char *s2 = *( char ** )p2; - - return FS_pathcmp( s1, s2 ); -} - -/* -================= -FS_ListFiles -================= -*/ -void **FS_ListFiles( const char *path, - const char *extension, - int flags, - int *numFiles ) -{ - searchpath_t *search; - void *listedFiles[MAX_LISTED_FILES]; - int count, total; - char buffer[MAX_OSPATH]; - void **dirlist; - int dircount; - void **list; - int i; - size_t len, pathlen; - char *s; - int valid = -1; - - if( flags & FS_SEARCH_BYFILTER ) { - if( !extension ) { - Com_Error( ERR_FATAL, "FS_ListFiles: NULL filter" ); - } - } - - count = 0; - - if( numFiles ) { - *numFiles = 0; - } - - if( !path ) { - path = ""; - pathlen = 0; - } else { - if( *path == '/' ) { - path++; - } - pathlen = strlen( path ); - } - - for( search = fs_searchpaths; search; search = search->next ) { - if( flags & FS_PATH_MASK ) { - if( ( flags & search->mode & FS_PATH_MASK ) == 0 ) { - continue; - } - } - 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_SEARCH_BYFILTER ) { - for( i = 0; i < search->pack->num_files; i++ ) { - s = search->pack->files[i].name; - - // check path - if( pathlen ) { - if( FS_pathcmpn( s, path, pathlen ) ) { - continue; - } - s += pathlen + 1; - } - - // check filter - if( !FS_WildCmp( extension, s ) ) { - continue; - } - - // copy filename - if( count == MAX_LISTED_FILES ) { - break; - } - - if( !( flags & FS_SEARCH_SAVEPATH ) ) { - s = COM_SkipPath( s ); - } - if( flags & FS_SEARCH_EXTRAINFO ) { - listedFiles[count++] = FS_CopyInfo( s, - search->pack->files[i].filelen, 0, 0 ); - } else { - listedFiles[count++] = FS_CopyString( s ); - } - } - } else { - for( i = 0; i < search->pack->num_files; i++ ) { - s = search->pack->files[i].name; - - // check path - if( pathlen && FS_pathcmpn( s, path, pathlen ) ) { - continue; - } - - // check extension - if( extension && !FS_ExtCmp( extension, s ) ) { - continue; - } - - // copy filename - if( count == MAX_LISTED_FILES ) { - break; - } - if( !( flags & FS_SEARCH_SAVEPATH ) ) { - s = COM_SkipPath( s ); - } - if( flags & FS_SEARCH_EXTRAINFO ) { - listedFiles[count++] = FS_CopyInfo( s, - search->pack->files[i].filelen, 0, 0 ); - } else { - listedFiles[count++] = FS_CopyString( s ); - } - } - } - } else { - if( ( flags & FS_TYPE_MASK ) == FS_TYPE_PAK ) { - // don't search in OS filesystem - continue; - } - - len = strlen( search->filename ); - - if( pathlen ) { - if( len + pathlen + 1 >= MAX_OSPATH ) { - continue; - } - if( valid == -1 ) { - if( validate_path( path ) ) { - FS_DPrintf( "%s: refusing invalid path: %s\n", - __func__, path ); - valid = 0; - } - } - if( valid == 0 ) { - continue; - } - memcpy( buffer, search->filename, len ); - buffer[len++] = '/'; - memcpy( buffer + len, path, pathlen + 1 ); - s = buffer; - } else { - s = search->filename; - } - - if( flags & FS_SEARCH_BYFILTER ) { - len += pathlen + 1; - } - - dirlist = Sys_ListFiles( s, extension, - flags|FS_SEARCH_NOSORT, len, &dircount ); - if( !dirlist ) { - continue; - } - - if( count + dircount > MAX_LISTED_FILES ) { - dircount = MAX_LISTED_FILES - count; - } - for( i = 0; i < dircount; i++ ) { - listedFiles[count++] = dirlist[i]; - } - - Z_Free( dirlist ); - - } - if( count == MAX_LISTED_FILES ) { - break; - } - } - - if( !count ) { - return NULL; - } - - if( flags & FS_SEARCH_EXTRAINFO ) { - // TODO - qsort( listedFiles, count, sizeof( listedFiles[0] ), infocmp ); - total = count; - } else { - // sort alphabetically (ignoring FS_SEARCH_NOSORT) - qsort( listedFiles, count, sizeof( listedFiles[0] ), alphacmp ); - - // remove duplicates - total = 1; - for( i = 1; i < count; i++ ) { - if( !FS_pathcmp( listedFiles[ i - 1 ], listedFiles[i] ) ) { - Z_Free( listedFiles[ i - 1 ] ); - listedFiles[i-1] = NULL; - } else { - total++; - } - } - } - - list = FS_Malloc( sizeof( void * ) * ( 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_FreeList -================= -*/ -void FS_FreeList( void **list ) { - void **p = list; - - while( *p ) { - Z_Free( *p++ ); - } - - Z_Free( list ); -} - -void FS_File_g( const char *path, const char *ext, int flags, genctx_t *ctx ) { - int i, numFiles; - void **list; - char *s, *p; - - list = FS_ListFiles( path, ext, flags, &numFiles ); - if( !list ) { - return; - } - - for( i = 0; i < numFiles; i++ ) { - s = list[i]; - if( flags & 0x80000000 ) { - p = COM_FileExtension( s ); - *p = 0; - } - if( ctx->count < ctx->size && !strncmp( s, ctx->partial, ctx->length ) ) { - ctx->matches[ctx->count++] = s; - } else { - Z_Free( s ); - } - } - - Z_Free( list ); -} - -static void print_file_list( const char *path, const char *ext, int flags ) { - void **list; - int ndirs = 0; - int i; - - if( ( list = FS_ListFiles( path, ext, flags, &ndirs ) ) != NULL ) { - for( i = 0; i < ndirs; i++ ) { - Com_Printf( "%s\n", ( char * )list[i] ); - } - FS_FreeList( list ); - } - Com_Printf( "%i files listed\n", ndirs ); -} - -/* -============ -FS_FDir_f -============ -*/ -static void FS_FDir_f( void ) { - int flags; - char *filter; - - if( Cmd_Argc() < 2 ) { - Com_Printf( "Usage: %s <filter> [full_path]\n", Cmd_Argv( 0 ) ); - return; - } - - filter = Cmd_Argv( 1 ); - - flags = FS_SEARCH_BYFILTER; - if( Cmd_Argc() > 2 ) { - flags |= FS_SEARCH_SAVEPATH; - } - - print_file_list( NULL, filter, flags ); -} - -/* -============ -FS_Dir_f -============ -*/ -static void FS_Dir_f( void ) { - char *path, *ext; - - if( Cmd_Argc() < 2 ) { - Com_Printf( "Usage: %s <directory> [.extension]\n", Cmd_Argv( 0 ) ); - return; - } - - path = Cmd_Argv( 1 ); - if( Cmd_Argc() > 2 ) { - ext = Cmd_Argv( 2 ); - } else { - ext = NULL; - } - - print_file_list( path, ext, 0 ); -} - -/* -============ -FS_WhereIs_f -============ -*/ -static void FS_WhereIs_f( void ) { - searchpath_t *search; - pack_t *pak; - packfile_t *entry; - unsigned hash; - char filename[MAX_OSPATH]; - char fullpath[MAX_OSPATH]; - char *path; - file_info_t info; - int total; - int valid; - size_t len; - qerror_t ret; - qboolean report_all; - - if( Cmd_Argc() < 2 ) { - Com_Printf( "Usage: %s <path> [all]\n", Cmd_Argv( 0 ) ); - return; - } - - Cmd_ArgvBuffer( 1, filename, sizeof( filename ) ); - report_all = Cmd_Argc() >= 3; - - path = expand_links( filename ); - if( path != filename ) { - Com_Printf( "%s is linked to %s\n", filename, path ); - } - - hash = Com_HashPath( path, 0 ); - - total = 0; - valid = -1; - 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->file_hash[ hash & ( pak->hash_size - 1 ) ]; - for( ; entry; entry = entry->hash_next ) { - if( !FS_pathcmp( entry->name, path ) ) { - Com_Printf( "%s/%s (%"PRIz" bytes)\n", pak->filename, - path, entry->filelen ); - if( !report_all ) { - return; - } - total++; - } - } - } else { - if( valid == -1 ) { - ret = validate_path( path ); - if( ret ) { - Com_WPrintf( "Not searching for %s in real file system: %s\n", - path, Q_ErrorString( ret ) ); - valid = 0; - } - } - if( valid == 0 ) { - continue; - } - len = Q_concat( fullpath, sizeof( fullpath ), - search->filename, "/", path, NULL ); - if( len >= sizeof( fullpath ) ) { - ret = Q_ERR_NAMETOOLONG; - goto fail; - } - //FS_ConvertToSysPath( fullpath ); - ret = Sys_GetPathInfo( fullpath, &info ); - if( !ret ) { - Com_Printf( "%s (%"PRIz" bytes)\n", fullpath, info.size ); - if( !report_all ) { - return; - } - total++; - } else if( ret != Q_ERR_NOENT ) { -fail: - Com_EPrintf( "Couldn't get info on %s: %s\n", fullpath, Q_ErrorString( ret ) ); - if( !report_all ) { - 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; -#if USE_ZLIB - int numFilesInZIP = 0; -#endif - - Com_Printf( "Current search path:\n" ); - for( s = fs_searchpaths; s; s = s->next ) { - if( s->pack ) { -#if USE_ZLIB - if( s->pack->type == FS_ZIP ) - numFilesInZIP += s->pack->num_files; - else -#endif - numFilesInPAK += s->pack->num_files; - Com_Printf( "%s (%i files)\n", s->pack->filename, s->pack->num_files ); - } else { - Com_Printf( "%s\n", s->filename ); - } - } - - if( numFilesInPAK ) { - Com_Printf( "%i files in PAK files\n", numFilesInPAK ); - } - -#if USE_ZLIB - if( numFilesInZIP ) { - Com_Printf( "%i files in PKZ files\n", numFilesInZIP ); - } -#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->hash_size; i++ ) { - if( !( file = pack->file_hash[i] ) ) { - continue; - } - len = 0; - for( ; file ; file = file->hash_next ) { - len++; - } - if( maxLen < len ) { - max = pack->file_hash[i]; - maxpack = pack; - maxLen = len; - } - totalLen += len; - totalHashSize++; - } - //totalHashSize += pack->hash_size; - } - -#ifdef _DEBUG - 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->hash_next ) { - Com_Printf( "%s\n", file->name ); - } - } -#endif // _DEBUG -} - -static void FS_Link_g( genctx_t *ctx ) { - symlink_t *link; - - for( link = fs_links; link; link = link->next ) { - if( !Prompt_AddMatch( ctx, link->name ) ) { - break; - } - } -} - -static void FS_Link_c( genctx_t *ctx, int argnum ) { - if( argnum == 1 ) { - FS_Link_g( ctx ); - } -} - -static void free_all_links( void ) { - symlink_t *link, *next; - - for( link = fs_links; link; link = next ) { - next = link->next; - Z_Free( link->target ); - Z_Free( link ); - } - fs_links = NULL; -} - -static void FS_UnLink_f( void ) { - static const cmd_option_t options[] = { - { "a", "all", "delete all links" }, - { "h", "help", "display this message" }, - { NULL } - }; - symlink_t *link, **next_p; - char *name; - int c; - - while( ( c = Cmd_ParseOptions( options ) ) != -1 ) { - switch( c ) { - case 'h': - Com_Printf( "Usage: %s [-ah] <name> -- delete symbolic link\n", - Cmd_Argv( 0 ) ); - Cmd_PrintHelp( options ); - return; - case 'a': - free_all_links(); - Com_Printf( "Deleted all symbolic links.\n" ); - return; - default: - return; - } - } - - name = cmd_optarg; - if( !name[0] ) { - Com_Printf( "Missing name argument.\n" - "Try '%s --help' for more information.\n", Cmd_Argv( 0 ) ); - return; - } - - for( link = fs_links, next_p = &fs_links; link; link = link->next ) { - if( !FS_pathcmp( link->name, name ) ) { - break; - } - next_p = &link->next; - } - if( !link ) { - Com_Printf( "Symbolic link %s does not exist.\n", name ); - return; - } - - *next_p = link->next; - Z_Free( link->target ); - Z_Free( link ); -} - -static void FS_Link_f( void ) { - int argc, count; - size_t len; - symlink_t *link; - char *name, *target; - - argc = Cmd_Argc(); - if( argc == 1 ) { - for( link = fs_links, count = 0; link; link = link->next, count++ ) { - Com_Printf( "%s --> %s\n", link->name, link->target ); - } - Com_Printf( "------------------\n" - "%d symbolic links listed.\n", count ); - return; - } - if( argc != 3 ) { - Com_Printf( "Usage: %s <name> <target>\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( link = fs_links; link; link = link->next ) { - if( !FS_pathcmp( link->name, name ) ) { - Z_Free( link->target ); - goto update; - } - } - - len = strlen( name ); - link = FS_Malloc( sizeof( *link ) + len ); - memcpy( link->name, name, len + 1 ); - link->namelen = len; - link->next = fs_links; - fs_links = link; - -update: - target = Cmd_Argv( 2 ); - link->target = FS_CopyString( target ); - link->targlen = strlen( target ); -} - -static void free_search_path( searchpath_t *path ) { - pack_put( path->pack ); - Z_Free( path ); -} - -static void free_all_paths( void ) { - searchpath_t *path, *next; - - for( path = fs_searchpaths; path; path = next ) { - next = path->next; - free_search_path( path ); - } - - fs_searchpaths = NULL; -} - -static void free_game_paths( void ) { - searchpath_t *path, *next; - - for( path = fs_searchpaths; path != fs_base_searchpaths; path = next ) { - next = path->next; - free_search_path( path ); - } - - fs_searchpaths = fs_base_searchpaths; -} - -static void setup_base_paths( void ) { - add_game_dir( FS_PATH_BASE|FS_PATH_GAME, "%s/"BASEGAME, sys_basedir->string ); - fs_base_searchpaths = fs_searchpaths; -} - -// Sets the gamedir and path to a different directory. -static void setup_game_paths( void ) { - if( fs_game->string[0] ) { - // add system path first - add_game_dir( FS_PATH_GAME, "%s/%s", sys_basedir->string, fs_game->string ); - - // home paths override system paths - if( sys_homedir->string[0] ) { - add_game_dir( FS_PATH_BASE, "%s/"BASEGAME, sys_homedir->string ); - add_game_dir( FS_PATH_GAME, "%s/%s", sys_homedir->string, fs_game->string ); - } - - // this var is set for compatibility with server browsers, etc - Cvar_FullSet( "gamedir", fs_game->string, CVAR_ROM|CVAR_SERVERINFO, FROM_CODE ); - } else { - if( sys_homedir->string[0] ) { - add_game_dir( FS_PATH_BASE|FS_PATH_GAME, - "%s/"BASEGAME, sys_homedir->string ); - } - - Cvar_FullSet( "gamedir", "", CVAR_ROM, FROM_CODE ); - } - - // this var is used by the game library to find it's home directory - Cvar_FullSet( "fs_gamedir", fs_gamedir, CVAR_ROM, FROM_CODE ); -} - -/* -================ -FS_Restart - -Unless total is true, reloads paks only up to base dir -================ -*/ -void FS_Restart( qboolean total ) { - Com_Printf( "---------- FS_Restart ----------\n" ); - - if( total ) { - // perform full reset - free_all_paths(); - setup_base_paths(); - } else { - // just change gamedir - free_game_paths(); - } - - setup_game_paths(); - - FS_Path_f(); - - Com_Printf( "--------------------------------\n" ); -} - -/* -============ -FS_Restart_f - -Console command to fully re-start the file system. -============ -*/ -static void FS_Restart_f( void ) { -#if USE_CLIENT - CL_RestartFilesystem( qtrue ); -#else - FS_Restart( qtrue ); -#endif -} - -static const cmdreg_t c_fs[] = { - { "path", FS_Path_f }, - { "fdir", FS_FDir_f }, - { "dir", FS_Dir_f }, - { "fs_stats", FS_Stats_f }, - { "whereis", FS_WhereIs_f }, - { "link", FS_Link_f, FS_Link_c }, - { "unlink", FS_UnLink_f, FS_Link_c }, - { "fs_restart", FS_Restart_f }, - - { NULL } -}; - -/* -================ -FS_Shutdown -================ -*/ -void FS_Shutdown( void ) { - file_t *file; - int i; - - if( !fs_searchpaths ) { - return; - } - - // close file handles - for( i = 0, file = fs_files; i < MAX_FILE_HANDLES; i++, file++ ) { - if( file->type != FS_FREE ) { - Com_WPrintf( "%s: closing handle %d\n", __func__, i + 1 ); - FS_FCloseFile( i + 1 ); - } - } - - // free symbolic links - free_all_links(); - - // free search paths - free_all_paths(); - -#if USE_ZLIB - inflateEnd( &fs_zipstream.stream ); -#endif - - Z_LeakTest( TAG_FILESYSTEM ); - - Cmd_Deregister( c_fs ); -} - -// this is called when local server starts up and gets it's latched variables, -// client receives a serverdata packet, or user changes the game by hand while -// disconnected -static void fs_game_changed( cvar_t *self ) { - char *s = self->string; - - // validate it - if( *s ) { - if( !FS_strcmp( s, BASEGAME ) ) { - Cvar_Reset( self ); - } else if( strchr( s, '/' ) ) { - Com_Printf( "'%s' should be a single directory name, not a path.\n", self->name ); - Cvar_Reset( self ); - } - } - - // check for the first time startup - if( !fs_base_searchpaths ) { - // start up with baseq2 by default - setup_base_paths(); - - // check for game override - setup_game_paths(); - - FS_Path_f(); - return; - } - - // otherwise, restart the filesystem -#if USE_CLIENT - CL_RestartFilesystem( qfalse ); -#else - FS_Restart( qfalse ); -#endif -} - -/* -================ -FS_Init -================ -*/ -void FS_Init( void ) { - Com_Printf( "---------- FS_Init ----------\n" ); - - Cmd_Register( c_fs ); - -#ifdef _DEBUG - fs_debug = Cvar_Get( "fs_debug", "0", 0 ); -#endif - - // get the game cvar and start the filesystem - fs_game = Cvar_Get( "game", DEFGAME, CVAR_LATCH|CVAR_SERVERINFO ); - fs_game->changed = fs_game_changed; - fs_game_changed( fs_game ); - - Com_Printf( "-----------------------------\n" ); -} - |