2
===========================================================================
3
Copyright (C) 1999-2005 Id Software, Inc.
5
This file is part of Quake III Arena source code.
7
Quake III Arena source code is free software; you can redistribute it
8
and/or modify it under the terms of the GNU General Public License as
9
published by the Free Software Foundation; either version 2 of the License,
10
or (at your option) any later version.
12
Quake III Arena source code is distributed in the hope that it will be
13
useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
14
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
GNU General Public License for more details.
17
You should have received a copy of the GNU General Public License
18
along with Quake III Arena source code; if not, write to the Free Software
19
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
20
===========================================================================
22
/*****************************************************************************
25
* desc: handle based filesystem for Quake III Arena
27
* $Archive: /MissionPack/code/qcommon/files.c $
29
*****************************************************************************/
37
=============================================================================
41
All of Quake's data access is through a hierarchical file system, but the contents of
42
the file system can be transparently merged from several sources.
44
A "qpath" is a reference to game file data. MAX_ZPATH is 256 characters, which must include
45
a terminating zero. "..", "\\", and ":" are explicitly illegal in qpaths to prevent any
46
references outside the quake directory system.
48
The "base path" is the path to the directory holding all the game directories and usually
49
the executable. It defaults to ".", but can be overridden with a "+set fs_basepath c:\quake3"
50
command line to allow code debugging in a different directory. Basepath cannot
51
be modified at all after startup. Any files that are created (demos, screenshots,
52
etc) will be created reletive to the base path, so base path should usually be writable.
54
The "cd path" is the path to an alternate hierarchy that will be searched if a file
55
is not located in the base path. A user can do a partial install that copies some
56
data to a base path created on their hard drive and leave the rest on the cd. Files
57
are never writen to the cd path. It defaults to a value set by the installer, like
58
"e:\quake3", but it can be overridden with "+set fs_cdpath g:\quake3".
60
If a user runs the game directly from a CD, the base path would be on the CD. This
61
should still function correctly, but all file writes will fail (harmlessly).
63
The "home path" is the path used for all write access. On win32 systems we have "base path"
64
== "home path", but on *nix systems the base installation is usually readonly, and
65
"home path" points to ~/.q3a or similar
67
The user can also install custom mods and content in "home path", so it should be searched
68
along with "home path" and "cd path" for game content.
71
The "base game" is the directory under the paths where data comes from by default, and
72
can be either "baseq3" or "demoq3".
74
The "current game" may be the same as the base game, or it may be the name of another
75
directory under the paths that should be searched for files before looking in the base game.
76
This is the basis for addons.
78
Clients automatically set the game directory after receiving a gamestate from a server,
79
so only servers need to worry about +set fs_game.
81
No other directories outside of the base game and current game will ever be referenced by
84
To save disk space and speed loading, directory trees can be collapsed into zip files.
85
The files use a ".pk3" extension to prevent users from unzipping them accidentally, but
86
otherwise the are simply normal uncompressed zip files. A game directory can have multiple
87
zip files of the form "pak0.pk3", "pak1.pk3", etc. Zip files are searched in decending order
88
from the highest number to the lowest, and will always take precedence over the filesystem.
89
This allows a pk3 distributed as a patch to override all existing data.
91
Because we will have updated executables freely available online, there is no point to
92
trying to restrict demo / oem versions of the game with code changes. Demo / oem versions
93
should be exactly the same executables as release versions, but with different data that
94
automatically restricts where game media can come from to prevent add-ons from working.
96
After the paths are initialized, quake will look for the product.txt file. If not
97
found and verified, the game will run in restricted mode. In restricted mode, only
98
files contained in demoq3/pak0.pk3 will be available for loading, and only if the zip header is
99
verified to not have been modified. A single exception is made for q3config.cfg. Files
100
can still be written out in restricted mode, so screenshots and demos are allowed.
101
Restricted mode can be tested by setting "+set fs_restrict 1" on the command line, even
102
if there is a valid product.txt under the basepath or cdpath.
104
If not running in restricted mode, and a file is not found in any local filesystem,
105
an attempt will be made to download it and save it under the base path.
107
If the "fs_copyfiles" cvar is set to 1, then every time a file is sourced from the cd
108
path, it will be copied over to the base path. This is a development aid to help build
109
test releases and to copy working sets over slow network links.
111
File search order: when FS_FOpenFileRead gets called it will go through the fs_searchpaths
112
structure and stop on the first successful hit. fs_searchpaths is built with successive
113
calls to FS_AddGameDirectory
115
Additionaly, we search in several subdirectories:
116
current game is the current mode
117
base game is a variable to allow mods based on other mods
118
(such as baseq3 + missionpack content combination in a mod for instance)
119
BASEGAME is the hardcoded base game ("baseq3")
121
e.g. the qpath "sound/newstuff/test.wav" would be searched for in the following places:
123
home path + current game's zip files
124
home path + current game's directory
125
base path + current game's zip files
126
base path + current game's directory
127
cd path + current game's zip files
128
cd path + current game's directory
130
home path + base game's zip file
131
home path + base game's directory
132
base path + base game's zip file
133
base path + base game's directory
134
cd path + base game's zip file
135
cd path + base game's directory
137
home path + BASEGAME's zip file
138
home path + BASEGAME's directory
139
base path + BASEGAME's zip file
140
base path + BASEGAME's directory
141
cd path + BASEGAME's zip file
142
cd path + BASEGAME's directory
144
server download, to be written to home path + current game's directory
147
The filesystem can be safely shutdown and reinitialized with different
148
basedir / cddir / game combinations, but all other subsystems that rely on it
149
(sound, video) must also be forced to restart.
151
Because the same files are loaded by both the clip model (CM_) and renderer (TR_)
152
subsystems, a simple single-file caching scheme is used. The CM_ subsystems will
153
load the file with a request to cache. Only one file will be kept cached at a time,
154
so any models that are going to be referenced by both subsystems should alternate
155
between the CM_ load function and the ref load function.
157
TODO: A qpath that starts with a leading slash will always refer to the base game, even if another
158
game is currently active. This allows character models, skins, and sounds to be downloaded
159
to a common directory no matter which game is active.
161
How to prevent downloading zip files?
162
Pass pk3 file names in systeminfo, and download before FS_Restart()?
164
Aborting a download disconnects the client from the server.
166
How to mark files as downloadable? Commercial add-ons won't be downloadable.
168
Non-commercial downloads will want to download the entire zip file.
169
the game would have to be reset to actually read the zip in
171
Auto-update information
177
separate server gamedir and client gamedir, so if the user starts
178
a local game after having connected to a network game, it won't stick
179
with the network game.
181
allow menu options for game selection?
183
Read / write config to floppy option.
185
Different version coexistance?
187
When building a pak file, make sure a q3config.cfg isn't present in it,
188
or configs will never get loaded from disk!
192
downloading (outside fs?)
193
game directory passing and restarting
195
=============================================================================
199
#define DEMOGAME "demota"
201
// every time a new demo pk3 file is built, this checksum must be updated.
202
// the easiest way to get it is to just run the game and see what it spits out
203
#define DEMO_PAK0_CHECKSUM 2985612116u
204
#define PAK0_CHECKSUM 1566731103u
206
// if this is defined, the executable positively won't work with any paks other
207
// than the demo pak, even if productid is present. This is only used for our
208
// last demo release to prevent the mac and linux users from using the demo
209
// executable with the production windows pak before the mac/linux products
210
// hit the shelves a little later
211
// NOW defined in build files
212
//#define PRE_RELEASE_TADEMO
214
#define MAX_ZPATH 256
215
#define MAX_SEARCH_PATHS 4096
216
#define MAX_FILEHASH_SIZE 1024
218
typedef struct fileInPack_s {
219
char *name; // name of the file
220
unsigned long pos; // file info position in zip
221
struct fileInPack_s* next; // next file in the hash
225
char pakFilename[MAX_OSPATH]; // c:\quake3\baseq3\pak0.pk3
226
char pakBasename[MAX_OSPATH]; // pak0
227
char pakGamename[MAX_OSPATH]; // baseq3
228
unzFile handle; // handle to zip file
229
int checksum; // regular checksum
230
int pure_checksum; // checksum for pure
231
int numfiles; // number of files in pk3
232
int referenced; // referenced file flags
233
int hashSize; // hash table size (power of 2)
234
fileInPack_t* *hashTable; // hash table
235
fileInPack_t* buildBuffer; // buffer with the filenames etc.
239
char path[MAX_OSPATH]; // c:\quake3
240
char gamedir[MAX_OSPATH]; // baseq3
243
typedef struct searchpath_s {
244
struct searchpath_s *next;
246
pack_t *pack; // only one of pack / dir will be non NULL
250
static char fs_gamedir[MAX_OSPATH]; // this will be a single file name with no separators
251
static cvar_t *fs_debug;
252
static cvar_t *fs_homepath;
253
static cvar_t *fs_basepath;
254
static cvar_t *fs_basegame;
255
static cvar_t *fs_cdpath;
256
static cvar_t *fs_copyfiles;
257
static cvar_t *fs_gamedirvar;
258
static cvar_t *fs_restrict;
259
static searchpath_t *fs_searchpaths;
260
static int fs_readCount; // total bytes read
261
static int fs_loadCount; // total files read
262
static int fs_loadStack; // total files in memory
263
static int fs_packFiles; // total number of files in packs
265
static int fs_fakeChkSum;
266
static int fs_checksumFeed;
268
typedef union qfile_gus {
273
typedef struct qfile_us {
279
qfile_ut handleFiles;
286
char name[MAX_ZPATH];
289
static fileHandleData_t fsh[MAX_FILE_HANDLES];
291
// TTimo - https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=540
292
// wether we did a reorder on the current search path when joining the server
293
static qboolean fs_reordered;
295
// never load anything from pk3 files that are not present at the server when pure
296
static int fs_numServerPaks;
297
static int fs_serverPaks[MAX_SEARCH_PATHS]; // checksums
298
static char *fs_serverPakNames[MAX_SEARCH_PATHS]; // pk3 names
300
// only used for autodownload, to make sure the client has at least
301
// all the pk3 files that are referenced at the server side
302
static int fs_numServerReferencedPaks;
303
static int fs_serverReferencedPaks[MAX_SEARCH_PATHS]; // checksums
304
static char *fs_serverReferencedPakNames[MAX_SEARCH_PATHS]; // pk3 names
306
// last valid game folder used
307
char lastValidBase[MAX_OSPATH];
308
char lastValidGame[MAX_OSPATH];
311
FILE* missingFiles = NULL;
320
qboolean FS_Initialized( void ) {
321
return (fs_searchpaths != NULL);
329
qboolean FS_PakIsPure( pack_t *pack ) {
332
if ( fs_numServerPaks ) {
333
for ( i = 0 ; i < fs_numServerPaks ; i++ ) {
334
// FIXME: also use hashed file names
335
// NOTE TTimo: a pk3 with same checksum but different name would be validated too
336
// 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'
337
if ( pack->checksum == fs_serverPaks[i] ) {
338
return qtrue; // on the aproved list
341
return qfalse; // not on the pure server pak list
353
int FS_LoadStack( void )
360
return a hash value for the filename
363
static long FS_HashFileName( const char *fname, int hashSize ) {
370
while (fname[i] != '\0') {
371
letter = tolower(fname[i]);
372
if (letter =='.') break; // don't include extension
373
if (letter =='\\') letter = '/'; // damn path names
374
if (letter == PATH_SEP) letter = '/'; // damn path names
375
hash+=(long)(letter)*(i+119);
378
hash = (hash ^ (hash >> 10) ^ (hash >> 20));
379
hash &= (hashSize-1);
383
static fileHandle_t FS_HandleForFile(void) {
386
for ( i = 1 ; i < MAX_FILE_HANDLES ; i++ ) {
387
if ( fsh[i].handleFiles.file.o == NULL ) {
391
Com_Error( ERR_DROP, "FS_HandleForFile: none free" );
395
static FILE *FS_FileForHandle( fileHandle_t f ) {
396
if ( f < 0 || f > MAX_FILE_HANDLES ) {
397
Com_Error( ERR_DROP, "FS_FileForHandle: out of reange" );
399
if (fsh[f].zipFile == qtrue) {
400
Com_Error( ERR_DROP, "FS_FileForHandle: can't get FILE on zip file" );
402
if ( ! fsh[f].handleFiles.file.o ) {
403
Com_Error( ERR_DROP, "FS_FileForHandle: NULL" );
406
return fsh[f].handleFiles.file.o;
409
void FS_ForceFlush( fileHandle_t f ) {
412
file = FS_FileForHandle(f);
413
setvbuf( file, NULL, _IONBF, 0 );
420
If this is called on a non-unique FILE (from a pak file),
421
it will return the size of the pak file, not the expected
425
int FS_filelength( fileHandle_t f ) {
430
h = FS_FileForHandle(f);
432
fseek (h, 0, SEEK_END);
434
fseek (h, pos, SEEK_SET);
443
Fix things up differently for win/unix/mac
446
static void FS_ReplaceSeparators( char *path ) {
449
for ( s = path ; *s ; s++ ) {
450
if ( *s == '/' || *s == '\\' ) {
460
Qpath may have either forward or backwards slashes
463
char *FS_BuildOSPath( const char *base, const char *game, const char *qpath ) {
464
char temp[MAX_OSPATH];
465
static char ospath[2][MAX_OSPATH];
468
toggle ^= 1; // flip-flop to allow two returns without clash
470
if( !game || !game[0] ) {
474
Com_sprintf( temp, sizeof(temp), "/%s/%s", game, qpath );
475
FS_ReplaceSeparators( temp );
476
Com_sprintf( ospath[toggle], sizeof( ospath[0] ), "%s%s", base, temp );
478
return ospath[toggle];
486
Creates any directories needed to store the given filename
489
static qboolean FS_CreatePath (char *OSPath) {
492
// make absolutely sure that it can't back up the path
493
// FIXME: is c: allowed???
494
if ( strstr( OSPath, ".." ) || strstr( OSPath, "::" ) ) {
495
Com_Printf( "WARNING: refusing to create relative path \"%s\"\n", OSPath );
499
for (ofs = OSPath+1 ; *ofs ; ofs++) {
500
if (*ofs == PATH_SEP) {
501
// create the directory
514
Copy a fully specified file from one place to another
517
static void FS_CopyFile( char *fromOSPath, char *toOSPath ) {
522
Com_Printf( "copy %s to %s\n", fromOSPath, toOSPath );
524
if (strstr(fromOSPath, "journal.dat") || strstr(fromOSPath, "journaldata.dat")) {
525
Com_Printf( "Ignoring journal files\n");
529
f = fopen( fromOSPath, "rb" );
533
fseek (f, 0, SEEK_END);
535
fseek (f, 0, SEEK_SET);
537
// we are using direct malloc instead of Z_Malloc here, so it
538
// probably won't work on a mac... Its only for developers anyway...
540
if (fread( buf, 1, len, f ) != len)
541
Com_Error( ERR_FATAL, "Short read in FS_Copyfiles()\n" );
544
if( FS_CreatePath( toOSPath ) ) {
548
f = fopen( toOSPath, "wb" );
552
if (fwrite( buf, 1, len, f ) != len)
553
Com_Error( ERR_FATAL, "Short write in FS_Copyfiles()\n" );
564
void FS_Remove( const char *osPath ) {
574
void FS_HomeRemove( const char *homePath ) {
575
remove( FS_BuildOSPath( fs_homepath->string,
576
fs_gamedir, homePath ) );
583
Tests if the file exists in the current gamedir, this DOES NOT
584
search the paths. This is to determine if opening a file to write
585
(which always goes into the current gamedir) will cause any overwrites.
586
NOTE TTimo: this goes with FS_FOpenFileWrite for opening the file afterwards
589
qboolean FS_FileExists( const char *file )
594
testpath = FS_BuildOSPath( fs_homepath->string, fs_gamedir, file );
596
f = fopen( testpath, "rb" );
608
Tests if the file exists
611
qboolean FS_SV_FileExists( const char *file )
616
testpath = FS_BuildOSPath( fs_homepath->string, file, "");
617
testpath[strlen(testpath)-1] = '\0';
619
f = fopen( testpath, "rb" );
634
fileHandle_t FS_SV_FOpenFileWrite( const char *filename ) {
638
if ( !fs_searchpaths ) {
639
Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
642
ospath = FS_BuildOSPath( fs_homepath->string, filename, "" );
643
ospath[strlen(ospath)-1] = '\0';
645
f = FS_HandleForFile();
646
fsh[f].zipFile = qfalse;
648
if ( fs_debug->integer ) {
649
Com_Printf( "FS_SV_FOpenFileWrite: %s\n", ospath );
652
if( FS_CreatePath( ospath ) ) {
656
Com_DPrintf( "writing to: %s\n", ospath );
657
fsh[f].handleFiles.file.o = fopen( ospath, "wb" );
659
Q_strncpyz( fsh[f].name, filename, sizeof( fsh[f].name ) );
661
fsh[f].handleSync = qfalse;
662
if (!fsh[f].handleFiles.file.o) {
671
search for a file somewhere below the home path, base path or cd path
672
we search in that order, matching FS_SV_FOpenFileRead order
675
int FS_SV_FOpenFileRead( const char *filename, fileHandle_t *fp ) {
679
if ( !fs_searchpaths ) {
680
Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
683
f = FS_HandleForFile();
684
fsh[f].zipFile = qfalse;
686
Q_strncpyz( fsh[f].name, filename, sizeof( fsh[f].name ) );
688
// don't let sound stutter
689
S_ClearSoundBuffer();
692
ospath = FS_BuildOSPath( fs_homepath->string, filename, "" );
693
// remove trailing slash
694
ospath[strlen(ospath)-1] = '\0';
696
if ( fs_debug->integer ) {
697
Com_Printf( "FS_SV_FOpenFileRead (fs_homepath): %s\n", ospath );
700
fsh[f].handleFiles.file.o = fopen( ospath, "rb" );
701
fsh[f].handleSync = qfalse;
702
if (!fsh[f].handleFiles.file.o)
704
// NOTE TTimo on non *nix systems, fs_homepath == fs_basepath, might want to avoid
705
if (Q_stricmp(fs_homepath->string,fs_basepath->string))
708
ospath = FS_BuildOSPath( fs_basepath->string, filename, "" );
709
ospath[strlen(ospath)-1] = '\0';
711
if ( fs_debug->integer )
713
Com_Printf( "FS_SV_FOpenFileRead (fs_basepath): %s\n", ospath );
716
fsh[f].handleFiles.file.o = fopen( ospath, "rb" );
717
fsh[f].handleSync = qfalse;
719
if ( !fsh[f].handleFiles.file.o )
726
if (!fsh[f].handleFiles.file.o) {
728
ospath = FS_BuildOSPath( fs_cdpath->string, filename, "" );
729
ospath[strlen(ospath)-1] = '\0';
731
if (fs_debug->integer)
733
Com_Printf( "FS_SV_FOpenFileRead (fs_cdpath) : %s\n", ospath );
736
fsh[f].handleFiles.file.o = fopen( ospath, "rb" );
737
fsh[f].handleSync = qfalse;
739
if( !fsh[f].handleFiles.file.o ) {
746
return FS_filelength(f);
758
void FS_SV_Rename( const char *from, const char *to ) {
759
char *from_ospath, *to_ospath;
761
if ( !fs_searchpaths ) {
762
Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
765
// don't let sound stutter
766
S_ClearSoundBuffer();
768
from_ospath = FS_BuildOSPath( fs_homepath->string, from, "" );
769
to_ospath = FS_BuildOSPath( fs_homepath->string, to, "" );
770
from_ospath[strlen(from_ospath)-1] = '\0';
771
to_ospath[strlen(to_ospath)-1] = '\0';
773
if ( fs_debug->integer ) {
774
Com_Printf( "FS_SV_Rename: %s --> %s\n", from_ospath, to_ospath );
777
if (rename( from_ospath, to_ospath )) {
778
// Failed, try copying it and deleting the original
779
FS_CopyFile ( from_ospath, to_ospath );
780
FS_Remove ( from_ospath );
792
void FS_Rename( const char *from, const char *to ) {
793
char *from_ospath, *to_ospath;
795
if ( !fs_searchpaths ) {
796
Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
799
// don't let sound stutter
800
S_ClearSoundBuffer();
802
from_ospath = FS_BuildOSPath( fs_homepath->string, fs_gamedir, from );
803
to_ospath = FS_BuildOSPath( fs_homepath->string, fs_gamedir, to );
805
if ( fs_debug->integer ) {
806
Com_Printf( "FS_Rename: %s --> %s\n", from_ospath, to_ospath );
809
if (rename( from_ospath, to_ospath )) {
810
// Failed, try copying it and deleting the original
811
FS_CopyFile ( from_ospath, to_ospath );
812
FS_Remove ( from_ospath );
820
If the FILE pointer is an open pak file, leave it open.
822
For some reason, other dll's can't just cal fclose()
823
on files returned by FS_FOpenFile...
826
void FS_FCloseFile( fileHandle_t f ) {
827
if ( !fs_searchpaths ) {
828
Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
831
if (fsh[f].streamed) {
832
Sys_EndStreamedFile(f);
834
if (fsh[f].zipFile == qtrue) {
835
unzCloseCurrentFile( fsh[f].handleFiles.file.z );
836
if ( fsh[f].handleFiles.unique ) {
837
unzClose( fsh[f].handleFiles.file.z );
839
Com_Memset( &fsh[f], 0, sizeof( fsh[f] ) );
843
// we didn't find it as a pak, so close it as a unique file
844
if (fsh[f].handleFiles.file.o) {
845
fclose (fsh[f].handleFiles.file.o);
847
Com_Memset( &fsh[f], 0, sizeof( fsh[f] ) );
856
fileHandle_t FS_FOpenFileWrite( const char *filename ) {
860
if ( !fs_searchpaths ) {
861
Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
864
f = FS_HandleForFile();
865
fsh[f].zipFile = qfalse;
867
ospath = FS_BuildOSPath( fs_homepath->string, fs_gamedir, filename );
869
if ( fs_debug->integer ) {
870
Com_Printf( "FS_FOpenFileWrite: %s\n", ospath );
873
if( FS_CreatePath( ospath ) ) {
877
// enabling the following line causes a recursive function call loop
878
// when running with +set logfile 1 +set developer 1
879
//Com_DPrintf( "writing to: %s\n", ospath );
880
fsh[f].handleFiles.file.o = fopen( ospath, "wb" );
882
Q_strncpyz( fsh[f].name, filename, sizeof( fsh[f].name ) );
884
fsh[f].handleSync = qfalse;
885
if (!fsh[f].handleFiles.file.o) {
897
fileHandle_t FS_FOpenFileAppend( const char *filename ) {
901
if ( !fs_searchpaths ) {
902
Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
905
f = FS_HandleForFile();
906
fsh[f].zipFile = qfalse;
908
Q_strncpyz( fsh[f].name, filename, sizeof( fsh[f].name ) );
910
// don't let sound stutter
911
S_ClearSoundBuffer();
913
ospath = FS_BuildOSPath( fs_homepath->string, fs_gamedir, filename );
915
if ( fs_debug->integer ) {
916
Com_Printf( "FS_FOpenFileAppend: %s\n", ospath );
919
if( FS_CreatePath( ospath ) ) {
923
fsh[f].handleFiles.file.o = fopen( ospath, "ab" );
924
fsh[f].handleSync = qfalse;
925
if (!fsh[f].handleFiles.file.o) {
935
Ignore case and seprator char distinctions
938
qboolean FS_FilenameCompare( const char *s1, const char *s2 ) {
945
if (c1 >= 'a' && c1 <= 'z') {
948
if (c2 >= 'a' && c2 <= 'z') {
952
if ( c1 == '\\' || c1 == ':' ) {
955
if ( c2 == '\\' || c2 == ':' ) {
960
return qtrue; // strings not equal
964
return qfalse; // strings are equal
972
char *FS_ShiftedStrStr(const char *string, const char *substring, int shift) {
973
char buf[MAX_STRING_TOKENS];
976
for (i = 0; substring[i]; i++) {
977
buf[i] = substring[i] + shift;
980
return strstr(string, buf);
987
Finds the file in the search path.
988
Returns filesize and an open FILE pointer.
989
Used for streaming data out of either a
990
separate file or a ZIP file.
993
extern qboolean com_fullyInitialized;
995
int FS_FOpenFileRead( const char *filename, fileHandle_t *file, qboolean uniqueFILE ) {
996
searchpath_t *search;
999
fileInPack_t *pakFile;
1009
if ( !fs_searchpaths ) {
1010
Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
1013
if ( file == NULL ) {
1014
// just wants to see if file is there
1015
for ( search = fs_searchpaths ; search ; search = search->next ) {
1017
if ( search->pack ) {
1018
hash = FS_HashFileName(filename, search->pack->hashSize);
1020
// is the element a pak file?
1021
if ( search->pack && search->pack->hashTable[hash] ) {
1022
// look through all the pak file elements
1024
pakFile = pak->hashTable[hash];
1026
// case and separator insensitive comparisons
1027
if ( !FS_FilenameCompare( pakFile->name, filename ) ) {
1031
pakFile = pakFile->next;
1032
} while(pakFile != NULL);
1033
} else if ( search->dir ) {
1036
netpath = FS_BuildOSPath( dir->path, dir->gamedir, filename );
1037
temp = fopen (netpath, "rb");
1049
Com_Error( ERR_FATAL, "FS_FOpenFileRead: NULL 'filename' parameter passed\n" );
1052
Com_sprintf (demoExt, sizeof(demoExt), ".dm_%d",PROTOCOL_VERSION );
1053
// qpaths are not supposed to have a leading slash
1054
if ( filename[0] == '/' || filename[0] == '\\' ) {
1058
// make absolutely sure that it can't back up the path.
1059
// The searchpaths do guarantee that something will always
1060
// be prepended, so we don't need to worry about "c:" or "//limbo"
1061
if ( strstr( filename, ".." ) || strstr( filename, "::" ) ) {
1066
// make sure the q3key file is only readable by the quake3.exe at initialization
1067
// any other time the key should only be accessed in memory using the provided functions
1068
if( com_fullyInitialized && strstr( filename, "q3key" ) ) {
1074
// search through the path, one element at a time
1077
*file = FS_HandleForFile();
1078
fsh[*file].handleFiles.unique = uniqueFILE;
1080
for ( search = fs_searchpaths ; search ; search = search->next ) {
1082
if ( search->pack ) {
1083
hash = FS_HashFileName(filename, search->pack->hashSize);
1085
// is the element a pak file?
1086
if ( search->pack && search->pack->hashTable[hash] ) {
1087
// disregard if it doesn't match one of the allowed pure pak files
1088
if ( !FS_PakIsPure(search->pack) ) {
1092
// look through all the pak file elements
1094
pakFile = pak->hashTable[hash];
1096
// case and separator insensitive comparisons
1097
if ( !FS_FilenameCompare( pakFile->name, filename ) ) {
1100
// mark the pak as having been referenced and mark specifics on cgame and ui
1101
// shaders, txt, arena files by themselves do not count as a reference as
1102
// these are loaded from all pk3s
1103
// from every pk3 file..
1104
l = strlen( filename );
1105
if ( !(pak->referenced & FS_GENERAL_REF)) {
1106
if ( Q_stricmp(filename + l - 7, ".shader") != 0 &&
1107
Q_stricmp(filename + l - 4, ".txt") != 0 &&
1108
Q_stricmp(filename + l - 4, ".cfg") != 0 &&
1109
Q_stricmp(filename + l - 7, ".config") != 0 &&
1110
strstr(filename, "levelshots") == NULL &&
1111
Q_stricmp(filename + l - 4, ".bot") != 0 &&
1112
Q_stricmp(filename + l - 6, ".arena") != 0 &&
1113
Q_stricmp(filename + l - 5, ".menu") != 0) {
1114
pak->referenced |= FS_GENERAL_REF;
1120
if (!(pak->referenced & FS_QAGAME_REF) && FS_ShiftedStrStr(filename, "dTZT`X!di`", 13)) {
1121
pak->referenced |= FS_QAGAME_REF;
1125
if (!(pak->referenced & FS_CGAME_REF) && FS_ShiftedStrStr(filename , "\\`Zf^'jof", 7)) {
1126
pak->referenced |= FS_CGAME_REF;
1130
if (!(pak->referenced & FS_UI_REF) && FS_ShiftedStrStr(filename , "pd)lqh", 5)) {
1131
pak->referenced |= FS_UI_REF;
1135
// open a new file on the pakfile
1136
fsh[*file].handleFiles.file.z = unzReOpen (pak->pakFilename, pak->handle);
1137
if (fsh[*file].handleFiles.file.z == NULL) {
1138
Com_Error (ERR_FATAL, "Couldn't reopen %s", pak->pakFilename);
1141
fsh[*file].handleFiles.file.z = pak->handle;
1143
Q_strncpyz( fsh[*file].name, filename, sizeof( fsh[*file].name ) );
1144
fsh[*file].zipFile = qtrue;
1145
zfi = (unz_s *)fsh[*file].handleFiles.file.z;
1146
// in case the file was new
1148
// set the file position in the zip file (also sets the current file info)
1149
unzSetCurrentFileInfoPosition(pak->handle, pakFile->pos);
1150
// copy the file info into the unzip structure
1151
Com_Memcpy( zfi, pak->handle, sizeof(unz_s) );
1152
// we copy this back into the structure
1154
// open the file in the zip
1155
unzOpenCurrentFile( fsh[*file].handleFiles.file.z );
1156
fsh[*file].zipFilePos = pakFile->pos;
1158
if ( fs_debug->integer ) {
1159
Com_Printf( "FS_FOpenFileRead: %s (found in '%s')\n",
1160
filename, pak->pakFilename );
1162
return zfi->cur_file_info.uncompressed_size;
1164
pakFile = pakFile->next;
1165
} while(pakFile != NULL);
1166
} else if ( search->dir ) {
1167
// check a file in the directory tree
1169
// if we are running restricted, the only files we
1170
// will allow to come from the directory are .cfg files
1171
l = strlen( filename );
1172
// FIXME TTimo I'm not sure about the fs_numServerPaks test
1173
// if you are using FS_ReadFile to find out if a file exists,
1174
// this test can make the search fail although the file is in the directory
1175
// I had the problem on https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=8
1176
// turned out I used FS_FileExists instead
1177
if ( fs_restrict->integer || fs_numServerPaks ) {
1179
if ( Q_stricmp( filename + l - 4, ".cfg" ) // for config files
1180
&& Q_stricmp( filename + l - 5, ".menu" ) // menu files
1181
&& Q_stricmp( filename + l - 5, ".game" ) // menu files
1182
&& Q_stricmp( filename + l - strlen(demoExt), demoExt ) // menu files
1183
&& Q_stricmp( filename + l - 4, ".dat" ) ) { // for journal files
1190
netpath = FS_BuildOSPath( dir->path, dir->gamedir, filename );
1191
fsh[*file].handleFiles.file.o = fopen (netpath, "rb");
1192
if ( !fsh[*file].handleFiles.file.o ) {
1196
if ( Q_stricmp( filename + l - 4, ".cfg" ) // for config files
1197
&& Q_stricmp( filename + l - 5, ".menu" ) // menu files
1198
&& Q_stricmp( filename + l - 5, ".game" ) // menu files
1199
&& Q_stricmp( filename + l - strlen(demoExt), demoExt ) // menu files
1200
&& Q_stricmp( filename + l - 4, ".dat" ) ) { // for journal files
1201
fs_fakeChkSum = random();
1204
Q_strncpyz( fsh[*file].name, filename, sizeof( fsh[*file].name ) );
1205
fsh[*file].zipFile = qfalse;
1206
if ( fs_debug->integer ) {
1207
Com_Printf( "FS_FOpenFileRead: %s (found in '%s/%s')\n", filename,
1208
dir->path, dir->gamedir );
1211
// if we are getting it from the cdpath, optionally copy it
1213
if ( fs_copyfiles->integer && !Q_stricmp( dir->path, fs_cdpath->string ) ) {
1216
copypath = FS_BuildOSPath( fs_basepath->string, dir->gamedir, filename );
1217
FS_CopyFile( netpath, copypath );
1220
return FS_filelength (*file);
1224
Com_DPrintf ("Can't find %s\n", filename);
1227
fprintf(missingFiles, "%s\n", filename);
1239
Properly handles partial reads
1242
int FS_Read2( void *buffer, int len, fileHandle_t f ) {
1243
if ( !fs_searchpaths ) {
1244
Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
1250
if (fsh[f].streamed) {
1252
fsh[f].streamed = qfalse;
1253
r = Sys_StreamedRead( buffer, len, 1, f);
1254
fsh[f].streamed = qtrue;
1257
return FS_Read( buffer, len, f);
1261
int FS_Read( void *buffer, int len, fileHandle_t f ) {
1262
int block, remaining;
1267
if ( !fs_searchpaths ) {
1268
Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
1275
buf = (byte *)buffer;
1276
fs_readCount += len;
1278
if (fsh[f].zipFile == qfalse) {
1283
read = fread (buf, 1, block, fsh[f].handleFiles.file.o);
1285
// we might have been trying to read from a CD, which
1286
// sometimes returns a 0 read on windows
1290
return len-remaining; //Com_Error (ERR_FATAL, "FS_Read: 0 bytes read");
1295
Com_Error (ERR_FATAL, "FS_Read: -1 bytes read");
1303
return unzReadCurrentFile(fsh[f].handleFiles.file.z, buffer, len);
1311
Properly handles partial writes
1314
int FS_Write( const void *buffer, int len, fileHandle_t h ) {
1315
int block, remaining;
1321
if ( !fs_searchpaths ) {
1322
Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
1329
f = FS_FileForHandle(h);
1330
buf = (byte *)buffer;
1336
written = fwrite (buf, 1, block, f);
1341
Com_Printf( "FS_Write: 0 bytes written\n" );
1346
if (written == -1) {
1347
Com_Printf( "FS_Write: -1 bytes written\n" );
1351
remaining -= written;
1354
if ( fsh[h].handleSync ) {
1360
void QDECL FS_Printf( fileHandle_t h, const char *fmt, ... ) {
1362
char msg[MAXPRINTMSG];
1364
va_start (argptr,fmt);
1365
Q_vsnprintf (msg, sizeof(msg), fmt, argptr);
1368
FS_Write(msg, strlen(msg), h);
1371
#define PK3_SEEK_BUFFER_SIZE 65536
1379
int FS_Seek( fileHandle_t f, long offset, int origin ) {
1382
if ( !fs_searchpaths ) {
1383
Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
1387
if (fsh[f].streamed) {
1388
fsh[f].streamed = qfalse;
1389
Sys_StreamSeek( f, offset, origin );
1390
fsh[f].streamed = qtrue;
1393
if (fsh[f].zipFile == qtrue) {
1394
//FIXME: this is incomplete and really, really
1395
//crappy (but better than what was here before)
1396
byte buffer[PK3_SEEK_BUFFER_SIZE];
1397
int remainder = offset;
1399
if( offset < 0 || origin == FS_SEEK_END ) {
1400
Com_Error( ERR_FATAL, "Negative offsets and FS_SEEK_END not implemented "
1401
"for FS_Seek on pk3 file contents\n" );
1407
unzSetCurrentFileInfoPosition(fsh[f].handleFiles.file.z, fsh[f].zipFilePos);
1408
unzOpenCurrentFile(fsh[f].handleFiles.file.z);
1412
while( remainder > PK3_SEEK_BUFFER_SIZE ) {
1413
FS_Read( buffer, PK3_SEEK_BUFFER_SIZE, f );
1414
remainder -= PK3_SEEK_BUFFER_SIZE;
1416
FS_Read( buffer, remainder, f );
1421
Com_Error( ERR_FATAL, "Bad origin in FS_Seek\n" );
1427
file = FS_FileForHandle(f);
1440
Com_Error( ERR_FATAL, "Bad origin in FS_Seek\n" );
1444
return fseek( file, offset, _origin );
1450
======================================================================================
1452
CONVENIENCE FUNCTIONS FOR ENTIRE FILES
1454
======================================================================================
1457
int FS_FileIsInPAK(const char *filename, int *pChecksum ) {
1458
searchpath_t *search;
1460
fileInPack_t *pakFile;
1463
if ( !fs_searchpaths ) {
1464
Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
1468
Com_Error( ERR_FATAL, "FS_FOpenFileRead: NULL 'filename' parameter passed\n" );
1471
// qpaths are not supposed to have a leading slash
1472
if ( filename[0] == '/' || filename[0] == '\\' ) {
1476
// make absolutely sure that it can't back up the path.
1477
// The searchpaths do guarantee that something will always
1478
// be prepended, so we don't need to worry about "c:" or "//limbo"
1479
if ( strstr( filename, ".." ) || strstr( filename, "::" ) ) {
1484
// search through the path, one element at a time
1487
for ( search = fs_searchpaths ; search ; search = search->next ) {
1490
hash = FS_HashFileName(filename, search->pack->hashSize);
1492
// is the element a pak file?
1493
if ( search->pack && search->pack->hashTable[hash] ) {
1494
// disregard if it doesn't match one of the allowed pure pak files
1495
if ( !FS_PakIsPure(search->pack) ) {
1499
// look through all the pak file elements
1501
pakFile = pak->hashTable[hash];
1503
// case and separator insensitive comparisons
1504
if ( !FS_FilenameCompare( pakFile->name, filename ) ) {
1506
*pChecksum = pak->pure_checksum;
1510
pakFile = pakFile->next;
1511
} while(pakFile != NULL);
1521
Filename are relative to the quake search path
1522
a null buffer will just return the file length without loading
1525
int FS_ReadFile( const char *qpath, void **buffer ) {
1531
if ( !fs_searchpaths ) {
1532
Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
1535
if ( !qpath || !qpath[0] ) {
1536
Com_Error( ERR_FATAL, "FS_ReadFile with empty name\n" );
1539
buf = NULL; // quiet compiler warning
1541
// if this is a .cfg file and we are playing back a journal, read
1542
// it from the journal file
1543
if ( strstr( qpath, ".cfg" ) ) {
1545
if ( com_journal && com_journal->integer == 2 ) {
1548
Com_DPrintf( "Loading %s from journal file.\n", qpath );
1549
r = FS_Read( &len, sizeof( len ), com_journalDataFile );
1550
if ( r != sizeof( len ) ) {
1551
if (buffer != NULL) *buffer = NULL;
1554
// if the file didn't exist when the journal was created
1556
if (buffer == NULL) {
1557
return 1; // hack for old journal files
1562
if (buffer == NULL) {
1566
buf = Hunk_AllocateTempMemory(len+1);
1569
r = FS_Read( buf, len, com_journalDataFile );
1571
Com_Error( ERR_FATAL, "Read from journalDataFile failed" );
1577
// guarantee that it will have a trailing 0 for string operations
1586
// look for it in the filesystem or pack files
1587
len = FS_FOpenFileRead( qpath, &h, qfalse );
1592
// if we are journalling and it is a config file, write a zero to the journal file
1593
if ( isConfig && com_journal && com_journal->integer == 1 ) {
1594
Com_DPrintf( "Writing zero for %s to journal file.\n", qpath );
1596
FS_Write( &len, sizeof( len ), com_journalDataFile );
1597
FS_Flush( com_journalDataFile );
1603
if ( isConfig && com_journal && com_journal->integer == 1 ) {
1604
Com_DPrintf( "Writing len for %s to journal file.\n", qpath );
1605
FS_Write( &len, sizeof( len ), com_journalDataFile );
1606
FS_Flush( com_journalDataFile );
1615
buf = Hunk_AllocateTempMemory(len+1);
1618
FS_Read (buf, len, h);
1620
// guarantee that it will have a trailing 0 for string operations
1624
// if we are journalling and it is a config file, write it to the journal file
1625
if ( isConfig && com_journal && com_journal->integer == 1 ) {
1626
Com_DPrintf( "Writing %s to journal file.\n", qpath );
1627
FS_Write( &len, sizeof( len ), com_journalDataFile );
1628
FS_Write( buf, len, com_journalDataFile );
1629
FS_Flush( com_journalDataFile );
1639
void FS_FreeFile( void *buffer ) {
1640
if ( !fs_searchpaths ) {
1641
Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
1644
Com_Error( ERR_FATAL, "FS_FreeFile( NULL )" );
1648
Hunk_FreeTempMemory( buffer );
1650
// if all of our temp files are free, clear all of our space
1651
if ( fs_loadStack == 0 ) {
1652
Hunk_ClearTempMemory();
1660
Filename are reletive to the quake search path
1663
void FS_WriteFile( const char *qpath, const void *buffer, int size ) {
1666
if ( !fs_searchpaths ) {
1667
Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
1670
if ( !qpath || !buffer ) {
1671
Com_Error( ERR_FATAL, "FS_WriteFile: NULL parameter" );
1674
f = FS_FOpenFileWrite( qpath );
1676
Com_Printf( "Failed to open %s\n", qpath );
1680
FS_Write( buffer, size, f );
1688
==========================================================================
1692
==========================================================================
1699
Creates a new pak_t in the search chain for the contents
1703
static pack_t *FS_LoadZipFile( char *zipfile, const char *basename )
1705
fileInPack_t *buildBuffer;
1710
char filename_inzip[MAX_ZPATH];
1711
unz_file_info file_info;
1714
int fs_numHeaderLongs;
1715
int *fs_headerLongs;
1718
fs_numHeaderLongs = 0;
1720
uf = unzOpen(zipfile);
1721
err = unzGetGlobalInfo (uf,&gi);
1726
fs_packFiles += gi.number_entry;
1729
unzGoToFirstFile(uf);
1730
for (i = 0; i < gi.number_entry; i++)
1732
err = unzGetCurrentFileInfo(uf, &file_info, filename_inzip, sizeof(filename_inzip), NULL, 0, NULL, 0);
1733
if (err != UNZ_OK) {
1736
len += strlen(filename_inzip) + 1;
1737
unzGoToNextFile(uf);
1740
buildBuffer = Z_Malloc( (gi.number_entry * sizeof( fileInPack_t )) + len );
1741
namePtr = ((char *) buildBuffer) + gi.number_entry * sizeof( fileInPack_t );
1742
fs_headerLongs = Z_Malloc( ( gi.number_entry + 1 ) * sizeof(int) );
1743
fs_headerLongs[ fs_numHeaderLongs++ ] = LittleLong( fs_checksumFeed );
1745
// get the hash table size from the number of files in the zip
1746
// because lots of custom pk3 files have less than 32 or 64 files
1747
for (i = 1; i <= MAX_FILEHASH_SIZE; i <<= 1) {
1748
if (i > gi.number_entry) {
1753
pack = Z_Malloc( sizeof( pack_t ) + i * sizeof(fileInPack_t *) );
1755
pack->hashTable = (fileInPack_t **) (((char *) pack) + sizeof( pack_t ));
1756
for(i = 0; i < pack->hashSize; i++) {
1757
pack->hashTable[i] = NULL;
1760
Q_strncpyz( pack->pakFilename, zipfile, sizeof( pack->pakFilename ) );
1761
Q_strncpyz( pack->pakBasename, basename, sizeof( pack->pakBasename ) );
1763
// strip .pk3 if needed
1764
if ( strlen( pack->pakBasename ) > 4 && !Q_stricmp( pack->pakBasename + strlen( pack->pakBasename ) - 4, ".pk3" ) ) {
1765
pack->pakBasename[strlen( pack->pakBasename ) - 4] = 0;
1769
pack->numfiles = gi.number_entry;
1770
unzGoToFirstFile(uf);
1772
for (i = 0; i < gi.number_entry; i++)
1774
err = unzGetCurrentFileInfo(uf, &file_info, filename_inzip, sizeof(filename_inzip), NULL, 0, NULL, 0);
1775
if (err != UNZ_OK) {
1778
if (file_info.uncompressed_size > 0) {
1779
fs_headerLongs[fs_numHeaderLongs++] = LittleLong(file_info.crc);
1781
Q_strlwr( filename_inzip );
1782
hash = FS_HashFileName(filename_inzip, pack->hashSize);
1783
buildBuffer[i].name = namePtr;
1784
strcpy( buildBuffer[i].name, filename_inzip );
1785
namePtr += strlen(filename_inzip) + 1;
1786
// store the file position in the zip
1787
unzGetCurrentFileInfoPosition(uf, &buildBuffer[i].pos);
1789
buildBuffer[i].next = pack->hashTable[hash];
1790
pack->hashTable[hash] = &buildBuffer[i];
1791
unzGoToNextFile(uf);
1794
pack->checksum = Com_BlockChecksum( &fs_headerLongs[ 1 ], 4 * ( fs_numHeaderLongs - 1 ) );
1795
pack->pure_checksum = Com_BlockChecksum( fs_headerLongs, 4 * fs_numHeaderLongs );
1796
pack->checksum = LittleLong( pack->checksum );
1797
pack->pure_checksum = LittleLong( pack->pure_checksum );
1799
Z_Free(fs_headerLongs);
1801
pack->buildBuffer = buildBuffer;
1806
=================================================================================
1808
DIRECTORY SCANNING FUNCTIONS
1810
=================================================================================
1813
#define MAX_FOUND_FILES 0x1000
1815
static int FS_ReturnPath( const char *zname, char *zpath, int *depth ) {
1816
int len, at, newdep;
1823
while(zname[at] != 0)
1825
if (zname[at]=='/' || zname[at]=='\\') {
1831
strcpy(zpath, zname);
1843
static int FS_AddFileToList( char *name, char *list[MAX_FOUND_FILES], int nfiles ) {
1846
if ( nfiles == MAX_FOUND_FILES - 1 ) {
1849
for ( i = 0 ; i < nfiles ; i++ ) {
1850
if ( !Q_stricmp( name, list[i] ) ) {
1851
return nfiles; // allready in list
1854
list[nfiles] = CopyString( name );
1862
FS_ListFilteredFiles
1864
Returns a uniqued list of files that match the given criteria
1865
from all search paths
1868
char **FS_ListFilteredFiles( const char *path, const char *extension, char *filter, int *numfiles ) {
1871
char *list[MAX_FOUND_FILES];
1872
searchpath_t *search;
1875
int extensionLength;
1876
int length, pathDepth, temp;
1878
fileInPack_t *buildBuffer;
1879
char zpath[MAX_ZPATH];
1881
if ( !fs_searchpaths ) {
1882
Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
1893
pathLength = strlen( path );
1894
if ( path[pathLength-1] == '\\' || path[pathLength-1] == '/' ) {
1897
extensionLength = strlen( extension );
1899
FS_ReturnPath(path, zpath, &pathDepth);
1902
// search through the path, one element at a time, adding to list
1904
for (search = fs_searchpaths ; search ; search = search->next) {
1905
// is the element a pak file?
1908
//ZOID: If we are pure, don't search for files on paks that
1909
// aren't on the pure list
1910
if ( !FS_PakIsPure(search->pack) ) {
1914
// look through all the pak file elements
1916
buildBuffer = pak->buildBuffer;
1917
for (i = 0; i < pak->numfiles; i++) {
1919
int zpathLen, depth;
1921
// check for directory match
1922
name = buildBuffer[i].name;
1926
if (!Com_FilterPath( filter, name, qfalse ))
1929
nfiles = FS_AddFileToList( name, list, nfiles );
1933
zpathLen = FS_ReturnPath(name, zpath, &depth);
1935
if ( (depth-pathDepth)>2 || pathLength > zpathLen || Q_stricmpn( name, path, pathLength ) ) {
1939
// check for extension match
1940
length = strlen( name );
1941
if ( length < extensionLength ) {
1945
if ( Q_stricmp( name + length - extensionLength, extension ) ) {
1952
temp++; // include the '/'
1954
nfiles = FS_AddFileToList( name + temp, list, nfiles );
1957
} else if (search->dir) { // scan for files in the filesystem
1963
// don't scan directories for files if we are pure or restricted
1964
if ( fs_restrict->integer || fs_numServerPaks ) {
1967
netpath = FS_BuildOSPath( search->dir->path, search->dir->gamedir, path );
1968
sysFiles = Sys_ListFiles( netpath, extension, filter, &numSysFiles, qfalse );
1969
for ( i = 0 ; i < numSysFiles ; i++ ) {
1972
nfiles = FS_AddFileToList( name, list, nfiles );
1974
Sys_FreeFileList( sysFiles );
1979
// return a copy of the list
1986
listCopy = Z_Malloc( ( nfiles + 1 ) * sizeof( *listCopy ) );
1987
for ( i = 0 ; i < nfiles ; i++ ) {
1988
listCopy[i] = list[i];
2000
char **FS_ListFiles( const char *path, const char *extension, int *numfiles ) {
2001
return FS_ListFilteredFiles( path, extension, NULL, numfiles );
2009
void FS_FreeFileList( char **list ) {
2012
if ( !fs_searchpaths ) {
2013
Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
2020
for ( i = 0 ; list[i] ; i++ ) {
2033
int FS_GetFileList( const char *path, const char *extension, char *listbuf, int bufsize ) {
2034
int nFiles, i, nTotal, nLen;
2035
char **pFiles = NULL;
2041
if (Q_stricmp(path, "$modlist") == 0) {
2042
return FS_GetModList(listbuf, bufsize);
2045
pFiles = FS_ListFiles(path, extension, &nFiles);
2047
for (i =0; i < nFiles; i++) {
2048
nLen = strlen(pFiles[i]) + 1;
2049
if (nTotal + nLen + 1 < bufsize) {
2050
strcpy(listbuf, pFiles[i]);
2060
FS_FreeFileList(pFiles);
2066
=======================
2067
Sys_ConcatenateFileLists
2069
mkv: Naive implementation. Concatenates three lists into a
2070
new list, and frees the old lists from the heap.
2071
bk001129 - from cvs1.17 (mkv)
2073
FIXME TTimo those two should move to common.c next to Sys_ListFiles
2074
=======================
2076
static unsigned int Sys_CountFileList(char **list)
2091
static char** Sys_ConcatenateFileLists( char **list0, char **list1, char **list2 )
2093
int totalLength = 0;
2094
char** cat = NULL, **dst, **src;
2096
totalLength += Sys_CountFileList(list0);
2097
totalLength += Sys_CountFileList(list1);
2098
totalLength += Sys_CountFileList(list2);
2100
/* Create new list. */
2101
dst = cat = Z_Malloc( ( totalLength + 1 ) * sizeof( char* ) );
2103
/* Copy over lists. */
2106
for (src = list0; *src; src++, dst++)
2111
for (src = list1; *src; src++, dst++)
2116
for (src = list2; *src; src++, dst++)
2120
// Terminate the list
2123
// Free our old lists.
2124
// NOTE: not freeing their content, it's been merged in dst and still being used
2125
if (list0) Z_Free( list0 );
2126
if (list1) Z_Free( list1 );
2127
if (list2) Z_Free( list2 );
2136
Returns a list of mod directory names
2137
A mod directory is a peer to baseq3 with a pk3 in it
2138
The directories are searched in base path, cd path and home path
2141
int FS_GetModList( char *listbuf, int bufsize ) {
2142
int nMods, i, j, nTotal, nLen, nPaks, nPotential, nDescLen;
2143
char **pFiles = NULL;
2144
char **pPaks = NULL;
2146
char descPath[MAX_OSPATH];
2147
fileHandle_t descHandle;
2150
char **pFiles0 = NULL;
2151
char **pFiles1 = NULL;
2152
char **pFiles2 = NULL;
2153
qboolean bDrop = qfalse;
2156
nMods = nPotential = nTotal = 0;
2158
pFiles0 = Sys_ListFiles( fs_homepath->string, NULL, NULL, &dummy, qtrue );
2159
pFiles1 = Sys_ListFiles( fs_basepath->string, NULL, NULL, &dummy, qtrue );
2160
pFiles2 = Sys_ListFiles( fs_cdpath->string, NULL, NULL, &dummy, qtrue );
2161
// we searched for mods in the three paths
2162
// it is likely that we have duplicate names now, which we will cleanup below
2163
pFiles = Sys_ConcatenateFileLists( pFiles0, pFiles1, pFiles2 );
2164
nPotential = Sys_CountFileList(pFiles);
2166
for ( i = 0 ; i < nPotential ; i++ ) {
2168
// NOTE: cleaner would involve more changes
2169
// ignore duplicate mod directories
2174
if (Q_stricmp(pFiles[j],name)==0) {
2175
// this one can be dropped
2184
// we drop "baseq3" "." and ".."
2185
if (Q_stricmp(name, BASEGAME) && Q_stricmpn(name, ".", 1)) {
2186
// now we need to find some .pk3 files to validate the mod
2187
// NOTE TTimo: (actually I'm not sure why .. what if it's a mod under developement with no .pk3?)
2188
// we didn't keep the information when we merged the directory names, as to what OS Path it was found under
2189
// so it could be in base path, cd path or home path
2190
// we will try each three of them here (yes, it's a bit messy)
2191
path = FS_BuildOSPath( fs_basepath->string, name, "" );
2193
pPaks = Sys_ListFiles(path, ".pk3", NULL, &nPaks, qfalse);
2194
Sys_FreeFileList( pPaks ); // we only use Sys_ListFiles to check wether .pk3 files are present
2196
/* Try on cd path */
2198
path = FS_BuildOSPath( fs_cdpath->string, name, "" );
2200
pPaks = Sys_ListFiles( path, ".pk3", NULL, &nPaks, qfalse );
2201
Sys_FreeFileList( pPaks );
2204
/* try on home path */
2207
path = FS_BuildOSPath( fs_homepath->string, name, "" );
2209
pPaks = Sys_ListFiles( path, ".pk3", NULL, &nPaks, qfalse );
2210
Sys_FreeFileList( pPaks );
2214
nLen = strlen(name) + 1;
2215
// nLen is the length of the mod path
2216
// we need to see if there is a description available
2218
strcpy(descPath, name);
2219
strcat(descPath, "/description.txt");
2220
nDescLen = FS_SV_FOpenFileRead( descPath, &descHandle );
2221
if ( nDescLen > 0 && descHandle) {
2223
file = FS_FileForHandle(descHandle);
2224
Com_Memset( descPath, 0, sizeof( descPath ) );
2225
nDescLen = fread(descPath, 1, 48, file);
2226
if (nDescLen >= 0) {
2227
descPath[nDescLen] = '\0';
2229
FS_FCloseFile(descHandle);
2231
strcpy(descPath, name);
2233
nDescLen = strlen(descPath) + 1;
2235
if (nTotal + nLen + 1 + nDescLen + 1 < bufsize) {
2236
strcpy(listbuf, name);
2238
strcpy(listbuf, descPath);
2239
listbuf += nDescLen;
2240
nTotal += nLen + nDescLen;
2249
Sys_FreeFileList( pFiles );
2257
//============================================================================
2264
void FS_Dir_f( void ) {
2271
if ( Cmd_Argc() < 2 || Cmd_Argc() > 3 ) {
2272
Com_Printf( "usage: dir <directory> [extension]\n" );
2276
if ( Cmd_Argc() == 2 ) {
2277
path = Cmd_Argv( 1 );
2280
path = Cmd_Argv( 1 );
2281
extension = Cmd_Argv( 2 );
2284
Com_Printf( "Directory of %s %s\n", path, extension );
2285
Com_Printf( "---------------\n" );
2287
dirnames = FS_ListFiles( path, extension, &ndirs );
2289
for ( i = 0; i < ndirs; i++ ) {
2290
Com_Printf( "%s\n", dirnames[i] );
2292
FS_FreeFileList( dirnames );
2300
void FS_ConvertPath( char *s ) {
2302
if ( *s == '\\' || *s == ':' ) {
2313
Ignore case and seprator char distinctions
2316
int FS_PathCmp( const char *s1, const char *s2 ) {
2323
if (c1 >= 'a' && c1 <= 'z') {
2326
if (c2 >= 'a' && c2 <= 'z') {
2330
if ( c1 == '\\' || c1 == ':' ) {
2333
if ( c2 == '\\' || c2 == ':' ) {
2338
return -1; // strings not equal
2345
return 0; // strings are equal
2353
void FS_SortFileList(char **filelist, int numfiles) {
2354
int i, j, k, numsortedfiles;
2357
sortedlist = Z_Malloc( ( numfiles + 1 ) * sizeof( *sortedlist ) );
2358
sortedlist[0] = NULL;
2360
for (i = 0; i < numfiles; i++) {
2361
for (j = 0; j < numsortedfiles; j++) {
2362
if (FS_PathCmp(filelist[i], sortedlist[j]) < 0) {
2366
for (k = numsortedfiles; k > j; k--) {
2367
sortedlist[k] = sortedlist[k-1];
2369
sortedlist[j] = filelist[i];
2372
Com_Memcpy(filelist, sortedlist, numfiles * sizeof( *filelist ) );
2381
void FS_NewDir_f( void ) {
2387
if ( Cmd_Argc() < 2 ) {
2388
Com_Printf( "usage: fdir <filter>\n" );
2389
Com_Printf( "example: fdir *q3dm*.bsp\n");
2393
filter = Cmd_Argv( 1 );
2395
Com_Printf( "---------------\n" );
2397
dirnames = FS_ListFilteredFiles( "", "", filter, &ndirs );
2399
FS_SortFileList(dirnames, ndirs);
2401
for ( i = 0; i < ndirs; i++ ) {
2402
FS_ConvertPath(dirnames[i]);
2403
Com_Printf( "%s\n", dirnames[i] );
2405
Com_Printf( "%d files listed\n", ndirs );
2406
FS_FreeFileList( dirnames );
2415
void FS_Path_f( void ) {
2419
Com_Printf ("Current search path:\n");
2420
for (s = fs_searchpaths; s; s = s->next) {
2422
Com_Printf ("%s (%i files)\n", s->pack->pakFilename, s->pack->numfiles);
2423
if ( fs_numServerPaks ) {
2424
if ( !FS_PakIsPure(s->pack) ) {
2425
Com_Printf( " not on the pure list\n" );
2427
Com_Printf( " on the pure list\n" );
2431
Com_Printf ("%s/%s\n", s->dir->path, s->dir->gamedir );
2437
for ( i = 1 ; i < MAX_FILE_HANDLES ; i++ ) {
2438
if ( fsh[i].handleFiles.file.o ) {
2439
Com_Printf( "handle %i: %s\n", i, fsh[i].name );
2448
The only purpose of this function is to allow game script files to copy
2449
arbitrary files furing an "fs_copyfiles 1" run.
2452
void FS_TouchFile_f( void ) {
2455
if ( Cmd_Argc() != 2 ) {
2456
Com_Printf( "Usage: touchFile <file>\n" );
2460
FS_FOpenFileRead( Cmd_Argv( 1 ), &f, qfalse );
2466
//===========================================================================
2469
static int QDECL paksort( const void *a, const void *b ) {
2475
return FS_PathCmp( aa, bb );
2482
Sets fs_gamedir, adds the directory to the head of the path,
2483
then loads the zip headers
2486
#define MAX_PAKFILES 1024
2487
static void FS_AddGameDirectory( const char *path, const char *dir ) {
2490
searchpath_t *search;
2495
char *sorted[MAX_PAKFILES];
2497
// this fixes the case where fs_basepath is the same as fs_cdpath
2498
// which happens on full installs
2499
for ( sp = fs_searchpaths ; sp ; sp = sp->next ) {
2500
if ( sp->dir && !Q_stricmp(sp->dir->path, path) && !Q_stricmp(sp->dir->gamedir, dir)) {
2501
return; // we've already got this one
2505
Q_strncpyz( fs_gamedir, dir, sizeof( fs_gamedir ) );
2508
// add the directory to the search path
2510
search = Z_Malloc (sizeof(searchpath_t));
2511
search->dir = Z_Malloc( sizeof( *search->dir ) );
2513
Q_strncpyz( search->dir->path, path, sizeof( search->dir->path ) );
2514
Q_strncpyz( search->dir->gamedir, dir, sizeof( search->dir->gamedir ) );
2515
search->next = fs_searchpaths;
2516
fs_searchpaths = search;
2518
// find all pak files in this directory
2519
pakfile = FS_BuildOSPath( path, dir, "" );
2520
pakfile[ strlen(pakfile) - 1 ] = 0; // strip the trailing slash
2522
pakfiles = Sys_ListFiles( pakfile, ".pk3", NULL, &numfiles, qfalse );
2524
// sort them so that later alphabetic matches override
2525
// earlier ones. This makes pak1.pk3 override pak0.pk3
2526
if ( numfiles > MAX_PAKFILES ) {
2527
numfiles = MAX_PAKFILES;
2529
for ( i = 0 ; i < numfiles ; i++ ) {
2530
sorted[i] = pakfiles[i];
2533
qsort( sorted, numfiles, sizeof(char*), paksort );
2535
for ( i = 0 ; i < numfiles ; i++ ) {
2536
pakfile = FS_BuildOSPath( path, dir, sorted[i] );
2537
if ( ( pak = FS_LoadZipFile( pakfile, sorted[i] ) ) == 0 )
2539
// store the game name for downloading
2540
strcpy(pak->pakGamename, dir);
2542
search = Z_Malloc (sizeof(searchpath_t));
2544
search->next = fs_searchpaths;
2545
fs_searchpaths = search;
2549
Sys_FreeFileList( pakfiles );
2557
qboolean FS_idPak( char *pak, char *base ) {
2560
for (i = 0; i < NUM_ID_PAKS; i++) {
2561
if ( !FS_FilenameCompare(pak, va("%s/pak%d", base, i)) ) {
2565
if (i < NUM_ID_PAKS) {
2575
Check whether the string contains stuff like "../" to prevent directory traversal bugs
2576
and return qtrue if it does.
2580
qboolean FS_CheckDirTraversal(const char *checkdir)
2582
if(strstr(checkdir, "../") || strstr(checkdir, "..\\"))
2595
Returns a list of pak files that we should download from the server. They all get stored
2596
in the current gamedir and an FS_Restart will be fired up after we download them all.
2598
The string is the format:
2600
@remotename@localname [repeat]
2602
static int fs_numServerReferencedPaks;
2603
static int fs_serverReferencedPaks[MAX_SEARCH_PATHS];
2604
static char *fs_serverReferencedPakNames[MAX_SEARCH_PATHS];
2609
we are not interested in a download string format, we want something human-readable
2610
(this is used for diagnostics while connecting to a pure server)
2614
qboolean FS_ComparePaks( char *neededpaks, int len, qboolean dlstring ) {
2616
qboolean havepak, badchecksum;
2617
char *origpos = neededpaks;
2620
if (!fs_numServerReferencedPaks)
2621
return qfalse; // Server didn't send any pack information along
2625
for ( i = 0 ; i < fs_numServerReferencedPaks ; i++ )
2627
// Ok, see if we have this pak file
2628
badchecksum = qfalse;
2631
// never autodownload any of the id paks
2632
if ( FS_idPak(fs_serverReferencedPakNames[i], BASEGAME) || FS_idPak(fs_serverReferencedPakNames[i], "missionpack") ) {
2636
// Make sure the server cannot make us write to non-quake3 directories.
2637
if(FS_CheckDirTraversal(fs_serverReferencedPakNames[i]))
2639
Com_Printf("WARNING: Invalid download name %s\n", fs_serverReferencedPakNames[i]);
2643
for ( sp = fs_searchpaths ; sp ; sp = sp->next ) {
2644
if ( sp->pack && sp->pack->checksum == fs_serverReferencedPaks[i] ) {
2645
havepak = qtrue; // This is it!
2650
if ( !havepak && fs_serverReferencedPakNames[i] && *fs_serverReferencedPakNames[i] ) {
2655
// We need this to make sure we won't hit the end of the buffer or the server could
2656
// overwrite non-pk3 files on clients by writing so much crap into neededpaks that
2657
// Q_strcat cuts off the .pk3 extension.
2659
origpos += strlen(origpos);
2662
Q_strcat( neededpaks, len, "@");
2663
Q_strcat( neededpaks, len, fs_serverReferencedPakNames[i] );
2664
Q_strcat( neededpaks, len, ".pk3" );
2667
Q_strcat( neededpaks, len, "@");
2668
// Do we have one with the same name?
2669
if ( FS_SV_FileExists( va( "%s.pk3", fs_serverReferencedPakNames[i] ) ) )
2672
// We already have one called this, we need to download it to another name
2673
// Make something up with the checksum in it
2674
Com_sprintf( st, sizeof( st ), "%s.%08x.pk3", fs_serverReferencedPakNames[i], fs_serverReferencedPaks[i] );
2675
Q_strcat( neededpaks, len, st );
2678
Q_strcat( neededpaks, len, fs_serverReferencedPakNames[i] );
2679
Q_strcat( neededpaks, len, ".pk3" );
2682
// Find out whether it might have overflowed the buffer and don't add this file to the
2683
// list if that is the case.
2684
if(strlen(origpos) + (origpos - neededpaks) >= len - 1)
2692
Q_strcat( neededpaks, len, fs_serverReferencedPakNames[i] );
2693
Q_strcat( neededpaks, len, ".pk3" );
2694
// Do we have one with the same name?
2695
if ( FS_SV_FileExists( va( "%s.pk3", fs_serverReferencedPakNames[i] ) ) )
2697
Q_strcat( neededpaks, len, " (local file exists with wrong checksum)");
2699
Q_strcat( neededpaks, len, "\n");
2704
if ( *neededpaks ) {
2708
return qfalse; // We have them all
2715
Frees all resources.
2718
void FS_Shutdown( qboolean closemfp ) {
2719
searchpath_t *p, *next;
2722
for(i = 0; i < MAX_FILE_HANDLES; i++) {
2723
if (fsh[i].fileSize) {
2729
for ( p = fs_searchpaths ; p ; p = next ) {
2733
unzClose(p->pack->handle);
2734
Z_Free( p->pack->buildBuffer );
2743
// any FS_ calls will now be an error until reinitialized
2744
fs_searchpaths = NULL;
2746
Cmd_RemoveCommand( "path" );
2747
Cmd_RemoveCommand( "dir" );
2748
Cmd_RemoveCommand( "fdir" );
2749
Cmd_RemoveCommand( "touchFile" );
2753
fclose(missingFiles);
2758
void Com_AppendCDKey( const char *filename );
2759
void Com_ReadCDKey( const char *filename );
2764
NOTE TTimo: the reordering that happens here is not reflected in the cvars (\cvarlist *pak*)
2765
this can lead to misleading situations, see https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=540
2768
static void FS_ReorderPurePaks( void )
2772
searchpath_t **p_insert_index, // for linked list reordering
2773
**p_previous; // when doing the scan
2775
// only relevant when connected to pure server
2776
if ( !fs_numServerPaks )
2779
fs_reordered = qfalse;
2781
p_insert_index = &fs_searchpaths; // we insert in order at the beginning of the list
2782
for ( i = 0 ; i < fs_numServerPaks ; i++ ) {
2783
p_previous = p_insert_index; // track the pointer-to-current-item
2784
for (s = *p_insert_index; s; s = s->next) {
2785
// the part of the list before p_insert_index has been sorted already
2786
if (s->pack && fs_serverPaks[i] == s->pack->checksum) {
2787
fs_reordered = qtrue;
2788
// move this element to the insert list
2789
*p_previous = s->next;
2790
s->next = *p_insert_index;
2791
*p_insert_index = s;
2792
// increment insert list
2793
p_insert_index = &s->next;
2794
break; // iterate to next server pack
2796
p_previous = &s->next;
2806
static void FS_Startup( const char *gameName ) {
2807
const char *homePath;
2810
Com_Printf( "----- FS_Startup -----\n" );
2812
fs_debug = Cvar_Get( "fs_debug", "0", 0 );
2813
fs_copyfiles = Cvar_Get( "fs_copyfiles", "0", CVAR_INIT );
2814
fs_cdpath = Cvar_Get ("fs_cdpath", Sys_DefaultCDPath(), CVAR_INIT );
2815
fs_basepath = Cvar_Get ("fs_basepath", Sys_DefaultInstallPath(), CVAR_INIT );
2816
fs_basegame = Cvar_Get ("fs_basegame", "", CVAR_INIT );
2817
homePath = Sys_DefaultHomePath();
2818
if (!homePath || !homePath[0]) {
2819
homePath = fs_basepath->string;
2821
fs_homepath = Cvar_Get ("fs_homepath", homePath, CVAR_INIT );
2822
fs_gamedirvar = Cvar_Get ("fs_game", "", CVAR_INIT|CVAR_SYSTEMINFO );
2823
fs_restrict = Cvar_Get ("fs_restrict", "", CVAR_INIT );
2825
// add search path elements in reverse priority order
2826
if (fs_cdpath->string[0]) {
2827
FS_AddGameDirectory( fs_cdpath->string, gameName );
2829
if (fs_basepath->string[0]) {
2830
FS_AddGameDirectory( fs_basepath->string, gameName );
2832
// fs_homepath is somewhat particular to *nix systems, only add if relevant
2833
// NOTE: same filtering below for mods and basegame
2834
if (fs_basepath->string[0] && Q_stricmp(fs_homepath->string,fs_basepath->string)) {
2835
FS_AddGameDirectory ( fs_homepath->string, gameName );
2838
// check for additional base game so mods can be based upon other mods
2839
if ( fs_basegame->string[0] && !Q_stricmp( gameName, BASEGAME ) && Q_stricmp( fs_basegame->string, gameName ) ) {
2840
if (fs_cdpath->string[0]) {
2841
FS_AddGameDirectory(fs_cdpath->string, fs_basegame->string);
2843
if (fs_basepath->string[0]) {
2844
FS_AddGameDirectory(fs_basepath->string, fs_basegame->string);
2846
if (fs_homepath->string[0] && Q_stricmp(fs_homepath->string,fs_basepath->string)) {
2847
FS_AddGameDirectory(fs_homepath->string, fs_basegame->string);
2851
// check for additional game folder for mods
2852
if ( fs_gamedirvar->string[0] && !Q_stricmp( gameName, BASEGAME ) && Q_stricmp( fs_gamedirvar->string, gameName ) ) {
2853
if (fs_cdpath->string[0]) {
2854
FS_AddGameDirectory(fs_cdpath->string, fs_gamedirvar->string);
2856
if (fs_basepath->string[0]) {
2857
FS_AddGameDirectory(fs_basepath->string, fs_gamedirvar->string);
2859
if (fs_homepath->string[0] && Q_stricmp(fs_homepath->string,fs_basepath->string)) {
2860
FS_AddGameDirectory(fs_homepath->string, fs_gamedirvar->string);
2864
Com_ReadCDKey(BASEGAME);
2865
fs = Cvar_Get ("fs_game", "", CVAR_INIT|CVAR_SYSTEMINFO );
2866
if (fs && fs->string[0] != 0) {
2867
Com_AppendCDKey( fs->string );
2871
Cmd_AddCommand ("path", FS_Path_f);
2872
Cmd_AddCommand ("dir", FS_Dir_f );
2873
Cmd_AddCommand ("fdir", FS_NewDir_f );
2874
Cmd_AddCommand ("touchFile", FS_TouchFile_f );
2876
// https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=506
2877
// reorder the pure pk3 files according to server order
2878
FS_ReorderPurePaks();
2880
// print the current search paths
2883
fs_gamedirvar->modified = qfalse; // We just loaded, it's not modified
2885
Com_Printf( "----------------------\n" );
2888
if (missingFiles == NULL) {
2889
missingFiles = fopen( "\\missing.txt", "ab" );
2892
Com_Printf( "%d files in pk3 files\n", fs_packFiles );
2899
Checks that pak0.pk3 is present and its checksum is correct
2900
Note: If you're building a game that doesn't depend on the
2901
Q3 media pak0.pk3, you'll want to remove this function
2904
static void FS_CheckPak0( void )
2908
qboolean foundPak0 = qfalse;
2910
for( path = fs_searchpaths; path; path = path->next ) {
2912
!Q_stricmpn( path->pack->pakBasename, "pak0", MAX_OSPATH ) &&
2913
(!Q_stricmpn( path->pack->pakGamename, BASEGAME, MAX_OSPATH ) ||
2914
!Q_stricmpn( path->pack->pakGamename, "demoq3", MAX_OSPATH ))) {
2917
if( path->pack->checksum == DEMO_PAK0_CHECKSUM ) {
2919
"**************************************************\n"
2920
"WARNING: It looks like you're using pak0.pk3\n"
2921
"from the demo. This may work fine, but it is not\n"
2922
"guaranteed or supported.\n"
2923
"**************************************************\n\n\n" );
2924
} else if( path->pack->checksum != PAK0_CHECKSUM ) {
2926
"**************************************************\n"
2927
"WARNING: pak0.pk3 is present but its checksum (%u)\n"
2928
"is not correct. Please re-copy pak0.pk3 from your\n"
2929
"legitimate Q3 CDROM.\n"
2930
"**************************************************\n\n\n",
2931
path->pack->checksum );
2937
Com_Error( ERR_FATAL, "Couldn't find pak0.pk3. Check that your Q3\n"
2938
"executable is in the correct place and that every file\n"
2939
"in the %s directory is present and readable.", BASEGAME);
2945
=====================
2948
Returns the checksum of the pk3 from which the server loaded the qagame.qvm
2949
=====================
2951
const char *FS_GamePureChecksum( void ) {
2952
static char info[MAX_STRING_TOKENS];
2953
searchpath_t *search;
2957
for ( search = fs_searchpaths ; search ; search = search->next ) {
2958
// is the element a pak file?
2959
if ( search->pack ) {
2960
if (search->pack->referenced & FS_QAGAME_REF) {
2961
Com_sprintf(info, sizeof(info), "%d", search->pack->checksum);
2970
=====================
2971
FS_LoadedPakChecksums
2973
Returns a space separated string containing the checksums of all loaded pk3 files.
2974
Servers with sv_pure set will get this string and pass it to clients.
2975
=====================
2977
const char *FS_LoadedPakChecksums( void ) {
2978
static char info[BIG_INFO_STRING];
2979
searchpath_t *search;
2983
for ( search = fs_searchpaths ; search ; search = search->next ) {
2984
// is the element a pak file?
2985
if ( !search->pack ) {
2989
Q_strcat( info, sizeof( info ), va("%i ", search->pack->checksum ) );
2996
=====================
2999
Returns a space separated string containing the names of all loaded pk3 files.
3000
Servers with sv_pure set will get this string and pass it to clients.
3001
=====================
3003
const char *FS_LoadedPakNames( void ) {
3004
static char info[BIG_INFO_STRING];
3005
searchpath_t *search;
3009
for ( search = fs_searchpaths ; search ; search = search->next ) {
3010
// is the element a pak file?
3011
if ( !search->pack ) {
3016
Q_strcat(info, sizeof( info ), " " );
3018
Q_strcat( info, sizeof( info ), search->pack->pakBasename );
3025
=====================
3026
FS_LoadedPakPureChecksums
3028
Returns a space separated string containing the pure checksums of all loaded pk3 files.
3029
Servers with sv_pure use these checksums to compare with the checksums the clients send
3031
=====================
3033
const char *FS_LoadedPakPureChecksums( void ) {
3034
static char info[BIG_INFO_STRING];
3035
searchpath_t *search;
3039
for ( search = fs_searchpaths ; search ; search = search->next ) {
3040
// is the element a pak file?
3041
if ( !search->pack ) {
3045
Q_strcat( info, sizeof( info ), va("%i ", search->pack->pure_checksum ) );
3052
=====================
3053
FS_ReferencedPakChecksums
3055
Returns a space separated string containing the checksums of all referenced pk3 files.
3056
The server will send this to the clients so they can check which files should be auto-downloaded.
3057
=====================
3059
const char *FS_ReferencedPakChecksums( void ) {
3060
static char info[BIG_INFO_STRING];
3061
searchpath_t *search;
3066
for ( search = fs_searchpaths ; search ; search = search->next ) {
3067
// is the element a pak file?
3068
if ( search->pack ) {
3069
if (search->pack->referenced || Q_stricmpn(search->pack->pakGamename, BASEGAME, strlen(BASEGAME))) {
3070
Q_strcat( info, sizeof( info ), va("%i ", search->pack->checksum ) );
3079
=====================
3080
FS_ReferencedPakPureChecksums
3082
Returns a space separated string containing the pure checksums of all referenced pk3 files.
3083
Servers with sv_pure set will get this string back from clients for pure validation
3085
The string has a specific order, "cgame ui @ ref1 ref2 ref3 ..."
3086
=====================
3088
const char *FS_ReferencedPakPureChecksums( void ) {
3089
static char info[BIG_INFO_STRING];
3090
searchpath_t *search;
3091
int nFlags, numPaks, checksum;
3095
checksum = fs_checksumFeed;
3097
for (nFlags = FS_CGAME_REF; nFlags; nFlags = nFlags >> 1) {
3098
if (nFlags & FS_GENERAL_REF) {
3099
// add a delimter between must haves and general refs
3100
//Q_strcat(info, sizeof(info), "@ ");
3101
info[strlen(info)+1] = '\0';
3102
info[strlen(info)+2] = '\0';
3103
info[strlen(info)] = '@';
3104
info[strlen(info)] = ' ';
3106
for ( search = fs_searchpaths ; search ; search = search->next ) {
3107
// is the element a pak file and has it been referenced based on flag?
3108
if ( search->pack && (search->pack->referenced & nFlags)) {
3109
Q_strcat( info, sizeof( info ), va("%i ", search->pack->pure_checksum ) );
3110
if (nFlags & (FS_CGAME_REF | FS_UI_REF)) {
3113
checksum ^= search->pack->pure_checksum;
3117
if (fs_fakeChkSum != 0) {
3118
// only added if a non-pure file is referenced
3119
Q_strcat( info, sizeof( info ), va("%i ", fs_fakeChkSum ) );
3122
// last checksum is the encoded number of referenced pk3s
3123
checksum ^= numPaks;
3124
Q_strcat( info, sizeof( info ), va("%i ", checksum ) );
3130
=====================
3131
FS_ReferencedPakNames
3133
Returns a space separated string containing the names of all referenced pk3 files.
3134
The server will send this to the clients so they can check which files should be auto-downloaded.
3135
=====================
3137
const char *FS_ReferencedPakNames( void ) {
3138
static char info[BIG_INFO_STRING];
3139
searchpath_t *search;
3143
// we want to return ALL pk3's from the fs_game path
3144
// and referenced one's from baseq3
3145
for ( search = fs_searchpaths ; search ; search = search->next ) {
3146
// is the element a pak file?
3147
if ( search->pack ) {
3149
Q_strcat(info, sizeof( info ), " " );
3151
if (search->pack->referenced || Q_stricmpn(search->pack->pakGamename, BASEGAME, strlen(BASEGAME))) {
3152
Q_strcat( info, sizeof( info ), search->pack->pakGamename );
3153
Q_strcat( info, sizeof( info ), "/" );
3154
Q_strcat( info, sizeof( info ), search->pack->pakBasename );
3163
=====================
3164
FS_ClearPakReferences
3165
=====================
3167
void FS_ClearPakReferences( int flags ) {
3168
searchpath_t *search;
3173
for ( search = fs_searchpaths; search; search = search->next ) {
3174
// is the element a pak file and has it been referenced?
3175
if ( search->pack ) {
3176
search->pack->referenced &= ~flags;
3183
=====================
3184
FS_PureServerSetLoadedPaks
3186
If the string is empty, all data sources will be allowed.
3187
If not empty, only pk3 files that match one of the space
3188
separated checksums will be checked for files, with the
3189
exception of .cfg and .dat files.
3190
=====================
3192
void FS_PureServerSetLoadedPaks( const char *pakSums, const char *pakNames ) {
3195
Cmd_TokenizeString( pakSums );
3198
if ( c > MAX_SEARCH_PATHS ) {
3199
c = MAX_SEARCH_PATHS;
3202
fs_numServerPaks = c;
3204
for ( i = 0 ; i < c ; i++ ) {
3205
fs_serverPaks[i] = atoi( Cmd_Argv( i ) );
3208
if (fs_numServerPaks) {
3209
Com_DPrintf( "Connected to a pure server.\n" );
3215
// https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=540
3216
// force a restart to make sure the search order will be correct
3217
Com_DPrintf( "FS search reorder is required\n" );
3218
FS_Restart(fs_checksumFeed);
3223
for ( i = 0 ; i < c ; i++ ) {
3224
if (fs_serverPakNames[i]) {
3225
Z_Free(fs_serverPakNames[i]);
3227
fs_serverPakNames[i] = NULL;
3229
if ( pakNames && *pakNames ) {
3230
Cmd_TokenizeString( pakNames );
3233
if ( d > MAX_SEARCH_PATHS ) {
3234
d = MAX_SEARCH_PATHS;
3237
for ( i = 0 ; i < d ; i++ ) {
3238
fs_serverPakNames[i] = CopyString( Cmd_Argv( i ) );
3244
=====================
3245
FS_PureServerSetReferencedPaks
3247
The checksums and names of the pk3 files referenced at the server
3248
are sent to the client and stored here. The client will use these
3249
checksums to see if any pk3 files need to be auto-downloaded.
3250
=====================
3252
void FS_PureServerSetReferencedPaks( const char *pakSums, const char *pakNames ) {
3255
Cmd_TokenizeString( pakSums );
3258
if ( c > MAX_SEARCH_PATHS ) {
3259
c = MAX_SEARCH_PATHS;
3262
for ( i = 0 ; i < c ; i++ ) {
3263
fs_serverReferencedPaks[i] = atoi( Cmd_Argv( i ) );
3266
for (i = 0 ; i < sizeof(fs_serverReferencedPakNames) / sizeof(*fs_serverReferencedPakNames); i++)
3268
if(fs_serverReferencedPakNames[i])
3269
Z_Free(fs_serverReferencedPakNames[i]);
3271
fs_serverReferencedPakNames[i] = NULL;
3274
if ( pakNames && *pakNames ) {
3275
Cmd_TokenizeString( pakNames );
3282
for ( i = 0 ; i < d ; i++ ) {
3283
fs_serverReferencedPakNames[i] = CopyString( Cmd_Argv( i ) );
3287
// ensure that there are as many checksums as there are pak names.
3291
fs_numServerReferencedPaks = c;
3298
Called only at inital startup, not when the filesystem
3299
is resetting due to a game change
3302
void FS_InitFilesystem( void ) {
3303
// allow command line parms to override our defaults
3304
// we have to specially handle this, because normal command
3305
// line variable sets don't happen until after the filesystem
3306
// has already been initialized
3307
Com_StartupVariable( "fs_cdpath" );
3308
Com_StartupVariable( "fs_basepath" );
3309
Com_StartupVariable( "fs_homepath" );
3310
Com_StartupVariable( "fs_game" );
3311
Com_StartupVariable( "fs_copyfiles" );
3312
Com_StartupVariable( "fs_restrict" );
3314
// try to start up normally
3315
FS_Startup( BASEGAME );
3319
// if we can't find default.cfg, assume that the paths are
3320
// busted and error out now, rather than getting an unreadable
3321
// graphics screen when the font fails to load
3322
if ( FS_ReadFile( "default.cfg", NULL ) <= 0 ) {
3323
Com_Error( ERR_FATAL, "Couldn't load default.cfg" );
3324
// bk001208 - SafeMode see below, FIXME?
3327
Q_strncpyz(lastValidBase, fs_basepath->string, sizeof(lastValidBase));
3328
Q_strncpyz(lastValidGame, fs_gamedirvar->string, sizeof(lastValidGame));
3330
// bk001208 - SafeMode see below, FIXME?
3339
void FS_Restart( int checksumFeed ) {
3341
// free anything we currently have loaded
3342
FS_Shutdown(qfalse);
3344
// set the checksum feed
3345
fs_checksumFeed = checksumFeed;
3347
// clear pak references
3348
FS_ClearPakReferences(0);
3350
// try to start up normally
3351
FS_Startup( BASEGAME );
3355
// if we can't find default.cfg, assume that the paths are
3356
// busted and error out now, rather than getting an unreadable
3357
// graphics screen when the font fails to load
3358
if ( FS_ReadFile( "default.cfg", NULL ) <= 0 ) {
3359
// this might happen when connecting to a pure server not using BASEGAME/pak0.pk3
3360
// (for instance a TA demo server)
3361
if (lastValidBase[0]) {
3362
FS_PureServerSetLoadedPaks("", "");
3363
Cvar_Set("fs_basepath", lastValidBase);
3364
Cvar_Set("fs_gamedirvar", lastValidGame);
3365
lastValidBase[0] = '\0';
3366
lastValidGame[0] = '\0';
3367
Cvar_Set( "fs_restrict", "0" );
3368
FS_Restart(checksumFeed);
3369
Com_Error( ERR_DROP, "Invalid game folder\n" );
3372
Com_Error( ERR_FATAL, "Couldn't load default.cfg" );
3375
// bk010116 - new check before safeMode
3376
if ( Q_stricmp(fs_gamedirvar->string, lastValidGame) ) {
3377
// skip the q3config.cfg if "safe" is on the command line
3378
if ( !Com_SafeMode() ) {
3379
Cbuf_AddText ("exec q3config.cfg\n");
3383
Q_strncpyz(lastValidBase, fs_basepath->string, sizeof(lastValidBase));
3384
Q_strncpyz(lastValidGame, fs_gamedirvar->string, sizeof(lastValidGame));
3390
FS_ConditionalRestart
3391
restart if necessary
3394
qboolean FS_ConditionalRestart( int checksumFeed ) {
3395
if( fs_gamedirvar->modified || checksumFeed != fs_checksumFeed ) {
3396
FS_Restart( checksumFeed );
3403
========================================================================================
3405
Handle based file calls for virtual machines
3407
========================================================================================
3410
int FS_FOpenFileByMode( const char *qpath, fileHandle_t *f, fsMode_t mode ) {
3418
r = FS_FOpenFileRead( qpath, f, qtrue );
3421
*f = FS_FOpenFileWrite( qpath );
3427
case FS_APPEND_SYNC:
3430
*f = FS_FOpenFileAppend( qpath );
3437
Com_Error( ERR_FATAL, "FSH_FOpenFile: bad mode" );
3446
if (fsh[*f].zipFile == qtrue) {
3447
fsh[*f].baseOffset = unztell(fsh[*f].handleFiles.file.z);
3449
fsh[*f].baseOffset = ftell(fsh[*f].handleFiles.file.o);
3451
fsh[*f].fileSize = r;
3452
fsh[*f].streamed = qfalse;
3454
if (mode == FS_READ) {
3455
Sys_BeginStreamedFile( *f, 0x4000 );
3456
fsh[*f].streamed = qtrue;
3459
fsh[*f].handleSync = sync;
3464
int FS_FTell( fileHandle_t f ) {
3466
if (fsh[f].zipFile == qtrue) {
3467
pos = unztell(fsh[f].handleFiles.file.z);
3469
pos = ftell(fsh[f].handleFiles.file.o);
3474
void FS_Flush( fileHandle_t f ) {
3475
fflush(fsh[f].handleFiles.file.o);
3478
void FS_FilenameCompletion( const char *dir, const char *ext,
3479
qboolean stripExt, void(*callback)(const char *s) ) {
3483
char filename[ MAX_STRING_CHARS ];
3485
filenames = FS_ListFilteredFiles( dir, ext, NULL, &nfiles );
3487
FS_SortFileList( filenames, nfiles );
3489
for( i = 0; i < nfiles; i++ ) {
3490
FS_ConvertPath( filenames[ i ] );
3491
Q_strncpyz( filename, filenames[ i ], MAX_STRING_CHARS );
3494
COM_StripExtension(filename, filename, sizeof(filename));
3497
callback( filename );
3499
FS_FreeFileList( filenames );