1 /*
2 This program is free software; you can redistribute it and/or
3 modify it under the terms of the GNU General Public License
4 as published by the Free Software Foundation; either version 2
5 of the License, or (at your option) any later version.
6 
7 This program is distributed in the hope that it will be useful,
8 but WITHOUT ANY WARRANTY; without even the implied warranty of
9 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
10 
11 See the GNU General Public License for more details.
12 
13 You should have received a copy of the GNU General Public License
14 along with this program; if not, write to the Free Software
15 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
16 
17 
18 */
19 
20 #include "qwsvdef.h"
21 
22 
23 /*
24 All of Quake's data access is through a hierchal file system, but the contents
25 of the file system can be transparently merged from several sources.
26 
27 The "base directory" is the path to the directory holding the quake.exe and
28 all game directories.  The sys_* files pass this to host_init in
29 quakeparms_t->basedir.  This can be overridden with the "-basedir" command
30 line parm to allow code debugging in a different directory.  The base
31 directory is only used during filesystem initialization.
32 
33 The "game directory" is the first tree on the search path and directory that
34 all generated files (savegames, screenshots, demos, config files) will be
35 saved to.  This can be overridden with the "-game" command line parameter.
36 The game directory can never be changed while quake is executing.  This is a
37 precaution against having a malicious server instruct clients to write files
38 over areas they shouldn't.
39 */
40 
41 typedef enum
42 {
43 	FS_LOAD_NONE     = 1,
44 	FS_LOAD_FILE_PAK = 2,
45 	FS_LOAD_FILE_ALL = FS_LOAD_FILE_PAK
46 } FS_Load_File_Types;
47 
48 /*
49 =============================================================================
50 
51 VARIABLES
52 
53 =============================================================================
54 */
55 
56 // WARNING: if you add some FS related global variable
57 // then made appropriate change to FS_ShutDown() too, if required.
58 
59 static char		fs_basedir[MAX_OSPATH];		// c:/quake
60 
61 char			fs_gamedir[MAX_OSPATH];		// c:/quake/qw
62 static char		fs_gamedirfile[MAX_QPATH];	// qw tf ctf and etc. In other words single dir name without path
63 
64 static searchpath_t	*fs_searchpaths = NULL;
65 static searchpath_t	*fs_base_searchpaths = NULL;	// without gamedirs
66 
67 hashtable_t		*filesystemhash = NULL;
68 qbool			filesystemchanged = true;
69 int				fs_hash_dups = 0;
70 int				fs_hash_files = 0;
71 
72 cvar_t fs_cache = {"fs_cache", "1"};
73 
74 /*
75 =============================================================================
76 
77 FORWARD DEFINITION
78 
79 =============================================================================
80 */
81 
82 static searchpath_t *FS_AddPathHandle(char *probablepath, searchpathfuncs_t *funcs, void *handle, qbool copyprotect, qbool istemporary, FS_Load_File_Types loadstuff);
83 
84 /*
85 =============================================================================
86 
87 COMMANDS
88 
89 =============================================================================
90 */
91 
92 /*
93 ============
94 FS_Path_f
95 
96 ============
97 */
FS_Path_f(void)98 static void FS_Path_f (void)
99 {
100 	searchpath_t	*search;
101 
102 	Con_Printf ("Current search path:\n");
103 
104 	for (search = fs_searchpaths; search ; search = search->next)
105 	{
106 		if (search == fs_base_searchpaths)
107 			Con_Printf ("----------\n");
108 
109 		search->funcs->PrintPath(search->handle);
110 	}
111 }
112 
113 /*
114 =============================================================================
115 
116 FUNCTIONS
117 
118 =============================================================================
119 */
120 
121 /*
122 ================
123 FS_GetCleanPath
124 
125 ================
126 */
FS_GetCleanPath(const char * pattern,char * outbuf,int outlen)127 static const char *FS_GetCleanPath(const char *pattern, char *outbuf, int outlen)
128 {
129 	char *s;
130 
131 	if (strchr(pattern, '\\'))
132 	{
133 		strlcpy(outbuf, pattern, outlen);
134 		pattern = outbuf;
135 
136 		Con_Printf("Warning: \\ characters in filename %s\n", pattern);
137 
138 		for (s = (char*)pattern; (s = strchr(s, '\\')); s++)
139 			*s = '/';
140 	}
141 
142 	if (*pattern == '/' || strstr(pattern, "..") || strstr(pattern, ":"))
143 		Con_Printf("Error: absolute path in filename %s\n", pattern);
144 	else
145 		return pattern;
146 
147 	return NULL;
148 }
149 
150 /*
151 ============
152 FS_FlushFSHash
153 
154 Flush FS hash and mark FS as changed,
155 so FS_FLocateFile() will be forced to call FS_RebuildFSHash().
156 ============
157 */
FS_FlushFSHash(void)158 void FS_FlushFSHash(void)
159 {
160 	if (filesystemhash)
161 	{
162 		Hash_Flush(filesystemhash);
163 	}
164 
165 	filesystemchanged = true;
166 }
167 
168 /*
169 ============
170 FS_RebuildFSHash
171 
172 Rebuild FS hash.
173 ============
174 */
FS_RebuildFSHash(void)175 static void FS_RebuildFSHash(void)
176 {
177 	searchpath_t	*search;
178 	if (!filesystemhash)
179 	{
180 		filesystemhash = Hash_InitTable(1024);
181 	}
182 	else
183 	{
184 		FS_FlushFSHash();
185 	}
186 
187 	fs_hash_dups = 0;
188 	fs_hash_files = 0;
189 
190 	for (search = fs_searchpaths ; search ; search = search->next)
191 	{
192 		search->funcs->BuildHash(search->handle);
193 	}
194 
195 	filesystemchanged = false;
196 
197 	Con_DPrintf("FS_RebuildFSHash: %i unique files, %i duplicates\n", fs_hash_files, fs_hash_dups);
198 }
199 
200 /*
201 ============
202 FS_FLocateFile
203 
204 Finds the file in the search path.
205 Look FSLF_ReturnType_e definition so you know that it returns.
206 ============
207 */
FS_FLocateFile(const char * filename,FSLF_ReturnType_e returntype,flocation_t * loc)208 int FS_FLocateFile(const char *filename, FSLF_ReturnType_e returntype, flocation_t *loc)
209 {
210 	int				depth = 0;
211 	int				len;
212 	searchpath_t	*search;
213 	char			cleanpath[MAX_OSPATH];
214 	void			*pf = NULL;
215 
216 	filename = FS_GetCleanPath(filename, cleanpath, sizeof(cleanpath));
217 	if (!filename)
218 		goto fail;
219 
220  	if (fs_cache.value)
221 	{
222 		if (filesystemchanged)
223 			FS_RebuildFSHash();
224 		pf = Hash_GetInsensitive(filesystemhash, filename);
225 		if (!pf)
226 			goto fail;
227 	}
228 
229 	//
230 	// search through the path, one element at a time.
231 	//
232 	for (search = fs_searchpaths ; search ; search = search->next)
233 	{
234 		if (search->funcs->FindFile(search->handle, loc, filename, pf))
235 		{
236 			if (loc)
237 			{
238 				loc->search = search;
239 				len = loc->len;
240 			}
241 			else
242 			{
243 				len = 0;
244 			}
245 
246 			goto out;
247 		}
248 
249 		depth += (search->funcs == &osfilefuncs || returntype == FSLFRT_DEPTH_ANYPATH);
250 	}
251 
252 fail:
253 	if (loc)
254 		loc->search = NULL;
255 	depth = 0x7fffffff; // NOTE: weird, we return it on fail in some cases, may cause mistakes by user.
256 	len = -1;
257 
258 out:
259 
260 /*	Debug printing removed
261  *	if (len>=0)
262 	{
263 		if (loc)
264 			Con_Printf("Found %s:%i\n", loc->rawname, loc->len);
265 		else
266 			Con_Printf("Found %s\n", filename);
267 	}
268 	else
269 		Con_Printf("Failed\n");
270 */
271 
272 	if (returntype == FSLFRT_IFFOUND)
273 		return len != -1;
274 	else if (returntype == FSLFRT_LENGTH)
275 		return len;
276 	else
277 		return depth;
278 }
279 
280 // internal struct, no point to expose it outside.
281 typedef struct
282 {
283 	searchpathfuncs_t *funcs;
284 	searchpath_t *parentpath;
285 	char *parentdesc;
286 } wildpaks_t;
287 
288 /*
289 ================
290 FS_AddWildDataFiles
291 
292 Add pack files masked by wildcards, e.g. '*.pak' .
293 That allow add not only packs named like pak0.pak pak1.pak but with random names too.
294 ================
295 */
FS_AddWildDataFiles(char * descriptor,int size,void * vparam)296 static int FS_AddWildDataFiles (char *descriptor, int size, void *vparam)
297 {
298 	wildpaks_t			*param = vparam;
299 	vfsfile_t			*vfs;
300 	searchpathfuncs_t	*funcs = param->funcs;
301 	searchpath_t		*search;
302 	void 				*pak;
303 	char				pakfile[MAX_OSPATH];
304 	flocation_t			loc = {0};
305 
306 	snprintf (pakfile, sizeof (pakfile), "%s%s", param->parentdesc, descriptor);
307 
308 	for (search = fs_searchpaths; search; search = search->next)
309 	{
310 		if (search->funcs != funcs)
311 			continue;
312 		if (!strcasecmp((char*)search->handle, pakfile))	//assumption: first member of structure is a char array
313 			return true; //already loaded (base paths?)
314 	}
315 
316 	search = param->parentpath;
317 
318 	if (!search->funcs->FindFile(search->handle, &loc, descriptor, NULL))
319 		return true;	//not found..
320 
321 	vfs = search->funcs->OpenVFS(search->handle, &loc, "rb");
322 	if (!vfs)
323 		return true;
324 	pak = funcs->OpenNew (vfs, pakfile);
325 	if (!pak)
326 	{
327 		VFS_CLOSE(vfs);
328 		return true;
329 	}
330 
331 	snprintf (pakfile, sizeof (pakfile), "%s%s/", param->parentdesc, descriptor);
332 	FS_AddPathHandle(pakfile, funcs, pak, true, false, FS_LOAD_FILE_ALL);
333 
334 	return true;
335 }
336 
337 /*
338 ================
339 FS_AddDataFiles
340 
341 Load pack files.
342 ================
343 */
FS_AddDataFiles(char * pathto,searchpath_t * parent,char * extension,searchpathfuncs_t * funcs)344 static void FS_AddDataFiles(char *pathto, searchpath_t *parent, char *extension, searchpathfuncs_t *funcs)
345 {
346 	int				i;
347 	char			pakfile[MAX_OSPATH];
348 	wildpaks_t		wp = {0};
349 
350 	// first load all the numbered pak files.
351 	for (i = 0; ; i++)
352 	{
353 		void		*handle;
354 		vfsfile_t	*vfs;
355 		flocation_t	loc = {0};
356 
357 		snprintf (pakfile, sizeof(pakfile), "pak%i.%s", i, extension);
358 		if (!parent->funcs->FindFile(parent->handle, &loc, pakfile, NULL))
359 			break;	//not found..
360 		snprintf (pakfile, sizeof(pakfile), "%spak%i.%s", pathto, i, extension);
361 		vfs = parent->funcs->OpenVFS(parent->handle, &loc, "rb");
362 		if (!vfs)
363 			break;
364 		handle = funcs->OpenNew (vfs, pakfile);
365 		if (!handle)
366 		{
367 			VFS_CLOSE(vfs);
368 			break;
369 		}
370 		snprintf (pakfile, sizeof(pakfile), "%spak%i.%s/", pathto, i, extension);
371 		FS_AddPathHandle(pakfile, funcs, handle, true, false, FS_LOAD_FILE_ALL);
372 	}
373 
374 	// now load the random ones.
375 	snprintf (pakfile, sizeof (pakfile), "*.%s", extension);
376 	wp.funcs = funcs;
377 	wp.parentdesc = pathto;
378 	wp.parentpath = parent;
379 	parent->funcs->EnumerateFiles(parent->handle, pakfile, FS_AddWildDataFiles, &wp);
380 }
381 
382 /*
383 ================
384 FS_AddPathHandle
385 
386 Adds searchpath and load pack files in it if requested.
387 ================
388 */
FS_AddPathHandle(char * probablepath,searchpathfuncs_t * funcs,void * handle,qbool copyprotect,qbool istemporary,FS_Load_File_Types loadstuff)389 static searchpath_t *FS_AddPathHandle(char *probablepath, searchpathfuncs_t *funcs, void *handle, qbool copyprotect, qbool istemporary, FS_Load_File_Types loadstuff)
390 {
391 	searchpath_t *search;
392 
393 	// allocate new search path and init it.
394 	search = (searchpath_t*)Q_malloc (sizeof(searchpath_t));
395 	search->copyprotected = copyprotect;
396 	search->istemporary = istemporary;
397 	search->handle = handle;
398 	search->funcs = funcs;
399 
400 	// link seach path in.
401 	search->next = fs_searchpaths;
402 	fs_searchpaths = search;
403 
404 	// mark file system is changed.
405 	filesystemchanged = true;
406 
407 	// add any data files too.
408 	if (loadstuff & FS_LOAD_FILE_PAK)
409 		FS_AddDataFiles(probablepath, search, "pak", &packfilefuncs);//q1/hl/h2/q2
410 
411 	return search;
412 }
413 
414 /*
415 ================
416 FS_AddGameDirectory
417 
418 Sets fs_gamedir, adds the directory to the head of the path,
419 then loads and adds pak0.pak pak1.pak ...
420 ================
421 */
FS_AddGameDirectory(char * dir,FS_Load_File_Types loadstuff)422 static void FS_AddGameDirectory (char *dir, FS_Load_File_Types loadstuff)
423 {
424 	char *p;
425 	searchpath_t *search;
426 
427 	if ((p = strrchr(dir, '/')))
428 		strlcpy (fs_gamedirfile, ++p, sizeof (fs_gamedirfile));
429 	else
430 		strlcpy (fs_gamedirfile, dir, sizeof (fs_gamedirfile));
431 
432 	strlcpy (fs_gamedir, dir, sizeof (fs_gamedir));
433 
434 	for (search = fs_searchpaths; search; search = search->next)
435 	{
436 		if (search->funcs != &osfilefuncs)
437 			continue; // ignore packs and such.
438 
439 		if (!strcasecmp(search->handle, fs_gamedir))
440 			return; //already loaded (base paths?)
441 	}
442 
443 	// add the directory to the search path
444 	FS_AddPathHandle (va("%s/", dir), &osfilefuncs, Q_strdup(dir), false, false, loadstuff);
445 }
446 
447 /*
448 ============
449 FS_SetGamedir
450 
451 Sets the gamedir and path to a different directory.
452 That is basically handy wrapper around FS_AddGameDirectory() for "gamedir" command.
453 ============
454 */
FS_SetGamedir(char * dir,qbool force)455 void FS_SetGamedir (char *dir, qbool force)
456 {
457 	if (strstr(dir, "..") || strstr(dir, "/") || strstr(dir, "\\") || strstr(dir, ":"))
458 	{
459 		Con_Printf ("Gamedir should be a single filename, not a path\n");
460 		return;
461 	}
462 
463 	if (!force && !strcmp(fs_gamedirfile, dir))
464 		return;		// Still the same, unless we forced.
465 
466 	// FIXME: do we need it? since it will be set in FS_AddGameDirectory().
467 	strlcpy (fs_gamedirfile, dir, sizeof(fs_gamedirfile));
468 
469 	// Free up any current game dir info.
470 	FS_FlushFSHash();
471 
472 	// free up any current game dir info
473 	while (fs_searchpaths != fs_base_searchpaths)
474 	{
475 		searchpath_t  *next;
476 
477 		fs_searchpaths->funcs->ClosePath(fs_searchpaths->handle);
478 		next = fs_searchpaths->next;
479 		Q_free (fs_searchpaths);
480 		fs_searchpaths = next;
481 	}
482 
483 	// mark file system is changed.
484 	filesystemchanged = true;
485 
486 	// Flush all data, so it will be forced to reload.
487 // well, mvdsv does not use cache anyway.
488 //	Cache_Flush ();
489 
490 	// FIXME: do we need it? since it will be set in FS_AddGameDirectory().
491 	snprintf (fs_gamedir, sizeof (fs_gamedir), "%s/%s", fs_basedir, dir);
492 
493 	FS_AddGameDirectory(va("%s/%s", fs_basedir, dir), FS_LOAD_FILE_ALL);
494 }
495 
496 
497 /*
498 ================
499 FS_InitModule
500 
501 Add commands and cvars.
502 ================
503 */
FS_InitModule(void)504 void FS_InitModule(void)
505 {
506 	Cmd_AddCommand ("path", FS_Path_f);
507 	Cvar_Register(&fs_cache);
508 }
509 
510 /*
511 ================
512 FS_Init
513 
514 ================
515 */
FS_InitEx(void)516 void FS_InitEx(void)
517 {
518 	int i;
519 	char *s;
520 
521 	FS_ShutDown();
522 
523 	if ((i = COM_CheckParm ("-basedir")) && i < COM_Argc() - 1)
524 	{
525 		// -basedir <path>
526 		// Overrides the system supplied base directory (under id1)
527 		strlcpy (fs_basedir, COM_Argv(i + 1), sizeof(fs_basedir));
528 	}
529  	else
530 	{
531 #if 0 // FIXME: made fs_basedir equal to cwd
532 		Sys_getcwd(fs_basedir, sizeof(fs_basedir) - 1);
533 #else
534 		strlcpy (fs_basedir, ".", sizeof(fs_basedir));
535 #endif
536 	}
537 
538 	// replace backslahes with slashes.
539 	for (s = fs_basedir; (s = strchr(s, '\\')); s++)
540 		*s = '/';
541 
542 	// remove terminating slash if any.
543 	i = (int)strlen(fs_basedir) - 1;
544 	if (i >= 0 && fs_basedir[i] == '/')
545 		fs_basedir[i] = 0;
546 
547 	// start up with id1 by default
548 	FS_AddGameDirectory(va("%s/%s", fs_basedir, "id1"),     FS_LOAD_FILE_ALL);
549 	FS_AddGameDirectory(va("%s/%s", fs_basedir, "qw"),      FS_LOAD_FILE_ALL);
550 
551 	// any set gamedirs will be freed up to here
552 	fs_base_searchpaths = fs_searchpaths;
553 
554 	// the user might want to override default game directory
555 	if (!(i = COM_CheckParm ("-game")))
556 		i = COM_CheckParm ("+gamedir");
557 	if (i && i < COM_Argc() - 1)
558 	{
559 		FS_SetGamedir (COM_Argv(i + 1), true);
560 		// FIXME: move in FS_SetGamedir() instead!!!
561 		Info_SetValueForStarKey (svs.info, "*gamedir", COM_Argv(i + 1), MAX_SERVERINFO_STRING);
562 	}
563 }
564 
565 /*
566 ================
567 FS_Init
568 
569 ================
570 */
FS_Init(void)571 void FS_Init( void )
572 {
573 	FS_InitModule(); // init commands and variables.
574 	FS_InitEx();
575 }
576 
577 /*
578 ================
579 FS_ShutDown
580 
581 ================
582 */
FS_ShutDown(void)583 void FS_ShutDown( void )
584 {
585 	// free data
586 	while (fs_searchpaths)
587 	{
588 		searchpath_t  *next = fs_searchpaths->next;
589 		Q_free (fs_searchpaths); // FIXME: close handles and such!!!
590 		fs_searchpaths = next;
591 	}
592 
593 	// flush all data, so it will be forced to reload
594 // well, mvdsv does not use cache anyway.
595 //	Cache_Flush ();
596 
597 	// reset globals
598 
599 	fs_base_searchpaths = fs_searchpaths = NULL;
600 
601 	fs_gamedir[0]		= 0;
602 	fs_gamedirfile[0]	= 0;
603 
604 	fs_basedir[0]		= 0;
605 
606 	// FIXME:
607 //hashtable_t		*filesystemhash = NULL;
608 //qbool				filesystemchanged = true;
609 //int				fs_hash_dups = 0;
610 //int				fs_hash_files = 0;
611 }
612 
613 /*
614 ================
615 FS_OpenVFS
616 
617 This should be how all files are opened.
618 ================
619 */
FS_OpenVFS(const char * filename,char * mode,relativeto_t relativeto)620 vfsfile_t *FS_OpenVFS(const char *filename, char *mode, relativeto_t relativeto)
621 {
622 	flocation_t loc = {0};
623 	vfsfile_t *vfs = NULL;
624 	char cleanname[MAX_OSPATH];
625 	char fullname[MAX_OSPATH];
626 
627 	//blanket-bans
628 	filename = FS_GetCleanPath(filename, cleanname, sizeof(cleanname));
629 	if (!filename)
630 		return NULL;
631 
632 	if (strcmp(mode, "rb"))
633 		if (strcmp(mode, "wb"))
634 			if (strcmp(mode, "ab"))
635 				return NULL; //urm, unable to write/append
636 
637 	/* General opening of files */
638 	switch (relativeto)
639 	{
640 	case FS_NONE_OS: 	//OS access only, no paks, open file as is
641 		snprintf(fullname, sizeof(fullname), "%s", filename);
642 		return VFSOS_Open(fullname, mode);
643 
644 	case FS_GAME_OS:	//OS access only, no paks
645 		snprintf(fullname, sizeof(fullname), "%s/%s/%s", fs_basedir, fs_gamedirfile, filename);
646 		if (strchr(mode, 'w') || strchr(mode, 'a'))
647 			FS_CreatePath(fullname); // FIXME: It should be moved to VFSOS_Open() itself?
648 		return VFSOS_Open(fullname, mode);
649 
650 	case FS_GAME:
651 //		snprintf(fullname, sizeof(fullname), "%s/%s/%s", fs_basedir, fs_gamedirfile, filename);
652 
653 		// That an error attempt to write with FS_GAME, since file can be in pack file. redirect.
654 		if (strchr(mode, 'w') || strchr(mode, 'a'))
655 			return FS_OpenVFS(filename, mode, FS_GAME_OS);
656 
657 		// Search on path, try to open if found.
658 		if (FS_FLocateFile(filename, FSLFRT_IFFOUND, &loc))
659 			return loc.search->funcs->OpenVFS(loc.search->handle, &loc, mode);
660 
661 		return NULL;
662 
663 	case FS_BASE_OS:	//OS access only, no paks
664 		snprintf(fullname, sizeof(fullname), "%s/%s", fs_basedir, filename);
665 		return VFSOS_Open(fullname, mode);
666 
667 	case FS_ANY:
668 		// That an error attempt to write with FS_ANY, since file can be in pack file.
669 		vfs = FS_OpenVFS(filename, mode, FS_GAME);
670 		if (vfs)
671 			return vfs;
672 
673 		vfs = FS_OpenVFS(filename, mode, FS_NONE_OS);
674 		if (vfs)
675 			return vfs;
676 
677 		return NULL;
678 
679 	default:
680 		Sys_Error("FS_OpenVFS: Bad relative path (%i)", relativeto);
681 		break;
682 	}
683 
684 	return NULL;
685 }
686 
687 //=======================================================================
688 
VFS_CHECKCALL(struct vfsfile_s * vf,void * fld,char * emsg)689 void VFS_CHECKCALL (struct vfsfile_s *vf, void *fld, char *emsg)
690 {
691 	if (!fld)
692 		Sys_Error("%s", emsg);
693 }
694 
VFS_CLOSE(struct vfsfile_s * vf)695 void VFS_CLOSE (struct vfsfile_s *vf)
696 {
697 	assert(vf);
698 	VFS_CHECKCALL(vf, vf->Close, "VFS_CLOSE");
699 	vf->Close(vf);
700 }
701 
VFS_TELL(struct vfsfile_s * vf)702 unsigned long VFS_TELL (struct vfsfile_s *vf)
703 {
704 	assert(vf);
705 	VFS_CHECKCALL(vf, vf->Tell, "VFS_TELL");
706 	return vf->Tell(vf);
707 }
708 
VFS_GETLEN(struct vfsfile_s * vf)709 unsigned long VFS_GETLEN (struct vfsfile_s *vf)
710 {
711 	assert(vf);
712 	VFS_CHECKCALL(vf, vf->GetLen, "VFS_GETLEN");
713 	return vf->GetLen(vf);
714 }
715 
716 /**
717  * VFS_SEEK() reposition a stream
718  * If whence is set to SEEK_SET, SEEK_CUR, or SEEK_END, the offset  is
719  * relative to the  start of the file, the current position indicator, or
720  * end-of-file, respectively.
721  * Return Value
722  * Upon successful completion, VFS_SEEK(), returns 0.
723  * Otherwise, -1 is returned
724  */
VFS_SEEK(struct vfsfile_s * vf,unsigned long pos,int whence)725 int VFS_SEEK (struct vfsfile_s *vf, unsigned long pos, int whence)
726 {
727 	assert(vf);
728 	VFS_CHECKCALL(vf, vf->Seek, "VFS_SEEK");
729 	return vf->Seek(vf, pos, whence);
730 }
731 
VFS_READ(struct vfsfile_s * vf,void * buffer,int bytestoread,vfserrno_t * err)732 int VFS_READ (struct vfsfile_s *vf, void *buffer, int bytestoread, vfserrno_t *err)
733 {
734 	assert(vf);
735 	VFS_CHECKCALL(vf, vf->ReadBytes, "VFS_READ");
736 	return vf->ReadBytes(vf, buffer, bytestoread, err);
737 }
738 
VFS_WRITE(struct vfsfile_s * vf,const void * buffer,int bytestowrite)739 int VFS_WRITE (struct vfsfile_s *vf, const void *buffer, int bytestowrite)
740 {
741 	assert(vf);
742 	VFS_CHECKCALL(vf, vf->WriteBytes, "VFS_WRITE");
743 	return vf->WriteBytes(vf, buffer, bytestowrite);
744 }
745 
VFS_FLUSH(struct vfsfile_s * vf)746 void VFS_FLUSH (struct vfsfile_s *vf)
747 {
748 	assert(vf);
749 	if(vf->Flush)
750 		vf->Flush(vf);
751 }
752 
753 // return null terminated string
VFS_GETS(struct vfsfile_s * vf,char * buffer,int buflen)754 char *VFS_GETS(struct vfsfile_s *vf, char *buffer, int buflen)
755 {
756 	char in;
757 	char *out = buffer;
758 	int len = buflen-1;
759 
760 	assert(vf);
761 	VFS_CHECKCALL(vf, vf->ReadBytes, "VFS_GETS");
762 
763 //	if (len == 0)
764 //		return NULL;
765 
766 // FIXME: I am not sure how to handle this better
767 	if (len <= 0)
768 		Sys_Error("VFS_GETS: len <= 0");
769 
770 	while (len > 0)
771 	{
772 		if (!VFS_READ(vf, &in, 1, NULL))
773 		{
774 			if (len == buflen-1)
775 				return NULL;
776 			*out = '\0';
777 			return buffer;
778 		}
779 		if (in == '\n')
780 			break;
781 		*out++ = in;
782 		len--;
783 	}
784 	*out = '\0';
785 
786 	return buffer;
787 }
788 
VFS_COPYPROTECTED(struct vfsfile_s * vf)789 qbool VFS_COPYPROTECTED(struct vfsfile_s *vf)
790 {
791 	assert(vf);
792 	return vf->copyprotected;
793 }
794 
795 /*
796 =============================================================================
797 
798 LEGACY FUNCTIONS
799 
800 =============================================================================
801 */
802 
803 /*
804 ================
805 FS_FileLength
806 
807 ================
808 */
FS_FileLength(FILE * f)809 long FS_FileLength (FILE *f)
810 {
811 	long pos, end;
812 
813 	pos = ftell (f);
814 	fseek (f, 0, SEEK_END);
815 	end = ftell (f);
816 	fseek (f, pos, SEEK_SET);
817 
818 	return end;
819 }
820 
821 /*
822 ============
823 FS_FileBase
824 
825 ============
826 */
FS_FileBase(char * in,char * out)827 void FS_FileBase (char *in, char *out)
828 {
829 	char *begin, *end;
830 	int len;
831 
832 	if (!(end = strrchr (in, '.')))
833 		end = in + strlen (in);
834 
835 	if (!(begin = strchr (in, '/')))
836 		begin = in;
837 	else
838 		begin++;
839 
840 	len = end - begin + 1;
841 	if (len < 1)
842 		strlcpy (out, "?model?", 8);
843 	else
844 		strlcpy (out, begin, min (len, MAX_OSPATH));
845 }
846 
847 /*
848 ============
849 FS_WriteFile
850 
851 The filename will be prefixed by the current game directory
852 ============
853 */
FS_WriteFile(char * filename,void * data,int len)854 void FS_WriteFile (char *filename, void *data, int len)
855 {
856 	FILE *f;
857 	char name[MAX_OSPATH];
858 
859 	snprintf (name, MAX_OSPATH, "%s/%s", fs_gamedir, filename);
860 
861 	f = fopen (name, "wb");
862 	if (!f)
863 	{
864 		Sys_mkdir (fs_gamedir);
865 		f = fopen (name, "wb");
866 		if (!f)
867 			Sys_Error ("Error opening %s", filename);
868 	}
869 
870 	Sys_Printf ("FS_WriteFile: %s\n", name);
871 	fwrite (data, 1, len, f);
872 	fclose (f);
873 }
874 
875 
876 /*
877 ============
878 FS_CreatePath
879 
880 Only used for CopyFile and download
881 ============
882 */
FS_CreatePath(char * path)883 void FS_CreatePath(char *path)
884 {
885 	char *s, save;
886 
887 	if (!*path)
888 		return;
889 
890 	for (s = path + 1; *s; s++)
891 	{
892 #ifdef _WIN32
893 		if (*s == '/' || *s == '\\')
894 		{
895 #else
896 		if (*s == '/')
897 		{
898 #endif
899 			save = *s;
900 			*s = 0;
901 			Sys_mkdir(path);
902 			*s = save;
903 		}
904 	}
905 }
906 
907 /*
908 ============
909 FS_LoadFile
910 
911 Filename are relative to the quake directory.
912 Always appends a 0 byte to the loaded data.
913 ============
914 */
915 static byte *FS_LoadFile (char *path, void *allocator, int *file_length)
916 {
917 	vfsfile_t *f = NULL;
918 	vfserrno_t err;
919 	flocation_t loc = {0};
920 	byte *buf;
921 	int len;
922 
923 	// Look for it in the filesystem or pack files.
924     FS_FLocateFile(path, FSLFRT_LENGTH, &loc);
925 	if (loc.search)
926 	{
927 		f = loc.search->funcs->OpenVFS(loc.search->handle, &loc, "rb");
928 	}
929 
930 	if (!f)
931 		return NULL;
932 
933 	// FIXME: should we use loc.len instead?
934 	len = VFS_GETLEN(f);
935 	if (file_length)
936 		*file_length = len;
937 
938 	if (allocator == Hunk_AllocName)
939 	{
940 		char base[32];
941 		// Extract the filename base name for hunk tag.
942 		FS_FileBase (path, base);
943 		buf = (byte *) Hunk_AllocName (len + 1, base);
944 	}
945 	else if (allocator == Hunk_TempAlloc)
946 	{
947 		buf = (byte *) Hunk_TempAlloc (len + 1);
948 	}
949 #if 0
950 	else if (allocator == Q_malloc)
951 	{
952 		buf = Q_malloc (len + 1);
953 	}
954 #endif
955 	else
956 	{
957 		Sys_Error ("FS_LoadFile: bad usehunk\n");
958 		return NULL;
959 	}
960 
961 	if (!buf)
962 	{
963 		Sys_Error ("FS_LoadFile: not enough space for %s\n", path);
964 		return NULL;
965 	}
966 
967 	buf[len] = 0;
968 
969 	VFS_READ(f, buf, len, &err);
970 	VFS_CLOSE(f);
971 
972 	return buf;
973 }
974 
975 byte *FS_LoadHunkFile (char *path, int *len)
976 {
977 	return FS_LoadFile (path, Hunk_AllocName, len);
978 }
979 
980 byte *FS_LoadTempFile (char *path, int *len)
981 {
982 	return FS_LoadFile (path, Hunk_TempAlloc, len);
983 }
984 
985 /*
986 ============
987 FS_NextPath
988 
989 Iterate along searchpaths (no packs).
990 ============
991 */
992 char *FS_NextPath (char *prevpath)
993 {
994 	searchpath_t	*s;
995 	char			*prev;
996 
997 	if (!prevpath)
998 		return fs_gamedir;
999 
1000 	prev = fs_gamedir;
1001 	for (s = fs_searchpaths; s ; s = s->next)
1002 	{
1003 		if (s->funcs != &osfilefuncs)
1004 			continue;
1005 
1006 		if (prevpath == prev)
1007 			return s->handle;
1008 		prev = s->handle;
1009 	}
1010 
1011 	return NULL;
1012 }
1013 
1014 /*
1015 ===========
1016 FS_UnsafeFilename
1017 
1018 Returns true if user-specified path is unsafe
1019 ===========
1020 */
1021 qbool FS_UnsafeFilename(const char* fileName)
1022 {
1023 	return !fileName ||
1024 		!*fileName || // invalid name.
1025 		fileName[1] == ':' ||	// dos filename absolute path specified - reject.
1026 		*fileName == '\\' ||
1027 		*fileName == '/' ||	// absolute path was given - reject.
1028 		strstr(fileName, "..");
1029 }
1030 
1031