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("/usr/local/share/quake2/%s", dir));
684 FS_AddGameDirectory (va("/usr/local/lib/quake2lnx/%s", dir));
685 FS_AddGameDirectory (va("%s/%s", fs_basedir->string, dir) );
686 FS_AddHomeAsGameDirectory(dir);
687 }
688 }
689
690
691 /*
692 ================
693 FS_Link_f
694
695 Creates a filelink_t
696 ================
697 */
FS_Link_f(void)698 void FS_Link_f (void)
699 {
700 filelink_t *l, **prev;
701
702 if (Cmd_Argc() != 3)
703 {
704 Com_Printf ("USAGE: link <from> <to>\n");
705 return;
706 }
707
708 // see if the link already exists
709 prev = &fs_links;
710 for (l=fs_links ; l ; l=l->next)
711 {
712 if (!strcmp (l->from, Cmd_Argv(1)))
713 {
714 Z_Free (l->to);
715 if (!strlen(Cmd_Argv(2)))
716 { // delete it
717 *prev = l->next;
718 Z_Free (l->from);
719 Z_Free (l);
720 return;
721 }
722 l->to = CopyString (Cmd_Argv(2));
723 return;
724 }
725 prev = &l->next;
726 }
727
728 // create a new link
729 l = Z_Malloc(sizeof(*l));
730 l->next = fs_links;
731 fs_links = l;
732 l->from = CopyString(Cmd_Argv(1));
733 l->fromlength = strlen(l->from);
734 l->to = CopyString(Cmd_Argv(2));
735 }
736
737 /*
738 ** FS_ListFiles
739 */
FS_ListFiles(char * findname,int * numfiles,unsigned musthave,unsigned canthave)740 char **FS_ListFiles( char *findname, int *numfiles, unsigned musthave, unsigned canthave )
741 {
742 char *s;
743 int nfiles = 0;
744 char **list = 0;
745
746 s = Sys_FindFirst( findname, musthave, canthave );
747 while ( s )
748 {
749 if ( s[strlen(s)-1] != '.' )
750 nfiles++;
751 s = Sys_FindNext( musthave, canthave );
752 }
753 Sys_FindClose ();
754
755 if ( !nfiles )
756 return NULL;
757
758 nfiles++; // add space for a guard
759 *numfiles = nfiles;
760
761 list = malloc( sizeof( char * ) * nfiles );
762 memset( list, 0, sizeof( char * ) * nfiles );
763
764 s = Sys_FindFirst( findname, musthave, canthave );
765 nfiles = 0;
766 while ( s )
767 {
768 if ( s[strlen(s)-1] != '.' )
769 {
770 list[nfiles] = strdup( s );
771 #ifdef _WIN32
772 strlwr( list[nfiles] );
773 #endif
774 nfiles++;
775 }
776 s = Sys_FindNext( musthave, canthave );
777 }
778 Sys_FindClose ();
779
780 return list;
781 }
782
783 /*
784 ** FS_Dir_f
785 */
FS_Dir_f(void)786 void FS_Dir_f( void )
787 {
788 char *path = NULL;
789 char findname[1024];
790 char wildcard[1024] = "*.*";
791 char **dirnames;
792 int ndirs;
793
794 if ( Cmd_Argc() != 1 )
795 {
796 strcpy( wildcard, Cmd_Argv( 1 ) );
797 }
798
799 while ( ( path = FS_NextPath( path ) ) != NULL )
800 {
801 char *tmp = findname;
802
803 Com_sprintf( findname, sizeof(findname), "%s/%s", path, wildcard );
804
805 while ( *tmp != 0 )
806 {
807 if ( *tmp == '\\' )
808 *tmp = '/';
809 tmp++;
810 }
811 Com_Printf( "Directory of %s\n", findname );
812 Com_Printf( "----\n" );
813
814 if ( ( dirnames = FS_ListFiles( findname, &ndirs, 0, 0 ) ) != 0 )
815 {
816 int i;
817
818 for ( i = 0; i < ndirs-1; i++ )
819 {
820 if ( strrchr( dirnames[i], '/' ) )
821 Com_Printf( "%s\n", strrchr( dirnames[i], '/' ) + 1 );
822 else
823 Com_Printf( "%s\n", dirnames[i] );
824
825 free( dirnames[i] );
826 }
827 free( dirnames );
828 }
829 Com_Printf( "\n" );
830 };
831 }
832
833 /*
834 ============
835 FS_Path_f
836
837 ============
838 */
FS_Path_f(void)839 void FS_Path_f (void)
840 {
841 searchpath_t *s;
842 filelink_t *l;
843
844 Com_Printf ("Current search path:\n");
845 for (s=fs_searchpaths ; s ; s=s->next)
846 {
847 if (s == fs_base_searchpaths)
848 Com_Printf ("----------\n");
849 if (s->pack)
850 Com_Printf ("%s (%i files)\n", s->pack->filename, s->pack->numfiles);
851 else
852 Com_Printf ("%s\n", s->filename);
853 }
854
855 Com_Printf ("\nLinks:\n");
856 for (l=fs_links ; l ; l=l->next)
857 Com_Printf ("%s : %s\n", l->from, l->to);
858 }
859
860 /*
861 ================
862 FS_NextPath
863
864 Allows enumerating all of the directories in the search path
865 ================
866 */
FS_NextPath(char * prevpath)867 char *FS_NextPath (char *prevpath)
868 {
869 searchpath_t *s;
870 char *prev;
871
872 prev = NULL; /* fs_gamedir is the first directory in the searchpath */
873 for (s=fs_searchpaths ; s ; s=s->next)
874 {
875 if (s->pack)
876 continue;
877 if (prevpath == NULL)
878 return s->filename;
879 if (prevpath == prev)
880 return s->filename;
881 prev = s->filename;
882 }
883
884 return NULL;
885 }
886
887
888 /*
889 ================
890 FS_InitFilesystem
891 ================
892 */
FS_InitFilesystem(void)893 void FS_InitFilesystem (void)
894 {
895 Cmd_AddCommand ("path", FS_Path_f);
896 Cmd_AddCommand ("link", FS_Link_f);
897 Cmd_AddCommand ("dir", FS_Dir_f );
898
899 //
900 // basedir <path>
901 // allows the game to run from outside the data tree
902 //
903 fs_basedir = Cvar_Get ("basedir", ".", CVAR_NOSET);
904
905 //
906 // cddir <path>
907 // Logically concatenates the cddir after the basedir for
908 // allows the game to run from outside the data tree
909 //
910 fs_cddir = Cvar_Get ("cddir", "", CVAR_NOSET);
911 if (fs_cddir->string[0])
912 FS_AddGameDirectory (va("%s/"BASEDIRNAME, fs_cddir->string) );
913
914 //
915 // add baseq2 to search path
916 //
917 FS_AddGameDirectory ("/usr/local/share/quake2/"BASEDIRNAME);
918 FS_AddGameDirectory ("/usr/local/lib/quake2lnx/"BASEDIRNAME);
919 FS_AddGameDirectory (va("%s/"BASEDIRNAME, fs_basedir->string) );
920
921 //
922 // then add a '.quake2/baseq2' directory in home directory by default
923 //
924 FS_AddHomeAsGameDirectory(BASEDIRNAME);
925
926 // any set gamedirs will be freed up to here
927 fs_base_searchpaths = fs_searchpaths;
928
929 // check for game override
930 fs_gamedirvar = Cvar_Get ("game", "", CVAR_LATCH|CVAR_SERVERINFO);
931 if (fs_gamedirvar->string[0])
932 FS_SetGamedir (fs_gamedirvar->string);
933 }
934