Quake-2

Quake 2 GPL Source Release
Log | Files | Refs

files.c (17534B)


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