2005-08-26 04:48:05 +00:00
/*
= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
Copyright ( C ) 1999 - 2005 Id Software , Inc .
This file is part of Quake III Arena source code .
Quake III Arena source code 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 .
Quake III Arena source code 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 Foobar ; if not , write to the Free Software
Foundation , Inc . , 51 Franklin St , Fifth Floor , Boston , MA 02110 - 1301 USA
= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
*/
/*****************************************************************************
* name : files . c
*
* desc : handle based filesystem for Quake III Arena
*
* $ Archive : / MissionPack / code / qcommon / files . c $
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
# include "../game/q_shared.h"
# include "qcommon.h"
# include "unzip.h"
/*
= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
QUAKE3 FILESYSTEM
All of Quake ' s data access is through a hierarchical file system , but the contents of
the file system can be transparently merged from several sources .
A " qpath " is a reference to game file data . MAX_ZPATH is 256 characters , which must include
a terminating zero . " .. " , " \\ " , and " : " are explicitly illegal in qpaths to prevent any
references outside the quake directory system .
The " base path " is the path to the directory holding all the game directories and usually
the executable . It defaults to " . " , but can be overridden with a " +set fs_basepath c: \ quake3 "
command line to allow code debugging in a different directory . Basepath cannot
be modified at all after startup . Any files that are created ( demos , screenshots ,
etc ) will be created reletive to the base path , so base path should usually be writable .
The " cd path " is the path to an alternate hierarchy that will be searched if a file
is not located in the base path . A user can do a partial install that copies some
data to a base path created on their hard drive and leave the rest on the cd . Files
are never writen to the cd path . It defaults to a value set by the installer , like
" e: \ quake3 " , but it can be overridden with " +set ds_cdpath g: \ quake3 " .
If a user runs the game directly from a CD , the base path would be on the CD . This
should still function correctly , but all file writes will fail ( harmlessly ) .
The " home path " is the path used for all write access . On win32 systems we have " base path "
= = " home path " , but on * nix systems the base installation is usually readonly , and
" home path " points to ~ / . q3a or similar
The user can also install custom mods and content in " home path " , so it should be searched
along with " home path " and " cd path " for game content .
The " base game " is the directory under the paths where data comes from by default , and
can be either " baseq3 " or " demoq3 " .
The " current game " may be the same as the base game , or it may be the name of another
directory under the paths that should be searched for files before looking in the base game .
This is the basis for addons .
Clients automatically set the game directory after receiving a gamestate from a server ,
so only servers need to worry about + set fs_game .
No other directories outside of the base game and current game will ever be referenced by
filesystem functions .
To save disk space and speed loading , directory trees can be collapsed into zip files .
The files use a " .pk3 " extension to prevent users from unzipping them accidentally , but
otherwise the are simply normal uncompressed zip files . A game directory can have multiple
zip files of the form " pak0.pk3 " , " pak1.pk3 " , etc . Zip files are searched in decending order
from the highest number to the lowest , and will always take precedence over the filesystem .
This allows a pk3 distributed as a patch to override all existing data .
Because we will have updated executables freely available online , there is no point to
trying to restrict demo / oem versions of the game with code changes . Demo / oem versions
should be exactly the same executables as release versions , but with different data that
automatically restricts where game media can come from to prevent add - ons from working .
After the paths are initialized , quake will look for the product . txt file . If not
found and verified , the game will run in restricted mode . In restricted mode , only
files contained in demoq3 / pak0 . pk3 will be available for loading , and only if the zip header is
verified to not have been modified . A single exception is made for q3config . cfg . Files
can still be written out in restricted mode , so screenshots and demos are allowed .
Restricted mode can be tested by setting " +set fs_restrict 1 " on the command line , even
if there is a valid product . txt under the basepath or cdpath .
If not running in restricted mode , and a file is not found in any local filesystem ,
an attempt will be made to download it and save it under the base path .
If the " fs_copyfiles " cvar is set to 1 , then every time a file is sourced from the cd
path , it will be copied over to the base path . This is a development aid to help build
test releases and to copy working sets over slow network links .
File search order : when FS_FOpenFileRead gets called it will go through the fs_searchpaths
structure and stop on the first successful hit . fs_searchpaths is built with successive
calls to FS_AddGameDirectory
Additionaly , we search in several subdirectories :
current game is the current mode
base game is a variable to allow mods based on other mods
( such as baseq3 + missionpack content combination in a mod for instance )
BASEGAME is the hardcoded base game ( " baseq3 " )
e . g . the qpath " sound/newstuff/test.wav " would be searched for in the following places :
home path + current game ' s zip files
home path + current game ' s directory
base path + current game ' s zip files
base path + current game ' s directory
cd path + current game ' s zip files
cd path + current game ' s directory
home path + base game ' s zip file
home path + base game ' s directory
base path + base game ' s zip file
base path + base game ' s directory
cd path + base game ' s zip file
cd path + base game ' s directory
home path + BASEGAME ' s zip file
home path + BASEGAME ' s directory
base path + BASEGAME ' s zip file
base path + BASEGAME ' s directory
cd path + BASEGAME ' s zip file
cd path + BASEGAME ' s directory
server download , to be written to home path + current game ' s directory
The filesystem can be safely shutdown and reinitialized with different
basedir / cddir / game combinations , but all other subsystems that rely on it
( sound , video ) must also be forced to restart .
Because the same files are loaded by both the clip model ( CM_ ) and renderer ( TR_ )
subsystems , a simple single - file caching scheme is used . The CM_ subsystems will
load the file with a request to cache . Only one file will be kept cached at a time ,
so any models that are going to be referenced by both subsystems should alternate
between the CM_ load function and the ref load function .
TODO : A qpath that starts with a leading slash will always refer to the base game , even if another
game is currently active . This allows character models , skins , and sounds to be downloaded
to a common directory no matter which game is active .
How to prevent downloading zip files ?
Pass pk3 file names in systeminfo , and download before FS_Restart ( ) ?
Aborting a download disconnects the client from the server .
How to mark files as downloadable ? Commercial add - ons won ' t be downloadable .
Non - commercial downloads will want to download the entire zip file .
the game would have to be reset to actually read the zip in
Auto - update information
Path separators
Casing
separate server gamedir and client gamedir , so if the user starts
a local game after having connected to a network game , it won ' t stick
with the network game .
allow menu options for game selection ?
Read / write config to floppy option .
Different version coexistance ?
When building a pak file , make sure a q3config . cfg isn ' t present in it ,
or configs will never get loaded from disk !
todo :
downloading ( outside fs ? )
game directory passing and restarting
= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
*/
# define DEMOGAME "demota"
// every time a new demo pk3 file is built, this checksum must be updated.
// the easiest way to get it is to just run the game and see what it spits out
# define DEMO_PAK_CHECKSUM 437558517u
// if this is defined, the executable positively won't work with any paks other
// than the demo pak, even if productid is present. This is only used for our
// last demo release to prevent the mac and linux users from using the demo
// executable with the production windows pak before the mac/linux products
// hit the shelves a little later
// NOW defined in build files
//#define PRE_RELEASE_TADEMO
# define MAX_ZPATH 256
# define MAX_SEARCH_PATHS 4096
# define MAX_FILEHASH_SIZE 1024
typedef struct fileInPack_s {
char * name ; // name of the file
unsigned long pos ; // file info position in zip
struct fileInPack_s * next ; // next file in the hash
} fileInPack_t ;
typedef struct {
char pakFilename [ MAX_OSPATH ] ; // c:\quake3\baseq3\pak0.pk3
char pakBasename [ MAX_OSPATH ] ; // pak0
char pakGamename [ MAX_OSPATH ] ; // baseq3
unzFile handle ; // handle to zip file
int checksum ; // regular checksum
int pure_checksum ; // checksum for pure
int numfiles ; // number of files in pk3
int referenced ; // referenced file flags
int hashSize ; // hash table size (power of 2)
fileInPack_t * * hashTable ; // hash table
fileInPack_t * buildBuffer ; // buffer with the filenames etc.
} pack_t ;
typedef struct {
char path [ MAX_OSPATH ] ; // c:\quake3
char gamedir [ MAX_OSPATH ] ; // baseq3
} directory_t ;
typedef struct searchpath_s {
struct searchpath_s * next ;
pack_t * pack ; // only one of pack / dir will be non NULL
directory_t * dir ;
} searchpath_t ;
static char fs_gamedir [ MAX_OSPATH ] ; // this will be a single file name with no separators
static cvar_t * fs_debug ;
static cvar_t * fs_homepath ;
static cvar_t * fs_basepath ;
static cvar_t * fs_basegame ;
static cvar_t * fs_cdpath ;
static cvar_t * fs_copyfiles ;
static cvar_t * fs_gamedirvar ;
static cvar_t * fs_restrict ;
static searchpath_t * fs_searchpaths ;
static int fs_readCount ; // total bytes read
static int fs_loadCount ; // total files read
static int fs_loadStack ; // total files in memory
static int fs_packFiles ; // total number of files in packs
static int fs_fakeChkSum ;
static int fs_checksumFeed ;
typedef union qfile_gus {
FILE * o ;
unzFile z ;
} qfile_gut ;
typedef struct qfile_us {
qfile_gut file ;
qboolean unique ;
} qfile_ut ;
typedef struct {
qfile_ut handleFiles ;
qboolean handleSync ;
int baseOffset ;
int fileSize ;
int zipFilePos ;
qboolean zipFile ;
qboolean streamed ;
char name [ MAX_ZPATH ] ;
} fileHandleData_t ;
static fileHandleData_t fsh [ MAX_FILE_HANDLES ] ;
// TTimo - https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=540
// wether we did a reorder on the current search path when joining the server
static qboolean fs_reordered ;
// never load anything from pk3 files that are not present at the server when pure
static int fs_numServerPaks ;
static int fs_serverPaks [ MAX_SEARCH_PATHS ] ; // checksums
static char * fs_serverPakNames [ MAX_SEARCH_PATHS ] ; // pk3 names
// only used for autodownload, to make sure the client has at least
// all the pk3 files that are referenced at the server side
static int fs_numServerReferencedPaks ;
static int fs_serverReferencedPaks [ MAX_SEARCH_PATHS ] ; // checksums
static char * fs_serverReferencedPakNames [ MAX_SEARCH_PATHS ] ; // pk3 names
// last valid game folder used
char lastValidBase [ MAX_OSPATH ] ;
char lastValidGame [ MAX_OSPATH ] ;
// productId: This file is copyright 1999 Id Software, and may not be duplicated except during a licensed installation of the full commercial version of Quake 3:Arena
static byte fs_scrambledProductId [ 152 ] = {
220 , 129 , 255 , 108 , 244 , 163 , 171 , 55 , 133 , 65 , 199 , 36 , 140 , 222 , 53 , 99 , 65 , 171 , 175 , 232 , 236 , 193 , 210 , 250 , 169 , 104 , 231 , 231 , 21 , 201 , 170 , 208 , 135 , 175 , 130 , 136 , 85 , 215 , 71 , 23 , 96 , 32 , 96 , 83 , 44 , 240 , 219 , 138 , 184 , 215 , 73 , 27 , 196 , 247 , 55 , 139 , 148 , 68 , 78 , 203 , 213 , 238 , 139 , 23 , 45 , 205 , 118 , 186 , 236 , 230 , 231 , 107 , 212 , 1 , 10 , 98 , 30 , 20 , 116 , 180 , 216 , 248 , 166 , 35 , 45 , 22 , 215 , 229 , 35 , 116 , 250 , 167 , 117 , 3 , 57 , 55 , 201 , 229 , 218 , 222 , 128 , 12 , 141 , 149 , 32 , 110 , 168 , 215 , 184 , 53 , 31 , 147 , 62 , 12 , 138 , 67 , 132 , 54 , 125 , 6 , 221 , 148 , 140 , 4 , 21 , 44 , 198 , 3 , 126 , 12 , 100 , 236 , 61 , 42 , 44 , 251 , 15 , 135 , 14 , 134 , 89 , 92 , 177 , 246 , 152 , 106 , 124 , 78 , 118 , 80 , 28 , 42
} ;
# ifdef FS_MISSING
FILE * missingFiles = NULL ;
# endif
/*
= = = = = = = = = = = = = =
FS_Initialized
= = = = = = = = = = = = = =
*/
qboolean FS_Initialized ( ) {
return ( fs_searchpaths ! = NULL ) ;
}
/*
= = = = = = = = = = = = = = = = =
FS_PakIsPure
= = = = = = = = = = = = = = = = =
*/
qboolean FS_PakIsPure ( pack_t * pack ) {
int i ;
if ( fs_numServerPaks ) {
for ( i = 0 ; i < fs_numServerPaks ; i + + ) {
// FIXME: also use hashed file names
// NOTE TTimo: a pk3 with same checksum but different name would be validated too
// I don't see this as allowing for any exploit, it would only happen if the client does manips of it's file names 'not a bug'
if ( pack - > checksum = = fs_serverPaks [ i ] ) {
return qtrue ; // on the aproved list
}
}
return qfalse ; // not on the pure server pak list
}
return qtrue ;
}
/*
= = = = = = = = = = = = = = = = =
FS_LoadStack
return load stack
= = = = = = = = = = = = = = = = =
*/
int FS_LoadStack ( )
{
return fs_loadStack ;
}
/*
= = = = = = = = = = = = = = = =
return a hash value for the filename
= = = = = = = = = = = = = = = =
*/
static long FS_HashFileName ( const char * fname , int hashSize ) {
int i ;
long hash ;
char letter ;
hash = 0 ;
i = 0 ;
while ( fname [ i ] ! = ' \0 ' ) {
letter = tolower ( fname [ i ] ) ;
if ( letter = = ' . ' ) break ; // don't include extension
if ( letter = = ' \\ ' ) letter = ' / ' ; // damn path names
if ( letter = = PATH_SEP ) letter = ' / ' ; // damn path names
hash + = ( long ) ( letter ) * ( i + 119 ) ;
i + + ;
}
hash = ( hash ^ ( hash > > 10 ) ^ ( hash > > 20 ) ) ;
hash & = ( hashSize - 1 ) ;
return hash ;
}
static fileHandle_t FS_HandleForFile ( void ) {
int i ;
for ( i = 1 ; i < MAX_FILE_HANDLES ; i + + ) {
if ( fsh [ i ] . handleFiles . file . o = = NULL ) {
return i ;
}
}
Com_Error ( ERR_DROP , " FS_HandleForFile: none free " ) ;
return 0 ;
}
static FILE * FS_FileForHandle ( fileHandle_t f ) {
if ( f < 0 | | f > MAX_FILE_HANDLES ) {
Com_Error ( ERR_DROP , " FS_FileForHandle: out of reange " ) ;
}
if ( fsh [ f ] . zipFile = = qtrue ) {
Com_Error ( ERR_DROP , " FS_FileForHandle: can't get FILE on zip file " ) ;
}
if ( ! fsh [ f ] . handleFiles . file . o ) {
Com_Error ( ERR_DROP , " FS_FileForHandle: NULL " ) ;
}
return fsh [ f ] . handleFiles . file . o ;
}
void FS_ForceFlush ( fileHandle_t f ) {
FILE * file ;
file = FS_FileForHandle ( f ) ;
setvbuf ( file , NULL , _IONBF , 0 ) ;
}
/*
= = = = = = = = = = = = = = = =
FS_filelength
If this is called on a non - unique FILE ( from a pak file ) ,
it will return the size of the pak file , not the expected
size of the file .
= = = = = = = = = = = = = = = =
*/
int FS_filelength ( fileHandle_t f ) {
int pos ;
int end ;
FILE * h ;
h = FS_FileForHandle ( f ) ;
pos = ftell ( h ) ;
fseek ( h , 0 , SEEK_END ) ;
end = ftell ( h ) ;
fseek ( h , pos , SEEK_SET ) ;
return end ;
}
/*
= = = = = = = = = = = = = = = = = = = =
FS_ReplaceSeparators
Fix things up differently for win / unix / mac
= = = = = = = = = = = = = = = = = = = =
*/
static void FS_ReplaceSeparators ( char * path ) {
char * s ;
for ( s = path ; * s ; s + + ) {
if ( * s = = ' / ' | | * s = = ' \\ ' ) {
* s = PATH_SEP ;
}
}
}
/*
= = = = = = = = = = = = = = = = = = =
FS_BuildOSPath
Qpath may have either forward or backwards slashes
= = = = = = = = = = = = = = = = = = =
*/
char * FS_BuildOSPath ( const char * base , const char * game , const char * qpath ) {
char temp [ MAX_OSPATH ] ;
static char ospath [ 2 ] [ MAX_OSPATH ] ;
static int toggle ;
toggle ^ = 1 ; // flip-flop to allow two returns without clash
if ( ! game | | ! game [ 0 ] ) {
game = fs_gamedir ;
}
Com_sprintf ( temp , sizeof ( temp ) , " /%s/%s " , game , qpath ) ;
FS_ReplaceSeparators ( temp ) ;
Com_sprintf ( ospath [ toggle ] , sizeof ( ospath [ 0 ] ) , " %s%s " , base , temp ) ;
return ospath [ toggle ] ;
}
/*
= = = = = = = = = = = =
FS_CreatePath
Creates any directories needed to store the given filename
= = = = = = = = = = = =
*/
static qboolean FS_CreatePath ( char * OSPath ) {
char * ofs ;
// make absolutely sure that it can't back up the path
// FIXME: is c: allowed???
if ( strstr ( OSPath , " .. " ) | | strstr ( OSPath , " :: " ) ) {
Com_Printf ( " WARNING: refusing to create relative path \" %s \" \n " , OSPath ) ;
return qtrue ;
}
for ( ofs = OSPath + 1 ; * ofs ; ofs + + ) {
if ( * ofs = = PATH_SEP ) {
// create the directory
* ofs = 0 ;
Sys_Mkdir ( OSPath ) ;
* ofs = PATH_SEP ;
}
}
return qfalse ;
}
/*
= = = = = = = = = = = = = = = = =
FS_CopyFile
Copy a fully specified file from one place to another
= = = = = = = = = = = = = = = = =
*/
static void FS_CopyFile ( char * fromOSPath , char * toOSPath ) {
FILE * f ;
int len ;
byte * buf ;
Com_Printf ( " copy %s to %s \n " , fromOSPath , toOSPath ) ;
if ( strstr ( fromOSPath , " journal.dat " ) | | strstr ( fromOSPath , " journaldata.dat " ) ) {
Com_Printf ( " Ignoring journal files \n " ) ;
return ;
}
f = fopen ( fromOSPath , " rb " ) ;
if ( ! f ) {
return ;
}
fseek ( f , 0 , SEEK_END ) ;
len = ftell ( f ) ;
fseek ( f , 0 , SEEK_SET ) ;
// we are using direct malloc instead of Z_Malloc here, so it
// probably won't work on a mac... Its only for developers anyway...
buf = malloc ( len ) ;
if ( fread ( buf , 1 , len , f ) ! = len )
Com_Error ( ERR_FATAL , " Short read in FS_Copyfiles() \n " ) ;
fclose ( f ) ;
if ( FS_CreatePath ( toOSPath ) ) {
return ;
}
f = fopen ( toOSPath , " wb " ) ;
if ( ! f ) {
return ;
}
if ( fwrite ( buf , 1 , len , f ) ! = len )
Com_Error ( ERR_FATAL , " Short write in FS_Copyfiles() \n " ) ;
fclose ( f ) ;
free ( buf ) ;
}
/*
= = = = = = = = = = =
FS_Remove
= = = = = = = = = = =
*/
static void FS_Remove ( const char * osPath ) {
remove ( osPath ) ;
}
/*
= = = = = = = = = = = = = = = =
FS_FileExists
Tests if the file exists in the current gamedir , this DOES NOT
search the paths . This is to determine if opening a file to write
( which always goes into the current gamedir ) will cause any overwrites .
NOTE TTimo : this goes with FS_FOpenFileWrite for opening the file afterwards
= = = = = = = = = = = = = = = =
*/
qboolean FS_FileExists ( const char * file )
{
FILE * f ;
char * testpath ;
testpath = FS_BuildOSPath ( fs_homepath - > string , fs_gamedir , file ) ;
f = fopen ( testpath , " rb " ) ;
if ( f ) {
fclose ( f ) ;
return qtrue ;
}
return qfalse ;
}
/*
= = = = = = = = = = = = = = = =
FS_SV_FileExists
Tests if the file exists
= = = = = = = = = = = = = = = =
*/
qboolean FS_SV_FileExists ( const char * file )
{
FILE * f ;
char * testpath ;
testpath = FS_BuildOSPath ( fs_homepath - > string , file , " " ) ;
testpath [ strlen ( testpath ) - 1 ] = ' \0 ' ;
f = fopen ( testpath , " rb " ) ;
if ( f ) {
fclose ( f ) ;
return qtrue ;
}
return qfalse ;
}
/*
= = = = = = = = = = =
FS_SV_FOpenFileWrite
= = = = = = = = = = =
*/
fileHandle_t FS_SV_FOpenFileWrite ( const char * filename ) {
char * ospath ;
fileHandle_t f ;
if ( ! fs_searchpaths ) {
Com_Error ( ERR_FATAL , " Filesystem call made without initialization \n " ) ;
}
ospath = FS_BuildOSPath ( fs_homepath - > string , filename , " " ) ;
ospath [ strlen ( ospath ) - 1 ] = ' \0 ' ;
f = FS_HandleForFile ( ) ;
fsh [ f ] . zipFile = qfalse ;
if ( fs_debug - > integer ) {
Com_Printf ( " FS_SV_FOpenFileWrite: %s \n " , ospath ) ;
}
if ( FS_CreatePath ( ospath ) ) {
return 0 ;
}
Com_DPrintf ( " writing to: %s \n " , ospath ) ;
fsh [ f ] . handleFiles . file . o = fopen ( ospath , " wb " ) ;
Q_strncpyz ( fsh [ f ] . name , filename , sizeof ( fsh [ f ] . name ) ) ;
fsh [ f ] . handleSync = qfalse ;
if ( ! fsh [ f ] . handleFiles . file . o ) {
f = 0 ;
}
return f ;
}
/*
= = = = = = = = = = =
FS_SV_FOpenFileRead
search for a file somewhere below the home path , base path or cd path
we search in that order , matching FS_SV_FOpenFileRead order
= = = = = = = = = = =
*/
int FS_SV_FOpenFileRead ( const char * filename , fileHandle_t * fp ) {
char * ospath ;
fileHandle_t f = 0 ;
if ( ! fs_searchpaths ) {
Com_Error ( ERR_FATAL , " Filesystem call made without initialization \n " ) ;
}
f = FS_HandleForFile ( ) ;
fsh [ f ] . zipFile = qfalse ;
Q_strncpyz ( fsh [ f ] . name , filename , sizeof ( fsh [ f ] . name ) ) ;
// don't let sound stutter
S_ClearSoundBuffer ( ) ;
// search homepath
ospath = FS_BuildOSPath ( fs_homepath - > string , filename , " " ) ;
// remove trailing slash
ospath [ strlen ( ospath ) - 1 ] = ' \0 ' ;
if ( fs_debug - > integer ) {
Com_Printf ( " FS_SV_FOpenFileRead (fs_homepath): %s \n " , ospath ) ;
}
fsh [ f ] . handleFiles . file . o = fopen ( ospath , " rb " ) ;
fsh [ f ] . handleSync = qfalse ;
if ( ! fsh [ f ] . handleFiles . file . o )
{
// NOTE TTimo on non *nix systems, fs_homepath == fs_basepath, might want to avoid
if ( Q_stricmp ( fs_homepath - > string , fs_basepath - > string ) )
{
// search basepath
ospath = FS_BuildOSPath ( fs_basepath - > string , filename , " " ) ;
ospath [ strlen ( ospath ) - 1 ] = ' \0 ' ;
if ( fs_debug - > integer )
{
Com_Printf ( " FS_SV_FOpenFileRead (fs_basepath): %s \n " , ospath ) ;
}
fsh [ f ] . handleFiles . file . o = fopen ( ospath , " rb " ) ;
fsh [ f ] . handleSync = qfalse ;
if ( ! fsh [ f ] . handleFiles . file . o )
{
f = 0 ;
}
}
}
if ( ! fsh [ f ] . handleFiles . file . o ) {
// search cd path
ospath = FS_BuildOSPath ( fs_cdpath - > string , filename , " " ) ;
ospath [ strlen ( ospath ) - 1 ] = ' \0 ' ;
if ( fs_debug - > integer )
{
Com_Printf ( " FS_SV_FOpenFileRead (fs_cdpath) : %s \n " , ospath ) ;
}
fsh [ f ] . handleFiles . file . o = fopen ( ospath , " rb " ) ;
fsh [ f ] . handleSync = qfalse ;
if ( ! fsh [ f ] . handleFiles . file . o ) {
f = 0 ;
}
}
* fp = f ;
if ( f ) {
return FS_filelength ( f ) ;
}
return 0 ;
}
/*
= = = = = = = = = = =
FS_SV_Rename
= = = = = = = = = = =
*/
void FS_SV_Rename ( const char * from , const char * to ) {
char * from_ospath , * to_ospath ;
if ( ! fs_searchpaths ) {
Com_Error ( ERR_FATAL , " Filesystem call made without initialization \n " ) ;
}
// don't let sound stutter
S_ClearSoundBuffer ( ) ;
from_ospath = FS_BuildOSPath ( fs_homepath - > string , from , " " ) ;
to_ospath = FS_BuildOSPath ( fs_homepath - > string , to , " " ) ;
from_ospath [ strlen ( from_ospath ) - 1 ] = ' \0 ' ;
to_ospath [ strlen ( to_ospath ) - 1 ] = ' \0 ' ;
if ( fs_debug - > integer ) {
Com_Printf ( " FS_SV_Rename: %s --> %s \n " , from_ospath , to_ospath ) ;
}
if ( rename ( from_ospath , to_ospath ) ) {
// Failed, try copying it and deleting the original
FS_CopyFile ( from_ospath , to_ospath ) ;
FS_Remove ( from_ospath ) ;
}
}
/*
= = = = = = = = = = =
FS_Rename
= = = = = = = = = = =
*/
void FS_Rename ( const char * from , const char * to ) {
char * from_ospath , * to_ospath ;
if ( ! fs_searchpaths ) {
Com_Error ( ERR_FATAL , " Filesystem call made without initialization \n " ) ;
}
// don't let sound stutter
S_ClearSoundBuffer ( ) ;
from_ospath = FS_BuildOSPath ( fs_homepath - > string , fs_gamedir , from ) ;
to_ospath = FS_BuildOSPath ( fs_homepath - > string , fs_gamedir , to ) ;
if ( fs_debug - > integer ) {
Com_Printf ( " FS_Rename: %s --> %s \n " , from_ospath , to_ospath ) ;
}
if ( rename ( from_ospath , to_ospath ) ) {
// Failed, try copying it and deleting the original
FS_CopyFile ( from_ospath , to_ospath ) ;
FS_Remove ( from_ospath ) ;
}
}
/*
= = = = = = = = = = = = = =
FS_FCloseFile
If the FILE pointer is an open pak file , leave it open .
For some reason , other dll ' s can ' t just cal fclose ( )
on files returned by FS_FOpenFile . . .
= = = = = = = = = = = = = =
*/
void FS_FCloseFile ( fileHandle_t f ) {
if ( ! fs_searchpaths ) {
Com_Error ( ERR_FATAL , " Filesystem call made without initialization \n " ) ;
}
if ( fsh [ f ] . streamed ) {
Sys_EndStreamedFile ( f ) ;
}
if ( fsh [ f ] . zipFile = = qtrue ) {
unzCloseCurrentFile ( fsh [ f ] . handleFiles . file . z ) ;
if ( fsh [ f ] . handleFiles . unique ) {
unzClose ( fsh [ f ] . handleFiles . file . z ) ;
}
Com_Memset ( & fsh [ f ] , 0 , sizeof ( fsh [ f ] ) ) ;
return ;
}
// we didn't find it as a pak, so close it as a unique file
if ( fsh [ f ] . handleFiles . file . o ) {
fclose ( fsh [ f ] . handleFiles . file . o ) ;
}
Com_Memset ( & fsh [ f ] , 0 , sizeof ( fsh [ f ] ) ) ;
}
/*
= = = = = = = = = = =
FS_FOpenFileWrite
= = = = = = = = = = =
*/
fileHandle_t FS_FOpenFileWrite ( const char * filename ) {
char * ospath ;
fileHandle_t f ;
if ( ! fs_searchpaths ) {
Com_Error ( ERR_FATAL , " Filesystem call made without initialization \n " ) ;
}
f = FS_HandleForFile ( ) ;
fsh [ f ] . zipFile = qfalse ;
ospath = FS_BuildOSPath ( fs_homepath - > string , fs_gamedir , filename ) ;
if ( fs_debug - > integer ) {
Com_Printf ( " FS_FOpenFileWrite: %s \n " , ospath ) ;
}
if ( FS_CreatePath ( ospath ) ) {
return 0 ;
}
// enabling the following line causes a recursive function call loop
// when running with +set logfile 1 +set developer 1
//Com_DPrintf( "writing to: %s\n", ospath );
fsh [ f ] . handleFiles . file . o = fopen ( ospath , " wb " ) ;
Q_strncpyz ( fsh [ f ] . name , filename , sizeof ( fsh [ f ] . name ) ) ;
fsh [ f ] . handleSync = qfalse ;
if ( ! fsh [ f ] . handleFiles . file . o ) {
f = 0 ;
}
return f ;
}
/*
= = = = = = = = = = =
FS_FOpenFileAppend
= = = = = = = = = = =
*/
fileHandle_t FS_FOpenFileAppend ( const char * filename ) {
char * ospath ;
fileHandle_t f ;
if ( ! fs_searchpaths ) {
Com_Error ( ERR_FATAL , " Filesystem call made without initialization \n " ) ;
}
f = FS_HandleForFile ( ) ;
fsh [ f ] . zipFile = qfalse ;
Q_strncpyz ( fsh [ f ] . name , filename , sizeof ( fsh [ f ] . name ) ) ;
// don't let sound stutter
S_ClearSoundBuffer ( ) ;
ospath = FS_BuildOSPath ( fs_homepath - > string , fs_gamedir , filename ) ;
if ( fs_debug - > integer ) {
Com_Printf ( " FS_FOpenFileAppend: %s \n " , ospath ) ;
}
if ( FS_CreatePath ( ospath ) ) {
return 0 ;
}
fsh [ f ] . handleFiles . file . o = fopen ( ospath , " ab " ) ;
fsh [ f ] . handleSync = qfalse ;
if ( ! fsh [ f ] . handleFiles . file . o ) {
f = 0 ;
}
return f ;
}
/*
= = = = = = = = = = =
FS_FilenameCompare
Ignore case and seprator char distinctions
= = = = = = = = = = =
*/
qboolean FS_FilenameCompare ( const char * s1 , const char * s2 ) {
int c1 , c2 ;
do {
c1 = * s1 + + ;
c2 = * s2 + + ;
if ( c1 > = ' a ' & & c1 < = ' z ' ) {
c1 - = ( ' a ' - ' A ' ) ;
}
if ( c2 > = ' a ' & & c2 < = ' z ' ) {
c2 - = ( ' a ' - ' A ' ) ;
}
if ( c1 = = ' \\ ' | | c1 = = ' : ' ) {
c1 = ' / ' ;
}
if ( c2 = = ' \\ ' | | c2 = = ' : ' ) {
c2 = ' / ' ;
}
if ( c1 ! = c2 ) {
return - 1 ; // strings not equal
}
} while ( c1 ) ;
return 0 ; // strings are equal
}
/*
= = = = = = = = = = =
FS_ShiftedStrStr
= = = = = = = = = = =
*/
char * FS_ShiftedStrStr ( const char * string , const char * substring , int shift ) {
char buf [ MAX_STRING_TOKENS ] ;
int i ;
for ( i = 0 ; substring [ i ] ; i + + ) {
buf [ i ] = substring [ i ] + shift ;
}
buf [ i ] = ' \0 ' ;
return strstr ( string , buf ) ;
}
/*
= = = = = = = = = = =
FS_FOpenFileRead
Finds the file in the search path .
Returns filesize and an open FILE pointer .
Used for streaming data out of either a
separate file or a ZIP file .
= = = = = = = = = = =
*/
extern qboolean com_fullyInitialized ;
int FS_FOpenFileRead ( const char * filename , fileHandle_t * file , qboolean uniqueFILE ) {
searchpath_t * search ;
char * netpath ;
pack_t * pak ;
fileInPack_t * pakFile ;
directory_t * dir ;
long hash ;
unz_s * zfi ;
FILE * temp ;
int l ;
char demoExt [ 16 ] ;
hash = 0 ;
if ( ! fs_searchpaths ) {
Com_Error ( ERR_FATAL , " Filesystem call made without initialization \n " ) ;
}
if ( file = = NULL ) {
// just wants to see if file is there
for ( search = fs_searchpaths ; search ; search = search - > next ) {
//
if ( search - > pack ) {
hash = FS_HashFileName ( filename , search - > pack - > hashSize ) ;
}
// is the element a pak file?
if ( search - > pack & & search - > pack - > hashTable [ hash ] ) {
// look through all the pak file elements
pak = search - > pack ;
pakFile = pak - > hashTable [ hash ] ;
do {
// case and separator insensitive comparisons
if ( ! FS_FilenameCompare ( pakFile - > name , filename ) ) {
// found it!
return qtrue ;
}
pakFile = pakFile - > next ;
} while ( pakFile ! = NULL ) ;
} else if ( search - > dir ) {
dir = search - > dir ;
netpath = FS_BuildOSPath ( dir - > path , dir - > gamedir , filename ) ;
temp = fopen ( netpath , " rb " ) ;
if ( ! temp ) {
continue ;
}
fclose ( temp ) ;
return qtrue ;
}
}
return qfalse ;
}
if ( ! filename ) {
Com_Error ( ERR_FATAL , " FS_FOpenFileRead: NULL 'filename' parameter passed \n " ) ;
}
Com_sprintf ( demoExt , sizeof ( demoExt ) , " .dm_%d " , PROTOCOL_VERSION ) ;
// qpaths are not supposed to have a leading slash
if ( filename [ 0 ] = = ' / ' | | filename [ 0 ] = = ' \\ ' ) {
filename + + ;
}
// make absolutely sure that it can't back up the path.
// The searchpaths do guarantee that something will always
// be prepended, so we don't need to worry about "c:" or "//limbo"
if ( strstr ( filename , " .. " ) | | strstr ( filename , " :: " ) ) {
* file = 0 ;
return - 1 ;
}
// make sure the q3key file is only readable by the quake3.exe at initialization
// any other time the key should only be accessed in memory using the provided functions
if ( com_fullyInitialized & & strstr ( filename , " q3key " ) ) {
* file = 0 ;
return - 1 ;
}
//
// search through the path, one element at a time
//
* file = FS_HandleForFile ( ) ;
fsh [ * file ] . handleFiles . unique = uniqueFILE ;
for ( search = fs_searchpaths ; search ; search = search - > next ) {
//
if ( search - > pack ) {
hash = FS_HashFileName ( filename , search - > pack - > hashSize ) ;
}
// is the element a pak file?
if ( search - > pack & & search - > pack - > hashTable [ hash ] ) {
// disregard if it doesn't match one of the allowed pure pak files
if ( ! FS_PakIsPure ( search - > pack ) ) {
continue ;
}
// look through all the pak file elements
pak = search - > pack ;
pakFile = pak - > hashTable [ hash ] ;
do {
// case and separator insensitive comparisons
if ( ! FS_FilenameCompare ( pakFile - > name , filename ) ) {
// found it!
// mark the pak as having been referenced and mark specifics on cgame and ui
// shaders, txt, arena files by themselves do not count as a reference as
// these are loaded from all pk3s
// from every pk3 file..
l = strlen ( filename ) ;
if ( ! ( pak - > referenced & FS_GENERAL_REF ) ) {
if ( Q_stricmp ( filename + l - 7 , " .shader " ) ! = 0 & &
Q_stricmp ( filename + l - 4 , " .txt " ) ! = 0 & &
Q_stricmp ( filename + l - 4 , " .cfg " ) ! = 0 & &
Q_stricmp ( filename + l - 7 , " .config " ) ! = 0 & &
strstr ( filename , " levelshots " ) = = NULL & &
Q_stricmp ( filename + l - 4 , " .bot " ) ! = 0 & &
Q_stricmp ( filename + l - 6 , " .arena " ) ! = 0 & &
Q_stricmp ( filename + l - 5 , " .menu " ) ! = 0 ) {
pak - > referenced | = FS_GENERAL_REF ;
}
}
// qagame.qvm - 13
// dTZT`X!di`
if ( ! ( pak - > referenced & FS_QAGAME_REF ) & & FS_ShiftedStrStr ( filename , " dTZT`X!di` " , 13 ) ) {
pak - > referenced | = FS_QAGAME_REF ;
}
// cgame.qvm - 7
// \`Zf^'jof
if ( ! ( pak - > referenced & FS_CGAME_REF ) & & FS_ShiftedStrStr ( filename , " \\ `Zf^'jof " , 7 ) ) {
pak - > referenced | = FS_CGAME_REF ;
}
// ui.qvm - 5
// pd)lqh
if ( ! ( pak - > referenced & FS_UI_REF ) & & FS_ShiftedStrStr ( filename , " pd)lqh " , 5 ) ) {
pak - > referenced | = FS_UI_REF ;
}
if ( uniqueFILE ) {
// open a new file on the pakfile
fsh [ * file ] . handleFiles . file . z = unzReOpen ( pak - > pakFilename , pak - > handle ) ;
if ( fsh [ * file ] . handleFiles . file . z = = NULL ) {
Com_Error ( ERR_FATAL , " Couldn't reopen %s " , pak - > pakFilename ) ;
}
} else {
fsh [ * file ] . handleFiles . file . z = pak - > handle ;
}
Q_strncpyz ( fsh [ * file ] . name , filename , sizeof ( fsh [ * file ] . name ) ) ;
fsh [ * file ] . zipFile = qtrue ;
zfi = ( unz_s * ) fsh [ * file ] . handleFiles . file . z ;
// in case the file was new
temp = zfi - > file ;
// set the file position in the zip file (also sets the current file info)
unzSetCurrentFileInfoPosition ( pak - > handle , pakFile - > pos ) ;
// copy the file info into the unzip structure
Com_Memcpy ( zfi , pak - > handle , sizeof ( unz_s ) ) ;
// we copy this back into the structure
zfi - > file = temp ;
// open the file in the zip
unzOpenCurrentFile ( fsh [ * file ] . handleFiles . file . z ) ;
fsh [ * file ] . zipFilePos = pakFile - > pos ;
if ( fs_debug - > integer ) {
Com_Printf ( " FS_FOpenFileRead: %s (found in '%s') \n " ,
filename , pak - > pakFilename ) ;
}
return zfi - > cur_file_info . uncompressed_size ;
}
pakFile = pakFile - > next ;
} while ( pakFile ! = NULL ) ;
} else if ( search - > dir ) {
// check a file in the directory tree
// if we are running restricted, the only files we
// will allow to come from the directory are .cfg files
l = strlen ( filename ) ;
// FIXME TTimo I'm not sure about the fs_numServerPaks test
// if you are using FS_ReadFile to find out if a file exists,
// this test can make the search fail although the file is in the directory
// I had the problem on https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=8
// turned out I used FS_FileExists instead
if ( fs_restrict - > integer | | fs_numServerPaks ) {
if ( Q_stricmp ( filename + l - 4 , " .cfg " ) // for config files
& & Q_stricmp ( filename + l - 5 , " .menu " ) // menu files
& & Q_stricmp ( filename + l - 5 , " .game " ) // menu files
& & Q_stricmp ( filename + l - strlen ( demoExt ) , demoExt ) // menu files
& & Q_stricmp ( filename + l - 4 , " .dat " ) ) { // for journal files
continue ;
}
}
dir = search - > dir ;
netpath = FS_BuildOSPath ( dir - > path , dir - > gamedir , filename ) ;
fsh [ * file ] . handleFiles . file . o = fopen ( netpath , " rb " ) ;
if ( ! fsh [ * file ] . handleFiles . file . o ) {
continue ;
}
if ( Q_stricmp ( filename + l - 4 , " .cfg " ) // for config files
& & Q_stricmp ( filename + l - 5 , " .menu " ) // menu files
& & Q_stricmp ( filename + l - 5 , " .game " ) // menu files
& & Q_stricmp ( filename + l - strlen ( demoExt ) , demoExt ) // menu files
& & Q_stricmp ( filename + l - 4 , " .dat " ) ) { // for journal files
fs_fakeChkSum = random ( ) ;
}
Q_strncpyz ( fsh [ * file ] . name , filename , sizeof ( fsh [ * file ] . name ) ) ;
fsh [ * file ] . zipFile = qfalse ;
if ( fs_debug - > integer ) {
Com_Printf ( " FS_FOpenFileRead: %s (found in '%s/%s') \n " , filename ,
dir - > path , dir - > gamedir ) ;
}
// if we are getting it from the cdpath, optionally copy it
// to the basepath
if ( fs_copyfiles - > integer & & ! Q_stricmp ( dir - > path , fs_cdpath - > string ) ) {
char * copypath ;
copypath = FS_BuildOSPath ( fs_basepath - > string , dir - > gamedir , filename ) ;
FS_CopyFile ( netpath , copypath ) ;
}
return FS_filelength ( * file ) ;
}
}
Com_DPrintf ( " Can't find %s \n " , filename ) ;
# ifdef FS_MISSING
if ( missingFiles ) {
fprintf ( missingFiles , " %s \n " , filename ) ;
}
# endif
* file = 0 ;
return - 1 ;
}
/*
= = = = = = = = = = = = = = = = =
FS_Read
Properly handles partial reads
= = = = = = = = = = = = = = = = =
*/
int FS_Read2 ( void * buffer , int len , fileHandle_t f ) {
if ( ! fs_searchpaths ) {
Com_Error ( ERR_FATAL , " Filesystem call made without initialization \n " ) ;
}
if ( ! f ) {
return 0 ;
}
if ( fsh [ f ] . streamed ) {
int r ;
fsh [ f ] . streamed = qfalse ;
r = Sys_StreamedRead ( buffer , len , 1 , f ) ;
fsh [ f ] . streamed = qtrue ;
return r ;
} else {
return FS_Read ( buffer , len , f ) ;
}
}
int FS_Read ( void * buffer , int len , fileHandle_t f ) {
int block , remaining ;
int read ;
byte * buf ;
int tries ;
if ( ! fs_searchpaths ) {
Com_Error ( ERR_FATAL , " Filesystem call made without initialization \n " ) ;
}
if ( ! f ) {
return 0 ;
}
buf = ( byte * ) buffer ;
fs_readCount + = len ;
if ( fsh [ f ] . zipFile = = qfalse ) {
remaining = len ;
tries = 0 ;
while ( remaining ) {
block = remaining ;
read = fread ( buf , 1 , block , fsh [ f ] . handleFiles . file . o ) ;
if ( read = = 0 ) {
// we might have been trying to read from a CD, which
// sometimes returns a 0 read on windows
if ( ! tries ) {
tries = 1 ;
} else {
return len - remaining ; //Com_Error (ERR_FATAL, "FS_Read: 0 bytes read");
}
}
if ( read = = - 1 ) {
Com_Error ( ERR_FATAL , " FS_Read: -1 bytes read " ) ;
}
remaining - = read ;
buf + = read ;
}
return len ;
} else {
return unzReadCurrentFile ( fsh [ f ] . handleFiles . file . z , buffer , len ) ;
}
}
/*
= = = = = = = = = = = = = = = = =
FS_Write
Properly handles partial writes
= = = = = = = = = = = = = = = = =
*/
int FS_Write ( const void * buffer , int len , fileHandle_t h ) {
int block , remaining ;
int written ;
byte * buf ;
int tries ;
FILE * f ;
if ( ! fs_searchpaths ) {
Com_Error ( ERR_FATAL , " Filesystem call made without initialization \n " ) ;
}
if ( ! h ) {
return 0 ;
}
f = FS_FileForHandle ( h ) ;
buf = ( byte * ) buffer ;
remaining = len ;
tries = 0 ;
while ( remaining ) {
block = remaining ;
written = fwrite ( buf , 1 , block , f ) ;
if ( written = = 0 ) {
if ( ! tries ) {
tries = 1 ;
} else {
Com_Printf ( " FS_Write: 0 bytes written \n " ) ;
return 0 ;
}
}
if ( written = = - 1 ) {
Com_Printf ( " FS_Write: -1 bytes written \n " ) ;
return 0 ;
}
remaining - = written ;
buf + = written ;
}
if ( fsh [ h ] . handleSync ) {
fflush ( f ) ;
}
return len ;
}
void QDECL FS_Printf ( fileHandle_t h , const char * fmt , . . . ) {
va_list argptr ;
char msg [ MAXPRINTMSG ] ;
va_start ( argptr , fmt ) ;
Q_vsnprintf ( msg , sizeof ( msg ) , fmt , argptr ) ;
va_end ( argptr ) ;
FS_Write ( msg , strlen ( msg ) , h ) ;
}
/*
= = = = = = = = = = = = = = = = =
FS_Seek
= = = = = = = = = = = = = = = = =
*/
int FS_Seek ( fileHandle_t f , long offset , int origin ) {
int _origin ;
char foo [ 65536 ] ;
if ( ! fs_searchpaths ) {
Com_Error ( ERR_FATAL , " Filesystem call made without initialization \n " ) ;
return - 1 ;
}
if ( fsh [ f ] . streamed ) {
fsh [ f ] . streamed = qfalse ;
Sys_StreamSeek ( f , offset , origin ) ;
fsh [ f ] . streamed = qtrue ;
}
if ( fsh [ f ] . zipFile = = qtrue ) {
if ( offset = = 0 & & origin = = FS_SEEK_SET ) {
// set the file position in the zip file (also sets the current file info)
unzSetCurrentFileInfoPosition ( fsh [ f ] . handleFiles . file . z , fsh [ f ] . zipFilePos ) ;
return unzOpenCurrentFile ( fsh [ f ] . handleFiles . file . z ) ;
} else if ( offset < 65536 ) {
// set the file position in the zip file (also sets the current file info)
unzSetCurrentFileInfoPosition ( fsh [ f ] . handleFiles . file . z , fsh [ f ] . zipFilePos ) ;
unzOpenCurrentFile ( fsh [ f ] . handleFiles . file . z ) ;
return FS_Read ( foo , offset , f ) ;
} else {
Com_Error ( ERR_FATAL , " ZIP FILE FSEEK NOT YET IMPLEMENTED \n " ) ;
return - 1 ;
}
} else {
FILE * file ;
file = FS_FileForHandle ( f ) ;
switch ( origin ) {
case FS_SEEK_CUR :
_origin = SEEK_CUR ;
break ;
case FS_SEEK_END :
_origin = SEEK_END ;
break ;
case FS_SEEK_SET :
_origin = SEEK_SET ;
break ;
default :
_origin = SEEK_CUR ;
Com_Error ( ERR_FATAL , " Bad origin in FS_Seek \n " ) ;
break ;
}
return fseek ( file , offset , _origin ) ;
}
}
/*
= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
CONVENIENCE FUNCTIONS FOR ENTIRE FILES
= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
*/
int FS_FileIsInPAK ( const char * filename , int * pChecksum ) {
searchpath_t * search ;
pack_t * pak ;
fileInPack_t * pakFile ;
long hash = 0 ;
if ( ! fs_searchpaths ) {
Com_Error ( ERR_FATAL , " Filesystem call made without initialization \n " ) ;
}
if ( ! filename ) {
Com_Error ( ERR_FATAL , " FS_FOpenFileRead: NULL 'filename' parameter passed \n " ) ;
}
// qpaths are not supposed to have a leading slash
if ( filename [ 0 ] = = ' / ' | | filename [ 0 ] = = ' \\ ' ) {
filename + + ;
}
// make absolutely sure that it can't back up the path.
// The searchpaths do guarantee that something will always
// be prepended, so we don't need to worry about "c:" or "//limbo"
if ( strstr ( filename , " .. " ) | | strstr ( filename , " :: " ) ) {
return - 1 ;
}
//
// search through the path, one element at a time
//
for ( search = fs_searchpaths ; search ; search = search - > next ) {
//
if ( search - > pack ) {
hash = FS_HashFileName ( filename , search - > pack - > hashSize ) ;
}
// is the element a pak file?
if ( search - > pack & & search - > pack - > hashTable [ hash ] ) {
// disregard if it doesn't match one of the allowed pure pak files
if ( ! FS_PakIsPure ( search - > pack ) ) {
continue ;
}
// look through all the pak file elements
pak = search - > pack ;
pakFile = pak - > hashTable [ hash ] ;
do {
// case and separator insensitive comparisons
if ( ! FS_FilenameCompare ( pakFile - > name , filename ) ) {
if ( pChecksum ) {
* pChecksum = pak - > pure_checksum ;
}
return 1 ;
}
pakFile = pakFile - > next ;
} while ( pakFile ! = NULL ) ;
}
}
return - 1 ;
}
/*
= = = = = = = = = = = =
FS_ReadFile
Filename are relative to the quake search path
a null buffer will just return the file length without loading
= = = = = = = = = = = =
*/
int FS_ReadFile ( const char * qpath , void * * buffer ) {
fileHandle_t h ;
byte * buf ;
qboolean isConfig ;
int len ;
if ( ! fs_searchpaths ) {
Com_Error ( ERR_FATAL , " Filesystem call made without initialization \n " ) ;
}
if ( ! qpath | | ! qpath [ 0 ] ) {
Com_Error ( ERR_FATAL , " FS_ReadFile with empty name \n " ) ;
}
buf = NULL ; // quiet compiler warning
// if this is a .cfg file and we are playing back a journal, read
// it from the journal file
if ( strstr ( qpath , " .cfg " ) ) {
isConfig = qtrue ;
if ( com_journal & & com_journal - > integer = = 2 ) {
int r ;
Com_DPrintf ( " Loading %s from journal file. \n " , qpath ) ;
r = FS_Read ( & len , sizeof ( len ) , com_journalDataFile ) ;
if ( r ! = sizeof ( len ) ) {
if ( buffer ! = NULL ) * buffer = NULL ;
return - 1 ;
}
// if the file didn't exist when the journal was created
if ( ! len ) {
if ( buffer = = NULL ) {
return 1 ; // hack for old journal files
}
* buffer = NULL ;
return - 1 ;
}
if ( buffer = = NULL ) {
return len ;
}
buf = Hunk_AllocateTempMemory ( len + 1 ) ;
* buffer = buf ;
r = FS_Read ( buf , len , com_journalDataFile ) ;
if ( r ! = len ) {
Com_Error ( ERR_FATAL , " Read from journalDataFile failed " ) ;
}
fs_loadCount + + ;
fs_loadStack + + ;
// guarantee that it will have a trailing 0 for string operations
buf [ len ] = 0 ;
return len ;
}
} else {
isConfig = qfalse ;
}
// look for it in the filesystem or pack files
len = FS_FOpenFileRead ( qpath , & h , qfalse ) ;
if ( h = = 0 ) {
if ( buffer ) {
* buffer = NULL ;
}
// if we are journalling and it is a config file, write a zero to the journal file
if ( isConfig & & com_journal & & com_journal - > integer = = 1 ) {
Com_DPrintf ( " Writing zero for %s to journal file. \n " , qpath ) ;
len = 0 ;
FS_Write ( & len , sizeof ( len ) , com_journalDataFile ) ;
FS_Flush ( com_journalDataFile ) ;
}
return - 1 ;
}
if ( ! buffer ) {
if ( isConfig & & com_journal & & com_journal - > integer = = 1 ) {
Com_DPrintf ( " Writing len for %s to journal file. \n " , qpath ) ;
FS_Write ( & len , sizeof ( len ) , com_journalDataFile ) ;
FS_Flush ( com_journalDataFile ) ;
}
FS_FCloseFile ( h ) ;
return len ;
}
fs_loadCount + + ;
fs_loadStack + + ;
buf = Hunk_AllocateTempMemory ( len + 1 ) ;
* buffer = buf ;
FS_Read ( buf , len , h ) ;
// guarantee that it will have a trailing 0 for string operations
buf [ len ] = 0 ;
FS_FCloseFile ( h ) ;
// if we are journalling and it is a config file, write it to the journal file
if ( isConfig & & com_journal & & com_journal - > integer = = 1 ) {
Com_DPrintf ( " Writing %s to journal file. \n " , qpath ) ;
FS_Write ( & len , sizeof ( len ) , com_journalDataFile ) ;
FS_Write ( buf , len , com_journalDataFile ) ;
FS_Flush ( com_journalDataFile ) ;
}
return len ;
}
/*
= = = = = = = = = = = = =
FS_FreeFile
= = = = = = = = = = = = =
*/
void FS_FreeFile ( void * buffer ) {
if ( ! fs_searchpaths ) {
Com_Error ( ERR_FATAL , " Filesystem call made without initialization \n " ) ;
}
if ( ! buffer ) {
Com_Error ( ERR_FATAL , " FS_FreeFile( NULL ) " ) ;
}
fs_loadStack - - ;
Hunk_FreeTempMemory ( buffer ) ;
// if all of our temp files are free, clear all of our space
if ( fs_loadStack = = 0 ) {
Hunk_ClearTempMemory ( ) ;
}
}
/*
= = = = = = = = = = = =
FS_WriteFile
Filename are reletive to the quake search path
= = = = = = = = = = = =
*/
void FS_WriteFile ( const char * qpath , const void * buffer , int size ) {
fileHandle_t f ;
if ( ! fs_searchpaths ) {
Com_Error ( ERR_FATAL , " Filesystem call made without initialization \n " ) ;
}
if ( ! qpath | | ! buffer ) {
Com_Error ( ERR_FATAL , " FS_WriteFile: NULL parameter " ) ;
}
f = FS_FOpenFileWrite ( qpath ) ;
if ( ! f ) {
Com_Printf ( " Failed to open %s \n " , qpath ) ;
return ;
}
FS_Write ( buffer , size , f ) ;
FS_FCloseFile ( f ) ;
}
/*
= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
ZIP FILE LOADING
= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
*/
/*
= = = = = = = = = = = = = = = = =
FS_LoadZipFile
Creates a new pak_t in the search chain for the contents
of a zip file .
= = = = = = = = = = = = = = = = =
*/
static pack_t * FS_LoadZipFile ( char * zipfile , const char * basename )
{
fileInPack_t * buildBuffer ;
pack_t * pack ;
unzFile uf ;
int err ;
unz_global_info gi ;
char filename_inzip [ MAX_ZPATH ] ;
unz_file_info file_info ;
int i , len ;
long hash ;
int fs_numHeaderLongs ;
int * fs_headerLongs ;
char * namePtr ;
fs_numHeaderLongs = 0 ;
uf = unzOpen ( zipfile ) ;
err = unzGetGlobalInfo ( uf , & gi ) ;
if ( err ! = UNZ_OK )
return NULL ;
fs_packFiles + = gi . number_entry ;
len = 0 ;
unzGoToFirstFile ( uf ) ;
for ( i = 0 ; i < gi . number_entry ; i + + )
{
err = unzGetCurrentFileInfo ( uf , & file_info , filename_inzip , sizeof ( filename_inzip ) , NULL , 0 , NULL , 0 ) ;
if ( err ! = UNZ_OK ) {
break ;
}
len + = strlen ( filename_inzip ) + 1 ;
unzGoToNextFile ( uf ) ;
}
buildBuffer = Z_Malloc ( ( gi . number_entry * sizeof ( fileInPack_t ) ) + len ) ;
namePtr = ( ( char * ) buildBuffer ) + gi . number_entry * sizeof ( fileInPack_t ) ;
fs_headerLongs = Z_Malloc ( gi . number_entry * sizeof ( int ) ) ;
// get the hash table size from the number of files in the zip
// because lots of custom pk3 files have less than 32 or 64 files
for ( i = 1 ; i < = MAX_FILEHASH_SIZE ; i < < = 1 ) {
if ( i > gi . number_entry ) {
break ;
}
}
pack = Z_Malloc ( sizeof ( pack_t ) + i * sizeof ( fileInPack_t * ) ) ;
pack - > hashSize = i ;
pack - > hashTable = ( fileInPack_t * * ) ( ( ( char * ) pack ) + sizeof ( pack_t ) ) ;
for ( i = 0 ; i < pack - > hashSize ; i + + ) {
pack - > hashTable [ i ] = NULL ;
}
Q_strncpyz ( pack - > pakFilename , zipfile , sizeof ( pack - > pakFilename ) ) ;
Q_strncpyz ( pack - > pakBasename , basename , sizeof ( pack - > pakBasename ) ) ;
// strip .pk3 if needed
if ( strlen ( pack - > pakBasename ) > 4 & & ! Q_stricmp ( pack - > pakBasename + strlen ( pack - > pakBasename ) - 4 , " .pk3 " ) ) {
pack - > pakBasename [ strlen ( pack - > pakBasename ) - 4 ] = 0 ;
}
pack - > handle = uf ;
pack - > numfiles = gi . number_entry ;
unzGoToFirstFile ( uf ) ;
for ( i = 0 ; i < gi . number_entry ; i + + )
{
err = unzGetCurrentFileInfo ( uf , & file_info , filename_inzip , sizeof ( filename_inzip ) , NULL , 0 , NULL , 0 ) ;
if ( err ! = UNZ_OK ) {
break ;
}
if ( file_info . uncompressed_size > 0 ) {
fs_headerLongs [ fs_numHeaderLongs + + ] = LittleLong ( file_info . crc ) ;
}
Q_strlwr ( filename_inzip ) ;
hash = FS_HashFileName ( filename_inzip , pack - > hashSize ) ;
buildBuffer [ i ] . name = namePtr ;
strcpy ( buildBuffer [ i ] . name , filename_inzip ) ;
namePtr + = strlen ( filename_inzip ) + 1 ;
// store the file position in the zip
unzGetCurrentFileInfoPosition ( uf , & buildBuffer [ i ] . pos ) ;
//
buildBuffer [ i ] . next = pack - > hashTable [ hash ] ;
pack - > hashTable [ hash ] = & buildBuffer [ i ] ;
unzGoToNextFile ( uf ) ;
}
pack - > checksum = Com_BlockChecksum ( fs_headerLongs , 4 * fs_numHeaderLongs ) ;
pack - > pure_checksum = Com_BlockChecksumKey ( fs_headerLongs , 4 * fs_numHeaderLongs , LittleLong ( fs_checksumFeed ) ) ;
pack - > checksum = LittleLong ( pack - > checksum ) ;
pack - > pure_checksum = LittleLong ( pack - > pure_checksum ) ;
Z_Free ( fs_headerLongs ) ;
pack - > buildBuffer = buildBuffer ;
return pack ;
}
/*
= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
DIRECTORY SCANNING FUNCTIONS
= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
*/
# define MAX_FOUND_FILES 0x1000
static int FS_ReturnPath ( const char * zname , char * zpath , int * depth ) {
int len , at , newdep ;
newdep = 0 ;
zpath [ 0 ] = 0 ;
len = 0 ;
at = 0 ;
while ( zname [ at ] ! = 0 )
{
if ( zname [ at ] = = ' / ' | | zname [ at ] = = ' \\ ' ) {
len = at ;
newdep + + ;
}
at + + ;
}
strcpy ( zpath , zname ) ;
zpath [ len ] = 0 ;
* depth = newdep ;
return len ;
}
/*
= = = = = = = = = = = = = = = = = =
FS_AddFileToList
= = = = = = = = = = = = = = = = = =
*/
static int FS_AddFileToList ( char * name , char * list [ MAX_FOUND_FILES ] , int nfiles ) {
int i ;
if ( nfiles = = MAX_FOUND_FILES - 1 ) {
return nfiles ;
}
for ( i = 0 ; i < nfiles ; i + + ) {
if ( ! Q_stricmp ( name , list [ i ] ) ) {
return nfiles ; // allready in list
}
}
list [ nfiles ] = CopyString ( name ) ;
nfiles + + ;
return nfiles ;
}
/*
= = = = = = = = = = = = = = =
FS_ListFilteredFiles
Returns a uniqued list of files that match the given criteria
from all search paths
= = = = = = = = = = = = = = =
*/
char * * FS_ListFilteredFiles ( const char * path , const char * extension , char * filter , int * numfiles ) {
int nfiles ;
char * * listCopy ;
char * list [ MAX_FOUND_FILES ] ;
searchpath_t * search ;
int i ;
int pathLength ;
int extensionLength ;
int length , pathDepth , temp ;
pack_t * pak ;
fileInPack_t * buildBuffer ;
char zpath [ MAX_ZPATH ] ;
if ( ! fs_searchpaths ) {
Com_Error ( ERR_FATAL , " Filesystem call made without initialization \n " ) ;
}
if ( ! path ) {
* numfiles = 0 ;
return NULL ;
}
if ( ! extension ) {
extension = " " ;
}
pathLength = strlen ( path ) ;
if ( path [ pathLength - 1 ] = = ' \\ ' | | path [ pathLength - 1 ] = = ' / ' ) {
pathLength - - ;
}
extensionLength = strlen ( extension ) ;
nfiles = 0 ;
FS_ReturnPath ( path , zpath , & pathDepth ) ;
//
// search through the path, one element at a time, adding to list
//
for ( search = fs_searchpaths ; search ; search = search - > next ) {
// is the element a pak file?
if ( search - > pack ) {
//ZOID: If we are pure, don't search for files on paks that
// aren't on the pure list
if ( ! FS_PakIsPure ( search - > pack ) ) {
continue ;
}
// look through all the pak file elements
pak = search - > pack ;
buildBuffer = pak - > buildBuffer ;
for ( i = 0 ; i < pak - > numfiles ; i + + ) {
char * name ;
int zpathLen , depth ;
// check for directory match
name = buildBuffer [ i ] . name ;
//
if ( filter ) {
// case insensitive
if ( ! Com_FilterPath ( filter , name , qfalse ) )
continue ;
// unique the match
nfiles = FS_AddFileToList ( name , list , nfiles ) ;
}
else {
zpathLen = FS_ReturnPath ( name , zpath , & depth ) ;
if ( ( depth - pathDepth ) > 2 | | pathLength > zpathLen | | Q_stricmpn ( name , path , pathLength ) ) {
continue ;
}
// check for extension match
length = strlen ( name ) ;
if ( length < extensionLength ) {
continue ;
}
if ( Q_stricmp ( name + length - extensionLength , extension ) ) {
continue ;
}
// unique the match
temp = pathLength ;
if ( pathLength ) {
temp + + ; // include the '/'
}
nfiles = FS_AddFileToList ( name + temp , list , nfiles ) ;
}
}
} else if ( search - > dir ) { // scan for files in the filesystem
char * netpath ;
int numSysFiles ;
char * * sysFiles ;
char * name ;
// don't scan directories for files if we are pure or restricted
if ( fs_restrict - > integer | | fs_numServerPaks ) {
continue ;
} else {
netpath = FS_BuildOSPath ( search - > dir - > path , search - > dir - > gamedir , path ) ;
sysFiles = Sys_ListFiles ( netpath , extension , filter , & numSysFiles , qfalse ) ;
for ( i = 0 ; i < numSysFiles ; i + + ) {
// unique the match
name = sysFiles [ i ] ;
nfiles = FS_AddFileToList ( name , list , nfiles ) ;
}
Sys_FreeFileList ( sysFiles ) ;
}
}
}
// return a copy of the list
* numfiles = nfiles ;
if ( ! nfiles ) {
return NULL ;
}
listCopy = Z_Malloc ( ( nfiles + 1 ) * sizeof ( * listCopy ) ) ;
for ( i = 0 ; i < nfiles ; i + + ) {
listCopy [ i ] = list [ i ] ;
}
listCopy [ i ] = NULL ;
return listCopy ;
}
/*
= = = = = = = = = = = = = = = = =
FS_ListFiles
= = = = = = = = = = = = = = = = =
*/
char * * FS_ListFiles ( const char * path , const char * extension , int * numfiles ) {
return FS_ListFilteredFiles ( path , extension , NULL , numfiles ) ;
}
/*
= = = = = = = = = = = = = = = = =
FS_FreeFileList
= = = = = = = = = = = = = = = = =
*/
void FS_FreeFileList ( char * * list ) {
int i ;
if ( ! fs_searchpaths ) {
Com_Error ( ERR_FATAL , " Filesystem call made without initialization \n " ) ;
}
if ( ! list ) {
return ;
}
for ( i = 0 ; list [ i ] ; i + + ) {
Z_Free ( list [ i ] ) ;
}
Z_Free ( list ) ;
}
/*
= = = = = = = = = = = = = = = =
FS_GetFileList
= = = = = = = = = = = = = = = =
*/
int FS_GetFileList ( const char * path , const char * extension , char * listbuf , int bufsize ) {
int nFiles , i , nTotal , nLen ;
char * * pFiles = NULL ;
* listbuf = 0 ;
nFiles = 0 ;
nTotal = 0 ;
if ( Q_stricmp ( path , " $modlist " ) = = 0 ) {
return FS_GetModList ( listbuf , bufsize ) ;
}
pFiles = FS_ListFiles ( path , extension , & nFiles ) ;
for ( i = 0 ; i < nFiles ; i + + ) {
nLen = strlen ( pFiles [ i ] ) + 1 ;
if ( nTotal + nLen + 1 < bufsize ) {
strcpy ( listbuf , pFiles [ i ] ) ;
listbuf + = nLen ;
nTotal + = nLen ;
}
else {
nFiles = i ;
break ;
}
}
FS_FreeFileList ( pFiles ) ;
return nFiles ;
}
/*
= = = = = = = = = = = = = = = = = = = = = = =
Sys_ConcatenateFileLists
mkv : Naive implementation . Concatenates three lists into a
new list , and frees the old lists from the heap .
bk001129 - from cvs1 .17 ( mkv )
FIXME TTimo those two should move to common . c next to Sys_ListFiles
= = = = = = = = = = = = = = = = = = = = = = =
*/
static unsigned int Sys_CountFileList ( char * * list )
{
int i = 0 ;
if ( list )
{
while ( * list )
{
list + + ;
i + + ;
}
}
return i ;
}
static char * * Sys_ConcatenateFileLists ( char * * list0 , char * * list1 , char * * list2 )
{
int totalLength = 0 ;
char * * cat = NULL , * * dst , * * src ;
totalLength + = Sys_CountFileList ( list0 ) ;
totalLength + = Sys_CountFileList ( list1 ) ;
totalLength + = Sys_CountFileList ( list2 ) ;
/* Create new list. */
dst = cat = Z_Malloc ( ( totalLength + 1 ) * sizeof ( char * ) ) ;
/* Copy over lists. */
if ( list0 )
{
for ( src = list0 ; * src ; src + + , dst + + )
* dst = * src ;
}
if ( list1 )
{
for ( src = list1 ; * src ; src + + , dst + + )
* dst = * src ;
}
if ( list2 )
{
for ( src = list2 ; * src ; src + + , dst + + )
* dst = * src ;
}
// Terminate the list
* dst = NULL ;
// Free our old lists.
// NOTE: not freeing their content, it's been merged in dst and still being used
if ( list0 ) Z_Free ( list0 ) ;
if ( list1 ) Z_Free ( list1 ) ;
if ( list2 ) Z_Free ( list2 ) ;
return cat ;
}
/*
= = = = = = = = = = = = = = = =
FS_GetModList
Returns a list of mod directory names
A mod directory is a peer to baseq3 with a pk3 in it
The directories are searched in base path , cd path and home path
= = = = = = = = = = = = = = = =
*/
int FS_GetModList ( char * listbuf , int bufsize ) {
int nMods , i , j , nTotal , nLen , nPaks , nPotential , nDescLen ;
char * * pFiles = NULL ;
char * * pPaks = NULL ;
char * name , * path ;
char descPath [ MAX_OSPATH ] ;
fileHandle_t descHandle ;
int dummy ;
char * * pFiles0 = NULL ;
char * * pFiles1 = NULL ;
char * * pFiles2 = NULL ;
qboolean bDrop = qfalse ;
* listbuf = 0 ;
nMods = nPotential = nTotal = 0 ;
pFiles0 = Sys_ListFiles ( fs_homepath - > string , NULL , NULL , & dummy , qtrue ) ;
pFiles1 = Sys_ListFiles ( fs_basepath - > string , NULL , NULL , & dummy , qtrue ) ;
pFiles2 = Sys_ListFiles ( fs_cdpath - > string , NULL , NULL , & dummy , qtrue ) ;
// we searched for mods in the three paths
// it is likely that we have duplicate names now, which we will cleanup below
pFiles = Sys_ConcatenateFileLists ( pFiles0 , pFiles1 , pFiles2 ) ;
nPotential = Sys_CountFileList ( pFiles ) ;
for ( i = 0 ; i < nPotential ; i + + ) {
name = pFiles [ i ] ;
// NOTE: cleaner would involve more changes
// ignore duplicate mod directories
if ( i ! = 0 ) {
bDrop = qfalse ;
for ( j = 0 ; j < i ; j + + )
{
if ( Q_stricmp ( pFiles [ j ] , name ) = = 0 ) {
// this one can be dropped
bDrop = qtrue ;
break ;
}
}
}
if ( bDrop ) {
continue ;
}
// we drop "baseq3" "." and ".."
if ( Q_stricmp ( name , " baseq3 " ) & & Q_stricmpn ( name , " . " , 1 ) ) {
// now we need to find some .pk3 files to validate the mod
// NOTE TTimo: (actually I'm not sure why .. what if it's a mod under developement with no .pk3?)
// we didn't keep the information when we merged the directory names, as to what OS Path it was found under
// so it could be in base path, cd path or home path
// we will try each three of them here (yes, it's a bit messy)
path = FS_BuildOSPath ( fs_basepath - > string , name , " " ) ;
nPaks = 0 ;
pPaks = Sys_ListFiles ( path , " .pk3 " , NULL , & nPaks , qfalse ) ;
Sys_FreeFileList ( pPaks ) ; // we only use Sys_ListFiles to check wether .pk3 files are present
/* Try on cd path */
if ( nPaks < = 0 ) {
path = FS_BuildOSPath ( fs_cdpath - > string , name , " " ) ;
nPaks = 0 ;
pPaks = Sys_ListFiles ( path , " .pk3 " , NULL , & nPaks , qfalse ) ;
Sys_FreeFileList ( pPaks ) ;
}
/* try on home path */
if ( nPaks < = 0 )
{
path = FS_BuildOSPath ( fs_homepath - > string , name , " " ) ;
nPaks = 0 ;
pPaks = Sys_ListFiles ( path , " .pk3 " , NULL , & nPaks , qfalse ) ;
Sys_FreeFileList ( pPaks ) ;
}
if ( nPaks > 0 ) {
nLen = strlen ( name ) + 1 ;
// nLen is the length of the mod path
// we need to see if there is a description available
descPath [ 0 ] = ' \0 ' ;
strcpy ( descPath , name ) ;
strcat ( descPath , " /description.txt " ) ;
nDescLen = FS_SV_FOpenFileRead ( descPath , & descHandle ) ;
if ( nDescLen > 0 & & descHandle ) {
FILE * file ;
file = FS_FileForHandle ( descHandle ) ;
Com_Memset ( descPath , 0 , sizeof ( descPath ) ) ;
nDescLen = fread ( descPath , 1 , 48 , file ) ;
if ( nDescLen > = 0 ) {
descPath [ nDescLen ] = ' \0 ' ;
}
FS_FCloseFile ( descHandle ) ;
} else {
strcpy ( descPath , name ) ;
}
nDescLen = strlen ( descPath ) + 1 ;
if ( nTotal + nLen + 1 + nDescLen + 1 < bufsize ) {
strcpy ( listbuf , name ) ;
listbuf + = nLen ;
strcpy ( listbuf , descPath ) ;
listbuf + = nDescLen ;
nTotal + = nLen + nDescLen ;
nMods + + ;
}
else {
break ;
}
}
}
}
Sys_FreeFileList ( pFiles ) ;
return nMods ;
}
//============================================================================
/*
= = = = = = = = = = = = = = = =
FS_Dir_f
= = = = = = = = = = = = = = = =
*/
void FS_Dir_f ( void ) {
char * path ;
char * extension ;
char * * dirnames ;
int ndirs ;
int i ;
if ( Cmd_Argc ( ) < 2 | | Cmd_Argc ( ) > 3 ) {
Com_Printf ( " usage: dir <directory> [extension] \n " ) ;
return ;
}
if ( Cmd_Argc ( ) = = 2 ) {
path = Cmd_Argv ( 1 ) ;
extension = " " ;
} else {
path = Cmd_Argv ( 1 ) ;
extension = Cmd_Argv ( 2 ) ;
}
Com_Printf ( " Directory of %s %s \n " , path , extension ) ;
Com_Printf ( " --------------- \n " ) ;
dirnames = FS_ListFiles ( path , extension , & ndirs ) ;
for ( i = 0 ; i < ndirs ; i + + ) {
Com_Printf ( " %s \n " , dirnames [ i ] ) ;
}
FS_FreeFileList ( dirnames ) ;
}
/*
= = = = = = = = = = =
FS_ConvertPath
= = = = = = = = = = =
*/
void FS_ConvertPath ( char * s ) {
while ( * s ) {
if ( * s = = ' \\ ' | | * s = = ' : ' ) {
* s = ' / ' ;
}
s + + ;
}
}
/*
= = = = = = = = = = =
FS_PathCmp
Ignore case and seprator char distinctions
= = = = = = = = = = =
*/
int FS_PathCmp ( const char * s1 , const char * s2 ) {
int c1 , c2 ;
do {
c1 = * s1 + + ;
c2 = * s2 + + ;
if ( c1 > = ' a ' & & c1 < = ' z ' ) {
c1 - = ( ' a ' - ' A ' ) ;
}
if ( c2 > = ' a ' & & c2 < = ' z ' ) {
c2 - = ( ' a ' - ' A ' ) ;
}
if ( c1 = = ' \\ ' | | c1 = = ' : ' ) {
c1 = ' / ' ;
}
if ( c2 = = ' \\ ' | | c2 = = ' : ' ) {
c2 = ' / ' ;
}
if ( c1 < c2 ) {
return - 1 ; // strings not equal
}
if ( c1 > c2 ) {
return 1 ;
}
} while ( c1 ) ;
return 0 ; // strings are equal
}
/*
= = = = = = = = = = = = = = = =
FS_SortFileList
= = = = = = = = = = = = = = = =
*/
void FS_SortFileList ( char * * filelist , int numfiles ) {
int i , j , k , numsortedfiles ;
char * * sortedlist ;
sortedlist = Z_Malloc ( ( numfiles + 1 ) * sizeof ( * sortedlist ) ) ;
sortedlist [ 0 ] = NULL ;
numsortedfiles = 0 ;
for ( i = 0 ; i < numfiles ; i + + ) {
for ( j = 0 ; j < numsortedfiles ; j + + ) {
if ( FS_PathCmp ( filelist [ i ] , sortedlist [ j ] ) < 0 ) {
break ;
}
}
for ( k = numsortedfiles ; k > j ; k - - ) {
sortedlist [ k ] = sortedlist [ k - 1 ] ;
}
sortedlist [ j ] = filelist [ i ] ;
numsortedfiles + + ;
}
Com_Memcpy ( filelist , sortedlist , numfiles * sizeof ( * filelist ) ) ;
Z_Free ( sortedlist ) ;
}
/*
= = = = = = = = = = = = = = = =
FS_NewDir_f
= = = = = = = = = = = = = = = =
*/
void FS_NewDir_f ( void ) {
char * filter ;
char * * dirnames ;
int ndirs ;
int i ;
if ( Cmd_Argc ( ) < 2 ) {
Com_Printf ( " usage: fdir <filter> \n " ) ;
Com_Printf ( " example: fdir *q3dm*.bsp \n " ) ;
return ;
}
filter = Cmd_Argv ( 1 ) ;
Com_Printf ( " --------------- \n " ) ;
dirnames = FS_ListFilteredFiles ( " " , " " , filter , & ndirs ) ;
FS_SortFileList ( dirnames , ndirs ) ;
for ( i = 0 ; i < ndirs ; i + + ) {
FS_ConvertPath ( dirnames [ i ] ) ;
Com_Printf ( " %s \n " , dirnames [ i ] ) ;
}
Com_Printf ( " %d files listed \n " , ndirs ) ;
FS_FreeFileList ( dirnames ) ;
}
/*
= = = = = = = = = = = =
FS_Path_f
= = = = = = = = = = = =
*/
void FS_Path_f ( void ) {
searchpath_t * s ;
int i ;
Com_Printf ( " Current search path: \n " ) ;
for ( s = fs_searchpaths ; s ; s = s - > next ) {
if ( s - > pack ) {
Com_Printf ( " %s (%i files) \n " , s - > pack - > pakFilename , s - > pack - > numfiles ) ;
if ( fs_numServerPaks ) {
if ( ! FS_PakIsPure ( s - > pack ) ) {
Com_Printf ( " not on the pure list \n " ) ;
} else {
Com_Printf ( " on the pure list \n " ) ;
}
}
} else {
Com_Printf ( " %s/%s \n " , s - > dir - > path , s - > dir - > gamedir ) ;
}
}
Com_Printf ( " \n " ) ;
for ( i = 1 ; i < MAX_FILE_HANDLES ; i + + ) {
if ( fsh [ i ] . handleFiles . file . o ) {
Com_Printf ( " handle %i: %s \n " , i , fsh [ i ] . name ) ;
}
}
}
/*
= = = = = = = = = = = =
FS_TouchFile_f
The only purpose of this function is to allow game script files to copy
arbitrary files furing an " fs_copyfiles 1 " run .
= = = = = = = = = = = =
*/
void FS_TouchFile_f ( void ) {
fileHandle_t f ;
if ( Cmd_Argc ( ) ! = 2 ) {
Com_Printf ( " Usage: touchFile <file> \n " ) ;
return ;
}
FS_FOpenFileRead ( Cmd_Argv ( 1 ) , & f , qfalse ) ;
if ( f ) {
FS_FCloseFile ( f ) ;
}
}
//===========================================================================
static int QDECL paksort ( const void * a , const void * b ) {
char * aa , * bb ;
aa = * ( char * * ) a ;
bb = * ( char * * ) b ;
return FS_PathCmp ( aa , bb ) ;
}
/*
= = = = = = = = = = = = = = = =
FS_AddGameDirectory
Sets fs_gamedir , adds the directory to the head of the path ,
then loads the zip headers
= = = = = = = = = = = = = = = =
*/
# define MAX_PAKFILES 1024
static void FS_AddGameDirectory ( const char * path , const char * dir ) {
searchpath_t * sp ;
int i ;
searchpath_t * search ;
pack_t * pak ;
char * pakfile ;
int numfiles ;
char * * pakfiles ;
char * sorted [ MAX_PAKFILES ] ;
// this fixes the case where fs_basepath is the same as fs_cdpath
// which happens on full installs
for ( sp = fs_searchpaths ; sp ; sp = sp - > next ) {
if ( sp - > dir & & ! Q_stricmp ( sp - > dir - > path , path ) & & ! Q_stricmp ( sp - > dir - > gamedir , dir ) ) {
return ; // we've already got this one
}
}
Q_strncpyz ( fs_gamedir , dir , sizeof ( fs_gamedir ) ) ;
//
// add the directory to the search path
//
search = Z_Malloc ( sizeof ( searchpath_t ) ) ;
search - > dir = Z_Malloc ( sizeof ( * search - > dir ) ) ;
Q_strncpyz ( search - > dir - > path , path , sizeof ( search - > dir - > path ) ) ;
Q_strncpyz ( search - > dir - > gamedir , dir , sizeof ( search - > dir - > gamedir ) ) ;
search - > next = fs_searchpaths ;
fs_searchpaths = search ;
// find all pak files in this directory
pakfile = FS_BuildOSPath ( path , dir , " " ) ;
pakfile [ strlen ( pakfile ) - 1 ] = 0 ; // strip the trailing slash
pakfiles = Sys_ListFiles ( pakfile , " .pk3 " , NULL , & numfiles , qfalse ) ;
// sort them so that later alphabetic matches override
// earlier ones. This makes pak1.pk3 override pak0.pk3
if ( numfiles > MAX_PAKFILES ) {
numfiles = MAX_PAKFILES ;
}
for ( i = 0 ; i < numfiles ; i + + ) {
sorted [ i ] = pakfiles [ i ] ;
}
qsort ( sorted , numfiles , 4 , paksort ) ;
for ( i = 0 ; i < numfiles ; i + + ) {
pakfile = FS_BuildOSPath ( path , dir , sorted [ i ] ) ;
if ( ( pak = FS_LoadZipFile ( pakfile , sorted [ i ] ) ) = = 0 )
continue ;
// store the game name for downloading
strcpy ( pak - > pakGamename , dir ) ;
search = Z_Malloc ( sizeof ( searchpath_t ) ) ;
search - > pack = pak ;
search - > next = fs_searchpaths ;
fs_searchpaths = search ;
}
// done
Sys_FreeFileList ( pakfiles ) ;
}
/*
= = = = = = = = = = = = = = = =
FS_idPak
= = = = = = = = = = = = = = = =
*/
qboolean FS_idPak ( char * pak , char * base ) {
int i ;
for ( i = 0 ; i < NUM_ID_PAKS ; i + + ) {
if ( ! FS_FilenameCompare ( pak , va ( " %s/pak%d " , base , i ) ) ) {
break ;
}
}
if ( i < NUM_ID_PAKS ) {
return qtrue ;
}
return qfalse ;
}
/*
= = = = = = = = = = = = = = = =
FS_ComparePaks
- - - - - - - - - - - - - - - -
dlstring = = qtrue
Returns a list of pak files that we should download from the server . They all get stored
in the current gamedir and an FS_Restart will be fired up after we download them all .
The string is the format :
@ remotename @ localname [ repeat ]
static int fs_numServerReferencedPaks ;
static int fs_serverReferencedPaks [ MAX_SEARCH_PATHS ] ;
static char * fs_serverReferencedPakNames [ MAX_SEARCH_PATHS ] ;
- - - - - - - - - - - - - - - -
dlstring = = qfalse
we are not interested in a download string format , we want something human - readable
( this is used for diagnostics while connecting to a pure server )
= = = = = = = = = = = = = = = =
*/
qboolean FS_ComparePaks ( char * neededpaks , int len , qboolean dlstring ) {
searchpath_t * sp ;
qboolean havepak , badchecksum ;
int i ;
if ( ! fs_numServerReferencedPaks ) {
return qfalse ; // Server didn't send any pack information along
}
* neededpaks = 0 ;
for ( i = 0 ; i < fs_numServerReferencedPaks ; i + + ) {
// Ok, see if we have this pak file
badchecksum = qfalse ;
havepak = qfalse ;
// never autodownload any of the id paks
if ( FS_idPak ( fs_serverReferencedPakNames [ i ] , " baseq3 " ) | | FS_idPak ( fs_serverReferencedPakNames [ i ] , " missionpack " ) ) {
continue ;
}
for ( sp = fs_searchpaths ; sp ; sp = sp - > next ) {
if ( sp - > pack & & sp - > pack - > checksum = = fs_serverReferencedPaks [ i ] ) {
havepak = qtrue ; // This is it!
break ;
}
}
if ( ! havepak & & fs_serverReferencedPakNames [ i ] & & * fs_serverReferencedPakNames [ i ] ) {
// Don't got it
if ( dlstring )
{
// Remote name
Q_strcat ( neededpaks , len , " @ " ) ;
Q_strcat ( neededpaks , len , fs_serverReferencedPakNames [ i ] ) ;
Q_strcat ( neededpaks , len , " .pk3 " ) ;
// Local name
Q_strcat ( neededpaks , len , " @ " ) ;
// Do we have one with the same name?
if ( FS_SV_FileExists ( va ( " %s.pk3 " , fs_serverReferencedPakNames [ i ] ) ) )
{
char st [ MAX_ZPATH ] ;
// We already have one called this, we need to download it to another name
// Make something up with the checksum in it
Com_sprintf ( st , sizeof ( st ) , " %s.%08x.pk3 " , fs_serverReferencedPakNames [ i ] , fs_serverReferencedPaks [ i ] ) ;
Q_strcat ( neededpaks , len , st ) ;
} else
{
Q_strcat ( neededpaks , len , fs_serverReferencedPakNames [ i ] ) ;
Q_strcat ( neededpaks , len , " .pk3 " ) ;
}
}
else
{
Q_strcat ( neededpaks , len , fs_serverReferencedPakNames [ i ] ) ;
Q_strcat ( neededpaks , len , " .pk3 " ) ;
// Do we have one with the same name?
if ( FS_SV_FileExists ( va ( " %s.pk3 " , fs_serverReferencedPakNames [ i ] ) ) )
{
Q_strcat ( neededpaks , len , " (local file exists with wrong checksum) " ) ;
}
Q_strcat ( neededpaks , len , " \n " ) ;
}
}
}
if ( * neededpaks ) {
return qtrue ;
}
return qfalse ; // We have them all
}
/*
= = = = = = = = = = = = = = = =
FS_Shutdown
Frees all resources and closes all files
= = = = = = = = = = = = = = = =
*/
void FS_Shutdown ( qboolean closemfp ) {
searchpath_t * p , * next ;
int i ;
for ( i = 0 ; i < MAX_FILE_HANDLES ; i + + ) {
if ( fsh [ i ] . fileSize ) {
FS_FCloseFile ( i ) ;
}
}
// free everything
for ( p = fs_searchpaths ; p ; p = next ) {
next = p - > next ;
if ( p - > pack ) {
unzClose ( p - > pack - > handle ) ;
Z_Free ( p - > pack - > buildBuffer ) ;
Z_Free ( p - > pack ) ;
}
if ( p - > dir ) {
Z_Free ( p - > dir ) ;
}
Z_Free ( p ) ;
}
// any FS_ calls will now be an error until reinitialized
fs_searchpaths = NULL ;
Cmd_RemoveCommand ( " path " ) ;
Cmd_RemoveCommand ( " dir " ) ;
Cmd_RemoveCommand ( " fdir " ) ;
Cmd_RemoveCommand ( " touchFile " ) ;
# ifdef FS_MISSING
if ( closemfp ) {
fclose ( missingFiles ) ;
}
# endif
}
void Com_AppendCDKey ( const char * filename ) ;
void Com_ReadCDKey ( const char * filename ) ;
/*
= = = = = = = = = = = = = = = =
FS_ReorderPurePaks
NOTE TTimo : the reordering that happens here is not reflected in the cvars ( \ cvarlist * pak * )
this can lead to misleading situations , see https : //zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=540
= = = = = = = = = = = = = = = =
*/
static void FS_ReorderPurePaks ( )
{
searchpath_t * s ;
int i ;
searchpath_t * * p_insert_index , // for linked list reordering
* * p_previous ; // when doing the scan
// only relevant when connected to pure server
if ( ! fs_numServerPaks )
return ;
fs_reordered = qfalse ;
p_insert_index = & fs_searchpaths ; // we insert in order at the beginning of the list
for ( i = 0 ; i < fs_numServerPaks ; i + + ) {
p_previous = p_insert_index ; // track the pointer-to-current-item
for ( s = * p_insert_index ; s ; s = s - > next ) {
// the part of the list before p_insert_index has been sorted already
if ( s - > pack & & fs_serverPaks [ i ] = = s - > pack - > checksum ) {
fs_reordered = qtrue ;
// move this element to the insert list
* p_previous = s - > next ;
s - > next = * p_insert_index ;
* p_insert_index = s ;
// increment insert list
p_insert_index = & s - > next ;
break ; // iterate to next server pack
}
p_previous = & s - > next ;
}
}
}
/*
= = = = = = = = = = = = = = = =
FS_Startup
= = = = = = = = = = = = = = = =
*/
static void FS_Startup ( const char * gameName ) {
const char * homePath ;
cvar_t * fs ;
Com_Printf ( " ----- FS_Startup ----- \n " ) ;
fs_debug = Cvar_Get ( " fs_debug " , " 0 " , 0 ) ;
fs_copyfiles = Cvar_Get ( " fs_copyfiles " , " 0 " , CVAR_INIT ) ;
fs_cdpath = Cvar_Get ( " fs_cdpath " , Sys_DefaultCDPath ( ) , CVAR_INIT ) ;
fs_basepath = Cvar_Get ( " fs_basepath " , Sys_DefaultInstallPath ( ) , CVAR_INIT ) ;
fs_basegame = Cvar_Get ( " fs_basegame " , " " , CVAR_INIT ) ;
homePath = Sys_DefaultHomePath ( ) ;
if ( ! homePath | | ! homePath [ 0 ] ) {
homePath = fs_basepath - > string ;
}
fs_homepath = Cvar_Get ( " fs_homepath " , homePath , CVAR_INIT ) ;
fs_gamedirvar = Cvar_Get ( " fs_game " , " " , CVAR_INIT | CVAR_SYSTEMINFO ) ;
fs_restrict = Cvar_Get ( " fs_restrict " , " " , CVAR_INIT ) ;
// add search path elements in reverse priority order
if ( fs_cdpath - > string [ 0 ] ) {
FS_AddGameDirectory ( fs_cdpath - > string , gameName ) ;
}
if ( fs_basepath - > string [ 0 ] ) {
FS_AddGameDirectory ( fs_basepath - > string , gameName ) ;
}
// fs_homepath is somewhat particular to *nix systems, only add if relevant
// NOTE: same filtering below for mods and basegame
if ( fs_basepath - > string [ 0 ] & & Q_stricmp ( fs_homepath - > string , fs_basepath - > string ) ) {
FS_AddGameDirectory ( fs_homepath - > string , gameName ) ;
}
// check for additional base game so mods can be based upon other mods
if ( fs_basegame - > string [ 0 ] & & ! Q_stricmp ( gameName , BASEGAME ) & & Q_stricmp ( fs_basegame - > string , gameName ) ) {
if ( fs_cdpath - > string [ 0 ] ) {
FS_AddGameDirectory ( fs_cdpath - > string , fs_basegame - > string ) ;
}
if ( fs_basepath - > string [ 0 ] ) {
FS_AddGameDirectory ( fs_basepath - > string , fs_basegame - > string ) ;
}
if ( fs_homepath - > string [ 0 ] & & Q_stricmp ( fs_homepath - > string , fs_basepath - > string ) ) {
FS_AddGameDirectory ( fs_homepath - > string , fs_basegame - > string ) ;
}
}
// check for additional game folder for mods
if ( fs_gamedirvar - > string [ 0 ] & & ! Q_stricmp ( gameName , BASEGAME ) & & Q_stricmp ( fs_gamedirvar - > string , gameName ) ) {
if ( fs_cdpath - > string [ 0 ] ) {
FS_AddGameDirectory ( fs_cdpath - > string , fs_gamedirvar - > string ) ;
}
if ( fs_basepath - > string [ 0 ] ) {
FS_AddGameDirectory ( fs_basepath - > string , fs_gamedirvar - > string ) ;
}
if ( fs_homepath - > string [ 0 ] & & Q_stricmp ( fs_homepath - > string , fs_basepath - > string ) ) {
FS_AddGameDirectory ( fs_homepath - > string , fs_gamedirvar - > string ) ;
}
}
Com_ReadCDKey ( " baseq3 " ) ;
fs = Cvar_Get ( " fs_game " , " " , CVAR_INIT | CVAR_SYSTEMINFO ) ;
if ( fs & & fs - > string [ 0 ] ! = 0 ) {
Com_AppendCDKey ( fs - > string ) ;
}
// add our commands
Cmd_AddCommand ( " path " , FS_Path_f ) ;
Cmd_AddCommand ( " dir " , FS_Dir_f ) ;
Cmd_AddCommand ( " fdir " , FS_NewDir_f ) ;
Cmd_AddCommand ( " touchFile " , FS_TouchFile_f ) ;
// https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=506
// reorder the pure pk3 files according to server order
FS_ReorderPurePaks ( ) ;
// print the current search paths
FS_Path_f ( ) ;
fs_gamedirvar - > modified = qfalse ; // We just loaded, it's not modified
Com_Printf ( " ---------------------- \n " ) ;
# ifdef FS_MISSING
if ( missingFiles = = NULL ) {
missingFiles = fopen ( " \\ missing.txt " , " ab " ) ;
}
# endif
Com_Printf ( " %d files in pk3 files \n " , fs_packFiles ) ;
}
/*
= = = = = = = = = = = = = = = = = = =
FS_SetRestrictions
Looks for product keys and restricts media add on ability
if the full version is not found
= = = = = = = = = = = = = = = = = = =
*/
static void FS_SetRestrictions ( void ) {
searchpath_t * path ;
# ifndef PRE_RELEASE_DEMO
char * productId ;
// if fs_restrict is set, don't even look for the id file,
// which allows the demo release to be tested even if
// the full game is present
if ( ! fs_restrict - > integer ) {
// look for the full game id
FS_ReadFile ( " productid.txt " , ( void * * ) & productId ) ;
if ( productId ) {
// check against the hardcoded string
int seed , i ;
seed = 5000 ;
for ( i = 0 ; i < sizeof ( fs_scrambledProductId ) ; i + + ) {
if ( ( fs_scrambledProductId [ i ] ^ ( seed & 255 ) ) ! = productId [ i ] ) {
break ;
}
seed = ( 69069 * seed + 1 ) ;
}
FS_FreeFile ( productId ) ;
if ( i = = sizeof ( fs_scrambledProductId ) ) {
return ; // no restrictions
}
Com_Error ( ERR_FATAL , " Invalid product identification " ) ;
}
}
# endif
Cvar_Set ( " fs_restrict " , " 1 " ) ;
Com_Printf ( " \n Running in restricted demo mode. \n \n " ) ;
// restart the filesystem with just the demo directory
FS_Shutdown ( qfalse ) ;
FS_Startup ( DEMOGAME ) ;
// make sure that the pak file has the header checksum we expect
for ( path = fs_searchpaths ; path ; path = path - > next ) {
if ( path - > pack ) {
// a tiny attempt to keep the checksum from being scannable from the exe
if ( ( path - > pack - > checksum ^ 0x02261994u ) ! = ( DEMO_PAK_CHECKSUM ^ 0x02261994u ) ) {
Com_Error ( ERR_FATAL , " Corrupted pak0.pk3: %u " , path - > pack - > checksum ) ;
}
}
}
}
/*
= = = = = = = = = = = = = = = = = = = = =
FS_GamePureChecksum
Returns the checksum of the pk3 from which the server loaded the qagame . qvm
= = = = = = = = = = = = = = = = = = = = =
*/
const char * FS_GamePureChecksum ( void ) {
static char info [ MAX_STRING_TOKENS ] ;
searchpath_t * search ;
info [ 0 ] = 0 ;
for ( search = fs_searchpaths ; search ; search = search - > next ) {
// is the element a pak file?
if ( search - > pack ) {
if ( search - > pack - > referenced & FS_QAGAME_REF ) {
Com_sprintf ( info , sizeof ( info ) , " %d " , search - > pack - > checksum ) ;
}
}
}
return info ;
}
/*
= = = = = = = = = = = = = = = = = = = = =
FS_LoadedPakChecksums
Returns a space separated string containing the checksums of all loaded pk3 files .
Servers with sv_pure set will get this string and pass it to clients .
= = = = = = = = = = = = = = = = = = = = =
*/
const char * FS_LoadedPakChecksums ( void ) {
static char info [ BIG_INFO_STRING ] ;
searchpath_t * search ;
info [ 0 ] = 0 ;
for ( search = fs_searchpaths ; search ; search = search - > next ) {
// is the element a pak file?
if ( ! search - > pack ) {
continue ;
}
Q_strcat ( info , sizeof ( info ) , va ( " %i " , search - > pack - > checksum ) ) ;
}
return info ;
}
/*
= = = = = = = = = = = = = = = = = = = = =
FS_LoadedPakNames
Returns a space separated string containing the names of all loaded pk3 files .
Servers with sv_pure set will get this string and pass it to clients .
= = = = = = = = = = = = = = = = = = = = =
*/
const char * FS_LoadedPakNames ( void ) {
static char info [ BIG_INFO_STRING ] ;
searchpath_t * search ;
info [ 0 ] = 0 ;
for ( search = fs_searchpaths ; search ; search = search - > next ) {
// is the element a pak file?
if ( ! search - > pack ) {
continue ;
}
if ( * info ) {
Q_strcat ( info , sizeof ( info ) , " " ) ;
}
Q_strcat ( info , sizeof ( info ) , search - > pack - > pakBasename ) ;
}
return info ;
}
/*
= = = = = = = = = = = = = = = = = = = = =
FS_LoadedPakPureChecksums
Returns a space separated string containing the pure checksums of all loaded pk3 files .
Servers with sv_pure use these checksums to compare with the checksums the clients send
back to the server .
= = = = = = = = = = = = = = = = = = = = =
*/
const char * FS_LoadedPakPureChecksums ( void ) {
static char info [ BIG_INFO_STRING ] ;
searchpath_t * search ;
info [ 0 ] = 0 ;
for ( search = fs_searchpaths ; search ; search = search - > next ) {
// is the element a pak file?
if ( ! search - > pack ) {
continue ;
}
Q_strcat ( info , sizeof ( info ) , va ( " %i " , search - > pack - > pure_checksum ) ) ;
}
return info ;
}
/*
= = = = = = = = = = = = = = = = = = = = =
FS_ReferencedPakChecksums
Returns a space separated string containing the checksums of all referenced pk3 files .
The server will send this to the clients so they can check which files should be auto - downloaded .
= = = = = = = = = = = = = = = = = = = = =
*/
const char * FS_ReferencedPakChecksums ( void ) {
static char info [ BIG_INFO_STRING ] ;
searchpath_t * search ;
info [ 0 ] = 0 ;
for ( search = fs_searchpaths ; search ; search = search - > next ) {
// is the element a pak file?
if ( search - > pack ) {
if ( search - > pack - > referenced | | Q_stricmpn ( search - > pack - > pakGamename , BASEGAME , strlen ( BASEGAME ) ) ) {
Q_strcat ( info , sizeof ( info ) , va ( " %i " , search - > pack - > checksum ) ) ;
}
}
}
return info ;
}
/*
= = = = = = = = = = = = = = = = = = = = =
FS_ReferencedPakPureChecksums
Returns a space separated string containing the pure checksums of all referenced pk3 files .
Servers with sv_pure set will get this string back from clients for pure validation
The string has a specific order , " cgame ui @ ref1 ref2 ref3 ... "
= = = = = = = = = = = = = = = = = = = = =
*/
const char * FS_ReferencedPakPureChecksums ( void ) {
static char info [ BIG_INFO_STRING ] ;
searchpath_t * search ;
int nFlags , numPaks , checksum ;
info [ 0 ] = 0 ;
checksum = fs_checksumFeed ;
numPaks = 0 ;
for ( nFlags = FS_CGAME_REF ; nFlags ; nFlags = nFlags > > 1 ) {
if ( nFlags & FS_GENERAL_REF ) {
// add a delimter between must haves and general refs
//Q_strcat(info, sizeof(info), "@ ");
info [ strlen ( info ) + 1 ] = ' \0 ' ;
info [ strlen ( info ) + 2 ] = ' \0 ' ;
info [ strlen ( info ) ] = ' @ ' ;
info [ strlen ( info ) ] = ' ' ;
}
for ( search = fs_searchpaths ; search ; search = search - > next ) {
// is the element a pak file and has it been referenced based on flag?
if ( search - > pack & & ( search - > pack - > referenced & nFlags ) ) {
Q_strcat ( info , sizeof ( info ) , va ( " %i " , search - > pack - > pure_checksum ) ) ;
if ( nFlags & ( FS_CGAME_REF | FS_UI_REF ) ) {
break ;
}
checksum ^ = search - > pack - > pure_checksum ;
numPaks + + ;
}
}
if ( fs_fakeChkSum ! = 0 ) {
// only added if a non-pure file is referenced
Q_strcat ( info , sizeof ( info ) , va ( " %i " , fs_fakeChkSum ) ) ;
}
}
// last checksum is the encoded number of referenced pk3s
checksum ^ = numPaks ;
Q_strcat ( info , sizeof ( info ) , va ( " %i " , checksum ) ) ;
return info ;
}
/*
= = = = = = = = = = = = = = = = = = = = =
FS_ReferencedPakNames
Returns a space separated string containing the names of all referenced pk3 files .
The server will send this to the clients so they can check which files should be auto - downloaded .
= = = = = = = = = = = = = = = = = = = = =
*/
const char * FS_ReferencedPakNames ( void ) {
static char info [ BIG_INFO_STRING ] ;
searchpath_t * search ;
info [ 0 ] = 0 ;
// we want to return ALL pk3's from the fs_game path
// and referenced one's from baseq3
for ( search = fs_searchpaths ; search ; search = search - > next ) {
// is the element a pak file?
if ( search - > pack ) {
if ( * info ) {
Q_strcat ( info , sizeof ( info ) , " " ) ;
}
if ( search - > pack - > referenced | | Q_stricmpn ( search - > pack - > pakGamename , BASEGAME , strlen ( BASEGAME ) ) ) {
Q_strcat ( info , sizeof ( info ) , search - > pack - > pakGamename ) ;
Q_strcat ( info , sizeof ( info ) , " / " ) ;
Q_strcat ( info , sizeof ( info ) , search - > pack - > pakBasename ) ;
}
}
}
return info ;
}
/*
= = = = = = = = = = = = = = = = = = = = =
FS_ClearPakReferences
= = = = = = = = = = = = = = = = = = = = =
*/
void FS_ClearPakReferences ( int flags ) {
searchpath_t * search ;
if ( ! flags ) {
flags = - 1 ;
}
for ( search = fs_searchpaths ; search ; search = search - > next ) {
// is the element a pak file and has it been referenced?
if ( search - > pack ) {
search - > pack - > referenced & = ~ flags ;
}
}
}
/*
= = = = = = = = = = = = = = = = = = = = =
FS_PureServerSetLoadedPaks
If the string is empty , all data sources will be allowed .
If not empty , only pk3 files that match one of the space
separated checksums will be checked for files , with the
exception of . cfg and . dat files .
= = = = = = = = = = = = = = = = = = = = =
*/
void FS_PureServerSetLoadedPaks ( const char * pakSums , const char * pakNames ) {
int i , c , d ;
Cmd_TokenizeString ( pakSums ) ;
c = Cmd_Argc ( ) ;
if ( c > MAX_SEARCH_PATHS ) {
c = MAX_SEARCH_PATHS ;
}
fs_numServerPaks = c ;
for ( i = 0 ; i < c ; i + + ) {
fs_serverPaks [ i ] = atoi ( Cmd_Argv ( i ) ) ;
}
if ( fs_numServerPaks ) {
Com_DPrintf ( " Connected to a pure server. \n " ) ;
}
else
{
if ( fs_reordered )
{
// https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=540
// force a restart to make sure the search order will be correct
Com_DPrintf ( " FS search reorder is required \n " ) ;
FS_Restart ( fs_checksumFeed ) ;
return ;
}
}
for ( i = 0 ; i < c ; i + + ) {
if ( fs_serverPakNames [ i ] ) {
Z_Free ( fs_serverPakNames [ i ] ) ;
}
fs_serverPakNames [ i ] = NULL ;
}
if ( pakNames & & * pakNames ) {
Cmd_TokenizeString ( pakNames ) ;
d = Cmd_Argc ( ) ;
if ( d > MAX_SEARCH_PATHS ) {
d = MAX_SEARCH_PATHS ;
}
for ( i = 0 ; i < d ; i + + ) {
fs_serverPakNames [ i ] = CopyString ( Cmd_Argv ( i ) ) ;
}
}
}
/*
= = = = = = = = = = = = = = = = = = = = =
FS_PureServerSetReferencedPaks
The checksums and names of the pk3 files referenced at the server
are sent to the client and stored here . The client will use these
checksums to see if any pk3 files need to be auto - downloaded .
= = = = = = = = = = = = = = = = = = = = =
*/
void FS_PureServerSetReferencedPaks ( const char * pakSums , const char * pakNames ) {
int i , c , d ;
Cmd_TokenizeString ( pakSums ) ;
c = Cmd_Argc ( ) ;
if ( c > MAX_SEARCH_PATHS ) {
c = MAX_SEARCH_PATHS ;
}
fs_numServerReferencedPaks = c ;
for ( i = 0 ; i < c ; i + + ) {
fs_serverReferencedPaks [ i ] = atoi ( Cmd_Argv ( i ) ) ;
}
for ( i = 0 ; i < c ; i + + ) {
if ( fs_serverReferencedPakNames [ i ] ) {
Z_Free ( fs_serverReferencedPakNames [ i ] ) ;
}
fs_serverReferencedPakNames [ i ] = NULL ;
}
if ( pakNames & & * pakNames ) {
Cmd_TokenizeString ( pakNames ) ;
d = Cmd_Argc ( ) ;
if ( d > MAX_SEARCH_PATHS ) {
d = MAX_SEARCH_PATHS ;
}
for ( i = 0 ; i < d ; i + + ) {
fs_serverReferencedPakNames [ i ] = CopyString ( Cmd_Argv ( i ) ) ;
}
}
}
/*
= = = = = = = = = = = = = = = =
FS_InitFilesystem
Called only at inital startup , not when the filesystem
is resetting due to a game change
= = = = = = = = = = = = = = = =
*/
void FS_InitFilesystem ( void ) {
// allow command line parms to override our defaults
// we have to specially handle this, because normal command
// line variable sets don't happen until after the filesystem
// has already been initialized
Com_StartupVariable ( " fs_cdpath " ) ;
Com_StartupVariable ( " fs_basepath " ) ;
Com_StartupVariable ( " fs_homepath " ) ;
Com_StartupVariable ( " fs_game " ) ;
Com_StartupVariable ( " fs_copyfiles " ) ;
Com_StartupVariable ( " fs_restrict " ) ;
// try to start up normally
FS_Startup ( BASEGAME ) ;
// see if we are going to allow add-ons
FS_SetRestrictions ( ) ;
// if we can't find default.cfg, assume that the paths are
// busted and error out now, rather than getting an unreadable
// graphics screen when the font fails to load
if ( FS_ReadFile ( " default.cfg " , NULL ) < = 0 ) {
Com_Error ( ERR_FATAL , " Couldn't load default.cfg " ) ;
// bk001208 - SafeMode see below, FIXME?
}
Q_strncpyz ( lastValidBase , fs_basepath - > string , sizeof ( lastValidBase ) ) ;
Q_strncpyz ( lastValidGame , fs_gamedirvar - > string , sizeof ( lastValidGame ) ) ;
// bk001208 - SafeMode see below, FIXME?
}
/*
= = = = = = = = = = = = = = = =
FS_Restart
= = = = = = = = = = = = = = = =
*/
void FS_Restart ( int checksumFeed ) {
// free anything we currently have loaded
FS_Shutdown ( qfalse ) ;
// set the checksum feed
fs_checksumFeed = checksumFeed ;
// clear pak references
FS_ClearPakReferences ( 0 ) ;
// try to start up normally
FS_Startup ( BASEGAME ) ;
// see if we are going to allow add-ons
FS_SetRestrictions ( ) ;
// if we can't find default.cfg, assume that the paths are
// busted and error out now, rather than getting an unreadable
// graphics screen when the font fails to load
if ( FS_ReadFile ( " default.cfg " , NULL ) < = 0 ) {
// this might happen when connecting to a pure server not using BASEGAME/pak0.pk3
// (for instance a TA demo server)
if ( lastValidBase [ 0 ] ) {
FS_PureServerSetLoadedPaks ( " " , " " ) ;
Cvar_Set ( " fs_basepath " , lastValidBase ) ;
Cvar_Set ( " fs_gamedirvar " , lastValidGame ) ;
lastValidBase [ 0 ] = ' \0 ' ;
lastValidGame [ 0 ] = ' \0 ' ;
Cvar_Set ( " fs_restrict " , " 0 " ) ;
FS_Restart ( checksumFeed ) ;
Com_Error ( ERR_DROP , " Invalid game folder \n " ) ;
return ;
}
Com_Error ( ERR_FATAL , " Couldn't load default.cfg " ) ;
}
// bk010116 - new check before safeMode
if ( Q_stricmp ( fs_gamedirvar - > string , lastValidGame ) ) {
// skip the q3config.cfg if "safe" is on the command line
if ( ! Com_SafeMode ( ) ) {
Cbuf_AddText ( " exec q3config.cfg \n " ) ;
}
}
Q_strncpyz ( lastValidBase , fs_basepath - > string , sizeof ( lastValidBase ) ) ;
Q_strncpyz ( lastValidGame , fs_gamedirvar - > string , sizeof ( lastValidGame ) ) ;
}
/*
= = = = = = = = = = = = = = = = =
FS_ConditionalRestart
restart if necessary
= = = = = = = = = = = = = = = = =
*/
qboolean FS_ConditionalRestart ( int checksumFeed ) {
if ( fs_gamedirvar - > modified | | checksumFeed ! = fs_checksumFeed ) {
FS_Restart ( checksumFeed ) ;
return qtrue ;
}
return qfalse ;
}
/*
= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
Handle based file calls for virtual machines
= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
*/
int FS_FOpenFileByMode ( const char * qpath , fileHandle_t * f , fsMode_t mode ) {
int r ;
qboolean sync ;
sync = qfalse ;
switch ( mode ) {
case FS_READ :
r = FS_FOpenFileRead ( qpath , f , qtrue ) ;
break ;
case FS_WRITE :
* f = FS_FOpenFileWrite ( qpath ) ;
r = 0 ;
if ( * f = = 0 ) {
r = - 1 ;
}
break ;
case FS_APPEND_SYNC :
sync = qtrue ;
case FS_APPEND :
* f = FS_FOpenFileAppend ( qpath ) ;
r = 0 ;
if ( * f = = 0 ) {
r = - 1 ;
}
break ;
default :
Com_Error ( ERR_FATAL , " FSH_FOpenFile: bad mode " ) ;
return - 1 ;
}
if ( ! f ) {
return r ;
}
if ( * f ) {
if ( fsh [ * f ] . zipFile = = qtrue ) {
fsh [ * f ] . baseOffset = unztell ( fsh [ * f ] . handleFiles . file . z ) ;
} else {
fsh [ * f ] . baseOffset = ftell ( fsh [ * f ] . handleFiles . file . o ) ;
}
fsh [ * f ] . fileSize = r ;
fsh [ * f ] . streamed = qfalse ;
if ( mode = = FS_READ ) {
Sys_BeginStreamedFile ( * f , 0x4000 ) ;
fsh [ * f ] . streamed = qtrue ;
}
}
fsh [ * f ] . handleSync = sync ;
return r ;
}
int FS_FTell ( fileHandle_t f ) {
int pos ;
if ( fsh [ f ] . zipFile = = qtrue ) {
pos = unztell ( fsh [ f ] . handleFiles . file . z ) ;
} else {
pos = ftell ( fsh [ f ] . handleFiles . file . o ) ;
}
return pos ;
}
void FS_Flush ( fileHandle_t f ) {
fflush ( fsh [ f ] . handleFiles . file . o ) ;
}