4 Copyright (C) 2003-2005 Mathieu Olivier
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.
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.
15 See the GNU General Public License for more details.
17 You should have received a copy of the GNU General Public License
18 along with this program; if not, write to:
20 Free Software Foundation, Inc.
21 59 Temple Place - Suite 330
22 Boston, MA 02111-1307, USA
35 # include <sys/stat.h>
41 // Win32 requires us to add O_BINARY, but the other OSes don't have it
46 // In case the system doesn't support the O_NONBLOCK flag
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.
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.
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.
74 =============================================================================
78 =============================================================================
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"
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
92 // Zlib constants (from zlib.h)
93 #define Z_SYNC_FLUSH 2
96 #define Z_STREAM_END 1
97 #define ZLIB_VERSION "1.2.3"
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
105 =============================================================================
109 =============================================================================
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
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
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
125 char *msg; // last error message, NULL if no error
126 void *state; // not visible by applications
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
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
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)
143 #define FILE_BUFF_SIZE 2048
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];
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
163 fs_offset_t buff_ind, buff_len; // buffer current index and length
164 unsigned char buff [FILE_BUFF_SIZE];
171 // ------ PK3 files on disk ------ //
173 // You can get the complete ZIP format description from PKWARE website
175 typedef struct pk3_endOfCentralDir_s
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;
188 // ------ PAK files on disk ------ //
189 typedef struct dpackfile_s
192 int filepos, filelen;
195 typedef struct dpackheader_s
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)
209 typedef struct packfile_s
211 char name [MAX_QPATH];
214 fs_offset_t packsize; // size in the package
215 fs_offset_t realsize; // real file size (uncompressed)
218 typedef struct pack_s
220 char filename [MAX_OSPATH];
222 int ignorecase; // PK3 ignores case
229 // Search paths for files (including packages)
230 typedef struct searchpath_s
232 // only one of filename / pack will be used
233 char filename[MAX_OSPATH];
235 struct searchpath_s *next;
240 =============================================================================
244 =============================================================================
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);
258 =============================================================================
262 =============================================================================
265 mempool_t *fs_mempool;
267 pack_t *packlist = NULL;
269 searchpath_t *fs_searchpaths = NULL;
271 #define MAX_FILES_IN_PACK 65536
273 char fs_gamedir[MAX_OSPATH];
274 char fs_basedir[MAX_OSPATH];
276 qboolean fs_modified; // set true if using non-id files
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)"};
282 =============================================================================
284 PRIVATE FUNCTIONS - PK3 HANDLING
286 =============================================================================
289 // Functions exported from zlib
290 #if defined(WIN32) && defined(ZLIB_USES_WINAPI)
291 # define ZEXPORT WINAPI
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);
301 #define qz_inflateInit2(strm, windowBits) \
302 qz_inflateInit2_((strm), (windowBits), ZLIB_VERSION, sizeof(z_stream))
304 static dllfunction_t zlibfuncs[] =
306 {"inflate", (void **) &qz_inflate},
307 {"inflateEnd", (void **) &qz_inflateEnd},
308 {"inflateInit2_", (void **) &qz_inflateInit2_},
309 {"inflateReset", (void **) &qz_inflateReset},
313 // Handle for Zlib DLL
314 static dllhandle_t zlib_dll = NULL;
324 void PK3_CloseLibrary (void)
326 Sys_UnloadLibrary (&zlib_dll);
334 Try to load the Zlib DLL
337 qboolean PK3_OpenLibrary (void)
339 const char* dllnames [] =
344 # ifdef ZLIB_USES_WINAPI
350 #elif defined(MACOSX)
364 if (! Sys_LoadLibrary (dllnames, &zlib_dll, zlibfuncs))
366 Con_Printf ("Compressed files support disabled\n");
370 Con_Printf ("Compressed files support enabled\n");
377 PK3_GetEndOfCentralDir
379 Extract the end of the central directory from a PK3 package
382 qboolean PK3_GetEndOfCentralDir (const char *packfile, int packhandle, pk3_endOfCentralDir_t *eocd)
384 fs_offset_t filesize, maxsize;
385 unsigned char *buffer, *ptr;
388 // Get the package size
389 filesize = lseek (packhandle, 0, SEEK_END);
390 if (filesize < ZIP_END_CDIR_SIZE)
393 // Load the end of the file in memory
394 if (filesize < ZIP_MAX_COMMENTS_SIZE + ZIP_END_CDIR_SIZE)
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)
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];
410 while (BuffBigLong (ptr) != ZIP_END_HEADER)
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);
442 Extract the file list from a PK3 file
445 int PK3_BuildFileList (pack_t *pack, const pk3_endOfCentralDir_t *eocd)
447 unsigned char *central_dir, *ptr;
449 fs_offset_t remaining;
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);
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;
462 for (ind = 0; ind < eocd->nbentries; ind++)
464 fs_offset_t namesize, count;
466 // Checking the remaining size
467 if (remaining < ZIP_CDIR_CHUNK_BASE_SIZE)
469 Mem_Free (central_dir);
472 remaining -= ZIP_CDIR_CHUNK_BASE_SIZE;
475 if (BuffBigLong (ptr) != ZIP_CDIR_HEADER)
477 Mem_Free (central_dir);
481 namesize = BuffLittleShort (&ptr[28]); // filename length
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)
490 // Still enough bytes for the name?
491 if (remaining < namesize || namesize >= (int)sizeof (*pack->files))
493 Mem_Free (central_dir);
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] != '/')
500 char filename [sizeof (pack->files[0].name)];
501 fs_offset_t offset, packsize, realsize;
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';
509 if (BuffLittleShort (&ptr[10]))
510 flags = PACKFILE_FLAG_DEFLATED;
513 offset = BuffLittleLong (&ptr[42]);
514 packsize = BuffLittleLong (&ptr[20]);
515 realsize = BuffLittleLong (&ptr[24]);
516 FS_AddFileToPack (filename, pack, offset, packsize, realsize, flags);
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;
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;
539 Create a package entry associated with a PK3 file
542 pack_t *FS_LoadPackPK3 (const char *packfile)
545 pk3_endOfCentralDir_t eocd;
549 packhandle = open (packfile, O_RDONLY | O_BINARY);
553 if (! PK3_GetEndOfCentralDir (packfile, packhandle, &eocd))
555 Con_Printf ("%s is not a PK3 file\n", packfile);
560 // Multi-volume ZIP archives are NOT allowed
561 if (eocd.disknum != 0 || eocd.cdir_disknum != 0)
563 Con_Printf ("%s is a multi-volume ZIP archive\n", packfile);
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)
573 Con_Printf ("%s contains too many files (%hu)\n", packfile, eocd.nbentries);
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;
589 real_nb_files = PK3_BuildFileList (pack, &eocd);
590 if (real_nb_files < 0)
592 Con_Printf ("%s is not a valid PK3 file\n", packfile);
598 Con_Printf("Added packfile %s (%i files)\n", packfile, real_nb_files);
605 PK3_GetTrueFileOffset
607 Find where the true file data offset is
610 qboolean PK3_GetTrueFileOffset (packfile_t *pfile, pack_t *pack)
612 unsigned char buffer [ZIP_LOCAL_CHUNK_BASE_SIZE];
616 if (pfile->flags & PACKFILE_FLAG_TRUEOFFS)
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)
624 Con_Printf ("Can't retrieve file %s in package %s\n", pfile->name, pack->filename);
628 // Skip name and extra field
629 pfile->offset += BuffLittleShort (&buffer[26]) + BuffLittleShort (&buffer[28]) + ZIP_LOCAL_CHUNK_BASE_SIZE;
631 pfile->flags |= PACKFILE_FLAG_TRUEOFFS;
637 =============================================================================
639 OTHER PRIVATE FUNCTIONS
641 =============================================================================
649 Add a file to the list of files contained into a package
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)
656 int (*strcmp_funct) (const char* str1, const char* str2);
657 int left, right, middle;
660 strcmp_funct = pack->ignorecase ? strcasecmp : strcmp;
662 // Look for the slot we should put that file into (binary search)
664 right = pack->numfiles - 1;
665 while (left <= right)
669 middle = (left + right) / 2;
670 diff = strcmp_funct (pack->files[middle].name, name);
672 // If we found the file, there's a problem
674 Con_Printf ("Package %s contains the file %s several times\n", pack->filename, name);
676 // If we're too far in the list
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));
688 strlcpy (pfile->name, name, sizeof (pfile->name));
689 pfile->offset = offset;
690 pfile->packsize = packsize;
691 pfile->realsize = realsize;
692 pfile->flags = flags;
702 Only used for FS_Open.
705 void FS_CreatePath (char *path)
709 for (ofs = path+1 ; *ofs ; ofs++)
711 if (*ofs == '/' || *ofs == '\\')
713 // create the directory
729 void FS_Path_f (void)
733 Con_Print("Current search path:\n");
734 for (s=fs_searchpaths ; s ; s=s->next)
737 Con_Printf("%s (%i files)\n", s->pack->filename, s->pack->numfiles);
739 Con_Printf("%s\n", s->filename);
748 Takes an explicit (not game tree related) path to a pak file.
750 Loads the header and directory, adding the files at the beginning
751 of the list so they override previous pack files.
754 pack_t *FS_LoadPackPAK (const char *packfile)
756 dpackheader_t header;
762 packhandle = open (packfile, O_RDONLY | O_BINARY);
765 read (packhandle, (void *)&header, sizeof(header));
766 if (memcmp(header.id, "PACK", 4))
768 Con_Printf ("%s is not a packfile\n", packfile);
772 header.dirofs = LittleLong (header.dirofs);
773 header.dirlen = LittleLong (header.dirlen);
775 if (header.dirlen % sizeof(dpackfile_t))
777 Con_Printf ("%s has an invalid directory size\n", packfile);
782 numpackfiles = header.dirlen / sizeof(dpackfile_t);
784 if (numpackfiles > MAX_FILES_IN_PACK)
786 Con_Printf ("%s has %i files\n", packfile, numpackfiles);
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))
795 Con_Printf("%s is an incomplete PAK, not loading\n", packfile);
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;
806 pack->files = (packfile_t *)Mem_Alloc(fs_mempool, numpackfiles * sizeof(packfile_t));
807 pack->next = packlist;
810 // parse the directory
811 for (i = 0;i < numpackfiles;i++)
813 fs_offset_t offset = LittleLong (info[i].filepos);
814 fs_offset_t size = LittleLong (info[i].filelen);
816 FS_AddFileToPack (info[i].name, pack, offset, size, size, PACKFILE_FLAG_TRUEOFFS);
821 Con_Printf("Added packfile %s (%i files)\n", packfile, numpackfiles);
829 Adds the given pack to the search path.
830 The pack type is autodetected by the file extension.
832 Returns true if the file was successfully added to the
833 search path or if it was already included.
835 If keep_plain_dirs is set, the pack will be added AFTER the first sequence of
839 static qboolean FS_AddPack_Fullpath(const char *pakfile, qboolean *already_loaded, qboolean keep_plain_dirs)
841 searchpath_t *search;
843 const char *ext = FS_FileExtension(pakfile);
845 for(search = fs_searchpaths; search; search = search->next)
847 if(search->pack && !strcasecmp(search->pack->filename, pakfile))
850 *already_loaded = true;
851 return true; // already loaded
856 *already_loaded = false;
858 if(!strcasecmp(ext, "pak"))
859 pak = FS_LoadPackPAK (pakfile);
860 else if(!strcasecmp(ext, "pk3"))
861 pak = FS_LoadPackPK3 (pakfile);
863 Con_Printf("\"%s\" does not have a pack extension\n", pakfile);
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)
873 insertion_point = fs_searchpaths;
876 if(!insertion_point->next)
878 if(insertion_point->next->pack)
880 insertion_point = insertion_point->next;
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...
888 search = (searchpath_t *)Mem_Alloc(fs_mempool, sizeof(searchpath_t));
890 search->next = fs_searchpaths;
891 fs_searchpaths = search;
894 // otherwise we want to append directly after insertion_point.
896 search = (searchpath_t *)Mem_Alloc(fs_mempool, sizeof(searchpath_t));
898 search->next = insertion_point->next;
899 insertion_point->next = search;
904 search = (searchpath_t *)Mem_Alloc(fs_mempool, sizeof(searchpath_t));
906 search->next = fs_searchpaths;
907 fs_searchpaths = search;
913 Con_Printf("unable to load pak \"%s\"\n", pakfile);
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.
926 Returns true if the file was successfully added to the
927 search path or if it was already included.
929 If keep_plain_dirs is set, the pack will be added AFTER the first sequence of
933 qboolean FS_AddPack(const char *pakfile, qboolean *already_loaded, qboolean keep_plain_dirs)
935 char fullpath[MAX_QPATH];
937 searchpath_t *search;
940 *already_loaded = false;
942 // then find the real name...
943 search = FS_FindFile(pakfile, &index, true);
944 if(!search || search->pack)
946 Con_Printf("could not find pak \"%s\"\n", pakfile);
950 dpsnprintf(fullpath, sizeof(fullpath), "%s%s", search->filename, pakfile);
952 return FS_AddPack_Fullpath(fullpath, already_loaded, keep_plain_dirs);
960 Sets fs_gamedir, adds the directory to the head of the path,
961 then loads and adds pak1.pak pak2.pak ...
964 void FS_AddGameDirectory (const char *dir)
966 stringlist_t *list, *current;
967 searchpath_t *search;
968 char pakfile[MAX_OSPATH];
970 strlcpy (fs_gamedir, dir, sizeof (fs_gamedir));
972 list = listdirectory(dir);
974 // add any PAK package in the directory
975 for (current = list;current;current = current->next)
977 if (!strcasecmp(FS_FileExtension(current->text), "pak"))
979 dpsnprintf (pakfile, sizeof (pakfile), "%s%s", dir, current->text);
980 FS_AddPack_Fullpath(pakfile, NULL, false);
984 // add any PK3 package in the director
985 for (current = list;current;current = current->next)
987 if (!strcasecmp(FS_FileExtension(current->text), "pk3"))
989 dpsnprintf (pakfile, sizeof (pakfile), "%s%s", dir, current->text);
990 FS_AddPack_Fullpath(pakfile, NULL, false);
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;
1009 void FS_AddGameHierarchy (const char *dir)
1012 const char *homedir;
1015 // Add the common game directory
1016 FS_AddGameDirectory (va("%s%s/", fs_basedir, dir));
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));
1032 static const char *FS_FileExtension (const char *in)
1034 const char *separator, *backslash, *colon, *dot;
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)
1044 dot = strrchr(in, '.');
1045 if (dot == NULL || (separator && (dot < separator)))
1060 searchpath_t *search;
1062 fs_mempool = Mem_AllocPool("file management", 0, NULL);
1064 strcpy(fs_gamedir, "");
1066 // If the base directory is explicitly defined by the compilation process
1067 #ifdef DP_FS_BASEDIR
1068 strcpy(fs_basedir, DP_FS_BASEDIR);
1071 strcpy(fs_basedir, "");
1074 // FIXME: is there a better way to find the directory outside the .app?
1075 if (strstr(com_argv[0], ".app/"))
1079 split = strstr(com_argv[0], ".app/");
1080 while (split > com_argv[0] && *split != '/')
1082 strlcpy(fs_basedir, com_argv[0], sizeof(fs_basedir));
1083 fs_basedir[split - com_argv[0]] = 0;
1091 // Overrides the system supplied base directory (under GAMENAME)
1092 // 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)
1093 i = COM_CheckParm ("-basedir");
1094 if (i && i < com_argc-1)
1096 strlcpy (fs_basedir, com_argv[i+1], sizeof (fs_basedir));
1097 i = (int)strlen (fs_basedir);
1098 if (i > 0 && (fs_basedir[i-1] == '\\' || fs_basedir[i-1] == '/'))
1099 fs_basedir[i-1] = 0;
1102 // add a path separator to the end of the basedir if it lacks one
1103 if (fs_basedir[0] && fs_basedir[strlen(fs_basedir) - 1] != '/' && fs_basedir[strlen(fs_basedir) - 1] != '\\')
1104 strlcat(fs_basedir, "/", sizeof(fs_basedir));
1106 // -path <dir or packfile> [<dir or packfile>] ...
1107 // Fully specifies the exact search path, overriding the generated one
1108 // 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)
1109 i = COM_CheckParm ("-path");
1113 while (++i < com_argc)
1115 if (!com_argv[i] || com_argv[i][0] == '+' || com_argv[i][0] == '-')
1118 if(!FS_AddPack_Fullpath(com_argv[i], NULL, false))
1120 search = (searchpath_t *)Mem_Alloc(fs_mempool, sizeof(searchpath_t));
1121 strlcpy (search->filename, com_argv[i], sizeof (search->filename));
1122 search->next = fs_searchpaths;
1123 fs_searchpaths = search;
1129 // add the game-specific paths
1130 // gamedirname1 (typically id1)
1131 FS_AddGameHierarchy (gamedirname1);
1133 // add the game-specific path, if any
1137 FS_AddGameHierarchy (gamedirname2);
1140 // set the com_modname (reported in server info)
1141 strlcpy(com_modname, gamedirname1, sizeof(com_modname));
1144 // Adds basedir/gamedir as an override game
1145 // LordHavoc: now supports multiple -game directories
1146 for (i = 1;i < com_argc;i++)
1150 if (!strcmp (com_argv[i], "-game") && i < com_argc-1)
1154 FS_AddGameHierarchy (com_argv[i]);
1155 // update the com_modname
1156 strlcpy (com_modname, com_argv[i], sizeof (com_modname));
1160 // If "-condebug" is in the command line, remove the previous log file
1161 if (COM_CheckParm ("-condebug") != 0)
1162 unlink (va("%s/qconsole.log", fs_gamedir));
1165 void FS_Init_Commands(void)
1167 Cvar_RegisterVariable (&scr_screenshot_name);
1169 Cmd_AddCommand ("path", FS_Path_f, "print searchpath (game directories and archives)");
1170 Cmd_AddCommand ("dir", FS_Dir_f, "list files in searchpath matching an * filename pattern, one per line");
1171 Cmd_AddCommand ("ls", FS_Ls_f, "list files in searchpath matching an * filename pattern, multiple per line");
1173 // set the default screenshot name to either the mod name or the
1174 // gamemode screenshot name
1176 Cvar_SetQuick (&scr_screenshot_name, com_modname);
1178 Cvar_SetQuick (&scr_screenshot_name, gamescreenshotname);
1186 void FS_Shutdown (void)
1188 Mem_FreePool (&fs_mempool);
1192 ====================
1195 Internal function used to create a qfile_t and open the relevant non-packed file on disk
1196 ====================
1198 static qfile_t* FS_SysOpen (const char* filepath, const char* mode, qboolean nonblocking)
1204 // Parse the mode string
1213 opt = O_CREAT | O_TRUNC;
1217 opt = O_CREAT | O_APPEND;
1220 Con_Printf ("FS_SysOpen(%s, %s): invalid mode\n", filepath, mode);
1223 for (ind = 1; mode[ind] != '\0'; ind++)
1234 Con_Printf ("FS_SysOpen(%s, %s): unknown character in mode (%c)\n",
1235 filepath, mode, mode[ind]);
1242 file = (qfile_t *)Mem_Alloc (fs_mempool, sizeof (*file));
1243 memset (file, 0, sizeof (*file));
1246 file->handle = open (filepath, mod | opt, 0666);
1247 if (file->handle < 0)
1253 file->real_length = lseek (file->handle, 0, SEEK_END);
1255 // For files opened in append mode, we start at the end of the file
1257 file->position = file->real_length;
1259 lseek (file->handle, 0, SEEK_SET);
1269 Open a packed file using its package file descriptor
1272 qfile_t *FS_OpenPackedFile (pack_t* pack, int pack_ind)
1278 pfile = &pack->files[pack_ind];
1280 // If we don't have the true offset, get it now
1281 if (! (pfile->flags & PACKFILE_FLAG_TRUEOFFS))
1282 if (!PK3_GetTrueFileOffset (pfile, pack))
1285 // No Zlib DLL = no compressed files
1286 if (!zlib_dll && (pfile->flags & PACKFILE_FLAG_DEFLATED))
1288 Con_Printf("WARNING: can't open the compressed file %s\n"
1289 "You need the Zlib DLL to use compressed files\n",
1294 // LordHavoc: lseek affects all duplicates of a handle so we do it before
1295 // the dup() call to avoid having to close the dup_handle on error here
1296 if (lseek (pack->handle, pfile->offset, SEEK_SET) == -1)
1298 Con_Printf ("FS_OpenPackedFile: can't lseek to %s in %s (offset: %d)\n",
1299 pfile->name, pack->filename, pfile->offset);
1303 dup_handle = dup (pack->handle);
1306 Con_Printf ("FS_OpenPackedFile: can't dup package's handle (pack: %s)\n", pack->filename);
1310 file = (qfile_t *)Mem_Alloc (fs_mempool, sizeof (*file));
1311 memset (file, 0, sizeof (*file));
1312 file->handle = dup_handle;
1313 file->flags = QFILE_FLAG_PACKED;
1314 file->real_length = pfile->realsize;
1315 file->offset = pfile->offset;
1319 if (pfile->flags & PACKFILE_FLAG_DEFLATED)
1323 file->flags |= QFILE_FLAG_DEFLATED;
1325 // We need some more variables
1326 ztk = (ztoolkit_t *)Mem_Alloc (fs_mempool, sizeof (*ztk));
1328 ztk->comp_length = pfile->packsize;
1330 // Initialize zlib stream
1331 ztk->zstream.next_in = ztk->input;
1332 ztk->zstream.avail_in = 0;
1334 /* From Zlib's "unzip.c":
1336 * windowBits is passed < 0 to tell that there is no zlib header.
1337 * Note that in this case inflate *requires* an extra "dummy" byte
1338 * after the compressed stream in order to complete decompression and
1339 * return Z_STREAM_END.
1340 * In unzip, i don't wait absolutely Z_STREAM_END because I known the
1341 * size of both compressed and uncompressed data
1343 if (qz_inflateInit2 (&ztk->zstream, -MAX_WBITS) != Z_OK)
1345 Con_Printf ("FS_OpenPackedFile: inflate init error (file: %s)\n", pfile->name);
1351 ztk->zstream.next_out = file->buff;
1352 ztk->zstream.avail_out = sizeof (file->buff);
1361 ====================
1364 Return true if the path should be rejected due to one of the following:
1365 1: path elements that are non-portable
1366 2: path elements that would allow access to files outside the game directory,
1367 or are just not a good idea for a mod to be using.
1368 ====================
1370 int FS_CheckNastyPath (const char *path)
1372 // Windows: don't allow \ in filenames (windows-only), period.
1373 // (on Windows \ is a directory separator, but / is also supported)
1374 if (strstr(path, "\\"))
1375 return 1; // non-portable
1377 // Mac: don't allow Mac-only filenames - : is a directory separator
1378 // instead of /, but we rely on / working already, so there's no reason to
1379 // support a Mac-only path
1380 // Amiga and Windows: : tries to go to root of drive
1381 if (strstr(path, ":"))
1382 return 1; // non-portable attempt to go to root of drive
1384 // Amiga: // is parent directory
1385 if (strstr(path, "//"))
1386 return 1; // non-portable attempt to go to parent directory
1388 // all: don't allow going to current directory (./) or parent directory (../ or /../)
1389 if (strstr(path, "./"))
1390 return 2; // attempt to go outside the game directory
1392 // Windows and UNIXes: don't allow absolute paths
1394 return 2; // attempt to go outside the game directory
1396 // after all these checks we're pretty sure it's a / separated filename
1397 // and won't do much if any harm
1403 ====================
1406 Look for a file in the packages and in the filesystem
1408 Return the searchpath where the file was found (or NULL)
1409 and the file index in the package if relevant
1410 ====================
1412 static searchpath_t *FS_FindFile (const char *name, int* index, qboolean quiet)
1414 searchpath_t *search;
1417 // search through the path, one element at a time
1418 for (search = fs_searchpaths;search;search = search->next)
1420 // is the element a pak file?
1423 int (*strcmp_funct) (const char* str1, const char* str2);
1424 int left, right, middle;
1427 strcmp_funct = pak->ignorecase ? strcasecmp : strcmp;
1429 // Look for the file (binary search)
1431 right = pak->numfiles - 1;
1432 while (left <= right)
1436 middle = (left + right) / 2;
1437 diff = strcmp_funct (pak->files[middle].name, name);
1442 if (!quiet && developer.integer >= 10)
1443 Con_Printf("FS_FindFile: %s in %s\n",
1444 pak->files[middle].name, pak->filename);
1451 // If we're too far in the list
1460 char netpath[MAX_OSPATH];
1461 dpsnprintf(netpath, sizeof(netpath), "%s%s", search->filename, name);
1462 if (FS_SysFileExists (netpath))
1464 if (!quiet && developer.integer >= 10)
1465 Con_Printf("FS_FindFile: %s\n", netpath);
1474 if (!quiet && developer.integer >= 10)
1475 Con_Printf("FS_FindFile: can't find %s\n", name);
1487 Look for a file in the search paths and open it in read-only mode
1490 qfile_t *FS_OpenReadFile (const char *filename, qboolean quiet, qboolean nonblocking)
1492 searchpath_t *search;
1495 search = FS_FindFile (filename, &pack_ind, quiet);
1501 // Found in the filesystem?
1504 char path [MAX_OSPATH];
1505 dpsnprintf (path, sizeof (path), "%s%s", search->filename, filename);
1506 return FS_SysOpen (path, "rb", nonblocking);
1509 // So, we found it in a package...
1510 return FS_OpenPackedFile (search->pack, pack_ind);
1515 =============================================================================
1517 MAIN PUBLIC FUNCTIONS
1519 =============================================================================
1523 ====================
1526 Open a file. The syntax is the same as fopen
1527 ====================
1529 qfile_t* FS_Open (const char* filepath, const char* mode, qboolean quiet, qboolean nonblocking)
1531 if (FS_CheckNastyPath(filepath))
1533 Con_Printf("FS_Open(\"%s\", \"%s\", %s): nasty filename rejected\n", filepath, mode, quiet ? "true" : "false");
1537 // If the file is opened in "write", "append", or "read/write" mode
1538 if (mode[0] == 'w' || mode[0] == 'a' || strchr (mode, '+'))
1540 char real_path [MAX_OSPATH];
1542 // Open the file on disk directly
1543 dpsnprintf (real_path, sizeof (real_path), "%s/%s", fs_gamedir, filepath);
1545 // Create directories up to the file
1546 FS_CreatePath (real_path);
1548 return FS_SysOpen (real_path, mode, nonblocking);
1550 // Else, we look at the various search paths and open the file in read-only mode
1552 return FS_OpenReadFile (filepath, quiet, nonblocking);
1557 ====================
1561 ====================
1563 int FS_Close (qfile_t* file)
1565 if (close (file->handle))
1570 qz_inflateEnd (&file->ztk->zstream);
1571 Mem_Free (file->ztk);
1580 ====================
1583 Write "datasize" bytes into a file
1584 ====================
1586 fs_offset_t FS_Write (qfile_t* file, const void* data, size_t datasize)
1590 // If necessary, seek to the exact file position we're supposed to be
1591 if (file->buff_ind != file->buff_len)
1592 lseek (file->handle, file->buff_ind - file->buff_len, SEEK_CUR);
1594 // Purge cached data
1597 // Write the buffer and update the position
1598 result = write (file->handle, data, (fs_offset_t)datasize);
1599 file->position = lseek (file->handle, 0, SEEK_CUR);
1600 if (file->real_length < file->position)
1601 file->real_length = file->position;
1611 ====================
1614 Read up to "buffersize" bytes from a file
1615 ====================
1617 fs_offset_t FS_Read (qfile_t* file, void* buffer, size_t buffersize)
1619 fs_offset_t count, done;
1621 if (buffersize == 0)
1624 // Get rid of the ungetc character
1625 if (file->ungetc != EOF)
1627 ((char*)buffer)[0] = file->ungetc;
1635 // First, we copy as many bytes as we can from "buff"
1636 if (file->buff_ind < file->buff_len)
1638 count = file->buff_len - file->buff_ind;
1640 done += ((fs_offset_t)buffersize > count) ? count : (fs_offset_t)buffersize;
1641 memcpy (buffer, &file->buff[file->buff_ind], done);
1642 file->buff_ind += done;
1645 if (buffersize == 0)
1649 // NOTE: at this point, the read buffer is always empty
1651 // If the file isn't compressed
1652 if (! (file->flags & QFILE_FLAG_DEFLATED))
1656 // We must take care to not read after the end of the file
1657 count = file->real_length - file->position;
1659 // If we have a lot of data to get, put them directly into "buffer"
1660 if (buffersize > sizeof (file->buff) / 2)
1662 if (count > (fs_offset_t)buffersize)
1663 count = (fs_offset_t)buffersize;
1664 lseek (file->handle, file->offset + file->position, SEEK_SET);
1665 nb = read (file->handle, &((unsigned char*)buffer)[done], count);
1669 file->position += nb;
1671 // Purge cached data
1677 if (count > (fs_offset_t)sizeof (file->buff))
1678 count = (fs_offset_t)sizeof (file->buff);
1679 lseek (file->handle, file->offset + file->position, SEEK_SET);
1680 nb = read (file->handle, file->buff, count);
1683 file->buff_len = nb;
1684 file->position += nb;
1686 // Copy the requested data in "buffer" (as much as we can)
1687 count = (fs_offset_t)buffersize > file->buff_len ? file->buff_len : (fs_offset_t)buffersize;
1688 memcpy (&((unsigned char*)buffer)[done], file->buff, count);
1689 file->buff_ind = count;
1697 // If the file is compressed, it's more complicated...
1698 // We cycle through a few operations until we have read enough data
1699 while (buffersize > 0)
1701 ztoolkit_t *ztk = file->ztk;
1704 // NOTE: at this point, the read buffer is always empty
1706 // If "input" is also empty, we need to refill it
1707 if (ztk->in_ind == ztk->in_len)
1709 // If we are at the end of the file
1710 if (file->position == file->real_length)
1713 count = (fs_offset_t)(ztk->comp_length - ztk->in_position);
1714 if (count > (fs_offset_t)sizeof (ztk->input))
1715 count = (fs_offset_t)sizeof (ztk->input);
1716 lseek (file->handle, file->offset + (fs_offset_t)ztk->in_position, SEEK_SET);
1717 if (read (file->handle, ztk->input, count) != count)
1719 Con_Printf ("FS_Read: unexpected end of file\n");
1724 ztk->in_len = count;
1725 ztk->in_position += count;
1728 ztk->zstream.next_in = &ztk->input[ztk->in_ind];
1729 ztk->zstream.avail_in = (unsigned int)(ztk->in_len - ztk->in_ind);
1731 // Now that we are sure we have compressed data available, we need to determine
1732 // if it's better to inflate it in "file->buff" or directly in "buffer"
1734 // Inflate the data in "file->buff"
1735 if (buffersize < sizeof (file->buff) / 2)
1737 ztk->zstream.next_out = file->buff;
1738 ztk->zstream.avail_out = sizeof (file->buff);
1739 error = qz_inflate (&ztk->zstream, Z_SYNC_FLUSH);
1740 if (error != Z_OK && error != Z_STREAM_END)
1742 Con_Printf ("FS_Read: Can't inflate file\n");
1745 ztk->in_ind = ztk->in_len - ztk->zstream.avail_in;
1747 file->buff_len = (fs_offset_t)sizeof (file->buff) - ztk->zstream.avail_out;
1748 file->position += file->buff_len;
1750 // Copy the requested data in "buffer" (as much as we can)
1751 count = (fs_offset_t)buffersize > file->buff_len ? file->buff_len : (fs_offset_t)buffersize;
1752 memcpy (&((unsigned char*)buffer)[done], file->buff, count);
1753 file->buff_ind = count;
1756 // Else, we inflate directly in "buffer"
1759 ztk->zstream.next_out = &((unsigned char*)buffer)[done];
1760 ztk->zstream.avail_out = (unsigned int)buffersize;
1761 error = qz_inflate (&ztk->zstream, Z_SYNC_FLUSH);
1762 if (error != Z_OK && error != Z_STREAM_END)
1764 Con_Printf ("FS_Read: Can't inflate file\n");
1767 ztk->in_ind = ztk->in_len - ztk->zstream.avail_in;
1769 // How much data did it inflate?
1770 count = (fs_offset_t)(buffersize - ztk->zstream.avail_out);
1771 file->position += count;
1773 // Purge cached data
1778 buffersize -= count;
1786 ====================
1789 Print a string into a file
1790 ====================
1792 int FS_Print (qfile_t* file, const char *msg)
1794 return (int)FS_Write (file, msg, strlen (msg));
1798 ====================
1801 Print a string into a file
1802 ====================
1804 int FS_Printf(qfile_t* file, const char* format, ...)
1809 va_start (args, format);
1810 result = FS_VPrintf (file, format, args);
1818 ====================
1821 Print a string into a file
1822 ====================
1824 int FS_VPrintf (qfile_t* file, const char* format, va_list ap)
1827 fs_offset_t buff_size = MAX_INPUTLINE;
1832 tempbuff = (char *)Mem_Alloc (tempmempool, buff_size);
1833 len = dpvsnprintf (tempbuff, buff_size, format, ap);
1834 if (len >= 0 && len < buff_size)
1836 Mem_Free (tempbuff);
1840 len = write (file->handle, tempbuff, len);
1841 Mem_Free (tempbuff);
1848 ====================
1851 Get the next character of a file
1852 ====================
1854 int FS_Getc (qfile_t* file)
1858 if (FS_Read (file, &c, 1) != 1)
1866 ====================
1869 Put a character back into the read buffer (only supports one character!)
1870 ====================
1872 int FS_UnGetc (qfile_t* file, unsigned char c)
1874 // If there's already a character waiting to be read
1875 if (file->ungetc != EOF)
1884 ====================
1887 Move the position index in a file
1888 ====================
1890 int FS_Seek (qfile_t* file, fs_offset_t offset, int whence)
1893 unsigned char* buffer;
1894 fs_offset_t buffersize;
1896 // Compute the file offset
1900 offset += file->position - file->buff_len + file->buff_ind;
1907 offset += file->real_length;
1913 if (offset < 0 || offset > (long) file->real_length)
1916 // If we have the data in our read buffer, we don't need to actually seek
1917 if (file->position - file->buff_len <= offset && offset <= file->position)
1919 file->buff_ind = offset + file->buff_len - file->position;
1923 // Purge cached data
1926 // Unpacked or uncompressed files can seek directly
1927 if (! (file->flags & QFILE_FLAG_DEFLATED))
1929 if (lseek (file->handle, file->offset + offset, SEEK_SET) == -1)
1931 file->position = offset;
1935 // Seeking in compressed files is more a hack than anything else,
1936 // but we need to support it, so here we go.
1939 // If we have to go back in the file, we need to restart from the beginning
1940 if (offset <= file->position)
1944 ztk->in_position = 0;
1946 lseek (file->handle, file->offset, SEEK_SET);
1948 // Reset the Zlib stream
1949 ztk->zstream.next_in = ztk->input;
1950 ztk->zstream.avail_in = 0;
1951 qz_inflateReset (&ztk->zstream);
1954 // We need a big buffer to force inflating into it directly
1955 buffersize = 2 * sizeof (file->buff);
1956 buffer = (unsigned char *)Mem_Alloc (tempmempool, buffersize);
1958 // Skip all data until we reach the requested offset
1959 while (offset > file->position)
1961 fs_offset_t diff = offset - file->position;
1962 fs_offset_t count, len;
1964 count = (diff > buffersize) ? buffersize : diff;
1965 len = FS_Read (file, buffer, count);
1979 ====================
1982 Give the current position in a file
1983 ====================
1985 fs_offset_t FS_Tell (qfile_t* file)
1987 return file->position - file->buff_len + file->buff_ind;
1992 ====================
1995 Erases any buffered input or output data
1996 ====================
1998 void FS_Purge (qfile_t* file)
2010 Filename are relative to the quake directory.
2011 Always appends a 0 byte.
2014 unsigned char *FS_LoadFile (const char *path, mempool_t *pool, qboolean quiet, fs_offset_t *filesizepointer)
2017 unsigned char *buf = NULL;
2018 fs_offset_t filesize = 0;
2020 file = FS_Open (path, "rb", quiet, false);
2023 filesize = file->real_length;
2024 buf = (unsigned char *)Mem_Alloc (pool, filesize + 1);
2025 buf[filesize] = '\0';
2026 FS_Read (file, buf, filesize);
2030 if (filesizepointer)
2031 *filesizepointer = filesize;
2040 The filename will be prefixed by the current game directory
2043 qboolean FS_WriteFile (const char *filename, void *data, fs_offset_t len)
2047 file = FS_Open (filename, "wb", false, false);
2050 Con_Printf("FS_WriteFile: failed on %s\n", filename);
2054 Con_DPrintf("FS_WriteFile: %s\n", filename);
2055 FS_Write (file, data, len);
2062 =============================================================================
2064 OTHERS PUBLIC FUNCTIONS
2066 =============================================================================
2074 void FS_StripExtension (const char *in, char *out, size_t size_out)
2081 while (*in && size_out > 1)
2085 else if (*in == '/' || *in == '\\' || *in == ':')
2102 void FS_DefaultExtension (char *path, const char *extension, size_t size_path)
2106 // if path doesn't have a .EXT, append extension
2107 // (extension should include the .)
2108 src = path + strlen(path) - 1;
2110 while (*src != '/' && src != path)
2113 return; // it has an extension
2117 strlcat (path, extension, size_path);
2125 Look for a file in the packages and in the filesystem
2128 qboolean FS_FileExists (const char *filename)
2130 return (FS_FindFile (filename, NULL, true) != NULL);
2138 Look for a file in the filesystem only
2141 qboolean FS_SysFileExists (const char *path)
2146 // TODO: use another function instead, to avoid opening the file
2147 desc = open (path, O_RDONLY | O_BINARY);
2156 if (stat (path,&buf) == -1)
2163 void FS_mkdir (const char *path)
2176 Allocate and fill a search structure with information on matching filenames.
2179 fssearch_t *FS_Search(const char *pattern, int caseinsensitive, int quiet)
2182 searchpath_t *searchpath;
2184 int i, basepathlength, numfiles, numchars;
2185 stringlist_t *dir, *dirfile, *liststart, *listcurrent, *listtemp;
2186 const char *slash, *backslash, *colon, *separator;
2188 char netpath[MAX_OSPATH];
2189 char temp[MAX_OSPATH];
2191 for (i = 0;pattern[i] == '.' || pattern[i] == ':' || pattern[i] == '/' || pattern[i] == '\\';i++)
2196 Con_Printf("Don't use punctuation at the beginning of a search pattern!\n");
2204 slash = strrchr(pattern, '/');
2205 backslash = strrchr(pattern, '\\');
2206 colon = strrchr(pattern, ':');
2207 separator = max(slash, backslash);
2208 separator = max(separator, colon);
2209 basepathlength = separator ? (separator + 1 - pattern) : 0;
2210 basepath = (char *)Mem_Alloc (tempmempool, basepathlength + 1);
2212 memcpy(basepath, pattern, basepathlength);
2213 basepath[basepathlength] = 0;
2215 // search through the path, one element at a time
2216 for (searchpath = fs_searchpaths;searchpath;searchpath = searchpath->next)
2218 // is the element a pak file?
2219 if (searchpath->pack)
2221 // look through all the pak file elements
2222 pak = searchpath->pack;
2223 for (i = 0;i < pak->numfiles;i++)
2225 strcpy(temp, pak->files[i].name);
2228 if (matchpattern(temp, (char *)pattern, true))
2230 for (listtemp = liststart;listtemp;listtemp = listtemp->next)
2231 if (!strcmp(listtemp->text, temp))
2233 if (listtemp == NULL)
2235 listcurrent = stringlistappend(listcurrent, temp);
2236 if (liststart == NULL)
2237 liststart = listcurrent;
2239 Con_DPrintf("SearchPackFile: %s : %s\n", pak->filename, temp);
2242 // strip off one path element at a time until empty
2243 // this way directories are added to the listing if they match the pattern
2244 slash = strrchr(temp, '/');
2245 backslash = strrchr(temp, '\\');
2246 colon = strrchr(temp, ':');
2248 if (separator < slash)
2250 if (separator < backslash)
2251 separator = backslash;
2252 if (separator < colon)
2254 *((char *)separator) = 0;
2260 // get a directory listing and look at each name
2261 dpsnprintf(netpath, sizeof (netpath), "%s%s", searchpath->filename, basepath);
2262 if ((dir = listdirectory(netpath)))
2264 for (dirfile = dir;dirfile;dirfile = dirfile->next)
2266 dpsnprintf(temp, sizeof(temp), "%s%s", basepath, dirfile->text);
2267 if (matchpattern(temp, (char *)pattern, true))
2269 for (listtemp = liststart;listtemp;listtemp = listtemp->next)
2270 if (!strcmp(listtemp->text, temp))
2272 if (listtemp == NULL)
2274 listcurrent = stringlistappend(listcurrent, temp);
2275 if (liststart == NULL)
2276 liststart = listcurrent;
2278 Con_DPrintf("SearchDirFile: %s\n", temp);
2289 liststart = stringlistsort(liststart);
2292 for (listtemp = liststart;listtemp;listtemp = listtemp->next)
2295 numchars += (int)strlen(listtemp->text) + 1;
2297 search = (fssearch_t *)Z_Malloc(sizeof(fssearch_t) + numchars + numfiles * sizeof(char *));
2298 search->filenames = (char **)((char *)search + sizeof(fssearch_t));
2299 search->filenamesbuffer = (char *)((char *)search + sizeof(fssearch_t) + numfiles * sizeof(char *));
2300 search->numfilenames = (int)numfiles;
2303 for (listtemp = liststart;listtemp;listtemp = listtemp->next)
2305 search->filenames[numfiles] = search->filenamesbuffer + numchars;
2306 strcpy(search->filenames[numfiles], listtemp->text);
2308 numchars += (int)strlen(listtemp->text) + 1;
2311 stringlistfree(liststart);
2318 void FS_FreeSearch(fssearch_t *search)
2323 extern int con_linewidth;
2324 int FS_ListDirectory(const char *pattern, int oneperline)
2333 char linebuf[MAX_INPUTLINE];
2335 search = FS_Search(pattern, true, true);
2338 numfiles = search->numfilenames;
2341 // FIXME: the names could be added to one column list and then
2342 // gradually shifted into the next column if they fit, and then the
2343 // next to make a compact variable width listing but it's a lot more
2345 // find width for columns
2347 for (i = 0;i < numfiles;i++)
2349 l = (int)strlen(search->filenames[i]);
2350 if (columnwidth < l)
2353 // count the spacing character
2355 // calculate number of columns
2356 numcolumns = con_linewidth / columnwidth;
2357 // don't bother with the column printing if it's only one column
2358 if (numcolumns >= 2)
2360 numlines = (numfiles + numcolumns - 1) / numcolumns;
2361 for (i = 0;i < numlines;i++)
2364 for (k = 0;k < numcolumns;k++)
2366 l = i * numcolumns + k;
2369 name = search->filenames[l];
2370 for (j = 0;name[j] && linebufpos + 1 < (int)sizeof(linebuf);j++)
2371 linebuf[linebufpos++] = name[j];
2372 // space out name unless it's the last on the line
2373 if (k + 1 < numcolumns && l + 1 < numfiles)
2374 for (;j < columnwidth && linebufpos + 1 < (int)sizeof(linebuf);j++)
2375 linebuf[linebufpos++] = ' ';
2378 linebuf[linebufpos] = 0;
2379 Con_Printf("%s\n", linebuf);
2386 for (i = 0;i < numfiles;i++)
2387 Con_Printf("%s\n", search->filenames[i]);
2388 FS_FreeSearch(search);
2389 return (int)numfiles;
2392 static void FS_ListDirectoryCmd (const char* cmdname, int oneperline)
2394 const char *pattern;
2397 Con_Printf("usage:\n%s [path/pattern]\n", cmdname);
2400 if (Cmd_Argc() == 2)
2401 pattern = Cmd_Argv(1);
2404 if (!FS_ListDirectory(pattern, oneperline))
2405 Con_Print("No files found.\n");
2410 FS_ListDirectoryCmd("dir", true);
2415 FS_ListDirectoryCmd("ls", false);
2418 const char *FS_WhichPack(const char *filename)
2421 searchpath_t *sp = FS_FindFile(filename, &index, true);
2423 return sp->pack->filename;