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 strlcpy(fs_gamedir, "", sizeof(fs_gamedir));
1066 // If the base directory is explicitly defined by the compilation process
1067 #ifdef DP_FS_BASEDIR
1068 strlcpy(fs_basedir, DP_FS_BASEDIR, sizeof(fs_basedir));
1070 strlcpy(fs_basedir, "", sizeof(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 // look for the pop.lmp file and set registered to true if it is found
1165 if (gamemode == GAME_NORMAL && !FS_FileExists("gfx/pop.lmp"))
1168 Con_Print("Playing shareware version, with modification.\nwarning: most mods require full quake data.\n");
1170 Con_Print("Playing shareware version.\n");
1174 Cvar_Set ("registered", "1");
1175 if (gamemode == GAME_NORMAL || gamemode == GAME_HIPNOTIC || gamemode == GAME_ROGUE)
1176 Con_Print("Playing registered version.\n");
1179 // set the default screenshot name to either the mod name or the
1180 // gamemode screenshot name
1182 Cvar_SetQuick (&scr_screenshot_name, com_modname);
1184 Cvar_SetQuick (&scr_screenshot_name, gamescreenshotname);
1187 void FS_Init_Commands(void)
1189 Cvar_RegisterVariable (&scr_screenshot_name);
1191 Cmd_AddCommand ("path", FS_Path_f, "print searchpath (game directories and archives)");
1192 Cmd_AddCommand ("dir", FS_Dir_f, "list files in searchpath matching an * filename pattern, one per line");
1193 Cmd_AddCommand ("ls", FS_Ls_f, "list files in searchpath matching an * filename pattern, multiple per line");
1201 void FS_Shutdown (void)
1203 Mem_FreePool (&fs_mempool);
1207 ====================
1210 Internal function used to create a qfile_t and open the relevant non-packed file on disk
1211 ====================
1213 static qfile_t* FS_SysOpen (const char* filepath, const char* mode, qboolean nonblocking)
1219 // Parse the mode string
1228 opt = O_CREAT | O_TRUNC;
1232 opt = O_CREAT | O_APPEND;
1235 Con_Printf ("FS_SysOpen(%s, %s): invalid mode\n", filepath, mode);
1238 for (ind = 1; mode[ind] != '\0'; ind++)
1249 Con_Printf ("FS_SysOpen(%s, %s): unknown character in mode (%c)\n",
1250 filepath, mode, mode[ind]);
1257 file = (qfile_t *)Mem_Alloc (fs_mempool, sizeof (*file));
1258 memset (file, 0, sizeof (*file));
1261 file->handle = open (filepath, mod | opt, 0666);
1262 if (file->handle < 0)
1268 file->real_length = lseek (file->handle, 0, SEEK_END);
1270 // For files opened in append mode, we start at the end of the file
1272 file->position = file->real_length;
1274 lseek (file->handle, 0, SEEK_SET);
1284 Open a packed file using its package file descriptor
1287 qfile_t *FS_OpenPackedFile (pack_t* pack, int pack_ind)
1293 pfile = &pack->files[pack_ind];
1295 // If we don't have the true offset, get it now
1296 if (! (pfile->flags & PACKFILE_FLAG_TRUEOFFS))
1297 if (!PK3_GetTrueFileOffset (pfile, pack))
1300 // No Zlib DLL = no compressed files
1301 if (!zlib_dll && (pfile->flags & PACKFILE_FLAG_DEFLATED))
1303 Con_Printf("WARNING: can't open the compressed file %s\n"
1304 "You need the Zlib DLL to use compressed files\n",
1309 // LordHavoc: lseek affects all duplicates of a handle so we do it before
1310 // the dup() call to avoid having to close the dup_handle on error here
1311 if (lseek (pack->handle, pfile->offset, SEEK_SET) == -1)
1313 Con_Printf ("FS_OpenPackedFile: can't lseek to %s in %s (offset: %d)\n",
1314 pfile->name, pack->filename, pfile->offset);
1318 dup_handle = dup (pack->handle);
1321 Con_Printf ("FS_OpenPackedFile: can't dup package's handle (pack: %s)\n", pack->filename);
1325 file = (qfile_t *)Mem_Alloc (fs_mempool, sizeof (*file));
1326 memset (file, 0, sizeof (*file));
1327 file->handle = dup_handle;
1328 file->flags = QFILE_FLAG_PACKED;
1329 file->real_length = pfile->realsize;
1330 file->offset = pfile->offset;
1334 if (pfile->flags & PACKFILE_FLAG_DEFLATED)
1338 file->flags |= QFILE_FLAG_DEFLATED;
1340 // We need some more variables
1341 ztk = (ztoolkit_t *)Mem_Alloc (fs_mempool, sizeof (*ztk));
1343 ztk->comp_length = pfile->packsize;
1345 // Initialize zlib stream
1346 ztk->zstream.next_in = ztk->input;
1347 ztk->zstream.avail_in = 0;
1349 /* From Zlib's "unzip.c":
1351 * windowBits is passed < 0 to tell that there is no zlib header.
1352 * Note that in this case inflate *requires* an extra "dummy" byte
1353 * after the compressed stream in order to complete decompression and
1354 * return Z_STREAM_END.
1355 * In unzip, i don't wait absolutely Z_STREAM_END because I known the
1356 * size of both compressed and uncompressed data
1358 if (qz_inflateInit2 (&ztk->zstream, -MAX_WBITS) != Z_OK)
1360 Con_Printf ("FS_OpenPackedFile: inflate init error (file: %s)\n", pfile->name);
1366 ztk->zstream.next_out = file->buff;
1367 ztk->zstream.avail_out = sizeof (file->buff);
1376 ====================
1379 Return true if the path should be rejected due to one of the following:
1380 1: path elements that are non-portable
1381 2: path elements that would allow access to files outside the game directory,
1382 or are just not a good idea for a mod to be using.
1383 ====================
1385 int FS_CheckNastyPath (const char *path)
1387 // Windows: don't allow \ in filenames (windows-only), period.
1388 // (on Windows \ is a directory separator, but / is also supported)
1389 if (strstr(path, "\\"))
1390 return 1; // non-portable
1392 // Mac: don't allow Mac-only filenames - : is a directory separator
1393 // instead of /, but we rely on / working already, so there's no reason to
1394 // support a Mac-only path
1395 // Amiga and Windows: : tries to go to root of drive
1396 if (strstr(path, ":"))
1397 return 1; // non-portable attempt to go to root of drive
1399 // Amiga: // is parent directory
1400 if (strstr(path, "//"))
1401 return 1; // non-portable attempt to go to parent directory
1403 // all: don't allow going to current directory (./) or parent directory (../ or /../)
1404 if (strstr(path, "./"))
1405 return 2; // attempt to go outside the game directory
1407 // Windows and UNIXes: don't allow absolute paths
1409 return 2; // attempt to go outside the game directory
1411 // after all these checks we're pretty sure it's a / separated filename
1412 // and won't do much if any harm
1418 ====================
1421 Look for a file in the packages and in the filesystem
1423 Return the searchpath where the file was found (or NULL)
1424 and the file index in the package if relevant
1425 ====================
1427 static searchpath_t *FS_FindFile (const char *name, int* index, qboolean quiet)
1429 searchpath_t *search;
1432 // search through the path, one element at a time
1433 for (search = fs_searchpaths;search;search = search->next)
1435 // is the element a pak file?
1438 int (*strcmp_funct) (const char* str1, const char* str2);
1439 int left, right, middle;
1442 strcmp_funct = pak->ignorecase ? strcasecmp : strcmp;
1444 // Look for the file (binary search)
1446 right = pak->numfiles - 1;
1447 while (left <= right)
1451 middle = (left + right) / 2;
1452 diff = strcmp_funct (pak->files[middle].name, name);
1457 if (!quiet && developer.integer >= 10)
1458 Con_Printf("FS_FindFile: %s in %s\n",
1459 pak->files[middle].name, pak->filename);
1466 // If we're too far in the list
1475 char netpath[MAX_OSPATH];
1476 dpsnprintf(netpath, sizeof(netpath), "%s%s", search->filename, name);
1477 if (FS_SysFileExists (netpath))
1479 if (!quiet && developer.integer >= 10)
1480 Con_Printf("FS_FindFile: %s\n", netpath);
1489 if (!quiet && developer.integer >= 10)
1490 Con_Printf("FS_FindFile: can't find %s\n", name);
1502 Look for a file in the search paths and open it in read-only mode
1505 qfile_t *FS_OpenReadFile (const char *filename, qboolean quiet, qboolean nonblocking)
1507 searchpath_t *search;
1510 search = FS_FindFile (filename, &pack_ind, quiet);
1516 // Found in the filesystem?
1519 char path [MAX_OSPATH];
1520 dpsnprintf (path, sizeof (path), "%s%s", search->filename, filename);
1521 return FS_SysOpen (path, "rb", nonblocking);
1524 // So, we found it in a package...
1525 return FS_OpenPackedFile (search->pack, pack_ind);
1530 =============================================================================
1532 MAIN PUBLIC FUNCTIONS
1534 =============================================================================
1538 ====================
1541 Open a file. The syntax is the same as fopen
1542 ====================
1544 qfile_t* FS_Open (const char* filepath, const char* mode, qboolean quiet, qboolean nonblocking)
1546 if (FS_CheckNastyPath(filepath))
1548 Con_Printf("FS_Open(\"%s\", \"%s\", %s): nasty filename rejected\n", filepath, mode, quiet ? "true" : "false");
1552 // If the file is opened in "write", "append", or "read/write" mode
1553 if (mode[0] == 'w' || mode[0] == 'a' || strchr (mode, '+'))
1555 char real_path [MAX_OSPATH];
1557 // Open the file on disk directly
1558 dpsnprintf (real_path, sizeof (real_path), "%s/%s", fs_gamedir, filepath);
1560 // Create directories up to the file
1561 FS_CreatePath (real_path);
1563 return FS_SysOpen (real_path, mode, nonblocking);
1565 // Else, we look at the various search paths and open the file in read-only mode
1567 return FS_OpenReadFile (filepath, quiet, nonblocking);
1572 ====================
1576 ====================
1578 int FS_Close (qfile_t* file)
1580 if (close (file->handle))
1585 qz_inflateEnd (&file->ztk->zstream);
1586 Mem_Free (file->ztk);
1595 ====================
1598 Write "datasize" bytes into a file
1599 ====================
1601 fs_offset_t FS_Write (qfile_t* file, const void* data, size_t datasize)
1605 // If necessary, seek to the exact file position we're supposed to be
1606 if (file->buff_ind != file->buff_len)
1607 lseek (file->handle, file->buff_ind - file->buff_len, SEEK_CUR);
1609 // Purge cached data
1612 // Write the buffer and update the position
1613 result = write (file->handle, data, (fs_offset_t)datasize);
1614 file->position = lseek (file->handle, 0, SEEK_CUR);
1615 if (file->real_length < file->position)
1616 file->real_length = file->position;
1626 ====================
1629 Read up to "buffersize" bytes from a file
1630 ====================
1632 fs_offset_t FS_Read (qfile_t* file, void* buffer, size_t buffersize)
1634 fs_offset_t count, done;
1636 if (buffersize == 0)
1639 // Get rid of the ungetc character
1640 if (file->ungetc != EOF)
1642 ((char*)buffer)[0] = file->ungetc;
1650 // First, we copy as many bytes as we can from "buff"
1651 if (file->buff_ind < file->buff_len)
1653 count = file->buff_len - file->buff_ind;
1655 done += ((fs_offset_t)buffersize > count) ? count : (fs_offset_t)buffersize;
1656 memcpy (buffer, &file->buff[file->buff_ind], done);
1657 file->buff_ind += done;
1660 if (buffersize == 0)
1664 // NOTE: at this point, the read buffer is always empty
1666 // If the file isn't compressed
1667 if (! (file->flags & QFILE_FLAG_DEFLATED))
1671 // We must take care to not read after the end of the file
1672 count = file->real_length - file->position;
1674 // If we have a lot of data to get, put them directly into "buffer"
1675 if (buffersize > sizeof (file->buff) / 2)
1677 if (count > (fs_offset_t)buffersize)
1678 count = (fs_offset_t)buffersize;
1679 lseek (file->handle, file->offset + file->position, SEEK_SET);
1680 nb = read (file->handle, &((unsigned char*)buffer)[done], count);
1684 file->position += nb;
1686 // Purge cached data
1692 if (count > (fs_offset_t)sizeof (file->buff))
1693 count = (fs_offset_t)sizeof (file->buff);
1694 lseek (file->handle, file->offset + file->position, SEEK_SET);
1695 nb = read (file->handle, file->buff, count);
1698 file->buff_len = nb;
1699 file->position += nb;
1701 // Copy the requested data in "buffer" (as much as we can)
1702 count = (fs_offset_t)buffersize > file->buff_len ? file->buff_len : (fs_offset_t)buffersize;
1703 memcpy (&((unsigned char*)buffer)[done], file->buff, count);
1704 file->buff_ind = count;
1712 // If the file is compressed, it's more complicated...
1713 // We cycle through a few operations until we have read enough data
1714 while (buffersize > 0)
1716 ztoolkit_t *ztk = file->ztk;
1719 // NOTE: at this point, the read buffer is always empty
1721 // If "input" is also empty, we need to refill it
1722 if (ztk->in_ind == ztk->in_len)
1724 // If we are at the end of the file
1725 if (file->position == file->real_length)
1728 count = (fs_offset_t)(ztk->comp_length - ztk->in_position);
1729 if (count > (fs_offset_t)sizeof (ztk->input))
1730 count = (fs_offset_t)sizeof (ztk->input);
1731 lseek (file->handle, file->offset + (fs_offset_t)ztk->in_position, SEEK_SET);
1732 if (read (file->handle, ztk->input, count) != count)
1734 Con_Printf ("FS_Read: unexpected end of file\n");
1739 ztk->in_len = count;
1740 ztk->in_position += count;
1743 ztk->zstream.next_in = &ztk->input[ztk->in_ind];
1744 ztk->zstream.avail_in = (unsigned int)(ztk->in_len - ztk->in_ind);
1746 // Now that we are sure we have compressed data available, we need to determine
1747 // if it's better to inflate it in "file->buff" or directly in "buffer"
1749 // Inflate the data in "file->buff"
1750 if (buffersize < sizeof (file->buff) / 2)
1752 ztk->zstream.next_out = file->buff;
1753 ztk->zstream.avail_out = sizeof (file->buff);
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 file->buff_len = (fs_offset_t)sizeof (file->buff) - ztk->zstream.avail_out;
1763 file->position += file->buff_len;
1765 // Copy the requested data in "buffer" (as much as we can)
1766 count = (fs_offset_t)buffersize > file->buff_len ? file->buff_len : (fs_offset_t)buffersize;
1767 memcpy (&((unsigned char*)buffer)[done], file->buff, count);
1768 file->buff_ind = count;
1771 // Else, we inflate directly in "buffer"
1774 ztk->zstream.next_out = &((unsigned char*)buffer)[done];
1775 ztk->zstream.avail_out = (unsigned int)buffersize;
1776 error = qz_inflate (&ztk->zstream, Z_SYNC_FLUSH);
1777 if (error != Z_OK && error != Z_STREAM_END)
1779 Con_Printf ("FS_Read: Can't inflate file\n");
1782 ztk->in_ind = ztk->in_len - ztk->zstream.avail_in;
1784 // How much data did it inflate?
1785 count = (fs_offset_t)(buffersize - ztk->zstream.avail_out);
1786 file->position += count;
1788 // Purge cached data
1793 buffersize -= count;
1801 ====================
1804 Print a string into a file
1805 ====================
1807 int FS_Print (qfile_t* file, const char *msg)
1809 return (int)FS_Write (file, msg, strlen (msg));
1813 ====================
1816 Print a string into a file
1817 ====================
1819 int FS_Printf(qfile_t* file, const char* format, ...)
1824 va_start (args, format);
1825 result = FS_VPrintf (file, format, args);
1833 ====================
1836 Print a string into a file
1837 ====================
1839 int FS_VPrintf (qfile_t* file, const char* format, va_list ap)
1842 fs_offset_t buff_size = MAX_INPUTLINE;
1847 tempbuff = (char *)Mem_Alloc (tempmempool, buff_size);
1848 len = dpvsnprintf (tempbuff, buff_size, format, ap);
1849 if (len >= 0 && len < buff_size)
1851 Mem_Free (tempbuff);
1855 len = write (file->handle, tempbuff, len);
1856 Mem_Free (tempbuff);
1863 ====================
1866 Get the next character of a file
1867 ====================
1869 int FS_Getc (qfile_t* file)
1873 if (FS_Read (file, &c, 1) != 1)
1881 ====================
1884 Put a character back into the read buffer (only supports one character!)
1885 ====================
1887 int FS_UnGetc (qfile_t* file, unsigned char c)
1889 // If there's already a character waiting to be read
1890 if (file->ungetc != EOF)
1899 ====================
1902 Move the position index in a file
1903 ====================
1905 int FS_Seek (qfile_t* file, fs_offset_t offset, int whence)
1908 unsigned char* buffer;
1909 fs_offset_t buffersize;
1911 // Compute the file offset
1915 offset += file->position - file->buff_len + file->buff_ind;
1922 offset += file->real_length;
1928 if (offset < 0 || offset > (long) file->real_length)
1931 // If we have the data in our read buffer, we don't need to actually seek
1932 if (file->position - file->buff_len <= offset && offset <= file->position)
1934 file->buff_ind = offset + file->buff_len - file->position;
1938 // Purge cached data
1941 // Unpacked or uncompressed files can seek directly
1942 if (! (file->flags & QFILE_FLAG_DEFLATED))
1944 if (lseek (file->handle, file->offset + offset, SEEK_SET) == -1)
1946 file->position = offset;
1950 // Seeking in compressed files is more a hack than anything else,
1951 // but we need to support it, so here we go.
1954 // If we have to go back in the file, we need to restart from the beginning
1955 if (offset <= file->position)
1959 ztk->in_position = 0;
1961 lseek (file->handle, file->offset, SEEK_SET);
1963 // Reset the Zlib stream
1964 ztk->zstream.next_in = ztk->input;
1965 ztk->zstream.avail_in = 0;
1966 qz_inflateReset (&ztk->zstream);
1969 // We need a big buffer to force inflating into it directly
1970 buffersize = 2 * sizeof (file->buff);
1971 buffer = (unsigned char *)Mem_Alloc (tempmempool, buffersize);
1973 // Skip all data until we reach the requested offset
1974 while (offset > file->position)
1976 fs_offset_t diff = offset - file->position;
1977 fs_offset_t count, len;
1979 count = (diff > buffersize) ? buffersize : diff;
1980 len = FS_Read (file, buffer, count);
1994 ====================
1997 Give the current position in a file
1998 ====================
2000 fs_offset_t FS_Tell (qfile_t* file)
2002 return file->position - file->buff_len + file->buff_ind;
2007 ====================
2010 Erases any buffered input or output data
2011 ====================
2013 void FS_Purge (qfile_t* file)
2025 Filename are relative to the quake directory.
2026 Always appends a 0 byte.
2029 unsigned char *FS_LoadFile (const char *path, mempool_t *pool, qboolean quiet, fs_offset_t *filesizepointer)
2032 unsigned char *buf = NULL;
2033 fs_offset_t filesize = 0;
2035 file = FS_Open (path, "rb", quiet, false);
2038 filesize = file->real_length;
2039 buf = (unsigned char *)Mem_Alloc (pool, filesize + 1);
2040 buf[filesize] = '\0';
2041 FS_Read (file, buf, filesize);
2045 if (filesizepointer)
2046 *filesizepointer = filesize;
2055 The filename will be prefixed by the current game directory
2058 qboolean FS_WriteFile (const char *filename, void *data, fs_offset_t len)
2062 file = FS_Open (filename, "wb", false, false);
2065 Con_Printf("FS_WriteFile: failed on %s\n", filename);
2069 Con_DPrintf("FS_WriteFile: %s\n", filename);
2070 FS_Write (file, data, len);
2077 =============================================================================
2079 OTHERS PUBLIC FUNCTIONS
2081 =============================================================================
2089 void FS_StripExtension (const char *in, char *out, size_t size_out)
2097 while ((currentchar = *in) && size_out > 1)
2099 if (currentchar == '.')
2101 else if (currentchar == '/' || currentchar == '\\' || currentchar == ':')
2103 *out++ = currentchar;
2119 void FS_DefaultExtension (char *path, const char *extension, size_t size_path)
2123 // if path doesn't have a .EXT, append extension
2124 // (extension should include the .)
2125 src = path + strlen(path) - 1;
2127 while (*src != '/' && src != path)
2130 return; // it has an extension
2134 strlcat (path, extension, size_path);
2142 Look for a file in the packages and in the filesystem
2145 qboolean FS_FileExists (const char *filename)
2147 return (FS_FindFile (filename, NULL, true) != NULL);
2155 Look for a file in the filesystem only
2158 qboolean FS_SysFileExists (const char *path)
2163 // TODO: use another function instead, to avoid opening the file
2164 desc = open (path, O_RDONLY | O_BINARY);
2173 if (stat (path,&buf) == -1)
2180 void FS_mkdir (const char *path)
2193 Allocate and fill a search structure with information on matching filenames.
2196 fssearch_t *FS_Search(const char *pattern, int caseinsensitive, int quiet)
2199 searchpath_t *searchpath;
2201 int i, basepathlength, numfiles, numchars;
2202 stringlist_t *dir, *dirfile, *liststart, *listcurrent, *listtemp;
2203 const char *slash, *backslash, *colon, *separator;
2205 char netpath[MAX_OSPATH];
2206 char temp[MAX_OSPATH];
2208 for (i = 0;pattern[i] == '.' || pattern[i] == ':' || pattern[i] == '/' || pattern[i] == '\\';i++)
2213 Con_Printf("Don't use punctuation at the beginning of a search pattern!\n");
2221 slash = strrchr(pattern, '/');
2222 backslash = strrchr(pattern, '\\');
2223 colon = strrchr(pattern, ':');
2224 separator = max(slash, backslash);
2225 separator = max(separator, colon);
2226 basepathlength = separator ? (separator + 1 - pattern) : 0;
2227 basepath = (char *)Mem_Alloc (tempmempool, basepathlength + 1);
2229 memcpy(basepath, pattern, basepathlength);
2230 basepath[basepathlength] = 0;
2232 // search through the path, one element at a time
2233 for (searchpath = fs_searchpaths;searchpath;searchpath = searchpath->next)
2235 // is the element a pak file?
2236 if (searchpath->pack)
2238 // look through all the pak file elements
2239 pak = searchpath->pack;
2240 for (i = 0;i < pak->numfiles;i++)
2242 strlcpy(temp, pak->files[i].name, sizeof(temp));
2245 if (matchpattern(temp, (char *)pattern, true))
2247 for (listtemp = liststart;listtemp;listtemp = listtemp->next)
2248 if (!strcmp(listtemp->text, temp))
2250 if (listtemp == NULL)
2252 listcurrent = stringlistappend(listcurrent, temp);
2253 if (liststart == NULL)
2254 liststart = listcurrent;
2256 Con_DPrintf("SearchPackFile: %s : %s\n", pak->filename, temp);
2259 // strip off one path element at a time until empty
2260 // this way directories are added to the listing if they match the pattern
2261 slash = strrchr(temp, '/');
2262 backslash = strrchr(temp, '\\');
2263 colon = strrchr(temp, ':');
2265 if (separator < slash)
2267 if (separator < backslash)
2268 separator = backslash;
2269 if (separator < colon)
2271 *((char *)separator) = 0;
2277 // get a directory listing and look at each name
2278 dpsnprintf(netpath, sizeof (netpath), "%s%s", searchpath->filename, basepath);
2279 if ((dir = listdirectory(netpath)))
2281 for (dirfile = dir;dirfile;dirfile = dirfile->next)
2283 dpsnprintf(temp, sizeof(temp), "%s%s", basepath, dirfile->text);
2284 if (matchpattern(temp, (char *)pattern, true))
2286 for (listtemp = liststart;listtemp;listtemp = listtemp->next)
2287 if (!strcmp(listtemp->text, temp))
2289 if (listtemp == NULL)
2291 listcurrent = stringlistappend(listcurrent, temp);
2292 if (liststart == NULL)
2293 liststart = listcurrent;
2295 Con_DPrintf("SearchDirFile: %s\n", temp);
2306 liststart = stringlistsort(liststart);
2309 for (listtemp = liststart;listtemp;listtemp = listtemp->next)
2312 numchars += (int)strlen(listtemp->text) + 1;
2314 search = (fssearch_t *)Z_Malloc(sizeof(fssearch_t) + numchars + numfiles * sizeof(char *));
2315 search->filenames = (char **)((char *)search + sizeof(fssearch_t));
2316 search->filenamesbuffer = (char *)((char *)search + sizeof(fssearch_t) + numfiles * sizeof(char *));
2317 search->numfilenames = (int)numfiles;
2320 for (listtemp = liststart;listtemp;listtemp = listtemp->next)
2323 search->filenames[numfiles] = search->filenamesbuffer + numchars;
2324 textlen = strlen(listtemp->text) + 1;
2325 memcpy(search->filenames[numfiles], listtemp->text, textlen);
2327 numchars += (int)textlen;
2330 stringlistfree(liststart);
2337 void FS_FreeSearch(fssearch_t *search)
2342 extern int con_linewidth;
2343 int FS_ListDirectory(const char *pattern, int oneperline)
2352 char linebuf[MAX_INPUTLINE];
2354 search = FS_Search(pattern, true, true);
2357 numfiles = search->numfilenames;
2360 // FIXME: the names could be added to one column list and then
2361 // gradually shifted into the next column if they fit, and then the
2362 // next to make a compact variable width listing but it's a lot more
2364 // find width for columns
2366 for (i = 0;i < numfiles;i++)
2368 l = (int)strlen(search->filenames[i]);
2369 if (columnwidth < l)
2372 // count the spacing character
2374 // calculate number of columns
2375 numcolumns = con_linewidth / columnwidth;
2376 // don't bother with the column printing if it's only one column
2377 if (numcolumns >= 2)
2379 numlines = (numfiles + numcolumns - 1) / numcolumns;
2380 for (i = 0;i < numlines;i++)
2383 for (k = 0;k < numcolumns;k++)
2385 l = i * numcolumns + k;
2388 name = search->filenames[l];
2389 for (j = 0;name[j] && linebufpos + 1 < (int)sizeof(linebuf);j++)
2390 linebuf[linebufpos++] = name[j];
2391 // space out name unless it's the last on the line
2392 if (k + 1 < numcolumns && l + 1 < numfiles)
2393 for (;j < columnwidth && linebufpos + 1 < (int)sizeof(linebuf);j++)
2394 linebuf[linebufpos++] = ' ';
2397 linebuf[linebufpos] = 0;
2398 Con_Printf("%s\n", linebuf);
2405 for (i = 0;i < numfiles;i++)
2406 Con_Printf("%s\n", search->filenames[i]);
2407 FS_FreeSearch(search);
2408 return (int)numfiles;
2411 static void FS_ListDirectoryCmd (const char* cmdname, int oneperline)
2413 const char *pattern;
2416 Con_Printf("usage:\n%s [path/pattern]\n", cmdname);
2419 if (Cmd_Argc() == 2)
2420 pattern = Cmd_Argv(1);
2423 if (!FS_ListDirectory(pattern, oneperline))
2424 Con_Print("No files found.\n");
2429 FS_ListDirectoryCmd("dir", true);
2434 FS_ListDirectoryCmd("ls", false);
2437 const char *FS_WhichPack(const char *filename)
2440 searchpath_t *sp = FS_FindFile(filename, &index, true);
2442 return sp->pack->filename;