4 Copyright (C) 2003-2006 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);
1070 strcpy(fs_basedir, "");
1073 // FIXME: is there a better way to find the directory outside the .app?
1074 if (strstr(com_argv[0], ".app/"))
1078 split = strstr(com_argv[0], ".app/");
1079 while (split > com_argv[0] && *split != '/')
1081 strlcpy(fs_basedir, com_argv[0], sizeof(fs_basedir));
1082 fs_basedir[split - com_argv[0]] = 0;
1090 // Overrides the system supplied base directory (under GAMENAME)
1091 // COMMANDLINEOPTION: Filesystem: -basedir <path> chooses what base directory the game data is in, inside this there should be a data directory for the game (for example id1)
1092 i = COM_CheckParm ("-basedir");
1093 if (i && i < com_argc-1)
1095 strlcpy (fs_basedir, com_argv[i+1], sizeof (fs_basedir));
1096 i = (int)strlen (fs_basedir);
1097 if (i > 0 && (fs_basedir[i-1] == '\\' || fs_basedir[i-1] == '/'))
1098 fs_basedir[i-1] = 0;
1101 // add a path separator to the end of the basedir if it lacks one
1102 if (fs_basedir[0] && fs_basedir[strlen(fs_basedir) - 1] != '/' && fs_basedir[strlen(fs_basedir) - 1] != '\\')
1103 strlcat(fs_basedir, "/", sizeof(fs_basedir));
1105 // -path <dir or packfile> [<dir or packfile>] ...
1106 // Fully specifies the exact search path, overriding the generated one
1107 // COMMANDLINEOPTION: Filesystem: -path <path ..> specifies the full search path manually, overriding the generated one, example: -path c:\quake\id1 c:\quake\pak0.pak c:\quake\pak1.pak (not recommended)
1108 i = COM_CheckParm ("-path");
1112 while (++i < com_argc)
1114 if (!com_argv[i] || com_argv[i][0] == '+' || com_argv[i][0] == '-')
1117 if(!FS_AddPack_Fullpath(com_argv[i], NULL, false))
1119 search = (searchpath_t *)Mem_Alloc(fs_mempool, sizeof(searchpath_t));
1120 strlcpy (search->filename, com_argv[i], sizeof (search->filename));
1121 search->next = fs_searchpaths;
1122 fs_searchpaths = search;
1128 // add the game-specific paths
1129 // gamedirname1 (typically id1)
1130 FS_AddGameHierarchy (gamedirname1);
1132 // add the game-specific path, if any
1136 FS_AddGameHierarchy (gamedirname2);
1139 // set the com_modname (reported in server info)
1140 strlcpy(com_modname, gamedirname1, sizeof(com_modname));
1143 // Adds basedir/gamedir as an override game
1144 // LordHavoc: now supports multiple -game directories
1145 for (i = 1;i < com_argc;i++)
1149 if (!strcmp (com_argv[i], "-game") && i < com_argc-1)
1153 FS_AddGameHierarchy (com_argv[i]);
1154 // update the com_modname
1155 strlcpy (com_modname, com_argv[i], sizeof (com_modname));
1159 // If "-condebug" is in the command line, remove the previous log file
1160 if (COM_CheckParm ("-condebug") != 0)
1161 unlink (va("%s/qconsole.log", fs_gamedir));
1164 void FS_Init_Commands(void)
1166 Cvar_RegisterVariable (&scr_screenshot_name);
1168 Cmd_AddCommand ("path", FS_Path_f, "print searchpath (game directories and archives)");
1169 Cmd_AddCommand ("dir", FS_Dir_f, "list files in searchpath matching an * filename pattern, one per line");
1170 Cmd_AddCommand ("ls", FS_Ls_f, "list files in searchpath matching an * filename pattern, multiple per line");
1172 // set the default screenshot name to either the mod name or the
1173 // gamemode screenshot name
1175 Cvar_SetQuick (&scr_screenshot_name, com_modname);
1177 Cvar_SetQuick (&scr_screenshot_name, gamescreenshotname);
1185 void FS_Shutdown (void)
1187 Mem_FreePool (&fs_mempool);
1191 ====================
1194 Internal function used to create a qfile_t and open the relevant non-packed file on disk
1195 ====================
1197 static qfile_t* FS_SysOpen (const char* filepath, const char* mode, qboolean nonblocking)
1203 // Parse the mode string
1212 opt = O_CREAT | O_TRUNC;
1216 opt = O_CREAT | O_APPEND;
1219 Con_Printf ("FS_SysOpen(%s, %s): invalid mode\n", filepath, mode);
1222 for (ind = 1; mode[ind] != '\0'; ind++)
1233 Con_Printf ("FS_SysOpen(%s, %s): unknown character in mode (%c)\n",
1234 filepath, mode, mode[ind]);
1241 file = (qfile_t *)Mem_Alloc (fs_mempool, sizeof (*file));
1242 memset (file, 0, sizeof (*file));
1245 file->handle = open (filepath, mod | opt, 0666);
1246 if (file->handle < 0)
1252 file->real_length = lseek (file->handle, 0, SEEK_END);
1254 // For files opened in append mode, we start at the end of the file
1256 file->position = file->real_length;
1258 lseek (file->handle, 0, SEEK_SET);
1268 Open a packed file using its package file descriptor
1271 qfile_t *FS_OpenPackedFile (pack_t* pack, int pack_ind)
1277 pfile = &pack->files[pack_ind];
1279 // If we don't have the true offset, get it now
1280 if (! (pfile->flags & PACKFILE_FLAG_TRUEOFFS))
1281 if (!PK3_GetTrueFileOffset (pfile, pack))
1284 // No Zlib DLL = no compressed files
1285 if (!zlib_dll && (pfile->flags & PACKFILE_FLAG_DEFLATED))
1287 Con_Printf("WARNING: can't open the compressed file %s\n"
1288 "You need the Zlib DLL to use compressed files\n",
1293 // LordHavoc: lseek affects all duplicates of a handle so we do it before
1294 // the dup() call to avoid having to close the dup_handle on error here
1295 if (lseek (pack->handle, pfile->offset, SEEK_SET) == -1)
1297 Con_Printf ("FS_OpenPackedFile: can't lseek to %s in %s (offset: %d)\n",
1298 pfile->name, pack->filename, pfile->offset);
1302 dup_handle = dup (pack->handle);
1305 Con_Printf ("FS_OpenPackedFile: can't dup package's handle (pack: %s)\n", pack->filename);
1309 file = (qfile_t *)Mem_Alloc (fs_mempool, sizeof (*file));
1310 memset (file, 0, sizeof (*file));
1311 file->handle = dup_handle;
1312 file->flags = QFILE_FLAG_PACKED;
1313 file->real_length = pfile->realsize;
1314 file->offset = pfile->offset;
1318 if (pfile->flags & PACKFILE_FLAG_DEFLATED)
1322 file->flags |= QFILE_FLAG_DEFLATED;
1324 // We need some more variables
1325 ztk = (ztoolkit_t *)Mem_Alloc (fs_mempool, sizeof (*ztk));
1327 ztk->comp_length = pfile->packsize;
1329 // Initialize zlib stream
1330 ztk->zstream.next_in = ztk->input;
1331 ztk->zstream.avail_in = 0;
1333 /* From Zlib's "unzip.c":
1335 * windowBits is passed < 0 to tell that there is no zlib header.
1336 * Note that in this case inflate *requires* an extra "dummy" byte
1337 * after the compressed stream in order to complete decompression and
1338 * return Z_STREAM_END.
1339 * In unzip, i don't wait absolutely Z_STREAM_END because I known the
1340 * size of both compressed and uncompressed data
1342 if (qz_inflateInit2 (&ztk->zstream, -MAX_WBITS) != Z_OK)
1344 Con_Printf ("FS_OpenPackedFile: inflate init error (file: %s)\n", pfile->name);
1350 ztk->zstream.next_out = file->buff;
1351 ztk->zstream.avail_out = sizeof (file->buff);
1360 ====================
1363 Return true if the path should be rejected due to one of the following:
1364 1: path elements that are non-portable
1365 2: path elements that would allow access to files outside the game directory,
1366 or are just not a good idea for a mod to be using.
1367 ====================
1369 int FS_CheckNastyPath (const char *path)
1371 // Windows: don't allow \ in filenames (windows-only), period.
1372 // (on Windows \ is a directory separator, but / is also supported)
1373 if (strstr(path, "\\"))
1374 return 1; // non-portable
1376 // Mac: don't allow Mac-only filenames - : is a directory separator
1377 // instead of /, but we rely on / working already, so there's no reason to
1378 // support a Mac-only path
1379 // Amiga and Windows: : tries to go to root of drive
1380 if (strstr(path, ":"))
1381 return 1; // non-portable attempt to go to root of drive
1383 // Amiga: // is parent directory
1384 if (strstr(path, "//"))
1385 return 1; // non-portable attempt to go to parent directory
1387 // all: don't allow going to current directory (./) or parent directory (../ or /../)
1388 if (strstr(path, "./"))
1389 return 2; // attempt to go outside the game directory
1391 // Windows and UNIXes: don't allow absolute paths
1393 return 2; // attempt to go outside the game directory
1395 // after all these checks we're pretty sure it's a / separated filename
1396 // and won't do much if any harm
1402 ====================
1405 Look for a file in the packages and in the filesystem
1407 Return the searchpath where the file was found (or NULL)
1408 and the file index in the package if relevant
1409 ====================
1411 static searchpath_t *FS_FindFile (const char *name, int* index, qboolean quiet)
1413 searchpath_t *search;
1416 // search through the path, one element at a time
1417 for (search = fs_searchpaths;search;search = search->next)
1419 // is the element a pak file?
1422 int (*strcmp_funct) (const char* str1, const char* str2);
1423 int left, right, middle;
1426 strcmp_funct = pak->ignorecase ? strcasecmp : strcmp;
1428 // Look for the file (binary search)
1430 right = pak->numfiles - 1;
1431 while (left <= right)
1435 middle = (left + right) / 2;
1436 diff = strcmp_funct (pak->files[middle].name, name);
1441 if (!quiet && developer.integer >= 10)
1442 Con_Printf("FS_FindFile: %s in %s\n",
1443 pak->files[middle].name, pak->filename);
1450 // If we're too far in the list
1459 char netpath[MAX_OSPATH];
1460 dpsnprintf(netpath, sizeof(netpath), "%s%s", search->filename, name);
1461 if (FS_SysFileExists (netpath))
1463 if (!quiet && developer.integer >= 10)
1464 Con_Printf("FS_FindFile: %s\n", netpath);
1473 if (!quiet && developer.integer >= 10)
1474 Con_Printf("FS_FindFile: can't find %s\n", name);
1486 Look for a file in the search paths and open it in read-only mode
1489 qfile_t *FS_OpenReadFile (const char *filename, qboolean quiet, qboolean nonblocking)
1491 searchpath_t *search;
1494 search = FS_FindFile (filename, &pack_ind, quiet);
1500 // Found in the filesystem?
1503 char path [MAX_OSPATH];
1504 dpsnprintf (path, sizeof (path), "%s%s", search->filename, filename);
1505 return FS_SysOpen (path, "rb", nonblocking);
1508 // So, we found it in a package...
1509 return FS_OpenPackedFile (search->pack, pack_ind);
1514 =============================================================================
1516 MAIN PUBLIC FUNCTIONS
1518 =============================================================================
1522 ====================
1525 Open a file. The syntax is the same as fopen
1526 ====================
1528 qfile_t* FS_Open (const char* filepath, const char* mode, qboolean quiet, qboolean nonblocking)
1530 if (FS_CheckNastyPath(filepath))
1532 Con_Printf("FS_Open(\"%s\", \"%s\", %s): nasty filename rejected\n", filepath, mode, quiet ? "true" : "false");
1536 // If the file is opened in "write", "append", or "read/write" mode
1537 if (mode[0] == 'w' || mode[0] == 'a' || strchr (mode, '+'))
1539 char real_path [MAX_OSPATH];
1541 // Open the file on disk directly
1542 dpsnprintf (real_path, sizeof (real_path), "%s/%s", fs_gamedir, filepath);
1544 // Create directories up to the file
1545 FS_CreatePath (real_path);
1547 return FS_SysOpen (real_path, mode, nonblocking);
1549 // Else, we look at the various search paths and open the file in read-only mode
1551 return FS_OpenReadFile (filepath, quiet, nonblocking);
1556 ====================
1560 ====================
1562 int FS_Close (qfile_t* file)
1564 if (close (file->handle))
1569 qz_inflateEnd (&file->ztk->zstream);
1570 Mem_Free (file->ztk);
1579 ====================
1582 Write "datasize" bytes into a file
1583 ====================
1585 fs_offset_t FS_Write (qfile_t* file, const void* data, size_t datasize)
1589 // If necessary, seek to the exact file position we're supposed to be
1590 if (file->buff_ind != file->buff_len)
1591 lseek (file->handle, file->buff_ind - file->buff_len, SEEK_CUR);
1593 // Purge cached data
1596 // Write the buffer and update the position
1597 result = write (file->handle, data, (fs_offset_t)datasize);
1598 file->position = lseek (file->handle, 0, SEEK_CUR);
1599 if (file->real_length < file->position)
1600 file->real_length = file->position;
1610 ====================
1613 Read up to "buffersize" bytes from a file
1614 ====================
1616 fs_offset_t FS_Read (qfile_t* file, void* buffer, size_t buffersize)
1618 fs_offset_t count, done;
1620 if (buffersize == 0)
1623 // Get rid of the ungetc character
1624 if (file->ungetc != EOF)
1626 ((char*)buffer)[0] = file->ungetc;
1634 // First, we copy as many bytes as we can from "buff"
1635 if (file->buff_ind < file->buff_len)
1637 count = file->buff_len - file->buff_ind;
1639 done += ((fs_offset_t)buffersize > count) ? count : (fs_offset_t)buffersize;
1640 memcpy (buffer, &file->buff[file->buff_ind], done);
1641 file->buff_ind += done;
1644 if (buffersize == 0)
1648 // NOTE: at this point, the read buffer is always empty
1650 // If the file isn't compressed
1651 if (! (file->flags & QFILE_FLAG_DEFLATED))
1655 // We must take care to not read after the end of the file
1656 count = file->real_length - file->position;
1658 // If we have a lot of data to get, put them directly into "buffer"
1659 if (buffersize > sizeof (file->buff) / 2)
1661 if (count > (fs_offset_t)buffersize)
1662 count = (fs_offset_t)buffersize;
1663 lseek (file->handle, file->offset + file->position, SEEK_SET);
1664 nb = read (file->handle, &((unsigned char*)buffer)[done], count);
1668 file->position += nb;
1670 // Purge cached data
1676 if (count > (fs_offset_t)sizeof (file->buff))
1677 count = (fs_offset_t)sizeof (file->buff);
1678 lseek (file->handle, file->offset + file->position, SEEK_SET);
1679 nb = read (file->handle, file->buff, count);
1682 file->buff_len = nb;
1683 file->position += nb;
1685 // Copy the requested data in "buffer" (as much as we can)
1686 count = (fs_offset_t)buffersize > file->buff_len ? file->buff_len : (fs_offset_t)buffersize;
1687 memcpy (&((unsigned char*)buffer)[done], file->buff, count);
1688 file->buff_ind = count;
1696 // If the file is compressed, it's more complicated...
1697 // We cycle through a few operations until we have read enough data
1698 while (buffersize > 0)
1700 ztoolkit_t *ztk = file->ztk;
1703 // NOTE: at this point, the read buffer is always empty
1705 // If "input" is also empty, we need to refill it
1706 if (ztk->in_ind == ztk->in_len)
1708 // If we are at the end of the file
1709 if (file->position == file->real_length)
1712 count = (fs_offset_t)(ztk->comp_length - ztk->in_position);
1713 if (count > (fs_offset_t)sizeof (ztk->input))
1714 count = (fs_offset_t)sizeof (ztk->input);
1715 lseek (file->handle, file->offset + (fs_offset_t)ztk->in_position, SEEK_SET);
1716 if (read (file->handle, ztk->input, count) != count)
1718 Con_Printf ("FS_Read: unexpected end of file\n");
1723 ztk->in_len = count;
1724 ztk->in_position += count;
1727 ztk->zstream.next_in = &ztk->input[ztk->in_ind];
1728 ztk->zstream.avail_in = (unsigned int)(ztk->in_len - ztk->in_ind);
1730 // Now that we are sure we have compressed data available, we need to determine
1731 // if it's better to inflate it in "file->buff" or directly in "buffer"
1733 // Inflate the data in "file->buff"
1734 if (buffersize < sizeof (file->buff) / 2)
1736 ztk->zstream.next_out = file->buff;
1737 ztk->zstream.avail_out = sizeof (file->buff);
1738 error = qz_inflate (&ztk->zstream, Z_SYNC_FLUSH);
1739 if (error != Z_OK && error != Z_STREAM_END)
1741 Con_Printf ("FS_Read: Can't inflate file\n");
1744 ztk->in_ind = ztk->in_len - ztk->zstream.avail_in;
1746 file->buff_len = (fs_offset_t)sizeof (file->buff) - ztk->zstream.avail_out;
1747 file->position += file->buff_len;
1749 // Copy the requested data in "buffer" (as much as we can)
1750 count = (fs_offset_t)buffersize > file->buff_len ? file->buff_len : (fs_offset_t)buffersize;
1751 memcpy (&((unsigned char*)buffer)[done], file->buff, count);
1752 file->buff_ind = count;
1755 // Else, we inflate directly in "buffer"
1758 ztk->zstream.next_out = &((unsigned char*)buffer)[done];
1759 ztk->zstream.avail_out = (unsigned int)buffersize;
1760 error = qz_inflate (&ztk->zstream, Z_SYNC_FLUSH);
1761 if (error != Z_OK && error != Z_STREAM_END)
1763 Con_Printf ("FS_Read: Can't inflate file\n");
1766 ztk->in_ind = ztk->in_len - ztk->zstream.avail_in;
1768 // How much data did it inflate?
1769 count = (fs_offset_t)(buffersize - ztk->zstream.avail_out);
1770 file->position += count;
1772 // Purge cached data
1777 buffersize -= count;
1785 ====================
1788 Print a string into a file
1789 ====================
1791 int FS_Print (qfile_t* file, const char *msg)
1793 return (int)FS_Write (file, msg, strlen (msg));
1797 ====================
1800 Print a string into a file
1801 ====================
1803 int FS_Printf(qfile_t* file, const char* format, ...)
1808 va_start (args, format);
1809 result = FS_VPrintf (file, format, args);
1817 ====================
1820 Print a string into a file
1821 ====================
1823 int FS_VPrintf (qfile_t* file, const char* format, va_list ap)
1826 fs_offset_t buff_size = MAX_INPUTLINE;
1831 tempbuff = (char *)Mem_Alloc (tempmempool, buff_size);
1832 len = dpvsnprintf (tempbuff, buff_size, format, ap);
1833 if (len >= 0 && len < buff_size)
1835 Mem_Free (tempbuff);
1839 len = write (file->handle, tempbuff, len);
1840 Mem_Free (tempbuff);
1847 ====================
1850 Get the next character of a file
1851 ====================
1853 int FS_Getc (qfile_t* file)
1857 if (FS_Read (file, &c, 1) != 1)
1865 ====================
1868 Put a character back into the read buffer (only supports one character!)
1869 ====================
1871 int FS_UnGetc (qfile_t* file, unsigned char c)
1873 // If there's already a character waiting to be read
1874 if (file->ungetc != EOF)
1883 ====================
1886 Move the position index in a file
1887 ====================
1889 int FS_Seek (qfile_t* file, fs_offset_t offset, int whence)
1892 unsigned char* buffer;
1893 fs_offset_t buffersize;
1895 // Compute the file offset
1899 offset += file->position - file->buff_len + file->buff_ind;
1906 offset += file->real_length;
1912 if (offset < 0 || offset > (long) file->real_length)
1915 // If we have the data in our read buffer, we don't need to actually seek
1916 if (file->position - file->buff_len <= offset && offset <= file->position)
1918 file->buff_ind = offset + file->buff_len - file->position;
1922 // Purge cached data
1925 // Unpacked or uncompressed files can seek directly
1926 if (! (file->flags & QFILE_FLAG_DEFLATED))
1928 if (lseek (file->handle, file->offset + offset, SEEK_SET) == -1)
1930 file->position = offset;
1934 // Seeking in compressed files is more a hack than anything else,
1935 // but we need to support it, so here we go.
1938 // If we have to go back in the file, we need to restart from the beginning
1939 if (offset <= file->position)
1943 ztk->in_position = 0;
1945 lseek (file->handle, file->offset, SEEK_SET);
1947 // Reset the Zlib stream
1948 ztk->zstream.next_in = ztk->input;
1949 ztk->zstream.avail_in = 0;
1950 qz_inflateReset (&ztk->zstream);
1953 // We need a big buffer to force inflating into it directly
1954 buffersize = 2 * sizeof (file->buff);
1955 buffer = (unsigned char *)Mem_Alloc (tempmempool, buffersize);
1957 // Skip all data until we reach the requested offset
1958 while (offset > file->position)
1960 fs_offset_t diff = offset - file->position;
1961 fs_offset_t count, len;
1963 count = (diff > buffersize) ? buffersize : diff;
1964 len = FS_Read (file, buffer, count);
1978 ====================
1981 Give the current position in a file
1982 ====================
1984 fs_offset_t FS_Tell (qfile_t* file)
1986 return file->position - file->buff_len + file->buff_ind;
1991 ====================
1994 Erases any buffered input or output data
1995 ====================
1997 void FS_Purge (qfile_t* file)
2009 Filename are relative to the quake directory.
2010 Always appends a 0 byte.
2013 unsigned char *FS_LoadFile (const char *path, mempool_t *pool, qboolean quiet, fs_offset_t *filesizepointer)
2016 unsigned char *buf = NULL;
2017 fs_offset_t filesize = 0;
2019 file = FS_Open (path, "rb", quiet, false);
2022 filesize = file->real_length;
2023 buf = (unsigned char *)Mem_Alloc (pool, filesize + 1);
2024 buf[filesize] = '\0';
2025 FS_Read (file, buf, filesize);
2029 if (filesizepointer)
2030 *filesizepointer = filesize;
2039 The filename will be prefixed by the current game directory
2042 qboolean FS_WriteFile (const char *filename, void *data, fs_offset_t len)
2046 file = FS_Open (filename, "wb", false, false);
2049 Con_Printf("FS_WriteFile: failed on %s\n", filename);
2053 Con_DPrintf("FS_WriteFile: %s\n", filename);
2054 FS_Write (file, data, len);
2061 =============================================================================
2063 OTHERS PUBLIC FUNCTIONS
2065 =============================================================================
2073 void FS_StripExtension (const char *in, char *out, size_t size_out)
2080 while (*in && size_out > 1)
2084 else if (*in == '/' || *in == '\\' || *in == ':')
2101 void FS_DefaultExtension (char *path, const char *extension, size_t size_path)
2105 // if path doesn't have a .EXT, append extension
2106 // (extension should include the .)
2107 src = path + strlen(path) - 1;
2109 while (*src != '/' && src != path)
2112 return; // it has an extension
2116 strlcat (path, extension, size_path);
2124 Look for a file in the packages and in the filesystem
2127 qboolean FS_FileExists (const char *filename)
2129 return (FS_FindFile (filename, NULL, true) != NULL);
2137 Look for a file in the filesystem only
2140 qboolean FS_SysFileExists (const char *path)
2145 // TODO: use another function instead, to avoid opening the file
2146 desc = open (path, O_RDONLY | O_BINARY);
2155 if (stat (path,&buf) == -1)
2162 void FS_mkdir (const char *path)
2175 Allocate and fill a search structure with information on matching filenames.
2178 fssearch_t *FS_Search(const char *pattern, int caseinsensitive, int quiet)
2181 searchpath_t *searchpath;
2183 int i, basepathlength, numfiles, numchars;
2184 stringlist_t *dir, *dirfile, *liststart, *listcurrent, *listtemp;
2185 const char *slash, *backslash, *colon, *separator;
2187 char netpath[MAX_OSPATH];
2188 char temp[MAX_OSPATH];
2190 for (i = 0;pattern[i] == '.' || pattern[i] == ':' || pattern[i] == '/' || pattern[i] == '\\';i++)
2195 Con_Printf("Don't use punctuation at the beginning of a search pattern!\n");
2203 slash = strrchr(pattern, '/');
2204 backslash = strrchr(pattern, '\\');
2205 colon = strrchr(pattern, ':');
2206 separator = max(slash, backslash);
2207 separator = max(separator, colon);
2208 basepathlength = separator ? (separator + 1 - pattern) : 0;
2209 basepath = (char *)Mem_Alloc (tempmempool, basepathlength + 1);
2211 memcpy(basepath, pattern, basepathlength);
2212 basepath[basepathlength] = 0;
2214 // search through the path, one element at a time
2215 for (searchpath = fs_searchpaths;searchpath;searchpath = searchpath->next)
2217 // is the element a pak file?
2218 if (searchpath->pack)
2220 // look through all the pak file elements
2221 pak = searchpath->pack;
2222 for (i = 0;i < pak->numfiles;i++)
2224 strcpy(temp, pak->files[i].name);
2227 if (matchpattern(temp, (char *)pattern, true))
2229 for (listtemp = liststart;listtemp;listtemp = listtemp->next)
2230 if (!strcmp(listtemp->text, temp))
2232 if (listtemp == NULL)
2234 listcurrent = stringlistappend(listcurrent, temp);
2235 if (liststart == NULL)
2236 liststart = listcurrent;
2238 Con_DPrintf("SearchPackFile: %s : %s\n", pak->filename, temp);
2241 // strip off one path element at a time until empty
2242 // this way directories are added to the listing if they match the pattern
2243 slash = strrchr(temp, '/');
2244 backslash = strrchr(temp, '\\');
2245 colon = strrchr(temp, ':');
2247 if (separator < slash)
2249 if (separator < backslash)
2250 separator = backslash;
2251 if (separator < colon)
2253 *((char *)separator) = 0;
2259 // get a directory listing and look at each name
2260 dpsnprintf(netpath, sizeof (netpath), "%s%s", searchpath->filename, basepath);
2261 if ((dir = listdirectory(netpath)))
2263 for (dirfile = dir;dirfile;dirfile = dirfile->next)
2265 dpsnprintf(temp, sizeof(temp), "%s%s", basepath, dirfile->text);
2266 if (matchpattern(temp, (char *)pattern, true))
2268 for (listtemp = liststart;listtemp;listtemp = listtemp->next)
2269 if (!strcmp(listtemp->text, temp))
2271 if (listtemp == NULL)
2273 listcurrent = stringlistappend(listcurrent, temp);
2274 if (liststart == NULL)
2275 liststart = listcurrent;
2277 Con_DPrintf("SearchDirFile: %s\n", temp);
2288 liststart = stringlistsort(liststart);
2291 for (listtemp = liststart;listtemp;listtemp = listtemp->next)
2294 numchars += (int)strlen(listtemp->text) + 1;
2296 search = (fssearch_t *)Z_Malloc(sizeof(fssearch_t) + numchars + numfiles * sizeof(char *));
2297 search->filenames = (char **)((char *)search + sizeof(fssearch_t));
2298 search->filenamesbuffer = (char *)((char *)search + sizeof(fssearch_t) + numfiles * sizeof(char *));
2299 search->numfilenames = (int)numfiles;
2302 for (listtemp = liststart;listtemp;listtemp = listtemp->next)
2304 search->filenames[numfiles] = search->filenamesbuffer + numchars;
2305 strcpy(search->filenames[numfiles], listtemp->text);
2307 numchars += (int)strlen(listtemp->text) + 1;
2310 stringlistfree(liststart);
2317 void FS_FreeSearch(fssearch_t *search)
2322 extern int con_linewidth;
2323 int FS_ListDirectory(const char *pattern, int oneperline)
2332 char linebuf[MAX_INPUTLINE];
2334 search = FS_Search(pattern, true, true);
2337 numfiles = search->numfilenames;
2340 // FIXME: the names could be added to one column list and then
2341 // gradually shifted into the next column if they fit, and then the
2342 // next to make a compact variable width listing but it's a lot more
2344 // find width for columns
2346 for (i = 0;i < numfiles;i++)
2348 l = (int)strlen(search->filenames[i]);
2349 if (columnwidth < l)
2352 // count the spacing character
2354 // calculate number of columns
2355 numcolumns = con_linewidth / columnwidth;
2356 // don't bother with the column printing if it's only one column
2357 if (numcolumns >= 2)
2359 numlines = (numfiles + numcolumns - 1) / numcolumns;
2360 for (i = 0;i < numlines;i++)
2363 for (k = 0;k < numcolumns;k++)
2365 l = i * numcolumns + k;
2368 name = search->filenames[l];
2369 for (j = 0;name[j] && linebufpos + 1 < (int)sizeof(linebuf);j++)
2370 linebuf[linebufpos++] = name[j];
2371 // space out name unless it's the last on the line
2372 if (k + 1 < numcolumns && l + 1 < numfiles)
2373 for (;j < columnwidth && linebufpos + 1 < (int)sizeof(linebuf);j++)
2374 linebuf[linebufpos++] = ' ';
2377 linebuf[linebufpos] = 0;
2378 Con_Printf("%s\n", linebuf);
2385 for (i = 0;i < numfiles;i++)
2386 Con_Printf("%s\n", search->filenames[i]);
2387 FS_FreeSearch(search);
2388 return (int)numfiles;
2391 static void FS_ListDirectoryCmd (const char* cmdname, int oneperline)
2393 const char *pattern;
2396 Con_Printf("usage:\n%s [path/pattern]\n", cmdname);
2399 if (Cmd_Argc() == 2)
2400 pattern = Cmd_Argv(1);
2403 if (!FS_ListDirectory(pattern, oneperline))
2404 Con_Print("No files found.\n");
2409 FS_ListDirectoryCmd("dir", true);
2414 FS_ListDirectoryCmd("ls", false);
2417 const char *FS_WhichPack(const char *filename)
2420 searchpath_t *sp = FS_FindFile(filename, &index, true);
2422 return sp->pack->filename;