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_basedir, "");
1065 strcpy(fs_gamedir, "");
1068 // FIXME: is there a better way to find the directory outside the .app?
1069 if (strstr(com_argv[0], ".app/"))
1073 split = strstr(com_argv[0], ".app/");
1074 while (split > com_argv[0] && *split != '/')
1076 strlcpy(fs_basedir, com_argv[0], sizeof(fs_basedir));
1077 fs_basedir[split - com_argv[0]] = 0;
1084 // Overrides the system supplied base directory (under GAMENAME)
1085 // COMMANDLINEOPTION: Filesystem: -basedir <path> chooses what base directory the game data is in, inside this there should be a data directory for the game (for example id1)
1086 i = COM_CheckParm ("-basedir");
1087 if (i && i < com_argc-1)
1089 strlcpy (fs_basedir, com_argv[i+1], sizeof (fs_basedir));
1090 i = (int)strlen (fs_basedir);
1091 if (i > 0 && (fs_basedir[i-1] == '\\' || fs_basedir[i-1] == '/'))
1092 fs_basedir[i-1] = 0;
1095 // add a path separator to the end of the basedir if it lacks one
1096 if (fs_basedir[0] && fs_basedir[strlen(fs_basedir) - 1] != '/' && fs_basedir[strlen(fs_basedir) - 1] != '\\')
1097 strlcat(fs_basedir, "/", sizeof(fs_basedir));
1099 // -path <dir or packfile> [<dir or packfile>] ...
1100 // Fully specifies the exact search path, overriding the generated one
1101 // COMMANDLINEOPTION: Filesystem: -path <path ..> specifies the full search path manually, overriding the generated one, example: -path c:\quake\id1 c:\quake\pak0.pak c:\quake\pak1.pak (not recommended)
1102 i = COM_CheckParm ("-path");
1106 while (++i < com_argc)
1108 if (!com_argv[i] || com_argv[i][0] == '+' || com_argv[i][0] == '-')
1111 if(!FS_AddPack_Fullpath(com_argv[i], NULL, false))
1113 search = (searchpath_t *)Mem_Alloc(fs_mempool, sizeof(searchpath_t));
1114 strlcpy (search->filename, com_argv[i], sizeof (search->filename));
1115 search->next = fs_searchpaths;
1116 fs_searchpaths = search;
1122 // add the game-specific paths
1123 // gamedirname1 (typically id1)
1124 FS_AddGameHierarchy (gamedirname1);
1126 // add the game-specific path, if any
1130 FS_AddGameHierarchy (gamedirname2);
1133 // set the com_modname (reported in server info)
1134 strlcpy(com_modname, gamedirname1, sizeof(com_modname));
1137 // Adds basedir/gamedir as an override game
1138 // LordHavoc: now supports multiple -game directories
1139 for (i = 1;i < com_argc;i++)
1143 if (!strcmp (com_argv[i], "-game") && i < com_argc-1)
1147 FS_AddGameHierarchy (com_argv[i]);
1148 // update the com_modname
1149 strlcpy (com_modname, com_argv[i], sizeof (com_modname));
1153 // If "-condebug" is in the command line, remove the previous log file
1154 if (COM_CheckParm ("-condebug") != 0)
1155 unlink (va("%s/qconsole.log", fs_gamedir));
1158 void FS_Init_Commands(void)
1160 Cvar_RegisterVariable (&scr_screenshot_name);
1162 Cmd_AddCommand ("path", FS_Path_f, "print searchpath (game directories and archives)");
1163 Cmd_AddCommand ("dir", FS_Dir_f, "list files in searchpath matching an * filename pattern, one per line");
1164 Cmd_AddCommand ("ls", FS_Ls_f, "list files in searchpath matching an * filename pattern, multiple per line");
1166 // set the default screenshot name to either the mod name or the
1167 // gamemode screenshot name
1169 Cvar_SetQuick (&scr_screenshot_name, com_modname);
1171 Cvar_SetQuick (&scr_screenshot_name, gamescreenshotname);
1179 void FS_Shutdown (void)
1181 Mem_FreePool (&fs_mempool);
1185 ====================
1188 Internal function used to create a qfile_t and open the relevant non-packed file on disk
1189 ====================
1191 static qfile_t* FS_SysOpen (const char* filepath, const char* mode, qboolean nonblocking)
1197 // Parse the mode string
1206 opt = O_CREAT | O_TRUNC;
1210 opt = O_CREAT | O_APPEND;
1213 Con_Printf ("FS_SysOpen(%s, %s): invalid mode\n", filepath, mode);
1216 for (ind = 1; mode[ind] != '\0'; ind++)
1227 Con_Printf ("FS_SysOpen(%s, %s): unknown character in mode (%c)\n",
1228 filepath, mode, mode[ind]);
1235 file = (qfile_t *)Mem_Alloc (fs_mempool, sizeof (*file));
1236 memset (file, 0, sizeof (*file));
1239 file->handle = open (filepath, mod | opt, 0666);
1240 if (file->handle < 0)
1246 file->real_length = lseek (file->handle, 0, SEEK_END);
1248 // For files opened in append mode, we start at the end of the file
1250 file->position = file->real_length;
1252 lseek (file->handle, 0, SEEK_SET);
1262 Open a packed file using its package file descriptor
1265 qfile_t *FS_OpenPackedFile (pack_t* pack, int pack_ind)
1271 pfile = &pack->files[pack_ind];
1273 // If we don't have the true offset, get it now
1274 if (! (pfile->flags & PACKFILE_FLAG_TRUEOFFS))
1275 if (!PK3_GetTrueFileOffset (pfile, pack))
1278 // No Zlib DLL = no compressed files
1279 if (!zlib_dll && (pfile->flags & PACKFILE_FLAG_DEFLATED))
1281 Con_Printf("WARNING: can't open the compressed file %s\n"
1282 "You need the Zlib DLL to use compressed files\n",
1287 // LordHavoc: lseek affects all duplicates of a handle so we do it before
1288 // the dup() call to avoid having to close the dup_handle on error here
1289 if (lseek (pack->handle, pfile->offset, SEEK_SET) == -1)
1291 Con_Printf ("FS_OpenPackedFile: can't lseek to %s in %s (offset: %d)\n",
1292 pfile->name, pack->filename, pfile->offset);
1296 dup_handle = dup (pack->handle);
1299 Con_Printf ("FS_OpenPackedFile: can't dup package's handle (pack: %s)\n", pack->filename);
1303 file = (qfile_t *)Mem_Alloc (fs_mempool, sizeof (*file));
1304 memset (file, 0, sizeof (*file));
1305 file->handle = dup_handle;
1306 file->flags = QFILE_FLAG_PACKED;
1307 file->real_length = pfile->realsize;
1308 file->offset = pfile->offset;
1312 if (pfile->flags & PACKFILE_FLAG_DEFLATED)
1316 file->flags |= QFILE_FLAG_DEFLATED;
1318 // We need some more variables
1319 ztk = (ztoolkit_t *)Mem_Alloc (fs_mempool, sizeof (*ztk));
1321 ztk->comp_length = pfile->packsize;
1323 // Initialize zlib stream
1324 ztk->zstream.next_in = ztk->input;
1325 ztk->zstream.avail_in = 0;
1327 /* From Zlib's "unzip.c":
1329 * windowBits is passed < 0 to tell that there is no zlib header.
1330 * Note that in this case inflate *requires* an extra "dummy" byte
1331 * after the compressed stream in order to complete decompression and
1332 * return Z_STREAM_END.
1333 * In unzip, i don't wait absolutely Z_STREAM_END because I known the
1334 * size of both compressed and uncompressed data
1336 if (qz_inflateInit2 (&ztk->zstream, -MAX_WBITS) != Z_OK)
1338 Con_Printf ("FS_OpenPackedFile: inflate init error (file: %s)\n", pfile->name);
1344 ztk->zstream.next_out = file->buff;
1345 ztk->zstream.avail_out = sizeof (file->buff);
1354 ====================
1357 Return true if the path should be rejected due to one of the following:
1358 1: path elements that are non-portable
1359 2: path elements that would allow access to files outside the game directory,
1360 or are just not a good idea for a mod to be using.
1361 ====================
1363 int FS_CheckNastyPath (const char *path)
1365 // Windows: don't allow \ in filenames (windows-only), period.
1366 // (on Windows \ is a directory separator, but / is also supported)
1367 if (strstr(path, "\\"))
1368 return 1; // non-portable
1370 // Mac: don't allow Mac-only filenames - : is a directory separator
1371 // instead of /, but we rely on / working already, so there's no reason to
1372 // support a Mac-only path
1373 // Amiga and Windows: : tries to go to root of drive
1374 if (strstr(path, ":"))
1375 return 1; // non-portable attempt to go to root of drive
1377 // Amiga: // is parent directory
1378 if (strstr(path, "//"))
1379 return 1; // non-portable attempt to go to parent directory
1381 // all: don't allow going to current directory (./) or parent directory (../ or /../)
1382 if (strstr(path, "./"))
1383 return 2; // attempt to go outside the game directory
1385 // Windows and UNIXes: don't allow absolute paths
1387 return 2; // attempt to go outside the game directory
1389 // after all these checks we're pretty sure it's a / separated filename
1390 // and won't do much if any harm
1396 ====================
1399 Look for a file in the packages and in the filesystem
1401 Return the searchpath where the file was found (or NULL)
1402 and the file index in the package if relevant
1403 ====================
1405 static searchpath_t *FS_FindFile (const char *name, int* index, qboolean quiet)
1407 searchpath_t *search;
1410 // search through the path, one element at a time
1411 for (search = fs_searchpaths;search;search = search->next)
1413 // is the element a pak file?
1416 int (*strcmp_funct) (const char* str1, const char* str2);
1417 int left, right, middle;
1420 strcmp_funct = pak->ignorecase ? strcasecmp : strcmp;
1422 // Look for the file (binary search)
1424 right = pak->numfiles - 1;
1425 while (left <= right)
1429 middle = (left + right) / 2;
1430 diff = strcmp_funct (pak->files[middle].name, name);
1435 if (!quiet && developer.integer >= 10)
1436 Con_Printf("FS_FindFile: %s in %s\n",
1437 pak->files[middle].name, pak->filename);
1444 // If we're too far in the list
1453 char netpath[MAX_OSPATH];
1454 dpsnprintf(netpath, sizeof(netpath), "%s%s", search->filename, name);
1455 if (FS_SysFileExists (netpath))
1457 if (!quiet && developer.integer >= 10)
1458 Con_Printf("FS_FindFile: %s\n", netpath);
1467 if (!quiet && developer.integer >= 10)
1468 Con_Printf("FS_FindFile: can't find %s\n", name);
1480 Look for a file in the search paths and open it in read-only mode
1483 qfile_t *FS_OpenReadFile (const char *filename, qboolean quiet, qboolean nonblocking)
1485 searchpath_t *search;
1488 search = FS_FindFile (filename, &pack_ind, quiet);
1494 // Found in the filesystem?
1497 char path [MAX_OSPATH];
1498 dpsnprintf (path, sizeof (path), "%s%s", search->filename, filename);
1499 return FS_SysOpen (path, "rb", nonblocking);
1502 // So, we found it in a package...
1503 return FS_OpenPackedFile (search->pack, pack_ind);
1508 =============================================================================
1510 MAIN PUBLIC FUNCTIONS
1512 =============================================================================
1516 ====================
1519 Open a file. The syntax is the same as fopen
1520 ====================
1522 qfile_t* FS_Open (const char* filepath, const char* mode, qboolean quiet, qboolean nonblocking)
1524 if (FS_CheckNastyPath(filepath))
1526 Con_Printf("FS_Open(\"%s\", \"%s\", %s): nasty filename rejected\n", filepath, mode, quiet ? "true" : "false");
1530 // If the file is opened in "write", "append", or "read/write" mode
1531 if (mode[0] == 'w' || mode[0] == 'a' || strchr (mode, '+'))
1533 char real_path [MAX_OSPATH];
1535 // Open the file on disk directly
1536 dpsnprintf (real_path, sizeof (real_path), "%s/%s", fs_gamedir, filepath);
1538 // Create directories up to the file
1539 FS_CreatePath (real_path);
1541 return FS_SysOpen (real_path, mode, nonblocking);
1543 // Else, we look at the various search paths and open the file in read-only mode
1545 return FS_OpenReadFile (filepath, quiet, nonblocking);
1550 ====================
1554 ====================
1556 int FS_Close (qfile_t* file)
1558 if (close (file->handle))
1563 qz_inflateEnd (&file->ztk->zstream);
1564 Mem_Free (file->ztk);
1573 ====================
1576 Write "datasize" bytes into a file
1577 ====================
1579 fs_offset_t FS_Write (qfile_t* file, const void* data, size_t datasize)
1583 // If necessary, seek to the exact file position we're supposed to be
1584 if (file->buff_ind != file->buff_len)
1585 lseek (file->handle, file->buff_ind - file->buff_len, SEEK_CUR);
1587 // Purge cached data
1590 // Write the buffer and update the position
1591 result = write (file->handle, data, (fs_offset_t)datasize);
1592 file->position = lseek (file->handle, 0, SEEK_CUR);
1593 if (file->real_length < file->position)
1594 file->real_length = file->position;
1604 ====================
1607 Read up to "buffersize" bytes from a file
1608 ====================
1610 fs_offset_t FS_Read (qfile_t* file, void* buffer, size_t buffersize)
1612 fs_offset_t count, done;
1614 if (buffersize == 0)
1617 // Get rid of the ungetc character
1618 if (file->ungetc != EOF)
1620 ((char*)buffer)[0] = file->ungetc;
1628 // First, we copy as many bytes as we can from "buff"
1629 if (file->buff_ind < file->buff_len)
1631 count = file->buff_len - file->buff_ind;
1633 done += ((fs_offset_t)buffersize > count) ? count : (fs_offset_t)buffersize;
1634 memcpy (buffer, &file->buff[file->buff_ind], done);
1635 file->buff_ind += done;
1638 if (buffersize == 0)
1642 // NOTE: at this point, the read buffer is always empty
1644 // If the file isn't compressed
1645 if (! (file->flags & QFILE_FLAG_DEFLATED))
1649 // We must take care to not read after the end of the file
1650 count = file->real_length - file->position;
1652 // If we have a lot of data to get, put them directly into "buffer"
1653 if (buffersize > sizeof (file->buff) / 2)
1655 if (count > (fs_offset_t)buffersize)
1656 count = (fs_offset_t)buffersize;
1657 lseek (file->handle, file->offset + file->position, SEEK_SET);
1658 nb = read (file->handle, &((unsigned char*)buffer)[done], count);
1662 file->position += nb;
1664 // Purge cached data
1670 if (count > (fs_offset_t)sizeof (file->buff))
1671 count = (fs_offset_t)sizeof (file->buff);
1672 lseek (file->handle, file->offset + file->position, SEEK_SET);
1673 nb = read (file->handle, file->buff, count);
1676 file->buff_len = nb;
1677 file->position += nb;
1679 // Copy the requested data in "buffer" (as much as we can)
1680 count = (fs_offset_t)buffersize > file->buff_len ? file->buff_len : (fs_offset_t)buffersize;
1681 memcpy (&((unsigned char*)buffer)[done], file->buff, count);
1682 file->buff_ind = count;
1690 // If the file is compressed, it's more complicated...
1691 // We cycle through a few operations until we have read enough data
1692 while (buffersize > 0)
1694 ztoolkit_t *ztk = file->ztk;
1697 // NOTE: at this point, the read buffer is always empty
1699 // If "input" is also empty, we need to refill it
1700 if (ztk->in_ind == ztk->in_len)
1702 // If we are at the end of the file
1703 if (file->position == file->real_length)
1706 count = (fs_offset_t)(ztk->comp_length - ztk->in_position);
1707 if (count > (fs_offset_t)sizeof (ztk->input))
1708 count = (fs_offset_t)sizeof (ztk->input);
1709 lseek (file->handle, file->offset + (fs_offset_t)ztk->in_position, SEEK_SET);
1710 if (read (file->handle, ztk->input, count) != count)
1712 Con_Printf ("FS_Read: unexpected end of file\n");
1717 ztk->in_len = count;
1718 ztk->in_position += count;
1721 ztk->zstream.next_in = &ztk->input[ztk->in_ind];
1722 ztk->zstream.avail_in = (unsigned int)(ztk->in_len - ztk->in_ind);
1724 // Now that we are sure we have compressed data available, we need to determine
1725 // if it's better to inflate it in "file->buff" or directly in "buffer"
1727 // Inflate the data in "file->buff"
1728 if (buffersize < sizeof (file->buff) / 2)
1730 ztk->zstream.next_out = file->buff;
1731 ztk->zstream.avail_out = sizeof (file->buff);
1732 error = qz_inflate (&ztk->zstream, Z_SYNC_FLUSH);
1733 if (error != Z_OK && error != Z_STREAM_END)
1735 Con_Printf ("FS_Read: Can't inflate file\n");
1738 ztk->in_ind = ztk->in_len - ztk->zstream.avail_in;
1740 file->buff_len = (fs_offset_t)sizeof (file->buff) - ztk->zstream.avail_out;
1741 file->position += file->buff_len;
1743 // Copy the requested data in "buffer" (as much as we can)
1744 count = (fs_offset_t)buffersize > file->buff_len ? file->buff_len : (fs_offset_t)buffersize;
1745 memcpy (&((unsigned char*)buffer)[done], file->buff, count);
1746 file->buff_ind = count;
1749 // Else, we inflate directly in "buffer"
1752 ztk->zstream.next_out = &((unsigned char*)buffer)[done];
1753 ztk->zstream.avail_out = (unsigned int)buffersize;
1754 error = qz_inflate (&ztk->zstream, Z_SYNC_FLUSH);
1755 if (error != Z_OK && error != Z_STREAM_END)
1757 Con_Printf ("FS_Read: Can't inflate file\n");
1760 ztk->in_ind = ztk->in_len - ztk->zstream.avail_in;
1762 // How much data did it inflate?
1763 count = (fs_offset_t)(buffersize - ztk->zstream.avail_out);
1764 file->position += count;
1766 // Purge cached data
1771 buffersize -= count;
1779 ====================
1782 Print a string into a file
1783 ====================
1785 int FS_Print (qfile_t* file, const char *msg)
1787 return (int)FS_Write (file, msg, strlen (msg));
1791 ====================
1794 Print a string into a file
1795 ====================
1797 int FS_Printf(qfile_t* file, const char* format, ...)
1802 va_start (args, format);
1803 result = FS_VPrintf (file, format, args);
1811 ====================
1814 Print a string into a file
1815 ====================
1817 int FS_VPrintf (qfile_t* file, const char* format, va_list ap)
1820 fs_offset_t buff_size = MAX_INPUTLINE;
1825 tempbuff = (char *)Mem_Alloc (tempmempool, buff_size);
1826 len = dpvsnprintf (tempbuff, buff_size, format, ap);
1827 if (len >= 0 && len < buff_size)
1829 Mem_Free (tempbuff);
1833 len = write (file->handle, tempbuff, len);
1834 Mem_Free (tempbuff);
1841 ====================
1844 Get the next character of a file
1845 ====================
1847 int FS_Getc (qfile_t* file)
1851 if (FS_Read (file, &c, 1) != 1)
1859 ====================
1862 Put a character back into the read buffer (only supports one character!)
1863 ====================
1865 int FS_UnGetc (qfile_t* file, unsigned char c)
1867 // If there's already a character waiting to be read
1868 if (file->ungetc != EOF)
1877 ====================
1880 Move the position index in a file
1881 ====================
1883 int FS_Seek (qfile_t* file, fs_offset_t offset, int whence)
1886 unsigned char* buffer;
1887 fs_offset_t buffersize;
1889 // Compute the file offset
1893 offset += file->position - file->buff_len + file->buff_ind;
1900 offset += file->real_length;
1906 if (offset < 0 || offset > (long) file->real_length)
1909 // If we have the data in our read buffer, we don't need to actually seek
1910 if (file->position - file->buff_len <= offset && offset <= file->position)
1912 file->buff_ind = offset + file->buff_len - file->position;
1916 // Purge cached data
1919 // Unpacked or uncompressed files can seek directly
1920 if (! (file->flags & QFILE_FLAG_DEFLATED))
1922 if (lseek (file->handle, file->offset + offset, SEEK_SET) == -1)
1924 file->position = offset;
1928 // Seeking in compressed files is more a hack than anything else,
1929 // but we need to support it, so here we go.
1932 // If we have to go back in the file, we need to restart from the beginning
1933 if (offset <= file->position)
1937 ztk->in_position = 0;
1939 lseek (file->handle, file->offset, SEEK_SET);
1941 // Reset the Zlib stream
1942 ztk->zstream.next_in = ztk->input;
1943 ztk->zstream.avail_in = 0;
1944 qz_inflateReset (&ztk->zstream);
1947 // We need a big buffer to force inflating into it directly
1948 buffersize = 2 * sizeof (file->buff);
1949 buffer = (unsigned char *)Mem_Alloc (tempmempool, buffersize);
1951 // Skip all data until we reach the requested offset
1952 while (offset > file->position)
1954 fs_offset_t diff = offset - file->position;
1955 fs_offset_t count, len;
1957 count = (diff > buffersize) ? buffersize : diff;
1958 len = FS_Read (file, buffer, count);
1972 ====================
1975 Give the current position in a file
1976 ====================
1978 fs_offset_t FS_Tell (qfile_t* file)
1980 return file->position - file->buff_len + file->buff_ind;
1985 ====================
1988 Erases any buffered input or output data
1989 ====================
1991 void FS_Purge (qfile_t* file)
2003 Filename are relative to the quake directory.
2004 Always appends a 0 byte.
2007 unsigned char *FS_LoadFile (const char *path, mempool_t *pool, qboolean quiet, fs_offset_t *filesizepointer)
2010 unsigned char *buf = NULL;
2011 fs_offset_t filesize = 0;
2013 file = FS_Open (path, "rb", quiet, false);
2016 filesize = file->real_length;
2017 buf = (unsigned char *)Mem_Alloc (pool, filesize + 1);
2018 buf[filesize] = '\0';
2019 FS_Read (file, buf, filesize);
2023 if (filesizepointer)
2024 *filesizepointer = filesize;
2033 The filename will be prefixed by the current game directory
2036 qboolean FS_WriteFile (const char *filename, void *data, fs_offset_t len)
2040 file = FS_Open (filename, "wb", false, false);
2043 Con_Printf("FS_WriteFile: failed on %s\n", filename);
2047 Con_DPrintf("FS_WriteFile: %s\n", filename);
2048 FS_Write (file, data, len);
2055 =============================================================================
2057 OTHERS PUBLIC FUNCTIONS
2059 =============================================================================
2067 void FS_StripExtension (const char *in, char *out, size_t size_out)
2074 while (*in && size_out > 1)
2078 else if (*in == '/' || *in == '\\' || *in == ':')
2095 void FS_DefaultExtension (char *path, const char *extension, size_t size_path)
2099 // if path doesn't have a .EXT, append extension
2100 // (extension should include the .)
2101 src = path + strlen(path) - 1;
2103 while (*src != '/' && src != path)
2106 return; // it has an extension
2110 strlcat (path, extension, size_path);
2118 Look for a file in the packages and in the filesystem
2121 qboolean FS_FileExists (const char *filename)
2123 return (FS_FindFile (filename, NULL, true) != NULL);
2131 Look for a file in the filesystem only
2134 qboolean FS_SysFileExists (const char *path)
2139 // TODO: use another function instead, to avoid opening the file
2140 desc = open (path, O_RDONLY | O_BINARY);
2149 if (stat (path,&buf) == -1)
2156 void FS_mkdir (const char *path)
2169 Allocate and fill a search structure with information on matching filenames.
2172 fssearch_t *FS_Search(const char *pattern, int caseinsensitive, int quiet)
2175 searchpath_t *searchpath;
2177 int i, basepathlength, numfiles, numchars;
2178 stringlist_t *dir, *dirfile, *liststart, *listcurrent, *listtemp;
2179 const char *slash, *backslash, *colon, *separator;
2181 char netpath[MAX_OSPATH];
2182 char temp[MAX_OSPATH];
2184 for (i = 0;pattern[i] == '.' || pattern[i] == ':' || pattern[i] == '/' || pattern[i] == '\\';i++)
2189 Con_Printf("Don't use punctuation at the beginning of a search pattern!\n");
2197 slash = strrchr(pattern, '/');
2198 backslash = strrchr(pattern, '\\');
2199 colon = strrchr(pattern, ':');
2200 separator = max(slash, backslash);
2201 separator = max(separator, colon);
2202 basepathlength = separator ? (separator + 1 - pattern) : 0;
2203 basepath = (char *)Mem_Alloc (tempmempool, basepathlength + 1);
2205 memcpy(basepath, pattern, basepathlength);
2206 basepath[basepathlength] = 0;
2208 // search through the path, one element at a time
2209 for (searchpath = fs_searchpaths;searchpath;searchpath = searchpath->next)
2211 // is the element a pak file?
2212 if (searchpath->pack)
2214 // look through all the pak file elements
2215 pak = searchpath->pack;
2216 for (i = 0;i < pak->numfiles;i++)
2218 strcpy(temp, pak->files[i].name);
2221 if (matchpattern(temp, (char *)pattern, true))
2223 for (listtemp = liststart;listtemp;listtemp = listtemp->next)
2224 if (!strcmp(listtemp->text, temp))
2226 if (listtemp == NULL)
2228 listcurrent = stringlistappend(listcurrent, temp);
2229 if (liststart == NULL)
2230 liststart = listcurrent;
2232 Con_DPrintf("SearchPackFile: %s : %s\n", pak->filename, temp);
2235 // strip off one path element at a time until empty
2236 // this way directories are added to the listing if they match the pattern
2237 slash = strrchr(temp, '/');
2238 backslash = strrchr(temp, '\\');
2239 colon = strrchr(temp, ':');
2241 if (separator < slash)
2243 if (separator < backslash)
2244 separator = backslash;
2245 if (separator < colon)
2247 *((char *)separator) = 0;
2253 // get a directory listing and look at each name
2254 dpsnprintf(netpath, sizeof (netpath), "%s%s", searchpath->filename, basepath);
2255 if ((dir = listdirectory(netpath)))
2257 for (dirfile = dir;dirfile;dirfile = dirfile->next)
2259 dpsnprintf(temp, sizeof(temp), "%s%s", basepath, dirfile->text);
2260 if (matchpattern(temp, (char *)pattern, true))
2262 for (listtemp = liststart;listtemp;listtemp = listtemp->next)
2263 if (!strcmp(listtemp->text, temp))
2265 if (listtemp == NULL)
2267 listcurrent = stringlistappend(listcurrent, temp);
2268 if (liststart == NULL)
2269 liststart = listcurrent;
2271 Con_DPrintf("SearchDirFile: %s\n", temp);
2282 liststart = stringlistsort(liststart);
2285 for (listtemp = liststart;listtemp;listtemp = listtemp->next)
2288 numchars += (int)strlen(listtemp->text) + 1;
2290 search = (fssearch_t *)Z_Malloc(sizeof(fssearch_t) + numchars + numfiles * sizeof(char *));
2291 search->filenames = (char **)((char *)search + sizeof(fssearch_t));
2292 search->filenamesbuffer = (char *)((char *)search + sizeof(fssearch_t) + numfiles * sizeof(char *));
2293 search->numfilenames = (int)numfiles;
2296 for (listtemp = liststart;listtemp;listtemp = listtemp->next)
2298 search->filenames[numfiles] = search->filenamesbuffer + numchars;
2299 strcpy(search->filenames[numfiles], listtemp->text);
2301 numchars += (int)strlen(listtemp->text) + 1;
2304 stringlistfree(liststart);
2311 void FS_FreeSearch(fssearch_t *search)
2316 extern int con_linewidth;
2317 int FS_ListDirectory(const char *pattern, int oneperline)
2326 char linebuf[MAX_INPUTLINE];
2328 search = FS_Search(pattern, true, true);
2331 numfiles = search->numfilenames;
2334 // FIXME: the names could be added to one column list and then
2335 // gradually shifted into the next column if they fit, and then the
2336 // next to make a compact variable width listing but it's a lot more
2338 // find width for columns
2340 for (i = 0;i < numfiles;i++)
2342 l = (int)strlen(search->filenames[i]);
2343 if (columnwidth < l)
2346 // count the spacing character
2348 // calculate number of columns
2349 numcolumns = con_linewidth / columnwidth;
2350 // don't bother with the column printing if it's only one column
2351 if (numcolumns >= 2)
2353 numlines = (numfiles + numcolumns - 1) / numcolumns;
2354 for (i = 0;i < numlines;i++)
2357 for (k = 0;k < numcolumns;k++)
2359 l = i * numcolumns + k;
2362 name = search->filenames[l];
2363 for (j = 0;name[j] && linebufpos + 1 < (int)sizeof(linebuf);j++)
2364 linebuf[linebufpos++] = name[j];
2365 // space out name unless it's the last on the line
2366 if (k + 1 < numcolumns && l + 1 < numfiles)
2367 for (;j < columnwidth && linebufpos + 1 < (int)sizeof(linebuf);j++)
2368 linebuf[linebufpos++] = ' ';
2371 linebuf[linebufpos] = 0;
2372 Con_Printf("%s\n", linebuf);
2379 for (i = 0;i < numfiles;i++)
2380 Con_Printf("%s\n", search->filenames[i]);
2381 FS_FreeSearch(search);
2382 return (int)numfiles;
2385 static void FS_ListDirectoryCmd (const char* cmdname, int oneperline)
2387 const char *pattern;
2390 Con_Printf("usage:\n%s [path/pattern]\n", cmdname);
2393 if (Cmd_Argc() == 2)
2394 pattern = Cmd_Argv(1);
2397 if (!FS_ListDirectory(pattern, oneperline))
2398 Con_Print("No files found.\n");
2403 FS_ListDirectoryCmd("dir", true);
2408 FS_ListDirectoryCmd("ls", false);
2411 const char *FS_WhichPack(const char *filename)
2414 searchpath_t *sp = FS_FindFile(filename, &index, true);
2416 return sp->pack->filename;