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