~ubuntu-branches/ubuntu/precise/openarena/precise

« back to all changes in this revision

Viewing changes to code/qcommon/files.c

  • Committer: Bazaar Package Importer
  • Author(s): Bruno "Fuddl" Kleinert
  • Date: 2007-01-20 12:28:09 UTC
  • Revision ID: james.westby@ubuntu.com-20070120122809-2yza5ojt7nqiyiam
Tags: upstream-0.6.0
ImportĀ upstreamĀ versionĀ 0.6.0

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
===========================================================================
 
3
Copyright (C) 1999-2005 Id Software, Inc.
 
4
 
 
5
This file is part of Quake III Arena source code.
 
6
 
 
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.
 
11
 
 
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.
 
16
 
 
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
===========================================================================
 
21
*/
 
22
/*****************************************************************************
 
23
 * name:                files.c
 
24
 *
 
25
 * desc:                handle based filesystem for Quake III Arena 
 
26
 *
 
27
 * $Archive: /MissionPack/code/qcommon/files.c $
 
28
 *
 
29
 *****************************************************************************/
 
30
 
 
31
 
 
32
#include "q_shared.h"
 
33
#include "qcommon.h"
 
34
#include "unzip.h"
 
35
 
 
36
/*
 
37
=============================================================================
 
38
 
 
39
QUAKE3 FILESYSTEM
 
40
 
 
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.
 
43
 
 
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.
 
47
 
 
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.
 
53
 
 
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".
 
59
 
 
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).
 
62
 
 
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
 
66
 
 
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.
 
69
 
 
70
 
 
71
The "base game" is the directory under the paths where data comes from by default, and
 
72
can be either "baseq3" or "demoq3".
 
73
 
 
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.
 
77
 
 
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.
 
80
 
 
81
No other directories outside of the base game and current game will ever be referenced by
 
82
filesystem functions.
 
83
 
 
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.
 
90
 
 
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.
 
95
 
 
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.
 
103
 
 
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.
 
106
 
 
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.
 
110
 
 
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
 
114
 
 
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")
 
120
 
 
121
e.g. the qpath "sound/newstuff/test.wav" would be searched for in the following places:
 
122
 
 
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
 
129
 
 
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
 
136
 
 
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
 
143
 
 
144
server download, to be written to home path + current game's directory
 
145
 
 
146
 
 
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.
 
150
 
 
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.
 
156
 
 
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.
 
160
 
 
161
How to prevent downloading zip files?
 
162
Pass pk3 file names in systeminfo, and download before FS_Restart()?
 
163
 
 
164
Aborting a download disconnects the client from the server.
 
165
 
 
166
How to mark files as downloadable?  Commercial add-ons won't be downloadable.
 
167
 
 
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
 
170
 
 
171
Auto-update information
 
172
 
 
173
Path separators
 
174
 
 
175
Casing
 
176
 
 
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.
 
180
 
 
181
  allow menu options for game selection?
 
182
 
 
183
Read / write config to floppy option.
 
184
 
 
185
Different version coexistance?
 
186
 
 
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!
 
189
 
 
190
  todo:
 
191
 
 
192
  downloading (outside fs?)
 
193
  game directory passing and restarting
 
194
 
 
195
=============================================================================
 
196
 
 
197
*/
 
198
 
 
199
#define DEMOGAME                        "demota"
 
200
 
 
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
 
205
 
 
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
 
213
 
 
214
#define MAX_ZPATH                       256
 
215
#define MAX_SEARCH_PATHS        4096
 
216
#define MAX_FILEHASH_SIZE       1024
 
217
 
 
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
 
222
} fileInPack_t;
 
223
 
 
224
typedef struct {
 
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.
 
236
} pack_t;
 
237
 
 
238
typedef struct {
 
239
        char            path[MAX_OSPATH];               // c:\quake3
 
240
        char            gamedir[MAX_OSPATH];    // baseq3
 
241
} directory_t;
 
242
 
 
243
typedef struct searchpath_s {
 
244
        struct searchpath_s *next;
 
245
 
 
246
        pack_t          *pack;          // only one of pack / dir will be non NULL
 
247
        directory_t     *dir;
 
248
} searchpath_t;
 
249
 
 
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
 
264
 
 
265
static int fs_fakeChkSum;
 
266
static int fs_checksumFeed;
 
267
 
 
268
typedef union qfile_gus {
 
269
        FILE*           o;
 
270
        unzFile         z;
 
271
} qfile_gut;
 
272
 
 
273
typedef struct qfile_us {
 
274
        qfile_gut       file;
 
275
        qboolean        unique;
 
276
} qfile_ut;
 
277
 
 
278
typedef struct {
 
279
        qfile_ut        handleFiles;
 
280
        qboolean        handleSync;
 
281
        int                     baseOffset;
 
282
        int                     fileSize;
 
283
        int                     zipFilePos;
 
284
        qboolean        zipFile;
 
285
        qboolean        streamed;
 
286
        char            name[MAX_ZPATH];
 
287
} fileHandleData_t;
 
288
 
 
289
static fileHandleData_t fsh[MAX_FILE_HANDLES];
 
290
 
 
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;
 
294
 
 
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
 
299
 
 
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
 
305
 
 
306
// last valid game folder used
 
307
char lastValidBase[MAX_OSPATH];
 
308
char lastValidGame[MAX_OSPATH];
 
309
 
 
310
#ifdef FS_MISSING
 
311
FILE*           missingFiles = NULL;
 
312
#endif
 
313
 
 
314
/*
 
315
==============
 
316
FS_Initialized
 
317
==============
 
318
*/
 
319
 
 
320
qboolean FS_Initialized( void ) {
 
321
        return (fs_searchpaths != NULL);
 
322
}
 
323
 
 
324
/*
 
325
=================
 
326
FS_PakIsPure
 
327
=================
 
328
*/
 
329
qboolean FS_PakIsPure( pack_t *pack ) {
 
330
        int i;
 
331
 
 
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
 
339
                        }
 
340
                }
 
341
                return qfalse;  // not on the pure server pak list
 
342
        }
 
343
        return qtrue;
 
344
}
 
345
 
 
346
 
 
347
/*
 
348
=================
 
349
FS_LoadStack
 
350
return load stack
 
351
=================
 
352
*/
 
353
int FS_LoadStack( void )
 
354
{
 
355
        return fs_loadStack;
 
356
}
 
357
                      
 
358
/*
 
359
================
 
360
return a hash value for the filename
 
361
================
 
362
*/
 
363
static long FS_HashFileName( const char *fname, int hashSize ) {
 
364
        int             i;
 
365
        long    hash;
 
366
        char    letter;
 
367
 
 
368
        hash = 0;
 
369
        i = 0;
 
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);
 
376
                i++;
 
377
        }
 
378
        hash = (hash ^ (hash >> 10) ^ (hash >> 20));
 
379
        hash &= (hashSize-1);
 
380
        return hash;
 
381
}
 
382
 
 
383
static fileHandle_t     FS_HandleForFile(void) {
 
384
        int             i;
 
385
 
 
386
        for ( i = 1 ; i < MAX_FILE_HANDLES ; i++ ) {
 
387
                if ( fsh[i].handleFiles.file.o == NULL ) {
 
388
                        return i;
 
389
                }
 
390
        }
 
391
        Com_Error( ERR_DROP, "FS_HandleForFile: none free" );
 
392
        return 0;
 
393
}
 
394
 
 
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" );
 
398
        }
 
399
        if (fsh[f].zipFile == qtrue) {
 
400
                Com_Error( ERR_DROP, "FS_FileForHandle: can't get FILE on zip file" );
 
401
        }
 
402
        if ( ! fsh[f].handleFiles.file.o ) {
 
403
                Com_Error( ERR_DROP, "FS_FileForHandle: NULL" );
 
404
        }
 
405
        
 
406
        return fsh[f].handleFiles.file.o;
 
407
}
 
408
 
 
409
void    FS_ForceFlush( fileHandle_t f ) {
 
410
        FILE *file;
 
411
 
 
412
        file = FS_FileForHandle(f);
 
413
        setvbuf( file, NULL, _IONBF, 0 );
 
414
}
 
415
 
 
416
/*
 
417
================
 
418
FS_filelength
 
419
 
 
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
 
422
size of the file.
 
423
================
 
424
*/
 
425
int FS_filelength( fileHandle_t f ) {
 
426
        int             pos;
 
427
        int             end;
 
428
        FILE*   h;
 
429
 
 
430
        h = FS_FileForHandle(f);
 
431
        pos = ftell (h);
 
432
        fseek (h, 0, SEEK_END);
 
433
        end = ftell (h);
 
434
        fseek (h, pos, SEEK_SET);
 
435
 
 
436
        return end;
 
437
}
 
438
 
 
439
/*
 
440
====================
 
441
FS_ReplaceSeparators
 
442
 
 
443
Fix things up differently for win/unix/mac
 
444
====================
 
445
*/
 
446
static void FS_ReplaceSeparators( char *path ) {
 
447
        char    *s;
 
448
 
 
449
        for ( s = path ; *s ; s++ ) {
 
450
                if ( *s == '/' || *s == '\\' ) {
 
451
                        *s = PATH_SEP;
 
452
                }
 
453
        }
 
454
}
 
455
 
 
456
/*
 
457
===================
 
458
FS_BuildOSPath
 
459
 
 
460
Qpath may have either forward or backwards slashes
 
461
===================
 
462
*/
 
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];
 
466
        static int toggle;
 
467
        
 
468
        toggle ^= 1;            // flip-flop to allow two returns without clash
 
469
 
 
470
        if( !game || !game[0] ) {
 
471
                game = fs_gamedir;
 
472
        }
 
473
 
 
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 );
 
477
        
 
478
        return ospath[toggle];
 
479
}
 
480
 
 
481
 
 
482
/*
 
483
============
 
484
FS_CreatePath
 
485
 
 
486
Creates any directories needed to store the given filename
 
487
============
 
488
*/
 
489
static qboolean FS_CreatePath (char *OSPath) {
 
490
        char    *ofs;
 
491
        
 
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 );
 
496
                return qtrue;
 
497
        }
 
498
 
 
499
        for (ofs = OSPath+1 ; *ofs ; ofs++) {
 
500
                if (*ofs == PATH_SEP) { 
 
501
                        // create the directory
 
502
                        *ofs = 0;
 
503
                        Sys_Mkdir (OSPath);
 
504
                        *ofs = PATH_SEP;
 
505
                }
 
506
        }
 
507
        return qfalse;
 
508
}
 
509
 
 
510
/*
 
511
=================
 
512
FS_CopyFile
 
513
 
 
514
Copy a fully specified file from one place to another
 
515
=================
 
516
*/
 
517
static void FS_CopyFile( char *fromOSPath, char *toOSPath ) {
 
518
        FILE    *f;
 
519
        int             len;
 
520
        byte    *buf;
 
521
 
 
522
        Com_Printf( "copy %s to %s\n", fromOSPath, toOSPath );
 
523
 
 
524
        if (strstr(fromOSPath, "journal.dat") || strstr(fromOSPath, "journaldata.dat")) {
 
525
                Com_Printf( "Ignoring journal files\n");
 
526
                return;
 
527
        }
 
528
 
 
529
        f = fopen( fromOSPath, "rb" );
 
530
        if ( !f ) {
 
531
                return;
 
532
        }
 
533
        fseek (f, 0, SEEK_END);
 
534
        len = ftell (f);
 
535
        fseek (f, 0, SEEK_SET);
 
536
 
 
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...
 
539
        buf = malloc( len );
 
540
        if (fread( buf, 1, len, f ) != len)
 
541
                Com_Error( ERR_FATAL, "Short read in FS_Copyfiles()\n" );
 
542
        fclose( f );
 
543
 
 
544
        if( FS_CreatePath( toOSPath ) ) {
 
545
                return;
 
546
        }
 
547
 
 
548
        f = fopen( toOSPath, "wb" );
 
549
        if ( !f ) {
 
550
                return;
 
551
        }
 
552
        if (fwrite( buf, 1, len, f ) != len)
 
553
                Com_Error( ERR_FATAL, "Short write in FS_Copyfiles()\n" );
 
554
        fclose( f );
 
555
        free( buf );
 
556
}
 
557
 
 
558
/*
 
559
===========
 
560
FS_Remove
 
561
 
 
562
===========
 
563
*/
 
564
void FS_Remove( const char *osPath ) {
 
565
        remove( osPath );
 
566
}
 
567
 
 
568
/*
 
569
===========
 
570
FS_HomeRemove
 
571
 
 
572
===========
 
573
*/
 
574
void FS_HomeRemove( const char *homePath ) {
 
575
        remove( FS_BuildOSPath( fs_homepath->string,
 
576
                        fs_gamedir, homePath ) );
 
577
}
 
578
 
 
579
/*
 
580
================
 
581
FS_FileExists
 
582
 
 
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
 
587
================
 
588
*/
 
589
qboolean FS_FileExists( const char *file )
 
590
{
 
591
        FILE *f;
 
592
        char *testpath;
 
593
 
 
594
        testpath = FS_BuildOSPath( fs_homepath->string, fs_gamedir, file );
 
595
 
 
596
        f = fopen( testpath, "rb" );
 
597
        if (f) {
 
598
                fclose( f );
 
599
                return qtrue;
 
600
        }
 
601
        return qfalse;
 
602
}
 
603
 
 
604
/*
 
605
================
 
606
FS_SV_FileExists
 
607
 
 
608
Tests if the file exists 
 
609
================
 
610
*/
 
611
qboolean FS_SV_FileExists( const char *file )
 
612
{
 
613
        FILE *f;
 
614
        char *testpath;
 
615
 
 
616
        testpath = FS_BuildOSPath( fs_homepath->string, file, "");
 
617
        testpath[strlen(testpath)-1] = '\0';
 
618
 
 
619
        f = fopen( testpath, "rb" );
 
620
        if (f) {
 
621
                fclose( f );
 
622
                return qtrue;
 
623
        }
 
624
        return qfalse;
 
625
}
 
626
 
 
627
 
 
628
/*
 
629
===========
 
630
FS_SV_FOpenFileWrite
 
631
 
 
632
===========
 
633
*/
 
634
fileHandle_t FS_SV_FOpenFileWrite( const char *filename ) {
 
635
        char *ospath;
 
636
        fileHandle_t    f;
 
637
 
 
638
        if ( !fs_searchpaths ) {
 
639
                Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
 
640
        }
 
641
 
 
642
        ospath = FS_BuildOSPath( fs_homepath->string, filename, "" );
 
643
        ospath[strlen(ospath)-1] = '\0';
 
644
 
 
645
        f = FS_HandleForFile();
 
646
        fsh[f].zipFile = qfalse;
 
647
 
 
648
        if ( fs_debug->integer ) {
 
649
                Com_Printf( "FS_SV_FOpenFileWrite: %s\n", ospath );
 
650
        }
 
651
 
 
652
        if( FS_CreatePath( ospath ) ) {
 
653
                return 0;
 
654
        }
 
655
 
 
656
        Com_DPrintf( "writing to: %s\n", ospath );
 
657
        fsh[f].handleFiles.file.o = fopen( ospath, "wb" );
 
658
 
 
659
        Q_strncpyz( fsh[f].name, filename, sizeof( fsh[f].name ) );
 
660
 
 
661
        fsh[f].handleSync = qfalse;
 
662
        if (!fsh[f].handleFiles.file.o) {
 
663
                f = 0;
 
664
        }
 
665
        return f;
 
666
}
 
667
 
 
668
/*
 
669
===========
 
670
FS_SV_FOpenFileRead
 
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
 
673
===========
 
674
*/
 
675
int FS_SV_FOpenFileRead( const char *filename, fileHandle_t *fp ) {
 
676
        char *ospath;
 
677
        fileHandle_t    f = 0;
 
678
 
 
679
        if ( !fs_searchpaths ) {
 
680
                Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
 
681
        }
 
682
 
 
683
        f = FS_HandleForFile();
 
684
        fsh[f].zipFile = qfalse;
 
685
 
 
686
        Q_strncpyz( fsh[f].name, filename, sizeof( fsh[f].name ) );
 
687
 
 
688
        // don't let sound stutter
 
689
        S_ClearSoundBuffer();
 
690
 
 
691
  // search homepath
 
692
        ospath = FS_BuildOSPath( fs_homepath->string, filename, "" );
 
693
        // remove trailing slash
 
694
        ospath[strlen(ospath)-1] = '\0';
 
695
 
 
696
        if ( fs_debug->integer ) {
 
697
                Com_Printf( "FS_SV_FOpenFileRead (fs_homepath): %s\n", ospath );
 
698
        }
 
699
 
 
700
        fsh[f].handleFiles.file.o = fopen( ospath, "rb" );
 
701
        fsh[f].handleSync = qfalse;
 
702
  if (!fsh[f].handleFiles.file.o)
 
703
  {
 
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))
 
706
    {
 
707
      // search basepath
 
708
      ospath = FS_BuildOSPath( fs_basepath->string, filename, "" );
 
709
      ospath[strlen(ospath)-1] = '\0';
 
710
 
 
711
      if ( fs_debug->integer )
 
712
      {
 
713
        Com_Printf( "FS_SV_FOpenFileRead (fs_basepath): %s\n", ospath );
 
714
      }
 
715
 
 
716
      fsh[f].handleFiles.file.o = fopen( ospath, "rb" );
 
717
      fsh[f].handleSync = qfalse;
 
718
 
 
719
      if ( !fsh[f].handleFiles.file.o )
 
720
      {
 
721
        f = 0;
 
722
      }
 
723
    }
 
724
  }
 
725
 
 
726
        if (!fsh[f].handleFiles.file.o) {
 
727
    // search cd path
 
728
    ospath = FS_BuildOSPath( fs_cdpath->string, filename, "" );
 
729
    ospath[strlen(ospath)-1] = '\0';
 
730
 
 
731
    if (fs_debug->integer)
 
732
    {
 
733
      Com_Printf( "FS_SV_FOpenFileRead (fs_cdpath) : %s\n", ospath );
 
734
    }
 
735
 
 
736
          fsh[f].handleFiles.file.o = fopen( ospath, "rb" );
 
737
          fsh[f].handleSync = qfalse;
 
738
 
 
739
          if( !fsh[f].handleFiles.file.o ) {
 
740
            f = 0;
 
741
          }
 
742
  }
 
743
  
 
744
        *fp = f;
 
745
        if (f) {
 
746
                return FS_filelength(f);
 
747
        }
 
748
        return 0;
 
749
}
 
750
 
 
751
 
 
752
/*
 
753
===========
 
754
FS_SV_Rename
 
755
 
 
756
===========
 
757
*/
 
758
void FS_SV_Rename( const char *from, const char *to ) {
 
759
        char                    *from_ospath, *to_ospath;
 
760
 
 
761
        if ( !fs_searchpaths ) {
 
762
                Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
 
763
        }
 
764
 
 
765
        // don't let sound stutter
 
766
        S_ClearSoundBuffer();
 
767
 
 
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';
 
772
 
 
773
        if ( fs_debug->integer ) {
 
774
                Com_Printf( "FS_SV_Rename: %s --> %s\n", from_ospath, to_ospath );
 
775
        }
 
776
 
 
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 );
 
781
        }
 
782
}
 
783
 
 
784
 
 
785
 
 
786
/*
 
787
===========
 
788
FS_Rename
 
789
 
 
790
===========
 
791
*/
 
792
void FS_Rename( const char *from, const char *to ) {
 
793
        char                    *from_ospath, *to_ospath;
 
794
 
 
795
        if ( !fs_searchpaths ) {
 
796
                Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
 
797
        }
 
798
 
 
799
        // don't let sound stutter
 
800
        S_ClearSoundBuffer();
 
801
 
 
802
        from_ospath = FS_BuildOSPath( fs_homepath->string, fs_gamedir, from );
 
803
        to_ospath = FS_BuildOSPath( fs_homepath->string, fs_gamedir, to );
 
804
 
 
805
        if ( fs_debug->integer ) {
 
806
                Com_Printf( "FS_Rename: %s --> %s\n", from_ospath, to_ospath );
 
807
        }
 
808
 
 
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 );
 
813
        }
 
814
}
 
815
 
 
816
/*
 
817
==============
 
818
FS_FCloseFile
 
819
 
 
820
If the FILE pointer is an open pak file, leave it open.
 
821
 
 
822
For some reason, other dll's can't just cal fclose()
 
823
on files returned by FS_FOpenFile...
 
824
==============
 
825
*/
 
826
void FS_FCloseFile( fileHandle_t f ) {
 
827
        if ( !fs_searchpaths ) {
 
828
                Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
 
829
        }
 
830
 
 
831
        if (fsh[f].streamed) {
 
832
                Sys_EndStreamedFile(f);
 
833
        }
 
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 );
 
838
                }
 
839
                Com_Memset( &fsh[f], 0, sizeof( fsh[f] ) );
 
840
                return;
 
841
        }
 
842
 
 
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);
 
846
        }
 
847
        Com_Memset( &fsh[f], 0, sizeof( fsh[f] ) );
 
848
}
 
849
 
 
850
/*
 
851
===========
 
852
FS_FOpenFileWrite
 
853
 
 
854
===========
 
855
*/
 
856
fileHandle_t FS_FOpenFileWrite( const char *filename ) {
 
857
        char                    *ospath;
 
858
        fileHandle_t    f;
 
859
 
 
860
        if ( !fs_searchpaths ) {
 
861
                Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
 
862
        }
 
863
 
 
864
        f = FS_HandleForFile();
 
865
        fsh[f].zipFile = qfalse;
 
866
 
 
867
        ospath = FS_BuildOSPath( fs_homepath->string, fs_gamedir, filename );
 
868
 
 
869
        if ( fs_debug->integer ) {
 
870
                Com_Printf( "FS_FOpenFileWrite: %s\n", ospath );
 
871
        }
 
872
 
 
873
        if( FS_CreatePath( ospath ) ) {
 
874
                return 0;
 
875
        }
 
876
 
 
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" );
 
881
 
 
882
        Q_strncpyz( fsh[f].name, filename, sizeof( fsh[f].name ) );
 
883
 
 
884
        fsh[f].handleSync = qfalse;
 
885
        if (!fsh[f].handleFiles.file.o) {
 
886
                f = 0;
 
887
        }
 
888
        return f;
 
889
}
 
890
 
 
891
/*
 
892
===========
 
893
FS_FOpenFileAppend
 
894
 
 
895
===========
 
896
*/
 
897
fileHandle_t FS_FOpenFileAppend( const char *filename ) {
 
898
        char                    *ospath;
 
899
        fileHandle_t    f;
 
900
 
 
901
        if ( !fs_searchpaths ) {
 
902
                Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
 
903
        }
 
904
 
 
905
        f = FS_HandleForFile();
 
906
        fsh[f].zipFile = qfalse;
 
907
 
 
908
        Q_strncpyz( fsh[f].name, filename, sizeof( fsh[f].name ) );
 
909
 
 
910
        // don't let sound stutter
 
911
        S_ClearSoundBuffer();
 
912
 
 
913
        ospath = FS_BuildOSPath( fs_homepath->string, fs_gamedir, filename );
 
914
 
 
915
        if ( fs_debug->integer ) {
 
916
                Com_Printf( "FS_FOpenFileAppend: %s\n", ospath );
 
917
        }
 
918
 
 
919
        if( FS_CreatePath( ospath ) ) {
 
920
                return 0;
 
921
        }
 
922
 
 
923
        fsh[f].handleFiles.file.o = fopen( ospath, "ab" );
 
924
        fsh[f].handleSync = qfalse;
 
925
        if (!fsh[f].handleFiles.file.o) {
 
926
                f = 0;
 
927
        }
 
928
        return f;
 
929
}
 
930
 
 
931
/*
 
932
===========
 
933
FS_FilenameCompare
 
934
 
 
935
Ignore case and seprator char distinctions
 
936
===========
 
937
*/
 
938
qboolean FS_FilenameCompare( const char *s1, const char *s2 ) {
 
939
        int             c1, c2;
 
940
        
 
941
        do {
 
942
                c1 = *s1++;
 
943
                c2 = *s2++;
 
944
 
 
945
                if (c1 >= 'a' && c1 <= 'z') {
 
946
                        c1 -= ('a' - 'A');
 
947
                }
 
948
                if (c2 >= 'a' && c2 <= 'z') {
 
949
                        c2 -= ('a' - 'A');
 
950
                }
 
951
 
 
952
                if ( c1 == '\\' || c1 == ':' ) {
 
953
                        c1 = '/';
 
954
                }
 
955
                if ( c2 == '\\' || c2 == ':' ) {
 
956
                        c2 = '/';
 
957
                }
 
958
                
 
959
                if (c1 != c2) {
 
960
                        return qtrue;           // strings not equal
 
961
                }
 
962
        } while (c1);
 
963
        
 
964
        return qfalse;          // strings are equal
 
965
}
 
966
 
 
967
/*
 
968
===========
 
969
FS_ShiftedStrStr
 
970
===========
 
971
*/
 
972
char *FS_ShiftedStrStr(const char *string, const char *substring, int shift) {
 
973
        char buf[MAX_STRING_TOKENS];
 
974
        int i;
 
975
 
 
976
        for (i = 0; substring[i]; i++) {
 
977
                buf[i] = substring[i] + shift;
 
978
        }
 
979
        buf[i] = '\0';
 
980
        return strstr(string, buf);
 
981
}
 
982
 
 
983
/*
 
984
===========
 
985
FS_FOpenFileRead
 
986
 
 
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.
 
991
===========
 
992
*/
 
993
extern qboolean         com_fullyInitialized;
 
994
 
 
995
int FS_FOpenFileRead( const char *filename, fileHandle_t *file, qboolean uniqueFILE ) {
 
996
        searchpath_t    *search;
 
997
        char                    *netpath;
 
998
        pack_t                  *pak;
 
999
        fileInPack_t    *pakFile;
 
1000
        directory_t             *dir;
 
1001
        long                    hash;
 
1002
        unz_s                   *zfi;
 
1003
        FILE                    *temp;
 
1004
        int                             l;
 
1005
        char demoExt[16];
 
1006
 
 
1007
        hash = 0;
 
1008
 
 
1009
        if ( !fs_searchpaths ) {
 
1010
                Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
 
1011
        }
 
1012
 
 
1013
        if ( file == NULL ) {
 
1014
                // just wants to see if file is there
 
1015
                for ( search = fs_searchpaths ; search ; search = search->next ) {
 
1016
                        //
 
1017
                        if ( search->pack ) {
 
1018
                                hash = FS_HashFileName(filename, search->pack->hashSize);
 
1019
                        }
 
1020
                        // is the element a pak file?
 
1021
                        if ( search->pack && search->pack->hashTable[hash] ) {
 
1022
                                // look through all the pak file elements
 
1023
                                pak = search->pack;
 
1024
                                pakFile = pak->hashTable[hash];
 
1025
                                do {
 
1026
                                        // case and separator insensitive comparisons
 
1027
                                        if ( !FS_FilenameCompare( pakFile->name, filename ) ) {
 
1028
                                                // found it!
 
1029
                                                return qtrue;
 
1030
                                        }
 
1031
                                        pakFile = pakFile->next;
 
1032
                                } while(pakFile != NULL);
 
1033
                        } else if ( search->dir ) {
 
1034
                                dir = search->dir;
 
1035
                        
 
1036
                                netpath = FS_BuildOSPath( dir->path, dir->gamedir, filename );
 
1037
                                temp = fopen (netpath, "rb");
 
1038
                                if ( !temp ) {
 
1039
                                        continue;
 
1040
                                }
 
1041
                                fclose(temp);
 
1042
                                return qtrue;
 
1043
                        }
 
1044
                }
 
1045
                return qfalse;
 
1046
        }
 
1047
 
 
1048
        if ( !filename ) {
 
1049
                Com_Error( ERR_FATAL, "FS_FOpenFileRead: NULL 'filename' parameter passed\n" );
 
1050
        }
 
1051
 
 
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] == '\\' ) {
 
1055
                filename++;
 
1056
        }
 
1057
 
 
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, "::" ) ) {
 
1062
                *file = 0;
 
1063
                return -1;
 
1064
        }
 
1065
 
 
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" ) ) {
 
1069
                *file = 0;
 
1070
                return -1;
 
1071
        }
 
1072
 
 
1073
        //
 
1074
        // search through the path, one element at a time
 
1075
        //
 
1076
 
 
1077
        *file = FS_HandleForFile();
 
1078
        fsh[*file].handleFiles.unique = uniqueFILE;
 
1079
 
 
1080
        for ( search = fs_searchpaths ; search ; search = search->next ) {
 
1081
                //
 
1082
                if ( search->pack ) {
 
1083
                        hash = FS_HashFileName(filename, search->pack->hashSize);
 
1084
                }
 
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) ) {
 
1089
                                continue;
 
1090
                        }
 
1091
 
 
1092
                        // look through all the pak file elements
 
1093
                        pak = search->pack;
 
1094
                        pakFile = pak->hashTable[hash];
 
1095
                        do {
 
1096
                                // case and separator insensitive comparisons
 
1097
                                if ( !FS_FilenameCompare( pakFile->name, filename ) ) {
 
1098
                                        // found it!
 
1099
 
 
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;
 
1115
                                                }
 
1116
                                        }
 
1117
 
 
1118
                                        // qagame.qvm   - 13
 
1119
                                        // dTZT`X!di`
 
1120
                                        if (!(pak->referenced & FS_QAGAME_REF) && FS_ShiftedStrStr(filename, "dTZT`X!di`", 13)) {
 
1121
                                                pak->referenced |= FS_QAGAME_REF;
 
1122
                                        }
 
1123
                                        // cgame.qvm    - 7
 
1124
                                        // \`Zf^'jof
 
1125
                                        if (!(pak->referenced & FS_CGAME_REF) && FS_ShiftedStrStr(filename , "\\`Zf^'jof", 7)) {
 
1126
                                                pak->referenced |= FS_CGAME_REF;
 
1127
                                        }
 
1128
                                        // ui.qvm               - 5
 
1129
                                        // pd)lqh
 
1130
                                        if (!(pak->referenced & FS_UI_REF) && FS_ShiftedStrStr(filename , "pd)lqh", 5)) {
 
1131
                                                pak->referenced |= FS_UI_REF;
 
1132
                                        }
 
1133
 
 
1134
                                        if ( uniqueFILE ) {
 
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);
 
1139
                                                }
 
1140
                                        } else {
 
1141
                                                fsh[*file].handleFiles.file.z = pak->handle;
 
1142
                                        }
 
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
 
1147
                                        temp = zfi->file;
 
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
 
1153
                                        zfi->file = temp;
 
1154
                                        // open the file in the zip
 
1155
                                        unzOpenCurrentFile( fsh[*file].handleFiles.file.z );
 
1156
                                        fsh[*file].zipFilePos = pakFile->pos;
 
1157
 
 
1158
                                        if ( fs_debug->integer ) {
 
1159
                                                Com_Printf( "FS_FOpenFileRead: %s (found in '%s')\n", 
 
1160
                                                        filename, pak->pakFilename );
 
1161
                                        }
 
1162
                                        return zfi->cur_file_info.uncompressed_size;
 
1163
                                }
 
1164
                                pakFile = pakFile->next;
 
1165
                        } while(pakFile != NULL);
 
1166
                } else if ( search->dir ) {
 
1167
                        // check a file in the directory tree
 
1168
 
 
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 ) {
 
1178
 
 
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
 
1184
                                        continue;
 
1185
                                }
 
1186
                        }
 
1187
 
 
1188
                        dir = search->dir;
 
1189
                        
 
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 ) {
 
1193
                                continue;
 
1194
                        }
 
1195
 
 
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();
 
1202
                        }
 
1203
      
 
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 );
 
1209
                        }
 
1210
 
 
1211
                        // if we are getting it from the cdpath, optionally copy it
 
1212
                        //  to the basepath
 
1213
                        if ( fs_copyfiles->integer && !Q_stricmp( dir->path, fs_cdpath->string ) ) {
 
1214
                                char    *copypath;
 
1215
 
 
1216
                                copypath = FS_BuildOSPath( fs_basepath->string, dir->gamedir, filename );
 
1217
                                FS_CopyFile( netpath, copypath );
 
1218
                        }
 
1219
 
 
1220
                        return FS_filelength (*file);
 
1221
                }               
 
1222
        }
 
1223
        
 
1224
        Com_DPrintf ("Can't find %s\n", filename);
 
1225
#ifdef FS_MISSING
 
1226
        if (missingFiles) {
 
1227
                fprintf(missingFiles, "%s\n", filename);
 
1228
        }
 
1229
#endif
 
1230
        *file = 0;
 
1231
        return -1;
 
1232
}
 
1233
 
 
1234
 
 
1235
/*
 
1236
=================
 
1237
FS_Read
 
1238
 
 
1239
Properly handles partial reads
 
1240
=================
 
1241
*/
 
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" );
 
1245
        }
 
1246
 
 
1247
        if ( !f ) {
 
1248
                return 0;
 
1249
        }
 
1250
        if (fsh[f].streamed) {
 
1251
                int r;
 
1252
                fsh[f].streamed = qfalse;
 
1253
                r = Sys_StreamedRead( buffer, len, 1, f);
 
1254
                fsh[f].streamed = qtrue;
 
1255
                return r;
 
1256
        } else {
 
1257
                return FS_Read( buffer, len, f);
 
1258
        }
 
1259
}
 
1260
 
 
1261
int FS_Read( void *buffer, int len, fileHandle_t f ) {
 
1262
        int             block, remaining;
 
1263
        int             read;
 
1264
        byte    *buf;
 
1265
        int             tries;
 
1266
 
 
1267
        if ( !fs_searchpaths ) {
 
1268
                Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
 
1269
        }
 
1270
 
 
1271
        if ( !f ) {
 
1272
                return 0;
 
1273
        }
 
1274
 
 
1275
        buf = (byte *)buffer;
 
1276
        fs_readCount += len;
 
1277
 
 
1278
        if (fsh[f].zipFile == qfalse) {
 
1279
                remaining = len;
 
1280
                tries = 0;
 
1281
                while (remaining) {
 
1282
                        block = remaining;
 
1283
                        read = fread (buf, 1, block, fsh[f].handleFiles.file.o);
 
1284
                        if (read == 0) {
 
1285
                                // we might have been trying to read from a CD, which
 
1286
                                // sometimes returns a 0 read on windows
 
1287
                                if (!tries) {
 
1288
                                        tries = 1;
 
1289
                                } else {
 
1290
                                        return len-remaining;   //Com_Error (ERR_FATAL, "FS_Read: 0 bytes read");
 
1291
                                }
 
1292
                        }
 
1293
 
 
1294
                        if (read == -1) {
 
1295
                                Com_Error (ERR_FATAL, "FS_Read: -1 bytes read");
 
1296
                        }
 
1297
 
 
1298
                        remaining -= read;
 
1299
                        buf += read;
 
1300
                }
 
1301
                return len;
 
1302
        } else {
 
1303
                return unzReadCurrentFile(fsh[f].handleFiles.file.z, buffer, len);
 
1304
        }
 
1305
}
 
1306
 
 
1307
/*
 
1308
=================
 
1309
FS_Write
 
1310
 
 
1311
Properly handles partial writes
 
1312
=================
 
1313
*/
 
1314
int FS_Write( const void *buffer, int len, fileHandle_t h ) {
 
1315
        int             block, remaining;
 
1316
        int             written;
 
1317
        byte    *buf;
 
1318
        int             tries;
 
1319
        FILE    *f;
 
1320
 
 
1321
        if ( !fs_searchpaths ) {
 
1322
                Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
 
1323
        }
 
1324
 
 
1325
        if ( !h ) {
 
1326
                return 0;
 
1327
        }
 
1328
 
 
1329
        f = FS_FileForHandle(h);
 
1330
        buf = (byte *)buffer;
 
1331
 
 
1332
        remaining = len;
 
1333
        tries = 0;
 
1334
        while (remaining) {
 
1335
                block = remaining;
 
1336
                written = fwrite (buf, 1, block, f);
 
1337
                if (written == 0) {
 
1338
                        if (!tries) {
 
1339
                                tries = 1;
 
1340
                        } else {
 
1341
                                Com_Printf( "FS_Write: 0 bytes written\n" );
 
1342
                                return 0;
 
1343
                        }
 
1344
                }
 
1345
 
 
1346
                if (written == -1) {
 
1347
                        Com_Printf( "FS_Write: -1 bytes written\n" );
 
1348
                        return 0;
 
1349
                }
 
1350
 
 
1351
                remaining -= written;
 
1352
                buf += written;
 
1353
        }
 
1354
        if ( fsh[h].handleSync ) {
 
1355
                fflush( f );
 
1356
        }
 
1357
        return len;
 
1358
}
 
1359
 
 
1360
void QDECL FS_Printf( fileHandle_t h, const char *fmt, ... ) {
 
1361
        va_list         argptr;
 
1362
        char            msg[MAXPRINTMSG];
 
1363
 
 
1364
        va_start (argptr,fmt);
 
1365
        Q_vsnprintf (msg, sizeof(msg), fmt, argptr);
 
1366
        va_end (argptr);
 
1367
 
 
1368
        FS_Write(msg, strlen(msg), h);
 
1369
}
 
1370
 
 
1371
#define PK3_SEEK_BUFFER_SIZE 65536
 
1372
 
 
1373
/*
 
1374
=================
 
1375
FS_Seek
 
1376
 
 
1377
=================
 
1378
*/
 
1379
int FS_Seek( fileHandle_t f, long offset, int origin ) {
 
1380
        int             _origin;
 
1381
 
 
1382
        if ( !fs_searchpaths ) {
 
1383
                Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
 
1384
                return -1;
 
1385
        }
 
1386
 
 
1387
        if (fsh[f].streamed) {
 
1388
                fsh[f].streamed = qfalse;
 
1389
                Sys_StreamSeek( f, offset, origin );
 
1390
                fsh[f].streamed = qtrue;
 
1391
        }
 
1392
 
 
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;
 
1398
 
 
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" );
 
1402
                        return -1;
 
1403
                }
 
1404
 
 
1405
                switch( origin ) {
 
1406
                        case FS_SEEK_SET:
 
1407
                                unzSetCurrentFileInfoPosition(fsh[f].handleFiles.file.z, fsh[f].zipFilePos);
 
1408
                                unzOpenCurrentFile(fsh[f].handleFiles.file.z);
 
1409
                                //fallthrough
 
1410
 
 
1411
                        case FS_SEEK_CUR:
 
1412
                                while( remainder > PK3_SEEK_BUFFER_SIZE ) {
 
1413
                                        FS_Read( buffer, PK3_SEEK_BUFFER_SIZE, f );
 
1414
                                        remainder -= PK3_SEEK_BUFFER_SIZE;
 
1415
                                }
 
1416
                                FS_Read( buffer, remainder, f );
 
1417
                                return offset;
 
1418
                                break;
 
1419
 
 
1420
                        default:
 
1421
                                Com_Error( ERR_FATAL, "Bad origin in FS_Seek\n" );
 
1422
                                return -1;
 
1423
                                break;
 
1424
                }
 
1425
        } else {
 
1426
                FILE *file;
 
1427
                file = FS_FileForHandle(f);
 
1428
                switch( origin ) {
 
1429
                case FS_SEEK_CUR:
 
1430
                        _origin = SEEK_CUR;
 
1431
                        break;
 
1432
                case FS_SEEK_END:
 
1433
                        _origin = SEEK_END;
 
1434
                        break;
 
1435
                case FS_SEEK_SET:
 
1436
                        _origin = SEEK_SET;
 
1437
                        break;
 
1438
                default:
 
1439
                        _origin = SEEK_CUR;
 
1440
                        Com_Error( ERR_FATAL, "Bad origin in FS_Seek\n" );
 
1441
                        break;
 
1442
                }
 
1443
 
 
1444
                return fseek( file, offset, _origin );
 
1445
        }
 
1446
}
 
1447
 
 
1448
 
 
1449
/*
 
1450
======================================================================================
 
1451
 
 
1452
CONVENIENCE FUNCTIONS FOR ENTIRE FILES
 
1453
 
 
1454
======================================================================================
 
1455
*/
 
1456
 
 
1457
int     FS_FileIsInPAK(const char *filename, int *pChecksum ) {
 
1458
        searchpath_t    *search;
 
1459
        pack_t                  *pak;
 
1460
        fileInPack_t    *pakFile;
 
1461
        long                    hash = 0;
 
1462
 
 
1463
        if ( !fs_searchpaths ) {
 
1464
                Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
 
1465
        }
 
1466
 
 
1467
        if ( !filename ) {
 
1468
                Com_Error( ERR_FATAL, "FS_FOpenFileRead: NULL 'filename' parameter passed\n" );
 
1469
        }
 
1470
 
 
1471
        // qpaths are not supposed to have a leading slash
 
1472
        if ( filename[0] == '/' || filename[0] == '\\' ) {
 
1473
                filename++;
 
1474
        }
 
1475
 
 
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, "::" ) ) {
 
1480
                return -1;
 
1481
        }
 
1482
 
 
1483
        //
 
1484
        // search through the path, one element at a time
 
1485
        //
 
1486
 
 
1487
        for ( search = fs_searchpaths ; search ; search = search->next ) {
 
1488
                //
 
1489
                if (search->pack) {
 
1490
                        hash = FS_HashFileName(filename, search->pack->hashSize);
 
1491
                }
 
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) ) {
 
1496
                                continue;
 
1497
                        }
 
1498
 
 
1499
                        // look through all the pak file elements
 
1500
                        pak = search->pack;
 
1501
                        pakFile = pak->hashTable[hash];
 
1502
                        do {
 
1503
                                // case and separator insensitive comparisons
 
1504
                                if ( !FS_FilenameCompare( pakFile->name, filename ) ) {
 
1505
                                        if (pChecksum) {
 
1506
                                                *pChecksum = pak->pure_checksum;
 
1507
                                        }
 
1508
                                        return 1;
 
1509
                                }
 
1510
                                pakFile = pakFile->next;
 
1511
                        } while(pakFile != NULL);
 
1512
                }
 
1513
        }
 
1514
        return -1;
 
1515
}
 
1516
 
 
1517
/*
 
1518
============
 
1519
FS_ReadFile
 
1520
 
 
1521
Filename are relative to the quake search path
 
1522
a null buffer will just return the file length without loading
 
1523
============
 
1524
*/
 
1525
int FS_ReadFile( const char *qpath, void **buffer ) {
 
1526
        fileHandle_t    h;
 
1527
        byte*                   buf;
 
1528
        qboolean                isConfig;
 
1529
        int                             len;
 
1530
 
 
1531
        if ( !fs_searchpaths ) {
 
1532
                Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
 
1533
        }
 
1534
 
 
1535
        if ( !qpath || !qpath[0] ) {
 
1536
                Com_Error( ERR_FATAL, "FS_ReadFile with empty name\n" );
 
1537
        }
 
1538
 
 
1539
        buf = NULL;     // quiet compiler warning
 
1540
 
 
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" ) ) {
 
1544
                isConfig = qtrue;
 
1545
                if ( com_journal && com_journal->integer == 2 ) {
 
1546
                        int             r;
 
1547
 
 
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;
 
1552
                                return -1;
 
1553
                        }
 
1554
                        // if the file didn't exist when the journal was created
 
1555
                        if (!len) {
 
1556
                                if (buffer == NULL) {
 
1557
                                        return 1;                       // hack for old journal files
 
1558
                                }
 
1559
                                *buffer = NULL;
 
1560
                                return -1;
 
1561
                        }
 
1562
                        if (buffer == NULL) {
 
1563
                                return len;
 
1564
                        }
 
1565
 
 
1566
                        buf = Hunk_AllocateTempMemory(len+1);
 
1567
                        *buffer = buf;
 
1568
 
 
1569
                        r = FS_Read( buf, len, com_journalDataFile );
 
1570
                        if ( r != len ) {
 
1571
                                Com_Error( ERR_FATAL, "Read from journalDataFile failed" );
 
1572
                        }
 
1573
 
 
1574
                        fs_loadCount++;
 
1575
                        fs_loadStack++;
 
1576
 
 
1577
                        // guarantee that it will have a trailing 0 for string operations
 
1578
                        buf[len] = 0;
 
1579
 
 
1580
                        return len;
 
1581
                }
 
1582
        } else {
 
1583
                isConfig = qfalse;
 
1584
        }
 
1585
 
 
1586
        // look for it in the filesystem or pack files
 
1587
        len = FS_FOpenFileRead( qpath, &h, qfalse );
 
1588
        if ( h == 0 ) {
 
1589
                if ( buffer ) {
 
1590
                        *buffer = NULL;
 
1591
                }
 
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 );
 
1595
                        len = 0;
 
1596
                        FS_Write( &len, sizeof( len ), com_journalDataFile );
 
1597
                        FS_Flush( com_journalDataFile );
 
1598
                }
 
1599
                return -1;
 
1600
        }
 
1601
        
 
1602
        if ( !buffer ) {
 
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 );
 
1607
                }
 
1608
                FS_FCloseFile( h);
 
1609
                return len;
 
1610
        }
 
1611
 
 
1612
        fs_loadCount++;
 
1613
        fs_loadStack++;
 
1614
 
 
1615
        buf = Hunk_AllocateTempMemory(len+1);
 
1616
        *buffer = buf;
 
1617
 
 
1618
        FS_Read (buf, len, h);
 
1619
 
 
1620
        // guarantee that it will have a trailing 0 for string operations
 
1621
        buf[len] = 0;
 
1622
        FS_FCloseFile( h );
 
1623
 
 
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 );
 
1630
        }
 
1631
        return len;
 
1632
}
 
1633
 
 
1634
/*
 
1635
=============
 
1636
FS_FreeFile
 
1637
=============
 
1638
*/
 
1639
void FS_FreeFile( void *buffer ) {
 
1640
        if ( !fs_searchpaths ) {
 
1641
                Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
 
1642
        }
 
1643
        if ( !buffer ) {
 
1644
                Com_Error( ERR_FATAL, "FS_FreeFile( NULL )" );
 
1645
        }
 
1646
        fs_loadStack--;
 
1647
 
 
1648
        Hunk_FreeTempMemory( buffer );
 
1649
 
 
1650
        // if all of our temp files are free, clear all of our space
 
1651
        if ( fs_loadStack == 0 ) {
 
1652
                Hunk_ClearTempMemory();
 
1653
        }
 
1654
}
 
1655
 
 
1656
/*
 
1657
============
 
1658
FS_WriteFile
 
1659
 
 
1660
Filename are reletive to the quake search path
 
1661
============
 
1662
*/
 
1663
void FS_WriteFile( const char *qpath, const void *buffer, int size ) {
 
1664
        fileHandle_t f;
 
1665
 
 
1666
        if ( !fs_searchpaths ) {
 
1667
                Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
 
1668
        }
 
1669
 
 
1670
        if ( !qpath || !buffer ) {
 
1671
                Com_Error( ERR_FATAL, "FS_WriteFile: NULL parameter" );
 
1672
        }
 
1673
 
 
1674
        f = FS_FOpenFileWrite( qpath );
 
1675
        if ( !f ) {
 
1676
                Com_Printf( "Failed to open %s\n", qpath );
 
1677
                return;
 
1678
        }
 
1679
 
 
1680
        FS_Write( buffer, size, f );
 
1681
 
 
1682
        FS_FCloseFile( f );
 
1683
}
 
1684
 
 
1685
 
 
1686
 
 
1687
/*
 
1688
==========================================================================
 
1689
 
 
1690
ZIP FILE LOADING
 
1691
 
 
1692
==========================================================================
 
1693
*/
 
1694
 
 
1695
/*
 
1696
=================
 
1697
FS_LoadZipFile
 
1698
 
 
1699
Creates a new pak_t in the search chain for the contents
 
1700
of a zip file.
 
1701
=================
 
1702
*/
 
1703
static pack_t *FS_LoadZipFile( char *zipfile, const char *basename )
 
1704
{
 
1705
        fileInPack_t    *buildBuffer;
 
1706
        pack_t                  *pack;
 
1707
        unzFile                 uf;
 
1708
        int                             err;
 
1709
        unz_global_info gi;
 
1710
        char                    filename_inzip[MAX_ZPATH];
 
1711
        unz_file_info   file_info;
 
1712
        int                             i, len;
 
1713
        long                    hash;
 
1714
        int                             fs_numHeaderLongs;
 
1715
        int                             *fs_headerLongs;
 
1716
        char                    *namePtr;
 
1717
 
 
1718
        fs_numHeaderLongs = 0;
 
1719
 
 
1720
        uf = unzOpen(zipfile);
 
1721
        err = unzGetGlobalInfo (uf,&gi);
 
1722
 
 
1723
        if (err != UNZ_OK)
 
1724
                return NULL;
 
1725
 
 
1726
        fs_packFiles += gi.number_entry;
 
1727
 
 
1728
        len = 0;
 
1729
        unzGoToFirstFile(uf);
 
1730
        for (i = 0; i < gi.number_entry; i++)
 
1731
        {
 
1732
                err = unzGetCurrentFileInfo(uf, &file_info, filename_inzip, sizeof(filename_inzip), NULL, 0, NULL, 0);
 
1733
                if (err != UNZ_OK) {
 
1734
                        break;
 
1735
                }
 
1736
                len += strlen(filename_inzip) + 1;
 
1737
                unzGoToNextFile(uf);
 
1738
        }
 
1739
 
 
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 );
 
1744
 
 
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) {
 
1749
                        break;
 
1750
                }
 
1751
        }
 
1752
 
 
1753
        pack = Z_Malloc( sizeof( pack_t ) + i * sizeof(fileInPack_t *) );
 
1754
        pack->hashSize = i;
 
1755
        pack->hashTable = (fileInPack_t **) (((char *) pack) + sizeof( pack_t ));
 
1756
        for(i = 0; i < pack->hashSize; i++) {
 
1757
                pack->hashTable[i] = NULL;
 
1758
        }
 
1759
 
 
1760
        Q_strncpyz( pack->pakFilename, zipfile, sizeof( pack->pakFilename ) );
 
1761
        Q_strncpyz( pack->pakBasename, basename, sizeof( pack->pakBasename ) );
 
1762
 
 
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;
 
1766
        }
 
1767
 
 
1768
        pack->handle = uf;
 
1769
        pack->numfiles = gi.number_entry;
 
1770
        unzGoToFirstFile(uf);
 
1771
 
 
1772
        for (i = 0; i < gi.number_entry; i++)
 
1773
        {
 
1774
                err = unzGetCurrentFileInfo(uf, &file_info, filename_inzip, sizeof(filename_inzip), NULL, 0, NULL, 0);
 
1775
                if (err != UNZ_OK) {
 
1776
                        break;
 
1777
                }
 
1778
                if (file_info.uncompressed_size > 0) {
 
1779
                        fs_headerLongs[fs_numHeaderLongs++] = LittleLong(file_info.crc);
 
1780
                }
 
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);
 
1788
                //
 
1789
                buildBuffer[i].next = pack->hashTable[hash];
 
1790
                pack->hashTable[hash] = &buildBuffer[i];
 
1791
                unzGoToNextFile(uf);
 
1792
        }
 
1793
 
 
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 );
 
1798
 
 
1799
        Z_Free(fs_headerLongs);
 
1800
 
 
1801
        pack->buildBuffer = buildBuffer;
 
1802
        return pack;
 
1803
}
 
1804
 
 
1805
/*
 
1806
=================================================================================
 
1807
 
 
1808
DIRECTORY SCANNING FUNCTIONS
 
1809
 
 
1810
=================================================================================
 
1811
*/
 
1812
 
 
1813
#define MAX_FOUND_FILES 0x1000
 
1814
 
 
1815
static int FS_ReturnPath( const char *zname, char *zpath, int *depth ) {
 
1816
        int len, at, newdep;
 
1817
 
 
1818
        newdep = 0;
 
1819
        zpath[0] = 0;
 
1820
        len = 0;
 
1821
        at = 0;
 
1822
 
 
1823
        while(zname[at] != 0)
 
1824
        {
 
1825
                if (zname[at]=='/' || zname[at]=='\\') {
 
1826
                        len = at;
 
1827
                        newdep++;
 
1828
                }
 
1829
                at++;
 
1830
        }
 
1831
        strcpy(zpath, zname);
 
1832
        zpath[len] = 0;
 
1833
        *depth = newdep;
 
1834
 
 
1835
        return len;
 
1836
}
 
1837
 
 
1838
/*
 
1839
==================
 
1840
FS_AddFileToList
 
1841
==================
 
1842
*/
 
1843
static int FS_AddFileToList( char *name, char *list[MAX_FOUND_FILES], int nfiles ) {
 
1844
        int             i;
 
1845
 
 
1846
        if ( nfiles == MAX_FOUND_FILES - 1 ) {
 
1847
                return nfiles;
 
1848
        }
 
1849
        for ( i = 0 ; i < nfiles ; i++ ) {
 
1850
                if ( !Q_stricmp( name, list[i] ) ) {
 
1851
                        return nfiles;          // allready in list
 
1852
                }
 
1853
        }
 
1854
        list[nfiles] = CopyString( name );
 
1855
        nfiles++;
 
1856
 
 
1857
        return nfiles;
 
1858
}
 
1859
 
 
1860
/*
 
1861
===============
 
1862
FS_ListFilteredFiles
 
1863
 
 
1864
Returns a uniqued list of files that match the given criteria
 
1865
from all search paths
 
1866
===============
 
1867
*/
 
1868
char **FS_ListFilteredFiles( const char *path, const char *extension, char *filter, int *numfiles ) {
 
1869
        int                             nfiles;
 
1870
        char                    **listCopy;
 
1871
        char                    *list[MAX_FOUND_FILES];
 
1872
        searchpath_t    *search;
 
1873
        int                             i;
 
1874
        int                             pathLength;
 
1875
        int                             extensionLength;
 
1876
        int                             length, pathDepth, temp;
 
1877
        pack_t                  *pak;
 
1878
        fileInPack_t    *buildBuffer;
 
1879
        char                    zpath[MAX_ZPATH];
 
1880
 
 
1881
        if ( !fs_searchpaths ) {
 
1882
                Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
 
1883
        }
 
1884
 
 
1885
        if ( !path ) {
 
1886
                *numfiles = 0;
 
1887
                return NULL;
 
1888
        }
 
1889
        if ( !extension ) {
 
1890
                extension = "";
 
1891
        }
 
1892
 
 
1893
        pathLength = strlen( path );
 
1894
        if ( path[pathLength-1] == '\\' || path[pathLength-1] == '/' ) {
 
1895
                pathLength--;
 
1896
        }
 
1897
        extensionLength = strlen( extension );
 
1898
        nfiles = 0;
 
1899
        FS_ReturnPath(path, zpath, &pathDepth);
 
1900
 
 
1901
        //
 
1902
        // search through the path, one element at a time, adding to list
 
1903
        //
 
1904
        for (search = fs_searchpaths ; search ; search = search->next) {
 
1905
                // is the element a pak file?
 
1906
                if (search->pack) {
 
1907
 
 
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) ) {
 
1911
                                continue;
 
1912
                        }
 
1913
 
 
1914
                        // look through all the pak file elements
 
1915
                        pak = search->pack;
 
1916
                        buildBuffer = pak->buildBuffer;
 
1917
                        for (i = 0; i < pak->numfiles; i++) {
 
1918
                                char    *name;
 
1919
                                int             zpathLen, depth;
 
1920
 
 
1921
                                // check for directory match
 
1922
                                name = buildBuffer[i].name;
 
1923
                                //
 
1924
                                if (filter) {
 
1925
                                        // case insensitive
 
1926
                                        if (!Com_FilterPath( filter, name, qfalse ))
 
1927
                                                continue;
 
1928
                                        // unique the match
 
1929
                                        nfiles = FS_AddFileToList( name, list, nfiles );
 
1930
                                }
 
1931
                                else {
 
1932
 
 
1933
                                        zpathLen = FS_ReturnPath(name, zpath, &depth);
 
1934
 
 
1935
                                        if ( (depth-pathDepth)>2 || pathLength > zpathLen || Q_stricmpn( name, path, pathLength ) ) {
 
1936
                                                continue;
 
1937
                                        }
 
1938
 
 
1939
                                        // check for extension match
 
1940
                                        length = strlen( name );
 
1941
                                        if ( length < extensionLength ) {
 
1942
                                                continue;
 
1943
                                        }
 
1944
 
 
1945
                                        if ( Q_stricmp( name + length - extensionLength, extension ) ) {
 
1946
                                                continue;
 
1947
                                        }
 
1948
                                        // unique the match
 
1949
 
 
1950
                                        temp = pathLength;
 
1951
                                        if (pathLength) {
 
1952
                                                temp++;         // include the '/'
 
1953
                                        }
 
1954
                                        nfiles = FS_AddFileToList( name + temp, list, nfiles );
 
1955
                                }
 
1956
                        }
 
1957
                } else if (search->dir) { // scan for files in the filesystem
 
1958
                        char    *netpath;
 
1959
                        int             numSysFiles;
 
1960
                        char    **sysFiles;
 
1961
                        char    *name;
 
1962
 
 
1963
                        // don't scan directories for files if we are pure or restricted
 
1964
                        if ( fs_restrict->integer || fs_numServerPaks ) {
 
1965
                        continue;
 
1966
                    } else {
 
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++ ) {
 
1970
                                        // unique the match
 
1971
                                        name = sysFiles[i];
 
1972
                                        nfiles = FS_AddFileToList( name, list, nfiles );
 
1973
                                }
 
1974
                                Sys_FreeFileList( sysFiles );
 
1975
                        }
 
1976
                }               
 
1977
        }
 
1978
 
 
1979
        // return a copy of the list
 
1980
        *numfiles = nfiles;
 
1981
 
 
1982
        if ( !nfiles ) {
 
1983
                return NULL;
 
1984
        }
 
1985
 
 
1986
        listCopy = Z_Malloc( ( nfiles + 1 ) * sizeof( *listCopy ) );
 
1987
        for ( i = 0 ; i < nfiles ; i++ ) {
 
1988
                listCopy[i] = list[i];
 
1989
        }
 
1990
        listCopy[i] = NULL;
 
1991
 
 
1992
        return listCopy;
 
1993
}
 
1994
 
 
1995
/*
 
1996
=================
 
1997
FS_ListFiles
 
1998
=================
 
1999
*/
 
2000
char **FS_ListFiles( const char *path, const char *extension, int *numfiles ) {
 
2001
        return FS_ListFilteredFiles( path, extension, NULL, numfiles );
 
2002
}
 
2003
 
 
2004
/*
 
2005
=================
 
2006
FS_FreeFileList
 
2007
=================
 
2008
*/
 
2009
void FS_FreeFileList( char **list ) {
 
2010
        int             i;
 
2011
 
 
2012
        if ( !fs_searchpaths ) {
 
2013
                Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
 
2014
        }
 
2015
 
 
2016
        if ( !list ) {
 
2017
                return;
 
2018
        }
 
2019
 
 
2020
        for ( i = 0 ; list[i] ; i++ ) {
 
2021
                Z_Free( list[i] );
 
2022
        }
 
2023
 
 
2024
        Z_Free( list );
 
2025
}
 
2026
 
 
2027
 
 
2028
/*
 
2029
================
 
2030
FS_GetFileList
 
2031
================
 
2032
*/
 
2033
int     FS_GetFileList(  const char *path, const char *extension, char *listbuf, int bufsize ) {
 
2034
        int             nFiles, i, nTotal, nLen;
 
2035
        char **pFiles = NULL;
 
2036
 
 
2037
        *listbuf = 0;
 
2038
        nFiles = 0;
 
2039
        nTotal = 0;
 
2040
 
 
2041
        if (Q_stricmp(path, "$modlist") == 0) {
 
2042
                return FS_GetModList(listbuf, bufsize);
 
2043
        }
 
2044
 
 
2045
        pFiles = FS_ListFiles(path, extension, &nFiles);
 
2046
 
 
2047
        for (i =0; i < nFiles; i++) {
 
2048
                nLen = strlen(pFiles[i]) + 1;
 
2049
                if (nTotal + nLen + 1 < bufsize) {
 
2050
                        strcpy(listbuf, pFiles[i]);
 
2051
                        listbuf += nLen;
 
2052
                        nTotal += nLen;
 
2053
                }
 
2054
                else {
 
2055
                        nFiles = i;
 
2056
                        break;
 
2057
                }
 
2058
        }
 
2059
 
 
2060
        FS_FreeFileList(pFiles);
 
2061
 
 
2062
        return nFiles;
 
2063
}
 
2064
 
 
2065
/*
 
2066
=======================
 
2067
Sys_ConcatenateFileLists
 
2068
 
 
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)
 
2072
 
 
2073
FIXME TTimo those two should move to common.c next to Sys_ListFiles
 
2074
=======================
 
2075
 */
 
2076
static unsigned int Sys_CountFileList(char **list)
 
2077
{
 
2078
  int i = 0;
 
2079
 
 
2080
  if (list)
 
2081
  {
 
2082
    while (*list)
 
2083
    {
 
2084
      list++;
 
2085
      i++;
 
2086
    }
 
2087
  }
 
2088
  return i;
 
2089
}
 
2090
 
 
2091
static char** Sys_ConcatenateFileLists( char **list0, char **list1, char **list2 )
 
2092
{
 
2093
  int totalLength = 0;
 
2094
  char** cat = NULL, **dst, **src;
 
2095
 
 
2096
  totalLength += Sys_CountFileList(list0);
 
2097
  totalLength += Sys_CountFileList(list1);
 
2098
  totalLength += Sys_CountFileList(list2);
 
2099
 
 
2100
  /* Create new list. */
 
2101
  dst = cat = Z_Malloc( ( totalLength + 1 ) * sizeof( char* ) );
 
2102
 
 
2103
  /* Copy over lists. */
 
2104
  if (list0)
 
2105
  {
 
2106
    for (src = list0; *src; src++, dst++)
 
2107
      *dst = *src;
 
2108
  }
 
2109
  if (list1)
 
2110
  {
 
2111
    for (src = list1; *src; src++, dst++)
 
2112
      *dst = *src;
 
2113
  }
 
2114
  if (list2)
 
2115
  {
 
2116
    for (src = list2; *src; src++, dst++)
 
2117
      *dst = *src;
 
2118
  }
 
2119
 
 
2120
  // Terminate the list
 
2121
  *dst = NULL;
 
2122
 
 
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 );
 
2128
 
 
2129
  return cat;
 
2130
}
 
2131
 
 
2132
/*
 
2133
================
 
2134
FS_GetModList
 
2135
 
 
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
 
2139
================
 
2140
*/
 
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;
 
2145
  char *name, *path;
 
2146
  char descPath[MAX_OSPATH];
 
2147
  fileHandle_t descHandle;
 
2148
 
 
2149
  int dummy;
 
2150
  char **pFiles0 = NULL;
 
2151
  char **pFiles1 = NULL;
 
2152
  char **pFiles2 = NULL;
 
2153
  qboolean bDrop = qfalse;
 
2154
 
 
2155
  *listbuf = 0;
 
2156
  nMods = nPotential = nTotal = 0;
 
2157
 
 
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);
 
2165
 
 
2166
  for ( i = 0 ; i < nPotential ; i++ ) {
 
2167
    name = pFiles[i];
 
2168
    // NOTE: cleaner would involve more changes
 
2169
    // ignore duplicate mod directories
 
2170
    if (i!=0) {
 
2171
      bDrop = qfalse;
 
2172
      for(j=0; j<i; j++)
 
2173
      {
 
2174
        if (Q_stricmp(pFiles[j],name)==0) {
 
2175
          // this one can be dropped
 
2176
          bDrop = qtrue;
 
2177
          break;
 
2178
        }
 
2179
      }
 
2180
    }
 
2181
    if (bDrop) {
 
2182
      continue;
 
2183
    }
 
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, "" );
 
2192
      nPaks = 0;
 
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
 
2195
 
 
2196
      /* Try on cd path */
 
2197
      if( nPaks <= 0 ) {
 
2198
        path = FS_BuildOSPath( fs_cdpath->string, name, "" );
 
2199
        nPaks = 0;
 
2200
        pPaks = Sys_ListFiles( path, ".pk3", NULL, &nPaks, qfalse );
 
2201
        Sys_FreeFileList( pPaks );
 
2202
      }
 
2203
 
 
2204
      /* try on home path */
 
2205
      if ( nPaks <= 0 )
 
2206
      {
 
2207
        path = FS_BuildOSPath( fs_homepath->string, name, "" );
 
2208
        nPaks = 0;
 
2209
        pPaks = Sys_ListFiles( path, ".pk3", NULL, &nPaks, qfalse );
 
2210
        Sys_FreeFileList( pPaks );
 
2211
      }
 
2212
 
 
2213
      if (nPaks > 0) {
 
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
 
2217
        descPath[0] = '\0';
 
2218
        strcpy(descPath, name);
 
2219
        strcat(descPath, "/description.txt");
 
2220
        nDescLen = FS_SV_FOpenFileRead( descPath, &descHandle );
 
2221
        if ( nDescLen > 0 && descHandle) {
 
2222
          FILE *file;
 
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';
 
2228
          }
 
2229
          FS_FCloseFile(descHandle);
 
2230
        } else {
 
2231
          strcpy(descPath, name);
 
2232
        }
 
2233
        nDescLen = strlen(descPath) + 1;
 
2234
 
 
2235
        if (nTotal + nLen + 1 + nDescLen + 1 < bufsize) {
 
2236
          strcpy(listbuf, name);
 
2237
          listbuf += nLen;
 
2238
          strcpy(listbuf, descPath);
 
2239
          listbuf += nDescLen;
 
2240
          nTotal += nLen + nDescLen;
 
2241
          nMods++;
 
2242
        }
 
2243
        else {
 
2244
          break;
 
2245
        }
 
2246
      }
 
2247
    }
 
2248
  }
 
2249
  Sys_FreeFileList( pFiles );
 
2250
 
 
2251
  return nMods;
 
2252
}
 
2253
 
 
2254
 
 
2255
 
 
2256
 
 
2257
//============================================================================
 
2258
 
 
2259
/*
 
2260
================
 
2261
FS_Dir_f
 
2262
================
 
2263
*/
 
2264
void FS_Dir_f( void ) {
 
2265
        char    *path;
 
2266
        char    *extension;
 
2267
        char    **dirnames;
 
2268
        int             ndirs;
 
2269
        int             i;
 
2270
 
 
2271
        if ( Cmd_Argc() < 2 || Cmd_Argc() > 3 ) {
 
2272
                Com_Printf( "usage: dir <directory> [extension]\n" );
 
2273
                return;
 
2274
        }
 
2275
 
 
2276
        if ( Cmd_Argc() == 2 ) {
 
2277
                path = Cmd_Argv( 1 );
 
2278
                extension = "";
 
2279
        } else {
 
2280
                path = Cmd_Argv( 1 );
 
2281
                extension = Cmd_Argv( 2 );
 
2282
        }
 
2283
 
 
2284
        Com_Printf( "Directory of %s %s\n", path, extension );
 
2285
        Com_Printf( "---------------\n" );
 
2286
 
 
2287
        dirnames = FS_ListFiles( path, extension, &ndirs );
 
2288
 
 
2289
        for ( i = 0; i < ndirs; i++ ) {
 
2290
                Com_Printf( "%s\n", dirnames[i] );
 
2291
        }
 
2292
        FS_FreeFileList( dirnames );
 
2293
}
 
2294
 
 
2295
/*
 
2296
===========
 
2297
FS_ConvertPath
 
2298
===========
 
2299
*/
 
2300
void FS_ConvertPath( char *s ) {
 
2301
        while (*s) {
 
2302
                if ( *s == '\\' || *s == ':' ) {
 
2303
                        *s = '/';
 
2304
                }
 
2305
                s++;
 
2306
        }
 
2307
}
 
2308
 
 
2309
/*
 
2310
===========
 
2311
FS_PathCmp
 
2312
 
 
2313
Ignore case and seprator char distinctions
 
2314
===========
 
2315
*/
 
2316
int FS_PathCmp( const char *s1, const char *s2 ) {
 
2317
        int             c1, c2;
 
2318
        
 
2319
        do {
 
2320
                c1 = *s1++;
 
2321
                c2 = *s2++;
 
2322
 
 
2323
                if (c1 >= 'a' && c1 <= 'z') {
 
2324
                        c1 -= ('a' - 'A');
 
2325
                }
 
2326
                if (c2 >= 'a' && c2 <= 'z') {
 
2327
                        c2 -= ('a' - 'A');
 
2328
                }
 
2329
 
 
2330
                if ( c1 == '\\' || c1 == ':' ) {
 
2331
                        c1 = '/';
 
2332
                }
 
2333
                if ( c2 == '\\' || c2 == ':' ) {
 
2334
                        c2 = '/';
 
2335
                }
 
2336
                
 
2337
                if (c1 < c2) {
 
2338
                        return -1;              // strings not equal
 
2339
                }
 
2340
                if (c1 > c2) {
 
2341
                        return 1;
 
2342
                }
 
2343
        } while (c1);
 
2344
        
 
2345
        return 0;               // strings are equal
 
2346
}
 
2347
 
 
2348
/*
 
2349
================
 
2350
FS_SortFileList
 
2351
================
 
2352
*/
 
2353
void FS_SortFileList(char **filelist, int numfiles) {
 
2354
        int i, j, k, numsortedfiles;
 
2355
        char **sortedlist;
 
2356
 
 
2357
        sortedlist = Z_Malloc( ( numfiles + 1 ) * sizeof( *sortedlist ) );
 
2358
        sortedlist[0] = NULL;
 
2359
        numsortedfiles = 0;
 
2360
        for (i = 0; i < numfiles; i++) {
 
2361
                for (j = 0; j < numsortedfiles; j++) {
 
2362
                        if (FS_PathCmp(filelist[i], sortedlist[j]) < 0) {
 
2363
                                break;
 
2364
                        }
 
2365
                }
 
2366
                for (k = numsortedfiles; k > j; k--) {
 
2367
                        sortedlist[k] = sortedlist[k-1];
 
2368
                }
 
2369
                sortedlist[j] = filelist[i];
 
2370
                numsortedfiles++;
 
2371
        }
 
2372
        Com_Memcpy(filelist, sortedlist, numfiles * sizeof( *filelist ) );
 
2373
        Z_Free(sortedlist);
 
2374
}
 
2375
 
 
2376
/*
 
2377
================
 
2378
FS_NewDir_f
 
2379
================
 
2380
*/
 
2381
void FS_NewDir_f( void ) {
 
2382
        char    *filter;
 
2383
        char    **dirnames;
 
2384
        int             ndirs;
 
2385
        int             i;
 
2386
 
 
2387
        if ( Cmd_Argc() < 2 ) {
 
2388
                Com_Printf( "usage: fdir <filter>\n" );
 
2389
                Com_Printf( "example: fdir *q3dm*.bsp\n");
 
2390
                return;
 
2391
        }
 
2392
 
 
2393
        filter = Cmd_Argv( 1 );
 
2394
 
 
2395
        Com_Printf( "---------------\n" );
 
2396
 
 
2397
        dirnames = FS_ListFilteredFiles( "", "", filter, &ndirs );
 
2398
 
 
2399
        FS_SortFileList(dirnames, ndirs);
 
2400
 
 
2401
        for ( i = 0; i < ndirs; i++ ) {
 
2402
                FS_ConvertPath(dirnames[i]);
 
2403
                Com_Printf( "%s\n", dirnames[i] );
 
2404
        }
 
2405
        Com_Printf( "%d files listed\n", ndirs );
 
2406
        FS_FreeFileList( dirnames );
 
2407
}
 
2408
 
 
2409
/*
 
2410
============
 
2411
FS_Path_f
 
2412
 
 
2413
============
 
2414
*/
 
2415
void FS_Path_f( void ) {
 
2416
        searchpath_t    *s;
 
2417
        int                             i;
 
2418
 
 
2419
        Com_Printf ("Current search path:\n");
 
2420
        for (s = fs_searchpaths; s; s = s->next) {
 
2421
                if (s->pack) {
 
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" );
 
2426
                                } else {
 
2427
                                        Com_Printf( "    on the pure list\n" );
 
2428
                                }
 
2429
                        }
 
2430
                } else {
 
2431
                        Com_Printf ("%s/%s\n", s->dir->path, s->dir->gamedir );
 
2432
                }
 
2433
        }
 
2434
 
 
2435
 
 
2436
        Com_Printf( "\n" );
 
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 );
 
2440
                }
 
2441
        }
 
2442
}
 
2443
 
 
2444
/*
 
2445
============
 
2446
FS_TouchFile_f
 
2447
 
 
2448
The only purpose of this function is to allow game script files to copy
 
2449
arbitrary files furing an "fs_copyfiles 1" run.
 
2450
============
 
2451
*/
 
2452
void FS_TouchFile_f( void ) {
 
2453
        fileHandle_t    f;
 
2454
 
 
2455
        if ( Cmd_Argc() != 2 ) {
 
2456
                Com_Printf( "Usage: touchFile <file>\n" );
 
2457
                return;
 
2458
        }
 
2459
 
 
2460
        FS_FOpenFileRead( Cmd_Argv( 1 ), &f, qfalse );
 
2461
        if ( f ) {
 
2462
                FS_FCloseFile( f );
 
2463
        }
 
2464
}
 
2465
 
 
2466
//===========================================================================
 
2467
 
 
2468
 
 
2469
static int QDECL paksort( const void *a, const void *b ) {
 
2470
        char    *aa, *bb;
 
2471
 
 
2472
        aa = *(char **)a;
 
2473
        bb = *(char **)b;
 
2474
 
 
2475
        return FS_PathCmp( aa, bb );
 
2476
}
 
2477
 
 
2478
/*
 
2479
================
 
2480
FS_AddGameDirectory
 
2481
 
 
2482
Sets fs_gamedir, adds the directory to the head of the path,
 
2483
then loads the zip headers
 
2484
================
 
2485
*/
 
2486
#define MAX_PAKFILES    1024
 
2487
static void FS_AddGameDirectory( const char *path, const char *dir ) {
 
2488
        searchpath_t    *sp;
 
2489
        int                             i;
 
2490
        searchpath_t    *search;
 
2491
        pack_t                  *pak;
 
2492
        char                    *pakfile;
 
2493
        int                             numfiles;
 
2494
        char                    **pakfiles;
 
2495
        char                    *sorted[MAX_PAKFILES];
 
2496
 
 
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
 
2502
                }
 
2503
        }
 
2504
        
 
2505
        Q_strncpyz( fs_gamedir, dir, sizeof( fs_gamedir ) );
 
2506
 
 
2507
        //
 
2508
        // add the directory to the search path
 
2509
        //
 
2510
        search = Z_Malloc (sizeof(searchpath_t));
 
2511
        search->dir = Z_Malloc( sizeof( *search->dir ) );
 
2512
 
 
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;
 
2517
 
 
2518
        // find all pak files in this directory
 
2519
        pakfile = FS_BuildOSPath( path, dir, "" );
 
2520
        pakfile[ strlen(pakfile) - 1 ] = 0;     // strip the trailing slash
 
2521
 
 
2522
        pakfiles = Sys_ListFiles( pakfile, ".pk3", NULL, &numfiles, qfalse );
 
2523
 
 
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;
 
2528
        }
 
2529
        for ( i = 0 ; i < numfiles ; i++ ) {
 
2530
                sorted[i] = pakfiles[i];
 
2531
        }
 
2532
 
 
2533
        qsort( sorted, numfiles, sizeof(char*), paksort );
 
2534
 
 
2535
        for ( i = 0 ; i < numfiles ; i++ ) {
 
2536
                pakfile = FS_BuildOSPath( path, dir, sorted[i] );
 
2537
                if ( ( pak = FS_LoadZipFile( pakfile, sorted[i] ) ) == 0 )
 
2538
                        continue;
 
2539
                // store the game name for downloading
 
2540
                strcpy(pak->pakGamename, dir);
 
2541
 
 
2542
                search = Z_Malloc (sizeof(searchpath_t));
 
2543
                search->pack = pak;
 
2544
                search->next = fs_searchpaths;
 
2545
                fs_searchpaths = search;
 
2546
        }
 
2547
 
 
2548
        // done
 
2549
        Sys_FreeFileList( pakfiles );
 
2550
}
 
2551
 
 
2552
/*
 
2553
================
 
2554
FS_idPak
 
2555
================
 
2556
*/
 
2557
qboolean FS_idPak( char *pak, char *base ) {
 
2558
        int i;
 
2559
 
 
2560
        for (i = 0; i < NUM_ID_PAKS; i++) {
 
2561
                if ( !FS_FilenameCompare(pak, va("%s/pak%d", base, i)) ) {
 
2562
                        break;
 
2563
                }
 
2564
        }
 
2565
        if (i < NUM_ID_PAKS) {
 
2566
                return qtrue;
 
2567
        }
 
2568
        return qfalse;
 
2569
}
 
2570
 
 
2571
/*
 
2572
================
 
2573
FS_idPak
 
2574
 
 
2575
Check whether the string contains stuff like "../" to prevent directory traversal bugs
 
2576
and return qtrue if it does.
 
2577
================
 
2578
*/
 
2579
 
 
2580
qboolean FS_CheckDirTraversal(const char *checkdir)
 
2581
{
 
2582
        if(strstr(checkdir, "../") || strstr(checkdir, "..\\"))
 
2583
                return qtrue;
 
2584
        
 
2585
        return qfalse;
 
2586
}
 
2587
 
 
2588
/*
 
2589
================
 
2590
FS_ComparePaks
 
2591
 
 
2592
----------------
 
2593
dlstring == qtrue
 
2594
 
 
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.
 
2597
 
 
2598
The string is the format:
 
2599
 
 
2600
@remotename@localname [repeat]
 
2601
 
 
2602
static int              fs_numServerReferencedPaks;
 
2603
static int              fs_serverReferencedPaks[MAX_SEARCH_PATHS];
 
2604
static char             *fs_serverReferencedPakNames[MAX_SEARCH_PATHS];
 
2605
 
 
2606
----------------
 
2607
dlstring == qfalse
 
2608
 
 
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)
 
2611
 
 
2612
================
 
2613
*/
 
2614
qboolean FS_ComparePaks( char *neededpaks, int len, qboolean dlstring ) {
 
2615
        searchpath_t    *sp;
 
2616
        qboolean havepak, badchecksum;
 
2617
        char *origpos = neededpaks;
 
2618
        int i;
 
2619
 
 
2620
        if (!fs_numServerReferencedPaks)
 
2621
                return qfalse; // Server didn't send any pack information along
 
2622
 
 
2623
        *neededpaks = 0;
 
2624
 
 
2625
        for ( i = 0 ; i < fs_numServerReferencedPaks ; i++ )
 
2626
        {
 
2627
                // Ok, see if we have this pak file
 
2628
                badchecksum = qfalse;
 
2629
                havepak = qfalse;
 
2630
 
 
2631
                // never autodownload any of the id paks
 
2632
                if ( FS_idPak(fs_serverReferencedPakNames[i], BASEGAME) || FS_idPak(fs_serverReferencedPakNames[i], "missionpack") ) {
 
2633
                        continue;
 
2634
                }
 
2635
 
 
2636
                // Make sure the server cannot make us write to non-quake3 directories.
 
2637
                if(FS_CheckDirTraversal(fs_serverReferencedPakNames[i]))
 
2638
                {
 
2639
                        Com_Printf("WARNING: Invalid download name %s\n", fs_serverReferencedPakNames[i]);
 
2640
                        continue;
 
2641
                }
 
2642
 
 
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!
 
2646
                                break;
 
2647
                        }
 
2648
                }
 
2649
 
 
2650
                if ( !havepak && fs_serverReferencedPakNames[i] && *fs_serverReferencedPakNames[i] ) { 
 
2651
                        // Don't got it
 
2652
 
 
2653
      if (dlstring)
 
2654
      {
 
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.
 
2658
        
 
2659
        origpos += strlen(origpos);
 
2660
        
 
2661
        // Remote name
 
2662
        Q_strcat( neededpaks, len, "@");
 
2663
        Q_strcat( neededpaks, len, fs_serverReferencedPakNames[i] );
 
2664
        Q_strcat( neededpaks, len, ".pk3" );
 
2665
 
 
2666
        // Local name
 
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] ) ) )
 
2670
        {
 
2671
          char st[MAX_ZPATH];
 
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 );
 
2676
        } else
 
2677
        {
 
2678
          Q_strcat( neededpaks, len, fs_serverReferencedPakNames[i] );
 
2679
          Q_strcat( neededpaks, len, ".pk3" );
 
2680
        }
 
2681
        
 
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)
 
2685
        {
 
2686
                *origpos = '\0';
 
2687
                break;
 
2688
        }
 
2689
      }
 
2690
      else
 
2691
      {
 
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] ) ) )
 
2696
        {
 
2697
          Q_strcat( neededpaks, len, " (local file exists with wrong checksum)");
 
2698
        }
 
2699
        Q_strcat( neededpaks, len, "\n");
 
2700
      }
 
2701
                }
 
2702
        }
 
2703
 
 
2704
        if ( *neededpaks ) {
 
2705
                return qtrue;
 
2706
        }
 
2707
 
 
2708
        return qfalse; // We have them all
 
2709
}
 
2710
 
 
2711
/*
 
2712
================
 
2713
FS_Shutdown
 
2714
 
 
2715
Frees all resources.
 
2716
================
 
2717
*/
 
2718
void FS_Shutdown( qboolean closemfp ) {
 
2719
        searchpath_t    *p, *next;
 
2720
        int     i;
 
2721
 
 
2722
        for(i = 0; i < MAX_FILE_HANDLES; i++) {
 
2723
                if (fsh[i].fileSize) {
 
2724
                        FS_FCloseFile(i);
 
2725
                }
 
2726
        }
 
2727
 
 
2728
        // free everything
 
2729
        for ( p = fs_searchpaths ; p ; p = next ) {
 
2730
                next = p->next;
 
2731
 
 
2732
                if ( p->pack ) {
 
2733
                        unzClose(p->pack->handle);
 
2734
                        Z_Free( p->pack->buildBuffer );
 
2735
                        Z_Free( p->pack );
 
2736
                }
 
2737
                if ( p->dir ) {
 
2738
                        Z_Free( p->dir );
 
2739
                }
 
2740
                Z_Free( p );
 
2741
        }
 
2742
 
 
2743
        // any FS_ calls will now be an error until reinitialized
 
2744
        fs_searchpaths = NULL;
 
2745
 
 
2746
        Cmd_RemoveCommand( "path" );
 
2747
        Cmd_RemoveCommand( "dir" );
 
2748
        Cmd_RemoveCommand( "fdir" );
 
2749
        Cmd_RemoveCommand( "touchFile" );
 
2750
 
 
2751
#ifdef FS_MISSING
 
2752
        if (closemfp) {
 
2753
                fclose(missingFiles);
 
2754
        }
 
2755
#endif
 
2756
}
 
2757
 
 
2758
void Com_AppendCDKey( const char *filename );
 
2759
void Com_ReadCDKey( const char *filename );
 
2760
 
 
2761
/*
 
2762
================
 
2763
FS_ReorderPurePaks
 
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
 
2766
================
 
2767
*/
 
2768
static void FS_ReorderPurePaks( void )
 
2769
{
 
2770
        searchpath_t *s;
 
2771
        int i;
 
2772
        searchpath_t **p_insert_index, // for linked list reordering
 
2773
                **p_previous; // when doing the scan
 
2774
        
 
2775
        // only relevant when connected to pure server
 
2776
        if ( !fs_numServerPaks )
 
2777
                return;
 
2778
        
 
2779
        fs_reordered = qfalse;
 
2780
        
 
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
 
2795
                        }
 
2796
                        p_previous = &s->next; 
 
2797
                }
 
2798
        }
 
2799
}
 
2800
 
 
2801
/*
 
2802
================
 
2803
FS_Startup
 
2804
================
 
2805
*/
 
2806
static void FS_Startup( const char *gameName ) {
 
2807
        const char *homePath;
 
2808
        cvar_t  *fs;
 
2809
 
 
2810
        Com_Printf( "----- FS_Startup -----\n" );
 
2811
 
 
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;
 
2820
        }
 
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 );
 
2824
 
 
2825
        // add search path elements in reverse priority order
 
2826
        if (fs_cdpath->string[0]) {
 
2827
                FS_AddGameDirectory( fs_cdpath->string, gameName );
 
2828
        }
 
2829
        if (fs_basepath->string[0]) {
 
2830
                FS_AddGameDirectory( fs_basepath->string, gameName );
 
2831
        }
 
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 );
 
2836
        }
 
2837
        
 
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);
 
2842
                }
 
2843
                if (fs_basepath->string[0]) {
 
2844
                        FS_AddGameDirectory(fs_basepath->string, fs_basegame->string);
 
2845
                }
 
2846
                if (fs_homepath->string[0] && Q_stricmp(fs_homepath->string,fs_basepath->string)) {
 
2847
                        FS_AddGameDirectory(fs_homepath->string, fs_basegame->string);
 
2848
                }
 
2849
        }
 
2850
 
 
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);
 
2855
                }
 
2856
                if (fs_basepath->string[0]) {
 
2857
                        FS_AddGameDirectory(fs_basepath->string, fs_gamedirvar->string);
 
2858
                }
 
2859
                if (fs_homepath->string[0] && Q_stricmp(fs_homepath->string,fs_basepath->string)) {
 
2860
                        FS_AddGameDirectory(fs_homepath->string, fs_gamedirvar->string);
 
2861
                }
 
2862
        }
 
2863
 
 
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 );
 
2868
        }
 
2869
 
 
2870
        // add our commands
 
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 );
 
2875
 
 
2876
        // https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=506
 
2877
        // reorder the pure pk3 files according to server order
 
2878
        FS_ReorderPurePaks();
 
2879
        
 
2880
        // print the current search paths
 
2881
        FS_Path_f();
 
2882
 
 
2883
        fs_gamedirvar->modified = qfalse; // We just loaded, it's not modified
 
2884
 
 
2885
        Com_Printf( "----------------------\n" );
 
2886
 
 
2887
#ifdef FS_MISSING
 
2888
        if (missingFiles == NULL) {
 
2889
                missingFiles = fopen( "\\missing.txt", "ab" );
 
2890
        }
 
2891
#endif
 
2892
        Com_Printf( "%d files in pk3 files\n", fs_packFiles );
 
2893
}
 
2894
 
 
2895
/*
 
2896
===================
 
2897
FS_CheckPak0
 
2898
 
 
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
 
2902
===================
 
2903
*/
 
2904
static void FS_CheckPak0( void )
 
2905
{
 
2906
/*
 
2907
        searchpath_t    *path;
 
2908
        qboolean                        foundPak0 = qfalse;
 
2909
 
 
2910
        for( path = fs_searchpaths; path; path = path->next ) {
 
2911
                if( path->pack &&
 
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 ))) {
 
2915
                        foundPak0 = qtrue;
 
2916
 
 
2917
                        if( path->pack->checksum == DEMO_PAK0_CHECKSUM ) {
 
2918
                                Com_Printf( "\n\n"
 
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 ) {
 
2925
                                Com_Printf( "\n\n"
 
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 );
 
2932
                        }
 
2933
                }
 
2934
        }
 
2935
 
 
2936
        if( !foundPak0 ) {
 
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);
 
2940
        }
 
2941
*/
 
2942
}
 
2943
 
 
2944
/*
 
2945
=====================
 
2946
FS_GamePureChecksum
 
2947
 
 
2948
Returns the checksum of the pk3 from which the server loaded the qagame.qvm
 
2949
=====================
 
2950
*/
 
2951
const char *FS_GamePureChecksum( void ) {
 
2952
        static char     info[MAX_STRING_TOKENS];
 
2953
        searchpath_t *search;
 
2954
 
 
2955
        info[0] = 0;
 
2956
 
 
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);
 
2962
                        }
 
2963
                }
 
2964
        }
 
2965
 
 
2966
        return info;
 
2967
}
 
2968
 
 
2969
/*
 
2970
=====================
 
2971
FS_LoadedPakChecksums
 
2972
 
 
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
=====================
 
2976
*/
 
2977
const char *FS_LoadedPakChecksums( void ) {
 
2978
        static char     info[BIG_INFO_STRING];
 
2979
        searchpath_t    *search;
 
2980
 
 
2981
        info[0] = 0;
 
2982
 
 
2983
        for ( search = fs_searchpaths ; search ; search = search->next ) {
 
2984
                // is the element a pak file? 
 
2985
                if ( !search->pack ) {
 
2986
                        continue;
 
2987
                }
 
2988
 
 
2989
                Q_strcat( info, sizeof( info ), va("%i ", search->pack->checksum ) );
 
2990
        }
 
2991
 
 
2992
        return info;
 
2993
}
 
2994
 
 
2995
/*
 
2996
=====================
 
2997
FS_LoadedPakNames
 
2998
 
 
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
=====================
 
3002
*/
 
3003
const char *FS_LoadedPakNames( void ) {
 
3004
        static char     info[BIG_INFO_STRING];
 
3005
        searchpath_t    *search;
 
3006
 
 
3007
        info[0] = 0;
 
3008
 
 
3009
        for ( search = fs_searchpaths ; search ; search = search->next ) {
 
3010
                // is the element a pak file?
 
3011
                if ( !search->pack ) {
 
3012
                        continue;
 
3013
                }
 
3014
 
 
3015
                if (*info) {
 
3016
                        Q_strcat(info, sizeof( info ), " " );
 
3017
                }
 
3018
                Q_strcat( info, sizeof( info ), search->pack->pakBasename );
 
3019
        }
 
3020
 
 
3021
        return info;
 
3022
}
 
3023
 
 
3024
/*
 
3025
=====================
 
3026
FS_LoadedPakPureChecksums
 
3027
 
 
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
 
3030
back to the server.
 
3031
=====================
 
3032
*/
 
3033
const char *FS_LoadedPakPureChecksums( void ) {
 
3034
        static char     info[BIG_INFO_STRING];
 
3035
        searchpath_t    *search;
 
3036
 
 
3037
        info[0] = 0;
 
3038
 
 
3039
        for ( search = fs_searchpaths ; search ; search = search->next ) {
 
3040
                // is the element a pak file? 
 
3041
                if ( !search->pack ) {
 
3042
                        continue;
 
3043
                }
 
3044
 
 
3045
                Q_strcat( info, sizeof( info ), va("%i ", search->pack->pure_checksum ) );
 
3046
        }
 
3047
 
 
3048
        return info;
 
3049
}
 
3050
 
 
3051
/*
 
3052
=====================
 
3053
FS_ReferencedPakChecksums
 
3054
 
 
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
=====================
 
3058
*/
 
3059
const char *FS_ReferencedPakChecksums( void ) {
 
3060
        static char     info[BIG_INFO_STRING];
 
3061
        searchpath_t *search;
 
3062
 
 
3063
        info[0] = 0;
 
3064
 
 
3065
 
 
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 ) );
 
3071
                        }
 
3072
                }
 
3073
        }
 
3074
 
 
3075
        return info;
 
3076
}
 
3077
 
 
3078
/*
 
3079
=====================
 
3080
FS_ReferencedPakPureChecksums
 
3081
 
 
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 
 
3084
 
 
3085
The string has a specific order, "cgame ui @ ref1 ref2 ref3 ..."
 
3086
=====================
 
3087
*/
 
3088
const char *FS_ReferencedPakPureChecksums( void ) {
 
3089
        static char     info[BIG_INFO_STRING];
 
3090
        searchpath_t    *search;
 
3091
        int nFlags, numPaks, checksum;
 
3092
 
 
3093
        info[0] = 0;
 
3094
 
 
3095
        checksum = fs_checksumFeed;
 
3096
        numPaks = 0;
 
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)] = ' ';
 
3105
                }
 
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)) {
 
3111
                                        break;
 
3112
                                }
 
3113
                                checksum ^= search->pack->pure_checksum;
 
3114
                                numPaks++;
 
3115
                        }
 
3116
                }
 
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 ) );
 
3120
                }
 
3121
        }
 
3122
        // last checksum is the encoded number of referenced pk3s
 
3123
        checksum ^= numPaks;
 
3124
        Q_strcat( info, sizeof( info ), va("%i ", checksum ) );
 
3125
 
 
3126
        return info;
 
3127
}
 
3128
 
 
3129
/*
 
3130
=====================
 
3131
FS_ReferencedPakNames
 
3132
 
 
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
=====================
 
3136
*/
 
3137
const char *FS_ReferencedPakNames( void ) {
 
3138
        static char     info[BIG_INFO_STRING];
 
3139
        searchpath_t    *search;
 
3140
 
 
3141
        info[0] = 0;
 
3142
 
 
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 ) {
 
3148
                        if (*info) {
 
3149
                                Q_strcat(info, sizeof( info ), " " );
 
3150
                        }
 
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 );
 
3155
                        }
 
3156
                }
 
3157
        }
 
3158
 
 
3159
        return info;
 
3160
}
 
3161
 
 
3162
/*
 
3163
=====================
 
3164
FS_ClearPakReferences
 
3165
=====================
 
3166
*/
 
3167
void FS_ClearPakReferences( int flags ) {
 
3168
        searchpath_t *search;
 
3169
 
 
3170
        if ( !flags ) {
 
3171
                flags = -1;
 
3172
        }
 
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;
 
3177
                }
 
3178
        }
 
3179
}
 
3180
 
 
3181
 
 
3182
/*
 
3183
=====================
 
3184
FS_PureServerSetLoadedPaks
 
3185
 
 
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
=====================
 
3191
*/
 
3192
void FS_PureServerSetLoadedPaks( const char *pakSums, const char *pakNames ) {
 
3193
        int             i, c, d;
 
3194
 
 
3195
        Cmd_TokenizeString( pakSums );
 
3196
 
 
3197
        c = Cmd_Argc();
 
3198
        if ( c > MAX_SEARCH_PATHS ) {
 
3199
                c = MAX_SEARCH_PATHS;
 
3200
        }
 
3201
 
 
3202
        fs_numServerPaks = c;
 
3203
 
 
3204
        for ( i = 0 ; i < c ; i++ ) {
 
3205
                fs_serverPaks[i] = atoi( Cmd_Argv( i ) );
 
3206
        }
 
3207
 
 
3208
        if (fs_numServerPaks) {
 
3209
                Com_DPrintf( "Connected to a pure server.\n" );
 
3210
        }
 
3211
        else
 
3212
        {
 
3213
                if (fs_reordered)
 
3214
                {
 
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);
 
3219
                        return;
 
3220
                }
 
3221
        }
 
3222
 
 
3223
        for ( i = 0 ; i < c ; i++ ) {
 
3224
                if (fs_serverPakNames[i]) {
 
3225
                        Z_Free(fs_serverPakNames[i]);
 
3226
                }
 
3227
                fs_serverPakNames[i] = NULL;
 
3228
        }
 
3229
        if ( pakNames && *pakNames ) {
 
3230
                Cmd_TokenizeString( pakNames );
 
3231
 
 
3232
                d = Cmd_Argc();
 
3233
                if ( d > MAX_SEARCH_PATHS ) {
 
3234
                        d = MAX_SEARCH_PATHS;
 
3235
                }
 
3236
 
 
3237
                for ( i = 0 ; i < d ; i++ ) {
 
3238
                        fs_serverPakNames[i] = CopyString( Cmd_Argv( i ) );
 
3239
                }
 
3240
        }
 
3241
}
 
3242
 
 
3243
/*
 
3244
=====================
 
3245
FS_PureServerSetReferencedPaks
 
3246
 
 
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
=====================
 
3251
*/
 
3252
void FS_PureServerSetReferencedPaks( const char *pakSums, const char *pakNames ) {
 
3253
        int             i, c, d = 0;
 
3254
 
 
3255
        Cmd_TokenizeString( pakSums );
 
3256
 
 
3257
        c = Cmd_Argc();
 
3258
        if ( c > MAX_SEARCH_PATHS ) {
 
3259
                c = MAX_SEARCH_PATHS;
 
3260
        }
 
3261
 
 
3262
        for ( i = 0 ; i < c ; i++ ) {
 
3263
                fs_serverReferencedPaks[i] = atoi( Cmd_Argv( i ) );
 
3264
        }
 
3265
 
 
3266
        for (i = 0 ; i < sizeof(fs_serverReferencedPakNames) / sizeof(*fs_serverReferencedPakNames); i++)
 
3267
        {
 
3268
                if(fs_serverReferencedPakNames[i])
 
3269
                        Z_Free(fs_serverReferencedPakNames[i]);
 
3270
 
 
3271
                fs_serverReferencedPakNames[i] = NULL;
 
3272
        }
 
3273
 
 
3274
        if ( pakNames && *pakNames ) {
 
3275
                Cmd_TokenizeString( pakNames );
 
3276
 
 
3277
                d = Cmd_Argc();
 
3278
 
 
3279
                if(d > c)
 
3280
                        d = c;
 
3281
 
 
3282
                for ( i = 0 ; i < d ; i++ ) {
 
3283
                        fs_serverReferencedPakNames[i] = CopyString( Cmd_Argv( i ) );
 
3284
                }
 
3285
        }
 
3286
        
 
3287
        // ensure that there are as many checksums as there are pak names.
 
3288
        if(d < c)
 
3289
                c = d;
 
3290
        
 
3291
        fs_numServerReferencedPaks = c; 
 
3292
}
 
3293
 
 
3294
/*
 
3295
================
 
3296
FS_InitFilesystem
 
3297
 
 
3298
Called only at inital startup, not when the filesystem
 
3299
is resetting due to a game change
 
3300
================
 
3301
*/
 
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" );
 
3313
 
 
3314
        // try to start up normally
 
3315
        FS_Startup( BASEGAME );
 
3316
 
 
3317
        FS_CheckPak0( );
 
3318
 
 
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?
 
3325
        }
 
3326
 
 
3327
        Q_strncpyz(lastValidBase, fs_basepath->string, sizeof(lastValidBase));
 
3328
        Q_strncpyz(lastValidGame, fs_gamedirvar->string, sizeof(lastValidGame));
 
3329
 
 
3330
  // bk001208 - SafeMode see below, FIXME?
 
3331
}
 
3332
 
 
3333
 
 
3334
/*
 
3335
================
 
3336
FS_Restart
 
3337
================
 
3338
*/
 
3339
void FS_Restart( int checksumFeed ) {
 
3340
 
 
3341
        // free anything we currently have loaded
 
3342
        FS_Shutdown(qfalse);
 
3343
 
 
3344
        // set the checksum feed
 
3345
        fs_checksumFeed = checksumFeed;
 
3346
 
 
3347
        // clear pak references
 
3348
        FS_ClearPakReferences(0);
 
3349
 
 
3350
        // try to start up normally
 
3351
        FS_Startup( BASEGAME );
 
3352
 
 
3353
        // FS_CheckPak0( );
 
3354
 
 
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" );
 
3370
                        return;
 
3371
                }
 
3372
                Com_Error( ERR_FATAL, "Couldn't load default.cfg" );
 
3373
        }
 
3374
 
 
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");
 
3380
                }
 
3381
        }
 
3382
 
 
3383
        Q_strncpyz(lastValidBase, fs_basepath->string, sizeof(lastValidBase));
 
3384
        Q_strncpyz(lastValidGame, fs_gamedirvar->string, sizeof(lastValidGame));
 
3385
 
 
3386
}
 
3387
 
 
3388
/*
 
3389
=================
 
3390
FS_ConditionalRestart
 
3391
restart if necessary
 
3392
=================
 
3393
*/
 
3394
qboolean FS_ConditionalRestart( int checksumFeed ) {
 
3395
        if( fs_gamedirvar->modified || checksumFeed != fs_checksumFeed ) {
 
3396
                FS_Restart( checksumFeed );
 
3397
                return qtrue;
 
3398
        }
 
3399
        return qfalse;
 
3400
}
 
3401
 
 
3402
/*
 
3403
========================================================================================
 
3404
 
 
3405
Handle based file calls for virtual machines
 
3406
 
 
3407
========================================================================================
 
3408
*/
 
3409
 
 
3410
int             FS_FOpenFileByMode( const char *qpath, fileHandle_t *f, fsMode_t mode ) {
 
3411
        int             r;
 
3412
        qboolean        sync;
 
3413
 
 
3414
        sync = qfalse;
 
3415
 
 
3416
        switch( mode ) {
 
3417
        case FS_READ:
 
3418
                r = FS_FOpenFileRead( qpath, f, qtrue );
 
3419
                break;
 
3420
        case FS_WRITE:
 
3421
                *f = FS_FOpenFileWrite( qpath );
 
3422
                r = 0;
 
3423
                if (*f == 0) {
 
3424
                        r = -1;
 
3425
                }
 
3426
                break;
 
3427
        case FS_APPEND_SYNC:
 
3428
                sync = qtrue;
 
3429
        case FS_APPEND:
 
3430
                *f = FS_FOpenFileAppend( qpath );
 
3431
                r = 0;
 
3432
                if (*f == 0) {
 
3433
                        r = -1;
 
3434
                }
 
3435
                break;
 
3436
        default:
 
3437
                Com_Error( ERR_FATAL, "FSH_FOpenFile: bad mode" );
 
3438
                return -1;
 
3439
        }
 
3440
 
 
3441
        if (!f) {
 
3442
                return r;
 
3443
        }
 
3444
 
 
3445
        if ( *f ) {
 
3446
                if (fsh[*f].zipFile == qtrue) {
 
3447
                        fsh[*f].baseOffset = unztell(fsh[*f].handleFiles.file.z);
 
3448
                } else {
 
3449
                        fsh[*f].baseOffset = ftell(fsh[*f].handleFiles.file.o);
 
3450
                }
 
3451
                fsh[*f].fileSize = r;
 
3452
                fsh[*f].streamed = qfalse;
 
3453
 
 
3454
                if (mode == FS_READ) {
 
3455
                        Sys_BeginStreamedFile( *f, 0x4000 );
 
3456
                        fsh[*f].streamed = qtrue;
 
3457
                }
 
3458
        }
 
3459
        fsh[*f].handleSync = sync;
 
3460
 
 
3461
        return r;
 
3462
}
 
3463
 
 
3464
int             FS_FTell( fileHandle_t f ) {
 
3465
        int pos;
 
3466
        if (fsh[f].zipFile == qtrue) {
 
3467
                pos = unztell(fsh[f].handleFiles.file.z);
 
3468
        } else {
 
3469
                pos = ftell(fsh[f].handleFiles.file.o);
 
3470
        }
 
3471
        return pos;
 
3472
}
 
3473
 
 
3474
void    FS_Flush( fileHandle_t f ) {
 
3475
        fflush(fsh[f].handleFiles.file.o);
 
3476
}
 
3477
 
 
3478
void    FS_FilenameCompletion( const char *dir, const char *ext,
 
3479
                qboolean stripExt, void(*callback)(const char *s) ) {
 
3480
        char    **filenames;
 
3481
        int             nfiles;
 
3482
        int             i;
 
3483
        char    filename[ MAX_STRING_CHARS ];
 
3484
 
 
3485
        filenames = FS_ListFilteredFiles( dir, ext, NULL, &nfiles );
 
3486
 
 
3487
        FS_SortFileList( filenames, nfiles );
 
3488
 
 
3489
        for( i = 0; i < nfiles; i++ ) {
 
3490
                FS_ConvertPath( filenames[ i ] );
 
3491
                Q_strncpyz( filename, filenames[ i ], MAX_STRING_CHARS );
 
3492
 
 
3493
                if( stripExt ) {
 
3494
                        COM_StripExtension(filename, filename, sizeof(filename));
 
3495
                }
 
3496
 
 
3497
                callback( filename );
 
3498
        }
 
3499
        FS_FreeFileList( filenames );
 
3500
}