files.c (87296B)
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 Foobar; 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 "../game/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 ds_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_PAK_CHECKSUM 437558517u 204 205 // if this is defined, the executable positively won't work with any paks other 206 // than the demo pak, even if productid is present. This is only used for our 207 // last demo release to prevent the mac and linux users from using the demo 208 // executable with the production windows pak before the mac/linux products 209 // hit the shelves a little later 210 // NOW defined in build files 211 //#define PRE_RELEASE_TADEMO 212 213 #define MAX_ZPATH 256 214 #define MAX_SEARCH_PATHS 4096 215 #define MAX_FILEHASH_SIZE 1024 216 217 typedef struct fileInPack_s { 218 char *name; // name of the file 219 unsigned long pos; // file info position in zip 220 struct fileInPack_s* next; // next file in the hash 221 } fileInPack_t; 222 223 typedef struct { 224 char pakFilename[MAX_OSPATH]; // c:\quake3\baseq3\pak0.pk3 225 char pakBasename[MAX_OSPATH]; // pak0 226 char pakGamename[MAX_OSPATH]; // baseq3 227 unzFile handle; // handle to zip file 228 int checksum; // regular checksum 229 int pure_checksum; // checksum for pure 230 int numfiles; // number of files in pk3 231 int referenced; // referenced file flags 232 int hashSize; // hash table size (power of 2) 233 fileInPack_t* *hashTable; // hash table 234 fileInPack_t* buildBuffer; // buffer with the filenames etc. 235 } pack_t; 236 237 typedef struct { 238 char path[MAX_OSPATH]; // c:\quake3 239 char gamedir[MAX_OSPATH]; // baseq3 240 } directory_t; 241 242 typedef struct searchpath_s { 243 struct searchpath_s *next; 244 245 pack_t *pack; // only one of pack / dir will be non NULL 246 directory_t *dir; 247 } searchpath_t; 248 249 static char fs_gamedir[MAX_OSPATH]; // this will be a single file name with no separators 250 static cvar_t *fs_debug; 251 static cvar_t *fs_homepath; 252 static cvar_t *fs_basepath; 253 static cvar_t *fs_basegame; 254 static cvar_t *fs_cdpath; 255 static cvar_t *fs_copyfiles; 256 static cvar_t *fs_gamedirvar; 257 static cvar_t *fs_restrict; 258 static searchpath_t *fs_searchpaths; 259 static int fs_readCount; // total bytes read 260 static int fs_loadCount; // total files read 261 static int fs_loadStack; // total files in memory 262 static int fs_packFiles; // total number of files in packs 263 264 static int fs_fakeChkSum; 265 static int fs_checksumFeed; 266 267 typedef union qfile_gus { 268 FILE* o; 269 unzFile z; 270 } qfile_gut; 271 272 typedef struct qfile_us { 273 qfile_gut file; 274 qboolean unique; 275 } qfile_ut; 276 277 typedef struct { 278 qfile_ut handleFiles; 279 qboolean handleSync; 280 int baseOffset; 281 int fileSize; 282 int zipFilePos; 283 qboolean zipFile; 284 qboolean streamed; 285 char name[MAX_ZPATH]; 286 } fileHandleData_t; 287 288 static fileHandleData_t fsh[MAX_FILE_HANDLES]; 289 290 // TTimo - https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=540 291 // wether we did a reorder on the current search path when joining the server 292 static qboolean fs_reordered; 293 294 // never load anything from pk3 files that are not present at the server when pure 295 static int fs_numServerPaks; 296 static int fs_serverPaks[MAX_SEARCH_PATHS]; // checksums 297 static char *fs_serverPakNames[MAX_SEARCH_PATHS]; // pk3 names 298 299 // only used for autodownload, to make sure the client has at least 300 // all the pk3 files that are referenced at the server side 301 static int fs_numServerReferencedPaks; 302 static int fs_serverReferencedPaks[MAX_SEARCH_PATHS]; // checksums 303 static char *fs_serverReferencedPakNames[MAX_SEARCH_PATHS]; // pk3 names 304 305 // last valid game folder used 306 char lastValidBase[MAX_OSPATH]; 307 char lastValidGame[MAX_OSPATH]; 308 309 // productId: This file is copyright 1999 Id Software, and may not be duplicated except during a licensed installation of the full commercial version of Quake 3:Arena 310 static byte fs_scrambledProductId[152] = { 311 220, 129, 255, 108, 244, 163, 171, 55, 133, 65, 199, 36, 140, 222, 53, 99, 65, 171, 175, 232, 236, 193, 210, 250, 169, 104, 231, 231, 21, 201, 170, 208, 135, 175, 130, 136, 85, 215, 71, 23, 96, 32, 96, 83, 44, 240, 219, 138, 184, 215, 73, 27, 196, 247, 55, 139, 148, 68, 78, 203, 213, 238, 139, 23, 45, 205, 118, 186, 236, 230, 231, 107, 212, 1, 10, 98, 30, 20, 116, 180, 216, 248, 166, 35, 45, 22, 215, 229, 35, 116, 250, 167, 117, 3, 57, 55, 201, 229, 218, 222, 128, 12, 141, 149, 32, 110, 168, 215, 184, 53, 31, 147, 62, 12, 138, 67, 132, 54, 125, 6, 221, 148, 140, 4, 21, 44, 198, 3, 126, 12, 100, 236, 61, 42, 44, 251, 15, 135, 14, 134, 89, 92, 177, 246, 152, 106, 124, 78, 118, 80, 28, 42 312 }; 313 314 #ifdef FS_MISSING 315 FILE* missingFiles = NULL; 316 #endif 317 318 /* 319 ============== 320 FS_Initialized 321 ============== 322 */ 323 324 qboolean FS_Initialized() { 325 return (fs_searchpaths != NULL); 326 } 327 328 /* 329 ================= 330 FS_PakIsPure 331 ================= 332 */ 333 qboolean FS_PakIsPure( pack_t *pack ) { 334 int i; 335 336 if ( fs_numServerPaks ) { 337 for ( i = 0 ; i < fs_numServerPaks ; i++ ) { 338 // FIXME: also use hashed file names 339 // NOTE TTimo: a pk3 with same checksum but different name would be validated too 340 // 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' 341 if ( pack->checksum == fs_serverPaks[i] ) { 342 return qtrue; // on the aproved list 343 } 344 } 345 return qfalse; // not on the pure server pak list 346 } 347 return qtrue; 348 } 349 350 351 /* 352 ================= 353 FS_LoadStack 354 return load stack 355 ================= 356 */ 357 int FS_LoadStack() 358 { 359 return fs_loadStack; 360 } 361 362 /* 363 ================ 364 return a hash value for the filename 365 ================ 366 */ 367 static long FS_HashFileName( const char *fname, int hashSize ) { 368 int i; 369 long hash; 370 char letter; 371 372 hash = 0; 373 i = 0; 374 while (fname[i] != '\0') { 375 letter = tolower(fname[i]); 376 if (letter =='.') break; // don't include extension 377 if (letter =='\\') letter = '/'; // damn path names 378 if (letter == PATH_SEP) letter = '/'; // damn path names 379 hash+=(long)(letter)*(i+119); 380 i++; 381 } 382 hash = (hash ^ (hash >> 10) ^ (hash >> 20)); 383 hash &= (hashSize-1); 384 return hash; 385 } 386 387 static fileHandle_t FS_HandleForFile(void) { 388 int i; 389 390 for ( i = 1 ; i < MAX_FILE_HANDLES ; i++ ) { 391 if ( fsh[i].handleFiles.file.o == NULL ) { 392 return i; 393 } 394 } 395 Com_Error( ERR_DROP, "FS_HandleForFile: none free" ); 396 return 0; 397 } 398 399 static FILE *FS_FileForHandle( fileHandle_t f ) { 400 if ( f < 0 || f > MAX_FILE_HANDLES ) { 401 Com_Error( ERR_DROP, "FS_FileForHandle: out of reange" ); 402 } 403 if (fsh[f].zipFile == qtrue) { 404 Com_Error( ERR_DROP, "FS_FileForHandle: can't get FILE on zip file" ); 405 } 406 if ( ! fsh[f].handleFiles.file.o ) { 407 Com_Error( ERR_DROP, "FS_FileForHandle: NULL" ); 408 } 409 410 return fsh[f].handleFiles.file.o; 411 } 412 413 void FS_ForceFlush( fileHandle_t f ) { 414 FILE *file; 415 416 file = FS_FileForHandle(f); 417 setvbuf( file, NULL, _IONBF, 0 ); 418 } 419 420 /* 421 ================ 422 FS_filelength 423 424 If this is called on a non-unique FILE (from a pak file), 425 it will return the size of the pak file, not the expected 426 size of the file. 427 ================ 428 */ 429 int FS_filelength( fileHandle_t f ) { 430 int pos; 431 int end; 432 FILE* h; 433 434 h = FS_FileForHandle(f); 435 pos = ftell (h); 436 fseek (h, 0, SEEK_END); 437 end = ftell (h); 438 fseek (h, pos, SEEK_SET); 439 440 return end; 441 } 442 443 /* 444 ==================== 445 FS_ReplaceSeparators 446 447 Fix things up differently for win/unix/mac 448 ==================== 449 */ 450 static void FS_ReplaceSeparators( char *path ) { 451 char *s; 452 453 for ( s = path ; *s ; s++ ) { 454 if ( *s == '/' || *s == '\\' ) { 455 *s = PATH_SEP; 456 } 457 } 458 } 459 460 /* 461 =================== 462 FS_BuildOSPath 463 464 Qpath may have either forward or backwards slashes 465 =================== 466 */ 467 char *FS_BuildOSPath( const char *base, const char *game, const char *qpath ) { 468 char temp[MAX_OSPATH]; 469 static char ospath[2][MAX_OSPATH]; 470 static int toggle; 471 472 toggle ^= 1; // flip-flop to allow two returns without clash 473 474 if( !game || !game[0] ) { 475 game = fs_gamedir; 476 } 477 478 Com_sprintf( temp, sizeof(temp), "/%s/%s", game, qpath ); 479 FS_ReplaceSeparators( temp ); 480 Com_sprintf( ospath[toggle], sizeof( ospath[0] ), "%s%s", base, temp ); 481 482 return ospath[toggle]; 483 } 484 485 486 /* 487 ============ 488 FS_CreatePath 489 490 Creates any directories needed to store the given filename 491 ============ 492 */ 493 static qboolean FS_CreatePath (char *OSPath) { 494 char *ofs; 495 496 // make absolutely sure that it can't back up the path 497 // FIXME: is c: allowed??? 498 if ( strstr( OSPath, ".." ) || strstr( OSPath, "::" ) ) { 499 Com_Printf( "WARNING: refusing to create relative path \"%s\"\n", OSPath ); 500 return qtrue; 501 } 502 503 for (ofs = OSPath+1 ; *ofs ; ofs++) { 504 if (*ofs == PATH_SEP) { 505 // create the directory 506 *ofs = 0; 507 Sys_Mkdir (OSPath); 508 *ofs = PATH_SEP; 509 } 510 } 511 return qfalse; 512 } 513 514 /* 515 ================= 516 FS_CopyFile 517 518 Copy a fully specified file from one place to another 519 ================= 520 */ 521 static void FS_CopyFile( char *fromOSPath, char *toOSPath ) { 522 FILE *f; 523 int len; 524 byte *buf; 525 526 Com_Printf( "copy %s to %s\n", fromOSPath, toOSPath ); 527 528 if (strstr(fromOSPath, "journal.dat") || strstr(fromOSPath, "journaldata.dat")) { 529 Com_Printf( "Ignoring journal files\n"); 530 return; 531 } 532 533 f = fopen( fromOSPath, "rb" ); 534 if ( !f ) { 535 return; 536 } 537 fseek (f, 0, SEEK_END); 538 len = ftell (f); 539 fseek (f, 0, SEEK_SET); 540 541 // we are using direct malloc instead of Z_Malloc here, so it 542 // probably won't work on a mac... Its only for developers anyway... 543 buf = malloc( len ); 544 if (fread( buf, 1, len, f ) != len) 545 Com_Error( ERR_FATAL, "Short read in FS_Copyfiles()\n" ); 546 fclose( f ); 547 548 if( FS_CreatePath( toOSPath ) ) { 549 return; 550 } 551 552 f = fopen( toOSPath, "wb" ); 553 if ( !f ) { 554 return; 555 } 556 if (fwrite( buf, 1, len, f ) != len) 557 Com_Error( ERR_FATAL, "Short write in FS_Copyfiles()\n" ); 558 fclose( f ); 559 free( buf ); 560 } 561 562 /* 563 =========== 564 FS_Remove 565 566 =========== 567 */ 568 static void FS_Remove( const char *osPath ) { 569 remove( osPath ); 570 } 571 572 /* 573 ================ 574 FS_FileExists 575 576 Tests if the file exists in the current gamedir, this DOES NOT 577 search the paths. This is to determine if opening a file to write 578 (which always goes into the current gamedir) will cause any overwrites. 579 NOTE TTimo: this goes with FS_FOpenFileWrite for opening the file afterwards 580 ================ 581 */ 582 qboolean FS_FileExists( const char *file ) 583 { 584 FILE *f; 585 char *testpath; 586 587 testpath = FS_BuildOSPath( fs_homepath->string, fs_gamedir, file ); 588 589 f = fopen( testpath, "rb" ); 590 if (f) { 591 fclose( f ); 592 return qtrue; 593 } 594 return qfalse; 595 } 596 597 /* 598 ================ 599 FS_SV_FileExists 600 601 Tests if the file exists 602 ================ 603 */ 604 qboolean FS_SV_FileExists( const char *file ) 605 { 606 FILE *f; 607 char *testpath; 608 609 testpath = FS_BuildOSPath( fs_homepath->string, file, ""); 610 testpath[strlen(testpath)-1] = '\0'; 611 612 f = fopen( testpath, "rb" ); 613 if (f) { 614 fclose( f ); 615 return qtrue; 616 } 617 return qfalse; 618 } 619 620 621 /* 622 =========== 623 FS_SV_FOpenFileWrite 624 625 =========== 626 */ 627 fileHandle_t FS_SV_FOpenFileWrite( const char *filename ) { 628 char *ospath; 629 fileHandle_t f; 630 631 if ( !fs_searchpaths ) { 632 Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" ); 633 } 634 635 ospath = FS_BuildOSPath( fs_homepath->string, filename, "" ); 636 ospath[strlen(ospath)-1] = '\0'; 637 638 f = FS_HandleForFile(); 639 fsh[f].zipFile = qfalse; 640 641 if ( fs_debug->integer ) { 642 Com_Printf( "FS_SV_FOpenFileWrite: %s\n", ospath ); 643 } 644 645 if( FS_CreatePath( ospath ) ) { 646 return 0; 647 } 648 649 Com_DPrintf( "writing to: %s\n", ospath ); 650 fsh[f].handleFiles.file.o = fopen( ospath, "wb" ); 651 652 Q_strncpyz( fsh[f].name, filename, sizeof( fsh[f].name ) ); 653 654 fsh[f].handleSync = qfalse; 655 if (!fsh[f].handleFiles.file.o) { 656 f = 0; 657 } 658 return f; 659 } 660 661 /* 662 =========== 663 FS_SV_FOpenFileRead 664 search for a file somewhere below the home path, base path or cd path 665 we search in that order, matching FS_SV_FOpenFileRead order 666 =========== 667 */ 668 int FS_SV_FOpenFileRead( const char *filename, fileHandle_t *fp ) { 669 char *ospath; 670 fileHandle_t f = 0; 671 672 if ( !fs_searchpaths ) { 673 Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" ); 674 } 675 676 f = FS_HandleForFile(); 677 fsh[f].zipFile = qfalse; 678 679 Q_strncpyz( fsh[f].name, filename, sizeof( fsh[f].name ) ); 680 681 // don't let sound stutter 682 S_ClearSoundBuffer(); 683 684 // search homepath 685 ospath = FS_BuildOSPath( fs_homepath->string, filename, "" ); 686 // remove trailing slash 687 ospath[strlen(ospath)-1] = '\0'; 688 689 if ( fs_debug->integer ) { 690 Com_Printf( "FS_SV_FOpenFileRead (fs_homepath): %s\n", ospath ); 691 } 692 693 fsh[f].handleFiles.file.o = fopen( ospath, "rb" ); 694 fsh[f].handleSync = qfalse; 695 if (!fsh[f].handleFiles.file.o) 696 { 697 // NOTE TTimo on non *nix systems, fs_homepath == fs_basepath, might want to avoid 698 if (Q_stricmp(fs_homepath->string,fs_basepath->string)) 699 { 700 // search basepath 701 ospath = FS_BuildOSPath( fs_basepath->string, filename, "" ); 702 ospath[strlen(ospath)-1] = '\0'; 703 704 if ( fs_debug->integer ) 705 { 706 Com_Printf( "FS_SV_FOpenFileRead (fs_basepath): %s\n", ospath ); 707 } 708 709 fsh[f].handleFiles.file.o = fopen( ospath, "rb" ); 710 fsh[f].handleSync = qfalse; 711 712 if ( !fsh[f].handleFiles.file.o ) 713 { 714 f = 0; 715 } 716 } 717 } 718 719 if (!fsh[f].handleFiles.file.o) { 720 // search cd path 721 ospath = FS_BuildOSPath( fs_cdpath->string, filename, "" ); 722 ospath[strlen(ospath)-1] = '\0'; 723 724 if (fs_debug->integer) 725 { 726 Com_Printf( "FS_SV_FOpenFileRead (fs_cdpath) : %s\n", ospath ); 727 } 728 729 fsh[f].handleFiles.file.o = fopen( ospath, "rb" ); 730 fsh[f].handleSync = qfalse; 731 732 if( !fsh[f].handleFiles.file.o ) { 733 f = 0; 734 } 735 } 736 737 *fp = f; 738 if (f) { 739 return FS_filelength(f); 740 } 741 return 0; 742 } 743 744 745 /* 746 =========== 747 FS_SV_Rename 748 749 =========== 750 */ 751 void FS_SV_Rename( const char *from, const char *to ) { 752 char *from_ospath, *to_ospath; 753 754 if ( !fs_searchpaths ) { 755 Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" ); 756 } 757 758 // don't let sound stutter 759 S_ClearSoundBuffer(); 760 761 from_ospath = FS_BuildOSPath( fs_homepath->string, from, "" ); 762 to_ospath = FS_BuildOSPath( fs_homepath->string, to, "" ); 763 from_ospath[strlen(from_ospath)-1] = '\0'; 764 to_ospath[strlen(to_ospath)-1] = '\0'; 765 766 if ( fs_debug->integer ) { 767 Com_Printf( "FS_SV_Rename: %s --> %s\n", from_ospath, to_ospath ); 768 } 769 770 if (rename( from_ospath, to_ospath )) { 771 // Failed, try copying it and deleting the original 772 FS_CopyFile ( from_ospath, to_ospath ); 773 FS_Remove ( from_ospath ); 774 } 775 } 776 777 778 779 /* 780 =========== 781 FS_Rename 782 783 =========== 784 */ 785 void FS_Rename( const char *from, const char *to ) { 786 char *from_ospath, *to_ospath; 787 788 if ( !fs_searchpaths ) { 789 Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" ); 790 } 791 792 // don't let sound stutter 793 S_ClearSoundBuffer(); 794 795 from_ospath = FS_BuildOSPath( fs_homepath->string, fs_gamedir, from ); 796 to_ospath = FS_BuildOSPath( fs_homepath->string, fs_gamedir, to ); 797 798 if ( fs_debug->integer ) { 799 Com_Printf( "FS_Rename: %s --> %s\n", from_ospath, to_ospath ); 800 } 801 802 if (rename( from_ospath, to_ospath )) { 803 // Failed, try copying it and deleting the original 804 FS_CopyFile ( from_ospath, to_ospath ); 805 FS_Remove ( from_ospath ); 806 } 807 } 808 809 /* 810 ============== 811 FS_FCloseFile 812 813 If the FILE pointer is an open pak file, leave it open. 814 815 For some reason, other dll's can't just cal fclose() 816 on files returned by FS_FOpenFile... 817 ============== 818 */ 819 void FS_FCloseFile( fileHandle_t f ) { 820 if ( !fs_searchpaths ) { 821 Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" ); 822 } 823 824 if (fsh[f].streamed) { 825 Sys_EndStreamedFile(f); 826 } 827 if (fsh[f].zipFile == qtrue) { 828 unzCloseCurrentFile( fsh[f].handleFiles.file.z ); 829 if ( fsh[f].handleFiles.unique ) { 830 unzClose( fsh[f].handleFiles.file.z ); 831 } 832 Com_Memset( &fsh[f], 0, sizeof( fsh[f] ) ); 833 return; 834 } 835 836 // we didn't find it as a pak, so close it as a unique file 837 if (fsh[f].handleFiles.file.o) { 838 fclose (fsh[f].handleFiles.file.o); 839 } 840 Com_Memset( &fsh[f], 0, sizeof( fsh[f] ) ); 841 } 842 843 /* 844 =========== 845 FS_FOpenFileWrite 846 847 =========== 848 */ 849 fileHandle_t FS_FOpenFileWrite( const char *filename ) { 850 char *ospath; 851 fileHandle_t f; 852 853 if ( !fs_searchpaths ) { 854 Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" ); 855 } 856 857 f = FS_HandleForFile(); 858 fsh[f].zipFile = qfalse; 859 860 ospath = FS_BuildOSPath( fs_homepath->string, fs_gamedir, filename ); 861 862 if ( fs_debug->integer ) { 863 Com_Printf( "FS_FOpenFileWrite: %s\n", ospath ); 864 } 865 866 if( FS_CreatePath( ospath ) ) { 867 return 0; 868 } 869 870 // enabling the following line causes a recursive function call loop 871 // when running with +set logfile 1 +set developer 1 872 //Com_DPrintf( "writing to: %s\n", ospath ); 873 fsh[f].handleFiles.file.o = fopen( ospath, "wb" ); 874 875 Q_strncpyz( fsh[f].name, filename, sizeof( fsh[f].name ) ); 876 877 fsh[f].handleSync = qfalse; 878 if (!fsh[f].handleFiles.file.o) { 879 f = 0; 880 } 881 return f; 882 } 883 884 /* 885 =========== 886 FS_FOpenFileAppend 887 888 =========== 889 */ 890 fileHandle_t FS_FOpenFileAppend( const char *filename ) { 891 char *ospath; 892 fileHandle_t f; 893 894 if ( !fs_searchpaths ) { 895 Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" ); 896 } 897 898 f = FS_HandleForFile(); 899 fsh[f].zipFile = qfalse; 900 901 Q_strncpyz( fsh[f].name, filename, sizeof( fsh[f].name ) ); 902 903 // don't let sound stutter 904 S_ClearSoundBuffer(); 905 906 ospath = FS_BuildOSPath( fs_homepath->string, fs_gamedir, filename ); 907 908 if ( fs_debug->integer ) { 909 Com_Printf( "FS_FOpenFileAppend: %s\n", ospath ); 910 } 911 912 if( FS_CreatePath( ospath ) ) { 913 return 0; 914 } 915 916 fsh[f].handleFiles.file.o = fopen( ospath, "ab" ); 917 fsh[f].handleSync = qfalse; 918 if (!fsh[f].handleFiles.file.o) { 919 f = 0; 920 } 921 return f; 922 } 923 924 /* 925 =========== 926 FS_FilenameCompare 927 928 Ignore case and seprator char distinctions 929 =========== 930 */ 931 qboolean FS_FilenameCompare( const char *s1, const char *s2 ) { 932 int c1, c2; 933 934 do { 935 c1 = *s1++; 936 c2 = *s2++; 937 938 if (c1 >= 'a' && c1 <= 'z') { 939 c1 -= ('a' - 'A'); 940 } 941 if (c2 >= 'a' && c2 <= 'z') { 942 c2 -= ('a' - 'A'); 943 } 944 945 if ( c1 == '\\' || c1 == ':' ) { 946 c1 = '/'; 947 } 948 if ( c2 == '\\' || c2 == ':' ) { 949 c2 = '/'; 950 } 951 952 if (c1 != c2) { 953 return -1; // strings not equal 954 } 955 } while (c1); 956 957 return 0; // strings are equal 958 } 959 960 /* 961 =========== 962 FS_ShiftedStrStr 963 =========== 964 */ 965 char *FS_ShiftedStrStr(const char *string, const char *substring, int shift) { 966 char buf[MAX_STRING_TOKENS]; 967 int i; 968 969 for (i = 0; substring[i]; i++) { 970 buf[i] = substring[i] + shift; 971 } 972 buf[i] = '\0'; 973 return strstr(string, buf); 974 } 975 976 /* 977 =========== 978 FS_FOpenFileRead 979 980 Finds the file in the search path. 981 Returns filesize and an open FILE pointer. 982 Used for streaming data out of either a 983 separate file or a ZIP file. 984 =========== 985 */ 986 extern qboolean com_fullyInitialized; 987 988 int FS_FOpenFileRead( const char *filename, fileHandle_t *file, qboolean uniqueFILE ) { 989 searchpath_t *search; 990 char *netpath; 991 pack_t *pak; 992 fileInPack_t *pakFile; 993 directory_t *dir; 994 long hash; 995 unz_s *zfi; 996 FILE *temp; 997 int l; 998 char demoExt[16]; 999 1000 hash = 0; 1001 1002 if ( !fs_searchpaths ) { 1003 Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" ); 1004 } 1005 1006 if ( file == NULL ) { 1007 // just wants to see if file is there 1008 for ( search = fs_searchpaths ; search ; search = search->next ) { 1009 // 1010 if ( search->pack ) { 1011 hash = FS_HashFileName(filename, search->pack->hashSize); 1012 } 1013 // is the element a pak file? 1014 if ( search->pack && search->pack->hashTable[hash] ) { 1015 // look through all the pak file elements 1016 pak = search->pack; 1017 pakFile = pak->hashTable[hash]; 1018 do { 1019 // case and separator insensitive comparisons 1020 if ( !FS_FilenameCompare( pakFile->name, filename ) ) { 1021 // found it! 1022 return qtrue; 1023 } 1024 pakFile = pakFile->next; 1025 } while(pakFile != NULL); 1026 } else if ( search->dir ) { 1027 dir = search->dir; 1028 1029 netpath = FS_BuildOSPath( dir->path, dir->gamedir, filename ); 1030 temp = fopen (netpath, "rb"); 1031 if ( !temp ) { 1032 continue; 1033 } 1034 fclose(temp); 1035 return qtrue; 1036 } 1037 } 1038 return qfalse; 1039 } 1040 1041 if ( !filename ) { 1042 Com_Error( ERR_FATAL, "FS_FOpenFileRead: NULL 'filename' parameter passed\n" ); 1043 } 1044 1045 Com_sprintf (demoExt, sizeof(demoExt), ".dm_%d",PROTOCOL_VERSION ); 1046 // qpaths are not supposed to have a leading slash 1047 if ( filename[0] == '/' || filename[0] == '\\' ) { 1048 filename++; 1049 } 1050 1051 // make absolutely sure that it can't back up the path. 1052 // The searchpaths do guarantee that something will always 1053 // be prepended, so we don't need to worry about "c:" or "//limbo" 1054 if ( strstr( filename, ".." ) || strstr( filename, "::" ) ) { 1055 *file = 0; 1056 return -1; 1057 } 1058 1059 // make sure the q3key file is only readable by the quake3.exe at initialization 1060 // any other time the key should only be accessed in memory using the provided functions 1061 if( com_fullyInitialized && strstr( filename, "q3key" ) ) { 1062 *file = 0; 1063 return -1; 1064 } 1065 1066 // 1067 // search through the path, one element at a time 1068 // 1069 1070 *file = FS_HandleForFile(); 1071 fsh[*file].handleFiles.unique = uniqueFILE; 1072 1073 for ( search = fs_searchpaths ; search ; search = search->next ) { 1074 // 1075 if ( search->pack ) { 1076 hash = FS_HashFileName(filename, search->pack->hashSize); 1077 } 1078 // is the element a pak file? 1079 if ( search->pack && search->pack->hashTable[hash] ) { 1080 // disregard if it doesn't match one of the allowed pure pak files 1081 if ( !FS_PakIsPure(search->pack) ) { 1082 continue; 1083 } 1084 1085 // look through all the pak file elements 1086 pak = search->pack; 1087 pakFile = pak->hashTable[hash]; 1088 do { 1089 // case and separator insensitive comparisons 1090 if ( !FS_FilenameCompare( pakFile->name, filename ) ) { 1091 // found it! 1092 1093 // mark the pak as having been referenced and mark specifics on cgame and ui 1094 // shaders, txt, arena files by themselves do not count as a reference as 1095 // these are loaded from all pk3s 1096 // from every pk3 file.. 1097 l = strlen( filename ); 1098 if ( !(pak->referenced & FS_GENERAL_REF)) { 1099 if ( Q_stricmp(filename + l - 7, ".shader") != 0 && 1100 Q_stricmp(filename + l - 4, ".txt") != 0 && 1101 Q_stricmp(filename + l - 4, ".cfg") != 0 && 1102 Q_stricmp(filename + l - 7, ".config") != 0 && 1103 strstr(filename, "levelshots") == NULL && 1104 Q_stricmp(filename + l - 4, ".bot") != 0 && 1105 Q_stricmp(filename + l - 6, ".arena") != 0 && 1106 Q_stricmp(filename + l - 5, ".menu") != 0) { 1107 pak->referenced |= FS_GENERAL_REF; 1108 } 1109 } 1110 1111 // qagame.qvm - 13 1112 // dTZT`X!di` 1113 if (!(pak->referenced & FS_QAGAME_REF) && FS_ShiftedStrStr(filename, "dTZT`X!di`", 13)) { 1114 pak->referenced |= FS_QAGAME_REF; 1115 } 1116 // cgame.qvm - 7 1117 // \`Zf^'jof 1118 if (!(pak->referenced & FS_CGAME_REF) && FS_ShiftedStrStr(filename , "\\`Zf^'jof", 7)) { 1119 pak->referenced |= FS_CGAME_REF; 1120 } 1121 // ui.qvm - 5 1122 // pd)lqh 1123 if (!(pak->referenced & FS_UI_REF) && FS_ShiftedStrStr(filename , "pd)lqh", 5)) { 1124 pak->referenced |= FS_UI_REF; 1125 } 1126 1127 if ( uniqueFILE ) { 1128 // open a new file on the pakfile 1129 fsh[*file].handleFiles.file.z = unzReOpen (pak->pakFilename, pak->handle); 1130 if (fsh[*file].handleFiles.file.z == NULL) { 1131 Com_Error (ERR_FATAL, "Couldn't reopen %s", pak->pakFilename); 1132 } 1133 } else { 1134 fsh[*file].handleFiles.file.z = pak->handle; 1135 } 1136 Q_strncpyz( fsh[*file].name, filename, sizeof( fsh[*file].name ) ); 1137 fsh[*file].zipFile = qtrue; 1138 zfi = (unz_s *)fsh[*file].handleFiles.file.z; 1139 // in case the file was new 1140 temp = zfi->file; 1141 // set the file position in the zip file (also sets the current file info) 1142 unzSetCurrentFileInfoPosition(pak->handle, pakFile->pos); 1143 // copy the file info into the unzip structure 1144 Com_Memcpy( zfi, pak->handle, sizeof(unz_s) ); 1145 // we copy this back into the structure 1146 zfi->file = temp; 1147 // open the file in the zip 1148 unzOpenCurrentFile( fsh[*file].handleFiles.file.z ); 1149 fsh[*file].zipFilePos = pakFile->pos; 1150 1151 if ( fs_debug->integer ) { 1152 Com_Printf( "FS_FOpenFileRead: %s (found in '%s')\n", 1153 filename, pak->pakFilename ); 1154 } 1155 return zfi->cur_file_info.uncompressed_size; 1156 } 1157 pakFile = pakFile->next; 1158 } while(pakFile != NULL); 1159 } else if ( search->dir ) { 1160 // check a file in the directory tree 1161 1162 // if we are running restricted, the only files we 1163 // will allow to come from the directory are .cfg files 1164 l = strlen( filename ); 1165 // FIXME TTimo I'm not sure about the fs_numServerPaks test 1166 // if you are using FS_ReadFile to find out if a file exists, 1167 // this test can make the search fail although the file is in the directory 1168 // I had the problem on https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=8 1169 // turned out I used FS_FileExists instead 1170 if ( fs_restrict->integer || fs_numServerPaks ) { 1171 1172 if ( Q_stricmp( filename + l - 4, ".cfg" ) // for config files 1173 && Q_stricmp( filename + l - 5, ".menu" ) // menu files 1174 && Q_stricmp( filename + l - 5, ".game" ) // menu files 1175 && Q_stricmp( filename + l - strlen(demoExt), demoExt ) // menu files 1176 && Q_stricmp( filename + l - 4, ".dat" ) ) { // for journal files 1177 continue; 1178 } 1179 } 1180 1181 dir = search->dir; 1182 1183 netpath = FS_BuildOSPath( dir->path, dir->gamedir, filename ); 1184 fsh[*file].handleFiles.file.o = fopen (netpath, "rb"); 1185 if ( !fsh[*file].handleFiles.file.o ) { 1186 continue; 1187 } 1188 1189 if ( Q_stricmp( filename + l - 4, ".cfg" ) // for config files 1190 && Q_stricmp( filename + l - 5, ".menu" ) // menu files 1191 && Q_stricmp( filename + l - 5, ".game" ) // menu files 1192 && Q_stricmp( filename + l - strlen(demoExt), demoExt ) // menu files 1193 && Q_stricmp( filename + l - 4, ".dat" ) ) { // for journal files 1194 fs_fakeChkSum = random(); 1195 } 1196 1197 Q_strncpyz( fsh[*file].name, filename, sizeof( fsh[*file].name ) ); 1198 fsh[*file].zipFile = qfalse; 1199 if ( fs_debug->integer ) { 1200 Com_Printf( "FS_FOpenFileRead: %s (found in '%s/%s')\n", filename, 1201 dir->path, dir->gamedir ); 1202 } 1203 1204 // if we are getting it from the cdpath, optionally copy it 1205 // to the basepath 1206 if ( fs_copyfiles->integer && !Q_stricmp( dir->path, fs_cdpath->string ) ) { 1207 char *copypath; 1208 1209 copypath = FS_BuildOSPath( fs_basepath->string, dir->gamedir, filename ); 1210 FS_CopyFile( netpath, copypath ); 1211 } 1212 1213 return FS_filelength (*file); 1214 } 1215 } 1216 1217 Com_DPrintf ("Can't find %s\n", filename); 1218 #ifdef FS_MISSING 1219 if (missingFiles) { 1220 fprintf(missingFiles, "%s\n", filename); 1221 } 1222 #endif 1223 *file = 0; 1224 return -1; 1225 } 1226 1227 1228 /* 1229 ================= 1230 FS_Read 1231 1232 Properly handles partial reads 1233 ================= 1234 */ 1235 int FS_Read2( void *buffer, int len, fileHandle_t f ) { 1236 if ( !fs_searchpaths ) { 1237 Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" ); 1238 } 1239 1240 if ( !f ) { 1241 return 0; 1242 } 1243 if (fsh[f].streamed) { 1244 int r; 1245 fsh[f].streamed = qfalse; 1246 r = Sys_StreamedRead( buffer, len, 1, f); 1247 fsh[f].streamed = qtrue; 1248 return r; 1249 } else { 1250 return FS_Read( buffer, len, f); 1251 } 1252 } 1253 1254 int FS_Read( void *buffer, int len, fileHandle_t f ) { 1255 int block, remaining; 1256 int read; 1257 byte *buf; 1258 int tries; 1259 1260 if ( !fs_searchpaths ) { 1261 Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" ); 1262 } 1263 1264 if ( !f ) { 1265 return 0; 1266 } 1267 1268 buf = (byte *)buffer; 1269 fs_readCount += len; 1270 1271 if (fsh[f].zipFile == qfalse) { 1272 remaining = len; 1273 tries = 0; 1274 while (remaining) { 1275 block = remaining; 1276 read = fread (buf, 1, block, fsh[f].handleFiles.file.o); 1277 if (read == 0) { 1278 // we might have been trying to read from a CD, which 1279 // sometimes returns a 0 read on windows 1280 if (!tries) { 1281 tries = 1; 1282 } else { 1283 return len-remaining; //Com_Error (ERR_FATAL, "FS_Read: 0 bytes read"); 1284 } 1285 } 1286 1287 if (read == -1) { 1288 Com_Error (ERR_FATAL, "FS_Read: -1 bytes read"); 1289 } 1290 1291 remaining -= read; 1292 buf += read; 1293 } 1294 return len; 1295 } else { 1296 return unzReadCurrentFile(fsh[f].handleFiles.file.z, buffer, len); 1297 } 1298 } 1299 1300 /* 1301 ================= 1302 FS_Write 1303 1304 Properly handles partial writes 1305 ================= 1306 */ 1307 int FS_Write( const void *buffer, int len, fileHandle_t h ) { 1308 int block, remaining; 1309 int written; 1310 byte *buf; 1311 int tries; 1312 FILE *f; 1313 1314 if ( !fs_searchpaths ) { 1315 Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" ); 1316 } 1317 1318 if ( !h ) { 1319 return 0; 1320 } 1321 1322 f = FS_FileForHandle(h); 1323 buf = (byte *)buffer; 1324 1325 remaining = len; 1326 tries = 0; 1327 while (remaining) { 1328 block = remaining; 1329 written = fwrite (buf, 1, block, f); 1330 if (written == 0) { 1331 if (!tries) { 1332 tries = 1; 1333 } else { 1334 Com_Printf( "FS_Write: 0 bytes written\n" ); 1335 return 0; 1336 } 1337 } 1338 1339 if (written == -1) { 1340 Com_Printf( "FS_Write: -1 bytes written\n" ); 1341 return 0; 1342 } 1343 1344 remaining -= written; 1345 buf += written; 1346 } 1347 if ( fsh[h].handleSync ) { 1348 fflush( f ); 1349 } 1350 return len; 1351 } 1352 1353 void QDECL FS_Printf( fileHandle_t h, const char *fmt, ... ) { 1354 va_list argptr; 1355 char msg[MAXPRINTMSG]; 1356 1357 va_start (argptr,fmt); 1358 Q_vsnprintf (msg, sizeof(msg), fmt, argptr); 1359 va_end (argptr); 1360 1361 FS_Write(msg, strlen(msg), h); 1362 } 1363 1364 /* 1365 ================= 1366 FS_Seek 1367 1368 ================= 1369 */ 1370 int FS_Seek( fileHandle_t f, long offset, int origin ) { 1371 int _origin; 1372 char foo[65536]; 1373 1374 if ( !fs_searchpaths ) { 1375 Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" ); 1376 return -1; 1377 } 1378 1379 if (fsh[f].streamed) { 1380 fsh[f].streamed = qfalse; 1381 Sys_StreamSeek( f, offset, origin ); 1382 fsh[f].streamed = qtrue; 1383 } 1384 1385 if (fsh[f].zipFile == qtrue) { 1386 if (offset == 0 && origin == FS_SEEK_SET) { 1387 // set the file position in the zip file (also sets the current file info) 1388 unzSetCurrentFileInfoPosition(fsh[f].handleFiles.file.z, fsh[f].zipFilePos); 1389 return unzOpenCurrentFile(fsh[f].handleFiles.file.z); 1390 } else if (offset<65536) { 1391 // set the file position in the zip file (also sets the current file info) 1392 unzSetCurrentFileInfoPosition(fsh[f].handleFiles.file.z, fsh[f].zipFilePos); 1393 unzOpenCurrentFile(fsh[f].handleFiles.file.z); 1394 return FS_Read(foo, offset, f); 1395 } else { 1396 Com_Error( ERR_FATAL, "ZIP FILE FSEEK NOT YET IMPLEMENTED\n" ); 1397 return -1; 1398 } 1399 } else { 1400 FILE *file; 1401 file = FS_FileForHandle(f); 1402 switch( origin ) { 1403 case FS_SEEK_CUR: 1404 _origin = SEEK_CUR; 1405 break; 1406 case FS_SEEK_END: 1407 _origin = SEEK_END; 1408 break; 1409 case FS_SEEK_SET: 1410 _origin = SEEK_SET; 1411 break; 1412 default: 1413 _origin = SEEK_CUR; 1414 Com_Error( ERR_FATAL, "Bad origin in FS_Seek\n" ); 1415 break; 1416 } 1417 1418 return fseek( file, offset, _origin ); 1419 } 1420 } 1421 1422 1423 /* 1424 ====================================================================================== 1425 1426 CONVENIENCE FUNCTIONS FOR ENTIRE FILES 1427 1428 ====================================================================================== 1429 */ 1430 1431 int FS_FileIsInPAK(const char *filename, int *pChecksum ) { 1432 searchpath_t *search; 1433 pack_t *pak; 1434 fileInPack_t *pakFile; 1435 long hash = 0; 1436 1437 if ( !fs_searchpaths ) { 1438 Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" ); 1439 } 1440 1441 if ( !filename ) { 1442 Com_Error( ERR_FATAL, "FS_FOpenFileRead: NULL 'filename' parameter passed\n" ); 1443 } 1444 1445 // qpaths are not supposed to have a leading slash 1446 if ( filename[0] == '/' || filename[0] == '\\' ) { 1447 filename++; 1448 } 1449 1450 // make absolutely sure that it can't back up the path. 1451 // The searchpaths do guarantee that something will always 1452 // be prepended, so we don't need to worry about "c:" or "//limbo" 1453 if ( strstr( filename, ".." ) || strstr( filename, "::" ) ) { 1454 return -1; 1455 } 1456 1457 // 1458 // search through the path, one element at a time 1459 // 1460 1461 for ( search = fs_searchpaths ; search ; search = search->next ) { 1462 // 1463 if (search->pack) { 1464 hash = FS_HashFileName(filename, search->pack->hashSize); 1465 } 1466 // is the element a pak file? 1467 if ( search->pack && search->pack->hashTable[hash] ) { 1468 // disregard if it doesn't match one of the allowed pure pak files 1469 if ( !FS_PakIsPure(search->pack) ) { 1470 continue; 1471 } 1472 1473 // look through all the pak file elements 1474 pak = search->pack; 1475 pakFile = pak->hashTable[hash]; 1476 do { 1477 // case and separator insensitive comparisons 1478 if ( !FS_FilenameCompare( pakFile->name, filename ) ) { 1479 if (pChecksum) { 1480 *pChecksum = pak->pure_checksum; 1481 } 1482 return 1; 1483 } 1484 pakFile = pakFile->next; 1485 } while(pakFile != NULL); 1486 } 1487 } 1488 return -1; 1489 } 1490 1491 /* 1492 ============ 1493 FS_ReadFile 1494 1495 Filename are relative to the quake search path 1496 a null buffer will just return the file length without loading 1497 ============ 1498 */ 1499 int FS_ReadFile( const char *qpath, void **buffer ) { 1500 fileHandle_t h; 1501 byte* buf; 1502 qboolean isConfig; 1503 int len; 1504 1505 if ( !fs_searchpaths ) { 1506 Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" ); 1507 } 1508 1509 if ( !qpath || !qpath[0] ) { 1510 Com_Error( ERR_FATAL, "FS_ReadFile with empty name\n" ); 1511 } 1512 1513 buf = NULL; // quiet compiler warning 1514 1515 // if this is a .cfg file and we are playing back a journal, read 1516 // it from the journal file 1517 if ( strstr( qpath, ".cfg" ) ) { 1518 isConfig = qtrue; 1519 if ( com_journal && com_journal->integer == 2 ) { 1520 int r; 1521 1522 Com_DPrintf( "Loading %s from journal file.\n", qpath ); 1523 r = FS_Read( &len, sizeof( len ), com_journalDataFile ); 1524 if ( r != sizeof( len ) ) { 1525 if (buffer != NULL) *buffer = NULL; 1526 return -1; 1527 } 1528 // if the file didn't exist when the journal was created 1529 if (!len) { 1530 if (buffer == NULL) { 1531 return 1; // hack for old journal files 1532 } 1533 *buffer = NULL; 1534 return -1; 1535 } 1536 if (buffer == NULL) { 1537 return len; 1538 } 1539 1540 buf = Hunk_AllocateTempMemory(len+1); 1541 *buffer = buf; 1542 1543 r = FS_Read( buf, len, com_journalDataFile ); 1544 if ( r != len ) { 1545 Com_Error( ERR_FATAL, "Read from journalDataFile failed" ); 1546 } 1547 1548 fs_loadCount++; 1549 fs_loadStack++; 1550 1551 // guarantee that it will have a trailing 0 for string operations 1552 buf[len] = 0; 1553 1554 return len; 1555 } 1556 } else { 1557 isConfig = qfalse; 1558 } 1559 1560 // look for it in the filesystem or pack files 1561 len = FS_FOpenFileRead( qpath, &h, qfalse ); 1562 if ( h == 0 ) { 1563 if ( buffer ) { 1564 *buffer = NULL; 1565 } 1566 // if we are journalling and it is a config file, write a zero to the journal file 1567 if ( isConfig && com_journal && com_journal->integer == 1 ) { 1568 Com_DPrintf( "Writing zero for %s to journal file.\n", qpath ); 1569 len = 0; 1570 FS_Write( &len, sizeof( len ), com_journalDataFile ); 1571 FS_Flush( com_journalDataFile ); 1572 } 1573 return -1; 1574 } 1575 1576 if ( !buffer ) { 1577 if ( isConfig && com_journal && com_journal->integer == 1 ) { 1578 Com_DPrintf( "Writing len for %s to journal file.\n", qpath ); 1579 FS_Write( &len, sizeof( len ), com_journalDataFile ); 1580 FS_Flush( com_journalDataFile ); 1581 } 1582 FS_FCloseFile( h); 1583 return len; 1584 } 1585 1586 fs_loadCount++; 1587 fs_loadStack++; 1588 1589 buf = Hunk_AllocateTempMemory(len+1); 1590 *buffer = buf; 1591 1592 FS_Read (buf, len, h); 1593 1594 // guarantee that it will have a trailing 0 for string operations 1595 buf[len] = 0; 1596 FS_FCloseFile( h ); 1597 1598 // if we are journalling and it is a config file, write it to the journal file 1599 if ( isConfig && com_journal && com_journal->integer == 1 ) { 1600 Com_DPrintf( "Writing %s to journal file.\n", qpath ); 1601 FS_Write( &len, sizeof( len ), com_journalDataFile ); 1602 FS_Write( buf, len, com_journalDataFile ); 1603 FS_Flush( com_journalDataFile ); 1604 } 1605 return len; 1606 } 1607 1608 /* 1609 ============= 1610 FS_FreeFile 1611 ============= 1612 */ 1613 void FS_FreeFile( void *buffer ) { 1614 if ( !fs_searchpaths ) { 1615 Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" ); 1616 } 1617 if ( !buffer ) { 1618 Com_Error( ERR_FATAL, "FS_FreeFile( NULL )" ); 1619 } 1620 fs_loadStack--; 1621 1622 Hunk_FreeTempMemory( buffer ); 1623 1624 // if all of our temp files are free, clear all of our space 1625 if ( fs_loadStack == 0 ) { 1626 Hunk_ClearTempMemory(); 1627 } 1628 } 1629 1630 /* 1631 ============ 1632 FS_WriteFile 1633 1634 Filename are reletive to the quake search path 1635 ============ 1636 */ 1637 void FS_WriteFile( const char *qpath, const void *buffer, int size ) { 1638 fileHandle_t f; 1639 1640 if ( !fs_searchpaths ) { 1641 Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" ); 1642 } 1643 1644 if ( !qpath || !buffer ) { 1645 Com_Error( ERR_FATAL, "FS_WriteFile: NULL parameter" ); 1646 } 1647 1648 f = FS_FOpenFileWrite( qpath ); 1649 if ( !f ) { 1650 Com_Printf( "Failed to open %s\n", qpath ); 1651 return; 1652 } 1653 1654 FS_Write( buffer, size, f ); 1655 1656 FS_FCloseFile( f ); 1657 } 1658 1659 1660 1661 /* 1662 ========================================================================== 1663 1664 ZIP FILE LOADING 1665 1666 ========================================================================== 1667 */ 1668 1669 /* 1670 ================= 1671 FS_LoadZipFile 1672 1673 Creates a new pak_t in the search chain for the contents 1674 of a zip file. 1675 ================= 1676 */ 1677 static pack_t *FS_LoadZipFile( char *zipfile, const char *basename ) 1678 { 1679 fileInPack_t *buildBuffer; 1680 pack_t *pack; 1681 unzFile uf; 1682 int err; 1683 unz_global_info gi; 1684 char filename_inzip[MAX_ZPATH]; 1685 unz_file_info file_info; 1686 int i, len; 1687 long hash; 1688 int fs_numHeaderLongs; 1689 int *fs_headerLongs; 1690 char *namePtr; 1691 1692 fs_numHeaderLongs = 0; 1693 1694 uf = unzOpen(zipfile); 1695 err = unzGetGlobalInfo (uf,&gi); 1696 1697 if (err != UNZ_OK) 1698 return NULL; 1699 1700 fs_packFiles += gi.number_entry; 1701 1702 len = 0; 1703 unzGoToFirstFile(uf); 1704 for (i = 0; i < gi.number_entry; i++) 1705 { 1706 err = unzGetCurrentFileInfo(uf, &file_info, filename_inzip, sizeof(filename_inzip), NULL, 0, NULL, 0); 1707 if (err != UNZ_OK) { 1708 break; 1709 } 1710 len += strlen(filename_inzip) + 1; 1711 unzGoToNextFile(uf); 1712 } 1713 1714 buildBuffer = Z_Malloc( (gi.number_entry * sizeof( fileInPack_t )) + len ); 1715 namePtr = ((char *) buildBuffer) + gi.number_entry * sizeof( fileInPack_t ); 1716 fs_headerLongs = Z_Malloc( gi.number_entry * sizeof(int) ); 1717 1718 // get the hash table size from the number of files in the zip 1719 // because lots of custom pk3 files have less than 32 or 64 files 1720 for (i = 1; i <= MAX_FILEHASH_SIZE; i <<= 1) { 1721 if (i > gi.number_entry) { 1722 break; 1723 } 1724 } 1725 1726 pack = Z_Malloc( sizeof( pack_t ) + i * sizeof(fileInPack_t *) ); 1727 pack->hashSize = i; 1728 pack->hashTable = (fileInPack_t **) (((char *) pack) + sizeof( pack_t )); 1729 for(i = 0; i < pack->hashSize; i++) { 1730 pack->hashTable[i] = NULL; 1731 } 1732 1733 Q_strncpyz( pack->pakFilename, zipfile, sizeof( pack->pakFilename ) ); 1734 Q_strncpyz( pack->pakBasename, basename, sizeof( pack->pakBasename ) ); 1735 1736 // strip .pk3 if needed 1737 if ( strlen( pack->pakBasename ) > 4 && !Q_stricmp( pack->pakBasename + strlen( pack->pakBasename ) - 4, ".pk3" ) ) { 1738 pack->pakBasename[strlen( pack->pakBasename ) - 4] = 0; 1739 } 1740 1741 pack->handle = uf; 1742 pack->numfiles = gi.number_entry; 1743 unzGoToFirstFile(uf); 1744 1745 for (i = 0; i < gi.number_entry; i++) 1746 { 1747 err = unzGetCurrentFileInfo(uf, &file_info, filename_inzip, sizeof(filename_inzip), NULL, 0, NULL, 0); 1748 if (err != UNZ_OK) { 1749 break; 1750 } 1751 if (file_info.uncompressed_size > 0) { 1752 fs_headerLongs[fs_numHeaderLongs++] = LittleLong(file_info.crc); 1753 } 1754 Q_strlwr( filename_inzip ); 1755 hash = FS_HashFileName(filename_inzip, pack->hashSize); 1756 buildBuffer[i].name = namePtr; 1757 strcpy( buildBuffer[i].name, filename_inzip ); 1758 namePtr += strlen(filename_inzip) + 1; 1759 // store the file position in the zip 1760 unzGetCurrentFileInfoPosition(uf, &buildBuffer[i].pos); 1761 // 1762 buildBuffer[i].next = pack->hashTable[hash]; 1763 pack->hashTable[hash] = &buildBuffer[i]; 1764 unzGoToNextFile(uf); 1765 } 1766 1767 pack->checksum = Com_BlockChecksum( fs_headerLongs, 4 * fs_numHeaderLongs ); 1768 pack->pure_checksum = Com_BlockChecksumKey( fs_headerLongs, 4 * fs_numHeaderLongs, LittleLong(fs_checksumFeed) ); 1769 pack->checksum = LittleLong( pack->checksum ); 1770 pack->pure_checksum = LittleLong( pack->pure_checksum ); 1771 1772 Z_Free(fs_headerLongs); 1773 1774 pack->buildBuffer = buildBuffer; 1775 return pack; 1776 } 1777 1778 /* 1779 ================================================================================= 1780 1781 DIRECTORY SCANNING FUNCTIONS 1782 1783 ================================================================================= 1784 */ 1785 1786 #define MAX_FOUND_FILES 0x1000 1787 1788 static int FS_ReturnPath( const char *zname, char *zpath, int *depth ) { 1789 int len, at, newdep; 1790 1791 newdep = 0; 1792 zpath[0] = 0; 1793 len = 0; 1794 at = 0; 1795 1796 while(zname[at] != 0) 1797 { 1798 if (zname[at]=='/' || zname[at]=='\\') { 1799 len = at; 1800 newdep++; 1801 } 1802 at++; 1803 } 1804 strcpy(zpath, zname); 1805 zpath[len] = 0; 1806 *depth = newdep; 1807 1808 return len; 1809 } 1810 1811 /* 1812 ================== 1813 FS_AddFileToList 1814 ================== 1815 */ 1816 static int FS_AddFileToList( char *name, char *list[MAX_FOUND_FILES], int nfiles ) { 1817 int i; 1818 1819 if ( nfiles == MAX_FOUND_FILES - 1 ) { 1820 return nfiles; 1821 } 1822 for ( i = 0 ; i < nfiles ; i++ ) { 1823 if ( !Q_stricmp( name, list[i] ) ) { 1824 return nfiles; // allready in list 1825 } 1826 } 1827 list[nfiles] = CopyString( name ); 1828 nfiles++; 1829 1830 return nfiles; 1831 } 1832 1833 /* 1834 =============== 1835 FS_ListFilteredFiles 1836 1837 Returns a uniqued list of files that match the given criteria 1838 from all search paths 1839 =============== 1840 */ 1841 char **FS_ListFilteredFiles( const char *path, const char *extension, char *filter, int *numfiles ) { 1842 int nfiles; 1843 char **listCopy; 1844 char *list[MAX_FOUND_FILES]; 1845 searchpath_t *search; 1846 int i; 1847 int pathLength; 1848 int extensionLength; 1849 int length, pathDepth, temp; 1850 pack_t *pak; 1851 fileInPack_t *buildBuffer; 1852 char zpath[MAX_ZPATH]; 1853 1854 if ( !fs_searchpaths ) { 1855 Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" ); 1856 } 1857 1858 if ( !path ) { 1859 *numfiles = 0; 1860 return NULL; 1861 } 1862 if ( !extension ) { 1863 extension = ""; 1864 } 1865 1866 pathLength = strlen( path ); 1867 if ( path[pathLength-1] == '\\' || path[pathLength-1] == '/' ) { 1868 pathLength--; 1869 } 1870 extensionLength = strlen( extension ); 1871 nfiles = 0; 1872 FS_ReturnPath(path, zpath, &pathDepth); 1873 1874 // 1875 // search through the path, one element at a time, adding to list 1876 // 1877 for (search = fs_searchpaths ; search ; search = search->next) { 1878 // is the element a pak file? 1879 if (search->pack) { 1880 1881 //ZOID: If we are pure, don't search for files on paks that 1882 // aren't on the pure list 1883 if ( !FS_PakIsPure(search->pack) ) { 1884 continue; 1885 } 1886 1887 // look through all the pak file elements 1888 pak = search->pack; 1889 buildBuffer = pak->buildBuffer; 1890 for (i = 0; i < pak->numfiles; i++) { 1891 char *name; 1892 int zpathLen, depth; 1893 1894 // check for directory match 1895 name = buildBuffer[i].name; 1896 // 1897 if (filter) { 1898 // case insensitive 1899 if (!Com_FilterPath( filter, name, qfalse )) 1900 continue; 1901 // unique the match 1902 nfiles = FS_AddFileToList( name, list, nfiles ); 1903 } 1904 else { 1905 1906 zpathLen = FS_ReturnPath(name, zpath, &depth); 1907 1908 if ( (depth-pathDepth)>2 || pathLength > zpathLen || Q_stricmpn( name, path, pathLength ) ) { 1909 continue; 1910 } 1911 1912 // check for extension match 1913 length = strlen( name ); 1914 if ( length < extensionLength ) { 1915 continue; 1916 } 1917 1918 if ( Q_stricmp( name + length - extensionLength, extension ) ) { 1919 continue; 1920 } 1921 // unique the match 1922 1923 temp = pathLength; 1924 if (pathLength) { 1925 temp++; // include the '/' 1926 } 1927 nfiles = FS_AddFileToList( name + temp, list, nfiles ); 1928 } 1929 } 1930 } else if (search->dir) { // scan for files in the filesystem 1931 char *netpath; 1932 int numSysFiles; 1933 char **sysFiles; 1934 char *name; 1935 1936 // don't scan directories for files if we are pure or restricted 1937 if ( fs_restrict->integer || fs_numServerPaks ) { 1938 continue; 1939 } else { 1940 netpath = FS_BuildOSPath( search->dir->path, search->dir->gamedir, path ); 1941 sysFiles = Sys_ListFiles( netpath, extension, filter, &numSysFiles, qfalse ); 1942 for ( i = 0 ; i < numSysFiles ; i++ ) { 1943 // unique the match 1944 name = sysFiles[i]; 1945 nfiles = FS_AddFileToList( name, list, nfiles ); 1946 } 1947 Sys_FreeFileList( sysFiles ); 1948 } 1949 } 1950 } 1951 1952 // return a copy of the list 1953 *numfiles = nfiles; 1954 1955 if ( !nfiles ) { 1956 return NULL; 1957 } 1958 1959 listCopy = Z_Malloc( ( nfiles + 1 ) * sizeof( *listCopy ) ); 1960 for ( i = 0 ; i < nfiles ; i++ ) { 1961 listCopy[i] = list[i]; 1962 } 1963 listCopy[i] = NULL; 1964 1965 return listCopy; 1966 } 1967 1968 /* 1969 ================= 1970 FS_ListFiles 1971 ================= 1972 */ 1973 char **FS_ListFiles( const char *path, const char *extension, int *numfiles ) { 1974 return FS_ListFilteredFiles( path, extension, NULL, numfiles ); 1975 } 1976 1977 /* 1978 ================= 1979 FS_FreeFileList 1980 ================= 1981 */ 1982 void FS_FreeFileList( char **list ) { 1983 int i; 1984 1985 if ( !fs_searchpaths ) { 1986 Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" ); 1987 } 1988 1989 if ( !list ) { 1990 return; 1991 } 1992 1993 for ( i = 0 ; list[i] ; i++ ) { 1994 Z_Free( list[i] ); 1995 } 1996 1997 Z_Free( list ); 1998 } 1999 2000 2001 /* 2002 ================ 2003 FS_GetFileList 2004 ================ 2005 */ 2006 int FS_GetFileList( const char *path, const char *extension, char *listbuf, int bufsize ) { 2007 int nFiles, i, nTotal, nLen; 2008 char **pFiles = NULL; 2009 2010 *listbuf = 0; 2011 nFiles = 0; 2012 nTotal = 0; 2013 2014 if (Q_stricmp(path, "$modlist") == 0) { 2015 return FS_GetModList(listbuf, bufsize); 2016 } 2017 2018 pFiles = FS_ListFiles(path, extension, &nFiles); 2019 2020 for (i =0; i < nFiles; i++) { 2021 nLen = strlen(pFiles[i]) + 1; 2022 if (nTotal + nLen + 1 < bufsize) { 2023 strcpy(listbuf, pFiles[i]); 2024 listbuf += nLen; 2025 nTotal += nLen; 2026 } 2027 else { 2028 nFiles = i; 2029 break; 2030 } 2031 } 2032 2033 FS_FreeFileList(pFiles); 2034 2035 return nFiles; 2036 } 2037 2038 /* 2039 ======================= 2040 Sys_ConcatenateFileLists 2041 2042 mkv: Naive implementation. Concatenates three lists into a 2043 new list, and frees the old lists from the heap. 2044 bk001129 - from cvs1.17 (mkv) 2045 2046 FIXME TTimo those two should move to common.c next to Sys_ListFiles 2047 ======================= 2048 */ 2049 static unsigned int Sys_CountFileList(char **list) 2050 { 2051 int i = 0; 2052 2053 if (list) 2054 { 2055 while (*list) 2056 { 2057 list++; 2058 i++; 2059 } 2060 } 2061 return i; 2062 } 2063 2064 static char** Sys_ConcatenateFileLists( char **list0, char **list1, char **list2 ) 2065 { 2066 int totalLength = 0; 2067 char** cat = NULL, **dst, **src; 2068 2069 totalLength += Sys_CountFileList(list0); 2070 totalLength += Sys_CountFileList(list1); 2071 totalLength += Sys_CountFileList(list2); 2072 2073 /* Create new list. */ 2074 dst = cat = Z_Malloc( ( totalLength + 1 ) * sizeof( char* ) ); 2075 2076 /* Copy over lists. */ 2077 if (list0) 2078 { 2079 for (src = list0; *src; src++, dst++) 2080 *dst = *src; 2081 } 2082 if (list1) 2083 { 2084 for (src = list1; *src; src++, dst++) 2085 *dst = *src; 2086 } 2087 if (list2) 2088 { 2089 for (src = list2; *src; src++, dst++) 2090 *dst = *src; 2091 } 2092 2093 // Terminate the list 2094 *dst = NULL; 2095 2096 // Free our old lists. 2097 // NOTE: not freeing their content, it's been merged in dst and still being used 2098 if (list0) Z_Free( list0 ); 2099 if (list1) Z_Free( list1 ); 2100 if (list2) Z_Free( list2 ); 2101 2102 return cat; 2103 } 2104 2105 /* 2106 ================ 2107 FS_GetModList 2108 2109 Returns a list of mod directory names 2110 A mod directory is a peer to baseq3 with a pk3 in it 2111 The directories are searched in base path, cd path and home path 2112 ================ 2113 */ 2114 int FS_GetModList( char *listbuf, int bufsize ) { 2115 int nMods, i, j, nTotal, nLen, nPaks, nPotential, nDescLen; 2116 char **pFiles = NULL; 2117 char **pPaks = NULL; 2118 char *name, *path; 2119 char descPath[MAX_OSPATH]; 2120 fileHandle_t descHandle; 2121 2122 int dummy; 2123 char **pFiles0 = NULL; 2124 char **pFiles1 = NULL; 2125 char **pFiles2 = NULL; 2126 qboolean bDrop = qfalse; 2127 2128 *listbuf = 0; 2129 nMods = nPotential = nTotal = 0; 2130 2131 pFiles0 = Sys_ListFiles( fs_homepath->string, NULL, NULL, &dummy, qtrue ); 2132 pFiles1 = Sys_ListFiles( fs_basepath->string, NULL, NULL, &dummy, qtrue ); 2133 pFiles2 = Sys_ListFiles( fs_cdpath->string, NULL, NULL, &dummy, qtrue ); 2134 // we searched for mods in the three paths 2135 // it is likely that we have duplicate names now, which we will cleanup below 2136 pFiles = Sys_ConcatenateFileLists( pFiles0, pFiles1, pFiles2 ); 2137 nPotential = Sys_CountFileList(pFiles); 2138 2139 for ( i = 0 ; i < nPotential ; i++ ) { 2140 name = pFiles[i]; 2141 // NOTE: cleaner would involve more changes 2142 // ignore duplicate mod directories 2143 if (i!=0) { 2144 bDrop = qfalse; 2145 for(j=0; j<i; j++) 2146 { 2147 if (Q_stricmp(pFiles[j],name)==0) { 2148 // this one can be dropped 2149 bDrop = qtrue; 2150 break; 2151 } 2152 } 2153 } 2154 if (bDrop) { 2155 continue; 2156 } 2157 // we drop "baseq3" "." and ".." 2158 if (Q_stricmp(name, "baseq3") && Q_stricmpn(name, ".", 1)) { 2159 // now we need to find some .pk3 files to validate the mod 2160 // NOTE TTimo: (actually I'm not sure why .. what if it's a mod under developement with no .pk3?) 2161 // we didn't keep the information when we merged the directory names, as to what OS Path it was found under 2162 // so it could be in base path, cd path or home path 2163 // we will try each three of them here (yes, it's a bit messy) 2164 path = FS_BuildOSPath( fs_basepath->string, name, "" ); 2165 nPaks = 0; 2166 pPaks = Sys_ListFiles(path, ".pk3", NULL, &nPaks, qfalse); 2167 Sys_FreeFileList( pPaks ); // we only use Sys_ListFiles to check wether .pk3 files are present 2168 2169 /* Try on cd path */ 2170 if( nPaks <= 0 ) { 2171 path = FS_BuildOSPath( fs_cdpath->string, name, "" ); 2172 nPaks = 0; 2173 pPaks = Sys_ListFiles( path, ".pk3", NULL, &nPaks, qfalse ); 2174 Sys_FreeFileList( pPaks ); 2175 } 2176 2177 /* try on home path */ 2178 if ( nPaks <= 0 ) 2179 { 2180 path = FS_BuildOSPath( fs_homepath->string, name, "" ); 2181 nPaks = 0; 2182 pPaks = Sys_ListFiles( path, ".pk3", NULL, &nPaks, qfalse ); 2183 Sys_FreeFileList( pPaks ); 2184 } 2185 2186 if (nPaks > 0) { 2187 nLen = strlen(name) + 1; 2188 // nLen is the length of the mod path 2189 // we need to see if there is a description available 2190 descPath[0] = '\0'; 2191 strcpy(descPath, name); 2192 strcat(descPath, "/description.txt"); 2193 nDescLen = FS_SV_FOpenFileRead( descPath, &descHandle ); 2194 if ( nDescLen > 0 && descHandle) { 2195 FILE *file; 2196 file = FS_FileForHandle(descHandle); 2197 Com_Memset( descPath, 0, sizeof( descPath ) ); 2198 nDescLen = fread(descPath, 1, 48, file); 2199 if (nDescLen >= 0) { 2200 descPath[nDescLen] = '\0'; 2201 } 2202 FS_FCloseFile(descHandle); 2203 } else { 2204 strcpy(descPath, name); 2205 } 2206 nDescLen = strlen(descPath) + 1; 2207 2208 if (nTotal + nLen + 1 + nDescLen + 1 < bufsize) { 2209 strcpy(listbuf, name); 2210 listbuf += nLen; 2211 strcpy(listbuf, descPath); 2212 listbuf += nDescLen; 2213 nTotal += nLen + nDescLen; 2214 nMods++; 2215 } 2216 else { 2217 break; 2218 } 2219 } 2220 } 2221 } 2222 Sys_FreeFileList( pFiles ); 2223 2224 return nMods; 2225 } 2226 2227 2228 2229 2230 //============================================================================ 2231 2232 /* 2233 ================ 2234 FS_Dir_f 2235 ================ 2236 */ 2237 void FS_Dir_f( void ) { 2238 char *path; 2239 char *extension; 2240 char **dirnames; 2241 int ndirs; 2242 int i; 2243 2244 if ( Cmd_Argc() < 2 || Cmd_Argc() > 3 ) { 2245 Com_Printf( "usage: dir <directory> [extension]\n" ); 2246 return; 2247 } 2248 2249 if ( Cmd_Argc() == 2 ) { 2250 path = Cmd_Argv( 1 ); 2251 extension = ""; 2252 } else { 2253 path = Cmd_Argv( 1 ); 2254 extension = Cmd_Argv( 2 ); 2255 } 2256 2257 Com_Printf( "Directory of %s %s\n", path, extension ); 2258 Com_Printf( "---------------\n" ); 2259 2260 dirnames = FS_ListFiles( path, extension, &ndirs ); 2261 2262 for ( i = 0; i < ndirs; i++ ) { 2263 Com_Printf( "%s\n", dirnames[i] ); 2264 } 2265 FS_FreeFileList( dirnames ); 2266 } 2267 2268 /* 2269 =========== 2270 FS_ConvertPath 2271 =========== 2272 */ 2273 void FS_ConvertPath( char *s ) { 2274 while (*s) { 2275 if ( *s == '\\' || *s == ':' ) { 2276 *s = '/'; 2277 } 2278 s++; 2279 } 2280 } 2281 2282 /* 2283 =========== 2284 FS_PathCmp 2285 2286 Ignore case and seprator char distinctions 2287 =========== 2288 */ 2289 int FS_PathCmp( const char *s1, const char *s2 ) { 2290 int c1, c2; 2291 2292 do { 2293 c1 = *s1++; 2294 c2 = *s2++; 2295 2296 if (c1 >= 'a' && c1 <= 'z') { 2297 c1 -= ('a' - 'A'); 2298 } 2299 if (c2 >= 'a' && c2 <= 'z') { 2300 c2 -= ('a' - 'A'); 2301 } 2302 2303 if ( c1 == '\\' || c1 == ':' ) { 2304 c1 = '/'; 2305 } 2306 if ( c2 == '\\' || c2 == ':' ) { 2307 c2 = '/'; 2308 } 2309 2310 if (c1 < c2) { 2311 return -1; // strings not equal 2312 } 2313 if (c1 > c2) { 2314 return 1; 2315 } 2316 } while (c1); 2317 2318 return 0; // strings are equal 2319 } 2320 2321 /* 2322 ================ 2323 FS_SortFileList 2324 ================ 2325 */ 2326 void FS_SortFileList(char **filelist, int numfiles) { 2327 int i, j, k, numsortedfiles; 2328 char **sortedlist; 2329 2330 sortedlist = Z_Malloc( ( numfiles + 1 ) * sizeof( *sortedlist ) ); 2331 sortedlist[0] = NULL; 2332 numsortedfiles = 0; 2333 for (i = 0; i < numfiles; i++) { 2334 for (j = 0; j < numsortedfiles; j++) { 2335 if (FS_PathCmp(filelist[i], sortedlist[j]) < 0) { 2336 break; 2337 } 2338 } 2339 for (k = numsortedfiles; k > j; k--) { 2340 sortedlist[k] = sortedlist[k-1]; 2341 } 2342 sortedlist[j] = filelist[i]; 2343 numsortedfiles++; 2344 } 2345 Com_Memcpy(filelist, sortedlist, numfiles * sizeof( *filelist ) ); 2346 Z_Free(sortedlist); 2347 } 2348 2349 /* 2350 ================ 2351 FS_NewDir_f 2352 ================ 2353 */ 2354 void FS_NewDir_f( void ) { 2355 char *filter; 2356 char **dirnames; 2357 int ndirs; 2358 int i; 2359 2360 if ( Cmd_Argc() < 2 ) { 2361 Com_Printf( "usage: fdir <filter>\n" ); 2362 Com_Printf( "example: fdir *q3dm*.bsp\n"); 2363 return; 2364 } 2365 2366 filter = Cmd_Argv( 1 ); 2367 2368 Com_Printf( "---------------\n" ); 2369 2370 dirnames = FS_ListFilteredFiles( "", "", filter, &ndirs ); 2371 2372 FS_SortFileList(dirnames, ndirs); 2373 2374 for ( i = 0; i < ndirs; i++ ) { 2375 FS_ConvertPath(dirnames[i]); 2376 Com_Printf( "%s\n", dirnames[i] ); 2377 } 2378 Com_Printf( "%d files listed\n", ndirs ); 2379 FS_FreeFileList( dirnames ); 2380 } 2381 2382 /* 2383 ============ 2384 FS_Path_f 2385 2386 ============ 2387 */ 2388 void FS_Path_f( void ) { 2389 searchpath_t *s; 2390 int i; 2391 2392 Com_Printf ("Current search path:\n"); 2393 for (s = fs_searchpaths; s; s = s->next) { 2394 if (s->pack) { 2395 Com_Printf ("%s (%i files)\n", s->pack->pakFilename, s->pack->numfiles); 2396 if ( fs_numServerPaks ) { 2397 if ( !FS_PakIsPure(s->pack) ) { 2398 Com_Printf( " not on the pure list\n" ); 2399 } else { 2400 Com_Printf( " on the pure list\n" ); 2401 } 2402 } 2403 } else { 2404 Com_Printf ("%s/%s\n", s->dir->path, s->dir->gamedir ); 2405 } 2406 } 2407 2408 2409 Com_Printf( "\n" ); 2410 for ( i = 1 ; i < MAX_FILE_HANDLES ; i++ ) { 2411 if ( fsh[i].handleFiles.file.o ) { 2412 Com_Printf( "handle %i: %s\n", i, fsh[i].name ); 2413 } 2414 } 2415 } 2416 2417 /* 2418 ============ 2419 FS_TouchFile_f 2420 2421 The only purpose of this function is to allow game script files to copy 2422 arbitrary files furing an "fs_copyfiles 1" run. 2423 ============ 2424 */ 2425 void FS_TouchFile_f( void ) { 2426 fileHandle_t f; 2427 2428 if ( Cmd_Argc() != 2 ) { 2429 Com_Printf( "Usage: touchFile <file>\n" ); 2430 return; 2431 } 2432 2433 FS_FOpenFileRead( Cmd_Argv( 1 ), &f, qfalse ); 2434 if ( f ) { 2435 FS_FCloseFile( f ); 2436 } 2437 } 2438 2439 //=========================================================================== 2440 2441 2442 static int QDECL paksort( const void *a, const void *b ) { 2443 char *aa, *bb; 2444 2445 aa = *(char **)a; 2446 bb = *(char **)b; 2447 2448 return FS_PathCmp( aa, bb ); 2449 } 2450 2451 /* 2452 ================ 2453 FS_AddGameDirectory 2454 2455 Sets fs_gamedir, adds the directory to the head of the path, 2456 then loads the zip headers 2457 ================ 2458 */ 2459 #define MAX_PAKFILES 1024 2460 static void FS_AddGameDirectory( const char *path, const char *dir ) { 2461 searchpath_t *sp; 2462 int i; 2463 searchpath_t *search; 2464 pack_t *pak; 2465 char *pakfile; 2466 int numfiles; 2467 char **pakfiles; 2468 char *sorted[MAX_PAKFILES]; 2469 2470 // this fixes the case where fs_basepath is the same as fs_cdpath 2471 // which happens on full installs 2472 for ( sp = fs_searchpaths ; sp ; sp = sp->next ) { 2473 if ( sp->dir && !Q_stricmp(sp->dir->path, path) && !Q_stricmp(sp->dir->gamedir, dir)) { 2474 return; // we've already got this one 2475 } 2476 } 2477 2478 Q_strncpyz( fs_gamedir, dir, sizeof( fs_gamedir ) ); 2479 2480 // 2481 // add the directory to the search path 2482 // 2483 search = Z_Malloc (sizeof(searchpath_t)); 2484 search->dir = Z_Malloc( sizeof( *search->dir ) ); 2485 2486 Q_strncpyz( search->dir->path, path, sizeof( search->dir->path ) ); 2487 Q_strncpyz( search->dir->gamedir, dir, sizeof( search->dir->gamedir ) ); 2488 search->next = fs_searchpaths; 2489 fs_searchpaths = search; 2490 2491 // find all pak files in this directory 2492 pakfile = FS_BuildOSPath( path, dir, "" ); 2493 pakfile[ strlen(pakfile) - 1 ] = 0; // strip the trailing slash 2494 2495 pakfiles = Sys_ListFiles( pakfile, ".pk3", NULL, &numfiles, qfalse ); 2496 2497 // sort them so that later alphabetic matches override 2498 // earlier ones. This makes pak1.pk3 override pak0.pk3 2499 if ( numfiles > MAX_PAKFILES ) { 2500 numfiles = MAX_PAKFILES; 2501 } 2502 for ( i = 0 ; i < numfiles ; i++ ) { 2503 sorted[i] = pakfiles[i]; 2504 } 2505 2506 qsort( sorted, numfiles, 4, paksort ); 2507 2508 for ( i = 0 ; i < numfiles ; i++ ) { 2509 pakfile = FS_BuildOSPath( path, dir, sorted[i] ); 2510 if ( ( pak = FS_LoadZipFile( pakfile, sorted[i] ) ) == 0 ) 2511 continue; 2512 // store the game name for downloading 2513 strcpy(pak->pakGamename, dir); 2514 2515 search = Z_Malloc (sizeof(searchpath_t)); 2516 search->pack = pak; 2517 search->next = fs_searchpaths; 2518 fs_searchpaths = search; 2519 } 2520 2521 // done 2522 Sys_FreeFileList( pakfiles ); 2523 } 2524 2525 /* 2526 ================ 2527 FS_idPak 2528 ================ 2529 */ 2530 qboolean FS_idPak( char *pak, char *base ) { 2531 int i; 2532 2533 for (i = 0; i < NUM_ID_PAKS; i++) { 2534 if ( !FS_FilenameCompare(pak, va("%s/pak%d", base, i)) ) { 2535 break; 2536 } 2537 } 2538 if (i < NUM_ID_PAKS) { 2539 return qtrue; 2540 } 2541 return qfalse; 2542 } 2543 2544 /* 2545 ================ 2546 FS_ComparePaks 2547 2548 ---------------- 2549 dlstring == qtrue 2550 2551 Returns a list of pak files that we should download from the server. They all get stored 2552 in the current gamedir and an FS_Restart will be fired up after we download them all. 2553 2554 The string is the format: 2555 2556 @remotename@localname [repeat] 2557 2558 static int fs_numServerReferencedPaks; 2559 static int fs_serverReferencedPaks[MAX_SEARCH_PATHS]; 2560 static char *fs_serverReferencedPakNames[MAX_SEARCH_PATHS]; 2561 2562 ---------------- 2563 dlstring == qfalse 2564 2565 we are not interested in a download string format, we want something human-readable 2566 (this is used for diagnostics while connecting to a pure server) 2567 2568 ================ 2569 */ 2570 qboolean FS_ComparePaks( char *neededpaks, int len, qboolean dlstring ) { 2571 searchpath_t *sp; 2572 qboolean havepak, badchecksum; 2573 int i; 2574 2575 if ( !fs_numServerReferencedPaks ) { 2576 return qfalse; // Server didn't send any pack information along 2577 } 2578 2579 *neededpaks = 0; 2580 2581 for ( i = 0 ; i < fs_numServerReferencedPaks ; i++ ) { 2582 // Ok, see if we have this pak file 2583 badchecksum = qfalse; 2584 havepak = qfalse; 2585 2586 // never autodownload any of the id paks 2587 if ( FS_idPak(fs_serverReferencedPakNames[i], "baseq3") || FS_idPak(fs_serverReferencedPakNames[i], "missionpack") ) { 2588 continue; 2589 } 2590 2591 for ( sp = fs_searchpaths ; sp ; sp = sp->next ) { 2592 if ( sp->pack && sp->pack->checksum == fs_serverReferencedPaks[i] ) { 2593 havepak = qtrue; // This is it! 2594 break; 2595 } 2596 } 2597 2598 if ( !havepak && fs_serverReferencedPakNames[i] && *fs_serverReferencedPakNames[i] ) { 2599 // Don't got it 2600 2601 if (dlstring) 2602 { 2603 // Remote name 2604 Q_strcat( neededpaks, len, "@"); 2605 Q_strcat( neededpaks, len, fs_serverReferencedPakNames[i] ); 2606 Q_strcat( neededpaks, len, ".pk3" ); 2607 2608 // Local name 2609 Q_strcat( neededpaks, len, "@"); 2610 // Do we have one with the same name? 2611 if ( FS_SV_FileExists( va( "%s.pk3", fs_serverReferencedPakNames[i] ) ) ) 2612 { 2613 char st[MAX_ZPATH]; 2614 // We already have one called this, we need to download it to another name 2615 // Make something up with the checksum in it 2616 Com_sprintf( st, sizeof( st ), "%s.%08x.pk3", fs_serverReferencedPakNames[i], fs_serverReferencedPaks[i] ); 2617 Q_strcat( neededpaks, len, st ); 2618 } else 2619 { 2620 Q_strcat( neededpaks, len, fs_serverReferencedPakNames[i] ); 2621 Q_strcat( neededpaks, len, ".pk3" ); 2622 } 2623 } 2624 else 2625 { 2626 Q_strcat( neededpaks, len, fs_serverReferencedPakNames[i] ); 2627 Q_strcat( neededpaks, len, ".pk3" ); 2628 // Do we have one with the same name? 2629 if ( FS_SV_FileExists( va( "%s.pk3", fs_serverReferencedPakNames[i] ) ) ) 2630 { 2631 Q_strcat( neededpaks, len, " (local file exists with wrong checksum)"); 2632 } 2633 Q_strcat( neededpaks, len, "\n"); 2634 } 2635 } 2636 } 2637 2638 if ( *neededpaks ) { 2639 return qtrue; 2640 } 2641 2642 return qfalse; // We have them all 2643 } 2644 2645 /* 2646 ================ 2647 FS_Shutdown 2648 2649 Frees all resources and closes all files 2650 ================ 2651 */ 2652 void FS_Shutdown( qboolean closemfp ) { 2653 searchpath_t *p, *next; 2654 int i; 2655 2656 for(i = 0; i < MAX_FILE_HANDLES; i++) { 2657 if (fsh[i].fileSize) { 2658 FS_FCloseFile(i); 2659 } 2660 } 2661 2662 // free everything 2663 for ( p = fs_searchpaths ; p ; p = next ) { 2664 next = p->next; 2665 2666 if ( p->pack ) { 2667 unzClose(p->pack->handle); 2668 Z_Free( p->pack->buildBuffer ); 2669 Z_Free( p->pack ); 2670 } 2671 if ( p->dir ) { 2672 Z_Free( p->dir ); 2673 } 2674 Z_Free( p ); 2675 } 2676 2677 // any FS_ calls will now be an error until reinitialized 2678 fs_searchpaths = NULL; 2679 2680 Cmd_RemoveCommand( "path" ); 2681 Cmd_RemoveCommand( "dir" ); 2682 Cmd_RemoveCommand( "fdir" ); 2683 Cmd_RemoveCommand( "touchFile" ); 2684 2685 #ifdef FS_MISSING 2686 if (closemfp) { 2687 fclose(missingFiles); 2688 } 2689 #endif 2690 } 2691 2692 void Com_AppendCDKey( const char *filename ); 2693 void Com_ReadCDKey( const char *filename ); 2694 2695 /* 2696 ================ 2697 FS_ReorderPurePaks 2698 NOTE TTimo: the reordering that happens here is not reflected in the cvars (\cvarlist *pak*) 2699 this can lead to misleading situations, see https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=540 2700 ================ 2701 */ 2702 static void FS_ReorderPurePaks() 2703 { 2704 searchpath_t *s; 2705 int i; 2706 searchpath_t **p_insert_index, // for linked list reordering 2707 **p_previous; // when doing the scan 2708 2709 // only relevant when connected to pure server 2710 if ( !fs_numServerPaks ) 2711 return; 2712 2713 fs_reordered = qfalse; 2714 2715 p_insert_index = &fs_searchpaths; // we insert in order at the beginning of the list 2716 for ( i = 0 ; i < fs_numServerPaks ; i++ ) { 2717 p_previous = p_insert_index; // track the pointer-to-current-item 2718 for (s = *p_insert_index; s; s = s->next) { 2719 // the part of the list before p_insert_index has been sorted already 2720 if (s->pack && fs_serverPaks[i] == s->pack->checksum) { 2721 fs_reordered = qtrue; 2722 // move this element to the insert list 2723 *p_previous = s->next; 2724 s->next = *p_insert_index; 2725 *p_insert_index = s; 2726 // increment insert list 2727 p_insert_index = &s->next; 2728 break; // iterate to next server pack 2729 } 2730 p_previous = &s->next; 2731 } 2732 } 2733 } 2734 2735 /* 2736 ================ 2737 FS_Startup 2738 ================ 2739 */ 2740 static void FS_Startup( const char *gameName ) { 2741 const char *homePath; 2742 cvar_t *fs; 2743 2744 Com_Printf( "----- FS_Startup -----\n" ); 2745 2746 fs_debug = Cvar_Get( "fs_debug", "0", 0 ); 2747 fs_copyfiles = Cvar_Get( "fs_copyfiles", "0", CVAR_INIT ); 2748 fs_cdpath = Cvar_Get ("fs_cdpath", Sys_DefaultCDPath(), CVAR_INIT ); 2749 fs_basepath = Cvar_Get ("fs_basepath", Sys_DefaultInstallPath(), CVAR_INIT ); 2750 fs_basegame = Cvar_Get ("fs_basegame", "", CVAR_INIT ); 2751 homePath = Sys_DefaultHomePath(); 2752 if (!homePath || !homePath[0]) { 2753 homePath = fs_basepath->string; 2754 } 2755 fs_homepath = Cvar_Get ("fs_homepath", homePath, CVAR_INIT ); 2756 fs_gamedirvar = Cvar_Get ("fs_game", "", CVAR_INIT|CVAR_SYSTEMINFO ); 2757 fs_restrict = Cvar_Get ("fs_restrict", "", CVAR_INIT ); 2758 2759 // add search path elements in reverse priority order 2760 if (fs_cdpath->string[0]) { 2761 FS_AddGameDirectory( fs_cdpath->string, gameName ); 2762 } 2763 if (fs_basepath->string[0]) { 2764 FS_AddGameDirectory( fs_basepath->string, gameName ); 2765 } 2766 // fs_homepath is somewhat particular to *nix systems, only add if relevant 2767 // NOTE: same filtering below for mods and basegame 2768 if (fs_basepath->string[0] && Q_stricmp(fs_homepath->string,fs_basepath->string)) { 2769 FS_AddGameDirectory ( fs_homepath->string, gameName ); 2770 } 2771 2772 // check for additional base game so mods can be based upon other mods 2773 if ( fs_basegame->string[0] && !Q_stricmp( gameName, BASEGAME ) && Q_stricmp( fs_basegame->string, gameName ) ) { 2774 if (fs_cdpath->string[0]) { 2775 FS_AddGameDirectory(fs_cdpath->string, fs_basegame->string); 2776 } 2777 if (fs_basepath->string[0]) { 2778 FS_AddGameDirectory(fs_basepath->string, fs_basegame->string); 2779 } 2780 if (fs_homepath->string[0] && Q_stricmp(fs_homepath->string,fs_basepath->string)) { 2781 FS_AddGameDirectory(fs_homepath->string, fs_basegame->string); 2782 } 2783 } 2784 2785 // check for additional game folder for mods 2786 if ( fs_gamedirvar->string[0] && !Q_stricmp( gameName, BASEGAME ) && Q_stricmp( fs_gamedirvar->string, gameName ) ) { 2787 if (fs_cdpath->string[0]) { 2788 FS_AddGameDirectory(fs_cdpath->string, fs_gamedirvar->string); 2789 } 2790 if (fs_basepath->string[0]) { 2791 FS_AddGameDirectory(fs_basepath->string, fs_gamedirvar->string); 2792 } 2793 if (fs_homepath->string[0] && Q_stricmp(fs_homepath->string,fs_basepath->string)) { 2794 FS_AddGameDirectory(fs_homepath->string, fs_gamedirvar->string); 2795 } 2796 } 2797 2798 Com_ReadCDKey( "baseq3" ); 2799 fs = Cvar_Get ("fs_game", "", CVAR_INIT|CVAR_SYSTEMINFO ); 2800 if (fs && fs->string[0] != 0) { 2801 Com_AppendCDKey( fs->string ); 2802 } 2803 2804 // add our commands 2805 Cmd_AddCommand ("path", FS_Path_f); 2806 Cmd_AddCommand ("dir", FS_Dir_f ); 2807 Cmd_AddCommand ("fdir", FS_NewDir_f ); 2808 Cmd_AddCommand ("touchFile", FS_TouchFile_f ); 2809 2810 // https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=506 2811 // reorder the pure pk3 files according to server order 2812 FS_ReorderPurePaks(); 2813 2814 // print the current search paths 2815 FS_Path_f(); 2816 2817 fs_gamedirvar->modified = qfalse; // We just loaded, it's not modified 2818 2819 Com_Printf( "----------------------\n" ); 2820 2821 #ifdef FS_MISSING 2822 if (missingFiles == NULL) { 2823 missingFiles = fopen( "\\missing.txt", "ab" ); 2824 } 2825 #endif 2826 Com_Printf( "%d files in pk3 files\n", fs_packFiles ); 2827 } 2828 2829 2830 /* 2831 =================== 2832 FS_SetRestrictions 2833 2834 Looks for product keys and restricts media add on ability 2835 if the full version is not found 2836 =================== 2837 */ 2838 static void FS_SetRestrictions( void ) { 2839 searchpath_t *path; 2840 2841 #ifndef PRE_RELEASE_DEMO 2842 char *productId; 2843 2844 // if fs_restrict is set, don't even look for the id file, 2845 // which allows the demo release to be tested even if 2846 // the full game is present 2847 if ( !fs_restrict->integer ) { 2848 // look for the full game id 2849 FS_ReadFile( "productid.txt", (void **)&productId ); 2850 if ( productId ) { 2851 // check against the hardcoded string 2852 int seed, i; 2853 2854 seed = 5000; 2855 for ( i = 0 ; i < sizeof( fs_scrambledProductId ) ; i++ ) { 2856 if ( ( fs_scrambledProductId[i] ^ (seed&255) ) != productId[i] ) { 2857 break; 2858 } 2859 seed = (69069 * seed + 1); 2860 } 2861 2862 FS_FreeFile( productId ); 2863 2864 if ( i == sizeof( fs_scrambledProductId ) ) { 2865 return; // no restrictions 2866 } 2867 Com_Error( ERR_FATAL, "Invalid product identification" ); 2868 } 2869 } 2870 #endif 2871 Cvar_Set( "fs_restrict", "1" ); 2872 2873 Com_Printf( "\nRunning in restricted demo mode.\n\n" ); 2874 2875 // restart the filesystem with just the demo directory 2876 FS_Shutdown(qfalse); 2877 FS_Startup( DEMOGAME ); 2878 2879 // make sure that the pak file has the header checksum we expect 2880 for ( path = fs_searchpaths ; path ; path = path->next ) { 2881 if ( path->pack ) { 2882 // a tiny attempt to keep the checksum from being scannable from the exe 2883 if ( (path->pack->checksum ^ 0x02261994u) != (DEMO_PAK_CHECKSUM ^ 0x02261994u) ) { 2884 Com_Error( ERR_FATAL, "Corrupted pak0.pk3: %u", path->pack->checksum ); 2885 } 2886 } 2887 } 2888 } 2889 2890 /* 2891 ===================== 2892 FS_GamePureChecksum 2893 2894 Returns the checksum of the pk3 from which the server loaded the qagame.qvm 2895 ===================== 2896 */ 2897 const char *FS_GamePureChecksum( void ) { 2898 static char info[MAX_STRING_TOKENS]; 2899 searchpath_t *search; 2900 2901 info[0] = 0; 2902 2903 for ( search = fs_searchpaths ; search ; search = search->next ) { 2904 // is the element a pak file? 2905 if ( search->pack ) { 2906 if (search->pack->referenced & FS_QAGAME_REF) { 2907 Com_sprintf(info, sizeof(info), "%d", search->pack->checksum); 2908 } 2909 } 2910 } 2911 2912 return info; 2913 } 2914 2915 /* 2916 ===================== 2917 FS_LoadedPakChecksums 2918 2919 Returns a space separated string containing the checksums of all loaded pk3 files. 2920 Servers with sv_pure set will get this string and pass it to clients. 2921 ===================== 2922 */ 2923 const char *FS_LoadedPakChecksums( void ) { 2924 static char info[BIG_INFO_STRING]; 2925 searchpath_t *search; 2926 2927 info[0] = 0; 2928 2929 for ( search = fs_searchpaths ; search ; search = search->next ) { 2930 // is the element a pak file? 2931 if ( !search->pack ) { 2932 continue; 2933 } 2934 2935 Q_strcat( info, sizeof( info ), va("%i ", search->pack->checksum ) ); 2936 } 2937 2938 return info; 2939 } 2940 2941 /* 2942 ===================== 2943 FS_LoadedPakNames 2944 2945 Returns a space separated string containing the names of all loaded pk3 files. 2946 Servers with sv_pure set will get this string and pass it to clients. 2947 ===================== 2948 */ 2949 const char *FS_LoadedPakNames( void ) { 2950 static char info[BIG_INFO_STRING]; 2951 searchpath_t *search; 2952 2953 info[0] = 0; 2954 2955 for ( search = fs_searchpaths ; search ; search = search->next ) { 2956 // is the element a pak file? 2957 if ( !search->pack ) { 2958 continue; 2959 } 2960 2961 if (*info) { 2962 Q_strcat(info, sizeof( info ), " " ); 2963 } 2964 Q_strcat( info, sizeof( info ), search->pack->pakBasename ); 2965 } 2966 2967 return info; 2968 } 2969 2970 /* 2971 ===================== 2972 FS_LoadedPakPureChecksums 2973 2974 Returns a space separated string containing the pure checksums of all loaded pk3 files. 2975 Servers with sv_pure use these checksums to compare with the checksums the clients send 2976 back to the server. 2977 ===================== 2978 */ 2979 const char *FS_LoadedPakPureChecksums( void ) { 2980 static char info[BIG_INFO_STRING]; 2981 searchpath_t *search; 2982 2983 info[0] = 0; 2984 2985 for ( search = fs_searchpaths ; search ; search = search->next ) { 2986 // is the element a pak file? 2987 if ( !search->pack ) { 2988 continue; 2989 } 2990 2991 Q_strcat( info, sizeof( info ), va("%i ", search->pack->pure_checksum ) ); 2992 } 2993 2994 return info; 2995 } 2996 2997 /* 2998 ===================== 2999 FS_ReferencedPakChecksums 3000 3001 Returns a space separated string containing the checksums of all referenced pk3 files. 3002 The server will send this to the clients so they can check which files should be auto-downloaded. 3003 ===================== 3004 */ 3005 const char *FS_ReferencedPakChecksums( void ) { 3006 static char info[BIG_INFO_STRING]; 3007 searchpath_t *search; 3008 3009 info[0] = 0; 3010 3011 3012 for ( search = fs_searchpaths ; search ; search = search->next ) { 3013 // is the element a pak file? 3014 if ( search->pack ) { 3015 if (search->pack->referenced || Q_stricmpn(search->pack->pakGamename, BASEGAME, strlen(BASEGAME))) { 3016 Q_strcat( info, sizeof( info ), va("%i ", search->pack->checksum ) ); 3017 } 3018 } 3019 } 3020 3021 return info; 3022 } 3023 3024 /* 3025 ===================== 3026 FS_ReferencedPakPureChecksums 3027 3028 Returns a space separated string containing the pure checksums of all referenced pk3 files. 3029 Servers with sv_pure set will get this string back from clients for pure validation 3030 3031 The string has a specific order, "cgame ui @ ref1 ref2 ref3 ..." 3032 ===================== 3033 */ 3034 const char *FS_ReferencedPakPureChecksums( void ) { 3035 static char info[BIG_INFO_STRING]; 3036 searchpath_t *search; 3037 int nFlags, numPaks, checksum; 3038 3039 info[0] = 0; 3040 3041 checksum = fs_checksumFeed; 3042 numPaks = 0; 3043 for (nFlags = FS_CGAME_REF; nFlags; nFlags = nFlags >> 1) { 3044 if (nFlags & FS_GENERAL_REF) { 3045 // add a delimter between must haves and general refs 3046 //Q_strcat(info, sizeof(info), "@ "); 3047 info[strlen(info)+1] = '\0'; 3048 info[strlen(info)+2] = '\0'; 3049 info[strlen(info)] = '@'; 3050 info[strlen(info)] = ' '; 3051 } 3052 for ( search = fs_searchpaths ; search ; search = search->next ) { 3053 // is the element a pak file and has it been referenced based on flag? 3054 if ( search->pack && (search->pack->referenced & nFlags)) { 3055 Q_strcat( info, sizeof( info ), va("%i ", search->pack->pure_checksum ) ); 3056 if (nFlags & (FS_CGAME_REF | FS_UI_REF)) { 3057 break; 3058 } 3059 checksum ^= search->pack->pure_checksum; 3060 numPaks++; 3061 } 3062 } 3063 if (fs_fakeChkSum != 0) { 3064 // only added if a non-pure file is referenced 3065 Q_strcat( info, sizeof( info ), va("%i ", fs_fakeChkSum ) ); 3066 } 3067 } 3068 // last checksum is the encoded number of referenced pk3s 3069 checksum ^= numPaks; 3070 Q_strcat( info, sizeof( info ), va("%i ", checksum ) ); 3071 3072 return info; 3073 } 3074 3075 /* 3076 ===================== 3077 FS_ReferencedPakNames 3078 3079 Returns a space separated string containing the names of all referenced pk3 files. 3080 The server will send this to the clients so they can check which files should be auto-downloaded. 3081 ===================== 3082 */ 3083 const char *FS_ReferencedPakNames( void ) { 3084 static char info[BIG_INFO_STRING]; 3085 searchpath_t *search; 3086 3087 info[0] = 0; 3088 3089 // we want to return ALL pk3's from the fs_game path 3090 // and referenced one's from baseq3 3091 for ( search = fs_searchpaths ; search ; search = search->next ) { 3092 // is the element a pak file? 3093 if ( search->pack ) { 3094 if (*info) { 3095 Q_strcat(info, sizeof( info ), " " ); 3096 } 3097 if (search->pack->referenced || Q_stricmpn(search->pack->pakGamename, BASEGAME, strlen(BASEGAME))) { 3098 Q_strcat( info, sizeof( info ), search->pack->pakGamename ); 3099 Q_strcat( info, sizeof( info ), "/" ); 3100 Q_strcat( info, sizeof( info ), search->pack->pakBasename ); 3101 } 3102 } 3103 } 3104 3105 return info; 3106 } 3107 3108 /* 3109 ===================== 3110 FS_ClearPakReferences 3111 ===================== 3112 */ 3113 void FS_ClearPakReferences( int flags ) { 3114 searchpath_t *search; 3115 3116 if ( !flags ) { 3117 flags = -1; 3118 } 3119 for ( search = fs_searchpaths; search; search = search->next ) { 3120 // is the element a pak file and has it been referenced? 3121 if ( search->pack ) { 3122 search->pack->referenced &= ~flags; 3123 } 3124 } 3125 } 3126 3127 3128 /* 3129 ===================== 3130 FS_PureServerSetLoadedPaks 3131 3132 If the string is empty, all data sources will be allowed. 3133 If not empty, only pk3 files that match one of the space 3134 separated checksums will be checked for files, with the 3135 exception of .cfg and .dat files. 3136 ===================== 3137 */ 3138 void FS_PureServerSetLoadedPaks( const char *pakSums, const char *pakNames ) { 3139 int i, c, d; 3140 3141 Cmd_TokenizeString( pakSums ); 3142 3143 c = Cmd_Argc(); 3144 if ( c > MAX_SEARCH_PATHS ) { 3145 c = MAX_SEARCH_PATHS; 3146 } 3147 3148 fs_numServerPaks = c; 3149 3150 for ( i = 0 ; i < c ; i++ ) { 3151 fs_serverPaks[i] = atoi( Cmd_Argv( i ) ); 3152 } 3153 3154 if (fs_numServerPaks) { 3155 Com_DPrintf( "Connected to a pure server.\n" ); 3156 } 3157 else 3158 { 3159 if (fs_reordered) 3160 { 3161 // https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=540 3162 // force a restart to make sure the search order will be correct 3163 Com_DPrintf( "FS search reorder is required\n" ); 3164 FS_Restart(fs_checksumFeed); 3165 return; 3166 } 3167 } 3168 3169 for ( i = 0 ; i < c ; i++ ) { 3170 if (fs_serverPakNames[i]) { 3171 Z_Free(fs_serverPakNames[i]); 3172 } 3173 fs_serverPakNames[i] = NULL; 3174 } 3175 if ( pakNames && *pakNames ) { 3176 Cmd_TokenizeString( pakNames ); 3177 3178 d = Cmd_Argc(); 3179 if ( d > MAX_SEARCH_PATHS ) { 3180 d = MAX_SEARCH_PATHS; 3181 } 3182 3183 for ( i = 0 ; i < d ; i++ ) { 3184 fs_serverPakNames[i] = CopyString( Cmd_Argv( i ) ); 3185 } 3186 } 3187 } 3188 3189 /* 3190 ===================== 3191 FS_PureServerSetReferencedPaks 3192 3193 The checksums and names of the pk3 files referenced at the server 3194 are sent to the client and stored here. The client will use these 3195 checksums to see if any pk3 files need to be auto-downloaded. 3196 ===================== 3197 */ 3198 void FS_PureServerSetReferencedPaks( const char *pakSums, const char *pakNames ) { 3199 int i, c, d; 3200 3201 Cmd_TokenizeString( pakSums ); 3202 3203 c = Cmd_Argc(); 3204 if ( c > MAX_SEARCH_PATHS ) { 3205 c = MAX_SEARCH_PATHS; 3206 } 3207 3208 fs_numServerReferencedPaks = c; 3209 3210 for ( i = 0 ; i < c ; i++ ) { 3211 fs_serverReferencedPaks[i] = atoi( Cmd_Argv( i ) ); 3212 } 3213 3214 for ( i = 0 ; i < c ; i++ ) { 3215 if (fs_serverReferencedPakNames[i]) { 3216 Z_Free(fs_serverReferencedPakNames[i]); 3217 } 3218 fs_serverReferencedPakNames[i] = NULL; 3219 } 3220 if ( pakNames && *pakNames ) { 3221 Cmd_TokenizeString( pakNames ); 3222 3223 d = Cmd_Argc(); 3224 if ( d > MAX_SEARCH_PATHS ) { 3225 d = MAX_SEARCH_PATHS; 3226 } 3227 3228 for ( i = 0 ; i < d ; i++ ) { 3229 fs_serverReferencedPakNames[i] = CopyString( Cmd_Argv( i ) ); 3230 } 3231 } 3232 } 3233 3234 /* 3235 ================ 3236 FS_InitFilesystem 3237 3238 Called only at inital startup, not when the filesystem 3239 is resetting due to a game change 3240 ================ 3241 */ 3242 void FS_InitFilesystem( void ) { 3243 // allow command line parms to override our defaults 3244 // we have to specially handle this, because normal command 3245 // line variable sets don't happen until after the filesystem 3246 // has already been initialized 3247 Com_StartupVariable( "fs_cdpath" ); 3248 Com_StartupVariable( "fs_basepath" ); 3249 Com_StartupVariable( "fs_homepath" ); 3250 Com_StartupVariable( "fs_game" ); 3251 Com_StartupVariable( "fs_copyfiles" ); 3252 Com_StartupVariable( "fs_restrict" ); 3253 3254 // try to start up normally 3255 FS_Startup( BASEGAME ); 3256 3257 // see if we are going to allow add-ons 3258 FS_SetRestrictions(); 3259 3260 // if we can't find default.cfg, assume that the paths are 3261 // busted and error out now, rather than getting an unreadable 3262 // graphics screen when the font fails to load 3263 if ( FS_ReadFile( "default.cfg", NULL ) <= 0 ) { 3264 Com_Error( ERR_FATAL, "Couldn't load default.cfg" ); 3265 // bk001208 - SafeMode see below, FIXME? 3266 } 3267 3268 Q_strncpyz(lastValidBase, fs_basepath->string, sizeof(lastValidBase)); 3269 Q_strncpyz(lastValidGame, fs_gamedirvar->string, sizeof(lastValidGame)); 3270 3271 // bk001208 - SafeMode see below, FIXME? 3272 } 3273 3274 3275 /* 3276 ================ 3277 FS_Restart 3278 ================ 3279 */ 3280 void FS_Restart( int checksumFeed ) { 3281 3282 // free anything we currently have loaded 3283 FS_Shutdown(qfalse); 3284 3285 // set the checksum feed 3286 fs_checksumFeed = checksumFeed; 3287 3288 // clear pak references 3289 FS_ClearPakReferences(0); 3290 3291 // try to start up normally 3292 FS_Startup( BASEGAME ); 3293 3294 // see if we are going to allow add-ons 3295 FS_SetRestrictions(); 3296 3297 // if we can't find default.cfg, assume that the paths are 3298 // busted and error out now, rather than getting an unreadable 3299 // graphics screen when the font fails to load 3300 if ( FS_ReadFile( "default.cfg", NULL ) <= 0 ) { 3301 // this might happen when connecting to a pure server not using BASEGAME/pak0.pk3 3302 // (for instance a TA demo server) 3303 if (lastValidBase[0]) { 3304 FS_PureServerSetLoadedPaks("", ""); 3305 Cvar_Set("fs_basepath", lastValidBase); 3306 Cvar_Set("fs_gamedirvar", lastValidGame); 3307 lastValidBase[0] = '\0'; 3308 lastValidGame[0] = '\0'; 3309 Cvar_Set( "fs_restrict", "0" ); 3310 FS_Restart(checksumFeed); 3311 Com_Error( ERR_DROP, "Invalid game folder\n" ); 3312 return; 3313 } 3314 Com_Error( ERR_FATAL, "Couldn't load default.cfg" ); 3315 } 3316 3317 // bk010116 - new check before safeMode 3318 if ( Q_stricmp(fs_gamedirvar->string, lastValidGame) ) { 3319 // skip the q3config.cfg if "safe" is on the command line 3320 if ( !Com_SafeMode() ) { 3321 Cbuf_AddText ("exec q3config.cfg\n"); 3322 } 3323 } 3324 3325 Q_strncpyz(lastValidBase, fs_basepath->string, sizeof(lastValidBase)); 3326 Q_strncpyz(lastValidGame, fs_gamedirvar->string, sizeof(lastValidGame)); 3327 3328 } 3329 3330 /* 3331 ================= 3332 FS_ConditionalRestart 3333 restart if necessary 3334 ================= 3335 */ 3336 qboolean FS_ConditionalRestart( int checksumFeed ) { 3337 if( fs_gamedirvar->modified || checksumFeed != fs_checksumFeed ) { 3338 FS_Restart( checksumFeed ); 3339 return qtrue; 3340 } 3341 return qfalse; 3342 } 3343 3344 /* 3345 ======================================================================================== 3346 3347 Handle based file calls for virtual machines 3348 3349 ======================================================================================== 3350 */ 3351 3352 int FS_FOpenFileByMode( const char *qpath, fileHandle_t *f, fsMode_t mode ) { 3353 int r; 3354 qboolean sync; 3355 3356 sync = qfalse; 3357 3358 switch( mode ) { 3359 case FS_READ: 3360 r = FS_FOpenFileRead( qpath, f, qtrue ); 3361 break; 3362 case FS_WRITE: 3363 *f = FS_FOpenFileWrite( qpath ); 3364 r = 0; 3365 if (*f == 0) { 3366 r = -1; 3367 } 3368 break; 3369 case FS_APPEND_SYNC: 3370 sync = qtrue; 3371 case FS_APPEND: 3372 *f = FS_FOpenFileAppend( qpath ); 3373 r = 0; 3374 if (*f == 0) { 3375 r = -1; 3376 } 3377 break; 3378 default: 3379 Com_Error( ERR_FATAL, "FSH_FOpenFile: bad mode" ); 3380 return -1; 3381 } 3382 3383 if (!f) { 3384 return r; 3385 } 3386 3387 if ( *f ) { 3388 if (fsh[*f].zipFile == qtrue) { 3389 fsh[*f].baseOffset = unztell(fsh[*f].handleFiles.file.z); 3390 } else { 3391 fsh[*f].baseOffset = ftell(fsh[*f].handleFiles.file.o); 3392 } 3393 fsh[*f].fileSize = r; 3394 fsh[*f].streamed = qfalse; 3395 3396 if (mode == FS_READ) { 3397 Sys_BeginStreamedFile( *f, 0x4000 ); 3398 fsh[*f].streamed = qtrue; 3399 } 3400 } 3401 fsh[*f].handleSync = sync; 3402 3403 return r; 3404 } 3405 3406 int FS_FTell( fileHandle_t f ) { 3407 int pos; 3408 if (fsh[f].zipFile == qtrue) { 3409 pos = unztell(fsh[f].handleFiles.file.z); 3410 } else { 3411 pos = ftell(fsh[f].handleFiles.file.o); 3412 } 3413 return pos; 3414 } 3415 3416 void FS_Flush( fileHandle_t f ) { 3417 fflush(fsh[f].handleFiles.file.o); 3418 } 3419