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