]> de.git.xonotic.org Git - xonotic/darkplaces.git/blob - fs.c
Removed all calls to strcpy; most of them are now calls to strlcpy or memcpy.
[xonotic/darkplaces.git] / fs.c
1 /*
2         DarkPlaces file system
3
4         Copyright (C) 2003-2006 Mathieu Olivier
5
6         This program is free software; you can redistribute it and/or
7         modify it under the terms of the GNU General Public License
8         as published by the Free Software Foundation; either version 2
9         of the License, or (at your option) any later version.
10
11         This program is distributed in the hope that it will be useful,
12         but WITHOUT ANY WARRANTY; without even the implied warranty of
13         MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
14
15         See the GNU General Public License for more details.
16
17         You should have received a copy of the GNU General Public License
18         along with this program; if not, write to:
19
20                 Free Software Foundation, Inc.
21                 59 Temple Place - Suite 330
22                 Boston, MA  02111-1307, USA
23 */
24
25 #include "quakedef.h"
26
27 #include <limits.h>
28 #include <fcntl.h>
29
30 #ifdef WIN32
31 # include <direct.h>
32 # include <io.h>
33 #else
34 # include <pwd.h>
35 # include <sys/stat.h>
36 # include <unistd.h>
37 #endif
38
39 #include "fs.h"
40
41 // Win32 requires us to add O_BINARY, but the other OSes don't have it
42 #ifndef O_BINARY
43 # define O_BINARY 0
44 #endif
45
46 // In case the system doesn't support the O_NONBLOCK flag
47 #ifndef O_NONBLOCK
48 # define O_NONBLOCK 0
49 #endif
50
51
52 /*
53
54 All of Quake's data access is through a hierchal file system, but the contents
55 of the file system can be transparently merged from several sources.
56
57 The "base directory" is the path to the directory holding the quake.exe and
58 all game directories.  The sys_* files pass this to host_init in
59 quakeparms_t->basedir.  This can be overridden with the "-basedir" command
60 line parm to allow code debugging in a different directory.  The base
61 directory is only used during filesystem initialization.
62
63 The "game directory" is the first tree on the search path and directory that
64 all generated files (savegames, screenshots, demos, config files) will be
65 saved to.  This can be overridden with the "-game" command line parameter.
66 The game directory can never be changed while quake is executing.  This is a
67 precaution against having a malicious server instruct clients to write files
68 over areas they shouldn't.
69
70 */
71
72
73 /*
74 =============================================================================
75
76 CONSTANTS
77
78 =============================================================================
79 */
80
81 // Magic numbers of a ZIP file (big-endian format)
82 #define ZIP_DATA_HEADER 0x504B0304  // "PK\3\4"
83 #define ZIP_CDIR_HEADER 0x504B0102  // "PK\1\2"
84 #define ZIP_END_HEADER  0x504B0506  // "PK\5\6"
85
86 // Other constants for ZIP files
87 #define ZIP_MAX_COMMENTS_SIZE           ((unsigned short)0xFFFF)
88 #define ZIP_END_CDIR_SIZE                       22
89 #define ZIP_CDIR_CHUNK_BASE_SIZE        46
90 #define ZIP_LOCAL_CHUNK_BASE_SIZE       30
91
92 // Zlib constants (from zlib.h)
93 #define Z_SYNC_FLUSH    2
94 #define MAX_WBITS               15
95 #define Z_OK                    0
96 #define Z_STREAM_END    1
97 #define ZLIB_VERSION    "1.2.3"
98
99 // Uncomment the following line if the zlib DLL you have still uses
100 // the 1.1.x series calling convention on Win32 (WINAPI)
101 //#define ZLIB_USES_WINAPI
102
103
104 /*
105 =============================================================================
106
107 TYPES
108
109 =============================================================================
110 */
111
112 // Zlib stream (from zlib.h)
113 // Warning: some pointers we don't use directly have
114 // been cast to "void*" for a matter of simplicity
115 typedef struct
116 {
117         unsigned char                   *next_in;       // next input byte
118         unsigned int    avail_in;       // number of bytes available at next_in
119         unsigned long   total_in;       // total nb of input bytes read so far
120
121         unsigned char                   *next_out;      // next output byte should be put there
122         unsigned int    avail_out;      // remaining free space at next_out
123         unsigned long   total_out;      // total nb of bytes output so far
124
125         char                    *msg;           // last error message, NULL if no error
126         void                    *state;         // not visible by applications
127
128         void                    *zalloc;        // used to allocate the internal state
129         void                    *zfree;         // used to free the internal state
130         void                    *opaque;        // private data object passed to zalloc and zfree
131
132         int                             data_type;      // best guess about the data type: ascii or binary
133         unsigned long   adler;          // adler32 value of the uncompressed data
134         unsigned long   reserved;       // reserved for future use
135 } z_stream;
136
137
138 // inside a package (PAK or PK3)
139 #define QFILE_FLAG_PACKED (1 << 0)
140 // file is compressed using the deflate algorithm (PK3 only)
141 #define QFILE_FLAG_DEFLATED (1 << 1)
142
143 #define FILE_BUFF_SIZE 2048
144 typedef struct
145 {
146         z_stream        zstream;
147         size_t          comp_length;                    // length of the compressed file
148         size_t          in_ind, in_len;                 // input buffer current index and length
149         size_t          in_position;                    // position in the compressed file
150         unsigned char           input [FILE_BUFF_SIZE];
151 } ztoolkit_t;
152
153 struct qfile_s
154 {
155         int                             flags;
156         int                             handle;                                 // file descriptor
157         fs_offset_t             real_length;                    // uncompressed file size (for files opened in "read" mode)
158         fs_offset_t             position;                               // current position in the file
159         fs_offset_t             offset;                                 // offset into the package (0 if external file)
160         int                             ungetc;                                 // single stored character from ungetc, cleared to EOF when read
161
162         // Contents buffer
163         fs_offset_t             buff_ind, buff_len;             // buffer current index and length
164         unsigned char                   buff [FILE_BUFF_SIZE];
165
166         // For zipped files
167         ztoolkit_t*             ztk;
168 };
169
170
171 // ------ PK3 files on disk ------ //
172
173 // You can get the complete ZIP format description from PKWARE website
174
175 typedef struct pk3_endOfCentralDir_s
176 {
177         unsigned int signature;
178         unsigned short disknum;
179         unsigned short cdir_disknum;    // number of the disk with the start of the central directory
180         unsigned short localentries;    // number of entries in the central directory on this disk
181         unsigned short nbentries;               // total number of entries in the central directory on this disk
182         unsigned int cdir_size;                 // size of the central directory
183         unsigned int cdir_offset;               // with respect to the starting disk number
184         unsigned short comment_size;
185 } pk3_endOfCentralDir_t;
186
187
188 // ------ PAK files on disk ------ //
189 typedef struct dpackfile_s
190 {
191         char name[56];
192         int filepos, filelen;
193 } dpackfile_t;
194
195 typedef struct dpackheader_s
196 {
197         char id[4];
198         int dirofs;
199         int dirlen;
200 } dpackheader_t;
201
202
203 // Packages in memory
204 // the offset in packfile_t is the true contents offset
205 #define PACKFILE_FLAG_TRUEOFFS (1 << 0)
206 // file compressed using the deflate algorithm
207 #define PACKFILE_FLAG_DEFLATED (1 << 1)
208
209 typedef struct packfile_s
210 {
211         char name [MAX_QPATH];
212         int flags;
213         fs_offset_t offset;
214         fs_offset_t packsize;   // size in the package
215         fs_offset_t realsize;   // real file size (uncompressed)
216 } packfile_t;
217
218 typedef struct pack_s
219 {
220         char filename [MAX_OSPATH];
221         int handle;
222         int ignorecase;  // PK3 ignores case
223         int numfiles;
224         packfile_t *files;
225         struct pack_s *next;
226 } pack_t;
227
228
229 // Search paths for files (including packages)
230 typedef struct searchpath_s
231 {
232         // only one of filename / pack will be used
233         char filename[MAX_OSPATH];
234         pack_t *pack;
235         struct searchpath_s *next;
236 } searchpath_t;
237
238
239 /*
240 =============================================================================
241
242 FUNCTION PROTOTYPES
243
244 =============================================================================
245 */
246
247 void FS_Dir_f(void);
248 void FS_Ls_f(void);
249
250 static const char *FS_FileExtension (const char *in);
251 static searchpath_t *FS_FindFile (const char *name, int* index, qboolean quiet);
252 static packfile_t* FS_AddFileToPack (const char* name, pack_t* pack,
253                                                                         fs_offset_t offset, fs_offset_t packsize,
254                                                                         fs_offset_t realsize, int flags);
255
256
257 /*
258 =============================================================================
259
260 VARIABLES
261
262 =============================================================================
263 */
264
265 mempool_t *fs_mempool;
266
267 pack_t *packlist = NULL;
268
269 searchpath_t *fs_searchpaths = NULL;
270
271 #define MAX_FILES_IN_PACK       65536
272
273 char fs_gamedir[MAX_OSPATH];
274 char fs_basedir[MAX_OSPATH];
275
276 qboolean fs_modified;   // set true if using non-id files
277
278 cvar_t scr_screenshot_name = {0, "scr_screenshot_name","dp", "prefix name for saved screenshots (changes based on -game commandline, as well as which game mode is running)"};
279
280
281 /*
282 =============================================================================
283
284 PRIVATE FUNCTIONS - PK3 HANDLING
285
286 =============================================================================
287 */
288
289 // Functions exported from zlib
290 #if defined(WIN32) && defined(ZLIB_USES_WINAPI)
291 # define ZEXPORT WINAPI
292 #else
293 # define ZEXPORT
294 #endif
295
296 static int (ZEXPORT *qz_inflate) (z_stream* strm, int flush);
297 static int (ZEXPORT *qz_inflateEnd) (z_stream* strm);
298 static int (ZEXPORT *qz_inflateInit2_) (z_stream* strm, int windowBits, const char *version, int stream_size);
299 static int (ZEXPORT *qz_inflateReset) (z_stream* strm);
300
301 #define qz_inflateInit2(strm, windowBits) \
302         qz_inflateInit2_((strm), (windowBits), ZLIB_VERSION, sizeof(z_stream))
303
304 static dllfunction_t zlibfuncs[] =
305 {
306         {"inflate",                     (void **) &qz_inflate},
307         {"inflateEnd",          (void **) &qz_inflateEnd},
308         {"inflateInit2_",       (void **) &qz_inflateInit2_},
309         {"inflateReset",        (void **) &qz_inflateReset},
310         {NULL, NULL}
311 };
312
313 // Handle for Zlib DLL
314 static dllhandle_t zlib_dll = NULL;
315
316
317 /*
318 ====================
319 PK3_CloseLibrary
320
321 Unload the Zlib DLL
322 ====================
323 */
324 void PK3_CloseLibrary (void)
325 {
326         Sys_UnloadLibrary (&zlib_dll);
327 }
328
329
330 /*
331 ====================
332 PK3_OpenLibrary
333
334 Try to load the Zlib DLL
335 ====================
336 */
337 qboolean PK3_OpenLibrary (void)
338 {
339         const char* dllnames [] =
340         {
341 #if defined(WIN64)
342                 "zlib64.dll",
343 #elif defined(WIN32)
344 # ifdef ZLIB_USES_WINAPI
345                 "zlibwapi.dll",
346                 "zlib.dll",
347 # else
348                 "zlib1.dll",
349 # endif
350 #elif defined(MACOSX)
351                 "libz.dylib",
352 #else
353                 "libz.so.1",
354                 "libz.so",
355 #endif
356                 NULL
357         };
358
359         // Already loaded?
360         if (zlib_dll)
361                 return true;
362
363         // Load the DLL
364         if (! Sys_LoadLibrary (dllnames, &zlib_dll, zlibfuncs))
365         {
366                 Con_Printf ("Compressed files support disabled\n");
367                 return false;
368         }
369
370         Con_Printf ("Compressed files support enabled\n");
371         return true;
372 }
373
374
375 /*
376 ====================
377 PK3_GetEndOfCentralDir
378
379 Extract the end of the central directory from a PK3 package
380 ====================
381 */
382 qboolean PK3_GetEndOfCentralDir (const char *packfile, int packhandle, pk3_endOfCentralDir_t *eocd)
383 {
384         fs_offset_t filesize, maxsize;
385         unsigned char *buffer, *ptr;
386         int ind;
387
388         // Get the package size
389         filesize = lseek (packhandle, 0, SEEK_END);
390         if (filesize < ZIP_END_CDIR_SIZE)
391                 return false;
392
393         // Load the end of the file in memory
394         if (filesize < ZIP_MAX_COMMENTS_SIZE + ZIP_END_CDIR_SIZE)
395                 maxsize = filesize;
396         else
397                 maxsize = ZIP_MAX_COMMENTS_SIZE + ZIP_END_CDIR_SIZE;
398         buffer = (unsigned char *)Mem_Alloc (tempmempool, maxsize);
399         lseek (packhandle, filesize - maxsize, SEEK_SET);
400         if (read (packhandle, buffer, maxsize) != (fs_offset_t) maxsize)
401         {
402                 Mem_Free (buffer);
403                 return false;
404         }
405
406         // Look for the end of central dir signature around the end of the file
407         maxsize -= ZIP_END_CDIR_SIZE;
408         ptr = &buffer[maxsize];
409         ind = 0;
410         while (BuffBigLong (ptr) != ZIP_END_HEADER)
411         {
412                 if (ind == maxsize)
413                 {
414                         Mem_Free (buffer);
415                         return false;
416                 }
417
418                 ind++;
419                 ptr--;
420         }
421
422         memcpy (eocd, ptr, ZIP_END_CDIR_SIZE);
423         eocd->signature = LittleLong (eocd->signature);
424         eocd->disknum = LittleShort (eocd->disknum);
425         eocd->cdir_disknum = LittleShort (eocd->cdir_disknum);
426         eocd->localentries = LittleShort (eocd->localentries);
427         eocd->nbentries = LittleShort (eocd->nbentries);
428         eocd->cdir_size = LittleLong (eocd->cdir_size);
429         eocd->cdir_offset = LittleLong (eocd->cdir_offset);
430         eocd->comment_size = LittleShort (eocd->comment_size);
431
432         Mem_Free (buffer);
433
434         return true;
435 }
436
437
438 /*
439 ====================
440 PK3_BuildFileList
441
442 Extract the file list from a PK3 file
443 ====================
444 */
445 int PK3_BuildFileList (pack_t *pack, const pk3_endOfCentralDir_t *eocd)
446 {
447         unsigned char *central_dir, *ptr;
448         unsigned int ind;
449         fs_offset_t remaining;
450
451         // Load the central directory in memory
452         central_dir = (unsigned char *)Mem_Alloc (tempmempool, eocd->cdir_size);
453         lseek (pack->handle, eocd->cdir_offset, SEEK_SET);
454         read (pack->handle, central_dir, eocd->cdir_size);
455
456         // Extract the files properties
457         // The parsing is done "by hand" because some fields have variable sizes and
458         // the constant part isn't 4-bytes aligned, which makes the use of structs difficult
459         remaining = eocd->cdir_size;
460         pack->numfiles = 0;
461         ptr = central_dir;
462         for (ind = 0; ind < eocd->nbentries; ind++)
463         {
464                 fs_offset_t namesize, count;
465
466                 // Checking the remaining size
467                 if (remaining < ZIP_CDIR_CHUNK_BASE_SIZE)
468                 {
469                         Mem_Free (central_dir);
470                         return -1;
471                 }
472                 remaining -= ZIP_CDIR_CHUNK_BASE_SIZE;
473
474                 // Check header
475                 if (BuffBigLong (ptr) != ZIP_CDIR_HEADER)
476                 {
477                         Mem_Free (central_dir);
478                         return -1;
479                 }
480
481                 namesize = BuffLittleShort (&ptr[28]);  // filename length
482
483                 // Check encryption, compression, and attributes
484                 // 1st uint8  : general purpose bit flag
485                 //    Check bits 0 (encryption), 3 (data descriptor after the file), and 5 (compressed patched data (?))
486                 // 2nd uint8 : external file attributes
487                 //    Check bits 3 (file is a directory) and 5 (file is a volume (?))
488                 if ((ptr[8] & 0x29) == 0 && (ptr[38] & 0x18) == 0)
489                 {
490                         // Still enough bytes for the name?
491                         if (remaining < namesize || namesize >= (int)sizeof (*pack->files))
492                         {
493                                 Mem_Free (central_dir);
494                                 return -1;
495                         }
496
497                         // WinZip doesn't use the "directory" attribute, so we need to check the name directly
498                         if (ptr[ZIP_CDIR_CHUNK_BASE_SIZE + namesize - 1] != '/')
499                         {
500                                 char filename [sizeof (pack->files[0].name)];
501                                 fs_offset_t offset, packsize, realsize;
502                                 int flags;
503
504                                 // Extract the name (strip it if necessary)
505                                 namesize = min(namesize, (int)sizeof (filename) - 1);
506                                 memcpy (filename, &ptr[ZIP_CDIR_CHUNK_BASE_SIZE], namesize);
507                                 filename[namesize] = '\0';
508
509                                 if (BuffLittleShort (&ptr[10]))
510                                         flags = PACKFILE_FLAG_DEFLATED;
511                                 else
512                                         flags = 0;
513                                 offset = BuffLittleLong (&ptr[42]);
514                                 packsize = BuffLittleLong (&ptr[20]);
515                                 realsize = BuffLittleLong (&ptr[24]);
516                                 FS_AddFileToPack (filename, pack, offset, packsize, realsize, flags);
517                         }
518                 }
519
520                 // Skip the name, additionnal field, and comment
521                 // 1er uint16 : extra field length
522                 // 2eme uint16 : file comment length
523                 count = namesize + BuffLittleShort (&ptr[30]) + BuffLittleShort (&ptr[32]);
524                 ptr += ZIP_CDIR_CHUNK_BASE_SIZE + count;
525                 remaining -= count;
526         }
527
528         // If the package is empty, central_dir is NULL here
529         if (central_dir != NULL)
530                 Mem_Free (central_dir);
531         return pack->numfiles;
532 }
533
534
535 /*
536 ====================
537 FS_LoadPackPK3
538
539 Create a package entry associated with a PK3 file
540 ====================
541 */
542 pack_t *FS_LoadPackPK3 (const char *packfile)
543 {
544         int packhandle;
545         pk3_endOfCentralDir_t eocd;
546         pack_t *pack;
547         int real_nb_files;
548
549         packhandle = open (packfile, O_RDONLY | O_BINARY);
550         if (packhandle < 0)
551                 return NULL;
552
553         if (! PK3_GetEndOfCentralDir (packfile, packhandle, &eocd))
554         {
555                 Con_Printf ("%s is not a PK3 file\n", packfile);
556                 close(packhandle);
557                 return NULL;
558         }
559
560         // Multi-volume ZIP archives are NOT allowed
561         if (eocd.disknum != 0 || eocd.cdir_disknum != 0)
562         {
563                 Con_Printf ("%s is a multi-volume ZIP archive\n", packfile);
564                 close(packhandle);
565                 return NULL;
566         }
567
568         // We only need to do this test if MAX_FILES_IN_PACK is lesser than 65535
569         // since eocd.nbentries is an unsigned 16 bits integer
570 #if MAX_FILES_IN_PACK < 65535
571         if (eocd.nbentries > MAX_FILES_IN_PACK)
572         {
573                 Con_Printf ("%s contains too many files (%hu)\n", packfile, eocd.nbentries);
574                 close(packhandle);
575                 return NULL;
576         }
577 #endif
578
579         // Create a package structure in memory
580         pack = (pack_t *)Mem_Alloc(fs_mempool, sizeof (pack_t));
581         pack->ignorecase = true; // PK3 ignores case
582         strlcpy (pack->filename, packfile, sizeof (pack->filename));
583         pack->handle = packhandle;
584         pack->numfiles = eocd.nbentries;
585         pack->files = (packfile_t *)Mem_Alloc(fs_mempool, eocd.nbentries * sizeof(packfile_t));
586         pack->next = packlist;
587         packlist = pack;
588
589         real_nb_files = PK3_BuildFileList (pack, &eocd);
590         if (real_nb_files < 0)
591         {
592                 Con_Printf ("%s is not a valid PK3 file\n", packfile);
593                 close(pack->handle);
594                 Mem_Free(pack);
595                 return NULL;
596         }
597
598         Con_Printf("Added packfile %s (%i files)\n", packfile, real_nb_files);
599         return pack;
600 }
601
602
603 /*
604 ====================
605 PK3_GetTrueFileOffset
606
607 Find where the true file data offset is
608 ====================
609 */
610 qboolean PK3_GetTrueFileOffset (packfile_t *pfile, pack_t *pack)
611 {
612         unsigned char buffer [ZIP_LOCAL_CHUNK_BASE_SIZE];
613         fs_offset_t count;
614
615         // Already found?
616         if (pfile->flags & PACKFILE_FLAG_TRUEOFFS)
617                 return true;
618
619         // Load the local file description
620         lseek (pack->handle, pfile->offset, SEEK_SET);
621         count = read (pack->handle, buffer, ZIP_LOCAL_CHUNK_BASE_SIZE);
622         if (count != ZIP_LOCAL_CHUNK_BASE_SIZE || BuffBigLong (buffer) != ZIP_DATA_HEADER)
623         {
624                 Con_Printf ("Can't retrieve file %s in package %s\n", pfile->name, pack->filename);
625                 return false;
626         }
627
628         // Skip name and extra field
629         pfile->offset += BuffLittleShort (&buffer[26]) + BuffLittleShort (&buffer[28]) + ZIP_LOCAL_CHUNK_BASE_SIZE;
630
631         pfile->flags |= PACKFILE_FLAG_TRUEOFFS;
632         return true;
633 }
634
635
636 /*
637 =============================================================================
638
639 OTHER PRIVATE FUNCTIONS
640
641 =============================================================================
642 */
643
644
645 /*
646 ====================
647 FS_AddFileToPack
648
649 Add a file to the list of files contained into a package
650 ====================
651 */
652 static packfile_t* FS_AddFileToPack (const char* name, pack_t* pack,
653                                                                          fs_offset_t offset, fs_offset_t packsize,
654                                                                          fs_offset_t realsize, int flags)
655 {
656         int (*strcmp_funct) (const char* str1, const char* str2);
657         int left, right, middle;
658         packfile_t *pfile;
659
660         strcmp_funct = pack->ignorecase ? strcasecmp : strcmp;
661
662         // Look for the slot we should put that file into (binary search)
663         left = 0;
664         right = pack->numfiles - 1;
665         while (left <= right)
666         {
667                 int diff;
668
669                 middle = (left + right) / 2;
670                 diff = strcmp_funct (pack->files[middle].name, name);
671
672                 // If we found the file, there's a problem
673                 if (!diff)
674                         Con_Printf ("Package %s contains the file %s several times\n", pack->filename, name);
675
676                 // If we're too far in the list
677                 if (diff > 0)
678                         right = middle - 1;
679                 else
680                         left = middle + 1;
681         }
682
683         // We have to move the right of the list by one slot to free the one we need
684         pfile = &pack->files[left];
685         memmove (pfile + 1, pfile, (pack->numfiles - left) * sizeof (*pfile));
686         pack->numfiles++;
687
688         strlcpy (pfile->name, name, sizeof (pfile->name));
689         pfile->offset = offset;
690         pfile->packsize = packsize;
691         pfile->realsize = realsize;
692         pfile->flags = flags;
693
694         return pfile;
695 }
696
697
698 /*
699 ============
700 FS_CreatePath
701
702 Only used for FS_Open.
703 ============
704 */
705 void FS_CreatePath (char *path)
706 {
707         char *ofs, save;
708
709         for (ofs = path+1 ; *ofs ; ofs++)
710         {
711                 if (*ofs == '/' || *ofs == '\\')
712                 {
713                         // create the directory
714                         save = *ofs;
715                         *ofs = 0;
716                         FS_mkdir (path);
717                         *ofs = save;
718                 }
719         }
720 }
721
722
723 /*
724 ============
725 FS_Path_f
726
727 ============
728 */
729 void FS_Path_f (void)
730 {
731         searchpath_t *s;
732
733         Con_Print("Current search path:\n");
734         for (s=fs_searchpaths ; s ; s=s->next)
735         {
736                 if (s->pack)
737                         Con_Printf("%s (%i files)\n", s->pack->filename, s->pack->numfiles);
738                 else
739                         Con_Printf("%s\n", s->filename);
740         }
741 }
742
743
744 /*
745 =================
746 FS_LoadPackPAK
747
748 Takes an explicit (not game tree related) path to a pak file.
749
750 Loads the header and directory, adding the files at the beginning
751 of the list so they override previous pack files.
752 =================
753 */
754 pack_t *FS_LoadPackPAK (const char *packfile)
755 {
756         dpackheader_t header;
757         int i, numpackfiles;
758         int packhandle;
759         pack_t *pack;
760         dpackfile_t *info;
761
762         packhandle = open (packfile, O_RDONLY | O_BINARY);
763         if (packhandle < 0)
764                 return NULL;
765         read (packhandle, (void *)&header, sizeof(header));
766         if (memcmp(header.id, "PACK", 4))
767         {
768                 Con_Printf ("%s is not a packfile\n", packfile);
769                 close(packhandle);
770                 return NULL;
771         }
772         header.dirofs = LittleLong (header.dirofs);
773         header.dirlen = LittleLong (header.dirlen);
774
775         if (header.dirlen % sizeof(dpackfile_t))
776         {
777                 Con_Printf ("%s has an invalid directory size\n", packfile);
778                 close(packhandle);
779                 return NULL;
780         }
781
782         numpackfiles = header.dirlen / sizeof(dpackfile_t);
783
784         if (numpackfiles > MAX_FILES_IN_PACK)
785         {
786                 Con_Printf ("%s has %i files\n", packfile, numpackfiles);
787                 close(packhandle);
788                 return NULL;
789         }
790
791         info = (dpackfile_t *)Mem_Alloc(tempmempool, sizeof(*info) * numpackfiles);
792         lseek (packhandle, header.dirofs, SEEK_SET);
793         if(header.dirlen != read (packhandle, (void *)info, header.dirlen))
794         {
795                 Con_Printf("%s is an incomplete PAK, not loading\n", packfile);
796                 Mem_Free(info);
797                 close(packhandle);
798                 return NULL;
799         }
800
801         pack = (pack_t *)Mem_Alloc(fs_mempool, sizeof (pack_t));
802         pack->ignorecase = false; // PAK is case sensitive
803         strlcpy (pack->filename, packfile, sizeof (pack->filename));
804         pack->handle = packhandle;
805         pack->numfiles = 0;
806         pack->files = (packfile_t *)Mem_Alloc(fs_mempool, numpackfiles * sizeof(packfile_t));
807         pack->next = packlist;
808         packlist = pack;
809
810         // parse the directory
811         for (i = 0;i < numpackfiles;i++)
812         {
813                 fs_offset_t offset = LittleLong (info[i].filepos);
814                 fs_offset_t size = LittleLong (info[i].filelen);
815
816                 FS_AddFileToPack (info[i].name, pack, offset, size, size, PACKFILE_FLAG_TRUEOFFS);
817         }
818
819         Mem_Free(info);
820
821         Con_Printf("Added packfile %s (%i files)\n", packfile, numpackfiles);
822         return pack;
823 }
824
825 /*
826 ================
827 FS_AddPack_Fullpath
828
829 Adds the given pack to the search path.
830 The pack type is autodetected by the file extension.
831
832 Returns true if the file was successfully added to the
833 search path or if it was already included.
834
835 If keep_plain_dirs is set, the pack will be added AFTER the first sequence of
836 plain directories.
837 ================
838 */
839 static qboolean FS_AddPack_Fullpath(const char *pakfile, qboolean *already_loaded, qboolean keep_plain_dirs)
840 {
841         searchpath_t *search;
842         pack_t *pak = NULL;
843         const char *ext = FS_FileExtension(pakfile);
844
845         for(search = fs_searchpaths; search; search = search->next)
846         {
847                 if(search->pack && !strcasecmp(search->pack->filename, pakfile))
848                 {
849                         if(already_loaded)
850                                 *already_loaded = true;
851                         return true; // already loaded
852                 }
853         }
854
855         if(already_loaded)
856                 *already_loaded = false;
857
858         if(!strcasecmp(ext, "pak"))
859                 pak = FS_LoadPackPAK (pakfile);
860         else if(!strcasecmp(ext, "pk3"))
861                 pak = FS_LoadPackPK3 (pakfile);
862         else
863                 Con_Printf("\"%s\" does not have a pack extension\n", pakfile);
864
865         if (pak)
866         {
867                 if(keep_plain_dirs)
868                 {
869                         // find the first item whose next one is a pack or NULL
870                         searchpath_t *insertion_point = 0;
871                         if(fs_searchpaths && !fs_searchpaths->pack)
872                         {
873                                 insertion_point = fs_searchpaths;
874                                 for(;;)
875                                 {
876                                         if(!insertion_point->next)
877                                                 break;
878                                         if(insertion_point->next->pack)
879                                                 break;
880                                         insertion_point = insertion_point->next;
881                                 }
882                         }
883                         // If insertion_point is NULL, this means that either there is no
884                         // item in the list yet, or that the very first item is a pack. In
885                         // that case, we want to insert at the beginning...
886                         if(!insertion_point)
887                         {
888                                 search = (searchpath_t *)Mem_Alloc(fs_mempool, sizeof(searchpath_t));
889                                 search->pack = pak;
890                                 search->next = fs_searchpaths;
891                                 fs_searchpaths = search;
892                         }
893                         else
894                         // otherwise we want to append directly after insertion_point.
895                         {
896                                 search = (searchpath_t *)Mem_Alloc(fs_mempool, sizeof(searchpath_t));
897                                 search->pack = pak;
898                                 search->next = insertion_point->next;
899                                 insertion_point->next = search;
900                         }
901                 }
902                 else
903                 {
904                         search = (searchpath_t *)Mem_Alloc(fs_mempool, sizeof(searchpath_t));
905                         search->pack = pak;
906                         search->next = fs_searchpaths;
907                         fs_searchpaths = search;
908                 }
909                 return true;
910         }
911         else
912         {
913                 Con_Printf("unable to load pak \"%s\"\n", pakfile);
914                 return false;
915         }
916 }
917
918
919 /*
920 ================
921 FS_AddPack
922
923 Adds the given pack to the search path and searches for it in the game path.
924 The pack type is autodetected by the file extension.
925
926 Returns true if the file was successfully added to the
927 search path or if it was already included.
928
929 If keep_plain_dirs is set, the pack will be added AFTER the first sequence of
930 plain directories.
931 ================
932 */
933 qboolean FS_AddPack(const char *pakfile, qboolean *already_loaded, qboolean keep_plain_dirs)
934 {
935         char fullpath[MAX_QPATH];
936         int index;
937         searchpath_t *search;
938
939         if(already_loaded)
940                 *already_loaded = false;
941
942         // then find the real name...
943         search = FS_FindFile(pakfile, &index, true);
944         if(!search || search->pack)
945         {
946                 Con_Printf("could not find pak \"%s\"\n", pakfile);
947                 return false;
948         }
949
950         dpsnprintf(fullpath, sizeof(fullpath), "%s%s", search->filename, pakfile);
951
952         return FS_AddPack_Fullpath(fullpath, already_loaded, keep_plain_dirs);
953 }
954
955
956 /*
957 ================
958 FS_AddGameDirectory
959
960 Sets fs_gamedir, adds the directory to the head of the path,
961 then loads and adds pak1.pak pak2.pak ...
962 ================
963 */
964 void FS_AddGameDirectory (const char *dir)
965 {
966         stringlist_t *list, *current;
967         searchpath_t *search;
968         char pakfile[MAX_OSPATH];
969
970         strlcpy (fs_gamedir, dir, sizeof (fs_gamedir));
971
972         list = listdirectory(dir);
973
974         // add any PAK package in the directory
975         for (current = list;current;current = current->next)
976         {
977                 if (!strcasecmp(FS_FileExtension(current->text), "pak"))
978                 {
979                         dpsnprintf (pakfile, sizeof (pakfile), "%s%s", dir, current->text);
980                         FS_AddPack_Fullpath(pakfile, NULL, false);
981                 }
982         }
983
984         // add any PK3 package in the director
985         for (current = list;current;current = current->next)
986         {
987                 if (!strcasecmp(FS_FileExtension(current->text), "pk3"))
988                 {
989                         dpsnprintf (pakfile, sizeof (pakfile), "%s%s", dir, current->text);
990                         FS_AddPack_Fullpath(pakfile, NULL, false);
991                 }
992         }
993         freedirectory(list);
994
995         // Add the directory to the search path
996         // (unpacked files have the priority over packed files)
997         search = (searchpath_t *)Mem_Alloc(fs_mempool, sizeof(searchpath_t));
998         strlcpy (search->filename, dir, sizeof (search->filename));
999         search->next = fs_searchpaths;
1000         fs_searchpaths = search;
1001 }
1002
1003
1004 /*
1005 ================
1006 FS_AddGameHierarchy
1007 ================
1008 */
1009 void FS_AddGameHierarchy (const char *dir)
1010 {
1011 #ifndef WIN32
1012         const char *homedir;
1013 #endif
1014
1015         // Add the common game directory
1016         FS_AddGameDirectory (va("%s%s/", fs_basedir, dir));
1017
1018 #ifndef WIN32
1019         // Add the personal game directory
1020         homedir = getenv ("HOME");
1021         if (homedir != NULL && homedir[0] != '\0')
1022                 FS_AddGameDirectory (va("%s/.%s/%s/", homedir, gameuserdirname, dir));
1023 #endif
1024 }
1025
1026
1027 /*
1028 ============
1029 FS_FileExtension
1030 ============
1031 */
1032 static const char *FS_FileExtension (const char *in)
1033 {
1034         const char *separator, *backslash, *colon, *dot;
1035
1036         separator = strrchr(in, '/');
1037         backslash = strrchr(in, '\\');
1038         if (!separator || separator < backslash)
1039                 separator = backslash;
1040         colon = strrchr(in, ':');
1041         if (!separator || separator < colon)
1042                 separator = colon;
1043
1044         dot = strrchr(in, '.');
1045         if (dot == NULL || (separator && (dot < separator)))
1046                 return "";
1047
1048         return dot + 1;
1049 }
1050
1051
1052 /*
1053 ================
1054 FS_Init
1055 ================
1056 */
1057 void FS_Init (void)
1058 {
1059         int i;
1060         searchpath_t *search;
1061
1062         fs_mempool = Mem_AllocPool("file management", 0, NULL);
1063
1064         strlcpy(fs_gamedir, "", sizeof(fs_gamedir));
1065
1066 // If the base directory is explicitly defined by the compilation process
1067 #ifdef DP_FS_BASEDIR
1068         strlcpy(fs_basedir, DP_FS_BASEDIR, sizeof(fs_basedir));
1069 #else
1070         strlcpy(fs_basedir, "", sizeof(fs_basedir));
1071
1072 #ifdef MACOSX
1073         // FIXME: is there a better way to find the directory outside the .app?
1074         if (strstr(com_argv[0], ".app/"))
1075         {
1076                 char *split;
1077
1078                 split = strstr(com_argv[0], ".app/");
1079                 while (split > com_argv[0] && *split != '/')
1080                         split--;
1081                 strlcpy(fs_basedir, com_argv[0], sizeof(fs_basedir));
1082                 fs_basedir[split - com_argv[0]] = 0;
1083         }
1084 #endif
1085 #endif
1086
1087         PK3_OpenLibrary ();
1088
1089         // -basedir <path>
1090         // Overrides the system supplied base directory (under GAMENAME)
1091 // COMMANDLINEOPTION: Filesystem: -basedir <path> chooses what base directory the game data is in, inside this there should be a data directory for the game (for example id1)
1092         i = COM_CheckParm ("-basedir");
1093         if (i && i < com_argc-1)
1094         {
1095                 strlcpy (fs_basedir, com_argv[i+1], sizeof (fs_basedir));
1096                 i = (int)strlen (fs_basedir);
1097                 if (i > 0 && (fs_basedir[i-1] == '\\' || fs_basedir[i-1] == '/'))
1098                         fs_basedir[i-1] = 0;
1099         }
1100
1101         // add a path separator to the end of the basedir if it lacks one
1102         if (fs_basedir[0] && fs_basedir[strlen(fs_basedir) - 1] != '/' && fs_basedir[strlen(fs_basedir) - 1] != '\\')
1103                 strlcat(fs_basedir, "/", sizeof(fs_basedir));
1104
1105         // -path <dir or packfile> [<dir or packfile>] ...
1106         // Fully specifies the exact search path, overriding the generated one
1107 // COMMANDLINEOPTION: Filesystem: -path <path ..> specifies the full search path manually, overriding the generated one, example: -path c:\quake\id1 c:\quake\pak0.pak c:\quake\pak1.pak (not recommended)
1108         i = COM_CheckParm ("-path");
1109         if (i)
1110         {
1111                 fs_modified = true;
1112                 while (++i < com_argc)
1113                 {
1114                         if (!com_argv[i] || com_argv[i][0] == '+' || com_argv[i][0] == '-')
1115                                 break;
1116
1117                         if(!FS_AddPack_Fullpath(com_argv[i], NULL, false))
1118                         {
1119                                 search = (searchpath_t *)Mem_Alloc(fs_mempool, sizeof(searchpath_t));
1120                                 strlcpy (search->filename, com_argv[i], sizeof (search->filename));
1121                                 search->next = fs_searchpaths;
1122                                 fs_searchpaths = search;
1123                         }
1124                 }
1125                 return;
1126         }
1127
1128         // add the game-specific paths
1129         // gamedirname1 (typically id1)
1130         FS_AddGameHierarchy (gamedirname1);
1131
1132         // add the game-specific path, if any
1133         if (gamedirname2)
1134         {
1135                 fs_modified = true;
1136                 FS_AddGameHierarchy (gamedirname2);
1137         }
1138
1139         // set the com_modname (reported in server info)
1140         strlcpy(com_modname, gamedirname1, sizeof(com_modname));
1141
1142         // -game <gamedir>
1143         // Adds basedir/gamedir as an override game
1144         // LordHavoc: now supports multiple -game directories
1145         for (i = 1;i < com_argc;i++)
1146         {
1147                 if (!com_argv[i])
1148                         continue;
1149                 if (!strcmp (com_argv[i], "-game") && i < com_argc-1)
1150                 {
1151                         i++;
1152                         fs_modified = true;
1153                         FS_AddGameHierarchy (com_argv[i]);
1154                         // update the com_modname
1155                         strlcpy (com_modname, com_argv[i], sizeof (com_modname));
1156                 }
1157         }
1158
1159         // If "-condebug" is in the command line, remove the previous log file
1160         if (COM_CheckParm ("-condebug") != 0)
1161                 unlink (va("%s/qconsole.log", fs_gamedir));
1162 }
1163
1164 void FS_Init_Commands(void)
1165 {
1166         Cvar_RegisterVariable (&scr_screenshot_name);
1167
1168         Cmd_AddCommand ("path", FS_Path_f, "print searchpath (game directories and archives)");
1169         Cmd_AddCommand ("dir", FS_Dir_f, "list files in searchpath matching an * filename pattern, one per line");
1170         Cmd_AddCommand ("ls", FS_Ls_f, "list files in searchpath matching an * filename pattern, multiple per line");
1171
1172         // set the default screenshot name to either the mod name or the
1173         // gamemode screenshot name
1174         if (fs_modified)
1175                 Cvar_SetQuick (&scr_screenshot_name, com_modname);
1176         else
1177                 Cvar_SetQuick (&scr_screenshot_name, gamescreenshotname);
1178 }
1179
1180 /*
1181 ================
1182 FS_Shutdown
1183 ================
1184 */
1185 void FS_Shutdown (void)
1186 {
1187         Mem_FreePool (&fs_mempool);
1188 }
1189
1190 /*
1191 ====================
1192 FS_SysOpen
1193
1194 Internal function used to create a qfile_t and open the relevant non-packed file on disk
1195 ====================
1196 */
1197 static qfile_t* FS_SysOpen (const char* filepath, const char* mode, qboolean nonblocking)
1198 {
1199         qfile_t* file;
1200         int mod, opt;
1201         unsigned int ind;
1202
1203         // Parse the mode string
1204         switch (mode[0])
1205         {
1206                 case 'r':
1207                         mod = O_RDONLY;
1208                         opt = 0;
1209                         break;
1210                 case 'w':
1211                         mod = O_WRONLY;
1212                         opt = O_CREAT | O_TRUNC;
1213                         break;
1214                 case 'a':
1215                         mod = O_WRONLY;
1216                         opt = O_CREAT | O_APPEND;
1217                         break;
1218                 default:
1219                         Con_Printf ("FS_SysOpen(%s, %s): invalid mode\n", filepath, mode);
1220                         return NULL;
1221         }
1222         for (ind = 1; mode[ind] != '\0'; ind++)
1223         {
1224                 switch (mode[ind])
1225                 {
1226                         case '+':
1227                                 mod = O_RDWR;
1228                                 break;
1229                         case 'b':
1230                                 opt |= O_BINARY;
1231                                 break;
1232                         default:
1233                                 Con_Printf ("FS_SysOpen(%s, %s): unknown character in mode (%c)\n",
1234                                                         filepath, mode, mode[ind]);
1235                 }
1236         }
1237
1238         if (nonblocking)
1239                 opt |= O_NONBLOCK;
1240
1241         file = (qfile_t *)Mem_Alloc (fs_mempool, sizeof (*file));
1242         memset (file, 0, sizeof (*file));
1243         file->ungetc = EOF;
1244
1245         file->handle = open (filepath, mod | opt, 0666);
1246         if (file->handle < 0)
1247         {
1248                 Mem_Free (file);
1249                 return NULL;
1250         }
1251
1252         file->real_length = lseek (file->handle, 0, SEEK_END);
1253
1254         // For files opened in append mode, we start at the end of the file
1255         if (mod & O_APPEND)
1256                 file->position = file->real_length;
1257         else
1258                 lseek (file->handle, 0, SEEK_SET);
1259
1260         return file;
1261 }
1262
1263
1264 /*
1265 ===========
1266 FS_OpenPackedFile
1267
1268 Open a packed file using its package file descriptor
1269 ===========
1270 */
1271 qfile_t *FS_OpenPackedFile (pack_t* pack, int pack_ind)
1272 {
1273         packfile_t *pfile;
1274         int dup_handle;
1275         qfile_t* file;
1276
1277         pfile = &pack->files[pack_ind];
1278
1279         // If we don't have the true offset, get it now
1280         if (! (pfile->flags & PACKFILE_FLAG_TRUEOFFS))
1281                 if (!PK3_GetTrueFileOffset (pfile, pack))
1282                         return NULL;
1283
1284         // No Zlib DLL = no compressed files
1285         if (!zlib_dll && (pfile->flags & PACKFILE_FLAG_DEFLATED))
1286         {
1287                 Con_Printf("WARNING: can't open the compressed file %s\n"
1288                                         "You need the Zlib DLL to use compressed files\n",
1289                                         pfile->name);
1290                 return NULL;
1291         }
1292
1293         // LordHavoc: lseek affects all duplicates of a handle so we do it before
1294         // the dup() call to avoid having to close the dup_handle on error here
1295         if (lseek (pack->handle, pfile->offset, SEEK_SET) == -1)
1296         {
1297                 Con_Printf ("FS_OpenPackedFile: can't lseek to %s in %s (offset: %d)\n",
1298                                         pfile->name, pack->filename, pfile->offset);
1299                 return NULL;
1300         }
1301
1302         dup_handle = dup (pack->handle);
1303         if (dup_handle < 0)
1304         {
1305                 Con_Printf ("FS_OpenPackedFile: can't dup package's handle (pack: %s)\n", pack->filename);
1306                 return NULL;
1307         }
1308
1309         file = (qfile_t *)Mem_Alloc (fs_mempool, sizeof (*file));
1310         memset (file, 0, sizeof (*file));
1311         file->handle = dup_handle;
1312         file->flags = QFILE_FLAG_PACKED;
1313         file->real_length = pfile->realsize;
1314         file->offset = pfile->offset;
1315         file->position = 0;
1316         file->ungetc = EOF;
1317
1318         if (pfile->flags & PACKFILE_FLAG_DEFLATED)
1319         {
1320                 ztoolkit_t *ztk;
1321
1322                 file->flags |= QFILE_FLAG_DEFLATED;
1323
1324                 // We need some more variables
1325                 ztk = (ztoolkit_t *)Mem_Alloc (fs_mempool, sizeof (*ztk));
1326
1327                 ztk->comp_length = pfile->packsize;
1328
1329                 // Initialize zlib stream
1330                 ztk->zstream.next_in = ztk->input;
1331                 ztk->zstream.avail_in = 0;
1332
1333                 /* From Zlib's "unzip.c":
1334                  *
1335                  * windowBits is passed < 0 to tell that there is no zlib header.
1336                  * Note that in this case inflate *requires* an extra "dummy" byte
1337                  * after the compressed stream in order to complete decompression and
1338                  * return Z_STREAM_END.
1339                  * In unzip, i don't wait absolutely Z_STREAM_END because I known the
1340                  * size of both compressed and uncompressed data
1341                  */
1342                 if (qz_inflateInit2 (&ztk->zstream, -MAX_WBITS) != Z_OK)
1343                 {
1344                         Con_Printf ("FS_OpenPackedFile: inflate init error (file: %s)\n", pfile->name);
1345                         close(dup_handle);
1346                         Mem_Free(file);
1347                         return NULL;
1348                 }
1349
1350                 ztk->zstream.next_out = file->buff;
1351                 ztk->zstream.avail_out = sizeof (file->buff);
1352
1353                 file->ztk = ztk;
1354         }
1355
1356         return file;
1357 }
1358
1359 /*
1360 ====================
1361 FS_CheckNastyPath
1362
1363 Return true if the path should be rejected due to one of the following:
1364 1: path elements that are non-portable
1365 2: path elements that would allow access to files outside the game directory,
1366    or are just not a good idea for a mod to be using.
1367 ====================
1368 */
1369 int FS_CheckNastyPath (const char *path)
1370 {
1371         // Windows: don't allow \ in filenames (windows-only), period.
1372         // (on Windows \ is a directory separator, but / is also supported)
1373         if (strstr(path, "\\"))
1374                 return 1; // non-portable
1375
1376         // Mac: don't allow Mac-only filenames - : is a directory separator
1377         // instead of /, but we rely on / working already, so there's no reason to
1378         // support a Mac-only path
1379         // Amiga and Windows: : tries to go to root of drive
1380         if (strstr(path, ":"))
1381                 return 1; // non-portable attempt to go to root of drive
1382
1383         // Amiga: // is parent directory
1384         if (strstr(path, "//"))
1385                 return 1; // non-portable attempt to go to parent directory
1386
1387         // all: don't allow going to current directory (./) or parent directory (../ or /../)
1388         if (strstr(path, "./"))
1389                 return 2; // attempt to go outside the game directory
1390
1391         // Windows and UNIXes: don't allow absolute paths
1392         if (path[0] == '/')
1393                 return 2; // attempt to go outside the game directory
1394
1395         // after all these checks we're pretty sure it's a / separated filename
1396         // and won't do much if any harm
1397         return false;
1398 }
1399
1400
1401 /*
1402 ====================
1403 FS_FindFile
1404
1405 Look for a file in the packages and in the filesystem
1406
1407 Return the searchpath where the file was found (or NULL)
1408 and the file index in the package if relevant
1409 ====================
1410 */
1411 static searchpath_t *FS_FindFile (const char *name, int* index, qboolean quiet)
1412 {
1413         searchpath_t *search;
1414         pack_t *pak;
1415
1416         // search through the path, one element at a time
1417         for (search = fs_searchpaths;search;search = search->next)
1418         {
1419                 // is the element a pak file?
1420                 if (search->pack)
1421                 {
1422                         int (*strcmp_funct) (const char* str1, const char* str2);
1423                         int left, right, middle;
1424
1425                         pak = search->pack;
1426                         strcmp_funct = pak->ignorecase ? strcasecmp : strcmp;
1427
1428                         // Look for the file (binary search)
1429                         left = 0;
1430                         right = pak->numfiles - 1;
1431                         while (left <= right)
1432                         {
1433                                 int diff;
1434
1435                                 middle = (left + right) / 2;
1436                                 diff = strcmp_funct (pak->files[middle].name, name);
1437
1438                                 // Found it
1439                                 if (!diff)
1440                                 {
1441                                         if (!quiet && developer.integer >= 10)
1442                                                 Con_Printf("FS_FindFile: %s in %s\n",
1443                                                                         pak->files[middle].name, pak->filename);
1444
1445                                         if (index != NULL)
1446                                                 *index = middle;
1447                                         return search;
1448                                 }
1449
1450                                 // If we're too far in the list
1451                                 if (diff > 0)
1452                                         right = middle - 1;
1453                                 else
1454                                         left = middle + 1;
1455                         }
1456                 }
1457                 else
1458                 {
1459                         char netpath[MAX_OSPATH];
1460                         dpsnprintf(netpath, sizeof(netpath), "%s%s", search->filename, name);
1461                         if (FS_SysFileExists (netpath))
1462                         {
1463                                 if (!quiet && developer.integer >= 10)
1464                                         Con_Printf("FS_FindFile: %s\n", netpath);
1465
1466                                 if (index != NULL)
1467                                         *index = -1;
1468                                 return search;
1469                         }
1470                 }
1471         }
1472
1473         if (!quiet && developer.integer >= 10)
1474                 Con_Printf("FS_FindFile: can't find %s\n", name);
1475
1476         if (index != NULL)
1477                 *index = -1;
1478         return NULL;
1479 }
1480
1481
1482 /*
1483 ===========
1484 FS_OpenReadFile
1485
1486 Look for a file in the search paths and open it in read-only mode
1487 ===========
1488 */
1489 qfile_t *FS_OpenReadFile (const char *filename, qboolean quiet, qboolean nonblocking)
1490 {
1491         searchpath_t *search;
1492         int pack_ind;
1493
1494         search = FS_FindFile (filename, &pack_ind, quiet);
1495
1496         // Not found?
1497         if (search == NULL)
1498                 return NULL;
1499
1500         // Found in the filesystem?
1501         if (pack_ind < 0)
1502         {
1503                 char path [MAX_OSPATH];
1504                 dpsnprintf (path, sizeof (path), "%s%s", search->filename, filename);
1505                 return FS_SysOpen (path, "rb", nonblocking);
1506         }
1507
1508         // So, we found it in a package...
1509         return FS_OpenPackedFile (search->pack, pack_ind);
1510 }
1511
1512
1513 /*
1514 =============================================================================
1515
1516 MAIN PUBLIC FUNCTIONS
1517
1518 =============================================================================
1519 */
1520
1521 /*
1522 ====================
1523 FS_Open
1524
1525 Open a file. The syntax is the same as fopen
1526 ====================
1527 */
1528 qfile_t* FS_Open (const char* filepath, const char* mode, qboolean quiet, qboolean nonblocking)
1529 {
1530         if (FS_CheckNastyPath(filepath))
1531         {
1532                 Con_Printf("FS_Open(\"%s\", \"%s\", %s): nasty filename rejected\n", filepath, mode, quiet ? "true" : "false");
1533                 return NULL;
1534         }
1535
1536         // If the file is opened in "write", "append", or "read/write" mode
1537         if (mode[0] == 'w' || mode[0] == 'a' || strchr (mode, '+'))
1538         {
1539                 char real_path [MAX_OSPATH];
1540
1541                 // Open the file on disk directly
1542                 dpsnprintf (real_path, sizeof (real_path), "%s/%s", fs_gamedir, filepath);
1543
1544                 // Create directories up to the file
1545                 FS_CreatePath (real_path);
1546
1547                 return FS_SysOpen (real_path, mode, nonblocking);
1548         }
1549         // Else, we look at the various search paths and open the file in read-only mode
1550         else
1551                 return FS_OpenReadFile (filepath, quiet, nonblocking);
1552 }
1553
1554
1555 /*
1556 ====================
1557 FS_Close
1558
1559 Close a file
1560 ====================
1561 */
1562 int FS_Close (qfile_t* file)
1563 {
1564         if (close (file->handle))
1565                 return EOF;
1566
1567         if (file->ztk)
1568         {
1569                 qz_inflateEnd (&file->ztk->zstream);
1570                 Mem_Free (file->ztk);
1571         }
1572
1573         Mem_Free (file);
1574         return 0;
1575 }
1576
1577
1578 /*
1579 ====================
1580 FS_Write
1581
1582 Write "datasize" bytes into a file
1583 ====================
1584 */
1585 fs_offset_t FS_Write (qfile_t* file, const void* data, size_t datasize)
1586 {
1587         fs_offset_t result;
1588
1589         // If necessary, seek to the exact file position we're supposed to be
1590         if (file->buff_ind != file->buff_len)
1591                 lseek (file->handle, file->buff_ind - file->buff_len, SEEK_CUR);
1592
1593         // Purge cached data
1594         FS_Purge (file);
1595
1596         // Write the buffer and update the position
1597         result = write (file->handle, data, (fs_offset_t)datasize);
1598         file->position = lseek (file->handle, 0, SEEK_CUR);
1599         if (file->real_length < file->position)
1600                 file->real_length = file->position;
1601
1602         if (result < 0)
1603                 return 0;
1604
1605         return result;
1606 }
1607
1608
1609 /*
1610 ====================
1611 FS_Read
1612
1613 Read up to "buffersize" bytes from a file
1614 ====================
1615 */
1616 fs_offset_t FS_Read (qfile_t* file, void* buffer, size_t buffersize)
1617 {
1618         fs_offset_t count, done;
1619
1620         if (buffersize == 0)
1621                 return 0;
1622
1623         // Get rid of the ungetc character
1624         if (file->ungetc != EOF)
1625         {
1626                 ((char*)buffer)[0] = file->ungetc;
1627                 buffersize--;
1628                 file->ungetc = EOF;
1629                 done = 1;
1630         }
1631         else
1632                 done = 0;
1633
1634         // First, we copy as many bytes as we can from "buff"
1635         if (file->buff_ind < file->buff_len)
1636         {
1637                 count = file->buff_len - file->buff_ind;
1638
1639                 done += ((fs_offset_t)buffersize > count) ? count : (fs_offset_t)buffersize;
1640                 memcpy (buffer, &file->buff[file->buff_ind], done);
1641                 file->buff_ind += done;
1642
1643                 buffersize -= done;
1644                 if (buffersize == 0)
1645                         return done;
1646         }
1647
1648         // NOTE: at this point, the read buffer is always empty
1649
1650         // If the file isn't compressed
1651         if (! (file->flags & QFILE_FLAG_DEFLATED))
1652         {
1653                 fs_offset_t nb;
1654
1655                 // We must take care to not read after the end of the file
1656                 count = file->real_length - file->position;
1657
1658                 // If we have a lot of data to get, put them directly into "buffer"
1659                 if (buffersize > sizeof (file->buff) / 2)
1660                 {
1661                         if (count > (fs_offset_t)buffersize)
1662                                 count = (fs_offset_t)buffersize;
1663                         lseek (file->handle, file->offset + file->position, SEEK_SET);
1664                         nb = read (file->handle, &((unsigned char*)buffer)[done], count);
1665                         if (nb > 0)
1666                         {
1667                                 done += nb;
1668                                 file->position += nb;
1669
1670                                 // Purge cached data
1671                                 FS_Purge (file);
1672                         }
1673                 }
1674                 else
1675                 {
1676                         if (count > (fs_offset_t)sizeof (file->buff))
1677                                 count = (fs_offset_t)sizeof (file->buff);
1678                         lseek (file->handle, file->offset + file->position, SEEK_SET);
1679                         nb = read (file->handle, file->buff, count);
1680                         if (nb > 0)
1681                         {
1682                                 file->buff_len = nb;
1683                                 file->position += nb;
1684
1685                                 // Copy the requested data in "buffer" (as much as we can)
1686                                 count = (fs_offset_t)buffersize > file->buff_len ? file->buff_len : (fs_offset_t)buffersize;
1687                                 memcpy (&((unsigned char*)buffer)[done], file->buff, count);
1688                                 file->buff_ind = count;
1689                                 done += count;
1690                         }
1691                 }
1692
1693                 return done;
1694         }
1695
1696         // If the file is compressed, it's more complicated...
1697         // We cycle through a few operations until we have read enough data
1698         while (buffersize > 0)
1699         {
1700                 ztoolkit_t *ztk = file->ztk;
1701                 int error;
1702
1703                 // NOTE: at this point, the read buffer is always empty
1704
1705                 // If "input" is also empty, we need to refill it
1706                 if (ztk->in_ind == ztk->in_len)
1707                 {
1708                         // If we are at the end of the file
1709                         if (file->position == file->real_length)
1710                                 return done;
1711
1712                         count = (fs_offset_t)(ztk->comp_length - ztk->in_position);
1713                         if (count > (fs_offset_t)sizeof (ztk->input))
1714                                 count = (fs_offset_t)sizeof (ztk->input);
1715                         lseek (file->handle, file->offset + (fs_offset_t)ztk->in_position, SEEK_SET);
1716                         if (read (file->handle, ztk->input, count) != count)
1717                         {
1718                                 Con_Printf ("FS_Read: unexpected end of file\n");
1719                                 break;
1720                         }
1721
1722                         ztk->in_ind = 0;
1723                         ztk->in_len = count;
1724                         ztk->in_position += count;
1725                 }
1726
1727                 ztk->zstream.next_in = &ztk->input[ztk->in_ind];
1728                 ztk->zstream.avail_in = (unsigned int)(ztk->in_len - ztk->in_ind);
1729
1730                 // Now that we are sure we have compressed data available, we need to determine
1731                 // if it's better to inflate it in "file->buff" or directly in "buffer"
1732
1733                 // Inflate the data in "file->buff"
1734                 if (buffersize < sizeof (file->buff) / 2)
1735                 {
1736                         ztk->zstream.next_out = file->buff;
1737                         ztk->zstream.avail_out = sizeof (file->buff);
1738                         error = qz_inflate (&ztk->zstream, Z_SYNC_FLUSH);
1739                         if (error != Z_OK && error != Z_STREAM_END)
1740                         {
1741                                 Con_Printf ("FS_Read: Can't inflate file\n");
1742                                 break;
1743                         }
1744                         ztk->in_ind = ztk->in_len - ztk->zstream.avail_in;
1745
1746                         file->buff_len = (fs_offset_t)sizeof (file->buff) - ztk->zstream.avail_out;
1747                         file->position += file->buff_len;
1748
1749                         // Copy the requested data in "buffer" (as much as we can)
1750                         count = (fs_offset_t)buffersize > file->buff_len ? file->buff_len : (fs_offset_t)buffersize;
1751                         memcpy (&((unsigned char*)buffer)[done], file->buff, count);
1752                         file->buff_ind = count;
1753                 }
1754
1755                 // Else, we inflate directly in "buffer"
1756                 else
1757                 {
1758                         ztk->zstream.next_out = &((unsigned char*)buffer)[done];
1759                         ztk->zstream.avail_out = (unsigned int)buffersize;
1760                         error = qz_inflate (&ztk->zstream, Z_SYNC_FLUSH);
1761                         if (error != Z_OK && error != Z_STREAM_END)
1762                         {
1763                                 Con_Printf ("FS_Read: Can't inflate file\n");
1764                                 break;
1765                         }
1766                         ztk->in_ind = ztk->in_len - ztk->zstream.avail_in;
1767
1768                         // How much data did it inflate?
1769                         count = (fs_offset_t)(buffersize - ztk->zstream.avail_out);
1770                         file->position += count;
1771
1772                         // Purge cached data
1773                         FS_Purge (file);
1774                 }
1775
1776                 done += count;
1777                 buffersize -= count;
1778         }
1779
1780         return done;
1781 }
1782
1783
1784 /*
1785 ====================
1786 FS_Print
1787
1788 Print a string into a file
1789 ====================
1790 */
1791 int FS_Print (qfile_t* file, const char *msg)
1792 {
1793         return (int)FS_Write (file, msg, strlen (msg));
1794 }
1795
1796 /*
1797 ====================
1798 FS_Printf
1799
1800 Print a string into a file
1801 ====================
1802 */
1803 int FS_Printf(qfile_t* file, const char* format, ...)
1804 {
1805         int result;
1806         va_list args;
1807
1808         va_start (args, format);
1809         result = FS_VPrintf (file, format, args);
1810         va_end (args);
1811
1812         return result;
1813 }
1814
1815
1816 /*
1817 ====================
1818 FS_VPrintf
1819
1820 Print a string into a file
1821 ====================
1822 */
1823 int FS_VPrintf (qfile_t* file, const char* format, va_list ap)
1824 {
1825         int len;
1826         fs_offset_t buff_size = MAX_INPUTLINE;
1827         char *tempbuff;
1828
1829         for (;;)
1830         {
1831                 tempbuff = (char *)Mem_Alloc (tempmempool, buff_size);
1832                 len = dpvsnprintf (tempbuff, buff_size, format, ap);
1833                 if (len >= 0 && len < buff_size)
1834                         break;
1835                 Mem_Free (tempbuff);
1836                 buff_size *= 2;
1837         }
1838
1839         len = write (file->handle, tempbuff, len);
1840         Mem_Free (tempbuff);
1841
1842         return len;
1843 }
1844
1845
1846 /*
1847 ====================
1848 FS_Getc
1849
1850 Get the next character of a file
1851 ====================
1852 */
1853 int FS_Getc (qfile_t* file)
1854 {
1855         char c;
1856
1857         if (FS_Read (file, &c, 1) != 1)
1858                 return EOF;
1859
1860         return c;
1861 }
1862
1863
1864 /*
1865 ====================
1866 FS_UnGetc
1867
1868 Put a character back into the read buffer (only supports one character!)
1869 ====================
1870 */
1871 int FS_UnGetc (qfile_t* file, unsigned char c)
1872 {
1873         // If there's already a character waiting to be read
1874         if (file->ungetc != EOF)
1875                 return EOF;
1876
1877         file->ungetc = c;
1878         return c;
1879 }
1880
1881
1882 /*
1883 ====================
1884 FS_Seek
1885
1886 Move the position index in a file
1887 ====================
1888 */
1889 int FS_Seek (qfile_t* file, fs_offset_t offset, int whence)
1890 {
1891         ztoolkit_t *ztk;
1892         unsigned char* buffer;
1893         fs_offset_t buffersize;
1894
1895         // Compute the file offset
1896         switch (whence)
1897         {
1898                 case SEEK_CUR:
1899                         offset += file->position - file->buff_len + file->buff_ind;
1900                         break;
1901
1902                 case SEEK_SET:
1903                         break;
1904
1905                 case SEEK_END:
1906                         offset += file->real_length;
1907                         break;
1908
1909                 default:
1910                         return -1;
1911         }
1912         if (offset < 0 || offset > (long) file->real_length)
1913                 return -1;
1914
1915         // If we have the data in our read buffer, we don't need to actually seek
1916         if (file->position - file->buff_len <= offset && offset <= file->position)
1917         {
1918                 file->buff_ind = offset + file->buff_len - file->position;
1919                 return 0;
1920         }
1921
1922         // Purge cached data
1923         FS_Purge (file);
1924
1925         // Unpacked or uncompressed files can seek directly
1926         if (! (file->flags & QFILE_FLAG_DEFLATED))
1927         {
1928                 if (lseek (file->handle, file->offset + offset, SEEK_SET) == -1)
1929                         return -1;
1930                 file->position = offset;
1931                 return 0;
1932         }
1933
1934         // Seeking in compressed files is more a hack than anything else,
1935         // but we need to support it, so here we go.
1936         ztk = file->ztk;
1937
1938         // If we have to go back in the file, we need to restart from the beginning
1939         if (offset <= file->position)
1940         {
1941                 ztk->in_ind = 0;
1942                 ztk->in_len = 0;
1943                 ztk->in_position = 0;
1944                 file->position = 0;
1945                 lseek (file->handle, file->offset, SEEK_SET);
1946
1947                 // Reset the Zlib stream
1948                 ztk->zstream.next_in = ztk->input;
1949                 ztk->zstream.avail_in = 0;
1950                 qz_inflateReset (&ztk->zstream);
1951         }
1952
1953         // We need a big buffer to force inflating into it directly
1954         buffersize = 2 * sizeof (file->buff);
1955         buffer = (unsigned char *)Mem_Alloc (tempmempool, buffersize);
1956
1957         // Skip all data until we reach the requested offset
1958         while (offset > file->position)
1959         {
1960                 fs_offset_t diff = offset - file->position;
1961                 fs_offset_t count, len;
1962
1963                 count = (diff > buffersize) ? buffersize : diff;
1964                 len = FS_Read (file, buffer, count);
1965                 if (len != count)
1966                 {
1967                         Mem_Free (buffer);
1968                         return -1;
1969                 }
1970         }
1971
1972         Mem_Free (buffer);
1973         return 0;
1974 }
1975
1976
1977 /*
1978 ====================
1979 FS_Tell
1980
1981 Give the current position in a file
1982 ====================
1983 */
1984 fs_offset_t FS_Tell (qfile_t* file)
1985 {
1986         return file->position - file->buff_len + file->buff_ind;
1987 }
1988
1989
1990 /*
1991 ====================
1992 FS_Purge
1993
1994 Erases any buffered input or output data
1995 ====================
1996 */
1997 void FS_Purge (qfile_t* file)
1998 {
1999         file->buff_len = 0;
2000         file->buff_ind = 0;
2001         file->ungetc = EOF;
2002 }
2003
2004
2005 /*
2006 ============
2007 FS_LoadFile
2008
2009 Filename are relative to the quake directory.
2010 Always appends a 0 byte.
2011 ============
2012 */
2013 unsigned char *FS_LoadFile (const char *path, mempool_t *pool, qboolean quiet, fs_offset_t *filesizepointer)
2014 {
2015         qfile_t *file;
2016         unsigned char *buf = NULL;
2017         fs_offset_t filesize = 0;
2018
2019         file = FS_Open (path, "rb", quiet, false);
2020         if (file)
2021         {
2022                 filesize = file->real_length;
2023                 buf = (unsigned char *)Mem_Alloc (pool, filesize + 1);
2024                 buf[filesize] = '\0';
2025                 FS_Read (file, buf, filesize);
2026                 FS_Close (file);
2027         }
2028
2029         if (filesizepointer)
2030                 *filesizepointer = filesize;
2031         return buf;
2032 }
2033
2034
2035 /*
2036 ============
2037 FS_WriteFile
2038
2039 The filename will be prefixed by the current game directory
2040 ============
2041 */
2042 qboolean FS_WriteFile (const char *filename, void *data, fs_offset_t len)
2043 {
2044         qfile_t *file;
2045
2046         file = FS_Open (filename, "wb", false, false);
2047         if (!file)
2048         {
2049                 Con_Printf("FS_WriteFile: failed on %s\n", filename);
2050                 return false;
2051         }
2052
2053         Con_DPrintf("FS_WriteFile: %s\n", filename);
2054         FS_Write (file, data, len);
2055         FS_Close (file);
2056         return true;
2057 }
2058
2059
2060 /*
2061 =============================================================================
2062
2063 OTHERS PUBLIC FUNCTIONS
2064
2065 =============================================================================
2066 */
2067
2068 /*
2069 ============
2070 FS_StripExtension
2071 ============
2072 */
2073 void FS_StripExtension (const char *in, char *out, size_t size_out)
2074 {
2075         char *last = NULL;
2076         char currentchar;
2077
2078         if (size_out == 0)
2079                 return;
2080
2081         while ((currentchar = *in) && size_out > 1)
2082         {
2083                 if (currentchar == '.')
2084                         last = out;
2085                 else if (currentchar == '/' || currentchar == '\\' || currentchar == ':')
2086                         last = NULL;
2087                 *out++ = currentchar;
2088                 in++;
2089                 size_out--;
2090         }
2091         if (last)
2092                 *last = 0;
2093         else
2094                 *out = 0;
2095 }
2096
2097
2098 /*
2099 ==================
2100 FS_DefaultExtension
2101 ==================
2102 */
2103 void FS_DefaultExtension (char *path, const char *extension, size_t size_path)
2104 {
2105         const char *src;
2106
2107         // if path doesn't have a .EXT, append extension
2108         // (extension should include the .)
2109         src = path + strlen(path) - 1;
2110
2111         while (*src != '/' && src != path)
2112         {
2113                 if (*src == '.')
2114                         return;                 // it has an extension
2115                 src--;
2116         }
2117
2118         strlcat (path, extension, size_path);
2119 }
2120
2121
2122 /*
2123 ==================
2124 FS_FileExists
2125
2126 Look for a file in the packages and in the filesystem
2127 ==================
2128 */
2129 qboolean FS_FileExists (const char *filename)
2130 {
2131         return (FS_FindFile (filename, NULL, true) != NULL);
2132 }
2133
2134
2135 /*
2136 ==================
2137 FS_SysFileExists
2138
2139 Look for a file in the filesystem only
2140 ==================
2141 */
2142 qboolean FS_SysFileExists (const char *path)
2143 {
2144 #if WIN32
2145         int desc;
2146
2147         // TODO: use another function instead, to avoid opening the file
2148         desc = open (path, O_RDONLY | O_BINARY);
2149         if (desc < 0)
2150                 return false;
2151
2152         close (desc);
2153         return true;
2154 #else
2155         struct stat buf;
2156
2157         if (stat (path,&buf) == -1)
2158                 return false;
2159
2160         return true;
2161 #endif
2162 }
2163
2164 void FS_mkdir (const char *path)
2165 {
2166 #if WIN32
2167         _mkdir (path);
2168 #else
2169         mkdir (path, 0777);
2170 #endif
2171 }
2172
2173 /*
2174 ===========
2175 FS_Search
2176
2177 Allocate and fill a search structure with information on matching filenames.
2178 ===========
2179 */
2180 fssearch_t *FS_Search(const char *pattern, int caseinsensitive, int quiet)
2181 {
2182         fssearch_t *search;
2183         searchpath_t *searchpath;
2184         pack_t *pak;
2185         int i, basepathlength, numfiles, numchars;
2186         stringlist_t *dir, *dirfile, *liststart, *listcurrent, *listtemp;
2187         const char *slash, *backslash, *colon, *separator;
2188         char *basepath;
2189         char netpath[MAX_OSPATH];
2190         char temp[MAX_OSPATH];
2191
2192         for (i = 0;pattern[i] == '.' || pattern[i] == ':' || pattern[i] == '/' || pattern[i] == '\\';i++)
2193                 ;
2194
2195         if (i > 0)
2196         {
2197                 Con_Printf("Don't use punctuation at the beginning of a search pattern!\n");
2198                 return NULL;
2199         }
2200
2201         search = NULL;
2202         liststart = NULL;
2203         listcurrent = NULL;
2204         listtemp = NULL;
2205         slash = strrchr(pattern, '/');
2206         backslash = strrchr(pattern, '\\');
2207         colon = strrchr(pattern, ':');
2208         separator = max(slash, backslash);
2209         separator = max(separator, colon);
2210         basepathlength = separator ? (separator + 1 - pattern) : 0;
2211         basepath = (char *)Mem_Alloc (tempmempool, basepathlength + 1);
2212         if (basepathlength)
2213                 memcpy(basepath, pattern, basepathlength);
2214         basepath[basepathlength] = 0;
2215
2216         // search through the path, one element at a time
2217         for (searchpath = fs_searchpaths;searchpath;searchpath = searchpath->next)
2218         {
2219                 // is the element a pak file?
2220                 if (searchpath->pack)
2221                 {
2222                         // look through all the pak file elements
2223                         pak = searchpath->pack;
2224                         for (i = 0;i < pak->numfiles;i++)
2225                         {
2226                                 strlcpy(temp, pak->files[i].name, sizeof(temp));
2227                                 while (temp[0])
2228                                 {
2229                                         if (matchpattern(temp, (char *)pattern, true))
2230                                         {
2231                                                 for (listtemp = liststart;listtemp;listtemp = listtemp->next)
2232                                                         if (!strcmp(listtemp->text, temp))
2233                                                                 break;
2234                                                 if (listtemp == NULL)
2235                                                 {
2236                                                         listcurrent = stringlistappend(listcurrent, temp);
2237                                                         if (liststart == NULL)
2238                                                                 liststart = listcurrent;
2239                                                         if (!quiet)
2240                                                                 Con_DPrintf("SearchPackFile: %s : %s\n", pak->filename, temp);
2241                                                 }
2242                                         }
2243                                         // strip off one path element at a time until empty
2244                                         // this way directories are added to the listing if they match the pattern
2245                                         slash = strrchr(temp, '/');
2246                                         backslash = strrchr(temp, '\\');
2247                                         colon = strrchr(temp, ':');
2248                                         separator = temp;
2249                                         if (separator < slash)
2250                                                 separator = slash;
2251                                         if (separator < backslash)
2252                                                 separator = backslash;
2253                                         if (separator < colon)
2254                                                 separator = colon;
2255                                         *((char *)separator) = 0;
2256                                 }
2257                         }
2258                 }
2259                 else
2260                 {
2261                         // get a directory listing and look at each name
2262                         dpsnprintf(netpath, sizeof (netpath), "%s%s", searchpath->filename, basepath);
2263                         if ((dir = listdirectory(netpath)))
2264                         {
2265                                 for (dirfile = dir;dirfile;dirfile = dirfile->next)
2266                                 {
2267                                         dpsnprintf(temp, sizeof(temp), "%s%s", basepath, dirfile->text);
2268                                         if (matchpattern(temp, (char *)pattern, true))
2269                                         {
2270                                                 for (listtemp = liststart;listtemp;listtemp = listtemp->next)
2271                                                         if (!strcmp(listtemp->text, temp))
2272                                                                 break;
2273                                                 if (listtemp == NULL)
2274                                                 {
2275                                                         listcurrent = stringlistappend(listcurrent, temp);
2276                                                         if (liststart == NULL)
2277                                                                 liststart = listcurrent;
2278                                                         if (!quiet)
2279                                                                 Con_DPrintf("SearchDirFile: %s\n", temp);
2280                                                 }
2281                                         }
2282                                 }
2283                                 freedirectory(dir);
2284                         }
2285                 }
2286         }
2287
2288         if (liststart)
2289         {
2290                 liststart = stringlistsort(liststart);
2291                 numfiles = 0;
2292                 numchars = 0;
2293                 for (listtemp = liststart;listtemp;listtemp = listtemp->next)
2294                 {
2295                         numfiles++;
2296                         numchars += (int)strlen(listtemp->text) + 1;
2297                 }
2298                 search = (fssearch_t *)Z_Malloc(sizeof(fssearch_t) + numchars + numfiles * sizeof(char *));
2299                 search->filenames = (char **)((char *)search + sizeof(fssearch_t));
2300                 search->filenamesbuffer = (char *)((char *)search + sizeof(fssearch_t) + numfiles * sizeof(char *));
2301                 search->numfilenames = (int)numfiles;
2302                 numfiles = 0;
2303                 numchars = 0;
2304                 for (listtemp = liststart;listtemp;listtemp = listtemp->next)
2305                 {
2306                         size_t textlen;
2307                         search->filenames[numfiles] = search->filenamesbuffer + numchars;
2308                         textlen = strlen(listtemp->text) + 1;
2309                         memcpy(search->filenames[numfiles], listtemp->text, textlen);
2310                         numfiles++;
2311                         numchars += (int)textlen;
2312                 }
2313                 if (liststart)
2314                         stringlistfree(liststart);
2315         }
2316
2317         Mem_Free(basepath);
2318         return search;
2319 }
2320
2321 void FS_FreeSearch(fssearch_t *search)
2322 {
2323         Z_Free(search);
2324 }
2325
2326 extern int con_linewidth;
2327 int FS_ListDirectory(const char *pattern, int oneperline)
2328 {
2329         int numfiles;
2330         int numcolumns;
2331         int numlines;
2332         int columnwidth;
2333         int linebufpos;
2334         int i, j, k, l;
2335         const char *name;
2336         char linebuf[MAX_INPUTLINE];
2337         fssearch_t *search;
2338         search = FS_Search(pattern, true, true);
2339         if (!search)
2340                 return 0;
2341         numfiles = search->numfilenames;
2342         if (!oneperline)
2343         {
2344                 // FIXME: the names could be added to one column list and then
2345                 // gradually shifted into the next column if they fit, and then the
2346                 // next to make a compact variable width listing but it's a lot more
2347                 // complicated...
2348                 // find width for columns
2349                 columnwidth = 0;
2350                 for (i = 0;i < numfiles;i++)
2351                 {
2352                         l = (int)strlen(search->filenames[i]);
2353                         if (columnwidth < l)
2354                                 columnwidth = l;
2355                 }
2356                 // count the spacing character
2357                 columnwidth++;
2358                 // calculate number of columns
2359                 numcolumns = con_linewidth / columnwidth;
2360                 // don't bother with the column printing if it's only one column
2361                 if (numcolumns >= 2)
2362                 {
2363                         numlines = (numfiles + numcolumns - 1) / numcolumns;
2364                         for (i = 0;i < numlines;i++)
2365                         {
2366                                 linebufpos = 0;
2367                                 for (k = 0;k < numcolumns;k++)
2368                                 {
2369                                         l = i * numcolumns + k;
2370                                         if (l < numfiles)
2371                                         {
2372                                                 name = search->filenames[l];
2373                                                 for (j = 0;name[j] && linebufpos + 1 < (int)sizeof(linebuf);j++)
2374                                                         linebuf[linebufpos++] = name[j];
2375                                                 // space out name unless it's the last on the line
2376                                                 if (k + 1 < numcolumns && l + 1 < numfiles)
2377                                                         for (;j < columnwidth && linebufpos + 1 < (int)sizeof(linebuf);j++)
2378                                                                 linebuf[linebufpos++] = ' ';
2379                                         }
2380                                 }
2381                                 linebuf[linebufpos] = 0;
2382                                 Con_Printf("%s\n", linebuf);
2383                         }
2384                 }
2385                 else
2386                         oneperline = true;
2387         }
2388         if (oneperline)
2389                 for (i = 0;i < numfiles;i++)
2390                         Con_Printf("%s\n", search->filenames[i]);
2391         FS_FreeSearch(search);
2392         return (int)numfiles;
2393 }
2394
2395 static void FS_ListDirectoryCmd (const char* cmdname, int oneperline)
2396 {
2397         const char *pattern;
2398         if (Cmd_Argc() > 3)
2399         {
2400                 Con_Printf("usage:\n%s [path/pattern]\n", cmdname);
2401                 return;
2402         }
2403         if (Cmd_Argc() == 2)
2404                 pattern = Cmd_Argv(1);
2405         else
2406                 pattern = "*";
2407         if (!FS_ListDirectory(pattern, oneperline))
2408                 Con_Print("No files found.\n");
2409 }
2410
2411 void FS_Dir_f(void)
2412 {
2413         FS_ListDirectoryCmd("dir", true);
2414 }
2415
2416 void FS_Ls_f(void)
2417 {
2418         FS_ListDirectoryCmd("ls", false);
2419 }
2420
2421 const char *FS_WhichPack(const char *filename)
2422 {
2423         int index;
2424         searchpath_t *sp = FS_FindFile(filename, &index, true);
2425         if(sp && sp->pack)
2426                 return sp->pack->filename;
2427         else
2428                 return 0;
2429 }