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 */
FS_filelength(FILE * f)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 */
FS_CreatePath(char * path)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 */
FS_FCloseFile(FILE * f)149 void FS_FCloseFile (FILE *f)
150 {
151 fclose (f);
152 }
153
154
155 // RAFAEL
156 /*
157 Developer_searchpath
158 */
Developer_searchpath(int who)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
FS_FOpenFile(char * filename,FILE ** file)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
FS_FOpenFile(char * filename,FILE ** file)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
FS_Read(void * buffer,int len,FILE * f)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 */
FS_LoadFile(char * path,void ** buffer)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 */
FS_FreeFile(void * buffer)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 */
FS_LoadPackFile(char * packfile)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 Adds the directory to the head of the path,
510 then loads and adds pak1.pak pak2.pak ...
511 ================
512 */
FS_AddGameDirectory(char * dir)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 //
521 // add the base directory to the search path
522 //
523 search = Z_Malloc (sizeof(searchpath_t));
524 strncpy (search->filename, dir, sizeof(search->filename)-1);
525 search->filename[sizeof(search->filename)-1] = 0;
526
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<50; 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 #ifdef QMAX
545 Com_sprintf (pakfile, sizeof(pakfile), "%s/maxpak.pak", dir);
546 pak = FS_LoadPackFile (pakfile);
547 search = Z_Malloc (sizeof(searchpath_t));
548 search->pack = pak;
549 search->next = fs_searchpaths;
550 fs_searchpaths = search;
551 #endif
552 }
553
554 /*
555 ================
556 FS_AddHomeAsGameDirectory
557
558 Use ~/.quake2/dir as fs_gamedir
559 ================
560 */
FS_AddHomeAsGameDirectory(char * dir)561 void FS_AddHomeAsGameDirectory (char *dir)
562 {
563 #ifndef _WIN32
564 char gdir[MAX_OSPATH];
565 char *homedir=getenv("HOME");
566 if(homedir)
567 {
568 int len = snprintf(gdir,sizeof(gdir),"%s/.quake2/%s/", homedir, dir);
569 Com_Printf("using %s for writing\n",gdir);
570 FS_CreatePath (gdir);
571
572 if ((len > 0) && (len < sizeof(gdir)) && (gdir[len-1] == '/'))
573 gdir[len-1] = 0;
574
575 strncpy(fs_gamedir,gdir,sizeof(fs_gamedir)-1);
576 fs_gamedir[sizeof(fs_gamedir)-1] = 0;
577
578 FS_AddGameDirectory (gdir);
579 }
580 #endif
581 }
582
583 /*
584 ============
585 FS_Gamedir
586
587 Called to find where to write a file (demos, savegames, etc)
588 ============
589 */
FS_Gamedir(void)590 char *FS_Gamedir (void)
591 {
592 return fs_gamedir;
593 }
594
595 /*
596 =============
597 FS_ExecAutoexec
598 =============
599 */
FS_ExecAutoexec(void)600 void FS_ExecAutoexec (void)
601 {
602 char name [MAX_QPATH];
603 searchpath_t *s, *end;
604
605 // don't look in baseq2 if gamedir is set
606 if (fs_searchpaths == fs_base_searchpaths)
607 end = NULL;
608 else
609 end = fs_base_searchpaths;
610
611 // search through all the paths for an autoexec.cfg file
612 for (s = fs_searchpaths ; s != end ; s = s->next)
613 {
614 if (s->pack)
615 continue;
616
617 Com_sprintf(name, sizeof(name), "%s/autoexec.cfg", s->filename);
618
619 if (Sys_FindFirst(name, 0, SFF_SUBDIR | SFF_HIDDEN | SFF_SYSTEM))
620 {
621 Cbuf_AddText ("exec autoexec.cfg\n");
622 Sys_FindClose();
623 break;
624 }
625 Sys_FindClose();
626 }
627 }
628
629
630 /*
631 ================
632 FS_SetGamedir
633
634 Sets the gamedir and path to a different directory.
635
636 ================
637 */
FS_SetGamedir(char * dir)638 void FS_SetGamedir (char *dir)
639 {
640 searchpath_t *next;
641
642 if (strstr(dir, "..") || strstr(dir, "/")
643 || strstr(dir, "\\") || strstr(dir, ":") )
644 {
645 Com_Printf ("Gamedir should be a single filename, not a path\n");
646 return;
647 }
648
649 //
650 // free up any current game dir info
651 //
652 while (fs_searchpaths != fs_base_searchpaths)
653 {
654 if (fs_searchpaths->pack)
655 {
656 fclose (fs_searchpaths->pack->handle);
657 Z_Free (fs_searchpaths->pack->files);
658 Z_Free (fs_searchpaths->pack);
659 }
660 next = fs_searchpaths->next;
661 Z_Free (fs_searchpaths);
662 fs_searchpaths = next;
663 }
664
665 //
666 // flush all data, so it will be forced to reload
667 //
668 if (dedicated && !dedicated->value)
669 Cbuf_AddText ("vid_restart\nsnd_restart\n");
670
671
672 // now add new entries for
673 if (!strcmp(dir,BASEDIRNAME) || (*dir == 0))
674 {
675 Cvar_FullSet ("gamedir", "", CVAR_SERVERINFO|CVAR_NOSET);
676 Cvar_FullSet ("game", "", CVAR_LATCH|CVAR_SERVERINFO);
677 }
678 else
679 {
680 Cvar_FullSet ("gamedir", dir, CVAR_SERVERINFO|CVAR_NOSET);
681 if (fs_cddir->string[0])
682 FS_AddGameDirectory (va("%s/%s", fs_cddir->string, dir) );
683 FS_AddGameDirectory (va("%s/%s", fs_basedir->string, dir) );
684 FS_AddHomeAsGameDirectory(dir);
685 }
686 }
687
688
689 /*
690 ================
691 FS_Link_f
692
693 Creates a filelink_t
694 ================
695 */
FS_Link_f(void)696 void FS_Link_f (void)
697 {
698 filelink_t *l, **prev;
699
700 if (Cmd_Argc() != 3)
701 {
702 Com_Printf ("USAGE: link <from> <to>\n");
703 return;
704 }
705
706 // see if the link already exists
707 prev = &fs_links;
708 for (l=fs_links ; l ; l=l->next)
709 {
710 if (!strcmp (l->from, Cmd_Argv(1)))
711 {
712 Z_Free (l->to);
713 if (!strlen(Cmd_Argv(2)))
714 { // delete it
715 *prev = l->next;
716 Z_Free (l->from);
717 Z_Free (l);
718 return;
719 }
720 l->to = CopyString (Cmd_Argv(2));
721 return;
722 }
723 prev = &l->next;
724 }
725
726 // create a new link
727 l = Z_Malloc(sizeof(*l));
728 l->next = fs_links;
729 fs_links = l;
730 l->from = CopyString(Cmd_Argv(1));
731 l->fromlength = strlen(l->from);
732 l->to = CopyString(Cmd_Argv(2));
733 }
734
735 /*
736 ** FS_ListFiles
737 */
FS_ListFiles(char * findname,int * numfiles,unsigned musthave,unsigned canthave)738 char **FS_ListFiles( char *findname, int *numfiles, unsigned musthave, unsigned canthave )
739 {
740 char *s;
741 int nfiles = 0;
742 char **list = 0;
743
744 s = Sys_FindFirst( findname, musthave, canthave );
745 while ( s )
746 {
747 if ( s[strlen(s)-1] != '.' )
748 nfiles++;
749 s = Sys_FindNext( musthave, canthave );
750 }
751 Sys_FindClose ();
752
753 if ( !nfiles )
754 return NULL;
755
756 nfiles++; // add space for a guard
757 *numfiles = nfiles;
758
759 list = malloc( sizeof( char * ) * nfiles );
760 memset( list, 0, sizeof( char * ) * nfiles );
761
762 s = Sys_FindFirst( findname, musthave, canthave );
763 nfiles = 0;
764 while ( s )
765 {
766 if ( s[strlen(s)-1] != '.' )
767 {
768 list[nfiles] = strdup( s );
769 #ifdef _WIN32
770 strlwr( list[nfiles] );
771 #endif
772 nfiles++;
773 }
774 s = Sys_FindNext( musthave, canthave );
775 }
776 Sys_FindClose ();
777
778 return list;
779 }
780
781 /*
782 ** FS_Dir_f
783 */
FS_Dir_f(void)784 void FS_Dir_f( void )
785 {
786 char *path = NULL;
787 char findname[1024];
788 char wildcard[1024] = "*.*";
789 char **dirnames;
790 int ndirs;
791
792 if ( Cmd_Argc() != 1 )
793 {
794 strcpy( wildcard, Cmd_Argv( 1 ) );
795 }
796
797 while ( ( path = FS_NextPath( path ) ) != NULL )
798 {
799 char *tmp = findname;
800
801 Com_sprintf( findname, sizeof(findname), "%s/%s", path, wildcard );
802
803 while ( *tmp != 0 )
804 {
805 if ( *tmp == '\\' )
806 *tmp = '/';
807 tmp++;
808 }
809 Com_Printf( "Directory of %s\n", findname );
810 Com_Printf( "----\n" );
811
812 if ( ( dirnames = FS_ListFiles( findname, &ndirs, 0, 0 ) ) != 0 )
813 {
814 int i;
815
816 for ( i = 0; i < ndirs-1; i++ )
817 {
818 if ( strrchr( dirnames[i], '/' ) )
819 Com_Printf( "%s\n", strrchr( dirnames[i], '/' ) + 1 );
820 else
821 Com_Printf( "%s\n", dirnames[i] );
822
823 free( dirnames[i] );
824 }
825 free( dirnames );
826 }
827 Com_Printf( "\n" );
828 };
829 }
830
831 /*
832 ============
833 FS_Path_f
834
835 ============
836 */
FS_Path_f(void)837 void FS_Path_f (void)
838 {
839 searchpath_t *s;
840 filelink_t *l;
841
842 Com_Printf ("Current search path:\n");
843 for (s=fs_searchpaths ; s ; s=s->next)
844 {
845 if (s == fs_base_searchpaths)
846 Com_Printf ("----------\n");
847 if (s->pack)
848 Com_Printf ("%s (%i files)\n", s->pack->filename, s->pack->numfiles);
849 else
850 Com_Printf ("%s\n", s->filename);
851 }
852
853 Com_Printf ("\nLinks:\n");
854 for (l=fs_links ; l ; l=l->next)
855 Com_Printf ("%s : %s\n", l->from, l->to);
856 }
857
858 /*
859 ================
860 FS_NextPath
861
862 Allows enumerating all of the directories in the search path
863 ================
864 */
FS_NextPath(char * prevpath)865 char *FS_NextPath (char *prevpath)
866 {
867 searchpath_t *s;
868 char *prev;
869
870 prev = NULL; /* fs_gamedir is the first directory in the searchpath */
871 for (s=fs_searchpaths ; s ; s=s->next)
872 {
873 if (s->pack)
874 continue;
875 if (prevpath == NULL)
876 return s->filename;
877 if (prevpath == prev)
878 return s->filename;
879 prev = s->filename;
880 }
881
882 return NULL;
883 }
884
885
886 /*
887 ================
888 FS_InitFilesystem
889 ================
890 */
FS_InitFilesystem(void)891 void FS_InitFilesystem (void)
892 {
893 Cmd_AddCommand ("path", FS_Path_f);
894 Cmd_AddCommand ("link", FS_Link_f);
895 Cmd_AddCommand ("dir", FS_Dir_f );
896
897 //
898 // basedir <path>
899 // allows the game to run from outside the data tree
900 //
901 fs_basedir = Cvar_Get ("basedir", ".", CVAR_NOSET);
902
903 //
904 // cddir <path>
905 // Logically concatenates the cddir after the basedir for
906 // allows the game to run from outside the data tree
907 //
908 fs_cddir = Cvar_Get ("cddir", "", CVAR_NOSET);
909 if (fs_cddir->string[0])
910 FS_AddGameDirectory (va("%s/"BASEDIRNAME, fs_cddir->string) );
911
912 //
913 // add baseq2 to search path
914 //
915 FS_AddGameDirectory (va("%s/"BASEDIRNAME, fs_basedir->string) );
916
917 //
918 // then add a '.quake2/baseq2' directory in home directory by default
919 //
920 FS_AddHomeAsGameDirectory(BASEDIRNAME);
921
922 // any set gamedirs will be freed up to here
923 fs_base_searchpaths = fs_searchpaths;
924
925 // check for game override
926 fs_gamedirvar = Cvar_Get ("game", "", CVAR_LATCH|CVAR_SERVERINFO);
927 if (fs_gamedirvar->string[0])
928 FS_SetGamedir (fs_gamedirvar->string);
929 }
930