summaryrefslogtreecommitdiff
path: root/source/files.c
diff options
context:
space:
mode:
Diffstat (limited to 'source/files.c')
-rw-r--r--source/files.c3051
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" );
-}
-