Quake II RTX doxygen  1.0 dev
files.c
Go to the documentation of this file.
1 /*
2 Copyright (C) 1997-2001 Id Software, Inc.
3 Copyright (C) 2019, NVIDIA CORPORATION. All rights reserved.
4 
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or
8 (at your option) any later version.
9 
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14 
15 You should have received a copy of the GNU General Public License along
16 with this program; if not, write to the Free Software Foundation, Inc.,
17 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 */
19 
20 #include "shared/shared.h"
21 #include "shared/list.h"
22 #include "common/common.h"
23 #include "common/cvar.h"
24 #include "common/error.h"
25 #include "common/files.h"
26 #include "common/prompt.h"
27 #include "system/system.h"
28 #include "client/client.h"
29 #include "format/pak.h"
30 
31 #include <fcntl.h>
32 
33 #include <sys/types.h>
34 #include <sys/stat.h>
35 #ifndef WIN32
36  #include <unistd.h>
37 #else
38  #define stat _stat
39 #endif
40 
41 #if USE_ZLIB
42 #include <zlib.h>
43 #endif
44 
45 /*
46 =============================================================================
47 
48 QUAKE FILESYSTEM
49 
50 - transparently merged from several sources
51 - relative to the single virtual root
52 - case insensitive at pakfiles level,
53  but may be case sensitive at host OS level
54 - uses / as path separators internally
55 
56 =============================================================================
57 */
58 
59 #define MAX_FILE_HANDLES 32
60 
61 #if USE_ZLIB
62 #define ZIP_MAXFILES 0x8000 // 32k files
63 #define ZIP_BUFSIZE 0x10000 // inflate in blocks of 64k
64 
65 #define ZIP_BUFREADCOMMENT 1024
66 #define ZIP_SIZELOCALHEADER 30
67 #define ZIP_SIZECENTRALHEADER 20
68 #define ZIP_SIZECENTRALDIRITEM 46
69 
70 #define ZIP_LOCALHEADERMAGIC 0x04034b50
71 #define ZIP_CENTRALHEADERMAGIC 0x02014b50
72 #define ZIP_ENDHEADERMAGIC 0x06054b50
73 #endif
74 
75 #ifdef _DEBUG
76 #define FS_DPrintf(...) \
77  if (fs_debug && fs_debug->integer) \
78  Com_LPrintf(PRINT_DEVELOPER, __VA_ARGS__)
79 #else
80 #define FS_DPrintf(...)
81 #endif
82 
83 #define PATH_NOT_CHECKED -1
84 
85 #define FOR_EACH_SYMLINK(link, list) \
86  LIST_FOR_EACH(symlink_t, link, list, entry)
87 
88 #define FOR_EACH_SYMLINK_SAFE(link, next, list) \
89  LIST_FOR_EACH_SAFE(symlink_t, link, next, list, entry)
90 
91 //
92 // in memory
93 //
94 
95 typedef enum {
99 #if USE_ZLIB
100  FS_ZIP,
101  FS_GZ,
102 #endif
104 } filetype_t;
105 
106 #if USE_ZLIB
107 typedef struct {
108  z_stream stream;
109  size_t rest_in;
110  byte buffer[ZIP_BUFSIZE];
111 } zipstream_t;
112 #endif
113 
114 typedef struct packfile_s {
115  char *name;
116  size_t namelen;
117  size_t filepos;
118  size_t filelen;
119 #if USE_ZLIB
120  size_t complen;
121  unsigned compmtd; // compression method, 0 (stored) or Z_DEFLATED
122  qboolean coherent; // true if local file header has been checked
123 #endif
124 
126 } packfile_t;
127 
128 typedef struct {
129  filetype_t type; // FS_PAK or FS_ZIP
130  unsigned refcount; // for tracking pack users
131  FILE *fp;
132  unsigned num_files;
135  unsigned hash_size;
136  char *names;
137  char *filename;
138 } pack_t;
139 
140 typedef struct searchpath_s {
142  unsigned mode;
143  pack_t *pack; // only one of filename / pack will be used
144  char filename[1];
145 } searchpath_t;
146 
147 typedef struct {
149  unsigned mode;
150  FILE *fp;
151 #if USE_ZLIB
152  void *zfp; // gzFile for FS_GZ or zipstream_t for FS_ZIP
153 #endif
154  packfile_t *entry; // pack entry this handle is tied to
155  pack_t *pack; // points to the pack entry is from
156  qboolean unique; // if true, then pack must be freed on close
157  qerror_t error; // stream error indicator from read/write operation
158  size_t rest_out; // remaining unread length for FS_PAK/FS_ZIP
159  size_t length; // total cached file length
160 } file_t;
161 
162 typedef struct {
163  list_t entry;
164  size_t targlen;
165  size_t namelen;
166  char *target;
167  char name[1];
168 } symlink_t;
169 
170 // these point to user home directory
171 char fs_gamedir[MAX_OSPATH];
172 //static char fs_basedir[MAX_OSPATH];
173 
176 
177 static list_t fs_hard_links;
178 static list_t fs_soft_links;
179 
181 
182 #ifdef _DEBUG
183 static int fs_count_read;
184 static int fs_count_open;
185 static int fs_count_strcmp;
186 static int fs_count_strlwr;
187 #define FS_COUNT_READ fs_count_read++
188 #define FS_COUNT_OPEN fs_count_open++
189 #define FS_COUNT_STRCMP fs_count_strcmp++
190 #define FS_COUNT_STRLWR fs_count_strlwr++
191 #else
192 #define FS_COUNT_READ (void)0
193 #define FS_COUNT_OPEN (void)0
194 #define FS_COUNT_STRCMP (void)0
195 #define FS_COUNT_STRLWR (void)0
196 #endif
197 
198 #ifdef _DEBUG
199 static cvar_t *fs_debug;
200 #endif
201 
202 cvar_t *fs_game;
203 
204 cvar_t *fs_shareware;
205 
206 #if USE_ZLIB
207 // local stream used for all file loads
208 static zipstream_t fs_zipstream;
209 
210 static void open_zip_file(file_t *file);
211 static void close_zip_file(file_t *file);
212 static ssize_t tell_zip_file(file_t *file);
213 static ssize_t read_zip_file(file_t *file, void *buf, size_t len);
214 #endif
215 
216 // for tracking users of pack_t instance
217 // allows FS to be restarted while reading something from pack
218 static pack_t *pack_get(pack_t *pack);
219 static void pack_put(pack_t *pack);
220 
221 /*
222 
223 All of Quake's data access is through a hierchal file system,
224 but the contents of the file system can be transparently merged from several sources.
225 
226 The "base directory" is the path to the directory holding all game directories.
227 The base directory is only used during filesystem initialization.
228 
229 The "game directory" is the first tree on the search path and directory that
230 all generated files (savegames, screenshots, demos, config files) will be saved to.
231 
232 */
233 
234 #ifdef _WIN32
235 char *FS_ReplaceSeparators(char *s, int separator)
236 {
237  char *p;
238 
239  p = s;
240  while (*p) {
241  if (*p == '/' || *p == '\\') {
242  *p = separator;
243  }
244  p++;
245  }
246 
247  return s;
248 }
249 #endif
250 
251 static inline qboolean validate_char(int c)
252 {
253  if (!Q_isprint(c))
254  return qfalse;
255 
256 #ifdef _WIN32
257  if (strchr("<>:\"|?*", c))
258  return qfalse;
259 #endif
260 
261  return qtrue;
262 }
263 
264 /*
265 ================
266 FS_ValidatePath
267 
268 Checks for bad (OS specific) and mixed case characters in path.
269 ================
270 */
271 int FS_ValidatePath(const char *s)
272 {
273  int res = PATH_VALID;
274 
275  for (; *s; s++) {
276  if (!validate_char(*s))
277  return PATH_INVALID;
278 
279  if (Q_isupper(*s))
280  res = PATH_MIXED_CASE;
281  }
282 
283  return res;
284 }
285 
286 /*
287 ================
288 FS_SanitizeFilenameVariable
289 
290 Checks that console variable is a valid single filename (not a path), otherwise
291 resets it to default value, printing a warning.
292 ================
293 */
295 {
296  if (!FS_ValidatePath(var->string)) {
297  Com_Printf("'%s' contains invalid characters for a filename.\n", var->name);
298  goto reset;
299  }
300 
301  if (strchr(var->string, '/') || strchr(var->string, '\\')) {
302  Com_Printf("'%s' should be a single filename, not a path.\n", var->name);
303  goto reset;
304  }
305 
306  return;
307 
308 reset:
309  Com_Printf("...falling back to %s\n", var->default_string);
310  Cvar_Reset(var);
311 }
312 
313 /*
314 ================
315 FS_NormalizePath
316 
317 Simplifies the path, converting backslashes to slashes and removing ./ and ../
318 components, as well as duplicated slashes. Any leading slashes are also skipped.
319 
320 May operate in place if in == out.
321 
323  foo\bar -> foo/bar
324  foo/.. -> <empty>
325  foo/../bar -> bar
326  foo/./bar -> foo/bar
327  foo//bar -> foo/bar
328  ./foo -> foo
329 ================
330 */
331 size_t FS_NormalizePath(char *out, const char *in)
332 {
333  char *start = out;
334  uint32_t pre = '/';
335 
336  while (1) {
337  int c = *in++;
338 
339  if (c == '/' || c == '\\' || c == 0) {
340  if ((pre & 0xffffff) == (('/' << 16) | ('.' << 8) | '.')) {
341  out -= 4;
342  if (out < start) {
343  // can't go past root
344  out = start;
345  if (c == 0)
346  break;
347  } else {
348  while (out > start && *out != '/')
349  out--;
350  if (c == 0)
351  break;
352  if (out > start)
353  // save the slash
354  out++;
355  }
356  pre = '/';
357  continue;
358  }
359 
360  if ((pre & 0xffff) == (('/' << 8) | '.')) {
361  // eat the dot
362  out--;
363  if (c == 0) {
364  if (out > start)
365  // eat the slash
366  out--;
367  break;
368  }
369  pre = '/';
370  continue;
371  }
372 
373  if ((pre & 0xff) == '/') {
374  if (c == 0)
375  break;
376  continue;
377  }
378 
379  if (c == 0)
380  break;
381  c = '/';
382  }
383 
384  pre = (pre << 8) | c;
385  *out++ = c;
386  }
387 
388  *out = 0;
389  return out - start;
390 }
391 
392 /*
393 ================
394 FS_NormalizePathBuffer
395 
396 Buffer safe version of FS_NormalizePath. Return value >= size signifies
397 overflow, empty string is stored in output buffer in this case.
398 ================
399 */
400 size_t FS_NormalizePathBuffer(char *out, const char *in, size_t size)
401 {
402  size_t len = strlen(in);
403 
404  if (len >= size) {
405  if (size)
406  *out = 0;
407  return len;
408  }
409 
410  return FS_NormalizePath(out, in);
411 }
412 
413 // =============================================================================
414 
415 static file_t *alloc_handle(qhandle_t *f)
416 {
417  file_t *file;
418  int i;
419 
420  for (i = 0, file = fs_files; i < MAX_FILE_HANDLES; i++, file++) {
421  if (file->type == FS_FREE) {
422  *f = i + 1;
423  return file;
424  }
425  }
426 
427  return NULL;
428 }
429 
430 static file_t *file_for_handle(qhandle_t f)
431 {
432  file_t *file;
433 
434  if (f < 1 || f > MAX_FILE_HANDLES)
435  return NULL;
436 
437  file = &fs_files[f - 1];
438  if (file->type == FS_FREE)
439  return NULL;
440 
441  if (file->type < FS_FREE || file->type >= FS_BAD)
442  Com_Error(ERR_FATAL, "%s: bad file type", __func__);
443 
444  return file;
445 }
446 
447 static void cleanup_path(char *s)
448 {
449  for (; *s; s++) {
450  if (!validate_char(*s))
451  *s = '_';
452  }
453 }
454 
455 // expects a buffer of at least MAX_OSPATH bytes!
456 static symlink_t *expand_links(list_t *list, char *buffer, size_t *len_p)
457 {
458  symlink_t *link;
459  size_t namelen = *len_p;
460 
461  FOR_EACH_SYMLINK(link, list) {
462  if (link->namelen > namelen) {
463  continue;
464  }
465  if (!FS_pathcmpn(buffer, link->name, link->namelen)) {
466  size_t newlen = namelen - link->namelen + link->targlen;
467 
468  if (newlen < MAX_OSPATH) {
469  memmove(buffer + link->targlen, buffer + link->namelen,
470  namelen - link->namelen + 1);
471  memcpy(buffer, link->target, link->targlen);
472  }
473 
474  *len_p = newlen;
475  return link;
476  }
477  }
478 
479  return NULL;
480 }
481 
482 /*
483 ================
484 FS_Length
485 ================
486 */
487 ssize_t FS_Length(qhandle_t f)
488 {
489  file_t *file = file_for_handle(f);
490 
491  if (!file)
492  return Q_ERR_BADF;
493 
494  if ((file->mode & FS_MODE_MASK) == FS_MODE_READ)
495  return file->length;
496 
497  return Q_ERR_NOSYS;
498 }
499 
500 /*
501 ============
502 FS_Tell
503 ============
504 */
505 ssize_t FS_Tell(qhandle_t f)
506 {
507  file_t *file = file_for_handle(f);
508  long ret;
509 
510  if (!file)
511  return Q_ERR_BADF;
512 
513  switch (file->type) {
514  case FS_REAL:
515  ret = ftell(file->fp);
516  if (ret == -1) {
517  return Q_Errno();
518  }
519  return ret;
520  case FS_PAK:
521  return file->length - file->rest_out;
522 #if USE_ZLIB
523  case FS_ZIP:
524  return tell_zip_file(file);
525  case FS_GZ:
526  ret = gztell(file->zfp);
527  if (ret == -1) {
528  return Q_ERR_LIBRARY_ERROR;
529  }
530  return ret;
531 #endif
532  default:
533  return Q_ERR_NOSYS;
534  }
535 }
536 
537 static qerror_t seek_pak_file(file_t *file, off_t offset)
538 {
539  packfile_t *entry = file->entry;
540  long filepos;
541 
542  if (offset > entry->filelen)
543  offset = entry->filelen;
544 
545  if (entry->filepos > LONG_MAX - offset)
546  return Q_ERR_INVAL;
547 
548  filepos = entry->filepos + offset;
549  if (fseek(file->fp, filepos, SEEK_SET) == -1)
550  return Q_Errno();
551 
552  file->rest_out = entry->filelen - offset;
553 
554  return Q_ERR_SUCCESS;
555 }
556 
557 /*
558 ============
559 FS_Seek
560 
561 Seeks to an absolute position within the file.
562 ============
563 */
564 qerror_t FS_Seek(qhandle_t f, off_t offset)
565 {
566  file_t *file = file_for_handle(f);
567 
568  if (!file)
569  return Q_ERR_BADF;
570 
571  if (offset > LONG_MAX)
572  return Q_ERR_INVAL;
573 
574  if (offset < 0)
575  offset = 0;
576 
577  switch (file->type) {
578  case FS_REAL:
579  if (fseek(file->fp, (long)offset, SEEK_SET) == -1) {
580  return Q_Errno();
581  }
582  return Q_ERR_SUCCESS;
583  case FS_PAK:
584  return seek_pak_file(file, offset);
585 #if USE_ZLIB
586  case FS_GZ:
587  if (gzseek(file->zfp, (z_off_t)offset, SEEK_SET) == -1) {
588  return Q_Errno();
589  }
590  return Q_ERR_SUCCESS;
591 #endif
592  default:
593  return Q_ERR_NOSYS;
594  }
595 }
596 
597 /*
598 ============
599 FS_CreatePath
600 
601 Creates any directories needed to store the given filename.
602 Expects a fully qualified, normalized system path (i.e. with / separators).
603 ============
604 */
605 qerror_t FS_CreatePath(char *path)
606 {
607  char *ofs;
608  int ret;
609 
610  ofs = path;
611 
612 #ifdef _WIN32
613  // check for UNC path and skip "//computer/share/" part
614  if (*path == '/' && path[1] == '/') {
615  char *p;
616 
617  p = strchr(path + 2, '/');
618  if (p) {
619  p = strchr(p + 1, '/');
620  if (p) {
621  ofs = p + 1;
622  }
623  }
624  }
625 #endif
626 
627  // skip leading slash(es)
628  for (; *ofs == '/'; ofs++)
629  ;
630 
631  for (; *ofs; ofs++) {
632  if (*ofs == '/') {
633  // create the directory
634  *ofs = 0;
635  ret = os_mkdir(path);
636  *ofs = '/';
637  if (ret == -1) {
638  qerror_t err = Q_Errno();
639  if (err != Q_ERR_EXIST)
640  return err;
641  }
642  }
643  }
644 
645  return Q_ERR_SUCCESS;
646 }
647 
648 #define FS_ERR_READ(fp) \
649  (ferror(fp) ? Q_Errno() : Q_ERR_UNEXPECTED_EOF)
650 #define FS_ERR_WRITE(fp) \
651  (ferror(fp) ? Q_Errno() : Q_ERR_FAILURE)
652 
653 /*
654 ============
655 FS_FilterFile
656 
657 Turns FS_REAL file into FS_GZIP by reopening it through GZIP.
658 File position is reset to the beginning of file.
659 ============
660 */
661 qerror_t FS_FilterFile(qhandle_t f)
662 {
663 #if USE_ZLIB
664  file_t *file = file_for_handle(f);
665  unsigned mode;
666  char *modeStr;
667  void *zfp;
668  uint32_t magic;
669  size_t length;
670  int fd;
671 
672  if (!file)
673  return Q_ERR_BADF;
674 
675  switch (file->type) {
676  case FS_GZ:
677  return Q_ERR_SUCCESS;
678  case FS_REAL:
679  break;
680  default:
681  return Q_ERR_NOSYS;
682  }
683 
684  mode = file->mode & FS_MODE_MASK;
685  switch (mode) {
686  case FS_MODE_READ:
687  // should have at least 10 bytes of header and 8 bytes of trailer
688  if (file->length < 18) {
689  return Q_ERR_FILE_TOO_SMALL;
690  }
691 
692  // seek to the header
693  if (fseek(file->fp, 0, SEEK_SET) == -1) {
694  return Q_Errno();
695  }
696 
697  // read magic
698  if (fread(&magic, 1, 4, file->fp) != 4) {
699  return FS_ERR_READ(file->fp);
700  }
701 
702  // check for gzip header
703  if (!CHECK_GZIP_HEADER(magic)) {
704  return Q_ERR_INVALID_FORMAT;
705  }
706 
707  // seek to the trailer
708  if (fseek(file->fp, file->length - 4, SEEK_SET) == -1) {
709  return Q_Errno();
710  }
711 
712  // read uncompressed length
713  if (fread(&magic, 1, 4, file->fp) != 4) {
714  return FS_ERR_READ(file->fp);
715  }
716 
717  length = LittleLong(magic);
718  modeStr = "rb";
719  break;
720 
721  case FS_MODE_WRITE:
722  length = 0;
723  modeStr = "wb";
724  break;
725 
726  default:
727  return Q_ERR_NOSYS;
728  }
729 
730  // rewind back to beginning
731  if (fseek(file->fp, 0, SEEK_SET) == -1) {
732  return Q_Errno();
733  }
734 
735  fd = os_fileno(file->fp);
736  if (fd == -1)
737  return Q_Errno();
738 
739  zfp = gzdopen(fd, modeStr);
740  if (!zfp) {
741  return Q_ERR_FAILURE;
742  }
743 
744  file->length = length;
745  file->zfp = zfp;
746  file->type = FS_GZ;
747  return Q_ERR_SUCCESS;
748 #else
749  return Q_ERR_NOSYS;
750 #endif
751 }
752 
753 
754 /*
755 ==============
756 FS_FCloseFile
757 ==============
758 */
759 void FS_FCloseFile(qhandle_t f)
760 {
761  file_t *file = file_for_handle(f);
762 
763  if (!file)
764  return;
765 
766  switch (file->type) {
767  case FS_REAL:
768  fclose(file->fp);
769  break;
770  case FS_PAK:
771  if (file->unique) {
772  fclose(file->fp);
773  pack_put(file->pack);
774  }
775  break;
776 #if USE_ZLIB
777  case FS_GZ:
778  gzclose(file->zfp);
779  fclose(file->fp);
780  break;
781  case FS_ZIP:
782  if (file->unique) {
783  close_zip_file(file);
784  pack_put(file->pack);
785  }
786  break;
787 #endif
788  default:
789  break;
790  }
791 
792  memset(file, 0, sizeof(*file));
793 }
794 
795 static qerror_t get_path_info(const char *path, file_info_t *info)
796 {
797  Q_STATBUF st;
798 
799  if (os_stat(path, &st) == -1)
800  return Q_Errno();
801 
802  if (Q_ISDIR(st.st_mode))
803  return Q_ERR_ISDIR;
804 
805  if (!Q_ISREG(st.st_mode))
806  return Q_ERR_FILE_NOT_REGULAR;
807 
808  if (info) {
809  info->size = st.st_size;
810  info->ctime = st.st_ctime;
811  info->mtime = st.st_mtime;
812  }
813 
814  return Q_ERR_SUCCESS;
815 }
816 
817 static qerror_t get_fp_info(FILE *fp, file_info_t *info)
818 {
819  Q_STATBUF st;
820  int fd;
821 
822  fd = os_fileno(fp);
823  if (fd == -1)
824  return Q_Errno();
825 
826  if (os_fstat(fd, &st) == -1)
827  return Q_Errno();
828 
829  if (Q_ISDIR(st.st_mode))
830  return Q_ERR_ISDIR;
831 
832  if (!Q_ISREG(st.st_mode))
833  return Q_ERR_FILE_NOT_REGULAR;
834 
835  if (info) {
836  info->size = st.st_size;
837  info->ctime = st.st_ctime;
838  info->mtime = st.st_mtime;
839  }
840 
841  return Q_ERR_SUCCESS;
842 }
843 
844 static inline FILE *fopen_hack(const char *path, const char *mode)
845 {
846 #ifndef _GNU_SOURCE
847  if (mode[0] == 'w' && mode[1] == 'x') {
848 #ifdef _WIN32
849  int flags = _O_WRONLY | _O_CREAT | _O_EXCL | _S_IREAD | _S_IWRITE;
850  int fd;
851  FILE *fp;
852 
853  if (mode[2] == 'b')
854  flags |= _O_BINARY;
855 
856  fd = _open(path, flags);
857  if (fd == -1)
858  return NULL;
859 
860  fp = _fdopen(fd, (flags & _O_BINARY) ? "wb" : "w");
861  if (fp == NULL)
862  _close(fd);
863 
864  return fp;
865 #else
866  int flags = O_WRONLY | O_CREAT | O_EXCL;
867  int perm = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH;
868  int fd;
869  FILE *fp;
870 
871  fd = open(path, flags, perm);
872  if (fd == -1)
873  return NULL;
874 
875  fp = fdopen(fd, "wb");
876  if (fp == NULL)
877  close(fd);
878 
879  return fp;
880 #endif
881  }
882 #endif // _GNU_SOURCE
883 
884  return fopen(path, mode);
885 }
886 
887 static ssize_t open_file_write(file_t *file, const char *name)
888 {
889  char normalized[MAX_OSPATH], fullpath[MAX_OSPATH];
890  FILE *fp;
891  char mode_str[8];
892  unsigned mode;
893  size_t len;
894  long pos;
895  qerror_t ret;
896 
897  // normalize the path
898  len = FS_NormalizePathBuffer(normalized, name, sizeof(normalized));
899  if (len >= sizeof(normalized)) {
900  return Q_ERR_NAMETOOLONG;
901  }
902 
903  // reject empty paths
904  if (len == 0) {
905  return Q_ERR_NAMETOOSHORT;
906  }
907 
908  // check for bad characters
909  if (!FS_ValidatePath(normalized)) {
910  ret = Q_ERR_INVALID_PATH;
911  goto fail1;
912  }
913 
914  // expand the path
915  if ((file->mode & FS_PATH_MASK) == FS_PATH_BASE) {
916  if (sys_homedir->string[0]) {
917  len = Q_concat(fullpath, sizeof(fullpath),
918  sys_homedir->string, "/" BASEGAME "/", normalized, NULL);
919  } else {
920  len = Q_concat(fullpath, sizeof(fullpath),
921  sys_basedir->string, "/" BASEGAME "/", normalized, NULL);
922  }
923  } else {
924  len = Q_concat(fullpath, sizeof(fullpath),
925  fs_gamedir, "/", normalized, NULL);
926  }
927  if (len >= sizeof(fullpath)) {
928  ret = Q_ERR_NAMETOOLONG;
929  goto fail1;
930  }
931 
932  mode = file->mode & FS_MODE_MASK;
933  switch (mode) {
934  case FS_MODE_APPEND:
935  strcpy(mode_str, "a");
936  break;
937  case FS_MODE_WRITE:
938  strcpy(mode_str, "w");
939  if (file->mode & FS_FLAG_EXCL)
940  strcat(mode_str, "x");
941  break;
942  case FS_MODE_RDWR:
943  // this mode is only used by client downloading code
944  // similar to FS_MODE_APPEND, but does not create
945  // the file if it does not exist
946  strcpy(mode_str, "r+");
947  break;
948  default:
949  ret = Q_ERR_INVAL;
950  goto fail1;
951  }
952 
953  // open in binary mode by default
954  if (!(file->mode & FS_FLAG_TEXT))
955  strcat(mode_str, "b");
956 
957  ret = FS_CreatePath(fullpath);
958  if (ret) {
959  goto fail1;
960  }
961 
962  fp = fopen_hack(fullpath, mode_str);
963  if (!fp) {
964  ret = Q_Errno();
965  goto fail1;
966  }
967 
968 #ifndef _WIN32
969  // check if this is a regular file
970  ret = get_fp_info(fp, NULL);
971  if (ret) {
972  goto fail2;
973  }
974 #endif
975 
976  switch (file->mode & FS_BUF_MASK) {
977  case FS_BUF_NONE:
978  // make it unbuffered
979  setvbuf(fp, NULL, _IONBF, BUFSIZ);
980  break;
981  case FS_BUF_LINE:
982  // make it line buffered
983  setvbuf(fp, NULL, _IOLBF, BUFSIZ);
984  break;
985  case FS_BUF_FULL:
986  // make it fully buffered
987  setvbuf(fp, NULL, _IOFBF, BUFSIZ);
988  break;
989  default:
990  // use default mode (normally fully buffered)
991  break;
992  }
993 
994  if (mode == FS_MODE_RDWR) {
995  // seek to the end of file for appending
996  if (fseek(fp, 0, SEEK_END) == -1) {
997  ret = Q_Errno();
998  goto fail2;
999  }
1000  }
1001 
1002  // return current position (non-zero for appending modes)
1003  pos = ftell(fp);
1004  if (pos == -1) {
1005  ret = Q_Errno();
1006  goto fail2;
1007  }
1008 
1009  FS_DPrintf("%s: %s: %lu bytes\n", __func__, fullpath, pos);
1010 
1011  file->type = FS_REAL;
1012  file->fp = fp;
1013  file->unique = qtrue;
1014  file->error = Q_ERR_SUCCESS;
1015  file->length = 0;
1016 
1017  return pos;
1018 
1019 fail2:
1020  fclose(fp);
1021 fail1:
1022  FS_DPrintf("%s: %s: %s\n", __func__, normalized, Q_ErrorString(ret));
1023  return ret;
1024 }
1025 
1026 #if USE_ZLIB
1027 
1028 static qerror_t check_header_coherency(FILE *fp, packfile_t *entry)
1029 {
1030  unsigned flags, comp_mtd;
1031  size_t comp_len, file_len;
1032  size_t name_size, xtra_size;
1033  byte header[ZIP_SIZELOCALHEADER];
1034  size_t ofs;
1035 
1036  if (fseek(fp, (long)entry->filepos, SEEK_SET) == -1)
1037  return Q_Errno();
1038  if (fread(header, 1, sizeof(header), fp) != sizeof(header))
1039  return FS_ERR_READ(fp);
1040 
1041  // check the magic
1042  if (LittleLongMem(&header[0]) != ZIP_LOCALHEADERMAGIC)
1043  return Q_ERR_NOT_COHERENT;
1044 
1045  flags = LittleShortMem(&header[6]);
1046  comp_mtd = LittleShortMem(&header[8]);
1047  comp_len = LittleLongMem(&header[18]);
1048  file_len = LittleLongMem(&header[22]);
1049  name_size = LittleShortMem(&header[26]);
1050  xtra_size = LittleShortMem(&header[28]);
1051 
1052  if (comp_mtd != entry->compmtd)
1053  return Q_ERR_NOT_COHERENT;
1054 
1055  // bit 3 tells that file lengths were not known
1056  // at the time local header was written, so don't check them
1057  if ((flags & 8) == 0) {
1058  if (comp_len != entry->complen)
1059  return Q_ERR_NOT_COHERENT;
1060  if (file_len != entry->filelen)
1061  return Q_ERR_NOT_COHERENT;
1062  }
1063 
1064  ofs = ZIP_SIZELOCALHEADER + name_size + xtra_size;
1065  if (entry->filepos > LONG_MAX - ofs) {
1066  return Q_ERR_SPIPE;
1067  }
1068 
1069  entry->filepos += ofs;
1070  entry->coherent = qtrue;
1071  return Q_ERR_SUCCESS;
1072 }
1073 
1074 static voidpf FS_zalloc(voidpf opaque, uInt items, uInt size)
1075 {
1076  return FS_Malloc(items * size);
1077 }
1078 
1079 static void FS_zfree(voidpf opaque, voidpf address)
1080 {
1081  Z_Free(address);
1082 }
1083 
1084 static void open_zip_file(file_t *file)
1085 {
1086  zipstream_t *s;
1087  z_streamp z;
1088 
1089  if (file->unique) {
1090  s = FS_Malloc(sizeof(*s));
1091  memset(&s->stream, 0, sizeof(s->stream));
1092  } else {
1093  s = &fs_zipstream;
1094  }
1095 
1096  z = &s->stream;
1097  if (z->state) {
1098  // already initialized, just reset
1099  inflateReset(z);
1100  } else {
1101  z->zalloc = FS_zalloc;
1102  z->zfree = FS_zfree;
1103  if (inflateInit2(z, -MAX_WBITS) != Z_OK) {
1104  Com_Error(ERR_FATAL, "%s: inflateInit2() failed", __func__);
1105  }
1106  }
1107 
1108  z->avail_in = z->avail_out = 0;
1109  z->total_in = z->total_out = 0;
1110  z->next_in = z->next_out = NULL;
1111 
1112  s->rest_in = file->entry->complen;
1113  file->zfp = s;
1114 }
1115 
1116 // only called for unique handles
1117 static void close_zip_file(file_t *file)
1118 {
1119  zipstream_t *s = file->zfp;
1120 
1121  inflateEnd(&s->stream);
1122  Z_Free(s);
1123 
1124  fclose(file->fp);
1125 }
1126 
1127 static ssize_t tell_zip_file(file_t *file)
1128 {
1129  zipstream_t *s = file->zfp;
1130 
1131  return s->stream.total_out;
1132 }
1133 
1134 static ssize_t read_zip_file(file_t *file, void *buf, size_t len)
1135 {
1136  zipstream_t *s = file->zfp;
1137  z_streamp z = &s->stream;
1138  size_t block, result;
1139  int ret;
1140 
1141  if (len > file->rest_out) {
1142  len = file->rest_out;
1143  }
1144  if (!len) {
1145  return 0;
1146  }
1147 
1148  z->next_out = buf;
1149  z->avail_out = (uInt)len;
1150 
1151  do {
1152  if (!z->avail_in) {
1153  if (!s->rest_in) {
1154  break;
1155  }
1156 
1157  // fill in the temp buffer
1158  block = ZIP_BUFSIZE;
1159  if (block > s->rest_in) {
1160  block = s->rest_in;
1161  }
1162 
1163  result = fread(s->buffer, 1, block, file->fp);
1164  if (result != block) {
1165  file->error = FS_ERR_READ(file->fp);
1166  if (!result) {
1167  break;
1168  }
1169  }
1170 
1171  s->rest_in -= result;
1172  z->next_in = s->buffer;
1173  z->avail_in = result;
1174  }
1175 
1176  ret = inflate(z, Z_SYNC_FLUSH);
1177  if (ret == Z_STREAM_END) {
1178  break;
1179  }
1180  if (ret != Z_OK) {
1181  file->error = Q_ERR_INFLATE_FAILED;
1182  break;
1183  }
1184  if (file->error) {
1185  break;
1186  }
1187  } while (z->avail_out);
1188 
1189  len -= z->avail_out;
1190  file->rest_out -= len;
1191 
1192  if (file->error && len == 0) {
1193  return file->error;
1194  }
1195 
1196  return len;
1197 }
1198 
1199 #endif
1200 
1201 // open a new file on the pakfile
1202 static ssize_t open_from_pak(file_t *file, pack_t *pack, packfile_t *entry, qboolean unique)
1203 {
1204  FILE *fp;
1205  qerror_t ret;
1206 
1207  if (unique) {
1208  fp = fopen(pack->filename, "rb");
1209  if (!fp) {
1210  ret = Q_Errno();
1211  goto fail1;
1212  }
1213  } else {
1214  fp = pack->fp;
1215  clearerr(fp);
1216  }
1217 
1218 #if USE_ZLIB
1219  if (pack->type == FS_ZIP && !entry->coherent) {
1220  ret = check_header_coherency(fp, entry);
1221  if (ret) {
1222  goto fail2;
1223  }
1224  }
1225 #endif
1226 
1227  if (fseek(fp, (long)entry->filepos, SEEK_SET) == -1) {
1228  ret = Q_Errno();
1229  goto fail2;
1230  }
1231 
1232  file->type = pack->type;
1233  file->fp = fp;
1234  file->entry = entry;
1235  file->pack = pack;
1236  file->unique = unique;
1237  file->error = Q_ERR_SUCCESS;
1238  file->rest_out = entry->filelen;
1239  file->length = entry->filelen;
1240 
1241 #if USE_ZLIB
1242  if (pack->type == FS_ZIP) {
1243  if (file->mode & FS_FLAG_DEFLATE) {
1244  // server wants raw deflated data for downloads
1245  file->type = FS_PAK;
1246  file->rest_out = entry->complen;
1247  file->length = entry->complen;
1248  } else if (entry->compmtd) {
1249  open_zip_file(file);
1250  } else {
1251  // stored, just pretend it's a packfile
1252  file->type = FS_PAK;
1253  }
1254  }
1255 #endif
1256 
1257  if (unique) {
1258  // reference source pak
1259  pack_get(pack);
1260  }
1261 
1262  FS_DPrintf("%s: %s/%s: %"PRIz" bytes\n",
1263  __func__, pack->filename, entry->name, file->length);
1264 
1265  return file->length;
1266 
1267 fail2:
1268  if (unique) {
1269  fclose(fp);
1270  }
1271 fail1:
1272  FS_DPrintf("%s: %s/%s: %s\n", __func__, pack->filename, entry->name, Q_ErrorString(ret));
1273  return ret;
1274 }
1275 
1276 static ssize_t open_from_disk(file_t *file, const char *fullpath)
1277 {
1278  FILE *fp;
1279  file_info_t info;
1280  qerror_t ret;
1281 
1282  FS_COUNT_OPEN;
1283 
1284  fp = fopen(fullpath, "rb");
1285  if (!fp) {
1286  ret = Q_Errno();
1287  goto fail;
1288  }
1289 
1290  ret = get_fp_info(fp, &info);
1291  if (ret) {
1292  fclose(fp);
1293  goto fail;
1294  }
1295 
1296  file->type = FS_REAL;
1297  file->fp = fp;
1298  file->unique = qtrue;
1299  file->error = Q_ERR_SUCCESS;
1300  file->length = info.size;
1301 
1302  FS_DPrintf("%s: %s: %"PRIz" bytes\n", __func__, fullpath, info.size);
1303  return info.size;
1304 
1305 fail:
1306  FS_DPrintf("%s: %s: %s\n", __func__, fullpath, Q_ErrorString(ret));
1307  return ret;
1308 }
1309 
1310 qerror_t FS_LastModified(char const * file, uint64_t * last_modified)
1311 {
1312 #ifndef NO_TEXTURE_RELOADS
1313  char fullpath[MAX_OSPATH];
1314  searchpath_t *search;
1315  int valid;
1316  size_t len;
1317 
1318  valid = PATH_NOT_CHECKED;
1319 
1320  for (search = fs_searchpaths; search; search = search->next) {
1321 
1322  // skip paks
1323  if (search->pack)
1324  continue;
1325 
1326  // don't error out immediately if the path is found to be invalid,
1327  // just stop looking for it in directory tree but continue to search
1328  // for it in packs, to give broken maps or mods a chance to work
1329  if (valid == PATH_NOT_CHECKED) {
1330  valid = FS_ValidatePath(file);
1331  }
1332  if (valid == PATH_INVALID) {
1333  continue;
1334  }
1335 
1336  // check a file in the directory tree
1337  len = Q_concat(fullpath, sizeof(fullpath), search->filename, "/", file, NULL);
1338  if (len >= sizeof(fullpath)) {
1339  return Q_ERR_NAMETOOLONG;
1340  }
1341 
1342  struct stat lstat;
1343  if (stat(fullpath, &lstat) == 0) {
1344  if (last_modified)
1345  *last_modified = lstat.st_mtime;
1346  return Q_ERR_SUCCESS;
1347  }
1348  }
1349 #else
1350  if (last_modified)
1351  *last_modified = 0;
1352 #endif
1353  return Q_ERR_INVALID_PATH;
1354 }
1355 
1356 // Finds the file in the search path.
1357 // Fills file_t and returns file length.
1358 // Used for streaming data out of either a pak file or a seperate file.
1359 static ssize_t open_file_read(file_t *file, const char *normalized, size_t namelen, qboolean unique)
1360 {
1361  char fullpath[MAX_OSPATH];
1362  searchpath_t *search;
1363  pack_t *pak;
1364  unsigned hash;
1365  packfile_t *entry;
1366  ssize_t ret;
1367  int valid;
1368  size_t len;
1369 
1370  FS_COUNT_READ;
1371 
1372  hash = FS_HashPath(normalized, 0);
1373 
1374  valid = PATH_NOT_CHECKED;
1375 
1376 // search through the path, one element at a time
1377  for (search = fs_searchpaths; search; search = search->next) {
1378  if (file->mode & FS_PATH_MASK) {
1379  if ((file->mode & search->mode & FS_PATH_MASK) == 0) {
1380  continue;
1381  }
1382  }
1383 
1384  // is the element a pak file?
1385  if (search->pack) {
1386  if ((file->mode & FS_TYPE_MASK) == FS_TYPE_REAL) {
1387  continue;
1388  }
1389  // don't bother searching in paks if length exceedes MAX_QPATH
1390  if (namelen >= MAX_QPATH) {
1391  continue;
1392  }
1393  pak = search->pack;
1394 #if USE_ZLIB
1395  if ((file->mode & FS_FLAG_DEFLATE) && pak->type != FS_ZIP) {
1396  continue;
1397  }
1398 #endif
1399  // look through all the pak file elements
1400  entry = pak->file_hash[hash & (pak->hash_size - 1)];
1401  for (; entry; entry = entry->hash_next) {
1402  if (entry->namelen != namelen) {
1403  continue;
1404  }
1405 #if USE_ZLIB
1406  if ((file->mode & FS_FLAG_DEFLATE) && entry->compmtd != Z_DEFLATED) {
1407  continue;
1408  }
1409 #endif
1411  if (!FS_pathcmp(entry->name, normalized)) {
1412  // found it!
1413  return open_from_pak(file, pak, entry, unique);
1414  }
1415  }
1416  } else {
1417  if ((file->mode & FS_TYPE_MASK) == FS_TYPE_PAK) {
1418  continue;
1419  }
1420 #if USE_ZLIB
1421  if (file->mode & FS_FLAG_DEFLATE) {
1422  continue;
1423  }
1424 #endif
1425  // don't error out immediately if the path is found to be invalid,
1426  // just stop looking for it in directory tree but continue to search
1427  // for it in packs, to give broken maps or mods a chance to work
1428  if (valid == PATH_NOT_CHECKED) {
1429  valid = FS_ValidatePath(normalized);
1430  }
1431  if (valid == PATH_INVALID) {
1432  continue;
1433  }
1434  // check a file in the directory tree
1435  len = Q_concat(fullpath, sizeof(fullpath),
1436  search->filename, "/", normalized, NULL);
1437  if (len >= sizeof(fullpath)) {
1438  ret = Q_ERR_NAMETOOLONG;
1439  goto fail;
1440  }
1441 
1442  ret = open_from_disk(file, fullpath);
1443  if (ret != Q_ERR_NOENT)
1444  return ret;
1445 
1446 #ifndef _WIN32
1447  if (valid == PATH_MIXED_CASE) {
1448  // convert to lower case and retry
1450  Q_strlwr(fullpath + strlen(search->filename) + 1);
1451  ret = open_from_disk(file, fullpath);
1452  if (ret != Q_ERR_NOENT)
1453  return ret;
1454  }
1455 #endif
1456  }
1457  }
1458 
1459  // return error if path was checked and found to be invalid
1460  ret = valid ? Q_ERR_NOENT : Q_ERR_INVALID_PATH;
1461 
1462 fail:
1463  FS_DPrintf("%s: %s: %s\n", __func__, normalized, Q_ErrorString(ret));
1464  return ret;
1465 }
1466 
1467 // Normalizes quake path, expands symlinks
1468 static ssize_t expand_open_file_read(file_t *file, const char *name, qboolean unique)
1469 {
1470  char normalized[MAX_OSPATH];
1471  ssize_t ret;
1472  size_t namelen;
1473 
1474 // normalize path
1475  namelen = FS_NormalizePathBuffer(normalized, name, MAX_OSPATH);
1476  if (namelen >= MAX_OSPATH) {
1477  return Q_ERR_NAMETOOLONG;
1478  }
1479 
1480 // expand hard symlinks
1481  if (expand_links(&fs_hard_links, normalized, &namelen) && namelen >= MAX_OSPATH) {
1482  return Q_ERR_NAMETOOLONG;
1483  }
1484 
1485 // reject empty paths
1486  if (namelen == 0) {
1487  return Q_ERR_NAMETOOSHORT;
1488  }
1489 
1490  ret = open_file_read(file, normalized, namelen, unique);
1491  if (ret == Q_ERR_NOENT) {
1492 // expand soft symlinks
1493  if (expand_links(&fs_soft_links, normalized, &namelen)) {
1494  if (namelen >= MAX_OSPATH) {
1495  return Q_ERR_NAMETOOLONG;
1496  }
1497  ret = open_file_read(file, normalized, namelen, unique);
1498  }
1499  }
1500 
1501  return ret;
1502 }
1503 
1504 static ssize_t read_pak_file(file_t *file, void *buf, size_t len)
1505 {
1506  size_t result;
1507 
1508  if (len > file->rest_out) {
1509  len = file->rest_out;
1510  }
1511  if (!len) {
1512  return 0;
1513  }
1514 
1515  result = fread(buf, 1, len, file->fp);
1516  if (result != len) {
1517  file->error = FS_ERR_READ(file->fp);
1518  if (!result) {
1519  return file->error;
1520  }
1521  }
1522 
1523  file->rest_out -= result;
1524  return result;
1525 }
1526 
1527 static ssize_t read_phys_file(file_t *file, void *buf, size_t len)
1528 {
1529  size_t result;
1530 
1531  result = fread(buf, 1, len, file->fp);
1532  if (result != len && ferror(file->fp)) {
1533  file->error = Q_Errno();
1534  if (!result) {
1535  return file->error;
1536  }
1537  }
1538 
1539  return result;
1540 }
1541 
1542 /*
1543 =================
1544 FS_Read
1545 =================
1546 */
1547 ssize_t FS_Read(void *buf, size_t len, qhandle_t f)
1548 {
1549  file_t *file = file_for_handle(f);
1550 #if USE_ZLIB
1551  int ret;
1552 #endif
1553 
1554  if (!file)
1555  return Q_ERR_BADF;
1556 
1557  if ((file->mode & FS_MODE_MASK) != FS_MODE_READ)
1558  return Q_ERR_INVAL;
1559 
1560  // can't continue after error
1561  if (file->error)
1562  return file->error;
1563 
1564  if (len > SSIZE_MAX)
1565  return Q_ERR_INVAL;
1566 
1567  if (len == 0)
1568  return 0;
1569 
1570  switch (file->type) {
1571  case FS_REAL:
1572  return read_phys_file(file, buf, len);
1573  case FS_PAK:
1574  return read_pak_file(file, buf, len);
1575 #if USE_ZLIB
1576  case FS_GZ:
1577  ret = gzread(file->zfp, buf, len);
1578  if (ret < 0) {
1579  return Q_ERR_LIBRARY_ERROR;
1580  }
1581  return ret;
1582  case FS_ZIP:
1583  return read_zip_file(file, buf, len);
1584 #endif
1585  default:
1586  return Q_ERR_NOSYS;
1587  }
1588 }
1589 
1590 ssize_t FS_ReadLine(qhandle_t f, char *buffer, size_t size)
1591 {
1592  file_t *file = file_for_handle(f);
1593  char *s;
1594  size_t len;
1595 
1596  if (!file)
1597  return Q_ERR_BADF;
1598 
1599  if ((file->mode & FS_MODE_MASK) != FS_MODE_READ)
1600  return Q_ERR_INVAL;
1601 
1602  if (file->type != FS_REAL)
1603  return Q_ERR_NOSYS;
1604 
1605  do {
1606  s = fgets(buffer, size, file->fp);
1607  if (!s) {
1608  return ferror(file->fp) ? Q_Errno() : 0;
1609  }
1610  len = strlen(s);
1611  } while (len < 2);
1612 
1613  s[len - 1] = 0;
1614  return len - 1;
1615 }
1616 
1617 void FS_Flush(qhandle_t f)
1618 {
1619  file_t *file = file_for_handle(f);
1620 
1621  if (!file)
1622  return;
1623 
1624  switch (file->type) {
1625  case FS_REAL:
1626  fflush(file->fp);
1627  break;
1628 #if USE_ZLIB
1629  case FS_GZ:
1630  gzflush(file->zfp, Z_SYNC_FLUSH);
1631  break;
1632 #endif
1633  default:
1634  break;
1635  }
1636 }
1637 
1638 /*
1639 =================
1640 FS_Write
1641 =================
1642 */
1643 ssize_t FS_Write(const void *buf, size_t len, qhandle_t f)
1644 {
1645  file_t *file = file_for_handle(f);
1646  size_t result;
1647 
1648  if (!file)
1649  return Q_ERR_BADF;
1650 
1651  if ((file->mode & FS_MODE_MASK) == FS_MODE_READ)
1652  return Q_ERR_INVAL;
1653 
1654  // can't continue after error
1655  if (file->error)
1656  return file->error;
1657 
1658  if (len > SSIZE_MAX)
1659  return Q_ERR_INVAL;
1660 
1661  if (len == 0)
1662  return 0;
1663 
1664  switch (file->type) {
1665  case FS_REAL:
1666  result = fwrite(buf, 1, len, file->fp);
1667  if (result != len) {
1668  file->error = FS_ERR_WRITE(file->fp);
1669  return file->error;
1670  }
1671  break;
1672 #if USE_ZLIB
1673  case FS_GZ:
1674  if (gzwrite(file->zfp, buf, len) == 0) {
1675  file->error = Q_ERR_LIBRARY_ERROR;
1676  return file->error;
1677  }
1678  break;
1679 #endif
1680  default:
1681  Com_Error(ERR_FATAL, "%s: bad file type", __func__);
1682  }
1683 
1684  return len;
1685 }
1686 
1687 /*
1688 ============
1689 FS_FOpenFile
1690 ============
1691 */
1692 ssize_t FS_FOpenFile(const char *name, qhandle_t *f, unsigned mode)
1693 {
1694  file_t *file;
1695  qhandle_t handle;
1696  ssize_t ret;
1697 
1698  if (!name || !f) {
1699  Com_Error(ERR_FATAL, "%s: NULL", __func__);
1700  }
1701 
1702  *f = 0;
1703 
1704  if (!fs_searchpaths) {
1705  return Q_ERR_AGAIN; // not yet initialized
1706  }
1707 
1708  // allocate new file handle
1709  file = alloc_handle(&handle);
1710  if (!file) {
1711  return Q_ERR_MFILE;
1712  }
1713 
1714  file->mode = mode;
1715 
1716  if ((mode & FS_MODE_MASK) == FS_MODE_READ) {
1717  ret = expand_open_file_read(file, name, qtrue);
1718  } else {
1719  ret = open_file_write(file, name);
1720  }
1721 
1722  if (ret >= 0) {
1723  *f = handle;
1724  }
1725 
1726  return ret;
1727 }
1728 
1729 // reading from outside of source directory is allowed, extension is optional
1730 static qhandle_t easy_open_read(char *buf, size_t size, unsigned mode,
1731  const char *dir, const char *name, const char *ext)
1732 {
1733  ssize_t len;
1734  qhandle_t f;
1735 
1736  if (*name == '/') {
1737  // full path is given, ignore directory and extension
1738  len = Q_strlcpy(buf, name + 1, size);
1739  } else {
1740  // first try without extension
1741  len = Q_concat(buf, size, dir, name, NULL);
1742  if (len >= size) {
1743  Q_PrintError("open", Q_ERR_NAMETOOLONG);
1744  return 0;
1745  }
1746 
1747  // print normalized path in case of error
1748  FS_NormalizePath(buf, buf);
1749 
1750  len = FS_FOpenFile(buf, &f, mode);
1751  if (f) {
1752  return f; // succeeded
1753  }
1754  if (len != Q_ERR_NOENT) {
1755  goto fail; // fatal error
1756  }
1757  if (!COM_CompareExtension(buf, ext)) {
1758  goto fail; // name already has the extension
1759  }
1760 
1761  // now try to append extension
1762  len = Q_strlcat(buf, ext, size);
1763  }
1764 
1765  if (len >= size) {
1766  Q_PrintError("open", Q_ERR_NAMETOOLONG);
1767  return 0;
1768  }
1769 
1770  len = FS_FOpenFile(buf, &f, mode);
1771  if (f) {
1772  return f;
1773  }
1774 
1775 fail:
1776  Com_Printf("Couldn't open %s: %s\n", buf, Q_ErrorString(len));
1777  return 0;
1778 }
1779 
1780 // writing to outside of destination directory is disallowed, extension is forced
1781 static qhandle_t easy_open_write(char *buf, size_t size, unsigned mode,
1782  const char *dir, const char *name, const char *ext)
1783 {
1784  char normalized[MAX_OSPATH];
1785  ssize_t len;
1786  qhandle_t f;
1787 
1788  // make it impossible to escape the destination directory when writing files
1789  len = FS_NormalizePathBuffer(normalized, name, sizeof(normalized));
1790  if (len >= sizeof(normalized)) {
1791  Q_PrintError("open", Q_ERR_NAMETOOLONG);
1792  return 0;
1793  }
1794 
1795  // reject empty filenames
1796  if (len == 0) {
1797  Q_PrintError("open", Q_ERR_NAMETOOSHORT);
1798  return 0;
1799  }
1800 
1801  // replace any bad characters with underscores to make automatic commands happy
1802  cleanup_path(normalized);
1803 
1804  // don't append the extension if name already has it
1805  if (!COM_CompareExtension(normalized, ext)) {
1806  ext = (mode & FS_FLAG_GZIP) ? "" : NULL;
1807  }
1808 
1809  len = Q_concat(buf, size, dir, normalized, ext,
1810  (mode & FS_FLAG_GZIP) ? ".gz" : NULL, NULL);
1811  if (len >= size) {
1812  Q_PrintError("open", Q_ERR_NAMETOOLONG);
1813  return 0;
1814  }
1815 
1816  len = FS_FOpenFile(buf, &f, mode);
1817  if (!f) {
1818  goto fail1;
1819  }
1820 
1821  if (mode & FS_FLAG_GZIP) {
1822  len = FS_FilterFile(f);
1823  if (len) {
1824  goto fail2;
1825  }
1826  }
1827 
1828  return f;
1829 
1830 fail2:
1831  FS_FCloseFile(f);
1832 fail1:
1833  Com_EPrintf("Couldn't open %s: %s\n", buf, Q_ErrorString(len));
1834  return 0;
1835 }
1836 
1837 /*
1838 ============
1839 FS_EasyOpenFile
1840 
1841 Helper function for various console commands. Concatenates
1842 the arguments, checks for path buffer overflow, and attempts
1843 to open the file, printing an error message in case of failure.
1844 ============
1845 */
1846 qhandle_t FS_EasyOpenFile(char *buf, size_t size, unsigned mode,
1847  const char *dir, const char *name, const char *ext)
1848 {
1849  if ((mode & FS_MODE_MASK) == FS_MODE_READ) {
1850  return easy_open_read(buf, size, mode, dir, name, ext);
1851  }
1852 
1853  return easy_open_write(buf, size, mode, dir, name, ext);
1854 }
1855 
1856 /*
1857 ============
1858 FS_LoadFile
1859 
1860 opens non-unique file handle as an optimization
1861 a NULL buffer will just return the file length without loading
1862 ============
1863 */
1864 ssize_t FS_LoadFileEx(const char *path, void **buffer, unsigned flags, memtag_t tag)
1865 {
1866  file_t *file;
1867  qhandle_t f;
1868  byte *buf;
1869  ssize_t len, read;
1870 
1871  if (!path) {
1872  Com_Error(ERR_FATAL, "%s: NULL", __func__);
1873  }
1874 
1875  if (buffer) {
1876  *buffer = NULL;
1877  }
1878 
1879  if (!fs_searchpaths) {
1880  return Q_ERR_AGAIN; // not yet initialized
1881  }
1882 
1883  // allocate new file handle
1884  file = alloc_handle(&f);
1885  if (!file) {
1886  return Q_ERR_MFILE;
1887  }
1888 
1889  file->mode = (flags & ~FS_MODE_MASK) | FS_MODE_READ;
1890 
1891  // look for it in the filesystem or pack files
1892  len = expand_open_file_read(file, path, qfalse);
1893  if (len < 0) {
1894  return len;
1895  }
1896 
1897  // NULL buffer just checks for file existence
1898  if (!buffer) {
1899  goto done;
1900  }
1901 
1902  // sanity check file size
1903  if (len > MAX_LOADFILE) {
1904  len = Q_ERR_FBIG;
1905  goto done;
1906  }
1907 
1908  // allocate chunk of memory, +1 for NUL
1909  buf = Z_TagMalloc(len + 1, tag);
1910 
1911  // read entire file
1912  read = FS_Read(buf, len, f);
1913  if (read != len) {
1914  len = read < 0 ? read : Q_ERR_UNEXPECTED_EOF;
1915  Z_Free(buf);
1916  goto done;
1917  }
1918 
1919  *buffer = buf;
1920  buf[len] = 0;
1921 
1922 done:
1923  FS_FCloseFile(f);
1924  return len;
1925 }
1926 
1927 /*
1928 ================
1929 FS_WriteFile
1930 ================
1931 */
1932 qerror_t FS_WriteFile(const char *path, const void *data, size_t len)
1933 {
1934  qhandle_t f;
1935  ssize_t write;
1936  qerror_t ret;
1937 
1938  // TODO: write to temp file perhaps?
1939  write = FS_FOpenFile(path, &f, FS_MODE_WRITE);
1940  if (!f) {
1941  return write;
1942  }
1943 
1944  write = FS_Write(data, len, f);
1945  ret = write == len ? Q_ERR_SUCCESS : write < 0 ? write : Q_ERR_FAILURE;
1946 
1947  FS_FCloseFile(f);
1948  return ret;
1949 }
1950 
1951 /*
1952 ============
1953 FS_EasyWriteFile
1954 
1955 Helper function for various console commands. Concatenates
1956 the arguments, checks for path buffer overflow, and attempts
1957 to write the file, printing an error message in case of failure.
1958 ============
1959 */
1960 qboolean FS_EasyWriteFile(char *buf, size_t size, unsigned mode,
1961  const char *dir, const char *name, const char *ext,
1962  const void *data, size_t len)
1963 {
1964  qhandle_t f;
1965  ssize_t write;
1966  qerror_t ret;
1967 
1968  // TODO: write to temp file perhaps?
1969  f = easy_open_write(buf, size, mode, dir, name, ext);
1970  if (!f) {
1971  return qfalse;
1972  }
1973 
1974  write = FS_Write(data, len, f);
1975  ret = write == len ? Q_ERR_SUCCESS : write < 0 ? write : Q_ERR_FAILURE;
1976 
1977  FS_FCloseFile(f);
1978 
1979  if (ret) {
1980  Com_EPrintf("Couldn't write %s: %s\n", buf, Q_ErrorString(ret));
1981  return qfalse;
1982  }
1983 
1984  return qtrue;
1985 }
1986 
1987 #if USE_CLIENT
1988 
1989 /*
1990 ================
1991 FS_RenameFile
1992 ================
1993 */
1994 qerror_t FS_RenameFile(const char *from, const char *to)
1995 {
1996  char normalized[MAX_OSPATH];
1997  char frompath[MAX_OSPATH];
1998  char topath[MAX_OSPATH];
1999  size_t len;
2000 
2001  // check from
2002  len = FS_NormalizePathBuffer(normalized, from, sizeof(normalized));
2003  if (len >= sizeof(normalized))
2004  return Q_ERR_NAMETOOLONG;
2005 
2006  if (!FS_ValidatePath(normalized))
2007  return Q_ERR_INVALID_PATH;
2008 
2009  len = Q_concat(frompath, sizeof(frompath), fs_gamedir, "/", normalized, NULL);
2010  if (len >= sizeof(frompath))
2011  return Q_ERR_NAMETOOLONG;
2012 
2013  // check to
2014  len = FS_NormalizePathBuffer(normalized, to, sizeof(normalized));
2015  if (len >= sizeof(normalized))
2016  return Q_ERR_NAMETOOLONG;
2017 
2018  if (!FS_ValidatePath(normalized))
2019  return Q_ERR_INVALID_PATH;
2020 
2021  len = Q_concat(topath, sizeof(topath), fs_gamedir, "/", normalized, NULL);
2022  if (len >= sizeof(topath))
2023  return Q_ERR_NAMETOOLONG;
2024 
2025  // rename it
2026  if (rename(frompath, topath))
2027  return Q_Errno();
2028 
2029  return Q_ERR_SUCCESS;
2030 }
2031 
2032 #endif // USE_CLIENT
2033 
2034 /*
2035 ================
2036 FS_FPrintf
2037 ================
2038 */
2039 ssize_t FS_FPrintf(qhandle_t f, const char *format, ...)
2040 {
2041  va_list argptr;
2042  char string[MAXPRINTMSG];
2043  size_t len;
2044 
2045  va_start(argptr, format);
2046  len = Q_vsnprintf(string, sizeof(string), format, argptr);
2047  va_end(argptr);
2048 
2049  if (len >= sizeof(string)) {
2050  return Q_ERR_STRING_TRUNCATED;
2051  }
2052 
2053  return FS_Write(string, len, f);
2054 }
2055 
2056 // references pack_t instance
2058 {
2059  pack->refcount++;
2060  return pack;
2061 }
2062 
2063 // dereferences pack_t instance
2064 static void pack_put(pack_t *pack)
2065 {
2066  if (!pack) {
2067  return;
2068  }
2069  if (!pack->refcount) {
2070  Com_Error(ERR_FATAL, "%s: refcount already zero", __func__);
2071  }
2072  if (!--pack->refcount) {
2073  FS_DPrintf("Freeing packfile %s\n", pack->filename);
2074  fclose(pack->fp);
2075  Z_Free(pack);
2076  }
2077 }
2078 
2079 // allocates pack_t instance along with filenames and hashes in one chunk of memory
2080 static pack_t *pack_alloc(FILE *fp, filetype_t type, const char *name,
2081  unsigned num_files, size_t names_len)
2082 {
2083  pack_t *pack;
2084  unsigned hash_size;
2085  size_t len;
2086 
2087  hash_size = npot32(num_files / 3);
2088 
2089  len = strlen(name) + 1;
2090  pack = FS_Malloc(sizeof(pack_t) +
2091  num_files * sizeof(packfile_t) +
2092  hash_size * sizeof(packfile_t *) +
2093  len + names_len);
2094  pack->type = type;
2095  pack->refcount = 0;
2096  pack->fp = fp;
2097  pack->num_files = num_files;
2098  pack->hash_size = hash_size;
2099  pack->files = (packfile_t *)(pack + 1);
2100  pack->file_hash = (packfile_t **)(pack->files + num_files);
2101  pack->filename = (char *)(pack->file_hash + hash_size);
2102  pack->names = pack->filename + len;
2103  memcpy(pack->filename, name, len);
2104  memset(pack->file_hash, 0, hash_size * sizeof(packfile_t *));
2105 
2106  return pack;
2107 }
2108 
2109 // normalizes and inserts the filename into hash table
2110 static void pack_hash_file(pack_t *pack, packfile_t *file)
2111 {
2112  unsigned hash;
2113 
2114  file->namelen = FS_NormalizePath(file->name, file->name);
2115 
2116  hash = FS_HashPath(file->name, pack->hash_size);
2117  file->hash_next = pack->file_hash[hash];
2118  pack->file_hash[hash] = file;
2119 }
2120 
2121 // Loads the header and directory, adding the files at the beginning
2122 // of the list so they override previous pack files.
2123 static pack_t *load_pak_file(const char *packfile)
2124 {
2125  dpackheader_t header;
2126  packfile_t *file;
2127  dpackfile_t *dfile;
2128  unsigned i, num_files;
2129  char *name;
2130  size_t len, names_len;
2131  pack_t *pack;
2132  FILE *fp;
2133  dpackfile_t info[MAX_FILES_IN_PACK];
2134 
2135  fp = fopen(packfile, "rb");
2136  if (!fp) {
2137  Com_Printf("Couldn't open %s: %s\n", packfile, strerror(errno));
2138  return NULL;
2139  }
2140 
2141  if (fread(&header, 1, sizeof(header), fp) != sizeof(header)) {
2142  Com_Printf("Reading header failed on %s\n", packfile);
2143  goto fail;
2144  }
2145 
2146  if (LittleLong(header.ident) != IDPAKHEADER) {
2147  Com_Printf("%s is not a 'PACK' file\n", packfile);
2148  goto fail;
2149  }
2150 
2151  header.dirlen = LittleLong(header.dirlen);
2152  if (header.dirlen > INT_MAX || header.dirlen % sizeof(dpackfile_t)) {
2153  Com_Printf("%s has bad directory length\n", packfile);
2154  goto fail;
2155  }
2156 
2157  num_files = header.dirlen / sizeof(dpackfile_t);
2158  if (num_files < 1) {
2159  Com_Printf("%s has no files\n", packfile);
2160  goto fail;
2161  }
2162  if (num_files > MAX_FILES_IN_PACK) {
2163  Com_Printf("%s has too many files: %u > %u\n", packfile, num_files, MAX_FILES_IN_PACK);
2164  goto fail;
2165  }
2166 
2167  header.dirofs = LittleLong(header.dirofs);
2168  if (header.dirofs > LONG_MAX - header.dirlen) {
2169  Com_Printf("%s has bad directory offset\n", packfile);
2170  goto fail;
2171  }
2172  if (fseek(fp, (long)header.dirofs, SEEK_SET)) {
2173  Com_Printf("Seeking to directory failed on %s\n", packfile);
2174  goto fail;
2175  }
2176  if (fread(info, 1, header.dirlen, fp) != header.dirlen) {
2177  Com_Printf("Reading directory failed on %s\n", packfile);
2178  goto fail;
2179  }
2180 
2181  names_len = 0;
2182  for (i = 0, dfile = info; i < num_files; i++, dfile++) {
2183  dfile->filepos = LittleLong(dfile->filepos);
2184  dfile->filelen = LittleLong(dfile->filelen);
2185  if (dfile->filelen > INT_MAX || dfile->filepos > INT_MAX - dfile->filelen) {
2186  Com_Printf("%s has bad directory structure\n", packfile);
2187  goto fail;
2188  }
2189  dfile->name[sizeof(dfile->name) - 1] = 0;
2190  names_len += strlen(dfile->name) + 1;
2191  }
2192 
2193 // allocate the pack
2194  pack = pack_alloc(fp, FS_PAK, packfile, num_files, names_len);
2195 
2196 // parse the directory
2197  file = pack->files;
2198  name = pack->names;
2199  for (i = 0, dfile = info; i < num_files; i++, dfile++) {
2200  len = strlen(dfile->name) + 1;
2201 
2202  file->name = memcpy(name, dfile->name, len);
2203  name += len;
2204 
2205  file->filepos = dfile->filepos;
2206  file->filelen = dfile->filelen;
2207 #if USE_ZLIB
2208  file->coherent = qtrue;
2209 #endif
2210 
2211  pack_hash_file(pack, file);
2212  file++;
2213  }
2214 
2215  FS_DPrintf("%s: %u files, %u hash\n",
2216  packfile, pack->num_files, pack->hash_size);
2217 
2218  return pack;
2219 
2220 fail:
2221  fclose(fp);
2222  return NULL;
2223 }
2224 
2225 #if USE_ZLIB
2226 
2227 // Locate the central directory of a zipfile (at the end, just before the global comment)
2228 static size_t search_central_header(FILE *fp)
2229 {
2230  size_t file_size, back_read;
2231  size_t max_back = 0xffff; // maximum size of global comment
2232  byte buf[ZIP_BUFREADCOMMENT + 4];
2233  long ret;
2234 
2235  if (fseek(fp, 0, SEEK_END) == -1)
2236  return 0;
2237 
2238  ret = ftell(fp);
2239  if (ret == -1)
2240  return 0;
2241  file_size = (size_t)ret;
2242  if (max_back > file_size)
2243  max_back = file_size;
2244 
2245  back_read = 4;
2246  while (back_read < max_back) {
2247  size_t i, read_size, read_pos;
2248 
2249  if (back_read + ZIP_BUFREADCOMMENT > max_back)
2250  back_read = max_back;
2251  else
2252  back_read += ZIP_BUFREADCOMMENT;
2253 
2254  read_pos = file_size - back_read;
2255 
2256  read_size = back_read;
2257  if (read_size > ZIP_BUFREADCOMMENT + 4)
2258  read_size = ZIP_BUFREADCOMMENT + 4;
2259 
2260  if (fseek(fp, (long)read_pos, SEEK_SET) == -1)
2261  break;
2262  if (fread(buf, 1, read_size, fp) != read_size)
2263  break;
2264 
2265  i = read_size - 4;
2266  do {
2267  // check the magic
2268  if (LittleLongMem(buf + i) == ZIP_ENDHEADERMAGIC)
2269  return read_pos + i;
2270  } while (i--);
2271  }
2272 
2273  return 0;
2274 }
2275 
2276 // Get Info about the current file in the zipfile, with internal only info
2277 static size_t get_file_info(FILE *fp, size_t pos, packfile_t *file, size_t *len, size_t remaining)
2278 {
2279  size_t name_size, xtra_size, comm_size;
2280  size_t comp_len, file_len, file_pos;
2281  unsigned comp_mtd;
2282  byte header[ZIP_SIZECENTRALDIRITEM]; // we can't use a struct here because of packing
2283 
2284  *len = 0;
2285 
2286  if (pos > LONG_MAX)
2287  return 0;
2288  if (fseek(fp, (long)pos, SEEK_SET) == -1)
2289  return 0;
2290  if (fread(header, 1, sizeof(header), fp) != sizeof(header))
2291  return 0;
2292 
2293  // check the magic
2294  if (LittleLongMem(&header[0]) != ZIP_CENTRALHEADERMAGIC)
2295  return 0;
2296 
2297  comp_mtd = LittleShortMem(&header[10]);
2298  comp_len = LittleLongMem(&header[20]);
2299  file_len = LittleLongMem(&header[24]);
2300  name_size = LittleShortMem(&header[28]);
2301  xtra_size = LittleShortMem(&header[30]);
2302  comm_size = LittleShortMem(&header[32]);
2303  file_pos = LittleLongMem(&header[42]);
2304 
2305  if (file_len > LONG_MAX)
2306  return 0;
2307  if (comp_len > LONG_MAX || file_pos > LONG_MAX - comp_len)
2308  return 0;
2309 
2310  if (!file_len || !comp_len) {
2311  goto skip; // skip directories and empty files
2312  }
2313  if (!comp_mtd) {
2314  if (file_len != comp_len) {
2315  FS_DPrintf("%s: skipping file stored with file_len != comp_len\n", __func__);
2316  goto skip;
2317  }
2318  } else if (comp_mtd != Z_DEFLATED) {
2319  FS_DPrintf("%s: skipping file compressed with unknown method\n", __func__);
2320  goto skip;
2321  }
2322  if (!name_size) {
2323  FS_DPrintf("%s: skipping file with empty name\n", __func__);
2324  goto skip;
2325  }
2326  if (name_size >= MAX_QPATH) {
2327  FS_DPrintf("%s: skipping file with oversize name\n", __func__);
2328  goto skip;
2329  }
2330 
2331  // fill in the info
2332  if (file) {
2333  if (name_size >= remaining)
2334  return 0; // directory changed on disk?
2335  file->compmtd = comp_mtd;
2336  file->complen = comp_len;
2337  file->filelen = file_len;
2338  file->filepos = file_pos;
2339  if (fread(file->name, 1, name_size, fp) != name_size)
2340  return 0;
2341  file->name[name_size] = 0;
2342  }
2343 
2344  *len = name_size + 1;
2345 
2346 skip:
2347  return ZIP_SIZECENTRALDIRITEM + name_size + xtra_size + comm_size;
2348 }
2349 
2350 static pack_t *load_zip_file(const char *packfile)
2351 {
2352  packfile_t *file;
2353  char *name;
2354  size_t len, names_len;
2355  unsigned i, num_disk, num_disk_cd, num_files, num_files_cd;
2356  size_t header_pos, central_ofs, central_size, central_end;
2357  size_t extra_bytes, ofs;
2358  pack_t *pack;
2359  FILE *fp;
2360  byte header[ZIP_SIZECENTRALHEADER];
2361 
2362  fp = fopen(packfile, "rb");
2363  if (!fp) {
2364  Com_Printf("Couldn't open %s: %s\n", packfile, strerror(errno));
2365  return NULL;
2366  }
2367 
2368  header_pos = search_central_header(fp);
2369  if (!header_pos) {
2370  Com_Printf("No central header found in %s\n", packfile);
2371  goto fail2;
2372  }
2373  if (fseek(fp, (long)header_pos, SEEK_SET) == -1) {
2374  Com_Printf("Couldn't seek to central header in %s\n", packfile);
2375  goto fail2;
2376  }
2377  if (fread(header, 1, sizeof(header), fp) != sizeof(header)) {
2378  Com_Printf("Reading central header failed on %s\n", packfile);
2379  goto fail2;
2380  }
2381 
2382  num_disk = LittleShortMem(&header[4]);
2383  num_disk_cd = LittleShortMem(&header[6]);
2384  num_files = LittleShortMem(&header[8]);
2385  num_files_cd = LittleShortMem(&header[10]);
2386  if (num_files_cd != num_files || num_disk_cd != 0 || num_disk != 0) {
2387  Com_Printf("%s is an unsupported multi-part archive\n", packfile);
2388  goto fail2;
2389  }
2390  if (num_files < 1) {
2391  Com_Printf("%s has no files\n", packfile);
2392  goto fail2;
2393  }
2394  if (num_files > ZIP_MAXFILES) {
2395  Com_Printf("%s has too many files: %u > %u\n", packfile, num_files, ZIP_MAXFILES);
2396  goto fail2;
2397  }
2398 
2399  central_size = LittleLongMem(&header[12]);
2400  central_ofs = LittleLongMem(&header[16]);
2401  central_end = central_ofs + central_size;
2402  if (central_end > header_pos || central_end < central_ofs) {
2403  Com_Printf("%s has bad central directory offset\n", packfile);
2404  goto fail2;
2405  }
2406 
2407 // non-zero for sfx?
2408  extra_bytes = header_pos - central_end;
2409  if (extra_bytes) {
2410  Com_Printf("%s has %"PRIz" extra bytes at the beginning, funny sfx archive?\n",
2411  packfile, extra_bytes);
2412  }
2413 
2414 // parse the directory
2415  num_files = 0;
2416  names_len = 0;
2417  header_pos = central_ofs + extra_bytes;
2418  for (i = 0; i < num_files_cd; i++) {
2419  ofs = get_file_info(fp, header_pos, NULL, &len, 0);
2420  if (!ofs) {
2421  Com_Printf("%s has bad central directory structure (pass %d)\n", packfile, 1);
2422  goto fail2;
2423  }
2424  header_pos += ofs;
2425 
2426  if (len) {
2427  names_len += len;
2428  num_files++;
2429  }
2430  }
2431 
2432  if (!num_files) {
2433  Com_Printf("%s has no valid files\n", packfile);
2434  goto fail2;
2435  }
2436 
2437 // allocate the pack
2438  pack = pack_alloc(fp, FS_ZIP, packfile, num_files, names_len);
2439 
2440 // parse the directory
2441  file = pack->files;
2442  name = pack->names;
2443  header_pos = central_ofs + extra_bytes;
2444  for (i = 0; i < num_files_cd; i++) {
2445  if (!num_files)
2446  break;
2447  file->name = name;
2448  ofs = get_file_info(fp, header_pos, file, &len, names_len);
2449  if (!ofs) {
2450  Com_Printf("%s has bad central directory structure (pass %d)\n", packfile, 2);
2451  goto fail1; // directory changed on disk?
2452  }
2453  header_pos += ofs;
2454 
2455  if (len) {
2456  // fix absolute position
2457  file->filepos += extra_bytes;
2458  file->coherent = qfalse;
2459 
2460  pack_hash_file(pack, file);
2461 
2462  // advance pointers, decrement counters
2463  file++;
2464  num_files--;
2465 
2466  name += len;
2467  names_len -= len;
2468  }
2469  }
2470 
2471  FS_DPrintf("%s: %u files, %u skipped, %u hash\n",
2472  packfile, pack->num_files, num_files_cd - pack->num_files, pack->hash_size);
2473 
2474  return pack;
2475 
2476 fail1:
2477  Z_Free(pack);
2478 fail2:
2479  fclose(fp);
2480  return NULL;
2481 }
2482 #endif
2483 
2484 // this is complicated as we need pakXX.pak loaded first,
2485 // sorted in numerical order, then the rest of the paks in
2486 // alphabetical order, e.g. pak0.pak, pak2.pak, pak17.pak, abc.pak...
2487 static int pakcmp(const void *p1, const void *p2)
2488 {
2489  char *s1 = *(char **)p1;
2490  char *s2 = *(char **)p2;
2491 
2492  if (!Q_stricmpn(s1, "pak", 3)) {
2493  if (!Q_stricmpn(s2, "pak", 3)) {
2494  unsigned long n1 = strtoul(s1 + 3, &s1, 10);
2495  unsigned long n2 = strtoul(s2 + 3, &s2, 10);
2496  if (n1 > n2) {
2497  return 1;
2498  }
2499  if (n1 < n2) {
2500  return -1;
2501  }
2502  goto alphacmp;
2503  }
2504  return -1;
2505  }
2506  if (!Q_stricmpn(s2, "pak", 3)) {
2507  return 1;
2508  }
2509 
2510 alphacmp:
2511  return Q_stricmp(s1, s2);
2512 }
2513 
2514 // sets fs_gamedir, adds the directory to the head of the path,
2515 // then loads and adds pak*.pak, then anything else in alphabethical order.
2516 static void q_printf(2, 3) add_game_dir(unsigned mode, const char *fmt, ...)
2517 {
2518  va_list argptr;
2519  searchpath_t *search;
2520  pack_t *pack;
2521  void *files[MAX_LISTED_FILES];
2522  int i, count;
2523  char path[MAX_OSPATH];
2524  size_t len;
2525 
2526  va_start(argptr, fmt);
2527  len = Q_vsnprintf(fs_gamedir, sizeof(fs_gamedir), fmt, argptr);
2528  va_end(argptr);
2529 
2530  if (len >= sizeof(fs_gamedir)) {
2531  Com_EPrintf("%s: refusing oversize path\n", __func__);
2532  return;
2533  }
2534 
2535 #ifdef _WIN32
2537 #endif
2538 
2539 #if USE_ZLIB
2540 #define PAK_EXT ".pak;.pkz"
2541 #else
2542 #define PAK_EXT ".pak"
2543 #endif
2544 
2545  // add any pack files
2546  count = 0;
2547  Sys_ListFiles_r(fs_gamedir, PAK_EXT, 0, 0, &count, files, 0);
2548 
2549  // Can't exit early for game directory
2550  if (!(mode & FS_PATH_GAME) && !count) {
2551  return;
2552  }
2553 
2554  qsort(files, count, sizeof(files[0]), pakcmp);
2555 
2556  for (i = 0; i < count; i++) {
2557  len = Q_concat(path, sizeof(path), fs_gamedir, "/", files[i], NULL);
2558  if (len >= sizeof(path)) {
2559  Com_EPrintf("%s: refusing oversize path\n", __func__);
2560  continue;
2561  }
2562 #if USE_ZLIB
2563  // FIXME: guess packfile type by contents instead?
2564  if (len > 4 && !Q_stricmp(path + len - 4, ".pkz"))
2565  pack = load_zip_file(path);
2566  else
2567 #endif
2568  pack = load_pak_file(path);
2569  if (!pack)
2570  continue;
2571  search = FS_Malloc(sizeof(searchpath_t));
2572  search->mode = mode;
2573  search->filename[0] = 0;
2574  search->pack = pack_get(pack);
2575  search->next = fs_searchpaths;
2576  fs_searchpaths = search;
2577  }
2578 
2579  for (i = 0; i < count; i++) {
2580  Z_Free(files[i]);
2581  }
2582 
2583  // add the directory to the search path
2584  // the directory has priority over the pak files
2585  search = FS_Malloc(sizeof(searchpath_t) + len);
2586  search->mode = mode;
2587  search->pack = NULL;
2588  memcpy(search->filename, fs_gamedir, len + 1);
2589  search->next = fs_searchpaths;
2590  fs_searchpaths = search;
2591 
2592 }
2593 
2594 /*
2595 =================
2596 FS_CopyInfo
2597 =================
2598 */
2599 file_info_t *FS_CopyInfo(const char *name, size_t size, time_t ctime, time_t mtime)
2600 {
2601  file_info_t *out;
2602  size_t len;
2603 
2604  if (!name) {
2605  return NULL;
2606  }
2607 
2608  len = strlen(name);
2609  out = FS_Mallocz(sizeof(*out) + len);
2610  out->size = size;
2611  out->ctime = ctime;
2612  out->mtime = mtime;
2613  memcpy(out->name, name, len + 1);
2614 
2615  return out;
2616 }
2617 
2618 void **FS_CopyList(void **list, int count)
2619 {
2620  void **out;
2621  int i;
2622 
2623  if (!count) {
2624  return NULL;
2625  }
2626 
2627  out = FS_Malloc(sizeof(void *) * (count + 1));
2628  for (i = 0; i < count; i++) {
2629  out[i] = list[i];
2630  }
2631  out[i] = NULL;
2632 
2633  return out;
2634 }
2635 
2636 qboolean FS_WildCmp(const char *filter, const char *string)
2637 {
2638  do {
2639  if (Com_WildCmpEx(filter, string, ';', qtrue)) {
2640  return qtrue;
2641  }
2642  filter = strchr(filter, ';');
2643  if (filter) filter++;
2644  } while (filter);
2645 
2646  return qfalse;
2647 }
2648 
2649 qboolean FS_ExtCmp(const char *ext, const char *name)
2650 {
2651  int c1, c2;
2652  const char *e, *n, *l;
2653 
2654  if (!name[0] || !ext[0]) {
2655  return qfalse;
2656  }
2657 
2658  for (l = name; l[1]; l++)
2659  ;
2660 
2661  for (e = ext; e[1]; e++)
2662  ;
2663 
2664 rescan:
2665  n = l;
2666  do {
2667  c1 = *e--;
2668  c2 = *n--;
2669 
2670  if (c1 == ';') {
2671  break; // matched
2672  }
2673 
2674  if (c1 != c2) {
2675  c1 = Q_tolower(c1);
2676  c2 = Q_tolower(c2);
2677  if (c1 != c2) {
2678  while (e > ext) {
2679  c1 = *e--;
2680  if (c1 == ';') {
2681  goto rescan;
2682  }
2683  }
2684  return qfalse;
2685  }
2686  }
2687  if (n < name) {
2688  return qfalse;
2689  }
2690  } while (e >= ext);
2691 
2692  return qtrue;
2693 }
2694 
2695 static int infocmp(const void *p1, const void *p2)
2696 {
2697  file_info_t *n1 = *(file_info_t **)p1;
2698  file_info_t *n2 = *(file_info_t **)p2;
2699 
2700  return FS_pathcmp(n1->name, n2->name);
2701 }
2702 
2703 static int alphacmp(const void *p1, const void *p2)
2704 {
2705  char *s1 = *(char **)p1;
2706  char *s2 = *(char **)p2;
2707 
2708  return FS_pathcmp(s1, s2);
2709 }
2710 
2711 /*
2712 =================
2713 FS_ListFiles
2714 =================
2715 */
2716 void **FS_ListFiles(const char *path,
2717  const char *filter,
2718  unsigned flags,
2719  int *count_p)
2720 {
2721  searchpath_t *search;
2722  packfile_t *file;
2723  void *files[MAX_LISTED_FILES], *info;
2724  int i, j, count, total;
2725  char normalized[MAX_OSPATH], buffer[MAX_OSPATH];
2726  void **list;
2727  size_t len, pathlen;
2728  char *s, *p;
2729  int valid;
2730 
2731  count = 0;
2732  valid = PATH_NOT_CHECKED;
2733 
2734  if (!path) {
2735  path = "";
2736  pathlen = 0;
2737  } else {
2738  // normalize the path
2739  pathlen = FS_NormalizePathBuffer(normalized, path, sizeof(normalized));
2740  if (pathlen >= sizeof(normalized)) {
2741  goto fail;
2742  }
2743 
2744  path = normalized;
2745  }
2746 
2747  // can't mix directory search with other flags
2748  if ((flags & FS_SEARCH_DIRSONLY) && (flags & FS_SEARCH_MASK & ~FS_SEARCH_DIRSONLY)) {
2749  goto fail;
2750  }
2751 
2752  for (search = fs_searchpaths; search; search = search->next) {
2753  if (flags & FS_PATH_MASK) {
2754  if ((flags & search->mode & FS_PATH_MASK) == 0) {
2755  continue;
2756  }
2757  }
2758  if (search->pack) {
2759  if ((flags & FS_TYPE_MASK) == FS_TYPE_REAL) {
2760  continue; // don't search in paks
2761  }
2762 
2763  for (i = 0; i < search->pack->num_files; i++) {
2764  file = &search->pack->files[i];
2765  s = file->name;
2766 
2767  // check path
2768  if (pathlen) {
2769  if (file->namelen < pathlen) {
2770  continue;
2771  }
2772  if (FS_pathcmpn(s, path, pathlen)) {
2773  continue;
2774  }
2775  if (s[pathlen] != '/') {
2776  continue; // matched prefix must be a directory
2777  }
2778  if (flags & FS_SEARCH_BYFILTER) {
2779  s += pathlen + 1;
2780  }
2781  } else if (path == normalized) {
2782  if (!(flags & FS_SEARCH_DIRSONLY) && strchr(s, '/')) {
2783  continue; // must be a file in the root directory
2784  }
2785  }
2786 
2787  // check filter
2788  if (filter) {
2789  if (flags & FS_SEARCH_BYFILTER) {
2790  if (!FS_WildCmp(filter, s)) {
2791  continue;
2792  }
2793  } else {
2794  if (!FS_ExtCmp(filter, s)) {
2795  continue;
2796  }
2797  }
2798  }
2799 
2800  // copy name off
2801  if (flags & (FS_SEARCH_DIRSONLY | FS_SEARCH_STRIPEXT)) {
2802  s = strcpy(buffer, s);
2803  }
2804 
2805  // hacky directory search support for pak files
2806  if (flags & FS_SEARCH_DIRSONLY) {
2807  p = s;
2808  if (pathlen) {
2809  p += pathlen + 1;
2810  }
2811  p = strchr(p, '/');
2812  if (!p) {
2813  continue; // does not have directory component
2814  }
2815  *p = 0;
2816  for (j = 0; j < count; j++) {
2817  if (!FS_pathcmp(files[j], s)) {
2818  break;
2819  }
2820  }
2821  if (j != count) {
2822  continue; // already listed this directory
2823  }
2824  }
2825 
2826  // strip path
2827  if (!(flags & FS_SEARCH_SAVEPATH)) {
2828  s = COM_SkipPath(s);
2829  }
2830 
2831  // strip extension
2832  if (flags & FS_SEARCH_STRIPEXT) {
2833  *COM_FileExtension(s) = 0;
2834  }
2835 
2836  if (!*s) {
2837  continue;
2838  }
2839 
2840  // copy info off
2841  if (flags & FS_SEARCH_EXTRAINFO) {
2842  info = FS_CopyInfo(s, file->filelen, 0, 0);
2843  } else {
2844  info = FS_CopyString(s);
2845  }
2846 
2847  files[count++] = info;
2848 
2849  if (count >= MAX_LISTED_FILES) {
2850  break;
2851  }
2852  }
2853  } else {
2854  if ((flags & FS_TYPE_MASK) == FS_TYPE_PAK) {
2855  continue; // don't search in filesystem
2856  }
2857 
2858  len = strlen(search->filename);
2859 
2860  if (pathlen) {
2861  if (len + pathlen + 1 >= MAX_OSPATH) {
2862  continue;
2863  }
2864  if (valid == PATH_NOT_CHECKED) {
2865  valid = FS_ValidatePath(path);
2866  }
2867  if (valid == PATH_INVALID) {
2868  continue;
2869  }
2870  s = memcpy(buffer, search->filename, len);
2871  s[len++] = '/';
2872  memcpy(s + len, path, pathlen + 1);
2873  } else {
2874  s = search->filename;
2875  }
2876 
2877  if (flags & FS_SEARCH_BYFILTER) {
2878  len += pathlen + 1;
2879  }
2880 
2881  Sys_ListFiles_r(s, filter, flags, len, &count, files, 0);
2882  }
2883 
2884  if (count >= MAX_LISTED_FILES) {
2885  break;
2886  }
2887  }
2888 
2889  if (!count) {
2890 fail:
2891  if (count_p) {
2892  *count_p = 0;
2893  }
2894  return NULL;
2895  }
2896 
2897  if (flags & FS_SEARCH_EXTRAINFO) {
2898  // TODO
2899  qsort(files, count, sizeof(files[0]), infocmp);
2900  total = count;
2901  } else {
2902  // sort alphabetically
2903  qsort(files, count, sizeof(files[0]), alphacmp);
2904 
2905  // remove duplicates
2906  total = 1;
2907  for (i = 1; i < count; i++) {
2908  if (!FS_pathcmp(files[i - 1], files[i])) {
2909  Z_Free(files[i - 1]);
2910  files[i - 1] = NULL;
2911  } else {
2912  total++;
2913  }
2914  }
2915  }
2916 
2917  list = FS_Malloc(sizeof(void *) * (total + 1));
2918 
2919  total = 0;
2920  for (i = 0; i < count; i++) {
2921  if (files[i]) {
2922  list[total++] = files[i];
2923  }
2924  }
2925  list[total] = NULL;
2926 
2927  if (count_p) {
2928  *count_p = total;
2929  }
2930 
2931  return list;
2932 }
2933 
2934 /*
2935 =================
2936 FS_FreeList
2937 =================
2938 */
2939 void FS_FreeList(void **list)
2940 {
2941  void **p;
2942 
2943  if (!list) {
2944  return;
2945  }
2946 
2947  for (p = list; *p; p++) {
2948  Z_Free(*p);
2949  }
2950 
2951  Z_Free(list);
2952 }
2953 
2954 void FS_File_g(const char *path, const char *ext, unsigned flags, genctx_t *ctx)
2955 {
2956  int i, numFiles;
2957  void **list;
2958  char *s;
2959 
2960  list = FS_ListFiles(path, ext, flags, &numFiles);
2961  if (!list) {
2962  return;
2963  }
2964 
2965  for (i = 0; i < numFiles; i++) {
2966  s = list[i];
2967  if (ctx->count < ctx->size && !strncmp(s, ctx->partial, ctx->length)) {
2968  ctx->matches[ctx->count++] = s;
2969  } else {
2970  Z_Free(s);
2971  }
2972  }
2973 
2974  Z_Free(list);
2975 }
2976 
2977 static void print_file_list(const char *path, const char *ext, unsigned flags)
2978 {
2979  void **list;
2980  int i, listed, total;
2981 
2982  list = FS_ListFiles(path, ext, flags, &total);
2983 
2984  // don't list too many files to avoid console spam
2985  listed = total > 128 ? 128 : total;
2986  for (i = 0; i < listed; i++) {
2987  Com_Printf("%s\n", (char *)list[i]);
2988  }
2989 
2990  FS_FreeList(list);
2991 
2992  if (listed == total) {
2993  Com_Printf("%i files listed\n", listed);
2994  } else {
2995  Com_Printf("%i files listed (%d files more)\n", listed, total - listed);
2996  }
2997 }
2998 
2999 /*
3000 ============
3001 FS_FDir_f
3002 ============
3003 */
3004 static void FS_FDir_f(void)
3005 {
3006  unsigned flags;
3007  char *filter;
3008 
3009  if (Cmd_Argc() < 2) {
3010  Com_Printf("Usage: %s <filter> [full_path]\n", Cmd_Argv(0));
3011  return;
3012  }
3013 
3014  filter = Cmd_Argv(1);
3015 
3016  flags = FS_SEARCH_BYFILTER;
3017  if (Cmd_Argc() > 2) {
3018  flags |= FS_SEARCH_SAVEPATH;
3019  }
3020 
3021  print_file_list(NULL, filter, flags);
3022 }
3023 
3024 /*
3025 ============
3026 FS_Dir_f
3027 ============
3028 */
3029 static void FS_Dir_f(void)
3030 {
3031  char *path, *ext;
3032 
3033  if (Cmd_Argc() < 2) {
3034  Com_Printf("Usage: %s <directory> [.extension]\n", Cmd_Argv(0));
3035  return;
3036  }
3037 
3038  path = Cmd_Argv(1);
3039  if (Cmd_Argc() > 2) {
3040  ext = Cmd_Argv(2);
3041  } else {
3042  ext = NULL;
3043  }
3044 
3045  print_file_list(path, ext, 0);
3046 }
3047 
3048 /*
3049 ============
3050 FS_WhereIs_f
3051 
3052 Verbosely looks up a filename with exactly the same logic as expand_open_file_read.
3053 ============
3054 */
3055 static void FS_WhereIs_f(void)
3056 {
3057  char normalized[MAX_OSPATH], fullpath[MAX_OSPATH];
3058  searchpath_t *search;
3059  pack_t *pak;
3060  packfile_t *entry;
3061  symlink_t *link;
3062  unsigned hash;
3063  file_info_t info;
3064  qerror_t ret;
3065  int total, valid;
3066  size_t len, namelen;
3067  qboolean report_all;
3068 
3069  if (Cmd_Argc() < 2) {
3070  Com_Printf("Usage: %s <path> [all]\n", Cmd_Argv(0));
3071  return;
3072  }
3073 
3074 // normalize path
3075  namelen = FS_NormalizePathBuffer(normalized, Cmd_Argv(1), MAX_OSPATH);
3076  if (namelen >= MAX_OSPATH) {
3077  Com_Printf("Refusing to lookup oversize path.\n");
3078  return;
3079  }
3080 
3081 // expand hard symlinks
3082  link = expand_links(&fs_hard_links, normalized, &namelen);
3083  if (link) {
3084  if (namelen >= MAX_OSPATH) {
3085  Com_Printf("Oversize symbolic link ('%s --> '%s').\n",
3086  link->name, link->target);
3087  return;
3088  }
3089 
3090  Com_Printf("Symbolic link ('%s' --> '%s') in effect.\n",
3091  link->name, link->target);
3092  }
3093 
3094  report_all = Cmd_Argc() >= 3;
3095  total = 0;
3096  link = NULL;
3097 
3098 // reject empty paths
3099  if (namelen == 0) {
3100  Com_Printf("Refusing to lookup empty path.\n");
3101  return;
3102  }
3103 
3104 recheck:
3105 
3106 // warn about non-standard path length
3107  if (namelen >= MAX_QPATH) {
3108  Com_Printf("Not searching for '%s' in pack files "
3109  "since path length exceedes %d characters.\n",
3110  normalized, MAX_QPATH - 1);
3111  }
3112 
3113  hash = FS_HashPath(normalized, 0);
3114 
3115  valid = PATH_NOT_CHECKED;
3116 
3117 // search through the path, one element at a time
3118  for (search = fs_searchpaths; search; search = search->next) {
3119  // is the element a pak file?
3120  if (search->pack) {
3121  // don't bother searching in paks if length exceedes MAX_QPATH
3122  if (namelen >= MAX_QPATH) {
3123  continue;
3124  }
3125  // look through all the pak file elements
3126  pak = search->pack;
3127  entry = pak->file_hash[hash & (pak->hash_size - 1)];
3128  for (; entry; entry = entry->hash_next) {
3129  if (entry->namelen != namelen) {
3130  continue;
3131  }
3132  if (!FS_pathcmp(entry->name, normalized)) {
3133  // found it!
3134  Com_Printf("%s/%s (%"PRIz" bytes)\n", pak->filename,
3135  normalized, entry->filelen);
3136  if (!report_all) {
3137  return;
3138  }
3139  total++;
3140  }
3141  }
3142  } else {
3143  if (valid == PATH_NOT_CHECKED) {
3144  valid = FS_ValidatePath(normalized);
3145  if (valid == PATH_INVALID) {
3146  // warn about invalid path
3147  Com_Printf("Not searching for '%s' in physical file "
3148  "system since path contains invalid characters.\n",
3149  normalized);
3150  }
3151  }
3152  if (valid == PATH_INVALID) {
3153  continue;
3154  }
3155 
3156  // check a file in the directory tree
3157  len = Q_concat(fullpath, MAX_OSPATH,
3158  search->filename, "/", normalized, NULL);
3159  if (len >= MAX_OSPATH) {
3160  Com_WPrintf("Full path length '%s/%s' exceeded %d characters.\n",
3161  search->filename, normalized, MAX_OSPATH - 1);
3162  if (!report_all) {
3163  return;
3164  }
3165  continue;
3166  }
3167 
3168  ret = get_path_info(fullpath, &info);
3169 
3170 #ifndef _WIN32
3171  if (ret == Q_ERR_NOENT && valid == PATH_MIXED_CASE) {
3172  Q_strlwr(fullpath + strlen(search->filename) + 1);
3173  ret = get_path_info(fullpath, &info);
3174  if (ret == Q_ERR_SUCCESS)
3175  Com_Printf("Physical path found after converting to lower case.\n");
3176  }
3177 #endif
3178 
3179  if (ret == Q_ERR_SUCCESS) {
3180  Com_Printf("%s (%"PRIz" bytes)\n", fullpath, info.size);
3181  if (!report_all) {
3182  return;
3183  }
3184  total++;
3185  } else if (ret != Q_ERR_NOENT) {
3186  Com_EPrintf("Couldn't get info on '%s': %s\n",
3187  fullpath, Q_ErrorString(ret));
3188  if (!report_all) {
3189  return;
3190  }
3191  }
3192  }
3193  }
3194 
3195  if ((total == 0 || report_all) && link == NULL) {
3196  // expand soft symlinks
3197  link = expand_links(&fs_soft_links, normalized, &namelen);
3198  if (link) {
3199  if (namelen >= MAX_OSPATH) {
3200  Com_Printf("Oversize symbolic link ('%s --> '%s').\n",
3201  link->name, link->target);
3202  return;
3203  }
3204 
3205  Com_Printf("Symbolic link ('%s' --> '%s') in effect.\n",
3206  link->name, link->target);
3207  goto recheck;
3208  }
3209  }
3210 
3211  if (total) {
3212  Com_Printf("%d instances of %s\n", total, normalized);
3213  } else {
3214  Com_Printf("%s was not found\n", normalized);
3215  }
3216 }
3217 
3218 /*
3219 ============
3220 FS_Path_f
3221 ============
3222 */
3223 static void FS_Path_f(void)
3224 {
3225  searchpath_t *s;
3226  int numFilesInPAK = 0;
3227 #if USE_ZLIB
3228  int numFilesInZIP = 0;
3229 #endif
3230  Com_Printf("Current search path:\n");
3231  for (s = fs_searchpaths; s; s = s->next) {
3232  if (s->pack) {
3233 #if USE_ZLIB
3234  if (s->pack->type == FS_ZIP)
3235  numFilesInZIP += s->pack->num_files;
3236  else
3237 #endif
3238  numFilesInPAK += s->pack->num_files;
3239  Com_Printf("%s (%i files)\n", s->pack->filename, s->pack->num_files);
3240  } else {
3241  Com_Printf("%s\n", s->filename);
3242  }
3243  }
3244 
3245  if (numFilesInPAK) {
3246  Com_Printf("%i files in PAK files\n", numFilesInPAK);
3247  }
3248 
3249 #if USE_ZLIB
3250  if (numFilesInZIP) {
3251  Com_Printf("%i files in PKZ files\n", numFilesInZIP);
3252  }
3253 #endif
3254 }
3255 
3256 #ifdef _DEBUG
3257 /*
3258 ================
3259 FS_Stats_f
3260 ================
3261 */
3262 static void FS_Stats_f(void)
3263 {
3264  searchpath_t *path;
3265  pack_t *pack, *maxpack = NULL;
3266  packfile_t *file, *max = NULL;
3267  int i;
3268  int len, maxLen = 0;
3269  int totalHashSize, totalLen;
3270 
3271  totalHashSize = totalLen = 0;
3272  for (path = fs_searchpaths; path; path = path->next) {
3273  if (!(pack = path->pack)) {
3274  continue;
3275  }
3276  for (i = 0; i < pack->hash_size; i++) {
3277  if (!(file = pack->file_hash[i])) {
3278  continue;
3279  }
3280  len = 0;
3281  for (; file; file = file->hash_next) {
3282  len++;
3283  }
3284  if (maxLen < len) {
3285  max = pack->file_hash[i];
3286  maxpack = pack;
3287  maxLen = len;
3288  }
3289  totalLen += len;
3290  totalHashSize++;
3291  }
3292  //totalHashSize += pack->hash_size;
3293  }
3294 
3295  Com_Printf("Total calls to open_file_read: %d\n", fs_count_read);
3296  Com_Printf("Total path comparsions: %d\n", fs_count_strcmp);
3297  Com_Printf("Total calls to open_from_disk: %d\n", fs_count_open);
3298  Com_Printf("Total mixed-case reopens: %d\n", fs_count_strlwr);
3299 
3300  if (!totalHashSize) {
3301  Com_Printf("No stats to display\n");
3302  return;
3303  }
3304 
3305  Com_Printf("Maximum hash bucket length is %d, average is %.2f\n", maxLen, (float)totalLen / totalHashSize);
3306  if (max) {
3307  Com_Printf("Dumping longest bucket (%s):\n", maxpack->filename);
3308  for (file = max; file; file = file->hash_next) {
3309  Com_Printf("%s\n", file->name);
3310  }
3311  }
3312 }
3313 #endif // _DEBUG
3314 
3315 static void FS_Link_g(genctx_t *ctx)
3316 {
3317  list_t *list;
3318  symlink_t *link;
3319 
3320  if (!strncmp(Cmd_Argv(ctx->argnum - 1), "soft", 4))
3321  list = &fs_soft_links;
3322  else
3323  list = &fs_hard_links;
3324 
3325  FOR_EACH_SYMLINK(link, list) {
3326  if (!Prompt_AddMatch(ctx, link->name)) {
3327  break;
3328  }
3329  }
3330 }
3331 
3332 static void FS_Link_c(genctx_t *ctx, int argnum)
3333 {
3334  if (argnum == 1) {
3335  FS_Link_g(ctx);
3336  }
3337 }
3338 
3339 static void free_all_links(list_t *list)
3340 {
3341  symlink_t *link, *next;
3342 
3343  FOR_EACH_SYMLINK_SAFE(link, next, list) {
3344  Z_Free(link->target);
3345  Z_Free(link);
3346  }
3347 
3348  List_Init(list);
3349 }
3350 
3351 static void FS_UnLink_f(void)
3352 {
3353  static const cmd_option_t options[] = {
3354  { "a", "all", "delete all links" },
3355  { "h", "help", "display this message" },
3356  { NULL }
3357  };
3358  list_t *list;
3359  symlink_t *link;
3360  char *name;
3361  int c;
3362 
3363  if (!strncmp(Cmd_Argv(0), "soft", 4))
3364  list = &fs_soft_links;
3365  else
3366  list = &fs_hard_links;
3367 
3368  while ((c = Cmd_ParseOptions(options)) != -1) {
3369  switch (c) {
3370  case 'h':
3371  Cmd_PrintUsage(options, "<name>");
3372  Com_Printf("Deletes a symbolic link with the specified name.");
3373  Cmd_PrintHelp(options);
3374  return;
3375  case 'a':
3376  free_all_links(list);
3377  Com_Printf("Deleted all symbolic links.\n");
3378  return;
3379  default:
3380  return;
3381  }
3382  }
3383 
3384  name = cmd_optarg;
3385  if (!name[0]) {
3386  Com_Printf("Missing name argument.\n");
3387  Cmd_PrintHint();
3388  return;
3389  }
3390 
3391  FOR_EACH_SYMLINK(link, list) {
3392  if (!FS_pathcmp(link->name, name)) {
3393  List_Remove(&link->entry);
3394  Z_Free(link->target);
3395  Z_Free(link);
3396  return;
3397  }
3398  }
3399 
3400  Com_Printf("Symbolic link '%s' does not exist.\n", name);
3401 }
3402 
3403 static void FS_Link_f(void)
3404 {
3405  int argc, count;
3406  list_t *list;
3407  symlink_t *link;
3408  size_t namelen, targlen;
3409  char name[MAX_OSPATH];
3410  char target[MAX_OSPATH];
3411 
3412  if (!strncmp(Cmd_Argv(0), "soft", 4))
3413  list = &fs_soft_links;
3414  else
3415  list = &fs_hard_links;
3416 
3417  argc = Cmd_Argc();
3418  if (argc == 1) {
3419  count = 0;
3420  FOR_EACH_SYMLINK(link, list) {
3421  Com_Printf("%s --> %s\n", link->name, link->target);
3422  count++;
3423  }
3424  Com_Printf("------------------\n"
3425  "%d symbolic link%s listed.\n", count, count == 1 ? "" : "s");
3426  return;
3427  }
3428 
3429  if (argc != 3) {
3430  Com_Printf("Usage: %s <name> <target>\n"
3431  "Creates symbolic link to target with the specified name.\n"
3432  "Virtual quake paths are accepted.\n"
3433  "Links are effective only for reading.\n",
3434  Cmd_Argv(0));
3435  return;
3436  }
3437 
3438  namelen = FS_NormalizePathBuffer(name, Cmd_Argv(1), sizeof(name));
3439  if (namelen == 0 || namelen >= sizeof(name)) {
3440  Com_Printf("Invalid symbolic link name.\n");
3441  return;
3442  }
3443 
3444  targlen = FS_NormalizePathBuffer(target, Cmd_Argv(2), sizeof(target));
3445  if (targlen == 0 || targlen >= sizeof(target)) {
3446  Com_Printf("Invalid symbolic link target.\n");
3447  return;
3448  }
3449 
3450  // search for existing link with this name
3451  FOR_EACH_SYMLINK(link, list) {
3452  if (!FS_pathcmp(link->name, name)) {
3453  Z_Free(link->target);
3454  goto update;
3455  }
3456  }
3457 
3458  // create new link
3459  link = FS_Malloc(sizeof(*link) + namelen);
3460  memcpy(link->name, name, namelen + 1);
3461  link->namelen = namelen;
3462  List_Append(list, &link->entry);
3463 
3464 update:
3465  link->target = FS_CopyString(target);
3466  link->targlen = targlen;
3467 }
3468 
3469 static void free_search_path(searchpath_t *path)
3470 {
3471  pack_put(path->pack);
3472  Z_Free(path);
3473 }
3474 
3475 static void free_all_paths(void)
3476 {
3477  searchpath_t *path, *next;
3478 
3479  for (path = fs_searchpaths; path; path = next) {
3480  next = path->next;
3481  free_search_path(path);
3482  }
3483 
3484  fs_searchpaths = NULL;
3485 }
3486 
3487 static void free_game_paths(void)
3488 {
3489  searchpath_t *path, *next;
3490 
3491  for (path = fs_searchpaths; path != fs_base_searchpaths; path = next) {
3492  next = path->next;
3493  free_search_path(path);
3494  }
3495 
3497 }
3498 
3499 static void setup_base_paths(void)
3500 {
3501  // base paths have both BASE and GAME bits set by default
3502  // the GAME bit will be removed once gamedir is set,
3503  // and will be put back once gamedir is reset to basegame
3504  add_game_dir(FS_PATH_BASE | FS_PATH_GAME, "%s/"BASEGAME, sys_basedir->string);
3506 }
3507 
3508 // Sets the gamedir and path to a different directory.
3509 static void setup_game_paths(void)
3510 {
3511  searchpath_t *path;
3512 
3513  if (fs_game->string[0]) {
3514  // add system path first
3515  add_game_dir(FS_PATH_GAME, "%s/%s", sys_basedir->string, fs_game->string);
3516 
3517  // home paths override system paths
3518  if (sys_homedir->string[0]) {
3519  add_game_dir(FS_PATH_BASE, "%s/"BASEGAME, sys_homedir->string);
3520  add_game_dir(FS_PATH_GAME, "%s/%s", sys_homedir->string, fs_game->string);
3521  }
3522 
3523  // remove the game bit from base paths
3524  for (path = fs_base_searchpaths; path; path = path->next) {
3525  path->mode &= ~FS_PATH_GAME;
3526  }
3527 
3528  // this var is set for compatibility with server browsers, etc
3529  Cvar_FullSet("gamedir", fs_game->string, CVAR_ROM | CVAR_SERVERINFO, FROM_CODE);
3530 
3531  } else {
3532  if (sys_homedir->string[0]) {
3533  add_game_dir(FS_PATH_BASE | FS_PATH_GAME,
3534  "%s/"BASEGAME, sys_homedir->string);
3535  }
3536 
3537  // add the game bit to base paths
3538  for (path = fs_base_searchpaths; path; path = path->next) {
3539  path->mode |= FS_PATH_GAME;
3540  }
3541 
3542  Cvar_FullSet("gamedir", "", CVAR_ROM, FROM_CODE);
3543  }
3544 
3545  // this var is used by the game library to find it's home directory
3546  Cvar_FullSet("fs_gamedir", fs_gamedir, CVAR_ROM, FROM_CODE);
3547 }
3548 
3549 /*
3550 ================
3551 FS_Restart
3552 
3553 Unless total is true, reloads paks only up to base dir
3554 ================
3555 */
3556 void FS_Restart(qboolean total)
3557 {
3558  Com_Printf("----- FS_Restart -----\n");
3559 
3560  if (total) {
3561  // perform full reset
3562  free_all_paths();
3563  setup_base_paths();
3564  } else {
3565  // just change gamedir
3566  free_game_paths();
3567  Q_snprintf(fs_gamedir, sizeof(fs_gamedir), "%s/"BASEGAME, sys_basedir->string);
3568 #ifdef _WIN32
3570 #endif
3571  }
3572 
3573  setup_game_paths();
3574 
3575  FS_Path_f();
3576 
3577  Com_Printf("----------------------\n");
3578 }
3579 
3580 /*
3581 ============
3582 FS_Restart_f
3583 
3584 Console command to fully re-start the file system.
3585 ============
3586 */
3587 static void FS_Restart_f(void)
3588 {
3589  CL_RestartFilesystem(qtrue);
3590 }
3591 
3592 static const cmdreg_t c_fs[] = {
3593  { "path", FS_Path_f },
3594  { "fdir", FS_FDir_f },
3595  { "dir", FS_Dir_f },
3596 #ifdef _DEBUG
3597  { "fs_stats", FS_Stats_f },
3598 #endif
3599  { "whereis", FS_WhereIs_f },
3600  { "link", FS_Link_f, FS_Link_c },
3601  { "unlink", FS_UnLink_f, FS_Link_c },
3602  { "softlink", FS_Link_f, FS_Link_c },
3603  { "softunlink", FS_UnLink_f, FS_Link_c },
3604  { "fs_restart", FS_Restart_f },
3605 
3606  { NULL }
3607 };
3608 
3609 /*
3610 ================
3611 FS_Shutdown
3612 ================
3613 */
3614 void FS_Shutdown(void)
3615 {
3616  file_t *file;
3617  int i;
3618 
3619  if (!fs_searchpaths) {
3620  return;
3621  }
3622 
3623  // close file handles
3624  for (i = 0, file = fs_files; i < MAX_FILE_HANDLES; i++, file++) {
3625  if (file->type != FS_FREE) {
3626  Com_WPrintf("%s: closing handle %d\n", __func__, i + 1);
3627  FS_FCloseFile(i + 1);
3628  }
3629  }
3630 
3631  // free symbolic links
3634 
3635  // free search paths
3636  free_all_paths();
3637 
3638 #if USE_ZLIB
3639  inflateEnd(&fs_zipstream.stream);
3640 #endif
3641 
3642  Z_LeakTest(TAG_FILESYSTEM);
3643 
3645 }
3646 
3647 // this is called when local server starts up and gets it's latched variables,
3648 // client receives a serverdata packet, or user changes the game by hand while
3649 // disconnected
3650 static void fs_game_changed(cvar_t *self)
3651 {
3652  char *s = self->string;
3653 
3654  // validate it
3655  if (*s) {
3656  if (!Q_stricmp(s, BASEGAME)) {
3657  Cvar_Reset(self);
3658  } else if (!COM_IsPath(s)) {
3659  Com_Printf("'%s' should contain characters [A-Za-z0-9_-] only.\n", self->name);
3660  Cvar_Reset(self);
3661  }
3662  }
3663 
3664  // check for the first time startup
3665  if (!fs_base_searchpaths) {
3666  // start up with baseq2 by default
3667  setup_base_paths();
3668 
3669  // check for game override
3670  setup_game_paths();
3671 
3672  FS_Path_f();
3673 
3674  // Detect if we're running full version of the game.
3675  // Shareware version can't have multiplayer enabled for legal reasons.
3676  if (FS_FileExists("maps/base1.bsp"))
3677  Cvar_Set("fs_shareware", "0");
3678  else
3679  Cvar_Set("fs_shareware", "1");
3680 
3681  if (!FS_FileExists("pics/colormap.pcx") || !FS_FileExists("pics/conchars.pcx") || !FS_FileExists("default.cfg"))
3682  {
3683  Com_Error(ERR_FATAL, "No game data files detected. Please make sure that there are .pak files"
3684  " in the game directory: %s.\nReinstalling the game can fix the issue.", fs_gamedir);
3685  }
3686 
3687  return;
3688  }
3689 
3690  // otherwise, restart the filesystem
3691  CL_RestartFilesystem(qfalse);
3692 
3693  // FIXME: if baseq2/autoexec.cfg exists DO NOT exec default.cfg and config.cfg.
3694  // this assumes user prefers to do configuration via autoexec.cfg and doesn't
3695  // want settings and binds messed up whenever gamedir changes after startup.
3696  if (!FS_FileExistsEx(COM_AUTOEXEC_CFG, FS_TYPE_REAL | FS_PATH_BASE)) {
3697  Com_AddConfigFile(COM_DEFAULT_CFG, FS_PATH_GAME);
3698  Com_AddConfigFile(COM_Q2RTX_CFG, 0);
3699  Com_AddConfigFile(COM_CONFIG_CFG, FS_TYPE_REAL | FS_PATH_GAME);
3700  }
3701 
3702  // exec autoexec.cfg (must be a real file within the game directory)
3703  Com_AddConfigFile(COM_AUTOEXEC_CFG, FS_TYPE_REAL | FS_PATH_GAME);
3704 
3705  // exec postexec.cfg (must be a real file)
3706  Com_AddConfigFile(COM_POSTEXEC_CFG, FS_TYPE_REAL);
3707 }
3708 
3709 /*
3710 ================
3711 FS_Init
3712 ================
3713 */
3714 void FS_Init(void)
3715 {
3716  Com_Printf("------- FS_Init -------\n");
3717 
3718  List_Init(&fs_hard_links);
3719  List_Init(&fs_soft_links);
3720 
3721  Cmd_Register(c_fs);
3722 
3723 #ifdef _DEBUG
3724  fs_debug = Cvar_Get("fs_debug", "0", 0);
3725 #endif
3726 
3727  fs_shareware = Cvar_Get("fs_shareware", "0", CVAR_ROM);
3728 
3729  // get the game cvar and start the filesystem
3730  fs_game = Cvar_Get("game", DEFGAME, CVAR_LATCH | CVAR_SERVERINFO);
3731  fs_game->changed = fs_game_changed;
3733 
3734  Com_Printf("-----------------------\n");
3735 }
3736 
read_phys_file
static ssize_t read_phys_file(file_t *file, void *buf, size_t len)
Definition: files.c:1527
FS_Restart
void FS_Restart(qboolean total)
Definition: files.c:3556
Q_strlcat
size_t Q_strlcat(char *dst, const char *src, size_t size)
Definition: shared.c:735
handle
static void * handle
Definition: dynamic.c:52
FS_Link_g
static void FS_Link_g(genctx_t *ctx)
Definition: files.c:3315
pack_t::refcount
unsigned refcount
Definition: files.c:130
FS_CopyInfo
file_info_t * FS_CopyInfo(const char *name, size_t size, time_t ctime, time_t mtime)
Definition: files.c:2599
Cvar_Set
cvar_t * Cvar_Set(const char *var_name, const char *value)
Definition: cvar.c:466
FS_EasyOpenFile
qhandle_t FS_EasyOpenFile(char *buf, size_t size, unsigned mode, const char *dir, const char *name, const char *ext)
Definition: files.c:1846
easy_open_read
static qhandle_t easy_open_read(char *buf, size_t size, unsigned mode, const char *dir, const char *name, const char *ext)
Definition: files.c:1730
FOR_EACH_SYMLINK
#define FOR_EACH_SYMLINK(link, list)
Definition: files.c:85
pack_t::files
packfile_t * files
Definition: files.c:133
fs_files
static file_t fs_files[MAX_FILE_HANDLES]
Definition: files.c:180
Sys_ListFiles_r
void Sys_ListFiles_r(const char *path, const char *filter, unsigned flags, size_t baselen, int *count_p, void **files, int depth)
Definition: system.c:868
file_t::rest_out
size_t rest_out
Definition: files.c:158
read_pak_file
static ssize_t read_pak_file(file_t *file, void *buf, size_t len)
Definition: files.c:1504
pack_put
static void pack_put(pack_t *pack)
Definition: files.c:2064
Com_AddConfigFile
void Com_AddConfigFile(const char *name, unsigned flags)
Definition: common.c:876
cmd_optarg
char * cmd_optarg
Definition: cmd.c:848
FS_COUNT_STRLWR
#define FS_COUNT_STRLWR
Definition: files.c:195
Q_snprintf
size_t Q_snprintf(char *dest, size_t size, const char *fmt,...)
Definition: shared.c:846
FS_Restart_f
static void FS_Restart_f(void)
Definition: files.c:3587
easy_open_write
static qhandle_t easy_open_write(char *buf, size_t size, unsigned mode, const char *dir, const char *name, const char *ext)
Definition: files.c:1781
FS_Read
ssize_t FS_Read(void *buf, size_t len, qhandle_t f)
Definition: files.c:1547
FS_LastModified
qerror_t FS_LastModified(char const *file, uint64_t *last_modified)
Definition: files.c:1310
FS_ExtCmp
qboolean FS_ExtCmp(const char *ext, const char *name)
Definition: files.c:2649
sys_homedir
cvar_t * sys_homedir
Definition: system.c:52
COM_IsPath
qboolean COM_IsPath(const char *s)
Definition: shared.c:348
FS_ERR_WRITE
#define FS_ERR_WRITE(fp)
Definition: files.c:650
st
spawn_temp_t st
Definition: g_main.c:25
FS_FilterFile
qerror_t FS_FilterFile(qhandle_t f)
Definition: files.c:661
packfile_s::filelen
size_t filelen
Definition: files.c:118
Cvar_Get
cvar_t * Cvar_Get(const char *var_name, const char *var_value, int flags)
Definition: cvar.c:257
Q_ErrorString
const char * Q_ErrorString(qerror_t error)
Definition: error.c:51
Z_LeakTest
void Z_LeakTest(memtag_t tag)
Definition: zone.c:120
FS_Path_f
static void FS_Path_f(void)
Definition: files.c:3223
open_file_write
static ssize_t open_file_write(file_t *file, const char *name)
Definition: files.c:887
pack_alloc
static pack_t * pack_alloc(FILE *fp, filetype_t type, const char *name, unsigned num_files, size_t names_len)
Definition: files.c:2080
file_t::pack
pack_t * pack
Definition: files.c:155
FS_COUNT_READ
#define FS_COUNT_READ
Definition: files.c:192
FS_FreeList
void FS_FreeList(void **list)
Definition: files.c:2939
FS_FDir_f
static void FS_FDir_f(void)
Definition: files.c:3004
FS_ReplaceSeparators
char * FS_ReplaceSeparators(char *s, int separator)
Definition: files.c:235
pack_get
static pack_t * pack_get(pack_t *pack)
Definition: files.c:2057
file_t::length
size_t length
Definition: files.c:159
FS_FPrintf
ssize_t FS_FPrintf(qhandle_t f, const char *format,...)
Definition: files.c:2039
PAK_EXT
#define PAK_EXT
validate_char
static qboolean validate_char(int c)
Definition: files.c:251
FS_FREE
@ FS_FREE
Definition: files.c:96
FS_Tell
ssize_t FS_Tell(qhandle_t f)
Definition: files.c:505
Cmd_PrintUsage
void Cmd_PrintUsage(const cmd_option_t *opt, const char *suffix)
Definition: cmd.c:1143
pack_t::type
filetype_t type
Definition: files.c:129
FS_WriteFile
qerror_t FS_WriteFile(const char *path, const void *data, size_t len)
Definition: files.c:1932
Q_vsnprintf
size_t Q_vsnprintf(char *dest, size_t size, const char *fmt, va_list argptr)
Definition: shared.c:791
ext
char ext[4]
Definition: images.c:657
MAX_FILE_HANDLES
#define MAX_FILE_HANDLES
Definition: files.c:59
pack_t
Definition: files.c:128
FS_REAL
@ FS_REAL
Definition: files.c:97
PATH_NOT_CHECKED
#define PATH_NOT_CHECKED
Definition: files.c:83
Cmd_Deregister
void Cmd_Deregister(const cmdreg_t *reg)
Definition: cmd.c:1580
FOR_EACH_SYMLINK_SAFE
#define FOR_EACH_SYMLINK_SAFE(link, next, list)
Definition: files.c:88
file_t::type
filetype_t type
Definition: files.c:148
FS_ERR_READ
#define FS_ERR_READ(fp)
Definition: files.c:648
Cmd_Argv
char * Cmd_Argv(int arg)
Definition: cmd.c:899
Cmd_Argc
int Cmd_Argc(void)
Definition: cmd.c:889
Prompt_AddMatch
qboolean Prompt_AddMatch(genctx_t *ctx, const char *s)
Definition: prompt.c:149
searchpath_s::next
struct searchpath_s * next
Definition: files.c:141
file_t::unique
qboolean unique
Definition: files.c:156
fopen_hack
static FILE * fopen_hack(const char *path, const char *mode)
Definition: files.c:844
packfile_s::filepos
size_t filepos
Definition: files.c:117
expand_open_file_read
static ssize_t expand_open_file_read(file_t *file, const char *name, qboolean unique)
Definition: files.c:1468
searchpath_s
Definition: files.c:140
FS_ValidatePath
int FS_ValidatePath(const char *s)
Definition: files.c:271
FS_FOpenFile
ssize_t FS_FOpenFile(const char *name, qhandle_t *f, unsigned mode)
Definition: files.c:1692
open_from_disk
static ssize_t open_from_disk(file_t *file, const char *fullpath)
Definition: files.c:1276
free_game_paths
static void free_game_paths(void)
Definition: files.c:3487
FS_COUNT_STRCMP
#define FS_COUNT_STRCMP
Definition: files.c:194
setup_base_paths
static void setup_base_paths(void)
Definition: files.c:3499
FS_COUNT_OPEN
#define FS_COUNT_OPEN
Definition: files.c:193
FS_NormalizePathBuffer
size_t FS_NormalizePathBuffer(char *out, const char *in, size_t size)
Definition: files.c:400
Z_TagMalloc
void * Z_TagMalloc(size_t size, memtag_t tag)
Definition: zone.c:275
Com_Error
void Com_Error(error_type_t type, const char *fmt,...)
Definition: g_main.c:258
FS_Link_c
static void FS_Link_c(genctx_t *ctx, int argnum)
Definition: files.c:3332
searchpath_t
struct searchpath_s searchpath_t
Cmd_ParseOptions
int Cmd_ParseOptions(const cmd_option_t *opt)
Definition: cmd.c:1057
FS_DPrintf
#define FS_DPrintf(...)
Definition: files.c:80
FS_File_g
void FS_File_g(const char *path, const char *ext, unsigned flags, genctx_t *ctx)
Definition: files.c:2954
Z_Free
void Z_Free(void *ptr)
Definition: zone.c:147
FS_Init
void FS_Init(void)
Definition: files.c:3714
Cmd_Register
void Cmd_Register(const cmdreg_t *reg)
Definition: cmd.c:1572
fs_shareware
cvar_t * fs_shareware
Definition: files.c:204
FS_ListFiles
void ** FS_ListFiles(const char *path, const char *filter, unsigned flags, int *count_p)
Definition: files.c:2716
pakcmp
static int pakcmp(const void *p1, const void *p2)
Definition: files.c:2487
free_all_links
static void free_all_links(list_t *list)
Definition: files.c:3339
packfile_s::namelen
size_t namelen
Definition: files.c:116
fs_game_changed
static void fs_game_changed(cvar_t *self)
Definition: files.c:3650
fs_base_searchpaths
static searchpath_t * fs_base_searchpaths
Definition: files.c:175
FS_WildCmp
qboolean FS_WildCmp(const char *filter, const char *string)
Definition: files.c:2636
fs_game
cvar_t * fs_game
Definition: files.c:202
pack_hash_file
static void pack_hash_file(pack_t *pack, packfile_t *file)
Definition: files.c:2110
pack_t::file_hash
packfile_t ** file_hash
Definition: files.c:134
Q_strlcpy
size_t Q_strlcpy(char *dst, const char *src, size_t size)
Definition: shared.c:715
free_search_path
static void free_search_path(searchpath_t *path)
Definition: files.c:3469
get_fp_info
static qerror_t get_fp_info(FILE *fp, file_info_t *info)
Definition: files.c:817
c_fs
static const cmdreg_t c_fs[]
Definition: files.c:3592
alloc_handle
static file_t * alloc_handle(qhandle_t *f)
Definition: files.c:415
print_file_list
static void print_file_list(const char *path, const char *ext, unsigned flags)
Definition: files.c:2977
file_for_handle
static file_t * file_for_handle(qhandle_t f)
Definition: files.c:430
Cvar_FullSet
cvar_t * Cvar_FullSet(const char *var_name, const char *value, int flags, from_t from)
Definition: cvar.c:437
pack_t::names
char * names
Definition: files.c:136
file_t
Definition: files.c:147
seek_pak_file
static qerror_t seek_pak_file(file_t *file, off_t offset)
Definition: files.c:537
FS_Shutdown
void FS_Shutdown(void)
Definition: files.c:3614
FS_Length
ssize_t FS_Length(qhandle_t f)
Definition: files.c:487
Com_WildCmpEx
qboolean Com_WildCmpEx(const char *filter, const char *string, int term, qboolean ignorecase)
Definition: utils.c:122
FS_WhereIs_f
static void FS_WhereIs_f(void)
Definition: files.c:3055
free_all_paths
static void free_all_paths(void)
Definition: files.c:3475
searchpath_s::mode
unsigned mode
Definition: files.c:142
FS_Write
ssize_t FS_Write(const void *buf, size_t len, qhandle_t f)
Definition: files.c:1643
pack_t::num_files
unsigned num_files
Definition: files.c:132
packfile_s::name
char * name
Definition: files.c:115
FS_SanitizeFilenameVariable
void FS_SanitizeFilenameVariable(cvar_t *var)
Definition: files.c:294
FS_Flush
void FS_Flush(qhandle_t f)
Definition: files.c:1617
c
statCounters_t c
Definition: main.c:30
CL_RestartFilesystem
void CL_RestartFilesystem(qboolean total)
Definition: main.c:2418
pack_t::hash_size
unsigned hash_size
Definition: files.c:135
searchpath_s::pack
pack_t * pack
Definition: files.c:143
FS_CopyList
void ** FS_CopyList(void **list, int count)
Definition: files.c:2618
file_t::mode
unsigned mode
Definition: files.c:149
fs_searchpaths
static searchpath_t * fs_searchpaths
Definition: files.c:174
file_t::entry
packfile_t * entry
Definition: files.c:154
q_printf
static void q_printf(2, 3)
Definition: files.c:2516
COM_SkipPath
char * COM_SkipPath(const char *pathname)
Definition: shared.c:152
packfile_t
struct packfile_s packfile_t
client.h
Cmd_PrintHelp
void Cmd_PrintHelp(const cmd_option_t *opt)
Definition: cmd.c:1160
setup_game_paths
static void setup_game_paths(void)
Definition: files.c:3509
Cmd_PrintHint
void Cmd_PrintHint(void)
Definition: cmd.c:1178
err
int err
Definition: win.h:24
expand_links
static symlink_t * expand_links(list_t *list, char *buffer, size_t *len_p)
Definition: files.c:456
packfile_s
Definition: files.c:114
fs_soft_links
static list_t fs_soft_links
Definition: files.c:178
filetype_t
filetype_t
Definition: files.c:95
pack
static uint32_t pack(uint64_t n)
Definition: hq2x.c:99
FS_Dir_f
static void FS_Dir_f(void)
Definition: files.c:3029
FS_EasyWriteFile
qboolean FS_EasyWriteFile(char *buf, size_t size, unsigned mode, const char *dir, const char *name, const char *ext, const void *data, size_t len)
Definition: files.c:1960
sys_basedir
cvar_t * sys_basedir
Definition: system.c:50
FS_UnLink_f
static void FS_UnLink_f(void)
Definition: files.c:3351
FS_CreatePath
qerror_t FS_CreatePath(char *path)
Definition: files.c:605
FS_FCloseFile
void FS_FCloseFile(qhandle_t f)
Definition: files.c:759
Q_concat
size_t Q_concat(char *dest, size_t size,...)
Definition: shared.c:758
open_from_pak
static ssize_t open_from_pak(file_t *file, pack_t *pack, packfile_t *entry, qboolean unique)
Definition: files.c:1202
alphacmp
static int alphacmp(const void *p1, const void *p2)
Definition: files.c:2703
file_t::error
qerror_t error
Definition: files.c:157
cleanup_path
static void cleanup_path(char *s)
Definition: files.c:447
packfile_s::hash_next
struct packfile_s * hash_next
Definition: files.c:125
FS_Seek
qerror_t FS_Seek(qhandle_t f, off_t offset)
Definition: files.c:564
FS_PAK
@ FS_PAK
Definition: files.c:98
file_t::fp
FILE * fp
Definition: files.c:150
pack_t::fp
FILE * fp
Definition: files.c:131
COM_FileExtension
char * COM_FileExtension(const char *in)
Definition: shared.c:199
open_file_read
static ssize_t open_file_read(file_t *file, const char *normalized, size_t namelen, qboolean unique)
Definition: files.c:1359
FS_Link_f
static void FS_Link_f(void)
Definition: files.c:3403
FS_BAD
@ FS_BAD
Definition: files.c:103
fs_gamedir
char fs_gamedir[MAX_OSPATH]
Definition: files.c:171
FS_LoadFileEx
ssize_t FS_LoadFileEx(const char *path, void **buffer, unsigned flags, memtag_t tag)
Definition: files.c:1864
pack_t::filename
char * filename
Definition: files.c:137
infocmp
static int infocmp(const void *p1, const void *p2)
Definition: files.c:2695
load_pak_file
static pack_t * load_pak_file(const char *packfile)
Definition: files.c:2123
searchpath_s::filename
char filename[1]
Definition: files.c:144
FS_NormalizePath
size_t FS_NormalizePath(char *out, const char *in)
Definition: files.c:331
fs_hard_links
static list_t fs_hard_links
Definition: files.c:177
FS_ReadLine
ssize_t FS_ReadLine(qhandle_t f, char *buffer, size_t size)
Definition: files.c:1590
get_path_info
static qerror_t get_path_info(const char *path, file_info_t *info)
Definition: files.c:795