2016-03-27 11:49:47 +02: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 Quake III Arena source code ; 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 "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 " 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 .
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
= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
*/
// 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_PAK0_CHECKSUM 2985612116u
static const unsigned pak_checksums [ ] = {
229290730u ,
651935416u ,
3954541494u ,
755737592u ,
1893706666u ,
3323617005u
//3709064859u,
//908855077u,
//977125798u
} ;
// 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
2017-07-21 04:21:00 +02:00
unsigned long len ; // uncompressed file size
2016-03-27 11:49:47 +02:00
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 ;
char fs_gamedir [ MAX_OSPATH ] ; // this will be a single file name with no separators
cvar_t * fs_debug ;
cvar_t * fs_mapdir ;
# ifdef MACOS_X
// Also search the .app bundle for .pk3 files
static cvar_t * fs_apppath ;
# endif
cvar_t * fs_basepath ;
static cvar_t * fs_basegame ;
static cvar_t * fs_gamedirvar ;
static cvar_t * fs_restrict ;
2023-01-31 00:53:24 +01:00
static cvar_t * fs_homepath ;
2016-03-27 11:49:47 +02:00
static cvar_t * fs_copyfiles ;
static cvar_t * fs_filedir ;
static searchpath_t * fs_searchpaths ;
static size_t 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 ;
static qboolean silentStart = qfalse ;
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 ;
2017-07-21 04:21:00 +02:00
int zipFileLen ;
2016-03-27 11:49:47 +02:00
qboolean zipFile ;
char name [ MAX_ZPATH ] ;
} fileHandleData_t ;
2017-10-26 02:23:38 +02:00
fileHandleData_t fsh [ MAX_FILE_HANDLES ] ;
2016-03-27 11:49:47 +02:00
// 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 ] ;
2018-08-21 01:22:14 +02:00
static const char * fs_originalPaks_main [ ] =
{
" Pak0.pk3 " ,
" Pak1.pk3 " ,
" Pak2.pk3 " ,
" Pak3.pk3 " ,
" Pak4.pk3 " ,
" Pak5.pk3 " ,
" Pak6.pk3 "
} ;
static const char * fs_originalPaks_mainta [ ] =
{
" pak1.pk3 " ,
" pak2.pk3 " ,
" pak3.pk3 " ,
" pak4.pk3 " ,
" pak5.pk3 "
} ;
static const char * fs_originalPaks_maintt [ ] =
{
" pak1.pk3 " ,
" pak2.pk3 " ,
" pak3.pk3 " ,
" pak4.pk3 "
} ;
2016-03-27 11:49:47 +02:00
# ifdef FS_MISSING
FILE * missingFiles = NULL ;
# endif
/*
= = = = = = = = = = = = = =
FS_Initialized
= = = = = = = = = = = = = =
*/
qboolean FS_Initialized ( void ) {
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 ( void )
{
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
= = = = = = = = = = = = = = = = = = = =
*/
2016-04-13 00:57:15 +02:00
void FS_ReplaceSeparators ( char * path ) {
2016-03-27 11:49:47 +02:00
char * s ;
for ( s = path ; * s ; s + + ) {
if ( * s = = ' / ' | | * s = = ' \\ ' ) {
* s = PATH_SEP ;
}
}
}
/*
= = = = = = = = = = = = = = = = = = = =
check_filecase
= = = = = = = = = = = = = = = = = = = =
*/
static int check_filecase ( char * Path ) {
/*
int retval ;
char * ptr ;
char * filename ;
char * basedir ;
// of course there is no portable way to open a dir in Linux or Windows
// DAMNIT
// FIXME: stub
*/
return 0 ;
}
/*
= = = = = = = = = = = = = = = = = = =
FS_CorrectCase
= = = = = = = = = = = = = = = = = = =
*/
void FS_CorrectCase ( char * path ) {
int getOut ;
char * ptr ;
if ( ! access ( path , 0 ) ) {
return ;
}
getOut = 0 ;
if ( * path = = ' / ' ) {
ptr = path + 1 ;
} else {
ptr = path ;
}
do
{
ptr = strchr ( ptr , ' / ' ) ;
if ( ptr ) {
* ptr = 0 ;
}
getOut = access ( path , 0 ) ;
if ( getOut ) {
getOut = ! check_filecase ( path ) ;
}
if ( ! ptr ) {
break ;
}
* ptr + + = ' / ' ;
} while ( ! getOut ) ;
}
/*
= = = = = = = = = = = = = = = = = = =
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 ) ;
FS_CorrectCase ( ospath [ toggle ] ) ;
return ospath [ toggle ] ;
}
/*
= = = = = = = = = = = =
FS_CreatePath
Creates any directories needed to store the given filename
= = = = = = = = = = = =
*/
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 = = ' / ' ) {
* ofs = PATH_SEP ;
}
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...
2017-10-26 02:23:38 +02:00
buf = ( byte * ) malloc ( len ) ;
2016-03-27 11:49:47 +02:00
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
= = = = = = = = = = =
*/
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 ;
2023-01-31 00:53:24 +01:00
testpath = FS_BuildOSPath ( fs_homepath - > string , fs_gamedir , file ) ;
2016-03-27 11:49:47 +02:00
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 ;
2023-01-31 00:53:24 +01:00
testpath = FS_BuildOSPath ( fs_homepath - > string , file , " " ) ;
2016-03-27 11:49:47 +02:00
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 " ) ;
}
2023-01-31 00:53:24 +01:00
ospath = FS_BuildOSPath ( fs_homepath - > string , filename , " " ) ;
2016-03-27 11:49:47 +02:00
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 then base path
in that 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
2023-01-31 00:53:24 +01:00
ospath = FS_BuildOSPath ( fs_homepath - > string , filename , " " ) ;
2016-03-27 11:49:47 +02:00
// remove trailing slash
ospath [ strlen ( ospath ) - 1 ] = ' \0 ' ;
if ( fs_debug - > integer ) {
2023-01-31 00:53:24 +01:00
Com_Printf ( " FS_SV_FOpenFileRead (fs_homepath): %s \n " , ospath ) ;
2016-03-27 11:49:47 +02:00
}
fsh [ f ] . handleFiles . file . o = fopen ( ospath , " rb " ) ;
fsh [ f ] . handleSync = qfalse ;
if ( ! fsh [ f ] . handleFiles . file . o )
{
2023-01-31 00:53:24 +01:00
// If fs_homepath == fs_basepath, don't bother
if ( Q_stricmp ( fs_homepath - > string , fs_basepath - > string ) )
2016-03-27 11:49:47 +02:00
{
// 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 ;
}
}
* 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 ( ) ;
2023-01-31 00:53:24 +01:00
from_ospath = FS_BuildOSPath ( fs_homepath - > string , from , " " ) ;
to_ospath = FS_BuildOSPath ( fs_homepath - > string , to , " " ) ;
2016-03-27 11:49:47 +02:00
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 ( ) ;
2023-01-31 00:53:24 +01:00
from_ospath = FS_BuildOSPath ( fs_homepath - > string , fs_gamedir , from ) ;
to_ospath = FS_BuildOSPath ( fs_homepath - > string , fs_gamedir , to ) ;
2016-03-27 11:49:47 +02:00
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 ] . 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 ;
2023-01-31 00:53:24 +01:00
ospath = FS_BuildOSPath ( fs_homepath - > string , fs_gamedir , filename ) ;
2016-03-27 11:49:47 +02:00
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_FOpenTextFileWrite
= = = = = = = = = = =
*/
fileHandle_t FS_FOpenTextFileWrite ( const char * filename ) {
char * ospath ;
fileHandle_t f ;
f = FS_HandleForFile ( ) ;
fsh [ f ] . zipFile = qfalse ;
Q_strncpyz ( fsh [ f ] . name , filename , sizeof ( fsh [ f ] . name ) ) ;
2023-01-31 00:53:24 +01:00
ospath = FS_BuildOSPath ( fs_homepath - > string , fs_gamedir , filename ) ;
2016-03-27 11:49:47 +02:00
if ( fs_debug - > integer ) {
Com_Printf ( " FS_FOpenFileWrite: %s \n " , ospath ) ;
}
if ( FS_CreatePath ( ospath ) ) {
return 0 ;
}
fsh [ f ] . handleFiles . file . o = fopen ( ospath , " wt " ) ;
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 ( ) ;
2023-01-31 00:53:24 +01:00
ospath = FS_BuildOSPath ( fs_homepath - > string , fs_gamedir , filename ) ;
2016-03-27 11:49:47 +02:00
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 qtrue ; // strings not equal
}
} while ( c1 ) ;
return qfalse ; // strings are equal
}
/*
= = = = = = = = = = =
FS_FileNewer
Check if source is newer than destination
= = = = = = = = = = =
*/
qboolean FS_FileNewer ( const char * source , const char * destination )
{
return qfalse ;
}
/*
= = = = = = = = = = =
FS_DeleteFile
= = = = = = = = = = =
*/
void FS_DeleteFile ( const char * filename )
{
char * ospath ;
if ( ! fs_searchpaths )
{
Com_Error ( 0 , " Filesystem call made without initialization \n " ) ;
}
2023-01-31 00:53:24 +01:00
ospath = FS_BuildOSPath ( fs_homepath - > string , fs_gamedir , filename ) ;
2016-03-27 11:49:47 +02:00
if ( fs_debug - > integer ) {
Com_Printf ( " FS_DeleteFile: %s \n " , ospath ) ;
}
remove ( ospath ) ;
}
/*
= = = = = = = = = = =
FS_CanonicalFilename
= = = = = = = = = = =
*/
void FS_CanonicalFilename ( char * filename )
{
char * p = filename ;
while ( * p )
{
if ( p [ 0 ] = = ' / ' & & p [ 1 ] = = ' / ' )
{
char * p2 = p + 1 ;
while ( * p2 )
{
p2 [ 0 ] = p2 [ 1 ] ;
p2 + + ;
}
}
p + + ;
}
}
/*
= = = = = = = = = = =
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 .
= = = = = = = = = = =
*/
int FS_FOpenFileRead ( const char * filename , fileHandle_t * file , qboolean uniqueFILE , qboolean quiet ) {
searchpath_t * search ;
char * netpath ;
pack_t * pak ;
fileInPack_t * pakFile ;
directory_t * dir ;
long hash ;
unz_s * zfi ;
FILE * temp ;
size_t l ;
char demoExt [ 16 ] ;
hash = 0 ;
if ( ! fs_searchpaths ) {
Com_Error ( ERR_FATAL , " Filesystem call made without initialization \n " ) ;
}
if ( ! file ) {
Com_Error ( ERR_FATAL , " FS_FOpenFileRead: NULL 'file' parameter passed \n " ) ;
}
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 , " .cfg " ) ! = 0 & &
Q_stricmp ( filename + l - 4 , " .dat " ) ! = 0 & &
Q_stricmp ( filename + l - 4 , " .ssv " ) ! = 0 & &
Q_stricmp ( filename + l - 7 , " .sav " ) ! = 0 & &
strstr ( filename , " levelshots " ) = = NULL & &
Q_stricmp ( filename + l - 4 , " .tga " ) ! = 0 ) {
pak - > referenced | = FS_GENERAL_REF ;
}
}
if ( ! ( pak - > referenced & FS_QAGAME_REF ) & & strstr ( filename , " qagame.qvm " ) ) {
pak - > referenced | = FS_QAGAME_REF ;
}
if ( ! ( pak - > referenced & FS_CGAME_REF ) & & strstr ( filename , " cgame.qvm " ) ) {
pak - > referenced | = FS_CGAME_REF ;
}
if ( ! ( pak - > referenced & FS_UI_REF ) & & strstr ( filename , " ui.qvm " ) ) {
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 ;
2017-07-21 04:21:00 +02:00
fsh [ * file ] . zipFileLen = pakFile - > len ;
2016-03-27 11:49:47 +02:00
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_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 ;
}
}
2023-01-31 00:53:24 +01:00
2016-03-27 11:49:47 +02:00
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 ) ;
}
return FS_filelength ( * file ) ;
}
}
if ( ! quiet ) {
Com_DPrintf ( " ^~^~^ Can't find %s \n " , filename ) ;
}
* file = 0 ;
return - 1 ;
}
size_t FS_Read ( void * buffer , size_t len , fileHandle_t f ) {
size_t block , remaining ;
size_t 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 , ( unsigned int ) len ) ;
}
}
/*
= = = = = = = = = = = = = = = = =
FS_Write
Properly handles partial writes
= = = = = = = = = = = = = = = = =
*/
size_t FS_Write ( const void * buffer , size_t len , fileHandle_t h ) {
size_t block , remaining ;
size_t 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 ) ;
}
# define PK3_SEEK_BUFFER_SIZE 65536
/*
= = = = = = = = = = = = = = = = =
FS_Seek
= = = = = = = = = = = = = = = = =
*/
int FS_Seek ( fileHandle_t f , long offset , fsOrigin_t origin ) {
2017-07-21 04:21:00 +02:00
#if 0
2016-03-27 11:49:47 +02:00
int _origin ;
if ( ! fs_searchpaths ) {
Com_Error ( ERR_FATAL , " Filesystem call made without initialization \n " ) ;
return - 1 ;
}
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 :
Com_Error ( ERR_FATAL , " Bad origin in FS_Seek \n " ) ;
_origin = 0 ;
break ;
}
if ( fsh [ f ] . zipFile = = qtrue ) {
if ( ! unzIsCurrentFileOpen ( fsh [ f ] . handleFiles . file . o ) ) {
Com_Error ( ERR_FATAL , " Tried to read from a zip file %s that was not open \n " , fsh [ f ] . name ) ;
}
return unzseek ( fsh [ f ] . handleFiles . file . o , offset , _origin ) ;
} else {
return fseek ( FS_FileForHandle ( f ) , offset , _origin ) ;
}
#if 0
if ( fsh [ f ] . streamed ) {
fsh [ f ] . streamed = qfalse ;
FS_Seek ( f , offset , origin ) ;
fsh [ f ] . streamed = qtrue ;
}
if ( fsh [ f ] . zipFile = = qtrue ) {
//FIXME: this is incomplete and really, really
//crappy (but better than what was here before)
byte buffer [ PK3_SEEK_BUFFER_SIZE ] ;
int remainder = offset ;
if ( offset < 0 | | origin = = FS_SEEK_END ) {
Com_Error ( ERR_FATAL , " Negative offsets and FS_SEEK_END not implemented "
" for FS_Seek on pk3 file contents \n " ) ;
return - 1 ;
}
switch ( origin ) {
case FS_SEEK_SET :
unzSetCurrentFileInfoPosition ( fsh [ f ] . handleFiles . file . z , fsh [ f ] . zipFilePos ) ;
unzOpenCurrentFile ( fsh [ f ] . handleFiles . file . z ) ;
//fallthrough
case FS_SEEK_CUR :
while ( remainder > PK3_SEEK_BUFFER_SIZE ) {
FS_Read ( buffer , PK3_SEEK_BUFFER_SIZE , f ) ;
remainder - = PK3_SEEK_BUFFER_SIZE ;
}
FS_Read ( buffer , remainder , f ) ;
return offset ;
break ;
default :
Com_Error ( ERR_FATAL , " Bad origin in FS_Seek \n " ) ;
return - 1 ;
break ;
}
} 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 ) ;
}
# endif
2017-07-21 04:21:00 +02:00
# else
int _origin ;
if ( ! fs_searchpaths ) {
Com_Error ( ERR_FATAL , " Filesystem call made without initialization " ) ;
return - 1 ;
}
if ( fsh [ f ] . zipFile = = qtrue ) {
//FIXME: this is really, really crappy
//(but better than what was here before)
byte buffer [ PK3_SEEK_BUFFER_SIZE ] ;
int remainder ;
int currentPosition = FS_FTell ( f ) ;
// change negative offsets into FS_SEEK_SET
if ( offset < 0 ) {
switch ( origin ) {
case FS_SEEK_END :
remainder = fsh [ f ] . zipFileLen + offset ;
break ;
case FS_SEEK_CUR :
remainder = currentPosition + offset ;
break ;
case FS_SEEK_SET :
default :
remainder = 0 ;
break ;
}
if ( remainder < 0 ) {
remainder = 0 ;
}
origin = FS_SEEK_SET ;
}
else {
if ( origin = = FS_SEEK_END ) {
remainder = fsh [ f ] . zipFileLen - currentPosition + offset ;
}
else {
remainder = offset ;
}
}
switch ( origin ) {
case FS_SEEK_SET :
if ( remainder = = currentPosition ) {
return 0 ;
}
unzSetOffset ( fsh [ f ] . handleFiles . file . z , fsh [ f ] . zipFilePos ) ;
unzOpenCurrentFile ( fsh [ f ] . handleFiles . file . z ) ;
//fallthrough
case FS_SEEK_END :
case FS_SEEK_CUR :
while ( remainder > PK3_SEEK_BUFFER_SIZE ) {
FS_Read ( buffer , PK3_SEEK_BUFFER_SIZE , f ) ;
remainder - = PK3_SEEK_BUFFER_SIZE ;
}
FS_Read ( buffer , remainder , f ) ;
return 0 ;
default :
Com_Error ( ERR_FATAL , " Bad origin in FS_Seek " ) ;
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 :
Com_Error ( ERR_FATAL , " Bad origin in FS_Seek " ) ;
break ;
}
return fseek ( file , offset , _origin ) ;
}
# endif
2016-03-27 11:49:47 +02:00
}
/*
= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
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_ReadFileEx
Filename are relative to the quake search path
a null buffer will just return the file length without loading
= = = = = = = = = = = =
*/
2023-05-27 15:36:19 +02:00
long FS_ReadFileEx ( const char * qpath , void * * buffer , qboolean quiet ) {
2016-03-27 11:49:47 +02:00
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 ] ) {
2018-09-17 23:50:38 +02:00
assert ( 0 ) ;
2016-03-27 11:49:47 +02:00
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 ) {
size_t 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 ;
}
2017-10-26 02:23:38 +02:00
buf = ( byte * ) Hunk_AllocateTempMemory ( len + 1 ) ;
2016-03-27 11:49:47 +02:00
* 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 , qtrue ) ;
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 + + ;
2017-10-26 02:23:38 +02:00
buf = ( byte * ) Hunk_AllocateTempMemory ( len + 1 ) ;
2016-03-27 11:49:47 +02:00
* 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_ReadFile
Filename are relative to the quake search path
a null buffer will just return the file length without loading
= = = = = = = = = = = =
*/
2023-05-27 15:36:19 +02:00
long FS_ReadFile ( const char * qpath , void * * buffer ) {
2016-03-27 11:49:47 +02:00
return FS_ReadFileEx ( qpath , buffer , qfalse ) ;
}
/*
= = = = = = = = = = = =
FS_Gamedir
Filename are relative to the quake search path
a null buffer will just return the file length without loading
= = = = = = = = = = = =
*/
const char * FS_Gamedir ( void )
{
return fs_gamedir ;
}
/*
= = = = = = = = = = = = =
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 ) ;
}
/*
= = = = = = = = = = = =
FS_PrepFileWrite
= = = = = = = = = = = =
*/
const char * FS_PrepFileWrite ( const char * filename ) {
static char ospath [ 256 ] ;
Com_sprintf ( ospath , sizeof ( ospath ) , " %s/%s " , fs_gamedir , filename ) ;
FS_ReplaceSeparators ( ospath ) ;
FS_CreatePath ( ospath ) ;
return ospath ;
}
/*
= = = = = = = = = = = =
FS_WriteFile
Filename are reletive to the quake search path
= = = = = = = = = = = =
*/
2023-05-07 20:38:08 +02:00
int FS_WriteFile ( const char * qpath , const void * buffer , int size ) {
2016-03-27 11:49:47 +02:00
fileHandle_t f ;
size_t len ;
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 0 ;
}
len = FS_Write ( buffer , size , f ) ;
FS_FCloseFile ( f ) ;
return len ;
}
/*
= = = = = = = = = = = =
FS_WriteTextFile
Filename are reletive to the quake search path
= = = = = = = = = = = =
*/
2023-05-07 20:38:08 +02:00
void FS_WriteTextFile ( const char * qpath , const void * buffer , int size ) {
2016-03-27 11:49:47 +02:00
fileHandle_t f ;
f = FS_FOpenTextFileWrite ( 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 ;
size_t 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 ) ;
}
2017-10-26 02:23:38 +02:00
buildBuffer = ( fileInPack_t * ) Z_Malloc ( ( gi . number_entry * sizeof ( fileInPack_t ) ) + len ) ;
2016-03-27 11:49:47 +02:00
namePtr = ( ( char * ) buildBuffer ) + gi . number_entry * sizeof ( fileInPack_t ) ;
2017-10-26 02:23:38 +02:00
fs_headerLongs = ( int * ) Z_Malloc ( ( gi . number_entry + 1 ) * sizeof ( int ) ) ;
2016-03-27 11:49:47 +02:00
fs_headerLongs [ fs_numHeaderLongs + + ] = LittleLong ( fs_checksumFeed ) ;
// 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 ;
}
}
2017-10-26 02:23:38 +02:00
pack = ( pack_t * ) Z_Malloc ( sizeof ( pack_t ) + i * sizeof ( fileInPack_t * ) ) ;
2016-03-27 11:49:47 +02:00
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
2017-07-21 04:21:00 +02:00
//unzGetCurrentFileInfoPosition(uf, &buildBuffer[i].pos);
buildBuffer [ i ] . pos = unzGetOffset ( uf ) ;
buildBuffer [ i ] . len = file_info . uncompressed_size ;
2016-03-27 11:49:47 +02:00
//
buildBuffer [ i ] . next = pack - > hashTable [ hash ] ;
pack - > hashTable [ hash ] = & buildBuffer [ i ] ;
unzGoToNextFile ( uf ) ;
}
pack - > checksum = Com_BlockChecksum ( & fs_headerLongs [ 1 ] , 4 * ( fs_numHeaderLongs - 1 ) ) ;
pack - > pure_checksum = Com_BlockChecksum ( fs_headerLongs , 4 * fs_numHeaderLongs ) ;
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 , size_t * depth ) {
int len , at ;
size_t 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
= = = = = = = = = = = = = = = = = =
*/
2017-10-26 02:23:38 +02:00
static int FS_AddFileToList ( char * name , char * * list , int nfiles ) {
2016-03-27 11:49:47 +02:00
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
= = = = = = = = = = = = = = =
*/
2023-02-01 00:28:40 +01:00
char * * FS_ListFilteredFiles ( const char * path , const char * extension , const char * filter , qboolean wantSubs , int * numfiles ) {
2016-03-27 11:49:47 +02:00
int nfiles ;
char * * listCopy ;
char * list [ MAX_FOUND_FILES ] ;
searchpath_t * search ;
int i ;
size_t pathLength ;
size_t extensionLength ;
size_t 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 ;
size_t 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_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 ;
}
2017-10-26 02:23:38 +02:00
listCopy = ( char * * ) Z_Malloc ( ( nfiles + 1 ) * sizeof ( * listCopy ) ) ;
2016-03-27 11:49:47 +02:00
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 , qboolean wantSubs , int * numfiles ) {
return FS_ListFilteredFiles ( path , extension , NULL , wantSubs , 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 ;
size_t 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 , qfalse , & 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 )
{
int totalLength = 0 ;
char * * cat = NULL , * * dst , * * src ;
totalLength + = Sys_CountFileList ( list0 ) ;
totalLength + = Sys_CountFileList ( list1 ) ;
/* Create new list. */
2017-10-26 02:23:38 +02:00
dst = cat = ( char * * ) Z_Malloc ( ( totalLength + 1 ) * sizeof ( char * ) ) ;
2016-03-27 11:49:47 +02:00
/* Copy over lists. */
if ( list0 )
{
for ( src = list0 ; * src ; src + + , dst + + )
* dst = * src ;
}
if ( list1 )
{
for ( src = list1 ; * 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 ) ;
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 i , j , nPaks , nMods ;
size_t nPotential , nTotal , nLen , 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 ;
qboolean bDrop = qfalse ;
* listbuf = 0 ;
nMods = 0 ;
nPotential = nTotal = 0 ;
2023-01-31 00:53:24 +01:00
pFiles0 = Sys_ListFiles ( fs_homepath - > string , NULL , NULL , & dummy , qtrue ) ;
2016-03-27 11:49:47 +02:00
pFiles1 = Sys_ListFiles ( fs_basepath - > 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 ) ;
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 , BASEGAME ) & & 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 home path */
if ( nPaks < = 0 )
{
2023-01-31 00:53:24 +01:00
path = FS_BuildOSPath ( fs_homepath - > string , name , " " ) ;
2016-03-27 11:49:47 +02:00
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 ;
}
2023-05-26 23:32:27 +02:00
void FS_GetModDescription ( const char * modDir , char * description , int descriptionLen ) {
// FIXME: unimplemented
}
2016-03-27 11:49:47 +02:00
//============================================================================
/*
= = = = = = = = = = = = = = = =
FS_Dir_f
= = = = = = = = = = = = = = = =
*/
void FS_Dir_f ( void ) {
char * path ;
2023-02-01 00:28:40 +01:00
const char * extension ;
2016-03-27 11:49:47 +02:00
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 , qfalse , & 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 ;
2017-10-26 02:23:38 +02:00
sortedlist = ( char * * ) Z_Malloc ( ( numfiles + 1 ) * sizeof ( * sortedlist ) ) ;
2016-03-27 11:49:47 +02:00
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 , qfalse , & 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
= = = = = = = = = = = =
*/
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 , qtrue ) ;
if ( f ) {
FS_FCloseFile ( f ) ;
}
}
//===========================================================================
2018-08-21 01:22:14 +02:00
static int QDECL paksort ( const void * a , const void * b ) {
2016-03-27 11:49:47 +02:00
char * aa , * bb ;
aa = * ( char * * ) a ;
bb = * ( char * * ) b ;
2018-08-21 01:22:14 +02:00
const char * f1 = COM_SkipPath ( aa ) ;
const char * f2 = COM_SkipPath ( bb ) ;
2016-03-27 11:49:47 +02:00
return FS_PathCmp ( aa , bb ) ;
}
2018-08-21 01:22:14 +02:00
static void FS_SortOriginalPaks ( char * * pakfiles , int numfiles , const char * * originalPaks , int numOriginalPaks )
{
int i , j ;
int numSorted = numfiles ;
char * tmp ;
for ( i = numfiles - 1 ; i > = 0 ; i - - )
{
for ( j = 0 ; j < numOriginalPaks ; j + + )
{
if ( ! stricmp ( pakfiles [ i ] , originalPaks [ j ] ) )
{
tmp = pakfiles [ numSorted - 1 ] ;
pakfiles [ numSorted - 1 ] = pakfiles [ i ] ;
pakfiles [ i ] = tmp ;
numSorted - - ;
}
}
}
}
2016-03-27 11:49:47 +02:00
/*
= = = = = = = = = = = = = = = =
FS_AddGameDirectory
Sets fs_gamedir , adds the directory to the head of the path ,
then loads the zip headers
= = = = = = = = = = = = = = = =
*/
2018-08-21 01:22:14 +02:00
void FS_AddGameDirectory ( const char * path , const char * dir ) {
FS_AddGameDirectory2 ( path , dir , false ) ;
}
void FS_AddGameDirectory2 ( const char * path , const char * dir , qboolean original_paks_priority ) {
2016-03-27 11:49:47 +02:00
searchpath_t * sp ;
int i ;
searchpath_t * search ;
pack_t * pak ;
char * pakfile ;
int numfiles ;
char * * pakfiles ;
// Unique
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 ) ) ;
// 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 ) ;
2018-08-21 01:22:14 +02:00
qsort ( pakfiles , numfiles , sizeof ( char * ) , paksort ) ;
if ( original_paks_priority )
{
if ( ! stricmp ( dir , " main " ) )
{
FS_SortOriginalPaks ( pakfiles , numfiles , fs_originalPaks_main , sizeof ( fs_originalPaks_main ) / sizeof ( fs_originalPaks_main [ 0 ] ) ) ;
}
else if ( ! stricmp ( dir , " mainta " ) )
{
FS_SortOriginalPaks ( pakfiles , numfiles , fs_originalPaks_mainta , sizeof ( fs_originalPaks_mainta ) / sizeof ( fs_originalPaks_mainta [ 0 ] ) ) ;
}
else if ( ! stricmp ( dir , " maintt " ) )
{
FS_SortOriginalPaks ( pakfiles , numfiles , fs_originalPaks_maintt , sizeof ( fs_originalPaks_maintt ) / sizeof ( fs_originalPaks_maintt [ 0 ] ) ) ;
}
}
2016-03-27 11:49:47 +02:00
for ( i = 0 ; i < numfiles ; i + + ) {
pakfile = FS_BuildOSPath ( path , dir , pakfiles [ i ] ) ;
if ( ( pak = FS_LoadZipFile ( pakfile , pakfiles [ i ] ) ) = = 0 )
continue ;
// store the game name for downloading
strcpy ( pak - > pakGamename , dir ) ;
2017-10-26 02:23:38 +02:00
search = ( searchpath_t * ) Z_Malloc ( sizeof ( searchpath_t ) ) ;
2016-03-27 11:49:47 +02:00
search - > pack = pak ;
search - > next = fs_searchpaths ;
2016-08-13 18:32:13 +02:00
search - > dir = NULL ;
2016-03-27 11:49:47 +02:00
fs_searchpaths = search ;
}
// done
Sys_FreeFileList ( pakfiles ) ;
//
// add the directory to the search path
//
2017-10-26 02:23:38 +02:00
search = ( searchpath_t * ) Z_Malloc ( sizeof ( searchpath_t ) ) ;
search - > dir = ( directory_t * ) Z_Malloc ( sizeof ( * search - > dir ) ) ;
2016-08-13 18:32:13 +02:00
search - > pack = NULL ;
2016-03-27 11:49:47 +02:00
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 ;
}
/*
= = = = = = = = = = = = = = = =
FS_idPak
= = = = = = = = = = = = = = = =
*/
2023-02-01 00:28:40 +01:00
qboolean FS_idPak ( const char * pak , const char * base ) {
2016-03-27 11:49:47 +02:00
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_idPak
Check whether the string contains stuff like " ../ " to prevent directory traversal bugs
and return qtrue if it does .
= = = = = = = = = = = = = = = =
*/
qboolean FS_CheckDirTraversal ( const char * checkdir )
{
if ( strstr ( checkdir , " ../ " ) | | strstr ( checkdir , " .. \\ " ) )
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 ;
char * origpos = neededpaks ;
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 ] , BASEGAME ) | | FS_idPak ( fs_serverReferencedPakNames [ i ] , " missionpack " ) ) {
continue ;
}
// Make sure the server cannot make us write to non-quake3 directories.
if ( FS_CheckDirTraversal ( fs_serverReferencedPakNames [ i ] ) )
{
Com_Printf ( " WARNING: Invalid download name %s \n " , fs_serverReferencedPakNames [ i ] ) ;
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 )
{
// We need this to make sure we won't hit the end of the buffer or the server could
// overwrite non-pk3 files on clients by writing so much crap into neededpaks that
// Q_strcat cuts off the .pk3 extension.
origpos + = strlen ( origpos ) ;
// 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 " ) ;
}
// Find out whether it might have overflowed the buffer and don't add this file to the
// list if that is the case.
if ( strlen ( origpos ) + ( origpos - neededpaks ) > = len - 1 )
{
* origpos = ' \0 ' ;
break ;
}
}
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 .
= = = = = = = = = = = = = = = =
*/
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 ( void )
{
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
= = = = = = = = = = = = = = = =
*/
2023-05-21 19:59:36 +02:00
static void FS_Startup ( const char * gameName , const char * extensionName )
2016-03-27 11:49:47 +02:00
{
2023-01-31 00:53:24 +01:00
const char * homePath ;
2016-03-27 11:49:47 +02:00
if ( ! silentStart ) {
Com_Printf ( " ----- FS_Startup ----- \n " ) ;
}
fs_debug = Cvar_Get ( " fs_debug " , " 0 " , 0 ) ;
fs_copyfiles = Cvar_Get ( " fs_copyfiles " , " 0 " , CVAR_INIT ) ;
2023-05-31 19:41:21 +02:00
fs_basepath = Cvar_Get ( " fs_basepath " , Sys_DefaultInstallPath ( ) , CVAR_INIT | CVAR_PROTECTED ) ;
2023-01-31 00:53:24 +01:00
homePath = Sys_DefaultHomePath ( ) ;
if ( ! homePath | | ! homePath [ 0 ] ) {
homePath = fs_basepath - > string ;
}
fs_homepath = Cvar_Get ( " fs_homepath " , homePath , CVAR_INIT | CVAR_ROM ) ;
2016-03-27 11:49:47 +02:00
fs_gamedirvar = Cvar_Get ( " fs_game " , " " , CVAR_INIT | CVAR_SYSTEMINFO ) ;
fs_restrict = Cvar_Get ( " fs_restrict " , " " , CVAR_INIT ) ;
2023-05-21 19:59:36 +02:00
if ( gameName ) {
FS_AddGameDirectory ( fs_basepath - > string , gameName ) ;
}
if ( Q_stricmp ( gameName , extensionName ) ) {
FS_AddGameDirectory ( fs_basepath - > string , extensionName ) ;
}
2016-03-27 11:49:47 +02:00
2023-05-21 19:59:36 +02:00
if ( * fs_gamedirvar - > string & & ! Q_stricmp ( gameName , GAME_EXTENSION_BASE ) & & Q_stricmp ( fs_gamedirvar - > string , gameName ) ) {
FS_AddGameDirectory ( fs_basepath - > string , fs_gamedirvar - > string ) ;
2016-03-27 11:49:47 +02:00
}
fs_mapdir = Cvar_Get ( " mapdir " , " " , 0 ) ;
fs_filedir = Cvar_Get ( " fs_filedir " , FS_BuildOSPath ( fs_basepath - > string , fs_gamedir , " /maps/ " ) , 0 ) ;
// 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 ) ;
2023-01-31 00:53:24 +01:00
Sys_Mkdir ( fs_homepath - > string ) ;
2016-03-27 11:49:47 +02:00
if ( ! silentStart ) {
// print the current search paths
FS_Path_f ( ) ;
}
fs_gamedirvar - > modified = qfalse ; // We just loaded, it's not modified
if ( ! silentStart ) {
Com_Printf ( " ---------------------- \n " ) ;
Com_Printf ( " %d files in pk3 files \n " , fs_packFiles ) ;
}
}
/*
= = = = = = = = = = = = = = = = = = =
FS_CheckPak0
Checks that pak0 . pk3 is present and its checksum is correct
Note : If you ' re building a game that doesn ' t depend on the
Q3 media pak0 . pk3 , you ' ll want to remove this function
= = = = = = = = = = = = = = = = = = =
*/
static void FS_CheckPak0 ( void )
{
searchpath_t * path ;
qboolean founddemo = qfalse ;
unsigned foundPak = 0 ;
for ( path = fs_searchpaths ; path ; path = path - > next )
{
const char * pakBasename = path - > pack - > pakBasename ;
if ( ! path - > pack )
continue ;
if ( ! Q_stricmpn ( path - > pack - > pakGamename , " demoq3 " , MAX_OSPATH )
& & ! Q_stricmpn ( pakBasename , " pak0 " , MAX_OSPATH ) )
{
founddemo = qtrue ;
if ( path - > pack - > checksum = = DEMO_PAK0_CHECKSUM )
{
Com_Printf ( " \n \n "
" ************************************************** \n "
" WARNING: It looks like you're using pak0.pk3 \n "
" from the demo. This may work fine, but it is not \n "
" guaranteed or supported. \n "
" ************************************************** \n \n \n " ) ;
}
}
else if ( ! Q_stricmpn ( path - > pack - > pakGamename , BASEGAME , MAX_OSPATH )
& & strlen ( pakBasename ) = = 4 & & ! Q_stricmpn ( pakBasename , " pak " , 3 )
& & pakBasename [ 3 ] > = ' 0 ' & & pakBasename [ 3 ] < = ' 8 ' )
{
if ( path - > pack - > checksum ! = pak_checksums [ pakBasename [ 3 ] - ' 0 ' ] )
{
if ( pakBasename [ 0 ] = = ' 0 ' )
{
Com_Printf ( " \n \n "
" ************************************************** \n "
" WARNING: pak0.pk3 is present but its checksum (%u) \n "
" is not correct. Please re-copy pak0.pk3 from your \n "
" legitimate Q3 CDROM. \n "
" ************************************************** \n \n \n " ,
path - > pack - > checksum ) ;
}
else
{
Com_Printf ( " \n \n "
" ************************************************** \n "
" WARNING: pak%d.pk3 is present but its checksum (%u) \n "
" is not correct. Please re-install the point release \n "
" ************************************************** \n \n \n " ,
pakBasename [ 3 ] - ' 0 ' , path - > pack - > checksum ) ;
}
}
foundPak | = 1 < < ( pakBasename [ 3 ] - ' 0 ' ) ;
}
}
// FIXME: IneQuation: re-enable this when we're done with other stuff
/*if(!founddemo && (foundPak & 0x1ff) != 0x1ff )
{
if ( ( foundPak & 1 ) ! = 1 )
{
Com_Printf ( " \n \n "
" pak0.pk3 is missing. Please copy it \n "
" from your legitimate Q3 CDROM. \n " ) ;
}
if ( ( foundPak & 0x1fe ) ! = 0x1fe )
{
Com_Printf ( " \n \n "
" Point Release files are missing. Please \n "
" re-install the 1.32 point release. \n " ) ;
}
Com_Printf ( " \n \n "
" Also check that your Q3 executable is in \n "
" the correct place and that every file \n "
" in the %s directory is present and readable. \n " , BASEGAME ) ;
if ( ! fs_gamedirvar - > string [ 0 ]
| | ! Q_stricmp ( fs_gamedirvar - > string , BASEGAME )
| | ! Q_stricmp ( fs_gamedirvar - > string , " missionpack " ) )
Com_Error ( ERR_FATAL , " You need to install Quake III Arena in order to play " ) ;
} */
}
/*
= = = = = = = = = = = = = = = = = = = = =
FS_SetRestrictions
= = = = = = = = = = = = = = = = = = = = =
*/
void FS_SetRestrictions ( void ) {
}
/*
= = = = = = = = = = = = = = = = = = = = =
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 = 0 ;
Cmd_TokenizeString ( pakSums ) ;
c = Cmd_Argc ( ) ;
if ( c > MAX_SEARCH_PATHS ) {
c = MAX_SEARCH_PATHS ;
}
for ( i = 0 ; i < c ; i + + ) {
fs_serverReferencedPaks [ i ] = atoi ( Cmd_Argv ( i ) ) ;
}
for ( i = 0 ; i < sizeof ( fs_serverReferencedPakNames ) / sizeof ( * fs_serverReferencedPakNames ) ; 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 > c )
d = c ;
for ( i = 0 ; i < d ; i + + ) {
fs_serverReferencedPakNames [ i ] = CopyString ( Cmd_Argv ( i ) ) ;
}
}
// ensure that there are as many checksums as there are pak names.
if ( d < c )
c = d ;
fs_numServerReferencedPaks = c ;
}
/*
= = = = = = = = = = = = = = = =
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_basepath " ) ;
Com_StartupVariable ( " fs_game " ) ;
Com_StartupVariable ( " fs_copyfiles " ) ;
Com_StartupVariable ( " fs_restrict " ) ;
2023-01-31 00:53:24 +01:00
Com_StartupVariable ( " fs_homepath " ) ;
2016-03-27 11:49:47 +02:00
// try to start up normally
2023-05-21 19:59:36 +02:00
FS_Startup ( BASEGAME , GAME_EXTENSION_BASE ) ;
2016-03-27 11:49:47 +02:00
FS_SetRestrictions ( ) ;
if ( ! silentStart ) {
// 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 ) {
2017-07-21 04:21:00 +02:00
//Com_Error( ERR_FATAL, "ERROR: Couldn't load default.cfg\n\n*********************************************\n**** PUT ORIGINAL MOHAA PAK*.PK3 FILES ****\n**** AND SOUND DIRECTORY IN MAIN/ SUBDIR ****\n*********************************************\n" );
Com_Error ( ERR_FATAL , " ERROR: Couldn't load default.cfg \n \n ********************************************* \n " ) ;
2016-03-27 11:49:47 +02:00
}
}
Q_strncpyz ( lastValidBase , fs_basepath - > string , sizeof ( lastValidBase ) ) ;
Q_strncpyz ( lastValidGame , fs_gamedirvar - > string , sizeof ( lastValidGame ) ) ;
}
/*
= = = = = = = = = = = = = = = =
FS_InitFilesystem2
Silently initialize the file system
= = = = = = = = = = = = = = = =
*/
void FS_InitFilesystem2 ( void ) {
silentStart = qtrue ;
FS_InitFilesystem ( ) ;
}
/*
= = = = = = = = = = = = = = = =
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
2023-05-21 19:59:36 +02:00
FS_Startup ( BASEGAME , GAME_EXTENSION_BASE ) ;
2016-03-27 11:49:47 +02:00
FS_CheckPak0 ( ) ;
// 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 ' ;
FS_Restart ( checksumFeed ) ;
Com_Error ( ERR_DROP , " Invalid game folder \n " ) ;
return ;
}
Com_Error ( ERR_FATAL , " Couldn't load default.cfg " ) ;
}
if ( Q_stricmp ( fs_gamedirvar - > string , lastValidGame ) ) {
// skip the q3config.cfg if "safe" is on the command line
if ( ! Com_SafeMode ( ) ) {
Cbuf_AddText ( " exec omconfig.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 , 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 ] . 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 ) ;
}
void FS_FilenameCompletion ( const char * dir , const char * ext ,
2023-05-26 20:53:00 +02:00
qboolean stripExt , void ( * callback ) ( const char * s ) , qboolean allowNonPureFilesOnDisk ) {
2016-03-27 11:49:47 +02:00
char * * filenames ;
int nfiles ;
int i ;
char filename [ MAX_STRING_CHARS ] ;
2023-05-26 20:53:00 +02:00
// FIXME: allowNonPureFilesOnDisk
filenames = FS_ListFilteredFiles ( dir , ext , NULL , qtrue , & nfiles ) ;
2016-03-27 11:49:47 +02:00
FS_SortFileList ( filenames , nfiles ) ;
for ( i = 0 ; i < nfiles ; i + + ) {
FS_ConvertPath ( filenames [ i ] ) ;
Q_strncpyz ( filename , filenames [ i ] , MAX_STRING_CHARS ) ;
if ( stripExt ) {
COM_StripExtension ( filename , filename , sizeof ( filename ) ) ;
}
callback ( filename ) ;
}
FS_FreeFileList ( filenames ) ;
}
2016-08-13 18:32:13 +02:00
# define ABSOLUTE_NAME_START 3
void FS_GetRelativeFilename ( const char * currentDirectory , const char * absoluteFilename , char * out , size_t destlen )
{
// declarations - put here so this should work in a C compiler
int afMarker = 0 , rfMarker = 0 ;
size_t cdLen = 0 , afLen = 0 ;
int i = 0 ;
int levels = 0 ;
cdLen = strlen ( currentDirectory ) ;
afLen = strlen ( absoluteFilename ) ;
// make sure the names are not too long or too short
if ( cdLen > destlen | | cdLen < ABSOLUTE_NAME_START + 1 | |
afLen > destlen | | afLen < ABSOLUTE_NAME_START + 1 )
{
return ;
}
// Handle DOS names that are on different drives:
if ( currentDirectory [ 0 ] ! = absoluteFilename [ 0 ] )
{
// not on the same drive, so only absolute filename will do
strncpy ( out , absoluteFilename , destlen ) ;
return ;
}
// they are on the same drive, find out how much of the current directory
// is in the absolute filename
i = ABSOLUTE_NAME_START ;
while ( i < afLen & & i < cdLen )
{
if ( currentDirectory [ i ] = = absoluteFilename [ i ]
| | currentDirectory [ i ] = = ' \\ ' & & absoluteFilename [ i ] = = ' / '
| | currentDirectory [ i ] = = ' / ' & & absoluteFilename [ i ] = = ' \\ ' )
{
i + + ;
}
else
{
break ;
}
}
if ( i = = cdLen )
{
if ( absoluteFilename [ i ] = = ' \\ ' | | absoluteFilename [ i - 1 ] = = ' \\ '
| | absoluteFilename [ i ] = = ' / ' | | absoluteFilename [ i - 1 ] = = ' / ' )
{
// the whole current directory name is in the file name,
// so we just trim off the current directory name to get the
// current file name.
if ( absoluteFilename [ i ] = = ' \\ ' | | absoluteFilename [ i ] = = ' / ' )
{
// a directory name might have a trailing slash but a relative
// file name should not have a leading one...
i + + ;
}
strncpy ( out , & absoluteFilename [ i ] , destlen ) ;
return ;
}
}
// The file is not in a child directory of the current directory, so we
// need to step back the appropriate number of parent directories by
// using "..\"s. First find out how many levels deeper we are than the
// common directory
afMarker = i ;
levels = 1 ;
// count the number of directory levels we have to go up to get to the
// common directory
while ( i < cdLen )
{
i + + ;
if ( currentDirectory [ i ] = = ' \\ ' | | currentDirectory [ i ] = = ' / ' )
{
// make sure it's not a trailing slash
i + + ;
if ( currentDirectory [ i ] ! = ' \0 ' )
{
levels + + ;
}
}
}
// move the absolute filename marker back to the start of the directory name
// that it has stopped in.
while ( afMarker > 0 & & absoluteFilename [ afMarker - 1 ] ! = ' \\ ' & & absoluteFilename [ afMarker - 1 ] ! = ' / ' )
{
afMarker - - ;
}
// check that the result will not be too long
if ( levels * 3 + afLen - afMarker > destlen )
{
return ;
}
// add the appropriate number of "..\"s.
rfMarker = 0 ;
for ( i = 0 ; i < levels ; i + + )
{
out [ rfMarker + + ] = ' . ' ;
out [ rfMarker + + ] = ' . ' ;
out [ rfMarker + + ] = PATH_SEP ;
}
// copy the rest of the filename into the result string
strcpy ( & out [ rfMarker ] , & absoluteFilename [ afMarker ] ) ;
}
2023-01-31 00:53:24 +01:00
const char * FS_GetCurrentGameDir ( )
{
return fs_gamedirvar - > string ;
}