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