files.c (17534B)
1 /* 2 Copyright (C) 1997-2001 Id Software, Inc. 3 4 This program is free software; you can redistribute it and/or 5 modify it under the terms of the GNU General Public License 6 as published by the Free Software Foundation; either version 2 7 of the License, or (at your option) any later version. 8 9 This program is distributed in the hope that it will be useful, 10 but WITHOUT ANY WARRANTY; without even the implied warranty of 11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 13 See the GNU General Public License for more details. 14 15 You should have received a copy of the GNU General Public License 16 along with this program; if not, write to the Free Software 17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 18 19 */ 20 21 #include "qcommon.h" 22 23 // define this to dissalow any data but the demo pak file 24 //#define NO_ADDONS 25 26 // if a packfile directory differs from this, it is assumed to be hacked 27 // Full version 28 #define PAK0_CHECKSUM 0x40e614e0 29 // Demo 30 //#define PAK0_CHECKSUM 0xb2c6d7ea 31 // OEM 32 //#define PAK0_CHECKSUM 0x78e135c 33 34 /* 35 ============================================================================= 36 37 QUAKE FILESYSTEM 38 39 ============================================================================= 40 */ 41 42 43 // 44 // in memory 45 // 46 47 typedef struct 48 { 49 char name[MAX_QPATH]; 50 int filepos, filelen; 51 } packfile_t; 52 53 typedef struct pack_s 54 { 55 char filename[MAX_OSPATH]; 56 FILE *handle; 57 int numfiles; 58 packfile_t *files; 59 } pack_t; 60 61 char fs_gamedir[MAX_OSPATH]; 62 cvar_t *fs_basedir; 63 cvar_t *fs_cddir; 64 cvar_t *fs_gamedirvar; 65 66 typedef struct filelink_s 67 { 68 struct filelink_s *next; 69 char *from; 70 int fromlength; 71 char *to; 72 } filelink_t; 73 74 filelink_t *fs_links; 75 76 typedef struct searchpath_s 77 { 78 char filename[MAX_OSPATH]; 79 pack_t *pack; // only one of filename / pack will be used 80 struct searchpath_s *next; 81 } searchpath_t; 82 83 searchpath_t *fs_searchpaths; 84 searchpath_t *fs_base_searchpaths; // without gamedirs 85 86 87 /* 88 89 All of Quake's data access is through a hierchal file system, but the contents of the file system can be transparently merged from several sources. 90 91 The "base directory" is the path to the directory holding the quake.exe and all game directories. The sys_* files pass this to host_init in quakeparms_t->basedir. This can be overridden with the "-basedir" command line parm to allow code debugging in a different directory. The base directory is 92 only used during filesystem initialization. 93 94 The "game directory" is the first tree on the search path and directory that all generated files (savegames, screenshots, demos, config files) will be saved to. This can be overridden with the "-game" command line parameter. The game directory can never be changed while quake is executing. This is a precacution against having a malicious server instruct clients to write files over areas they shouldn't. 95 96 */ 97 98 99 /* 100 ================ 101 FS_filelength 102 ================ 103 */ 104 int FS_filelength (FILE *f) 105 { 106 int pos; 107 int end; 108 109 pos = ftell (f); 110 fseek (f, 0, SEEK_END); 111 end = ftell (f); 112 fseek (f, pos, SEEK_SET); 113 114 return end; 115 } 116 117 118 /* 119 ============ 120 FS_CreatePath 121 122 Creates any directories needed to store the given filename 123 ============ 124 */ 125 void FS_CreatePath (char *path) 126 { 127 char *ofs; 128 129 for (ofs = path+1 ; *ofs ; ofs++) 130 { 131 if (*ofs == '/') 132 { // create the directory 133 *ofs = 0; 134 Sys_Mkdir (path); 135 *ofs = '/'; 136 } 137 } 138 } 139 140 141 /* 142 ============== 143 FS_FCloseFile 144 145 For some reason, other dll's can't just cal fclose() 146 on files returned by FS_FOpenFile... 147 ============== 148 */ 149 void FS_FCloseFile (FILE *f) 150 { 151 fclose (f); 152 } 153 154 155 // RAFAEL 156 /* 157 Developer_searchpath 158 */ 159 int Developer_searchpath (int who) 160 { 161 162 int ch; 163 // PMM - warning removal 164 // char *start; 165 searchpath_t *search; 166 167 if (who == 1) // xatrix 168 ch = 'x'; 169 else if (who == 2) 170 ch = 'r'; 171 172 for (search = fs_searchpaths ; search ; search = search->next) 173 { 174 if (strstr (search->filename, "xatrix")) 175 return 1; 176 177 if (strstr (search->filename, "rogue")) 178 return 2; 179 /* 180 start = strchr (search->filename, ch); 181 182 if (start == NULL) 183 continue; 184 185 if (strcmp (start ,"xatrix") == 0) 186 return (1); 187 */ 188 } 189 return (0); 190 191 } 192 193 194 /* 195 =========== 196 FS_FOpenFile 197 198 Finds the file in the search path. 199 returns filesize and an open FILE * 200 Used for streaming data out of either a pak file or 201 a seperate file. 202 =========== 203 */ 204 int file_from_pak = 0; 205 #ifndef NO_ADDONS 206 int FS_FOpenFile (char *filename, FILE **file) 207 { 208 searchpath_t *search; 209 char netpath[MAX_OSPATH]; 210 pack_t *pak; 211 int i; 212 filelink_t *link; 213 214 file_from_pak = 0; 215 216 // check for links first 217 for (link = fs_links ; link ; link=link->next) 218 { 219 if (!strncmp (filename, link->from, link->fromlength)) 220 { 221 Com_sprintf (netpath, sizeof(netpath), "%s%s",link->to, filename+link->fromlength); 222 *file = fopen (netpath, "rb"); 223 if (*file) 224 { 225 Com_DPrintf ("link file: %s\n",netpath); 226 return FS_filelength (*file); 227 } 228 return -1; 229 } 230 } 231 232 // 233 // search through the path, one element at a time 234 // 235 for (search = fs_searchpaths ; search ; search = search->next) 236 { 237 // is the element a pak file? 238 if (search->pack) 239 { 240 // look through all the pak file elements 241 pak = search->pack; 242 for (i=0 ; i<pak->numfiles ; i++) 243 if (!Q_strcasecmp (pak->files[i].name, filename)) 244 { // found it! 245 file_from_pak = 1; 246 Com_DPrintf ("PackFile: %s : %s\n",pak->filename, filename); 247 // open a new file on the pakfile 248 *file = fopen (pak->filename, "rb"); 249 if (!*file) 250 Com_Error (ERR_FATAL, "Couldn't reopen %s", pak->filename); 251 fseek (*file, pak->files[i].filepos, SEEK_SET); 252 return pak->files[i].filelen; 253 } 254 } 255 else 256 { 257 // check a file in the directory tree 258 259 Com_sprintf (netpath, sizeof(netpath), "%s/%s",search->filename, filename); 260 261 *file = fopen (netpath, "rb"); 262 if (!*file) 263 continue; 264 265 Com_DPrintf ("FindFile: %s\n",netpath); 266 267 return FS_filelength (*file); 268 } 269 270 } 271 272 Com_DPrintf ("FindFile: can't find %s\n", filename); 273 274 *file = NULL; 275 return -1; 276 } 277 278 #else 279 280 // this is just for demos to prevent add on hacking 281 282 int FS_FOpenFile (char *filename, FILE **file) 283 { 284 searchpath_t *search; 285 char netpath[MAX_OSPATH]; 286 pack_t *pak; 287 int i; 288 289 file_from_pak = 0; 290 291 // get config from directory, everything else from pak 292 if (!strcmp(filename, "config.cfg") || !strncmp(filename, "players/", 8)) 293 { 294 Com_sprintf (netpath, sizeof(netpath), "%s/%s",FS_Gamedir(), filename); 295 296 *file = fopen (netpath, "rb"); 297 if (!*file) 298 return -1; 299 300 Com_DPrintf ("FindFile: %s\n",netpath); 301 302 return FS_filelength (*file); 303 } 304 305 for (search = fs_searchpaths ; search ; search = search->next) 306 if (search->pack) 307 break; 308 if (!search) 309 { 310 *file = NULL; 311 return -1; 312 } 313 314 pak = search->pack; 315 for (i=0 ; i<pak->numfiles ; i++) 316 if (!Q_strcasecmp (pak->files[i].name, filename)) 317 { // found it! 318 file_from_pak = 1; 319 Com_DPrintf ("PackFile: %s : %s\n",pak->filename, filename); 320 // open a new file on the pakfile 321 *file = fopen (pak->filename, "rb"); 322 if (!*file) 323 Com_Error (ERR_FATAL, "Couldn't reopen %s", pak->filename); 324 fseek (*file, pak->files[i].filepos, SEEK_SET); 325 return pak->files[i].filelen; 326 } 327 328 Com_DPrintf ("FindFile: can't find %s\n", filename); 329 330 *file = NULL; 331 return -1; 332 } 333 334 #endif 335 336 337 /* 338 ================= 339 FS_ReadFile 340 341 Properly handles partial reads 342 ================= 343 */ 344 void CDAudio_Stop(void); 345 #define MAX_READ 0x10000 // read in blocks of 64k 346 void FS_Read (void *buffer, int len, FILE *f) 347 { 348 int block, remaining; 349 int read; 350 byte *buf; 351 int tries; 352 353 buf = (byte *)buffer; 354 355 // read in chunks for progress bar 356 remaining = len; 357 tries = 0; 358 while (remaining) 359 { 360 block = remaining; 361 if (block > MAX_READ) 362 block = MAX_READ; 363 read = fread (buf, 1, block, f); 364 if (read == 0) 365 { 366 // we might have been trying to read from a CD 367 if (!tries) 368 { 369 tries = 1; 370 CDAudio_Stop(); 371 } 372 else 373 Com_Error (ERR_FATAL, "FS_Read: 0 bytes read"); 374 } 375 376 if (read == -1) 377 Com_Error (ERR_FATAL, "FS_Read: -1 bytes read"); 378 379 // do some progress bar thing here... 380 381 remaining -= read; 382 buf += read; 383 } 384 } 385 386 /* 387 ============ 388 FS_LoadFile 389 390 Filename are reletive to the quake search path 391 a null buffer will just return the file length without loading 392 ============ 393 */ 394 int FS_LoadFile (char *path, void **buffer) 395 { 396 FILE *h; 397 byte *buf; 398 int len; 399 400 buf = NULL; // quiet compiler warning 401 402 // look for it in the filesystem or pack files 403 len = FS_FOpenFile (path, &h); 404 if (!h) 405 { 406 if (buffer) 407 *buffer = NULL; 408 return -1; 409 } 410 411 if (!buffer) 412 { 413 fclose (h); 414 return len; 415 } 416 417 buf = Z_Malloc(len); 418 *buffer = buf; 419 420 FS_Read (buf, len, h); 421 422 fclose (h); 423 424 return len; 425 } 426 427 428 /* 429 ============= 430 FS_FreeFile 431 ============= 432 */ 433 void FS_FreeFile (void *buffer) 434 { 435 Z_Free (buffer); 436 } 437 438 /* 439 ================= 440 FS_LoadPackFile 441 442 Takes an explicit (not game tree related) path to a pak file. 443 444 Loads the header and directory, adding the files at the beginning 445 of the list so they override previous pack files. 446 ================= 447 */ 448 pack_t *FS_LoadPackFile (char *packfile) 449 { 450 dpackheader_t header; 451 int i; 452 packfile_t *newfiles; 453 int numpackfiles; 454 pack_t *pack; 455 FILE *packhandle; 456 dpackfile_t info[MAX_FILES_IN_PACK]; 457 unsigned checksum; 458 459 packhandle = fopen(packfile, "rb"); 460 if (!packhandle) 461 return NULL; 462 463 fread (&header, 1, sizeof(header), packhandle); 464 if (LittleLong(header.ident) != IDPAKHEADER) 465 Com_Error (ERR_FATAL, "%s is not a packfile", packfile); 466 header.dirofs = LittleLong (header.dirofs); 467 header.dirlen = LittleLong (header.dirlen); 468 469 numpackfiles = header.dirlen / sizeof(dpackfile_t); 470 471 if (numpackfiles > MAX_FILES_IN_PACK) 472 Com_Error (ERR_FATAL, "%s has %i files", packfile, numpackfiles); 473 474 newfiles = Z_Malloc (numpackfiles * sizeof(packfile_t)); 475 476 fseek (packhandle, header.dirofs, SEEK_SET); 477 fread (info, 1, header.dirlen, packhandle); 478 479 // crc the directory to check for modifications 480 checksum = Com_BlockChecksum ((void *)info, header.dirlen); 481 482 #ifdef NO_ADDONS 483 if (checksum != PAK0_CHECKSUM) 484 return NULL; 485 #endif 486 // parse the directory 487 for (i=0 ; i<numpackfiles ; i++) 488 { 489 strcpy (newfiles[i].name, info[i].name); 490 newfiles[i].filepos = LittleLong(info[i].filepos); 491 newfiles[i].filelen = LittleLong(info[i].filelen); 492 } 493 494 pack = Z_Malloc (sizeof (pack_t)); 495 strcpy (pack->filename, packfile); 496 pack->handle = packhandle; 497 pack->numfiles = numpackfiles; 498 pack->files = newfiles; 499 500 Com_Printf ("Added packfile %s (%i files)\n", packfile, numpackfiles); 501 return pack; 502 } 503 504 505 /* 506 ================ 507 FS_AddGameDirectory 508 509 Sets fs_gamedir, adds the directory to the head of the path, 510 then loads and adds pak1.pak pak2.pak ... 511 ================ 512 */ 513 void FS_AddGameDirectory (char *dir) 514 { 515 int i; 516 searchpath_t *search; 517 pack_t *pak; 518 char pakfile[MAX_OSPATH]; 519 520 strcpy (fs_gamedir, dir); 521 522 // 523 // add the directory to the search path 524 // 525 search = Z_Malloc (sizeof(searchpath_t)); 526 strcpy (search->filename, dir); 527 search->next = fs_searchpaths; 528 fs_searchpaths = search; 529 530 // 531 // add any pak files in the format pak0.pak pak1.pak, ... 532 // 533 for (i=0; i<10; i++) 534 { 535 Com_sprintf (pakfile, sizeof(pakfile), "%s/pak%i.pak", dir, i); 536 pak = FS_LoadPackFile (pakfile); 537 if (!pak) 538 continue; 539 search = Z_Malloc (sizeof(searchpath_t)); 540 search->pack = pak; 541 search->next = fs_searchpaths; 542 fs_searchpaths = search; 543 } 544 545 546 } 547 548 /* 549 ============ 550 FS_Gamedir 551 552 Called to find where to write a file (demos, savegames, etc) 553 ============ 554 */ 555 char *FS_Gamedir (void) 556 { 557 return fs_gamedir; 558 } 559 560 /* 561 ============= 562 FS_ExecAutoexec 563 ============= 564 */ 565 void FS_ExecAutoexec (void) 566 { 567 char *dir; 568 char name [MAX_QPATH]; 569 570 dir = Cvar_VariableString("gamedir"); 571 if (*dir) 572 Com_sprintf(name, sizeof(name), "%s/%s/autoexec.cfg", fs_basedir->string, dir); 573 else 574 Com_sprintf(name, sizeof(name), "%s/%s/autoexec.cfg", fs_basedir->string, BASEDIRNAME); 575 if (Sys_FindFirst(name, 0, SFF_SUBDIR | SFF_HIDDEN | SFF_SYSTEM)) 576 Cbuf_AddText ("exec autoexec.cfg\n"); 577 Sys_FindClose(); 578 } 579 580 581 /* 582 ================ 583 FS_SetGamedir 584 585 Sets the gamedir and path to a different directory. 586 ================ 587 */ 588 void FS_SetGamedir (char *dir) 589 { 590 searchpath_t *next; 591 592 if (strstr(dir, "..") || strstr(dir, "/") 593 || strstr(dir, "\\") || strstr(dir, ":") ) 594 { 595 Com_Printf ("Gamedir should be a single filename, not a path\n"); 596 return; 597 } 598 599 // 600 // free up any current game dir info 601 // 602 while (fs_searchpaths != fs_base_searchpaths) 603 { 604 if (fs_searchpaths->pack) 605 { 606 fclose (fs_searchpaths->pack->handle); 607 Z_Free (fs_searchpaths->pack->files); 608 Z_Free (fs_searchpaths->pack); 609 } 610 next = fs_searchpaths->next; 611 Z_Free (fs_searchpaths); 612 fs_searchpaths = next; 613 } 614 615 // 616 // flush all data, so it will be forced to reload 617 // 618 if (dedicated && !dedicated->value) 619 Cbuf_AddText ("vid_restart\nsnd_restart\n"); 620 621 Com_sprintf (fs_gamedir, sizeof(fs_gamedir), "%s/%s", fs_basedir->string, dir); 622 623 if (!strcmp(dir,BASEDIRNAME) || (*dir == 0)) 624 { 625 Cvar_FullSet ("gamedir", "", CVAR_SERVERINFO|CVAR_NOSET); 626 Cvar_FullSet ("game", "", CVAR_LATCH|CVAR_SERVERINFO); 627 } 628 else 629 { 630 Cvar_FullSet ("gamedir", dir, CVAR_SERVERINFO|CVAR_NOSET); 631 if (fs_cddir->string[0]) 632 FS_AddGameDirectory (va("%s/%s", fs_cddir->string, dir) ); 633 FS_AddGameDirectory (va("%s/%s", fs_basedir->string, dir) ); 634 } 635 } 636 637 638 /* 639 ================ 640 FS_Link_f 641 642 Creates a filelink_t 643 ================ 644 */ 645 void FS_Link_f (void) 646 { 647 filelink_t *l, **prev; 648 649 if (Cmd_Argc() != 3) 650 { 651 Com_Printf ("USAGE: link <from> <to>\n"); 652 return; 653 } 654 655 // see if the link already exists 656 prev = &fs_links; 657 for (l=fs_links ; l ; l=l->next) 658 { 659 if (!strcmp (l->from, Cmd_Argv(1))) 660 { 661 Z_Free (l->to); 662 if (!strlen(Cmd_Argv(2))) 663 { // delete it 664 *prev = l->next; 665 Z_Free (l->from); 666 Z_Free (l); 667 return; 668 } 669 l->to = CopyString (Cmd_Argv(2)); 670 return; 671 } 672 prev = &l->next; 673 } 674 675 // create a new link 676 l = Z_Malloc(sizeof(*l)); 677 l->next = fs_links; 678 fs_links = l; 679 l->from = CopyString(Cmd_Argv(1)); 680 l->fromlength = strlen(l->from); 681 l->to = CopyString(Cmd_Argv(2)); 682 } 683 684 /* 685 ** FS_ListFiles 686 */ 687 char **FS_ListFiles( char *findname, int *numfiles, unsigned musthave, unsigned canthave ) 688 { 689 char *s; 690 int nfiles = 0; 691 char **list = 0; 692 693 s = Sys_FindFirst( findname, musthave, canthave ); 694 while ( s ) 695 { 696 if ( s[strlen(s)-1] != '.' ) 697 nfiles++; 698 s = Sys_FindNext( musthave, canthave ); 699 } 700 Sys_FindClose (); 701 702 if ( !nfiles ) 703 return NULL; 704 705 nfiles++; // add space for a guard 706 *numfiles = nfiles; 707 708 list = malloc( sizeof( char * ) * nfiles ); 709 memset( list, 0, sizeof( char * ) * nfiles ); 710 711 s = Sys_FindFirst( findname, musthave, canthave ); 712 nfiles = 0; 713 while ( s ) 714 { 715 if ( s[strlen(s)-1] != '.' ) 716 { 717 list[nfiles] = strdup( s ); 718 #ifdef _WIN32 719 strlwr( list[nfiles] ); 720 #endif 721 nfiles++; 722 } 723 s = Sys_FindNext( musthave, canthave ); 724 } 725 Sys_FindClose (); 726 727 return list; 728 } 729 730 /* 731 ** FS_Dir_f 732 */ 733 void FS_Dir_f( void ) 734 { 735 char *path = NULL; 736 char findname[1024]; 737 char wildcard[1024] = "*.*"; 738 char **dirnames; 739 int ndirs; 740 741 if ( Cmd_Argc() != 1 ) 742 { 743 strcpy( wildcard, Cmd_Argv( 1 ) ); 744 } 745 746 while ( ( path = FS_NextPath( path ) ) != NULL ) 747 { 748 char *tmp = findname; 749 750 Com_sprintf( findname, sizeof(findname), "%s/%s", path, wildcard ); 751 752 while ( *tmp != 0 ) 753 { 754 if ( *tmp == '\\' ) 755 *tmp = '/'; 756 tmp++; 757 } 758 Com_Printf( "Directory of %s\n", findname ); 759 Com_Printf( "----\n" ); 760 761 if ( ( dirnames = FS_ListFiles( findname, &ndirs, 0, 0 ) ) != 0 ) 762 { 763 int i; 764 765 for ( i = 0; i < ndirs-1; i++ ) 766 { 767 if ( strrchr( dirnames[i], '/' ) ) 768 Com_Printf( "%s\n", strrchr( dirnames[i], '/' ) + 1 ); 769 else 770 Com_Printf( "%s\n", dirnames[i] ); 771 772 free( dirnames[i] ); 773 } 774 free( dirnames ); 775 } 776 Com_Printf( "\n" ); 777 }; 778 } 779 780 /* 781 ============ 782 FS_Path_f 783 784 ============ 785 */ 786 void FS_Path_f (void) 787 { 788 searchpath_t *s; 789 filelink_t *l; 790 791 Com_Printf ("Current search path:\n"); 792 for (s=fs_searchpaths ; s ; s=s->next) 793 { 794 if (s == fs_base_searchpaths) 795 Com_Printf ("----------\n"); 796 if (s->pack) 797 Com_Printf ("%s (%i files)\n", s->pack->filename, s->pack->numfiles); 798 else 799 Com_Printf ("%s\n", s->filename); 800 } 801 802 Com_Printf ("\nLinks:\n"); 803 for (l=fs_links ; l ; l=l->next) 804 Com_Printf ("%s : %s\n", l->from, l->to); 805 } 806 807 /* 808 ================ 809 FS_NextPath 810 811 Allows enumerating all of the directories in the search path 812 ================ 813 */ 814 char *FS_NextPath (char *prevpath) 815 { 816 searchpath_t *s; 817 char *prev; 818 819 if (!prevpath) 820 return fs_gamedir; 821 822 prev = fs_gamedir; 823 for (s=fs_searchpaths ; s ; s=s->next) 824 { 825 if (s->pack) 826 continue; 827 if (prevpath == prev) 828 return s->filename; 829 prev = s->filename; 830 } 831 832 return NULL; 833 } 834 835 836 /* 837 ================ 838 FS_InitFilesystem 839 ================ 840 */ 841 void FS_InitFilesystem (void) 842 { 843 Cmd_AddCommand ("path", FS_Path_f); 844 Cmd_AddCommand ("link", FS_Link_f); 845 Cmd_AddCommand ("dir", FS_Dir_f ); 846 847 // 848 // basedir <path> 849 // allows the game to run from outside the data tree 850 // 851 fs_basedir = Cvar_Get ("basedir", ".", CVAR_NOSET); 852 853 // 854 // cddir <path> 855 // Logically concatenates the cddir after the basedir for 856 // allows the game to run from outside the data tree 857 // 858 fs_cddir = Cvar_Get ("cddir", "", CVAR_NOSET); 859 if (fs_cddir->string[0]) 860 FS_AddGameDirectory (va("%s/"BASEDIRNAME, fs_cddir->string) ); 861 862 // 863 // start up with baseq2 by default 864 // 865 FS_AddGameDirectory (va("%s/"BASEDIRNAME, fs_basedir->string) ); 866 867 // any set gamedirs will be freed up to here 868 fs_base_searchpaths = fs_searchpaths; 869 870 // check for game override 871 fs_gamedirvar = Cvar_Get ("game", "", CVAR_LATCH|CVAR_SERVERINFO); 872 if (fs_gamedirvar->string[0]) 873 FS_SetGamedir (fs_gamedirvar->string); 874 } 875 876 877