File: qcommon\files.c

    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 if (*fs_gamedir) 558 return fs_gamedir; 559 else 560 return BASEDIRNAME; 561 } 562 563 /* 564 ============= 565 FS_ExecAutoexec 566 ============= 567 */ 568 void FS_ExecAutoexec (void) 569 { 570 char *dir; 571 char name [MAX_QPATH]; 572 573 dir = Cvar_VariableString("gamedir"); 574 if (*dir) 575 Com_sprintf(name, sizeof(name), "%s/%s/autoexec.cfg", fs_basedir->string, dir); 576 else 577 Com_sprintf(name, sizeof(name), "%s/%s/autoexec.cfg", fs_basedir->string, BASEDIRNAME); 578 if (Sys_FindFirst(name, 0, SFF_SUBDIR | SFF_HIDDEN | SFF_SYSTEM)) 579 Cbuf_AddText ("exec autoexec.cfg\n"); 580 Sys_FindClose(); 581 } 582 583 584 /* 585 ================ 586 FS_SetGamedir 587 588 Sets the gamedir and path to a different directory. 589 ================ 590 */ 591 void FS_SetGamedir (char *dir) 592 { 593 searchpath_t *next; 594 595 if (strstr(dir, "..") || strstr(dir, "/") 596 || strstr(dir, "\\") || strstr(dir, ":") ) 597 { 598 Com_Printf ("Gamedir should be a single filename, not a path\n"); 599 return; 600 } 601 602 // 603 // free up any current game dir info 604 // 605 while (fs_searchpaths != fs_base_searchpaths) 606 { 607 if (fs_searchpaths->pack) 608 { 609 fclose (fs_searchpaths->pack->handle); 610 Z_Free (fs_searchpaths->pack->files); 611 Z_Free (fs_searchpaths->pack); 612 } 613 next = fs_searchpaths->next; 614 Z_Free (fs_searchpaths); 615 fs_searchpaths = next; 616 } 617 618 // 619 // flush all data, so it will be forced to reload 620 // 621 if (dedicated && !dedicated->value) 622 Cbuf_AddText ("vid_restart\nsnd_restart\n"); 623 624 Com_sprintf (fs_gamedir, sizeof(fs_gamedir), "%s/%s", fs_basedir->string, dir); 625 626 if (!strcmp(dir,BASEDIRNAME) || (*dir == 0)) 627 { 628 Cvar_FullSet ("gamedir", "", CVAR_SERVERINFO|CVAR_NOSET); 629 Cvar_FullSet ("game", "", CVAR_LATCH|CVAR_SERVERINFO); 630 } 631 else 632 { 633 Cvar_FullSet ("gamedir", dir, CVAR_SERVERINFO|CVAR_NOSET); 634 if (fs_cddir->string[0]) 635 FS_AddGameDirectory (va("%s/%s", fs_cddir->string, dir) ); 636 FS_AddGameDirectory (va("%s/%s", fs_basedir->string, dir) ); 637 } 638 } 639 640 641 /* 642 ================ 643 FS_Link_f 644 645 Creates a filelink_t 646 ================ 647 */ 648 void FS_Link_f (void) 649 { 650 filelink_t *l, **prev; 651 652 if (Cmd_Argc() != 3) 653 { 654 Com_Printf ("USAGE: link <from> <to>\n"); 655 return; 656 } 657 658 // see if the link already exists 659 prev = &fs_links; 660 for (l=fs_links ; l ; l=l->next) 661 { 662 if (!strcmp (l->from, Cmd_Argv(1))) 663 { 664 Z_Free (l->to); 665 if (!strlen(Cmd_Argv(2))) 666 { // delete it 667 *prev = l->next; 668 Z_Free (l->from); 669 Z_Free (l); 670 return; 671 } 672 l->to = CopyString (Cmd_Argv(2)); 673 return; 674 } 675 prev = &l->next; 676 } 677 678 // create a new link 679 l = Z_Malloc(sizeof(*l)); 680 l->next = fs_links; 681 fs_links = l; 682 l->from = CopyString(Cmd_Argv(1)); 683 l->fromlength = strlen(l->from); 684 l->to = CopyString(Cmd_Argv(2)); 685 } 686 687 /* 688 ** FS_ListFiles 689 */ 690 char **FS_ListFiles( char *findname, int *numfiles, unsigned musthave, unsigned canthave ) 691 { 692 char *s; 693 int nfiles = 0; 694 char **list = 0; 695 696 s = Sys_FindFirst( findname, musthave, canthave ); 697 while ( s ) 698 { 699 if ( s[strlen(s)-1] != '.' ) 700 nfiles++; 701 s = Sys_FindNext( musthave, canthave ); 702 } 703 Sys_FindClose (); 704 705 if ( !nfiles ) 706 return NULL; 707 708 nfiles++; // add space for a guard 709 *numfiles = nfiles; 710 711 list = malloc( sizeof( char * ) * nfiles ); 712 memset( list, 0, sizeof( char * ) * nfiles ); 713 714 s = Sys_FindFirst( findname, musthave, canthave ); 715 nfiles = 0; 716 while ( s ) 717 { 718 if ( s[strlen(s)-1] != '.' ) 719 { 720 list[nfiles] = strdup( s ); 721 #ifdef _WIN32 722 strlwr( list[nfiles] ); 723 #endif 724 nfiles++; 725 } 726 s = Sys_FindNext( musthave, canthave ); 727 } 728 Sys_FindClose (); 729 730 return list; 731 } 732 733 /* 734 ** FS_Dir_f 735 */ 736 void FS_Dir_f( void ) 737 { 738 char *path = NULL; 739 char findname[1024]; 740 char wildcard[1024] = "*.*"; 741 char **dirnames; 742 int ndirs; 743 744 if ( Cmd_Argc() != 1 ) 745 { 746 strcpy( wildcard, Cmd_Argv( 1 ) ); 747 } 748 749 while ( ( path = FS_NextPath( path ) ) != NULL ) 750 { 751 char *tmp = findname; 752 753 Com_sprintf( findname, sizeof(findname), "%s/%s", path, wildcard ); 754 755 while ( *tmp != 0 ) 756 { 757 if ( *tmp == '\\' ) 758 *tmp = '/'; 759 tmp++; 760 } 761 Com_Printf( "Directory of %s\n", findname ); 762 Com_Printf( "----\n" ); 763 764 if ( ( dirnames = FS_ListFiles( findname, &ndirs, 0, 0 ) ) != 0 ) 765 { 766 int i; 767 768 for ( i = 0; i < ndirs-1; i++ ) 769 { 770 if ( strrchr( dirnames[i], '/' ) ) 771 Com_Printf( "%s\n", strrchr( dirnames[i], '/' ) + 1 ); 772 else 773 Com_Printf( "%s\n", dirnames[i] ); 774 775 free( dirnames[i] ); 776 } 777 free( dirnames ); 778 } 779 Com_Printf( "\n" ); 780 }; 781 } 782 783 /* 784 ============ 785 FS_Path_f 786 787 ============ 788 */ 789 void FS_Path_f (void) 790 { 791 searchpath_t *s; 792 filelink_t *l; 793 794 Com_Printf ("Current search path:\n"); 795 for (s=fs_searchpaths ; s ; s=s->next) 796 { 797 if (s == fs_base_searchpaths) 798 Com_Printf ("----------\n"); 799 if (s->pack) 800 Com_Printf ("%s (%i files)\n", s->pack->filename, s->pack->numfiles); 801 else 802 Com_Printf ("%s\n", s->filename); 803 } 804 805 Com_Printf ("\nLinks:\n"); 806 for (l=fs_links ; l ; l=l->next) 807 Com_Printf ("%s : %s\n", l->from, l->to); 808 } 809 810 /* 811 ================ 812 FS_NextPath 813 814 Allows enumerating all of the directories in the search path 815 ================ 816 */ 817 char *FS_NextPath (char *prevpath) 818 { 819 searchpath_t *s; 820 char *prev; 821 822 if (!prevpath) 823 return fs_gamedir; 824 825 prev = fs_gamedir; 826 for (s=fs_searchpaths ; s ; s=s->next) 827 { 828 if (s->pack) 829 continue; 830 if (prevpath == prev) 831 return s->filename; 832 prev = s->filename; 833 } 834 835 return NULL; 836 } 837 838 839 /* 840 ================ 841 FS_InitFilesystem 842 ================ 843 */ 844 void FS_InitFilesystem (void) 845 { 846 Cmd_AddCommand ("path", FS_Path_f); 847 Cmd_AddCommand ("link", FS_Link_f); 848 Cmd_AddCommand ("dir", FS_Dir_f ); 849 850 // 851 // basedir <path> 852 // allows the game to run from outside the data tree 853 // 854 fs_basedir = Cvar_Get ("basedir", ".", CVAR_NOSET); 855 856 // 857 // cddir <path> 858 // Logically concatenates the cddir after the basedir for 859 // allows the game to run from outside the data tree 860 // 861 fs_cddir = Cvar_Get ("cddir", "", CVAR_NOSET); 862 if (fs_cddir->string[0]) 863 FS_AddGameDirectory (va("%s/"BASEDIRNAME, fs_cddir->string) ); 864 865 // 866 // start up with baseq2 by default 867 // 868 FS_AddGameDirectory (va("%s/"BASEDIRNAME, fs_basedir->string) ); 869 870 // any set gamedirs will be freed up to here 871 fs_base_searchpaths = fs_searchpaths; 872 873 // check for game override 874 fs_gamedirvar = Cvar_Get ("game", "", CVAR_LATCH|CVAR_SERVERINFO); 875 if (fs_gamedirvar->string[0]) 876 FS_SetGamedir (fs_gamedirvar->string); 877 } 878 879 880 881