]> icculus.org git repositories - divverent/darkplaces.git/blob - fs.c
don't allow runaway loops caused by Con_Print during notify/console drawing
[divverent/darkplaces.git] / fs.c
1 /*
2         DarkPlaces file system
3
4         Copyright (C) 2003-2005 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         strcpy(fs_basedir, "");
1065         strcpy(fs_gamedir, "");
1066
1067 #ifdef MACOSX
1068         // FIXME: is there a better way to find the directory outside the .app?
1069         if (strstr(com_argv[0], ".app/"))
1070         {
1071                 char *split;
1072
1073                 split = strstr(com_argv[0], ".app/");
1074                 while (split > com_argv[0] && *split != '/')
1075                         split--;
1076                 strlcpy(fs_basedir, com_argv[0], sizeof(fs_basedir));
1077                 fs_basedir[split - com_argv[0]] = 0;
1078         }
1079 #endif
1080
1081         PK3_OpenLibrary ();
1082
1083         // -basedir <path>
1084         // Overrides the system supplied base directory (under GAMENAME)
1085 // 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)
1086         i = COM_CheckParm ("-basedir");
1087         if (i && i < com_argc-1)
1088         {
1089                 strlcpy (fs_basedir, com_argv[i+1], sizeof (fs_basedir));
1090                 i = (int)strlen (fs_basedir);
1091                 if (i > 0 && (fs_basedir[i-1] == '\\' || fs_basedir[i-1] == '/'))
1092                         fs_basedir[i-1] = 0;
1093         }
1094
1095         // add a path separator to the end of the basedir if it lacks one
1096         if (fs_basedir[0] && fs_basedir[strlen(fs_basedir) - 1] != '/' && fs_basedir[strlen(fs_basedir) - 1] != '\\')
1097                 strlcat(fs_basedir, "/", sizeof(fs_basedir));
1098
1099         // -path <dir or packfile> [<dir or packfile>] ...
1100         // Fully specifies the exact search path, overriding the generated one
1101 // 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)
1102         i = COM_CheckParm ("-path");
1103         if (i)
1104         {
1105                 fs_modified = true;
1106                 while (++i < com_argc)
1107                 {
1108                         if (!com_argv[i] || com_argv[i][0] == '+' || com_argv[i][0] == '-')
1109                                 break;
1110
1111                         if(!FS_AddPack_Fullpath(com_argv[i], NULL, false))
1112                         {
1113                                 search = (searchpath_t *)Mem_Alloc(fs_mempool, sizeof(searchpath_t));
1114                                 strlcpy (search->filename, com_argv[i], sizeof (search->filename));
1115                                 search->next = fs_searchpaths;
1116                                 fs_searchpaths = search;
1117                         }
1118                 }
1119                 return;
1120         }
1121
1122         // add the game-specific paths
1123         // gamedirname1 (typically id1)
1124         FS_AddGameHierarchy (gamedirname1);
1125
1126         // add the game-specific path, if any
1127         if (gamedirname2)
1128         {
1129                 fs_modified = true;
1130                 FS_AddGameHierarchy (gamedirname2);
1131         }
1132
1133         // set the com_modname (reported in server info)
1134         strlcpy(com_modname, gamedirname1, sizeof(com_modname));
1135
1136         // -game <gamedir>
1137         // Adds basedir/gamedir as an override game
1138         // LordHavoc: now supports multiple -game directories
1139         for (i = 1;i < com_argc;i++)
1140         {
1141                 if (!com_argv[i])
1142                         continue;
1143                 if (!strcmp (com_argv[i], "-game") && i < com_argc-1)
1144                 {
1145                         i++;
1146                         fs_modified = true;
1147                         FS_AddGameHierarchy (com_argv[i]);
1148                         // update the com_modname
1149                         strlcpy (com_modname, com_argv[i], sizeof (com_modname));
1150                 }
1151         }
1152
1153         // If "-condebug" is in the command line, remove the previous log file
1154         if (COM_CheckParm ("-condebug") != 0)
1155                 unlink (va("%s/qconsole.log", fs_gamedir));
1156 }
1157
1158 void FS_Init_Commands(void)
1159 {
1160         Cvar_RegisterVariable (&scr_screenshot_name);
1161
1162         Cmd_AddCommand ("path", FS_Path_f, "print searchpath (game directories and archives)");
1163         Cmd_AddCommand ("dir", FS_Dir_f, "list files in searchpath matching an * filename pattern, one per line");
1164         Cmd_AddCommand ("ls", FS_Ls_f, "list files in searchpath matching an * filename pattern, multiple per line");
1165
1166         // set the default screenshot name to either the mod name or the
1167         // gamemode screenshot name
1168         if (fs_modified)
1169                 Cvar_SetQuick (&scr_screenshot_name, com_modname);
1170         else
1171                 Cvar_SetQuick (&scr_screenshot_name, gamescreenshotname);
1172 }
1173
1174 /*
1175 ================
1176 FS_Shutdown
1177 ================
1178 */
1179 void FS_Shutdown (void)
1180 {
1181         Mem_FreePool (&fs_mempool);
1182 }
1183
1184 /*
1185 ====================
1186 FS_SysOpen
1187
1188 Internal function used to create a qfile_t and open the relevant non-packed file on disk
1189 ====================
1190 */
1191 static qfile_t* FS_SysOpen (const char* filepath, const char* mode, qboolean nonblocking)
1192 {
1193         qfile_t* file;
1194         int mod, opt;
1195         unsigned int ind;
1196
1197         // Parse the mode string
1198         switch (mode[0])
1199         {
1200                 case 'r':
1201                         mod = O_RDONLY;
1202                         opt = 0;
1203                         break;
1204                 case 'w':
1205                         mod = O_WRONLY;
1206                         opt = O_CREAT | O_TRUNC;
1207                         break;
1208                 case 'a':
1209                         mod = O_WRONLY;
1210                         opt = O_CREAT | O_APPEND;
1211                         break;
1212                 default:
1213                         Con_Printf ("FS_SysOpen(%s, %s): invalid mode\n", filepath, mode);
1214                         return NULL;
1215         }
1216         for (ind = 1; mode[ind] != '\0'; ind++)
1217         {
1218                 switch (mode[ind])
1219                 {
1220                         case '+':
1221                                 mod = O_RDWR;
1222                                 break;
1223                         case 'b':
1224                                 opt |= O_BINARY;
1225                                 break;
1226                         default:
1227                                 Con_Printf ("FS_SysOpen(%s, %s): unknown character in mode (%c)\n",
1228                                                         filepath, mode, mode[ind]);
1229                 }
1230         }
1231
1232         if (nonblocking)
1233                 opt |= O_NONBLOCK;
1234
1235         file = (qfile_t *)Mem_Alloc (fs_mempool, sizeof (*file));
1236         memset (file, 0, sizeof (*file));
1237         file->ungetc = EOF;
1238
1239         file->handle = open (filepath, mod | opt, 0666);
1240         if (file->handle < 0)
1241         {
1242                 Mem_Free (file);
1243                 return NULL;
1244         }
1245
1246         file->real_length = lseek (file->handle, 0, SEEK_END);
1247
1248         // For files opened in append mode, we start at the end of the file
1249         if (mod & O_APPEND)
1250                 file->position = file->real_length;
1251         else
1252                 lseek (file->handle, 0, SEEK_SET);
1253
1254         return file;
1255 }
1256
1257
1258 /*
1259 ===========
1260 FS_OpenPackedFile
1261
1262 Open a packed file using its package file descriptor
1263 ===========
1264 */
1265 qfile_t *FS_OpenPackedFile (pack_t* pack, int pack_ind)
1266 {
1267         packfile_t *pfile;
1268         int dup_handle;
1269         qfile_t* file;
1270
1271         pfile = &pack->files[pack_ind];
1272
1273         // If we don't have the true offset, get it now
1274         if (! (pfile->flags & PACKFILE_FLAG_TRUEOFFS))
1275                 if (!PK3_GetTrueFileOffset (pfile, pack))
1276                         return NULL;
1277
1278         // No Zlib DLL = no compressed files
1279         if (!zlib_dll && (pfile->flags & PACKFILE_FLAG_DEFLATED))
1280         {
1281                 Con_Printf("WARNING: can't open the compressed file %s\n"
1282                                         "You need the Zlib DLL to use compressed files\n",
1283                                         pfile->name);
1284                 return NULL;
1285         }
1286
1287         // LordHavoc: lseek affects all duplicates of a handle so we do it before
1288         // the dup() call to avoid having to close the dup_handle on error here
1289         if (lseek (pack->handle, pfile->offset, SEEK_SET) == -1)
1290         {
1291                 Con_Printf ("FS_OpenPackedFile: can't lseek to %s in %s (offset: %d)\n",
1292                                         pfile->name, pack->filename, pfile->offset);
1293                 return NULL;
1294         }
1295
1296         dup_handle = dup (pack->handle);
1297         if (dup_handle < 0)
1298         {
1299                 Con_Printf ("FS_OpenPackedFile: can't dup package's handle (pack: %s)\n", pack->filename);
1300                 return NULL;
1301         }
1302
1303         file = (qfile_t *)Mem_Alloc (fs_mempool, sizeof (*file));
1304         memset (file, 0, sizeof (*file));
1305         file->handle = dup_handle;
1306         file->flags = QFILE_FLAG_PACKED;
1307         file->real_length = pfile->realsize;
1308         file->offset = pfile->offset;
1309         file->position = 0;
1310         file->ungetc = EOF;
1311
1312         if (pfile->flags & PACKFILE_FLAG_DEFLATED)
1313         {
1314                 ztoolkit_t *ztk;
1315
1316                 file->flags |= QFILE_FLAG_DEFLATED;
1317
1318                 // We need some more variables
1319                 ztk = (ztoolkit_t *)Mem_Alloc (fs_mempool, sizeof (*ztk));
1320
1321                 ztk->comp_length = pfile->packsize;
1322
1323                 // Initialize zlib stream
1324                 ztk->zstream.next_in = ztk->input;
1325                 ztk->zstream.avail_in = 0;
1326
1327                 /* From Zlib's "unzip.c":
1328                  *
1329                  * windowBits is passed < 0 to tell that there is no zlib header.
1330                  * Note that in this case inflate *requires* an extra "dummy" byte
1331                  * after the compressed stream in order to complete decompression and
1332                  * return Z_STREAM_END.
1333                  * In unzip, i don't wait absolutely Z_STREAM_END because I known the
1334                  * size of both compressed and uncompressed data
1335                  */
1336                 if (qz_inflateInit2 (&ztk->zstream, -MAX_WBITS) != Z_OK)
1337                 {
1338                         Con_Printf ("FS_OpenPackedFile: inflate init error (file: %s)\n", pfile->name);
1339                         close(dup_handle);
1340                         Mem_Free(file);
1341                         return NULL;
1342                 }
1343
1344                 ztk->zstream.next_out = file->buff;
1345                 ztk->zstream.avail_out = sizeof (file->buff);
1346
1347                 file->ztk = ztk;
1348         }
1349
1350         return file;
1351 }
1352
1353 /*
1354 ====================
1355 FS_CheckNastyPath
1356
1357 Return true if the path should be rejected due to one of the following:
1358 1: path elements that are non-portable
1359 2: path elements that would allow access to files outside the game directory,
1360    or are just not a good idea for a mod to be using.
1361 ====================
1362 */
1363 int FS_CheckNastyPath (const char *path)
1364 {
1365         // Windows: don't allow \ in filenames (windows-only), period.
1366         // (on Windows \ is a directory separator, but / is also supported)
1367         if (strstr(path, "\\"))
1368                 return 1; // non-portable
1369
1370         // Mac: don't allow Mac-only filenames - : is a directory separator
1371         // instead of /, but we rely on / working already, so there's no reason to
1372         // support a Mac-only path
1373         // Amiga and Windows: : tries to go to root of drive
1374         if (strstr(path, ":"))
1375                 return 1; // non-portable attempt to go to root of drive
1376
1377         // Amiga: // is parent directory
1378         if (strstr(path, "//"))
1379                 return 1; // non-portable attempt to go to parent directory
1380
1381         // all: don't allow going to current directory (./) or parent directory (../ or /../)
1382         if (strstr(path, "./"))
1383                 return 2; // attempt to go outside the game directory
1384
1385         // Windows and UNIXes: don't allow absolute paths
1386         if (path[0] == '/')
1387                 return 2; // attempt to go outside the game directory
1388
1389         // after all these checks we're pretty sure it's a / separated filename
1390         // and won't do much if any harm
1391         return false;
1392 }
1393
1394
1395 /*
1396 ====================
1397 FS_FindFile
1398
1399 Look for a file in the packages and in the filesystem
1400
1401 Return the searchpath where the file was found (or NULL)
1402 and the file index in the package if relevant
1403 ====================
1404 */
1405 static searchpath_t *FS_FindFile (const char *name, int* index, qboolean quiet)
1406 {
1407         searchpath_t *search;
1408         pack_t *pak;
1409
1410         // search through the path, one element at a time
1411         for (search = fs_searchpaths;search;search = search->next)
1412         {
1413                 // is the element a pak file?
1414                 if (search->pack)
1415                 {
1416                         int (*strcmp_funct) (const char* str1, const char* str2);
1417                         int left, right, middle;
1418
1419                         pak = search->pack;
1420                         strcmp_funct = pak->ignorecase ? strcasecmp : strcmp;
1421
1422                         // Look for the file (binary search)
1423                         left = 0;
1424                         right = pak->numfiles - 1;
1425                         while (left <= right)
1426                         {
1427                                 int diff;
1428
1429                                 middle = (left + right) / 2;
1430                                 diff = strcmp_funct (pak->files[middle].name, name);
1431
1432                                 // Found it
1433                                 if (!diff)
1434                                 {
1435                                         if (!quiet && developer.integer >= 10)
1436                                                 Con_Printf("FS_FindFile: %s in %s\n",
1437                                                                         pak->files[middle].name, pak->filename);
1438
1439                                         if (index != NULL)
1440                                                 *index = middle;
1441                                         return search;
1442                                 }
1443
1444                                 // If we're too far in the list
1445                                 if (diff > 0)
1446                                         right = middle - 1;
1447                                 else
1448                                         left = middle + 1;
1449                         }
1450                 }
1451                 else
1452                 {
1453                         char netpath[MAX_OSPATH];
1454                         dpsnprintf(netpath, sizeof(netpath), "%s%s", search->filename, name);
1455                         if (FS_SysFileExists (netpath))
1456                         {
1457                                 if (!quiet && developer.integer >= 10)
1458                                         Con_Printf("FS_FindFile: %s\n", netpath);
1459
1460                                 if (index != NULL)
1461                                         *index = -1;
1462                                 return search;
1463                         }
1464                 }
1465         }
1466
1467         if (!quiet && developer.integer >= 10)
1468                 Con_Printf("FS_FindFile: can't find %s\n", name);
1469
1470         if (index != NULL)
1471                 *index = -1;
1472         return NULL;
1473 }
1474
1475
1476 /*
1477 ===========
1478 FS_OpenReadFile
1479
1480 Look for a file in the search paths and open it in read-only mode
1481 ===========
1482 */
1483 qfile_t *FS_OpenReadFile (const char *filename, qboolean quiet, qboolean nonblocking)
1484 {
1485         searchpath_t *search;
1486         int pack_ind;
1487
1488         search = FS_FindFile (filename, &pack_ind, quiet);
1489
1490         // Not found?
1491         if (search == NULL)
1492                 return NULL;
1493
1494         // Found in the filesystem?
1495         if (pack_ind < 0)
1496         {
1497                 char path [MAX_OSPATH];
1498                 dpsnprintf (path, sizeof (path), "%s%s", search->filename, filename);
1499                 return FS_SysOpen (path, "rb", nonblocking);
1500         }
1501
1502         // So, we found it in a package...
1503         return FS_OpenPackedFile (search->pack, pack_ind);
1504 }
1505
1506
1507 /*
1508 =============================================================================
1509
1510 MAIN PUBLIC FUNCTIONS
1511
1512 =============================================================================
1513 */
1514
1515 /*
1516 ====================
1517 FS_Open
1518
1519 Open a file. The syntax is the same as fopen
1520 ====================
1521 */
1522 qfile_t* FS_Open (const char* filepath, const char* mode, qboolean quiet, qboolean nonblocking)
1523 {
1524         if (FS_CheckNastyPath(filepath))
1525         {
1526                 Con_Printf("FS_Open(\"%s\", \"%s\", %s): nasty filename rejected\n", filepath, mode, quiet ? "true" : "false");
1527                 return NULL;
1528         }
1529
1530         // If the file is opened in "write", "append", or "read/write" mode
1531         if (mode[0] == 'w' || mode[0] == 'a' || strchr (mode, '+'))
1532         {
1533                 char real_path [MAX_OSPATH];
1534
1535                 // Open the file on disk directly
1536                 dpsnprintf (real_path, sizeof (real_path), "%s/%s", fs_gamedir, filepath);
1537
1538                 // Create directories up to the file
1539                 FS_CreatePath (real_path);
1540
1541                 return FS_SysOpen (real_path, mode, nonblocking);
1542         }
1543         // Else, we look at the various search paths and open the file in read-only mode
1544         else
1545                 return FS_OpenReadFile (filepath, quiet, nonblocking);
1546 }
1547
1548
1549 /*
1550 ====================
1551 FS_Close
1552
1553 Close a file
1554 ====================
1555 */
1556 int FS_Close (qfile_t* file)
1557 {
1558         if (close (file->handle))
1559                 return EOF;
1560
1561         if (file->ztk)
1562         {
1563                 qz_inflateEnd (&file->ztk->zstream);
1564                 Mem_Free (file->ztk);
1565         }
1566
1567         Mem_Free (file);
1568         return 0;
1569 }
1570
1571
1572 /*
1573 ====================
1574 FS_Write
1575
1576 Write "datasize" bytes into a file
1577 ====================
1578 */
1579 fs_offset_t FS_Write (qfile_t* file, const void* data, size_t datasize)
1580 {
1581         fs_offset_t result;
1582
1583         // If necessary, seek to the exact file position we're supposed to be
1584         if (file->buff_ind != file->buff_len)
1585                 lseek (file->handle, file->buff_ind - file->buff_len, SEEK_CUR);
1586
1587         // Purge cached data
1588         FS_Purge (file);
1589
1590         // Write the buffer and update the position
1591         result = write (file->handle, data, (fs_offset_t)datasize);
1592         file->position = lseek (file->handle, 0, SEEK_CUR);
1593         if (file->real_length < file->position)
1594                 file->real_length = file->position;
1595
1596         if (result < 0)
1597                 return 0;
1598
1599         return result;
1600 }
1601
1602
1603 /*
1604 ====================
1605 FS_Read
1606
1607 Read up to "buffersize" bytes from a file
1608 ====================
1609 */
1610 fs_offset_t FS_Read (qfile_t* file, void* buffer, size_t buffersize)
1611 {
1612         fs_offset_t count, done;
1613
1614         if (buffersize == 0)
1615                 return 0;
1616
1617         // Get rid of the ungetc character
1618         if (file->ungetc != EOF)
1619         {
1620                 ((char*)buffer)[0] = file->ungetc;
1621                 buffersize--;
1622                 file->ungetc = EOF;
1623                 done = 1;
1624         }
1625         else
1626                 done = 0;
1627
1628         // First, we copy as many bytes as we can from "buff"
1629         if (file->buff_ind < file->buff_len)
1630         {
1631                 count = file->buff_len - file->buff_ind;
1632
1633                 done += ((fs_offset_t)buffersize > count) ? count : (fs_offset_t)buffersize;
1634                 memcpy (buffer, &file->buff[file->buff_ind], done);
1635                 file->buff_ind += done;
1636
1637                 buffersize -= done;
1638                 if (buffersize == 0)
1639                         return done;
1640         }
1641
1642         // NOTE: at this point, the read buffer is always empty
1643
1644         // If the file isn't compressed
1645         if (! (file->flags & QFILE_FLAG_DEFLATED))
1646         {
1647                 fs_offset_t nb;
1648
1649                 // We must take care to not read after the end of the file
1650                 count = file->real_length - file->position;
1651
1652                 // If we have a lot of data to get, put them directly into "buffer"
1653                 if (buffersize > sizeof (file->buff) / 2)
1654                 {
1655                         if (count > (fs_offset_t)buffersize)
1656                                 count = (fs_offset_t)buffersize;
1657                         lseek (file->handle, file->offset + file->position, SEEK_SET);
1658                         nb = read (file->handle, &((unsigned char*)buffer)[done], count);
1659                         if (nb > 0)
1660                         {
1661                                 done += nb;
1662                                 file->position += nb;
1663
1664                                 // Purge cached data
1665                                 FS_Purge (file);
1666                         }
1667                 }
1668                 else
1669                 {
1670                         if (count > (fs_offset_t)sizeof (file->buff))
1671                                 count = (fs_offset_t)sizeof (file->buff);
1672                         lseek (file->handle, file->offset + file->position, SEEK_SET);
1673                         nb = read (file->handle, file->buff, count);
1674                         if (nb > 0)
1675                         {
1676                                 file->buff_len = nb;
1677                                 file->position += nb;
1678
1679                                 // Copy the requested data in "buffer" (as much as we can)
1680                                 count = (fs_offset_t)buffersize > file->buff_len ? file->buff_len : (fs_offset_t)buffersize;
1681                                 memcpy (&((unsigned char*)buffer)[done], file->buff, count);
1682                                 file->buff_ind = count;
1683                                 done += count;
1684                         }
1685                 }
1686
1687                 return done;
1688         }
1689
1690         // If the file is compressed, it's more complicated...
1691         // We cycle through a few operations until we have read enough data
1692         while (buffersize > 0)
1693         {
1694                 ztoolkit_t *ztk = file->ztk;
1695                 int error;
1696
1697                 // NOTE: at this point, the read buffer is always empty
1698
1699                 // If "input" is also empty, we need to refill it
1700                 if (ztk->in_ind == ztk->in_len)
1701                 {
1702                         // If we are at the end of the file
1703                         if (file->position == file->real_length)
1704                                 return done;
1705
1706                         count = (fs_offset_t)(ztk->comp_length - ztk->in_position);
1707                         if (count > (fs_offset_t)sizeof (ztk->input))
1708                                 count = (fs_offset_t)sizeof (ztk->input);
1709                         lseek (file->handle, file->offset + (fs_offset_t)ztk->in_position, SEEK_SET);
1710                         if (read (file->handle, ztk->input, count) != count)
1711                         {
1712                                 Con_Printf ("FS_Read: unexpected end of file\n");
1713                                 break;
1714                         }
1715
1716                         ztk->in_ind = 0;
1717                         ztk->in_len = count;
1718                         ztk->in_position += count;
1719                 }
1720
1721                 ztk->zstream.next_in = &ztk->input[ztk->in_ind];
1722                 ztk->zstream.avail_in = (unsigned int)(ztk->in_len - ztk->in_ind);
1723
1724                 // Now that we are sure we have compressed data available, we need to determine
1725                 // if it's better to inflate it in "file->buff" or directly in "buffer"
1726
1727                 // Inflate the data in "file->buff"
1728                 if (buffersize < sizeof (file->buff) / 2)
1729                 {
1730                         ztk->zstream.next_out = file->buff;
1731                         ztk->zstream.avail_out = sizeof (file->buff);
1732                         error = qz_inflate (&ztk->zstream, Z_SYNC_FLUSH);
1733                         if (error != Z_OK && error != Z_STREAM_END)
1734                         {
1735                                 Con_Printf ("FS_Read: Can't inflate file\n");
1736                                 break;
1737                         }
1738                         ztk->in_ind = ztk->in_len - ztk->zstream.avail_in;
1739
1740                         file->buff_len = (fs_offset_t)sizeof (file->buff) - ztk->zstream.avail_out;
1741                         file->position += file->buff_len;
1742
1743                         // Copy the requested data in "buffer" (as much as we can)
1744                         count = (fs_offset_t)buffersize > file->buff_len ? file->buff_len : (fs_offset_t)buffersize;
1745                         memcpy (&((unsigned char*)buffer)[done], file->buff, count);
1746                         file->buff_ind = count;
1747                 }
1748
1749                 // Else, we inflate directly in "buffer"
1750                 else
1751                 {
1752                         ztk->zstream.next_out = &((unsigned char*)buffer)[done];
1753                         ztk->zstream.avail_out = (unsigned int)buffersize;
1754                         error = qz_inflate (&ztk->zstream, Z_SYNC_FLUSH);
1755                         if (error != Z_OK && error != Z_STREAM_END)
1756                         {
1757                                 Con_Printf ("FS_Read: Can't inflate file\n");
1758                                 break;
1759                         }
1760                         ztk->in_ind = ztk->in_len - ztk->zstream.avail_in;
1761
1762                         // How much data did it inflate?
1763                         count = (fs_offset_t)(buffersize - ztk->zstream.avail_out);
1764                         file->position += count;
1765
1766                         // Purge cached data
1767                         FS_Purge (file);
1768                 }
1769
1770                 done += count;
1771                 buffersize -= count;
1772         }
1773
1774         return done;
1775 }
1776
1777
1778 /*
1779 ====================
1780 FS_Print
1781
1782 Print a string into a file
1783 ====================
1784 */
1785 int FS_Print (qfile_t* file, const char *msg)
1786 {
1787         return (int)FS_Write (file, msg, strlen (msg));
1788 }
1789
1790 /*
1791 ====================
1792 FS_Printf
1793
1794 Print a string into a file
1795 ====================
1796 */
1797 int FS_Printf(qfile_t* file, const char* format, ...)
1798 {
1799         int result;
1800         va_list args;
1801
1802         va_start (args, format);
1803         result = FS_VPrintf (file, format, args);
1804         va_end (args);
1805
1806         return result;
1807 }
1808
1809
1810 /*
1811 ====================
1812 FS_VPrintf
1813
1814 Print a string into a file
1815 ====================
1816 */
1817 int FS_VPrintf (qfile_t* file, const char* format, va_list ap)
1818 {
1819         int len;
1820         fs_offset_t buff_size = MAX_INPUTLINE;
1821         char *tempbuff;
1822
1823         for (;;)
1824         {
1825                 tempbuff = (char *)Mem_Alloc (tempmempool, buff_size);
1826                 len = dpvsnprintf (tempbuff, buff_size, format, ap);
1827                 if (len >= 0 && len < buff_size)
1828                         break;
1829                 Mem_Free (tempbuff);
1830                 buff_size *= 2;
1831         }
1832
1833         len = write (file->handle, tempbuff, len);
1834         Mem_Free (tempbuff);
1835
1836         return len;
1837 }
1838
1839
1840 /*
1841 ====================
1842 FS_Getc
1843
1844 Get the next character of a file
1845 ====================
1846 */
1847 int FS_Getc (qfile_t* file)
1848 {
1849         char c;
1850
1851         if (FS_Read (file, &c, 1) != 1)
1852                 return EOF;
1853
1854         return c;
1855 }
1856
1857
1858 /*
1859 ====================
1860 FS_UnGetc
1861
1862 Put a character back into the read buffer (only supports one character!)
1863 ====================
1864 */
1865 int FS_UnGetc (qfile_t* file, unsigned char c)
1866 {
1867         // If there's already a character waiting to be read
1868         if (file->ungetc != EOF)
1869                 return EOF;
1870
1871         file->ungetc = c;
1872         return c;
1873 }
1874
1875
1876 /*
1877 ====================
1878 FS_Seek
1879
1880 Move the position index in a file
1881 ====================
1882 */
1883 int FS_Seek (qfile_t* file, fs_offset_t offset, int whence)
1884 {
1885         ztoolkit_t *ztk;
1886         unsigned char* buffer;
1887         fs_offset_t buffersize;
1888
1889         // Compute the file offset
1890         switch (whence)
1891         {
1892                 case SEEK_CUR:
1893                         offset += file->position - file->buff_len + file->buff_ind;
1894                         break;
1895
1896                 case SEEK_SET:
1897                         break;
1898
1899                 case SEEK_END:
1900                         offset += file->real_length;
1901                         break;
1902
1903                 default:
1904                         return -1;
1905         }
1906         if (offset < 0 || offset > (long) file->real_length)
1907                 return -1;
1908
1909         // If we have the data in our read buffer, we don't need to actually seek
1910         if (file->position - file->buff_len <= offset && offset <= file->position)
1911         {
1912                 file->buff_ind = offset + file->buff_len - file->position;
1913                 return 0;
1914         }
1915
1916         // Purge cached data
1917         FS_Purge (file);
1918
1919         // Unpacked or uncompressed files can seek directly
1920         if (! (file->flags & QFILE_FLAG_DEFLATED))
1921         {
1922                 if (lseek (file->handle, file->offset + offset, SEEK_SET) == -1)
1923                         return -1;
1924                 file->position = offset;
1925                 return 0;
1926         }
1927
1928         // Seeking in compressed files is more a hack than anything else,
1929         // but we need to support it, so here we go.
1930         ztk = file->ztk;
1931
1932         // If we have to go back in the file, we need to restart from the beginning
1933         if (offset <= file->position)
1934         {
1935                 ztk->in_ind = 0;
1936                 ztk->in_len = 0;
1937                 ztk->in_position = 0;
1938                 file->position = 0;
1939                 lseek (file->handle, file->offset, SEEK_SET);
1940
1941                 // Reset the Zlib stream
1942                 ztk->zstream.next_in = ztk->input;
1943                 ztk->zstream.avail_in = 0;
1944                 qz_inflateReset (&ztk->zstream);
1945         }
1946
1947         // We need a big buffer to force inflating into it directly
1948         buffersize = 2 * sizeof (file->buff);
1949         buffer = (unsigned char *)Mem_Alloc (tempmempool, buffersize);
1950
1951         // Skip all data until we reach the requested offset
1952         while (offset > file->position)
1953         {
1954                 fs_offset_t diff = offset - file->position;
1955                 fs_offset_t count, len;
1956
1957                 count = (diff > buffersize) ? buffersize : diff;
1958                 len = FS_Read (file, buffer, count);
1959                 if (len != count)
1960                 {
1961                         Mem_Free (buffer);
1962                         return -1;
1963                 }
1964         }
1965
1966         Mem_Free (buffer);
1967         return 0;
1968 }
1969
1970
1971 /*
1972 ====================
1973 FS_Tell
1974
1975 Give the current position in a file
1976 ====================
1977 */
1978 fs_offset_t FS_Tell (qfile_t* file)
1979 {
1980         return file->position - file->buff_len + file->buff_ind;
1981 }
1982
1983
1984 /*
1985 ====================
1986 FS_Purge
1987
1988 Erases any buffered input or output data
1989 ====================
1990 */
1991 void FS_Purge (qfile_t* file)
1992 {
1993         file->buff_len = 0;
1994         file->buff_ind = 0;
1995         file->ungetc = EOF;
1996 }
1997
1998
1999 /*
2000 ============
2001 FS_LoadFile
2002
2003 Filename are relative to the quake directory.
2004 Always appends a 0 byte.
2005 ============
2006 */
2007 unsigned char *FS_LoadFile (const char *path, mempool_t *pool, qboolean quiet, fs_offset_t *filesizepointer)
2008 {
2009         qfile_t *file;
2010         unsigned char *buf = NULL;
2011         fs_offset_t filesize = 0;
2012
2013         file = FS_Open (path, "rb", quiet, false);
2014         if (file)
2015         {
2016                 filesize = file->real_length;
2017                 buf = (unsigned char *)Mem_Alloc (pool, filesize + 1);
2018                 buf[filesize] = '\0';
2019                 FS_Read (file, buf, filesize);
2020                 FS_Close (file);
2021         }
2022
2023         if (filesizepointer)
2024                 *filesizepointer = filesize;
2025         return buf;
2026 }
2027
2028
2029 /*
2030 ============
2031 FS_WriteFile
2032
2033 The filename will be prefixed by the current game directory
2034 ============
2035 */
2036 qboolean FS_WriteFile (const char *filename, void *data, fs_offset_t len)
2037 {
2038         qfile_t *file;
2039
2040         file = FS_Open (filename, "wb", false, false);
2041         if (!file)
2042         {
2043                 Con_Printf("FS_WriteFile: failed on %s\n", filename);
2044                 return false;
2045         }
2046
2047         Con_DPrintf("FS_WriteFile: %s\n", filename);
2048         FS_Write (file, data, len);
2049         FS_Close (file);
2050         return true;
2051 }
2052
2053
2054 /*
2055 =============================================================================
2056
2057 OTHERS PUBLIC FUNCTIONS
2058
2059 =============================================================================
2060 */
2061
2062 /*
2063 ============
2064 FS_StripExtension
2065 ============
2066 */
2067 void FS_StripExtension (const char *in, char *out, size_t size_out)
2068 {
2069         char *last = NULL;
2070
2071         if (size_out == 0)
2072                 return;
2073
2074         while (*in && size_out > 1)
2075         {
2076                 if (*in == '.')
2077                         last = out;
2078                 else if (*in == '/' || *in == '\\' || *in == ':')
2079                         last = NULL;
2080                 *out++ = *in++;
2081                 size_out--;
2082         }
2083         if (last)
2084                 *last = 0;
2085         else
2086                 *out = 0;
2087 }
2088
2089
2090 /*
2091 ==================
2092 FS_DefaultExtension
2093 ==================
2094 */
2095 void FS_DefaultExtension (char *path, const char *extension, size_t size_path)
2096 {
2097         const char *src;
2098
2099         // if path doesn't have a .EXT, append extension
2100         // (extension should include the .)
2101         src = path + strlen(path) - 1;
2102
2103         while (*src != '/' && src != path)
2104         {
2105                 if (*src == '.')
2106                         return;                 // it has an extension
2107                 src--;
2108         }
2109
2110         strlcat (path, extension, size_path);
2111 }
2112
2113
2114 /*
2115 ==================
2116 FS_FileExists
2117
2118 Look for a file in the packages and in the filesystem
2119 ==================
2120 */
2121 qboolean FS_FileExists (const char *filename)
2122 {
2123         return (FS_FindFile (filename, NULL, true) != NULL);
2124 }
2125
2126
2127 /*
2128 ==================
2129 FS_SysFileExists
2130
2131 Look for a file in the filesystem only
2132 ==================
2133 */
2134 qboolean FS_SysFileExists (const char *path)
2135 {
2136 #if WIN32
2137         int desc;
2138
2139         // TODO: use another function instead, to avoid opening the file
2140         desc = open (path, O_RDONLY | O_BINARY);
2141         if (desc < 0)
2142                 return false;
2143
2144         close (desc);
2145         return true;
2146 #else
2147         struct stat buf;
2148
2149         if (stat (path,&buf) == -1)
2150                 return false;
2151
2152         return true;
2153 #endif
2154 }
2155
2156 void FS_mkdir (const char *path)
2157 {
2158 #if WIN32
2159         _mkdir (path);
2160 #else
2161         mkdir (path, 0777);
2162 #endif
2163 }
2164
2165 /*
2166 ===========
2167 FS_Search
2168
2169 Allocate and fill a search structure with information on matching filenames.
2170 ===========
2171 */
2172 fssearch_t *FS_Search(const char *pattern, int caseinsensitive, int quiet)
2173 {
2174         fssearch_t *search;
2175         searchpath_t *searchpath;
2176         pack_t *pak;
2177         int i, basepathlength, numfiles, numchars;
2178         stringlist_t *dir, *dirfile, *liststart, *listcurrent, *listtemp;
2179         const char *slash, *backslash, *colon, *separator;
2180         char *basepath;
2181         char netpath[MAX_OSPATH];
2182         char temp[MAX_OSPATH];
2183
2184         for (i = 0;pattern[i] == '.' || pattern[i] == ':' || pattern[i] == '/' || pattern[i] == '\\';i++)
2185                 ;
2186
2187         if (i > 0)
2188         {
2189                 Con_Printf("Don't use punctuation at the beginning of a search pattern!\n");
2190                 return NULL;
2191         }
2192
2193         search = NULL;
2194         liststart = NULL;
2195         listcurrent = NULL;
2196         listtemp = NULL;
2197         slash = strrchr(pattern, '/');
2198         backslash = strrchr(pattern, '\\');
2199         colon = strrchr(pattern, ':');
2200         separator = max(slash, backslash);
2201         separator = max(separator, colon);
2202         basepathlength = separator ? (separator + 1 - pattern) : 0;
2203         basepath = (char *)Mem_Alloc (tempmempool, basepathlength + 1);
2204         if (basepathlength)
2205                 memcpy(basepath, pattern, basepathlength);
2206         basepath[basepathlength] = 0;
2207
2208         // search through the path, one element at a time
2209         for (searchpath = fs_searchpaths;searchpath;searchpath = searchpath->next)
2210         {
2211                 // is the element a pak file?
2212                 if (searchpath->pack)
2213                 {
2214                         // look through all the pak file elements
2215                         pak = searchpath->pack;
2216                         for (i = 0;i < pak->numfiles;i++)
2217                         {
2218                                 strcpy(temp, pak->files[i].name);
2219                                 while (temp[0])
2220                                 {
2221                                         if (matchpattern(temp, (char *)pattern, true))
2222                                         {
2223                                                 for (listtemp = liststart;listtemp;listtemp = listtemp->next)
2224                                                         if (!strcmp(listtemp->text, temp))
2225                                                                 break;
2226                                                 if (listtemp == NULL)
2227                                                 {
2228                                                         listcurrent = stringlistappend(listcurrent, temp);
2229                                                         if (liststart == NULL)
2230                                                                 liststart = listcurrent;
2231                                                         if (!quiet)
2232                                                                 Con_DPrintf("SearchPackFile: %s : %s\n", pak->filename, temp);
2233                                                 }
2234                                         }
2235                                         // strip off one path element at a time until empty
2236                                         // this way directories are added to the listing if they match the pattern
2237                                         slash = strrchr(temp, '/');
2238                                         backslash = strrchr(temp, '\\');
2239                                         colon = strrchr(temp, ':');
2240                                         separator = temp;
2241                                         if (separator < slash)
2242                                                 separator = slash;
2243                                         if (separator < backslash)
2244                                                 separator = backslash;
2245                                         if (separator < colon)
2246                                                 separator = colon;
2247                                         *((char *)separator) = 0;
2248                                 }
2249                         }
2250                 }
2251                 else
2252                 {
2253                         // get a directory listing and look at each name
2254                         dpsnprintf(netpath, sizeof (netpath), "%s%s", searchpath->filename, basepath);
2255                         if ((dir = listdirectory(netpath)))
2256                         {
2257                                 for (dirfile = dir;dirfile;dirfile = dirfile->next)
2258                                 {
2259                                         dpsnprintf(temp, sizeof(temp), "%s%s", basepath, dirfile->text);
2260                                         if (matchpattern(temp, (char *)pattern, true))
2261                                         {
2262                                                 for (listtemp = liststart;listtemp;listtemp = listtemp->next)
2263                                                         if (!strcmp(listtemp->text, temp))
2264                                                                 break;
2265                                                 if (listtemp == NULL)
2266                                                 {
2267                                                         listcurrent = stringlistappend(listcurrent, temp);
2268                                                         if (liststart == NULL)
2269                                                                 liststart = listcurrent;
2270                                                         if (!quiet)
2271                                                                 Con_DPrintf("SearchDirFile: %s\n", temp);
2272                                                 }
2273                                         }
2274                                 }
2275                                 freedirectory(dir);
2276                         }
2277                 }
2278         }
2279
2280         if (liststart)
2281         {
2282                 liststart = stringlistsort(liststart);
2283                 numfiles = 0;
2284                 numchars = 0;
2285                 for (listtemp = liststart;listtemp;listtemp = listtemp->next)
2286                 {
2287                         numfiles++;
2288                         numchars += (int)strlen(listtemp->text) + 1;
2289                 }
2290                 search = (fssearch_t *)Z_Malloc(sizeof(fssearch_t) + numchars + numfiles * sizeof(char *));
2291                 search->filenames = (char **)((char *)search + sizeof(fssearch_t));
2292                 search->filenamesbuffer = (char *)((char *)search + sizeof(fssearch_t) + numfiles * sizeof(char *));
2293                 search->numfilenames = (int)numfiles;
2294                 numfiles = 0;
2295                 numchars = 0;
2296                 for (listtemp = liststart;listtemp;listtemp = listtemp->next)
2297                 {
2298                         search->filenames[numfiles] = search->filenamesbuffer + numchars;
2299                         strcpy(search->filenames[numfiles], listtemp->text);
2300                         numfiles++;
2301                         numchars += (int)strlen(listtemp->text) + 1;
2302                 }
2303                 if (liststart)
2304                         stringlistfree(liststart);
2305         }
2306
2307         Mem_Free(basepath);
2308         return search;
2309 }
2310
2311 void FS_FreeSearch(fssearch_t *search)
2312 {
2313         Z_Free(search);
2314 }
2315
2316 extern int con_linewidth;
2317 int FS_ListDirectory(const char *pattern, int oneperline)
2318 {
2319         int numfiles;
2320         int numcolumns;
2321         int numlines;
2322         int columnwidth;
2323         int linebufpos;
2324         int i, j, k, l;
2325         const char *name;
2326         char linebuf[MAX_INPUTLINE];
2327         fssearch_t *search;
2328         search = FS_Search(pattern, true, true);
2329         if (!search)
2330                 return 0;
2331         numfiles = search->numfilenames;
2332         if (!oneperline)
2333         {
2334                 // FIXME: the names could be added to one column list and then
2335                 // gradually shifted into the next column if they fit, and then the
2336                 // next to make a compact variable width listing but it's a lot more
2337                 // complicated...
2338                 // find width for columns
2339                 columnwidth = 0;
2340                 for (i = 0;i < numfiles;i++)
2341                 {
2342                         l = (int)strlen(search->filenames[i]);
2343                         if (columnwidth < l)
2344                                 columnwidth = l;
2345                 }
2346                 // count the spacing character
2347                 columnwidth++;
2348                 // calculate number of columns
2349                 numcolumns = con_linewidth / columnwidth;
2350                 // don't bother with the column printing if it's only one column
2351                 if (numcolumns >= 2)
2352                 {
2353                         numlines = (numfiles + numcolumns - 1) / numcolumns;
2354                         for (i = 0;i < numlines;i++)
2355                         {
2356                                 linebufpos = 0;
2357                                 for (k = 0;k < numcolumns;k++)
2358                                 {
2359                                         l = i * numcolumns + k;
2360                                         if (l < numfiles)
2361                                         {
2362                                                 name = search->filenames[l];
2363                                                 for (j = 0;name[j] && linebufpos + 1 < (int)sizeof(linebuf);j++)
2364                                                         linebuf[linebufpos++] = name[j];
2365                                                 // space out name unless it's the last on the line
2366                                                 if (k + 1 < numcolumns && l + 1 < numfiles)
2367                                                         for (;j < columnwidth && linebufpos + 1 < (int)sizeof(linebuf);j++)
2368                                                                 linebuf[linebufpos++] = ' ';
2369                                         }
2370                                 }
2371                                 linebuf[linebufpos] = 0;
2372                                 Con_Printf("%s\n", linebuf);
2373                         }
2374                 }
2375                 else
2376                         oneperline = true;
2377         }
2378         if (oneperline)
2379                 for (i = 0;i < numfiles;i++)
2380                         Con_Printf("%s\n", search->filenames[i]);
2381         FS_FreeSearch(search);
2382         return (int)numfiles;
2383 }
2384
2385 static void FS_ListDirectoryCmd (const char* cmdname, int oneperline)
2386 {
2387         const char *pattern;
2388         if (Cmd_Argc() > 3)
2389         {
2390                 Con_Printf("usage:\n%s [path/pattern]\n", cmdname);
2391                 return;
2392         }
2393         if (Cmd_Argc() == 2)
2394                 pattern = Cmd_Argv(1);
2395         else
2396                 pattern = "*";
2397         if (!FS_ListDirectory(pattern, oneperline))
2398                 Con_Print("No files found.\n");
2399 }
2400
2401 void FS_Dir_f(void)
2402 {
2403         FS_ListDirectoryCmd("dir", true);
2404 }
2405
2406 void FS_Ls_f(void)
2407 {
2408         FS_ListDirectoryCmd("ls", false);
2409 }
2410
2411 const char *FS_WhichPack(const char *filename)
2412 {
2413         int index;
2414         searchpath_t *sp = FS_FindFile(filename, &index, true);
2415         if(sp && sp->pack)
2416                 return sp->pack->filename;
2417         else
2418                 return 0;
2419 }