4 Copyright (C) 2003 Mathieu Olivier
5 Copyright (C) 1999,2000 contributors of the QuakeForge project
7 This program is free software; you can redistribute it and/or
8 modify it under the terms of the GNU General Public License
9 as published by the Free Software Foundation; either version 2
10 of the License, or (at your option) any later version.
12 This program is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
16 See the GNU General Public License for more details.
18 You should have received a copy of the GNU General Public License
19 along with this program; if not, write to:
21 Free Software Foundation, Inc.
22 59 Temple Place - Suite 330
23 Boston, MA 02111-1307, USA
36 # include <sys/stat.h>
46 // use syscalls instead of f* functions
47 #define FS_USESYSCALLS
49 // Win32 requires us to add O_BINARY, but the other OSes don't have it
59 All of Quake's data access is through a hierchal file system, but the contents
60 of the file system can be transparently merged from several sources.
62 The "base directory" is the path to the directory holding the quake.exe and
63 all game directories. The sys_* files pass this to host_init in
64 quakeparms_t->basedir. This can be overridden with the "-basedir" command
65 line parm to allow code debugging in a different directory. The base
66 directory is only used during filesystem initialization.
68 The "game directory" is the first tree on the search path and directory that
69 all generated files (savegames, screenshots, demos, config files) will be
70 saved to. This can be overridden with the "-game" command line parameter.
71 The game directory can never be changed while quake is executing. This is a
72 precacution against having a malicious server instruct clients to write files
73 over areas they shouldn't.
79 =============================================================================
83 =============================================================================
86 // Magic numbers of a ZIP file (big-endian format)
87 #define ZIP_DATA_HEADER 0x504B0304 // "PK\3\4"
88 #define ZIP_CDIR_HEADER 0x504B0102 // "PK\1\2"
89 #define ZIP_END_HEADER 0x504B0506 // "PK\5\6"
91 // Other constants for ZIP files
92 #define ZIP_MAX_COMMENTS_SIZE ((unsigned short)0xFFFF)
93 #define ZIP_END_CDIR_SIZE 22
94 #define ZIP_CDIR_CHUNK_BASE_SIZE 46
95 #define ZIP_LOCAL_CHUNK_BASE_SIZE 30
97 // Zlib constants (from zlib.h)
98 #define Z_SYNC_FLUSH 2
101 #define Z_STREAM_END 1
102 #define ZLIB_VERSION "1.1.4"
106 =============================================================================
110 =============================================================================
113 // Zlib stream (from zlib.h)
114 // Warning: some pointers we don't use directly have
115 // been cast to "void*" for a matter of simplicity
118 qbyte *next_in; // next input byte
119 unsigned int avail_in; // number of bytes available at next_in
120 unsigned long total_in; // total nb of input bytes read so far
122 qbyte *next_out; // next output byte should be put there
123 unsigned int avail_out; // remaining free space at next_out
124 unsigned long total_out; // total nb of bytes output so far
126 char *msg; // last error message, NULL if no error
127 void *state; // not visible by applications
129 void *zalloc; // used to allocate the internal state
130 void *zfree; // used to free the internal state
131 void *opaque; // private data object passed to zalloc and zfree
133 int data_type; // best guess about the data type: ascii or binary
134 unsigned long adler; // adler32 value of the uncompressed data
135 unsigned long reserved; // reserved for future use
139 // Our own file structure on top of FILE
143 FS_FLAG_PACKED = (1 << 0), // inside a package (PAK or PK3)
144 FS_FLAG_DEFLATED = (1 << 1) // file is compressed using the deflate algorithm (PK3 only)
147 #define ZBUFF_SIZE 1024
151 size_t real_length; // length of the uncompressed file
152 size_t in_ind, in_max; // input buffer index and counter
153 size_t in_position; // position in the compressed file
154 size_t out_ind, out_max; // output buffer index and counter
155 size_t out_position; // how many bytes did we uncompress until now?
156 qbyte input [ZBUFF_SIZE];
157 qbyte output [ZBUFF_SIZE];
163 #ifdef FS_USESYSCALLS
168 size_t length; // file size on disk (PACKED only)
169 size_t offset; // offset into a package (PACKED only)
170 size_t position; // current position in the file (PACKED only)
171 ztoolkit_t* z; // used for inflating (DEFLATED only)
175 // ------ PK3 files on disk ------ //
177 // You can get the complete ZIP format description from PKWARE website
181 unsigned int signature;
182 unsigned short disknum;
183 unsigned short cdir_disknum; // number of the disk with the start of the central directory
184 unsigned short localentries; // number of entries in the central directory on this disk
185 unsigned short nbentries; // total number of entries in the central directory on this disk
186 unsigned int cdir_size; // size of the central directory
187 unsigned int cdir_offset; // with respect to the starting disk number
188 unsigned short comment_size;
189 } pk3_endOfCentralDir_t;
192 // ------ PAK files on disk ------ //
196 int filepos, filelen;
207 // Packages in memory
211 FILE_FLAG_TRUEOFFS = (1 << 0), // the offset in packfile_t is the true contents offset
212 FILE_FLAG_DEFLATED = (1 << 1) // file compressed using the deflate algorithm
217 char name [MAX_QPATH];
220 size_t packsize; // size in the package
221 size_t realsize; // real file size (uncompressed)
224 typedef struct pack_s
226 char filename [MAX_OSPATH];
227 #ifdef FS_USESYSCALLS
232 int ignorecase; // PK3 ignores case
240 // Search paths for files (including packages)
241 typedef struct searchpath_s
243 // only one of filename / pack will be used
244 char filename[MAX_OSPATH];
246 struct searchpath_s *next;
251 =============================================================================
255 =============================================================================
261 static packfile_t* FS_AddFileToPack (const char* name, pack_t* pack,
262 size_t offset, size_t packsize,
263 size_t realsize, file_flags_t flags);
267 =============================================================================
271 =============================================================================
274 mempool_t *fs_mempool;
275 mempool_t *pak_mempool;
279 pack_t *packlist = NULL;
281 searchpath_t *fs_searchpaths = NULL;
283 #define MAX_FILES_IN_PACK 65536
285 char fs_gamedir[MAX_OSPATH];
286 char fs_basedir[MAX_OSPATH];
288 qboolean fs_modified; // set true if using non-id files
292 =============================================================================
294 PRIVATE FUNCTIONS - PK3 HANDLING
296 =============================================================================
299 // Functions exported from zlib
301 # define ZEXPORT WINAPI
306 static int (ZEXPORT *qz_inflate) (z_stream* strm, int flush);
307 static int (ZEXPORT *qz_inflateEnd) (z_stream* strm);
308 static int (ZEXPORT *qz_inflateInit2_) (z_stream* strm, int windowBits, const char *version, int stream_size);
309 static int (ZEXPORT *qz_inflateReset) (z_stream* strm);
311 #define qz_inflateInit2(strm, windowBits) \
312 qz_inflateInit2_((strm), (windowBits), ZLIB_VERSION, sizeof(z_stream))
314 static dllfunction_t zlibfuncs[] =
316 {"inflate", (void **) &qz_inflate},
317 {"inflateEnd", (void **) &qz_inflateEnd},
318 {"inflateInit2_", (void **) &qz_inflateInit2_},
319 {"inflateReset", (void **) &qz_inflateReset},
323 // Handle for Zlib DLL
324 static dllhandle_t zlib_dll = NULL;
334 void PK3_CloseLibrary (void)
336 Sys_UnloadLibrary (&zlib_dll);
344 Try to load the Zlib DLL
347 qboolean PK3_OpenLibrary (void)
356 dllname = "zlib.dll";
357 #elif defined(MACOSX)
358 dllname = "libz.dylib";
364 if (! Sys_LoadLibrary (dllname, &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 #ifdef FS_USESYSCALLS
383 qboolean PK3_GetEndOfCentralDir (const char *packfile, int packhandle, pk3_endOfCentralDir_t *eocd)
385 qboolean PK3_GetEndOfCentralDir (const char *packfile, FILE *packhandle, pk3_endOfCentralDir_t *eocd)
388 long filesize, maxsize;
392 // Get the package size
393 #ifdef FS_USESYSCALLS
394 filesize = lseek (packhandle, 0, SEEK_END);
396 fseek (packhandle, 0, SEEK_END);
397 filesize = ftell(packhandle);
399 if (filesize < ZIP_END_CDIR_SIZE)
402 // Load the end of the file in memory
403 if (filesize < ZIP_MAX_COMMENTS_SIZE + ZIP_END_CDIR_SIZE)
406 maxsize = ZIP_MAX_COMMENTS_SIZE + ZIP_END_CDIR_SIZE;
407 buffer = Mem_Alloc (tempmempool, maxsize);
408 #ifdef FS_USESYSCALLS
409 lseek (packhandle, filesize - maxsize, SEEK_SET);
410 if (read (packhandle, buffer, maxsize) != (ssize_t) maxsize)
412 fseek (packhandle, filesize - maxsize, SEEK_SET);
413 if (fread (buffer, 1, maxsize, packhandle) != (size_t) maxsize)
420 // Look for the end of central dir signature around the end of the file
421 maxsize -= ZIP_END_CDIR_SIZE;
422 ptr = &buffer[maxsize];
424 while (BuffBigLong (ptr) != ZIP_END_HEADER)
436 memcpy (eocd, ptr, ZIP_END_CDIR_SIZE);
437 eocd->signature = LittleLong (eocd->signature);
438 eocd->disknum = LittleShort (eocd->disknum);
439 eocd->cdir_disknum = LittleShort (eocd->cdir_disknum);
440 eocd->localentries = LittleShort (eocd->localentries);
441 eocd->nbentries = LittleShort (eocd->nbentries);
442 eocd->cdir_size = LittleLong (eocd->cdir_size);
443 eocd->cdir_offset = LittleLong (eocd->cdir_offset);
444 eocd->comment_size = LittleShort (eocd->comment_size);
456 Extract the file list from a PK3 file
459 int PK3_BuildFileList (pack_t *pack, const pk3_endOfCentralDir_t *eocd)
461 qbyte *central_dir, *ptr;
465 // Load the central directory in memory
466 central_dir = Mem_Alloc (tempmempool, eocd->cdir_size);
467 #ifdef FS_USESYSCALLS
468 lseek (pack->handle, eocd->cdir_offset, SEEK_SET);
469 read (pack->handle, central_dir, eocd->cdir_size);
471 fseek (pack->handle, eocd->cdir_offset, SEEK_SET);
472 fread (central_dir, 1, eocd->cdir_size, pack->handle);
475 // Extract the files properties
476 // The parsing is done "by hand" because some fields have variable sizes and
477 // the constant part isn't 4-bytes aligned, which makes the use of structs difficult
478 remaining = eocd->cdir_size;
481 for (ind = 0; ind < eocd->nbentries; ind++)
483 size_t namesize, count;
485 // Checking the remaining size
486 if (remaining < ZIP_CDIR_CHUNK_BASE_SIZE)
488 Mem_Free (central_dir);
491 remaining -= ZIP_CDIR_CHUNK_BASE_SIZE;
494 if (BuffBigLong (ptr) != ZIP_CDIR_HEADER)
496 Mem_Free (central_dir);
500 namesize = BuffLittleShort (&ptr[28]); // filename length
502 // Check encryption, compression, and attributes
503 // 1st uint8 : general purpose bit flag
504 // Check bits 0 (encryption), 3 (data descriptor after the file), and 5 (compressed patched data (?))
505 // 2nd uint8 : external file attributes
506 // Check bits 3 (file is a directory) and 5 (file is a volume (?))
507 if ((ptr[8] & 0x29) == 0 && (ptr[38] & 0x18) == 0)
509 // Still enough bytes for the name?
510 if ((size_t) remaining < namesize || namesize >= sizeof (*pack->files))
512 Mem_Free (central_dir);
516 // WinZip doesn't use the "directory" attribute, so we need to check the name directly
517 if (ptr[ZIP_CDIR_CHUNK_BASE_SIZE + namesize - 1] != '/')
519 char filename [sizeof (pack->files[0].name)];
520 size_t offset, packsize, realsize;
523 // Extract the name (strip it if necessary)
524 if (namesize >= sizeof (filename))
525 namesize = sizeof (filename) - 1;
526 memcpy (filename, &ptr[ZIP_CDIR_CHUNK_BASE_SIZE], namesize);
527 filename[namesize] = '\0';
529 if (BuffLittleShort (&ptr[10]))
530 flags = FILE_FLAG_DEFLATED;
533 offset = BuffLittleLong (&ptr[42]);
534 packsize = BuffLittleLong (&ptr[20]);
535 realsize = BuffLittleLong (&ptr[24]);
536 FS_AddFileToPack (filename, pack, offset, packsize, realsize, flags);
540 // Skip the name, additionnal field, and comment
541 // 1er uint16 : extra field length
542 // 2eme uint16 : file comment length
543 count = namesize + BuffLittleShort (&ptr[30]) + BuffLittleShort (&ptr[32]);
544 ptr += ZIP_CDIR_CHUNK_BASE_SIZE + count;
548 // If the package is empty, central_dir is NULL here
549 if (central_dir != NULL)
550 Mem_Free (central_dir);
551 return pack->numfiles;
559 Create a package entry associated with a PK3 file
562 pack_t *FS_LoadPackPK3 (const char *packfile)
564 #ifdef FS_USESYSCALLS
569 pk3_endOfCentralDir_t eocd;
573 #ifdef FS_USESYSCALLS
574 packhandle = open (packfile, O_RDONLY | O_BINARY);
578 packhandle = fopen (packfile, "rb");
583 if (! PK3_GetEndOfCentralDir (packfile, packhandle, &eocd))
584 Sys_Error ("%s is not a PK3 file", packfile);
586 // Multi-volume ZIP archives are NOT allowed
587 if (eocd.disknum != 0 || eocd.cdir_disknum != 0)
588 Sys_Error ("%s is a multi-volume ZIP archive", packfile);
590 // We only need to do this test if MAX_FILES_IN_PACK is lesser than 65535
591 // since eocd.nbentries is an unsigned 16 bits integer
592 #if MAX_FILES_IN_PACK < 65535
593 if (eocd.nbentries > MAX_FILES_IN_PACK)
594 Sys_Error ("%s contains too many files (%hu)", packfile, eocd.nbentries);
597 // Create a package structure in memory
598 pack = Mem_Alloc (pak_mempool, sizeof (pack_t));
599 pack->ignorecase = true; // PK3 ignores case
600 strlcpy (pack->filename, packfile, sizeof (pack->filename));
601 pack->handle = packhandle;
602 pack->numfiles = eocd.nbentries;
603 pack->mempool = Mem_AllocPool (packfile, 0, NULL);
604 pack->files = Mem_Alloc (pack->mempool, eocd.nbentries * sizeof(packfile_t));
605 pack->next = packlist;
608 real_nb_files = PK3_BuildFileList (pack, &eocd);
609 if (real_nb_files < 0)
610 Sys_Error ("%s is not a valid PK3 file", packfile);
612 Con_Printf("Added packfile %s (%i files)\n", packfile, real_nb_files);
619 PK3_GetTrueFileOffset
621 Find where the true file data offset is
624 void PK3_GetTrueFileOffset (packfile_t *file, pack_t *pack)
626 qbyte buffer [ZIP_LOCAL_CHUNK_BASE_SIZE];
630 if (file->flags & FILE_FLAG_TRUEOFFS)
633 // Load the local file description
634 #ifdef FS_USESYSCALLS
635 lseek (pack->handle, file->offset, SEEK_SET);
636 count = read (pack->handle, buffer, ZIP_LOCAL_CHUNK_BASE_SIZE);
638 fseek (pack->handle, file->offset, SEEK_SET);
639 count = fread (buffer, 1, ZIP_LOCAL_CHUNK_BASE_SIZE, pack->handle);
641 if (count != ZIP_LOCAL_CHUNK_BASE_SIZE || BuffBigLong (buffer) != ZIP_DATA_HEADER)
642 Sys_Error ("Can't retrieve file %s in package %s", file->name, pack->filename);
644 // Skip name and extra field
645 file->offset += BuffLittleShort (&buffer[26]) + BuffLittleShort (&buffer[28]) + ZIP_LOCAL_CHUNK_BASE_SIZE;
647 file->flags |= FILE_FLAG_TRUEOFFS;
652 =============================================================================
654 OTHER PRIVATE FUNCTIONS
656 =============================================================================
664 Add a file to the list of files contained into a package
667 static packfile_t* FS_AddFileToPack (const char* name, pack_t* pack,
668 size_t offset, size_t packsize,
669 size_t realsize, file_flags_t flags)
671 int (*strcmp_funct) (const char* str1, const char* str2);
672 int left, right, middle;
675 strcmp_funct = pack->ignorecase ? strcasecmp : strcmp;
677 // Look for the slot we should put that file into (binary search)
679 right = pack->numfiles - 1;
680 while (left <= right)
684 middle = (left + right) / 2;
685 diff = strcmp_funct (pack->files[middle].name, name);
687 // If we found the file, there's a problem
689 Sys_Error ("Package %s contains the file %s several times\n",
690 pack->filename, name);
692 // If we're too far in the list
699 // We have to move the right of the list by one slot to free the one we need
700 file = &pack->files[left];
701 memmove (file + 1, file, (pack->numfiles - left) * sizeof (*file));
704 strlcpy (file->name, name, sizeof (file->name));
705 file->offset = offset;
706 file->packsize = packsize;
707 file->realsize = realsize;
718 Only used for FS_Open.
721 void FS_CreatePath (char *path)
725 for (ofs = path+1 ; *ofs ; ofs++)
727 if (*ofs == '/' || *ofs == '\\')
729 // create the directory
745 void FS_Path_f (void)
749 Con_Print("Current search path:\n");
750 for (s=fs_searchpaths ; s ; s=s->next)
754 Con_Printf("%s (%i files)\n", s->pack->filename, s->pack->numfiles);
757 Con_Printf("%s\n", s->filename);
766 Takes an explicit (not game tree related) path to a pak file.
768 Loads the header and directory, adding the files at the beginning
769 of the list so they override previous pack files.
772 pack_t *FS_LoadPackPAK (const char *packfile)
774 dpackheader_t header;
776 #ifdef FS_USESYSCALLS
782 dpackfile_t *info; // temporary alloc, allowing huge pack directories
784 #ifdef FS_USESYSCALLS
785 packhandle = open (packfile, O_RDONLY | O_BINARY);
788 read (packhandle, (void *)&header, sizeof(header));
790 packhandle = fopen (packfile, "rb");
793 fread ((void *)&header, 1, sizeof(header), packhandle);
795 if (memcmp(header.id, "PACK", 4))
796 Sys_Error ("%s is not a packfile", packfile);
797 header.dirofs = LittleLong (header.dirofs);
798 header.dirlen = LittleLong (header.dirlen);
800 if (header.dirlen % sizeof(dpackfile_t))
801 Sys_Error ("%s has an invalid directory size", packfile);
803 numpackfiles = header.dirlen / sizeof(dpackfile_t);
805 if (numpackfiles > MAX_FILES_IN_PACK)
806 Sys_Error ("%s has %i files", packfile, numpackfiles);
808 pack = Mem_Alloc(pak_mempool, sizeof (pack_t));
809 pack->ignorecase = false; // PAK is case sensitive
810 strlcpy (pack->filename, packfile, sizeof (pack->filename));
811 pack->handle = packhandle;
813 pack->mempool = Mem_AllocPool(packfile, 0, NULL);
814 pack->files = Mem_Alloc(pack->mempool, numpackfiles * sizeof(packfile_t));
815 pack->next = packlist;
818 info = Mem_Alloc(tempmempool, sizeof(*info) * numpackfiles);
819 #ifdef FS_USESYSCALLS
820 lseek (packhandle, header.dirofs, SEEK_SET);
821 read (packhandle, (void *)info, header.dirlen);
823 fseek (packhandle, header.dirofs, SEEK_SET);
824 fread ((void *)info, 1, header.dirlen, packhandle);
827 // parse the directory
828 for (i = 0;i < numpackfiles;i++)
830 size_t offset = LittleLong (info[i].filepos);
831 size_t size = LittleLong (info[i].filelen);
833 FS_AddFileToPack (info[i].name, pack, offset, size, size, FILE_FLAG_TRUEOFFS);
838 Con_Printf("Added packfile %s (%i files)\n", packfile, numpackfiles);
847 Sets fs_gamedir, adds the directory to the head of the path,
848 then loads and adds pak1.pak pak2.pak ...
851 void FS_AddGameDirectory (char *dir)
853 stringlist_t *list, *current;
854 searchpath_t *search;
856 char pakfile[MAX_OSPATH];
858 strlcpy (fs_gamedir, dir, sizeof (fs_gamedir));
860 list = listdirectory(dir);
862 // add any PAK package in the directory
863 for (current = list;current;current = current->next)
865 if (matchpattern(current->text, "*.pak", true))
867 snprintf (pakfile, sizeof (pakfile), "%s/%s", dir, current->text);
868 pak = FS_LoadPackPAK (pakfile);
871 search = Mem_Alloc(pak_mempool, sizeof(searchpath_t));
873 search->next = fs_searchpaths;
874 fs_searchpaths = search;
877 Con_Printf("unable to load pak \"%s\"\n", pakfile);
881 // add any PK3 package in the director
882 for (current = list;current;current = current->next)
884 if (matchpattern(current->text, "*.pk3", true))
886 snprintf (pakfile, sizeof (pakfile), "%s/%s", dir, current->text);
887 pak = FS_LoadPackPK3 (pakfile);
890 search = Mem_Alloc(pak_mempool, sizeof(searchpath_t));
892 search->next = fs_searchpaths;
893 fs_searchpaths = search;
896 Con_Printf("unable to load pak \"%s\"\n", pakfile);
901 // Add the directory to the search path
902 // (unpacked files have the priority over packed files)
903 search = Mem_Alloc(pak_mempool, sizeof(searchpath_t));
904 strlcpy (search->filename, dir, sizeof (search->filename));
905 search->next = fs_searchpaths;
906 fs_searchpaths = search;
912 FS_AddHomeAsGameDirectory
914 Use ~/.games/darkplaces/dir as fs_gamedir
917 void FS_AddHomeAsGameDirectory (const char *dir)
920 char *homedir=getenv("HOME");
921 char gdir[MAX_OSPATH];
924 int len = snprintf(gdir,sizeof(gdir),"%s/.darkplaces/%s/", homedir, dir);
925 Con_Printf("using %s for writing\n",gdir);
926 FS_CreatePath (gdir);
928 if ((len > 0) && (len < sizeof(gdir)) && (gdir[len-1] == '/'))
931 strncpy(fs_gamedir,gdir,sizeof(fs_gamedir)-1);
932 fs_gamedir[sizeof(fs_gamedir)-1] = 0;
934 FS_AddGameDirectory (gdir);
945 char *FS_FileExtension (const char *in)
947 static char exten[8];
948 const char *slash, *backslash, *colon, *dot, *separator;
951 slash = strrchr(in, '/');
952 backslash = strrchr(in, '\\');
953 colon = strrchr(in, ':');
954 dot = strrchr(in, '.');
956 if (separator < backslash)
957 separator = backslash;
958 if (separator < colon)
960 if (dot == NULL || dot < separator)
963 for (i = 0;i < 7 && dot[i];i++)
978 searchpath_t *search;
980 fs_mempool = Mem_AllocPool("file management", 0, NULL);
981 pak_mempool = Mem_AllocPool("paks", 0, NULL);
983 Cvar_RegisterVariable (&scr_screenshot_name);
985 Cmd_AddCommand ("path", FS_Path_f);
986 Cmd_AddCommand ("dir", FS_Dir_f);
987 Cmd_AddCommand ("ls", FS_Ls_f);
989 strcpy(fs_basedir, ".");
990 strcpy(fs_gamedir, ".");
995 // Overrides the system supplied base directory (under GAMENAME)
996 // 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)
997 i = COM_CheckParm ("-basedir");
998 if (i && i < com_argc-1)
1000 strlcpy (fs_basedir, com_argv[i+1], sizeof (fs_basedir));
1001 i = strlen (fs_basedir);
1002 if (i > 0 && (fs_basedir[i-1] == '\\' || fs_basedir[i-1] == '/'))
1003 fs_basedir[i-1] = 0;
1006 // -path <dir or packfile> [<dir or packfile>] ...
1007 // Fully specifies the exact search path, overriding the generated one
1008 // 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)
1009 i = COM_CheckParm ("-path");
1013 while (++i < com_argc)
1015 if (!com_argv[i] || com_argv[i][0] == '+' || com_argv[i][0] == '-')
1018 search = Mem_Alloc(pak_mempool, sizeof(searchpath_t));
1019 if (!strcasecmp (FS_FileExtension(com_argv[i]), "pak"))
1021 search->pack = FS_LoadPackPAK (com_argv[i]);
1023 Sys_Error ("Couldn't load packfile: %s", com_argv[i]);
1025 else if (!strcasecmp (FS_FileExtension (com_argv[i]), "pk3"))
1027 search->pack = FS_LoadPackPK3 (com_argv[i]);
1029 Sys_Error ("Couldn't load packfile: %s", com_argv[i]);
1032 strlcpy (search->filename, com_argv[i], sizeof (search->filename));
1033 search->next = fs_searchpaths;
1034 fs_searchpaths = search;
1039 // start up with GAMENAME by default (id1)
1040 strlcpy (com_modname, GAMENAME, sizeof (com_modname));
1041 FS_AddGameDirectory (va("%s/"GAMENAME, fs_basedir));
1042 FS_AddHomeAsGameDirectory(GAMENAME);
1043 Cvar_SetQuick (&scr_screenshot_name, gamescreenshotname);
1045 // add the game-specific path, if any
1049 strlcpy (com_modname, gamedirname, sizeof (com_modname));
1050 FS_AddGameDirectory (va("%s/%s", fs_basedir, gamedirname));
1051 FS_AddHomeAsGameDirectory(gamedirname);
1055 // Adds basedir/gamedir as an override game
1056 // LordHavoc: now supports multiple -game directories
1057 for (i = 1;i < com_argc;i++)
1061 if (!strcmp (com_argv[i], "-game") && i < com_argc-1)
1065 strlcpy (com_modname, com_argv[i], sizeof (com_modname));
1066 FS_AddGameDirectory (va("%s/%s", fs_basedir, com_argv[i]));
1067 FS_AddHomeAsGameDirectory(com_argv[i]);
1068 Cvar_SetQuick (&scr_screenshot_name, com_modname);
1072 // If "-condebug" is in the command line, remove the previous log file
1073 if (COM_CheckParm ("-condebug") != 0)
1074 unlink (va("%s/qconsole.log", fs_gamedir));
1082 void FS_Shutdown (void)
1084 Mem_FreePool (&pak_mempool);
1085 Mem_FreePool (&fs_mempool);
1089 ====================
1092 Internal function used to create a qfile_t and open the relevant file on disk
1093 ====================
1095 static qfile_t* FS_SysOpen (const char* filepath, const char* mode)
1099 file = Mem_Alloc (fs_mempool, sizeof (*file));
1100 memset (file, 0, sizeof (*file));
1102 #ifdef FS_USESYSCALLS
1103 if (strchr(mode, 'r'))
1104 file->stream = open (filepath, O_RDONLY | O_BINARY);
1105 else if (strchr(mode, 'w'))
1106 file->stream = open (filepath, O_WRONLY | O_BINARY | O_CREAT | O_TRUNC, 0666);
1107 else if (strchr(mode, 'a'))
1108 file->stream = open (filepath, O_RDWR | O_BINARY | O_CREAT | O_APPEND, 0666);
1111 if (file->stream < 0)
1117 file->stream = fopen (filepath, mode);
1134 qfile_t *FS_OpenRead (const char *path, int offs, int len)
1138 file = FS_SysOpen (path, "rb");
1141 Sys_Error ("Couldn't open %s", path);
1146 if (offs < 0 || len < 0)
1148 // We set fs_filesize here for normal files
1149 #ifdef FS_USESYSCALLS
1150 fs_filesize = lseek (file->stream, 0, SEEK_END);
1151 lseek (file->stream, 0, SEEK_SET);
1153 fseek (file->stream, 0, SEEK_END);
1154 fs_filesize = ftell (file->stream);
1155 fseek (file->stream, 0, SEEK_SET);
1161 #ifdef FS_USESYSCALLS
1162 lseek (file->stream, offs, SEEK_SET);
1164 fseek (file->stream, offs, SEEK_SET);
1167 file->flags |= FS_FLAG_PACKED;
1169 file->offset = offs;
1177 ====================
1180 Return true if the path should be rejected due to one of the following:
1181 1: path elements that are non-portable
1182 2: path elements that would allow access to files outside the game directory,
1183 or are just not a good idea for a mod to be using.
1184 ====================
1186 int FS_CheckNastyPath (const char *path)
1188 // Windows: don't allow \ in filenames (windows-only), period.
1189 // (on Windows \ is a directory separator, but / is also supported)
1190 if (strstr(path, "\\"))
1191 return 1; // non-portable
1192 // Mac: don't allow Mac-only filenames - : is a directory separator
1193 // instead of /, but we rely on / working already, so there's no reason to
1194 // support a Mac-only path
1195 // Amiga and Windows: : tries to go to root of drive
1196 if (strstr(path, ":"))
1197 return 1; // non-portable attempt to go to root of drive
1198 // Amiga: // is parent directory
1199 if (strstr(path, "//"))
1200 return 1; // non-portable attempt to go to parent directory
1201 // all: don't allow going to current directory (./) or parent directory (../ or /../)
1202 if (strstr(path, "./"))
1203 return 2; // attempt to go to parent directory
1204 // after all these checks we're pretty sure it's a / separated filename
1205 // and won't do much if any harm
1211 ====================
1214 Look for a file in the packages and in the filesystem
1216 Return the searchpath where the file was found (or NULL)
1217 and the file index in the package if relevant
1218 ====================
1220 static searchpath_t *FS_FindFile (const char *name, int* index, qboolean quiet)
1222 searchpath_t *search;
1225 // search through the path, one element at a time
1226 for (search = fs_searchpaths;search;search = search->next)
1228 // is the element a pak file?
1231 int (*strcmp_funct) (const char* str1, const char* str2);
1232 int left, right, middle;
1235 strcmp_funct = pak->ignorecase ? strcasecmp : strcmp;
1237 // Look for the file (binary search)
1239 right = pak->numfiles - 1;
1240 while (left <= right)
1244 middle = (left + right) / 2;
1245 diff = strcmp_funct (pak->files[middle].name, name);
1251 Con_DPrintf("FS_FindFile: %s in %s\n",
1252 pak->files[middle].name, pak->filename);
1259 // If we're too far in the list
1268 char netpath[MAX_OSPATH];
1269 snprintf(netpath, sizeof(netpath), "%s/%s", search->filename, name);
1270 if (FS_SysFileExists (netpath))
1273 Con_DPrintf("FS_FindFile: %s\n", netpath);
1283 Con_DPrintf("FS_FindFile: can't find %s\n", name);
1295 If the requested file is inside a packfile, a new qfile_t* will be opened
1301 qfile_t *FS_FOpenFile (const char *filename, qboolean quiet)
1303 searchpath_t *search;
1304 packfile_t *packfile;
1308 search = FS_FindFile (filename, &i, quiet);
1317 // Found in the filesystem?
1320 char netpath[MAX_OSPATH];
1321 snprintf(netpath, sizeof(netpath), "%s/%s", search->filename, filename);
1322 return FS_OpenRead(netpath, -1, -1);
1325 // So, we found it in a package...
1326 packfile = &search->pack->files[i];
1328 // If we don't have the true offset, get it now
1329 if (! (packfile->flags & FILE_FLAG_TRUEOFFS))
1330 PK3_GetTrueFileOffset (packfile, search->pack);
1332 // No Zlib DLL = no compressed files
1333 if (!zlib_dll && (packfile->flags & FILE_FLAG_DEFLATED))
1335 Con_Printf("WARNING: can't open the compressed file %s\n"
1336 "You need the Zlib DLL to use compressed files\n",
1342 // open a new file in the pakfile
1343 file = FS_OpenRead (search->pack->filename, packfile->offset, packfile->packsize);
1344 fs_filesize = packfile->realsize;
1346 if (packfile->flags & FILE_FLAG_DEFLATED)
1350 file->flags |= FS_FLAG_DEFLATED;
1352 // We need some more variables
1353 ztk = Mem_Alloc (fs_mempool, sizeof (*file->z));
1355 ztk->real_length = packfile->realsize;
1357 // Initialize zlib stream
1358 ztk->zstream.next_in = ztk->input;
1359 ztk->zstream.avail_in = 0;
1361 /* From Zlib's "unzip.c":
1363 * windowBits is passed < 0 to tell that there is no zlib header.
1364 * Note that in this case inflate *requires* an extra "dummy" byte
1365 * after the compressed stream in order to complete decompression and
1366 * return Z_STREAM_END.
1367 * In unzip, i don't wait absolutely Z_STREAM_END because I known the
1368 * size of both compressed and uncompressed data
1370 if (qz_inflateInit2 (&ztk->zstream, -MAX_WBITS) != Z_OK)
1371 Sys_Error ("inflate init error (file: %s)", filename);
1373 ztk->zstream.next_out = ztk->output;
1374 ztk->zstream.avail_out = sizeof (ztk->output);
1384 =============================================================================
1386 MAIN PUBLIC FUNCTIONS
1388 =============================================================================
1392 ====================
1395 Open a file. The syntax is the same as fopen
1396 ====================
1398 qfile_t* FS_Open (const char* filepath, const char* mode, qboolean quiet)
1400 if (FS_CheckNastyPath(filepath))
1402 Con_Printf("FS_Open(\"%s\", \"%s\", %s): nasty filename rejected\n", filepath, mode, quiet ? "true" : "false");
1406 // If the file is opened in "write" or "append" mode
1407 if (strchr (mode, 'w') || strchr (mode, 'a'))
1409 char real_path [MAX_OSPATH];
1411 // Open the file on disk directly
1412 snprintf (real_path, sizeof (real_path), "%s/%s", fs_gamedir, filepath);
1414 // Create directories up to the file
1415 FS_CreatePath (real_path);
1417 return FS_SysOpen (real_path, mode);
1420 // Else, we look at the various search paths
1421 return FS_FOpenFile (filepath, quiet);
1426 ====================
1430 ====================
1432 int FS_Close (qfile_t* file)
1434 #ifdef FS_USESYSCALLS
1435 if (close (file->stream))
1437 if (fclose (file->stream))
1443 qz_inflateEnd (&file->z->zstream);
1453 ====================
1456 Write "datasize" bytes into a file
1457 ====================
1459 size_t FS_Write (qfile_t* file, const void* data, size_t datasize)
1461 #ifdef FS_USESYSCALLS
1462 return write (file->stream, data, datasize);
1464 return fwrite (data, 1, datasize, file->stream);
1470 ====================
1473 Read up to "buffersize" bytes from a file
1474 ====================
1476 size_t FS_Read (qfile_t* file, void* buffer, size_t buffersize)
1481 // Quick path for unpacked files
1482 if (! (file->flags & FS_FLAG_PACKED))
1483 #ifdef FS_USESYSCALLS
1484 return read (file->stream, buffer, buffersize);
1486 return fread (buffer, 1, buffersize, file->stream);
1489 // If the file isn't compressed
1490 if (! (file->flags & FS_FLAG_DEFLATED))
1492 // We must take care to not read after the end of the file
1493 count = file->length - file->position;
1494 if (buffersize > count)
1497 #ifdef FS_USESYSCALLS
1498 nb = read (file->stream, buffer, buffersize);
1500 nb = fread (buffer, 1, buffersize, file->stream);
1503 file->position += nb;
1507 // If the file is compressed, it's more complicated...
1510 // First, we copy as many bytes as we can from "output"
1511 if (ztk->out_ind < ztk->out_max)
1513 count = ztk->out_max - ztk->out_ind;
1515 nb = (buffersize > count) ? count : buffersize;
1516 memcpy (buffer, &ztk->output[ztk->out_ind], nb);
1518 file->position += nb;
1523 // We cycle through a few operations until we have inflated enough data
1524 while (nb < buffersize)
1526 // NOTE: at this point, "output" should always be empty
1528 // If "input" is also empty, we need to fill it
1529 if (ztk->in_ind == ztk->in_max)
1533 // If we are at the end of the file
1534 if (ztk->out_position == ztk->real_length)
1537 remain = file->length - ztk->in_position;
1538 count = (remain > sizeof (ztk->input)) ? sizeof (ztk->input) : remain;
1539 #ifdef FS_USESYSCALLS
1540 read (file->stream, ztk->input, count);
1542 fread (ztk->input, 1, count, file->stream);
1545 // Update indexes and counters
1547 ztk->in_max = count;
1548 ztk->in_position += count;
1551 // Now that we are sure we have compressed data available, we need to determine
1552 // if it's better to inflate it in "output" or directly in "buffer" (we are in this
1553 // case if we still need more bytes than "output" can contain)
1555 ztk->zstream.next_in = &ztk->input[ztk->in_ind];
1556 ztk->zstream.avail_in = ztk->in_max - ztk->in_ind;
1558 // If output will be able to contain at least 1 more byte than the data we need
1559 if (buffersize - nb < sizeof (ztk->output))
1563 // Inflate the data in "output"
1564 ztk->zstream.next_out = ztk->output;
1565 ztk->zstream.avail_out = sizeof (ztk->output);
1566 error = qz_inflate (&ztk->zstream, Z_SYNC_FLUSH);
1567 if (error != Z_OK && error != Z_STREAM_END)
1568 Sys_Error ("Can't inflate file");
1569 ztk->in_ind = ztk->in_max - ztk->zstream.avail_in;
1570 ztk->out_max = sizeof (ztk->output) - ztk->zstream.avail_out;
1571 ztk->out_position += ztk->out_max;
1573 // Copy the requested data in "buffer" (as much as we can)
1574 count = (buffersize - nb > ztk->out_max) ? ztk->out_max : buffersize - nb;
1575 memcpy (&((qbyte*)buffer)[nb], ztk->output, count);
1576 ztk->out_ind = count;
1579 // Else, we inflate directly in "buffer"
1584 // Inflate the data in "buffer"
1585 ztk->zstream.next_out = &((qbyte*)buffer)[nb];
1586 ztk->zstream.avail_out = buffersize - nb;
1587 error = qz_inflate (&ztk->zstream, Z_SYNC_FLUSH);
1588 if (error != Z_OK && error != Z_STREAM_END)
1589 Sys_Error ("Can't inflate file");
1590 ztk->in_ind = ztk->in_max - ztk->zstream.avail_in;
1592 // Invalidate the output data (for FS_Seek)
1596 // How much data did it inflate?
1597 count = buffersize - nb - ztk->zstream.avail_out;
1598 ztk->out_position += count;
1602 file->position += count;
1610 ====================
1613 Flush the file output stream
1614 ====================
1616 int FS_Flush (qfile_t* file)
1618 #ifdef FS_USESYSCALLS
1621 return fflush (file->stream);
1627 ====================
1630 Print a string into a file
1631 ====================
1633 int FS_Print(qfile_t* file, const char *msg)
1635 return FS_Write(file, msg, strlen(msg));
1639 ====================
1642 Print a string into a file
1643 ====================
1645 int FS_Printf(qfile_t* file, const char* format, ...)
1650 va_start (args, format);
1651 result = FS_VPrintf(file, format, args);
1659 ====================
1662 Print a string into a file
1663 ====================
1665 int FS_VPrintf(qfile_t* file, const char* format, va_list ap)
1667 #ifdef FS_USESYSCALLS
1670 char tempstring[1024];
1671 len = vsnprintf (tempstring, sizeof(tempstring), format, ap);
1672 if (len >= sizeof(tempstring))
1675 char *temp = Mem_Alloc(tempmempool, len + 1);
1676 len = vsnprintf (temp, len + 1, format, ap);
1677 result = write (file->stream, temp, len);
1682 return write (file->stream, tempstring, len);
1685 return vfprintf (file->stream, format, ap);
1691 ====================
1694 Get the next character of a file
1695 ====================
1697 int FS_Getc (qfile_t* file)
1701 if (FS_Read (file, &c, 1) != 1)
1709 ====================
1712 Move the position index in a file
1713 ====================
1715 int FS_Seek (qfile_t* file, long offset, int whence)
1717 // Quick path for unpacked files
1718 if (! (file->flags & FS_FLAG_PACKED))
1719 #ifdef FS_USESYSCALLS
1721 if (lseek (file->stream, offset, whence) == -1)
1726 return fseek (file->stream, offset, whence);
1729 // Seeking in compressed files is more a hack than anything else,
1730 // but we need to support it, so here it is.
1731 if (file->flags & FS_FLAG_DEFLATED)
1733 ztoolkit_t *ztk = file->z;
1734 qbyte buffer [sizeof (ztk->output)]; // it's big to force inflating into buffer directly
1739 offset += file->position;
1746 offset += ztk->real_length;
1752 if (offset < 0 || offset > (long) ztk->real_length)
1755 // If we need to go back in the file
1756 if (offset <= (long) file->position)
1758 // If we still have the data we need in the output buffer
1759 if (file->position - offset <= ztk->out_ind)
1761 ztk->out_ind -= file->position - offset;
1762 file->position = offset;
1766 // Else, we restart from the beginning of the file
1769 ztk->in_position = 0;
1772 ztk->out_position = 0;
1774 #ifdef FS_USESYSCALLS
1775 lseek (file->stream, file->offset, SEEK_SET);
1777 fseek (file->stream, file->offset, SEEK_SET);
1780 // Reset the Zlib stream
1781 ztk->zstream.next_in = ztk->input;
1782 ztk->zstream.avail_in = 0;
1783 qz_inflateReset (&ztk->zstream);
1786 // Skip all data until we reach the requested offset
1787 while ((long) file->position < offset)
1789 size_t diff = offset - file->position;
1792 count = (diff > sizeof (buffer)) ? sizeof (buffer) : diff;
1793 len = FS_Read (file, buffer, count);
1801 // Packed files receive a special treatment too, because
1802 // we need to make sure it doesn't go outside of the file
1806 offset += file->position;
1813 offset += file->length;
1819 if (offset < 0 || offset > (long) file->length)
1822 #ifdef FS_USESYSCALLS
1823 if (lseek (file->stream, file->offset + offset, SEEK_SET) == -1)
1826 if (fseek (file->stream, file->offset + offset, SEEK_SET) == -1)
1829 file->position = offset;
1835 ====================
1838 Give the current position in a file
1839 ====================
1841 long FS_Tell (qfile_t* file)
1843 if (file->flags & FS_FLAG_PACKED)
1844 return file->position;
1846 #ifdef FS_USESYSCALLS
1847 return lseek (file->stream, 0, SEEK_CUR);
1849 return ftell (file->stream);
1855 ====================
1858 Extract a line from a file
1859 ====================
1861 char* FS_Gets (qfile_t* file, char* buffer, int buffersize)
1865 // Quick path for unpacked files
1866 #ifndef FS_USESYSCALLS
1867 if (! (file->flags & FS_FLAG_PACKED))
1868 return fgets (buffer, buffersize, file->stream);
1871 for (ind = 0; ind < (size_t) buffersize - 1; ind++)
1873 int c = FS_Getc (file);
1888 buffer[ind + 1] = '\0';
1897 buffer[buffersize - 1] = '\0';
1906 Dynamic length version of fgets. DO NOT free the buffer.
1909 char *FS_Getline (qfile_t *file)
1911 static int size = 256;
1912 static char *buf = 0;
1917 buf = Mem_Alloc (fs_mempool, size);
1919 if (!FS_Gets (file, buf, size))
1923 while (buf[len - 1] != '\n' && buf[len - 1] != '\r')
1925 t = Mem_Alloc (fs_mempool, size + 256);
1926 memcpy(t, buf, size);
1930 if (!FS_Gets (file, buf + len, size - len))
1934 while ((len = strlen(buf)) && (buf[len - 1] == '\n' || buf[len - 1] == '\r'))
1941 ====================
1944 Extract a line from a file
1945 ====================
1947 // FIXME: remove this function?
1948 int FS_Eof (qfile_t* file)
1950 if (file->flags & FS_FLAG_PACKED)
1952 if (file->flags & FS_FLAG_DEFLATED)
1953 return (file->position == file->z->real_length);
1955 return (file->position == file->length);
1958 #ifdef FS_USESYSCALLS
1959 Sys_Error("FS_Eof: not implemented using syscalls\n");
1962 return feof (file->stream);
1971 Filename are relative to the quake directory.
1972 Always appends a 0 byte.
1975 qbyte *FS_LoadFile (const char *path, mempool_t *pool, qboolean quiet)
1980 // look for it in the filesystem or pack files
1981 h = FS_Open (path, "rb", quiet);
1985 buf = Mem_Alloc(pool, fs_filesize+1);
1987 Sys_Error ("FS_LoadFile: not enough available memory for %s (size %i)", path, fs_filesize);
1989 ((qbyte *)buf)[fs_filesize] = 0;
1991 FS_Read (h, buf, fs_filesize);
2002 The filename will be prefixed by the current game directory
2005 qboolean FS_WriteFile (const char *filename, void *data, int len)
2009 handle = FS_Open (filename, "wb", false);
2012 Con_Printf("FS_WriteFile: failed on %s\n", filename);
2016 Con_DPrintf("FS_WriteFile: %s\n", filename);
2017 FS_Write (handle, data, len);
2024 =============================================================================
2026 OTHERS PUBLIC FUNCTIONS
2028 =============================================================================
2036 void FS_StripExtension (const char *in, char *out, size_t size_out)
2043 while (*in && size_out > 1)
2047 else if (*in == '/' || *in == '\\' || *in == ':')
2064 void FS_DefaultExtension (char *path, const char *extension, size_t size_path)
2068 // if path doesn't have a .EXT, append extension
2069 // (extension should include the .)
2070 src = path + strlen(path) - 1;
2072 while (*src != '/' && src != path)
2075 return; // it has an extension
2079 strlcat (path, extension, size_path);
2087 Look for a file in the packages and in the filesystem
2090 qboolean FS_FileExists (const char *filename)
2092 return (FS_FindFile (filename, NULL, true) != NULL);
2100 Look for a file in the filesystem only
2103 qboolean FS_SysFileExists (const char *path)
2108 f = fopen (path, "rb");
2119 if (stat (path,&buf) == -1)
2126 void FS_mkdir (const char *path)
2139 Allocate and fill a search structure with information on matching filenames.
2142 fssearch_t *FS_Search(const char *pattern, int caseinsensitive, int quiet)
2145 searchpath_t *searchpath;
2147 int i, basepathlength, numfiles, numchars;
2148 stringlist_t *dir, *dirfile, *liststart, *listcurrent, *listtemp;
2149 const char *slash, *backslash, *colon, *separator;
2151 char netpath[MAX_OSPATH];
2152 char temp[MAX_OSPATH];
2154 while(!strncmp(pattern, "./", 2))
2156 while(!strncmp(pattern, ".\\", 2))
2163 slash = strrchr(pattern, '/');
2164 backslash = strrchr(pattern, '\\');
2165 colon = strrchr(pattern, ':');
2166 separator = pattern;
2167 if (separator < slash)
2169 if (separator < backslash)
2170 separator = backslash;
2171 if (separator < colon)
2173 basepathlength = separator - pattern;
2174 basepath = Mem_Alloc (tempmempool, basepathlength + 1);
2176 memcpy(basepath, pattern, basepathlength);
2177 basepath[basepathlength] = 0;
2179 // search through the path, one element at a time
2180 for (searchpath = fs_searchpaths;searchpath;searchpath = searchpath->next)
2182 // is the element a pak file?
2183 if (searchpath->pack)
2185 // look through all the pak file elements
2186 pak = searchpath->pack;
2187 for (i = 0;i < pak->numfiles;i++)
2189 strcpy(temp, pak->files[i].name);
2192 if (matchpattern(temp, (char *)pattern, true))
2194 for (listtemp = liststart;listtemp;listtemp = listtemp->next)
2195 if (!strcmp(listtemp->text, temp))
2197 if (listtemp == NULL)
2199 listcurrent = stringlistappend(listcurrent, temp);
2200 if (liststart == NULL)
2201 liststart = listcurrent;
2203 Con_DPrintf("SearchPackFile: %s : %s\n", pak->filename, temp);
2206 // strip off one path element at a time until empty
2207 // this way directories are added to the listing if they match the pattern
2208 slash = strrchr(temp, '/');
2209 backslash = strrchr(temp, '\\');
2210 colon = strrchr(temp, ':');
2212 if (separator < slash)
2214 if (separator < backslash)
2215 separator = backslash;
2216 if (separator < colon)
2218 *((char *)separator) = 0;
2224 // get a directory listing and look at each name
2225 snprintf(netpath, sizeof (netpath), "%s/%s", searchpath->filename, basepath);
2226 if ((dir = listdirectory(netpath)))
2228 for (dirfile = dir;dirfile;dirfile = dirfile->next)
2230 snprintf(temp, sizeof(temp), "%s/%s", basepath, dirfile->text);
2231 if (matchpattern(temp, (char *)pattern, true))
2233 for (listtemp = liststart;listtemp;listtemp = listtemp->next)
2234 if (!strcmp(listtemp->text, temp))
2236 if (listtemp == NULL)
2238 listcurrent = stringlistappend(listcurrent, temp);
2239 if (liststart == NULL)
2240 liststart = listcurrent;
2242 Con_DPrintf("SearchDirFile: %s\n", temp);
2253 liststart = stringlistsort(liststart);
2256 for (listtemp = liststart;listtemp;listtemp = listtemp->next)
2259 numchars += strlen(listtemp->text) + 1;
2261 search = Z_Malloc(sizeof(fssearch_t) + numchars + numfiles * sizeof(char *));
2262 search->filenames = (char **)((char *)search + sizeof(fssearch_t));
2263 search->filenamesbuffer = (char *)((char *)search + sizeof(fssearch_t) + numfiles * sizeof(char *));
2264 search->numfilenames = numfiles;
2267 for (listtemp = liststart;listtemp;listtemp = listtemp->next)
2269 search->filenames[numfiles] = search->filenamesbuffer + numchars;
2270 strcpy(search->filenames[numfiles], listtemp->text);
2272 numchars += strlen(listtemp->text) + 1;
2275 stringlistfree(liststart);
2282 void FS_FreeSearch(fssearch_t *search)
2287 extern int con_linewidth;
2288 int FS_ListDirectory(const char *pattern, int oneperline)
2299 search = FS_Search(pattern, true, true);
2302 numfiles = search->numfilenames;
2305 // FIXME: the names could be added to one column list and then
2306 // gradually shifted into the next column if they fit, and then the
2307 // next to make a compact variable width listing but it's a lot more
2309 // find width for columns
2311 for (i = 0;i < numfiles;i++)
2313 l = strlen(search->filenames[i]);
2314 if (columnwidth < l)
2317 // count the spacing character
2319 // calculate number of columns
2320 numcolumns = con_linewidth / columnwidth;
2321 // don't bother with the column printing if it's only one column
2322 if (numcolumns >= 2)
2324 numlines = (numfiles + numcolumns - 1) / numcolumns;
2325 for (i = 0;i < numlines;i++)
2328 for (k = 0;k < numcolumns;k++)
2330 l = i * numcolumns + k;
2333 name = search->filenames[l];
2334 for (j = 0;name[j] && j < (int)sizeof(linebuf) - 1;j++)
2335 linebuf[linebufpos++] = name[j];
2336 // space out name unless it's the last on the line
2337 if (k < (numcolumns - 1) && l < (numfiles - 1))
2338 for (;j < columnwidth && j < (int)sizeof(linebuf) - 1;j++)
2339 linebuf[linebufpos++] = ' ';
2342 linebuf[linebufpos] = 0;
2343 Con_Printf("%s\n", linebuf);
2350 for (i = 0;i < numfiles;i++)
2351 Con_Printf("%s\n", search->filenames[i]);
2352 FS_FreeSearch(search);
2356 static void FS_ListDirectoryCmd (const char* cmdname, int oneperline)
2358 const char *pattern;
2361 Con_Printf("usage:\n%s [path/pattern]\n", cmdname);
2364 if (Cmd_Argc() == 2)
2365 pattern = Cmd_Argv(1);
2368 if (!FS_ListDirectory(pattern, oneperline))
2369 Con_Print("No files found.\n");
2374 FS_ListDirectoryCmd("dir", true);
2379 FS_ListDirectoryCmd("ls", false);