1 /*
2 	quakefs.c
3 
4 	virtual filesystem functions
5 
6 	Copyright (C) 1996-1997  Id Software, Inc.
7 
8 	This program is free software; you can redistribute it and/or
9 	modify it under the terms of the GNU General Public License
10 	as published by the Free Software Foundation; either version 2
11 	of the License, or (at your option) any later version.
12 
13 	This program is distributed in the hope that it will be useful,
14 	but WITHOUT ANY WARRANTY; without even the implied warranty of
15 	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
16 
17 	See the GNU General Public License for more details.
18 
19 	You should have received a copy of the GNU General Public License
20 	along with this program; if not, write to:
21 
22 		Free Software Foundation, Inc.
23 		59 Temple Place - Suite 330
24 		Boston, MA  02111-1307, USA
25 
26 */
27 #ifdef HAVE_CONFIG_H
28 # include "config.h"
29 #endif
30 
31 #ifdef HAVE_STRING_H
32 # include <string.h>
33 #endif
34 #ifdef HAVE_STRINGS_H
35 # include <strings.h>
36 #endif
37 #ifdef HAVE_UNISTD_H
38 # include <unistd.h>
39 #endif
40 #ifdef HAVE_IO_H
41 # include <io.h>
42 #endif
43 
44 #if defined(_WIN32) && defined(HAVE_MALLOC_H)
45 #include <malloc.h>
46 #endif
47 
48 #include <ctype.h>
49 #include <dirent.h>
50 #include <errno.h>
51 #include <fcntl.h>
52 #include <stdarg.h>
53 #include <stdlib.h>
54 #include <sys/stat.h>
55 
56 #ifdef HAVE_FNMATCH_H
57 # define model_t sunmodel_t
58 # include <fnmatch.h>
59 # undef model_t
60 #else
61 # ifdef _WIN32
62 # include "fnmatch.h"
63 # endif
64 #endif
65 
66 #ifdef HAVE_IO_H
67 # include <io.h>
68 #endif
69 
70 #ifdef _MSC_VER
71 # define _POSIX_
72 #endif
73 
74 #include <limits.h>
75 
76 #include "qfalloca.h"
77 
78 #include "QF/cmd.h"
79 #include "QF/cvar.h"
80 #include "QF/dstring.h"
81 #include "QF/hash.h"
82 #include "QF/mathlib.h"
83 #include "QF/pak.h"
84 #include "QF/pakfile.h"
85 #include "QF/qargs.h"
86 #include "QF/qendian.h"
87 #include "QF/qfplist.h"
88 #include "QF/qtypes.h"
89 #include "QF/quakefs.h"
90 #include "QF/sys.h"
91 #include "QF/va.h"
92 #include "QF/zone.h"
93 
94 #include "compat.h"
95 
96 #ifndef HAVE_FNMATCH_PROTO
97 int fnmatch (const char *__pattern, const char *__string, int __flags);
98 #endif
99 
100 /*
101 	All of Quake's data access is through a hierchical file system, but the
102 	contents of the file system can be transparently merged from several
103 	sources.
104 
105 	The "user directory" is the path to the directory holding the quake.exe
106 	and all game directories.  This can be overridden with the "fs_sharepath"
107 	and "fs_userpath" cvars to allow code debugging in a different directory.
108 	The base directory is used only during filesystem initialization.
109 
110 	The "game directory" is the first tree on the search path and directory
111 	that all generated files (savegames, screenshots, demos, config files)
112 	will be saved to.  This can be overridden with the "-game" command line
113 	parameter.  The game directory can never be changed while quake is
114 	executing.  This is a precacution against having a malicious server
115 	instruct clients to write files over areas they shouldn't.
116 
117 	The "cache directory" is used only during development to save network
118 	bandwidth, especially over ISDN / T1 lines.  If there is a cache directory
119 	specified, when a file is found by the normal search path, it will be
120 	mirrored into the cache directory, then opened there.
121 */
122 
123 // QUAKE FILESYSTEM
124 
125 static cvar_t *fs_userpath;
126 static cvar_t *fs_sharepath;
127 static cvar_t *fs_dirconf;
128 
129 VISIBLE const char *qfs_userpath;
130 
131 VISIBLE int qfs_filesize;
132 
133 typedef struct searchpath_s {
134 	char       *filename;
135 	struct pack_s *pack;	// only one of filename / pack will be used
136 	struct searchpath_s *next;
137 } searchpath_t;
138 
139 static searchpath_t *qfs_searchpaths;
140 
141 //QFS
142 
143 typedef struct qfs_var_s {
144 	char       *var;
145 	char       *val;
146 } qfs_var_t;
147 
148 static void qfs_add_gamedir (const char *dir);
149 
150 VISIBLE gamedir_t  *qfs_gamedir;
151 static plitem_t *qfs_gd_plist;
152 static const char *qfs_game = "";
153 static const char *qfs_default_dirconf =
154 	"{"
155 	"   QF = {"
156 	"       Path = \"QF\";"
157 	"   };"
158 	"	Quake = {"
159 	"		Inherit = QF;"
160 	"		Path = \"id1\";"
161 	"		GameCode = \"progs.dat\";"
162 	"		HudType = \"id\";"
163 	"	};"
164 	"	QuakeWorld = {"
165 	"		Inherit = (Quake);"
166 	"		Path = \"qw\";"
167 	"		SkinPath = \"${path}/skins\";"
168 	"		GameCode = \"qwprogs.dat\";"
169 	"		HudType = \"id\";"
170 	"	};"
171 	"	\"Hipnotic\" = {"
172 	"		Inherit = (Quake);"
173 	"		Path = \"hipnotic\";"
174 	"		HudType = \"hipnotic\";"
175 	"	};"
176 	"	\"Rogue\" = {"
177 	"		Inherit = (Quake);"
178 	"		Path = \"rogue\";"
179 	"		HudType = \"rogue\";"
180 	"	};"
181 	"	\"qw:qw\" = {"
182 	"		Inherit = (QuakeWorld);"
183 	"	};"
184 	"	\"qw:*\" = {"
185 	"		Inherit = (QuakeWorld);"
186 	"		Path = \"$gamedir\";"
187 	"	};"
188 	"	\"nq:*\" = {"
189 	"		Inherit = (Quake);"
190 	"		Path = \"$gamedir\";"
191 	"	};"
192 	"	\"hipnotic:*\" = {"
193 	"		Inherit = (Hipnotic);"
194 	"		Path = \"$gamedir\";"
195 	"	};"
196 	"	\"rogue:*\" = {"
197 	"		Inherit = (Rogue);"
198 	"		Path = \"$gamedir\";"
199 	"	};"
200 	"	\"abyss\" = {"
201 	"		Inherit = (Quake);"
202 	"		Path = \"abyss\";"
203 	"	};"
204 	"	\"abyss:*\" = {"
205 	"		Inherit = (abyss);"
206 	"		Path = \"$gamedir\";"
207 	"	};"
208 	"}";
209 
210 
211 #define GAMEDIR_CALLBACK_CHUNK 16
212 static gamedir_callback_t **gamedir_callbacks;
213 static int num_gamedir_callbacks;
214 static int max_gamedir_callbacks;
215 
216 static const char *
qfs_var_get_key(const void * _v,void * unused)217 qfs_var_get_key (const void *_v, void *unused)
218 {
219 	return ((qfs_var_t *)_v)->var;
220 }
221 
222 static void
qfs_var_free(void * _v,void * unused)223 qfs_var_free (void *_v, void *unused)
224 {
225 	qfs_var_t  *v = (qfs_var_t *) _v;
226 	free (v->var);
227 	free (v->val);
228 	free (v);
229 }
230 
231 static hashtab_t *
qfs_new_vars(void)232 qfs_new_vars (void)
233 {
234 	return Hash_NewTable (61, qfs_var_get_key, qfs_var_free, 0);
235 }
236 
237 static void
qfs_set_var(hashtab_t * vars,const char * var,const char * val)238 qfs_set_var (hashtab_t *vars, const char *var, const char *val)
239 {
240 	qfs_var_t  *v = Hash_Find (vars, var);
241 
242 	if (!v) {
243 		v = malloc (sizeof (qfs_var_t));
244 		v->var = strdup (var);
245 		v->val = 0;
246 		Hash_Add (vars, v);
247 	}
248 	if (v->val)
249 		free (v->val);
250 	v->val = strdup (val);
251 }
252 
253 static inline int
qfs_isident(byte c)254 qfs_isident (byte c)
255 {
256 	return ((c >= 'a' && c <='z') || (c >= 'A' && c <='Z')
257 			|| (c >= '0' && c <= '9') || c == '_');
258 }
259 
260 static char *
qfs_var_subst(const char * string,hashtab_t * vars)261 qfs_var_subst (const char *string, hashtab_t *vars)
262 {
263 	dstring_t  *new = dstring_newstr ();
264 	const char *s = string;
265 	const char *e = s;
266 	const char *var;
267 	qfs_var_t  *sub;
268 
269 	while (1) {
270 		while (*e && *e != '$')
271 			e++;
272 		dstring_appendsubstr (new, s, (e - s));
273 		if (!*e++)
274 			break;
275 		if (*e == '$') {
276 			dstring_appendstr (new, "$");
277 			s = ++e;
278 		} else if (*e == '{') {
279 			s = e;
280 			while (*e && *e != '}')
281 				e++;
282 			if (!*e) {
283 				dstring_appendsubstr (new, s, (e - s));
284 				break;
285 			}
286 			var = va ("%.*s", (int) (e - s) - 1, s + 1);
287 			sub = Hash_Find (vars, var);
288 			if (sub)
289 				dstring_appendstr (new, sub->val);
290 			else
291 				dstring_appendsubstr (new, s - 1, (e - s) + 2);
292 			s = ++e;
293 		} else if (qfs_isident (*e)) {
294 			s = e;
295 			while (qfs_isident (*e))
296 				e++;
297 			var = va ("%.*s", (int) (e - s), s);
298 			sub = Hash_Find (vars, var);
299 			if (sub)
300 				dstring_appendstr (new, sub->val);
301 			else
302 				dstring_appendsubstr (new, s - 1, (e - s) + 1);
303 			s = e;
304 		} else {
305 			dstring_appendstr (new, "$");
306 			s = e;
307 		}
308 	}
309 	return dstring_freeze (new);
310 }
311 
312 static void
qfs_get_gd_params(plitem_t * gdpl,gamedir_t * gamedir,dstring_t * path,hashtab_t * vars)313 qfs_get_gd_params (plitem_t *gdpl, gamedir_t *gamedir, dstring_t *path,
314 				   hashtab_t *vars)
315 {
316 	plitem_t   *p;
317 	const char *ps;
318 
319 	if ((p = PL_ObjectForKey (gdpl, "Path")) && *(ps = PL_String (p))) {
320 		char       *str = qfs_var_subst (ps, vars);
321 
322 		qfs_set_var (vars, "path", str);
323 		if (path->str[0])
324 			dstring_appendstr (path, ":");
325 		dstring_appendstr (path, str);
326 		free (str);
327 	}
328 	if (!gamedir->gamecode && (p = PL_ObjectForKey (gdpl, "GameCode")))
329 		gamedir->gamecode = qfs_var_subst (PL_String (p), vars);
330 	if (!gamedir->hudtype && (p = PL_ObjectForKey (gdpl, "HudType")))
331 		gamedir->hudtype = qfs_var_subst (PL_String (p), vars);
332 	if (!gamedir->dir.skins && (p = PL_ObjectForKey (gdpl, "SkinPath")))
333 		gamedir->dir.skins = qfs_var_subst (PL_String (p), vars);
334 	if (!gamedir->dir.models && (p = PL_ObjectForKey (gdpl, "ModelPath")))
335 		gamedir->dir.models = qfs_var_subst (PL_String (p), vars);
336 	if (!gamedir->dir.sound && (p = PL_ObjectForKey (gdpl, "SoundPath")))
337 		gamedir->dir.sound = qfs_var_subst (PL_String (p), vars);
338 	if (!gamedir->dir.maps && (p = PL_ObjectForKey (gdpl, "MapPath")))
339 		gamedir->dir.maps = qfs_var_subst (PL_String (p), vars);
340 	if (!gamedir->dir.shots && (p = PL_ObjectForKey (gdpl, "ShotsPath")))
341 		gamedir->dir.shots = qfs_var_subst (PL_String (p), vars);
342 }
343 
344 static void
qfs_inherit(plitem_t * plist,plitem_t * gdpl,gamedir_t * gamedir,dstring_t * path,hashtab_t * dirs,hashtab_t * vars)345 qfs_inherit (plitem_t *plist, plitem_t *gdpl, gamedir_t *gamedir,
346 			 dstring_t *path, hashtab_t *dirs, hashtab_t *vars)
347 {
348 	plitem_t   *base_item;
349 
350 	if (!(base_item = PL_ObjectForKey (gdpl, "Inherit")))
351 		return;
352 	switch (PL_Type (base_item)) {
353 		case QFString:
354 			{
355 				const char *base = PL_String (base_item);
356 				if (Hash_Find (dirs, base))
357 					return;
358 				gdpl = PL_ObjectForKey (plist, base);
359 				if (!gdpl) {
360 					Sys_Printf ("base `%s' not found\n", base);
361 					return;
362 				}
363 				qfs_set_var (vars, "gamedir", base);
364 				Hash_Add (dirs, strdup (base));
365 				qfs_get_gd_params (gdpl, gamedir, path, vars);
366 				qfs_inherit (plist, gdpl, gamedir, path, dirs, vars);
367 			}
368 			break;
369 		case QFArray:
370 			{
371 				int         i, num_dirs;
372 				plitem_t   *basedir_item;
373 				const char *basedir;
374 
375 				num_dirs = PL_A_NumObjects (base_item);
376 				for (i = 0; i < num_dirs; i++) {
377 					basedir_item = PL_ObjectAtIndex (base_item, i);
378 					if (!basedir_item)
379 						continue;
380 					basedir = PL_String (basedir_item);
381 					if (!basedir || Hash_Find (dirs, basedir))
382 						continue;
383 					gdpl = PL_ObjectForKey (plist, basedir);
384 					if (!gdpl) {
385 						Sys_Printf ("base `%s' not found\n", basedir);
386 						continue;
387 					}
388 					qfs_set_var (vars, "gamedir", basedir);
389 					Hash_Add (dirs, strdup (basedir));
390 					qfs_get_gd_params (gdpl, gamedir, path, vars);
391 					qfs_inherit (plist, gdpl, gamedir, path, dirs, vars);
392 				}
393 			}
394 			break;
395 		default:
396 			break;
397 	}
398 }
399 
400 static int
qfs_compare(const void * a,const void * b)401 qfs_compare (const void *a, const void *b)
402 {
403 	return strcmp (*(const char **) a, *(const char **) b);
404 }
405 
406 static const char *
qfs_dir_get_key(const void * _k,void * unused)407 qfs_dir_get_key (const void *_k, void *unused)
408 {
409 	return _k;
410 }
411 
412 static void
qfs_dir_free(void * _k,void * unused)413 qfs_dir_free (void *_k, void *unused)
414 {
415 	free (_k);
416 }
417 
418 static plitem_t *
qfs_find_gamedir(const char * name,hashtab_t * dirs)419 qfs_find_gamedir (const char *name, hashtab_t *dirs)
420 {
421 	plitem_t   *gdpl = PL_ObjectForKey (qfs_gd_plist, name);
422 
423 	if (!gdpl) {
424 		plitem_t   *keys = PL_D_AllKeys (qfs_gd_plist);
425 		int         num_keys = PL_A_NumObjects (keys);
426 		const char **list = malloc (num_keys * sizeof (char *));
427 		int         i;
428 
429 		for (i = 0; i < num_keys; i++)
430 			list[i] = PL_String (PL_ObjectAtIndex (keys, i));
431 		qsort (list, num_keys, sizeof (const char *), qfs_compare);
432 
433 		while (i--) {
434 			if (!fnmatch (list[i], name, 0)) {
435 				gdpl = PL_ObjectForKey (qfs_gd_plist, list[i]);
436 				Hash_Add (dirs, strdup (list[i]));
437 				break;
438 			}
439 		}
440 		free (list);
441 		PL_Free (keys);
442 	}
443 	return gdpl;
444 }
445 
446 static void
qfs_process_path(const char * path,const char * gamedir)447 qfs_process_path (const char *path, const char *gamedir)
448 {
449 	const char *e = path + strlen (path);
450 	const char *s = e;
451 	dstring_t  *dir = dstring_new ();
452 
453 	while (s >= path) {
454 		while (s != path && s[-1] !=':')
455 			s--;
456 		if (s != e) {
457 			dsprintf (dir, "%.*s", (int) (e - s), s);
458 			qfs_add_gamedir (dir->str);
459 		}
460 		e = --s;
461 	}
462 	dstring_delete (dir);
463 }
464 
465 static void
qfs_build_gamedir(const char ** list)466 qfs_build_gamedir (const char **list)
467 {
468 	int         j;
469 	gamedir_t  *gamedir;
470 	plitem_t   *gdpl;
471 	dstring_t  *path;
472 	hashtab_t  *dirs = Hash_NewTable (31, qfs_dir_get_key, qfs_dir_free, 0);
473 	hashtab_t  *vars = qfs_new_vars ();
474 	const char *dir = 0;
475 
476 	qfs_set_var (vars, "game", qfs_game);
477 
478 	if (qfs_gamedir) {
479 		if (qfs_gamedir->name)
480 			free ((char *)qfs_gamedir->name);
481 		if (qfs_gamedir->gamedir)
482 			free ((char *)qfs_gamedir->gamedir);
483 		if (qfs_gamedir->path)
484 			free ((char *)qfs_gamedir->path);
485 		if (qfs_gamedir->gamecode)
486 			free ((char *)qfs_gamedir->gamecode);
487 		if (qfs_gamedir->dir.def)
488 			free ((char *)qfs_gamedir->dir.def);
489 		if (qfs_gamedir->dir.skins)
490 			free ((char *)qfs_gamedir->dir.skins);
491 		if (qfs_gamedir->dir.models)
492 			free ((char *)qfs_gamedir->dir.models);
493 		if (qfs_gamedir->dir.sound)
494 			free ((char *)qfs_gamedir->dir.sound);
495 		if (qfs_gamedir->dir.maps)
496 			free ((char *)qfs_gamedir->dir.maps);
497 		free (qfs_gamedir);
498 	}
499 
500 	while (qfs_searchpaths) {
501 		searchpath_t *next;
502 
503 		if (qfs_searchpaths->pack) {
504 			Qclose (qfs_searchpaths->pack->handle);
505 			free (qfs_searchpaths->pack->files);
506 			free (qfs_searchpaths->pack);
507 		}
508 		if (qfs_searchpaths->filename)
509 			free (qfs_searchpaths->filename);
510 
511 		next = qfs_searchpaths->next;
512 		free (qfs_searchpaths);
513 		qfs_searchpaths = next;
514 	}
515 
516 	for (j = 0; list[j]; j++)
517 		;
518 	gamedir = calloc (1, sizeof (gamedir_t));
519 	path = dstring_newstr ();
520 	while (j--) {
521 		const char *name = va ("%s:%s", qfs_game, dir = list[j]);
522 		if (Hash_Find (dirs, name))
523 			continue;
524 		gdpl = qfs_find_gamedir (name, dirs);
525 		if (!gdpl) {
526 			Sys_Printf ("gamedir `%s' not found\n", name);
527 			continue;
528 		}
529 		Hash_Add (dirs, strdup (name));
530 		if (!j) {
531 			gamedir->name = strdup (name);
532 			gamedir->gamedir = strdup (list[j]);
533 		}
534 		qfs_set_var (vars, "gamedir", dir);
535 		qfs_get_gd_params (gdpl, gamedir, path, vars);
536 		qfs_inherit (qfs_gd_plist, gdpl, gamedir, path, dirs, vars);
537 	}
538 	gamedir->path = path->str;
539 
540 	for (dir = gamedir->path; *dir && *dir != ':'; dir++)
541 		;
542 	gamedir->dir.def = nva ("%.*s", (int) (dir - gamedir->path),
543 							gamedir->path);
544 	if (!gamedir->hudtype)
545 		gamedir->hudtype = strdup ("id");
546 	if (!gamedir->dir.skins)
547 		gamedir->dir.skins = nva ("%s/skins", gamedir->dir.def);
548 	if (!gamedir->dir.models)
549 		gamedir->dir.models = nva ("%s/progs", gamedir->dir.def);
550 	if (!gamedir->dir.sound)
551 		gamedir->dir.sound = nva ("%s/sound", gamedir->dir.def);
552 	if (!gamedir->dir.maps)
553 		gamedir->dir.maps = nva ("%s/maps", gamedir->dir.def);
554 	if (!gamedir->dir.shots)
555 		gamedir->dir.shots = strdup ("QF");
556 
557 	qfs_gamedir = gamedir;
558 	Sys_MaskPrintf (SYS_FS, "%s\n", qfs_gamedir->name);
559 	Sys_MaskPrintf (SYS_FS, "    gamedir : %s\n", qfs_gamedir->gamedir);
560 	Sys_MaskPrintf (SYS_FS, "    path    : %s\n", qfs_gamedir->path);
561 	Sys_MaskPrintf (SYS_FS, "    gamecode: %s\n", qfs_gamedir->gamecode);
562 	Sys_MaskPrintf (SYS_FS, "    hudtype : %s\n", qfs_gamedir->hudtype);
563 	Sys_MaskPrintf (SYS_FS, "    def     : %s\n", qfs_gamedir->dir.def);
564 	Sys_MaskPrintf (SYS_FS, "    skins   : %s\n", qfs_gamedir->dir.skins);
565 	Sys_MaskPrintf (SYS_FS, "    models  : %s\n", qfs_gamedir->dir.models);
566 	Sys_MaskPrintf (SYS_FS, "    sound   : %s\n", qfs_gamedir->dir.sound);
567 	Sys_MaskPrintf (SYS_FS, "    maps    : %s\n", qfs_gamedir->dir.maps);
568 	qfs_process_path (qfs_gamedir->path, dir);
569 	free (path);
570 	Hash_DelTable (dirs);
571 	Hash_DelTable (vars);
572 }
573 
574 static void
qfs_load_config(void)575 qfs_load_config (void)
576 {
577 	QFile      *f = 0;
578 	int         len;
579 	char       *buf;
580 	char       *dirconf;
581 
582 	if (*fs_dirconf->string) {
583 		dirconf = Sys_ExpandSquiggle (fs_dirconf->string);
584 		if (!(f = Qopen (dirconf, "rt")))
585 			Sys_MaskPrintf (SYS_FS,
586 							"Could not load `%s', using builtin defaults\n",
587 							dirconf);
588 		free (dirconf);
589 	}
590 	if (!f)
591 		goto no_config;
592 
593 	len = Qfilesize (f);
594 	buf = malloc (len + 3); // +3 for { } and \0
595 
596 	Qread (f, buf + 1, len);
597 	Qclose (f);
598 
599 	// convert the config file to a plist dictionary
600 	buf[0] = '{';
601 	buf[len + 1] = '}';
602 	buf[len + 2] = 0;
603 	if (qfs_gd_plist)
604 		PL_Free (qfs_gd_plist);
605 	qfs_gd_plist = PL_GetPropertyList (buf);
606 	free (buf);
607 	if (qfs_gd_plist && PL_Type (qfs_gd_plist) == QFDictionary)
608 		return;		// done
609 	Sys_Printf ("not a dictionary\n");
610 no_config:
611 	if (qfs_gd_plist)
612 		PL_Free (qfs_gd_plist);
613 	qfs_gd_plist = PL_GetPropertyList (qfs_default_dirconf);
614 }
615 
616 /*
617 	qfs_contains_updir
618 
619 	Checks if a string contains an updir ('..'), either at the ends or
620 	surrounded by slashes ('/').  Doesn't check for leading slash.
621 	Assumes canonical (compressed) path.
622 */
623 static inline int
qfs_contains_updir(const char * path,int levels)624 qfs_contains_updir (const char *path, int levels)
625 {
626 	do {
627 		if (path[0] != '.' || path[1] != '.'
628 			|| (path[2] != '/' && path[2] != 0))
629 			return 0;
630 		if (!path[2])
631 			break;
632 		// first part of path is ../
633 		if (levels <= 0)
634 			return 1;
635 		path += 3;
636 	} while (levels-- > 0);
637 	return 0;
638 }
639 
640 static int
qfs_expand_path(dstring_t * full_path,const char * base,const char * path,int levels)641 qfs_expand_path (dstring_t *full_path, const char *base, const char *path,
642 				 int levels)
643 {
644 	const char *separator = "/";
645 	char       *cpath;
646 	int         len;
647 
648 	if (!base || !*base) {
649 		errno = EACCES;
650 		return -1;
651 	}
652 	cpath = QFS_CompressPath (path);
653 	if (qfs_contains_updir (cpath, levels)) {
654 		free (cpath);
655 		errno = EACCES;
656 		return -1;
657 	}
658 	if (*cpath == '/')
659 		separator = "";
660 	len = strlen (base);
661 	if (len && base[len - 1] == '/')
662 		len--;
663 	dsprintf (full_path, "%.*s%s%s", len, base, separator, cpath);
664 	free (cpath);
665 	return 0;
666 }
667 
668 static int
qfs_expand_userpath(dstring_t * full_path,const char * path)669 qfs_expand_userpath (dstring_t *full_path, const char *path)
670 {
671 	return qfs_expand_path (full_path, qfs_userpath, path, 0);
672 }
673 
674 VISIBLE char *
QFS_FileBase(const char * in)675 QFS_FileBase (const char *in)
676 {
677 	const char *base;
678 	const char *ext;
679 	int         len;
680 	char       *out;
681 
682 	base = QFS_SkipPath (in);
683 	ext = QFS_FileExtension (base);
684 	len = ext - base;
685 	out = malloc (len + 1);
686 	strncpy (out, base, len);
687 	out [len] = 0;
688 	return out;
689 }
690 
691 
692 static void
qfs_path_f(void)693 qfs_path_f (void)
694 {
695 	searchpath_t *s;
696 
697 	Sys_Printf ("Current search path:\n");
698 	for (s = qfs_searchpaths; s; s = s->next) {
699 		if (s->pack)
700 			Sys_Printf ("%s (%i files)\n", s->pack->filename,
701 						s->pack->numfiles);
702 		else
703 			Sys_Printf ("%s\n", s->filename);
704 	}
705 }
706 
707 VISIBLE void
QFS_WriteFile(const char * filename,const void * data,int len)708 QFS_WriteFile (const char *filename, const void *data, int len)
709 {
710 	QFile      *f;
711 
712 	f = QFS_WOpen (filename, 0);
713 	if (!f) {
714 		Sys_Error ("Error opening %s", filename);
715 	}
716 
717 	Qwrite (f, data, len);
718 	Qclose (f);
719 }
720 
721 static QFile *
qfs_openread(const char * path,int offs,int len,int zip)722 qfs_openread (const char *path, int offs, int len, int zip)
723 {
724 	QFile      *file;
725 
726 	if (offs < 0 || len < 0)
727 		file = Qopen (path, zip ? "rbz" : "rb");
728 	else
729 		file = Qsubopen (path, offs, len, zip);
730 
731 	if (!file) {
732 		Sys_Error ("Couldn't open %s", path);
733 		return 0;
734 	}
735 
736 	qfs_filesize = Qfilesize (file);
737 
738 	return file;
739 }
740 
741 VISIBLE char *
QFS_CompressPath(const char * pth)742 QFS_CompressPath (const char *pth)
743 {
744 	char       *p, *d;
745 	char       *path= malloc (strlen (pth) + 1);
746 
747 	for (d = path; *pth; d++, pth++) {
748 		if (*pth == '\\')
749 			*d = '/';
750 		else
751 			*d = *pth;
752 	}
753 	*d = 0;
754 
755 	p = path;
756 	while (*p) {
757 		if (p[0] == '.') {
758 			if (p[1] == '.') {
759 				if (p[2] == '/' || p[2] == 0) {
760 					d = p;
761 					if (d > path)
762 						d--;
763 					while (d > path && d[-1] != '/')
764 						d--;
765 					if (d == path
766 						&& d[0] == '.' && d[1] == '.'
767 						&& (d[2] == '/' || d[2] == '\0')) {
768 						p += 2 + (p[2] == '/');
769 						continue;
770 					}
771 					if (d[0] == '/'
772 						&& d[1] == '.' && d[2] == '.'
773 						&& (d[3] == '/' || d[3] == '\0')) {
774 						*p = 0;
775 						p += 2 + (p[2] == '/');
776 						continue;
777 					}
778 					p = p + 2 + (p[2] == '/');
779 					memmove (d, p, strlen (p) + 1);
780 					p = d;
781 					continue;
782 				}
783 			} else if (p[1] == '/') {
784 				memmove (p, p + 2, strlen (p + 2) + 1);
785 				continue;
786 			} else if (p[1] == 0) {
787 				p[0] = 0;
788 			}
789 		}
790 		while (*p && *p != '/')
791 			p++;
792 		if (*p == '/') {
793 			p++;
794 			// skip over multiple / (foo//bar -> foo/bar)
795 			for (d = p; *d == '/'; d++)
796 				;
797 			if (d != p)
798 				memmove (p, d, strlen (d) + 1);
799 		}
800 	}
801 	// strip any trailing /, but not if it's the root /
802 	if (--p > path && *p == '/')
803 		*p = 0;
804 
805 	return path;
806 }
807 
808 VISIBLE int file_from_pak; // global indicating file came from pack file ZOID
809 
810 /*
811 	QFS_FOpenFile
812 
813 	Finds the file in the search path.
814 	Sets qfs_filesize and one of handle or file
815 */
816 
817 static int
open_file(searchpath_t * search,const char * filename,QFile ** gzfile,dstring_t * foundname,int zip)818 open_file (searchpath_t *search, const char *filename, QFile **gzfile,
819 		   dstring_t *foundname, int zip)
820 {
821 	file_from_pak = 0;
822 
823 	// is the element a pak file?
824 	if (search->pack) {
825 		dpackfile_t *packfile;
826 
827 		packfile = pack_find_file (search->pack, filename);
828 		if (packfile) {
829 			Sys_MaskPrintf (SYS_FS_F, "PackFile: %s : %s\n",
830 							search->pack->filename, packfile->name);
831 			// open a new file on the pakfile
832 			if (foundname) {
833 				dstring_clearstr (foundname);
834 				dstring_appendstr (foundname, packfile->name);
835 			}
836 			*gzfile = qfs_openread (search->pack->filename, packfile->filepos,
837 									packfile->filelen, zip);
838 			file_from_pak = 1;
839 			return qfs_filesize;
840 		}
841 	} else {
842 		// check a file in the directory tree
843 		dstring_t  *netpath = dstring_new ();
844 
845 		if (qfs_expand_path (netpath, search->filename, filename, 1) == 0) {
846 			if (foundname) {
847 				dstring_clearstr (foundname);
848 				dstring_appendstr (foundname, filename);
849 			}
850 			if (Sys_FileTime (netpath->str) == -1) {
851 				dstring_delete (netpath);
852 				return -1;
853 			}
854 
855 			Sys_MaskPrintf (SYS_FS_F, "FindFile: %s\n", netpath->str);
856 
857 			*gzfile = qfs_openread (netpath->str, -1, -1, zip);
858 			dstring_delete (netpath);
859 			return qfs_filesize;
860 		}
861 		dstring_delete (netpath);
862 		return -1;
863 	}
864 
865 	return -1;
866 }
867 
868 VISIBLE int
_QFS_FOpenFile(const char * filename,QFile ** gzfile,dstring_t * foundname,int zip)869 _QFS_FOpenFile (const char *filename, QFile **gzfile,
870 				dstring_t *foundname, int zip)
871 {
872 	searchpath_t *search;
873 	char       *path;
874 #ifdef HAVE_VORBIS
875 	char       *oggfilename;
876 #endif
877 #ifdef HAVE_ZLIB
878 	char       *gzfilename;
879 #endif
880 
881 	// make sure they're not trying to do weird stuff with our private files
882 	path = QFS_CompressPath (filename);
883 	if (qfs_contains_updir(path, 1)) {
884 		Sys_MaskPrintf (SYS_FS,
885 						"FindFile: %s: attempt to escape directory tree!\n",
886 						path);
887 		goto error;
888 	}
889 
890 #ifdef HAVE_VORBIS
891 	if (strequal (".wav", QFS_FileExtension (path))) {
892 		oggfilename = alloca (strlen (path) + 1);
893 		QFS_StripExtension (path, oggfilename);
894 		strncat (oggfilename, ".ogg",
895 				 sizeof (oggfilename) - strlen (oggfilename) - 1);
896 	} else {
897 		oggfilename = 0;
898 	}
899 #endif
900 #ifdef HAVE_ZLIB
901 	gzfilename = alloca (strlen (path) + 3 + 1);
902 	sprintf (gzfilename, "%s.gz", path);
903 #endif
904 
905 	// search through the path, one element at a time
906 	for (search = qfs_searchpaths; search; search = search->next) {
907 #ifdef HAVE_VORBIS
908 		//NOTE gzipped oggs not supported
909 		if (oggfilename
910 			&& open_file (search, oggfilename, gzfile, foundname, false) != -1)
911 			goto ok;
912 #endif
913 #ifdef HAVE_ZLIB
914 		if (open_file (search, gzfilename, gzfile, foundname, zip) != -1)
915 			goto ok;
916 #endif
917 		if (open_file (search, path, gzfile, foundname, zip) != -1)
918 			goto ok;
919 	}
920 
921 	Sys_MaskPrintf (SYS_FS_NF, "FindFile: can't find %s\n", filename);
922 error:
923 	*gzfile = NULL;
924 	qfs_filesize = -1;
925 	free (path);
926 	return -1;
927 ok:
928 	free(path);
929 	return qfs_filesize;
930 }
931 
932 VISIBLE int
QFS_FOpenFile(const char * filename,QFile ** gzfile)933 QFS_FOpenFile (const char *filename, QFile **gzfile)
934 {
935 	return _QFS_FOpenFile (filename, gzfile, 0, 1);
936 }
937 
938 cache_user_t *loadcache;
939 byte       *loadbuf;
940 int         loadsize;
941 
942 /*
943 	QFS_LoadFile
944 
945 	Filename are relative to the quake directory.
946 	Always appends a 0 byte to the loaded data.
947 */
948 VISIBLE byte *
QFS_LoadFile(const char * path,int usehunk)949 QFS_LoadFile (const char *path, int usehunk)
950 {
951 	QFile      *h;
952 	byte       *buf = NULL;
953 	char       *base;
954 	int         len;
955 
956 	// look for it in the filesystem or pack files
957 	len = qfs_filesize = QFS_FOpenFile (path, &h);
958 	if (!h)
959 		return NULL;
960 
961 	// extract the filename base name for hunk tag
962 	base = QFS_FileBase (path);
963 
964 	if (usehunk == 1)
965 		buf = Hunk_AllocName (len + 1, base);
966 	else if (usehunk == 2)
967 		buf = Hunk_TempAlloc (len + 1);
968 	else if (usehunk == 0)
969 		buf = calloc (1, len + 1);
970 	else if (usehunk == 3)
971 		buf = Cache_Alloc (loadcache, len + 1, base);
972 	else if (usehunk == 4) {
973 		if (len + 1 > loadsize)
974 			buf = Hunk_TempAlloc (len + 1);
975 		else
976 			buf = loadbuf;
977 	} else
978 		Sys_Error ("QFS_LoadFile: bad usehunk");
979 
980 	if (!buf)
981 		Sys_Error ("QFS_LoadFile: not enough space for %s", path);
982 
983 	buf[len] = 0;
984 	Qread (h, buf, len);
985 	Qclose (h);
986 
987 	free (base);
988 
989 	return buf;
990 }
991 
992 VISIBLE byte *
QFS_LoadHunkFile(const char * path)993 QFS_LoadHunkFile (const char *path)
994 {
995 	return QFS_LoadFile (path, 1);
996 }
997 
998 VISIBLE void
QFS_LoadCacheFile(const char * path,struct cache_user_s * cu)999 QFS_LoadCacheFile (const char *path, struct cache_user_s *cu)
1000 {
1001 	loadcache = cu;
1002 	QFS_LoadFile (path, 3);
1003 }
1004 
1005 // uses temp hunk if larger than bufsize
1006 VISIBLE byte *
QFS_LoadStackFile(const char * path,void * buffer,int bufsize)1007 QFS_LoadStackFile (const char *path, void *buffer, int bufsize)
1008 {
1009 	byte       *buf;
1010 
1011 	loadbuf = (byte *) buffer;
1012 	loadsize = bufsize;
1013 	buf = QFS_LoadFile (path, 4);
1014 
1015 	return buf;
1016 }
1017 
1018 /*
1019 	qfs_load_pakfile
1020 
1021 	Takes an explicit (not game tree related) path to a pak file.
1022 
1023 	Loads the header and directory, adding the files at the beginning
1024 	of the list so they override previous pack files.
1025 */
1026 static pack_t     *
qfs_load_pakfile(char * packfile)1027 qfs_load_pakfile (char *packfile)
1028 {
1029 	pack_t     *pack = pack_open (packfile);
1030 
1031 	if (pack)
1032 		Sys_MaskPrintf (SYS_FS, "Added packfile %s (%i files)\n",
1033 					packfile, pack->numfiles);
1034 	return pack;
1035 }
1036 
1037 #define FBLOCK_SIZE	32
1038 
1039 // Note, this is /NOT/ a work-alike strcmp, this groups numbers sanely.
1040 static int
qfs_file_sort(char ** os1,char ** os2)1041 qfs_file_sort (char **os1, char **os2)
1042 {
1043 	int         in1, in2, n1, n2;
1044 	char       *s1, *s2;
1045 
1046 	s1 = *os1;
1047 	s2 = *os2;
1048 
1049 	while (1) {
1050 		in1 = in2 = n1 = n2 = 0;
1051 
1052 		if ((in1 = isdigit ((byte) *s1)))
1053 			n1 = strtol (s1, &s1, 10);
1054 
1055 		if ((in2 = isdigit ((byte) *s2)))
1056 			n2 = strtol (s2, &s2, 10);
1057 
1058 		if (in1 && in2) {
1059 			if (n1 != n2)
1060 				return n1 - n2;
1061 		} else {
1062 			if (*s1 != *s2)
1063 				return *s1 - *s2;
1064 			else if (*s1 == '\0')
1065 				return *s1 - *s2;
1066 			s1++;
1067 			s2++;
1068 		}
1069 	}
1070 }
1071 
1072 static void
qfs_load_gamedir(const char * dir)1073 qfs_load_gamedir (const char *dir)
1074 {
1075 	searchpath_t *search;
1076 	pack_t     *pak;
1077 	DIR        *dir_ptr;
1078 	struct dirent *dirent;
1079 	char      **pakfiles = NULL;
1080 	int         i = 0, bufsize = 0, count = 0;
1081 
1082 	Sys_MaskPrintf (SYS_FS, "qfs_load_gamedir (\"%s\")\n", dir);
1083 
1084 	pakfiles = calloc (1, FBLOCK_SIZE * sizeof (char *));
1085 
1086 	bufsize += FBLOCK_SIZE;
1087 	if (!pakfiles)
1088 		goto qfs_load_gamedir_free;
1089 
1090 	for (i = 0; i < bufsize; i++) {
1091 		pakfiles[i] = NULL;
1092 	}
1093 
1094 	dir_ptr = opendir (dir);
1095 	if (!dir_ptr)
1096 		goto qfs_load_gamedir_free;
1097 
1098 	while ((dirent = readdir (dir_ptr))) {
1099 		if (!fnmatch ("*.pak", dirent->d_name, 0)) {
1100 			if (count >= bufsize) {
1101 				bufsize += FBLOCK_SIZE;
1102 				pakfiles = realloc (pakfiles, bufsize * sizeof (char *));
1103 
1104 				if (!pakfiles)
1105 					goto qfs_load_gamedir_free;
1106 				for (i = count; i < bufsize; i++)
1107 					pakfiles[i] = NULL;
1108 			}
1109 
1110 			// at this point, dir is known to not have a trailing /, and
1111 			// dirent->d_name definitely won't start with one.
1112 			pakfiles[count] = nva ("%s/%s", dir, dirent->d_name);
1113 			if (!pakfiles[count])
1114 				Sys_Error ("qfs_load_gamedir: Memory allocation failure");
1115 			count++;
1116 		}
1117 	}
1118 	closedir (dir_ptr);
1119 
1120 	qsort (pakfiles, count, sizeof (char *),
1121 		   (int (*)(const void *, const void *)) qfs_file_sort);
1122 
1123 	for (i = 0; i < count; i++) {
1124 		pak = qfs_load_pakfile (pakfiles[i]);
1125 
1126 		if (!pak) {
1127 			Sys_Error ("Bad pakfile %s!!", pakfiles[i]);
1128 		} else {
1129 			search = malloc (sizeof (searchpath_t));
1130 			search->filename = 0;
1131 			search->pack = pak;
1132 			search->next = qfs_searchpaths;
1133 			qfs_searchpaths = search;
1134 		}
1135 	}
1136 
1137   qfs_load_gamedir_free:
1138 	for (i = 0; i < count; i++)
1139 		free (pakfiles[i]);
1140 	free (pakfiles);
1141 }
1142 
1143 /*
1144 	qfs_add_dir
1145 
1146 	Adds the directory to the head of the path, then loads and adds pak0.pak
1147 	pak1.pak ...
1148 */
1149 static void
qfs_add_dir(const char * dir)1150 qfs_add_dir (const char *dir)
1151 {
1152 	searchpath_t *search;
1153 
1154 	// add the directory to the search path
1155 	search = malloc (sizeof (searchpath_t));
1156 	search->filename = strdup (dir);
1157 	search->pack = 0;
1158 	search->next = qfs_searchpaths;
1159 	qfs_searchpaths = search;
1160 
1161 	// add any pak files in the format pak0.pak pak1.pak, ...
1162 	qfs_load_gamedir (dir);
1163 }
1164 
1165 static void
qfs_add_gamedir(const char * dir)1166 qfs_add_gamedir (const char *dir)
1167 {
1168 	const char *e;
1169 	const char *s;
1170 	dstring_t  *s_dir;
1171 	dstring_t  *f_dir;
1172 
1173 	if (!*dir)
1174 		return;
1175 	e = fs_sharepath->string + strlen (fs_sharepath->string);
1176 	s = e;
1177 	s_dir = dstring_new ();
1178 	f_dir = dstring_new ();
1179 
1180 	while (s >= fs_sharepath->string) {
1181 		while (s != fs_sharepath->string && s[-1] !=':')
1182 			s--;
1183 		if (s != e) {
1184 			dsprintf (s_dir, "%.*s", (int) (e - s), s);
1185 			if (strcmp (s_dir->str, fs_userpath->string) != 0) {
1186 				if (qfs_expand_path (f_dir, s_dir->str, dir, 0) != 0) {
1187 					Sys_Printf ("dropping bad directory %s\n", dir);
1188 					break;
1189 				}
1190 				Sys_MaskPrintf (SYS_FS, "qfs_add_gamedir (\"%s\")\n",
1191 								f_dir->str);
1192 
1193 				qfs_add_dir (f_dir->str);
1194 			}
1195 		}
1196 		e = --s;
1197 	}
1198 
1199 	qfs_expand_userpath (f_dir, dir);
1200 	Sys_MaskPrintf (SYS_FS, "qfs_add_gamedir (\"%s\")\n", f_dir->str);
1201 	qfs_add_dir (f_dir->str);
1202 
1203 	dstring_delete (f_dir);
1204 	dstring_delete (s_dir);
1205 }
1206 
1207 /*
1208 	QFS_Gamedir
1209 
1210 	Sets the gamedir and path to a different directory.
1211 */
1212 VISIBLE void
QFS_Gamedir(const char * gamedir)1213 QFS_Gamedir (const char *gamedir)
1214 {
1215 	int         i;
1216 	const char *list[2] = {gamedir, 0};
1217 
1218 	qfs_build_gamedir (list);
1219 
1220 	// Make sure everyone else knows we've changed gamedirs
1221 	for (i = 0; i < num_gamedir_callbacks; i++) {
1222 		gamedir_callbacks[i] (0);
1223 	}
1224 	Cache_Flush ();
1225 	for (i = 0; i < num_gamedir_callbacks; i++) {
1226 		gamedir_callbacks[i] (1);
1227 	}
1228 }
1229 
1230 VISIBLE void
QFS_GamedirCallback(gamedir_callback_t * func)1231 QFS_GamedirCallback (gamedir_callback_t *func)
1232 {
1233 	if (num_gamedir_callbacks == max_gamedir_callbacks) {
1234 		size_t size = (max_gamedir_callbacks + GAMEDIR_CALLBACK_CHUNK)
1235 					  * sizeof (gamedir_callback_t *);
1236 		gamedir_callbacks = realloc (gamedir_callbacks, size);
1237 		if (!gamedir_callbacks)
1238 			Sys_Error ("Too many gamedir callbacks!\n");
1239 		max_gamedir_callbacks += GAMEDIR_CALLBACK_CHUNK;
1240 	}
1241 
1242 	if (!func) {
1243 		Sys_Error ("null gamedir callback\n");
1244 	}
1245 
1246 	gamedir_callbacks[num_gamedir_callbacks] = func;
1247 	num_gamedir_callbacks++;
1248 }
1249 
1250 static void
qfs_path_cvar(cvar_t * var)1251 qfs_path_cvar (cvar_t *var)
1252 {
1253 	char       *cpath = QFS_CompressPath (var->string);
1254 	if (strcmp (cpath, var->string))
1255 		Cvar_Set (var, cpath);
1256 	free (cpath);
1257 }
1258 
1259 VISIBLE void
QFS_Init(const char * game)1260 QFS_Init (const char *game)
1261 {
1262 	int         i;
1263 
1264 	fs_sharepath = Cvar_Get ("fs_sharepath", FS_SHAREPATH, CVAR_ROM,
1265 							 qfs_path_cvar,
1266 							 "location of shared (read-only) game "
1267 							 "directories");
1268 	fs_userpath = Cvar_Get ("fs_userpath", FS_USERPATH, CVAR_ROM,
1269 							qfs_path_cvar,
1270 							"location of your game directories");
1271 	fs_dirconf = Cvar_Get ("fs_dirconf", "", CVAR_ROM, NULL,
1272 							"full path to gamedir.conf FIXME");
1273 
1274 	Cmd_AddCommand ("path", qfs_path_f, "Show what paths Quake is using");
1275 
1276 	qfs_userpath = Sys_ExpandSquiggle (fs_userpath->string);
1277 
1278 	qfs_load_config ();
1279 
1280 	qfs_game = game;
1281 
1282 	if ((i = COM_CheckParm ("-game")) && i < com_argc - 1) {
1283 		char       *gamedirs = NULL;
1284 		const char **list;
1285 		char       *where;
1286 		int         j, count = 1;
1287 
1288 		gamedirs = strdup (com_argv[i + 1]);
1289 
1290 		for (j = 0; gamedirs[j]; j++)
1291 			if (gamedirs[j] == ',')
1292 				count++;
1293 
1294 		list = calloc (count + 1, sizeof (char *));
1295 
1296 		j = 0;
1297 		where = strtok (gamedirs, ",");
1298 		while (where) {
1299 			list[j++] = where;
1300 			where = strtok (NULL, ",");
1301 		}
1302 		qfs_build_gamedir (list);
1303 		free (gamedirs);
1304 		free ((void*)list);
1305 	} else {
1306 		QFS_Gamedir ("");
1307 	}
1308 }
1309 
1310 VISIBLE const char *
QFS_SkipPath(const char * pathname)1311 QFS_SkipPath (const char *pathname)
1312 {
1313 	const char *last;
1314 
1315 	// char after last / on the line
1316 	if ((last = strrchr (pathname, '/')))
1317 		last++;
1318 	else
1319 		last = pathname;
1320 
1321 	return last;
1322 }
1323 
1324 VISIBLE void
QFS_StripExtension(const char * in,char * out)1325 QFS_StripExtension (const char *in, char *out)
1326 {
1327 	char       *tmp;
1328 
1329 	if (out != in)
1330 		strcpy (out, in);
1331 
1332 	tmp = out + (QFS_FileExtension (out) - out);
1333 	*tmp = 0;
1334 }
1335 
1336 VISIBLE const char *
QFS_FileExtension(const char * in)1337 QFS_FileExtension (const char *in)
1338 {
1339 	const char *tmp;
1340 	const char *end = in + strlen (in);
1341 
1342 	for (tmp = end; tmp != in; tmp--) {
1343 		if (tmp[-1] == '/')
1344 			return end;
1345 		if (tmp[-1] == '.') {
1346 			if (tmp - 1 == in || tmp[-2] == '/')
1347 				return end;
1348 			return tmp - 1;
1349 		}
1350 	}
1351 
1352 	return end;
1353 }
1354 
1355 VISIBLE void
QFS_DefaultExtension(dstring_t * path,const char * extension)1356 QFS_DefaultExtension (dstring_t *path, const char *extension)
1357 {
1358 	const char *ext;
1359 
1360 	// if path doesn't have a .EXT, append extension
1361 	// (extension should include the .)
1362 	ext = QFS_FileExtension (path->str);
1363 	if (*ext)
1364 		return;						// it has an extension
1365 
1366 	dstring_appendstr (path, extension);
1367 }
1368 
1369 VISIBLE void
QFS_SetExtension(struct dstring_s * path,const char * extension)1370 QFS_SetExtension (struct dstring_s *path, const char *extension)
1371 {
1372 	const char *ext = QFS_FileExtension (path->str);
1373 	int         offs = ext - path->str;
1374 
1375 	if (*ext) {
1376 		// path has an extension... cut it off
1377 		path->str[offs] = 0;
1378 		path->size = offs + 1;
1379 	}
1380 	dstring_appendstr (path, extension);
1381 }
1382 
1383 VISIBLE int
QFS_NextFilename(dstring_t * filename,const char * prefix,const char * ext)1384 QFS_NextFilename (dstring_t *filename, const char *prefix, const char *ext)
1385 {
1386 	char       *digits;
1387 	int         i;
1388 	int         ret = 0;
1389 	dstring_t  *full_path = dstring_new ();
1390 
1391 	dsprintf (filename, "%s0000%s", prefix, ext);
1392 	digits = filename->str + strlen (prefix);
1393 
1394 	for (i = 0; i <= 9999; i++) {
1395 		digits[0] = i / 1000 + '0';
1396 		digits[1] = i / 100 % 10 + '0';
1397 		digits[2] = i / 10 % 10 + '0';
1398 		digits[3] = i % 10 + '0';
1399 
1400 		if (qfs_expand_userpath (full_path, filename->str) == -1)
1401 			break;
1402 		if (Sys_FileTime (full_path->str) == -1) {
1403 			// file doesn't exist, so we can use this name
1404 			ret = 1;
1405 			break;
1406 		}
1407 	}
1408 	dstring_delete (full_path);
1409 	return ret;
1410 }
1411 
1412 VISIBLE QFile *
QFS_Open(const char * path,const char * mode)1413 QFS_Open (const char *path, const char *mode)
1414 {
1415 	dstring_t  *full_path = dstring_new ();
1416 	QFile      *file = 0;
1417 	const char *m;
1418 	int         write = 0;
1419 
1420 	if (qfs_expand_userpath (full_path, path) == 0) {
1421 		Sys_MaskPrintf (SYS_FS, "QFS_Open: %s %s\n", full_path->str, mode);
1422 		for (m = mode; *m; m++)
1423 			if (*m == 'w' || *m == '+' || *m == 'a')
1424 				write = 1;
1425 		if (write)
1426 			if (Sys_CreatePath (full_path->str) == -1)
1427 				goto done;
1428 		file = Qopen (full_path->str, mode);
1429 	}
1430 done:
1431 	dstring_delete (full_path);
1432 	return file;
1433 }
1434 
1435 VISIBLE QFile *
QFS_WOpen(const char * path,int zip)1436 QFS_WOpen (const char *path, int zip)
1437 {
1438 	char        mode[5] = "wb\000\000\000";
1439 
1440 	if (zip) {
1441 		mode[2] = 'z';
1442 		mode[3] = bound (1, zip, 9) + '0';
1443 	}
1444 	return QFS_Open (path, mode);
1445 }
1446 
1447 VISIBLE int
QFS_Rename(const char * old_path,const char * new_path)1448 QFS_Rename (const char *old_path, const char *new_path)
1449 {
1450 	dstring_t  *full_old = dstring_new ();
1451 	dstring_t  *full_new = dstring_new ();
1452 	int         ret;
1453 
1454 	if ((ret = qfs_expand_userpath (full_old, old_path)) != -1)
1455 		if ((ret = qfs_expand_userpath (full_new, new_path)) != -1)
1456 			if ((ret = Sys_CreatePath (full_new->str)) != -1) {
1457 				Sys_MaskPrintf (SYS_FS, "QFS_Rename %s %s\n", full_old->str,
1458 								full_new->str);
1459 				ret = Qrename (full_old->str, full_new->str);
1460 			}
1461 	dstring_delete (full_old);
1462 	dstring_delete (full_new);
1463 	return ret;
1464 }
1465 
1466 VISIBLE int
QFS_Remove(const char * path)1467 QFS_Remove (const char *path)
1468 {
1469 	dstring_t  *full_path = dstring_new ();
1470 	int         ret;
1471 
1472 	if ((ret = qfs_expand_userpath (full_path, path)) != -1)
1473 		ret = Qremove (full_path->str);
1474 	dstring_delete (full_path);
1475 	return ret;
1476 }
1477 
1478 VISIBLE filelist_t *
QFS_FilelistNew(void)1479 QFS_FilelistNew (void)
1480 {
1481 	return calloc (1, sizeof (filelist_t));
1482 }
1483 
1484 VISIBLE void
QFS_FilelistAdd(filelist_t * filelist,const char * fname,const char * ext)1485 QFS_FilelistAdd (filelist_t *filelist, const char *fname, const char *ext)
1486 {
1487 	char      **new_list;
1488 	char      *s, *str;
1489 
1490 	while ((s = strchr(fname, '/')))
1491 		fname = s + 1;
1492 	if (filelist->count == filelist->size) {
1493 		filelist->size += 32;
1494 		new_list = realloc (filelist->list, filelist->size * sizeof (char *));
1495 
1496 		if (!new_list) {
1497 			filelist->size -= 32;
1498 			return;
1499 		}
1500 		filelist->list = new_list;
1501 	}
1502 	str = strdup (fname);
1503 
1504 	if (ext && (s = strstr(str, va(".%s", ext))))
1505 		*s = 0;
1506 	filelist->list[filelist->count++] = str;
1507 }
1508 
1509 VISIBLE void
QFS_FilelistFill(filelist_t * list,const char * path,const char * ext,int strip)1510 QFS_FilelistFill (filelist_t *list, const char *path, const char *ext,
1511 				  int strip)
1512 {
1513 	searchpath_t *search;
1514 	DIR        *dir_ptr;
1515 	struct dirent *dirent;
1516 	char       *cpath, *cp;
1517 	const char *separator = "/";
1518 
1519 	if (strchr (ext, '/') || strchr (ext, '\\'))
1520 		return;
1521 
1522 	cp = cpath = QFS_CompressPath (path);
1523 	if (*cp && cp[strlen (cp) - 1] == '/')
1524 		separator = "";
1525 
1526 	for (search = qfs_searchpaths; search != NULL; search = search->next) {
1527 		if (search->pack) {
1528 			int         i;
1529 			pack_t     *pak = search->pack;
1530 
1531 			for (i = 0; i < pak->numfiles; i++) {
1532 				char       *name = pak->files[i].name;
1533 				if (!fnmatch (va("%s%s*.%s", cp, separator, ext), name,
1534 							  FNM_PATHNAME)
1535 					|| !fnmatch (va("%s%s*.%s.gz", cp, separator, ext), name,
1536 								 FNM_PATHNAME))
1537 					QFS_FilelistAdd (list, name, strip ? ext : 0);
1538 			}
1539 		} else {
1540 			dir_ptr = opendir (va ("%s/%s", search->filename, cp));
1541 			if (!dir_ptr)
1542 				continue;
1543 			while ((dirent = readdir (dir_ptr)))
1544 				if (!fnmatch (va("*.%s", ext), dirent->d_name, 0)
1545 					|| !fnmatch (va("*.%s.gz", ext), dirent->d_name, 0))
1546 					QFS_FilelistAdd (list, dirent->d_name, strip ? ext : 0);
1547 			closedir (dir_ptr);
1548 		}
1549 	}
1550 	free (cpath);
1551 }
1552 
1553 VISIBLE void
QFS_FilelistFree(filelist_t * list)1554 QFS_FilelistFree (filelist_t *list)
1555 {
1556 	int         i;
1557 
1558 	for (i = 0; i < list->count; i++)
1559 		free (list->list[i]);
1560 	free (list->list);
1561 	free (list);
1562 }
1563