4 Copyright (C) 2003-2005 Mathieu Olivier
6 This program is free software; you can redistribute it and/or
7 modify it under the terms of the GNU General Public License
8 as published by the Free Software Foundation; either version 2
9 of the License, or (at your option) any later version.
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
15 See the GNU General Public License for more details.
17 You should have received a copy of the GNU General Public License
18 along with this program; if not, write to:
20 Free Software Foundation, Inc.
21 59 Temple Place - Suite 330
22 Boston, MA 02111-1307, USA
35 # include <sys/stat.h>
41 // Win32 requires us to add O_BINARY, but the other OSes don't have it
46 // In case the system doesn't support the O_NONBLOCK flag
54 All of Quake's data access is through a hierchal file system, but the contents
55 of the file system can be transparently merged from several sources.
57 The "base directory" is the path to the directory holding the quake.exe and
58 all game directories. The sys_* files pass this to host_init in
59 quakeparms_t->basedir. This can be overridden with the "-basedir" command
60 line parm to allow code debugging in a different directory. The base
61 directory is only used during filesystem initialization.
63 The "game directory" is the first tree on the search path and directory that
64 all generated files (savegames, screenshots, demos, config files) will be
65 saved to. This can be overridden with the "-game" command line parameter.
66 The game directory can never be changed while quake is executing. This is a
67 precaution against having a malicious server instruct clients to write files
68 over areas they shouldn't.
74 =============================================================================
78 =============================================================================
81 // Magic numbers of a ZIP file (big-endian format)
82 #define ZIP_DATA_HEADER 0x504B0304 // "PK\3\4"
83 #define ZIP_CDIR_HEADER 0x504B0102 // "PK\1\2"
84 #define ZIP_END_HEADER 0x504B0506 // "PK\5\6"
86 // Other constants for ZIP files
87 #define ZIP_MAX_COMMENTS_SIZE ((unsigned short)0xFFFF)
88 #define ZIP_END_CDIR_SIZE 22
89 #define ZIP_CDIR_CHUNK_BASE_SIZE 46
90 #define ZIP_LOCAL_CHUNK_BASE_SIZE 30
92 // Zlib constants (from zlib.h)
93 #define Z_SYNC_FLUSH 2
96 #define Z_STREAM_END 1
97 #define ZLIB_VERSION "1.2.3"
99 // Uncomment the following line if the zlib DLL you have still uses
100 // the 1.1.x series calling convention on Win32 (WINAPI)
101 //#define ZLIB_USES_WINAPI
105 =============================================================================
109 =============================================================================
112 // Zlib stream (from zlib.h)
113 // Warning: some pointers we don't use directly have
114 // been cast to "void*" for a matter of simplicity
117 qbyte *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 qbyte *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 qbyte 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 qbyte 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 packfile_t* FS_AddFileToPack (const char* name, pack_t* pack,
251 fs_offset_t offset, fs_offset_t packsize,
252 fs_offset_t realsize, int flags);
256 =============================================================================
260 =============================================================================
263 mempool_t *fs_mempool;
265 fs_offset_t fs_filesize;
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"};
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 long filesize, maxsize;
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 = (qbyte *)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 qbyte *central_dir, *ptr;
449 fs_offset_t remaining;
451 // Load the central directory in memory
452 central_dir = (qbyte *)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", 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", 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)", 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", 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 qbyte 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", 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", 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", packfile);
782 numpackfiles = header.dirlen / sizeof(dpackfile_t);
784 if (numpackfiles > MAX_FILES_IN_PACK)
786 Con_Printf ("%s has %i files", packfile, numpackfiles);
791 pack = (pack_t *)Mem_Alloc(fs_mempool, sizeof (pack_t));
792 pack->ignorecase = false; // PAK is case sensitive
793 strlcpy (pack->filename, packfile, sizeof (pack->filename));
794 pack->handle = packhandle;
796 pack->files = (packfile_t *)Mem_Alloc(fs_mempool, numpackfiles * sizeof(packfile_t));
797 pack->next = packlist;
800 info = (dpackfile_t *)Mem_Alloc(tempmempool, sizeof(*info) * numpackfiles);
801 lseek (packhandle, header.dirofs, SEEK_SET);
802 read (packhandle, (void *)info, header.dirlen);
804 // parse the directory
805 for (i = 0;i < numpackfiles;i++)
807 fs_offset_t offset = LittleLong (info[i].filepos);
808 fs_offset_t size = LittleLong (info[i].filelen);
810 FS_AddFileToPack (info[i].name, pack, offset, size, size, PACKFILE_FLAG_TRUEOFFS);
815 Con_Printf("Added packfile %s (%i files)\n", packfile, numpackfiles);
824 Sets fs_gamedir, adds the directory to the head of the path,
825 then loads and adds pak1.pak pak2.pak ...
828 void FS_AddGameDirectory (const char *dir)
830 stringlist_t *list, *current;
831 searchpath_t *search;
833 char pakfile[MAX_OSPATH];
835 strlcpy (fs_gamedir, dir, sizeof (fs_gamedir));
837 list = listdirectory(dir);
839 // add any PAK package in the directory
840 for (current = list;current;current = current->next)
842 if (matchpattern(current->text, "*.pak", true))
844 dpsnprintf (pakfile, sizeof (pakfile), "%s%s", dir, current->text);
845 pak = FS_LoadPackPAK (pakfile);
848 search = (searchpath_t *)Mem_Alloc(fs_mempool, sizeof(searchpath_t));
850 search->next = fs_searchpaths;
851 fs_searchpaths = search;
854 Con_Printf("unable to load pak \"%s\"\n", pakfile);
858 // add any PK3 package in the director
859 for (current = list;current;current = current->next)
861 if (matchpattern(current->text, "*.pk3", true))
863 dpsnprintf (pakfile, sizeof (pakfile), "%s%s", dir, current->text);
864 pak = FS_LoadPackPK3 (pakfile);
867 search = (searchpath_t *)Mem_Alloc(fs_mempool, sizeof(searchpath_t));
869 search->next = fs_searchpaths;
870 fs_searchpaths = search;
873 Con_Printf("unable to load pak \"%s\"\n", pakfile);
878 // Add the directory to the search path
879 // (unpacked files have the priority over packed files)
880 search = (searchpath_t *)Mem_Alloc(fs_mempool, sizeof(searchpath_t));
881 strlcpy (search->filename, dir, sizeof (search->filename));
882 search->next = fs_searchpaths;
883 fs_searchpaths = search;
892 void FS_AddGameHierarchy (const char *dir)
898 // Add the common game directory
899 FS_AddGameDirectory (va("%s/%s/", fs_basedir, dir));
902 // Add the personal game directory
903 homedir = getenv ("HOME");
904 if (homedir != NULL && homedir[0] != '\0')
905 FS_AddGameDirectory (va("%s/.%s/%s/", homedir, gameuserdirname, dir));
915 static const char *FS_FileExtension (const char *in)
917 const char *separator, *backslash, *colon, *dot;
919 separator = strrchr(in, '/');
920 backslash = strrchr(in, '\\');
921 if (separator < backslash)
922 separator = backslash;
923 colon = strrchr(in, ':');
924 if (separator < colon)
927 dot = strrchr(in, '.');
928 if (dot == NULL || dot < separator)
943 searchpath_t *search;
945 fs_mempool = Mem_AllocPool("file management", 0, NULL);
947 strcpy(fs_basedir, ".");
948 strcpy(fs_gamedir, "");
951 // FIXME: is there a better way to find the directory outside the .app?
952 if (strstr(com_argv[0], ".app/"))
956 split = strstr(com_argv[0], ".app/");
957 while (split > com_argv[0] && *split != '/')
959 strlcpy(fs_basedir, com_argv[0], sizeof(fs_basedir));
960 fs_basedir[split - com_argv[0]] = 0;
967 // Overrides the system supplied base directory (under GAMENAME)
968 // 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)
969 i = COM_CheckParm ("-basedir");
970 if (i && i < com_argc-1)
972 strlcpy (fs_basedir, com_argv[i+1], sizeof (fs_basedir));
973 i = (int)strlen (fs_basedir);
974 if (i > 0 && (fs_basedir[i-1] == '\\' || fs_basedir[i-1] == '/'))
978 // -path <dir or packfile> [<dir or packfile>] ...
979 // Fully specifies the exact search path, overriding the generated one
980 // 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)
981 i = COM_CheckParm ("-path");
985 while (++i < com_argc)
987 if (!com_argv[i] || com_argv[i][0] == '+' || com_argv[i][0] == '-')
990 search = (searchpath_t *)Mem_Alloc(fs_mempool, sizeof(searchpath_t));
991 if (!strcasecmp (FS_FileExtension(com_argv[i]), "pak"))
993 search->pack = FS_LoadPackPAK (com_argv[i]);
996 Con_Printf ("Couldn't load packfile: %s", com_argv[i]);
1001 else if (!strcasecmp (FS_FileExtension (com_argv[i]), "pk3"))
1003 search->pack = FS_LoadPackPK3 (com_argv[i]);
1006 Con_Printf ("Couldn't load packfile: %s", com_argv[i]);
1012 strlcpy (search->filename, com_argv[i], sizeof (search->filename));
1013 search->next = fs_searchpaths;
1014 fs_searchpaths = search;
1019 // add the game-specific paths
1020 // gamedirname1 (typically id1)
1021 FS_AddGameHierarchy (gamedirname1);
1023 // add the game-specific path, if any
1027 FS_AddGameHierarchy (gamedirname2);
1030 // set the com_modname (reported in server info)
1031 strlcpy(com_modname, gamedirname1, sizeof(com_modname));
1034 // Adds basedir/gamedir as an override game
1035 // LordHavoc: now supports multiple -game directories
1036 for (i = 1;i < com_argc;i++)
1040 if (!strcmp (com_argv[i], "-game") && i < com_argc-1)
1044 FS_AddGameHierarchy (com_argv[i]);
1045 // update the com_modname
1046 strlcpy (com_modname, com_argv[i], sizeof (com_modname));
1050 // If "-condebug" is in the command line, remove the previous log file
1051 if (COM_CheckParm ("-condebug") != 0)
1052 unlink (va("%s/qconsole.log", fs_gamedir));
1055 void FS_Init_Commands(void)
1057 Cvar_RegisterVariable (&scr_screenshot_name);
1059 Cmd_AddCommand ("path", FS_Path_f);
1060 Cmd_AddCommand ("dir", FS_Dir_f);
1061 Cmd_AddCommand ("ls", FS_Ls_f);
1063 // set the default screenshot name to either the mod name or the
1064 // gamemode screenshot name
1066 Cvar_SetQuick (&scr_screenshot_name, com_modname);
1068 Cvar_SetQuick (&scr_screenshot_name, gamescreenshotname);
1076 void FS_Shutdown (void)
1078 Mem_FreePool (&fs_mempool);
1082 ====================
1085 Internal function used to create a qfile_t and open the relevant non-packed file on disk
1086 ====================
1088 static qfile_t* FS_SysOpen (const char* filepath, const char* mode, qboolean nonblocking)
1094 // Parse the mode string
1103 opt = O_CREAT | O_TRUNC;
1107 opt = O_CREAT | O_APPEND;
1110 Con_Printf ("FS_SysOpen(%s, %s): invalid mode\n", filepath, mode);
1113 for (ind = 1; mode[ind] != '\0'; ind++)
1124 Con_Printf ("FS_SysOpen(%s, %s): unknown character in mode (%c)\n",
1125 filepath, mode, mode[ind]);
1132 file = (qfile_t *)Mem_Alloc (fs_mempool, sizeof (*file));
1133 memset (file, 0, sizeof (*file));
1136 file->handle = open (filepath, mod | opt, 0666);
1137 if (file->handle < 0)
1143 file->real_length = lseek (file->handle, 0, SEEK_END);
1145 // For files opened in append mode, we start at the end of the file
1147 file->position = file->real_length;
1149 lseek (file->handle, 0, SEEK_SET);
1159 Open a packed file using its package file descriptor
1162 qfile_t *FS_OpenPackedFile (pack_t* pack, int pack_ind)
1168 pfile = &pack->files[pack_ind];
1172 // If we don't have the true offset, get it now
1173 if (! (pfile->flags & PACKFILE_FLAG_TRUEOFFS))
1174 if (!PK3_GetTrueFileOffset (pfile, pack))
1177 // No Zlib DLL = no compressed files
1178 if (!zlib_dll && (pfile->flags & PACKFILE_FLAG_DEFLATED))
1180 Con_Printf("WARNING: can't open the compressed file %s\n"
1181 "You need the Zlib DLL to use compressed files\n",
1186 // LordHavoc: lseek affects all duplicates of a handle so we do it before
1187 // the dup() call to avoid having to close the dup_handle on error here
1188 if (lseek (pack->handle, pfile->offset, SEEK_SET) == -1)
1190 Con_Printf ("FS_OpenPackedFile: can't lseek to %s in %s (offset: %d)",
1191 pfile->name, pack->filename, pfile->offset);
1195 dup_handle = dup (pack->handle);
1198 Con_Printf ("FS_OpenPackedFile: can't dup package's handle (pack: %s)", pack->filename);
1202 file = (qfile_t *)Mem_Alloc (fs_mempool, sizeof (*file));
1203 memset (file, 0, sizeof (*file));
1204 file->handle = dup_handle;
1205 file->flags = QFILE_FLAG_PACKED;
1206 file->real_length = pfile->realsize;
1207 file->offset = pfile->offset;
1211 if (pfile->flags & PACKFILE_FLAG_DEFLATED)
1215 file->flags |= QFILE_FLAG_DEFLATED;
1217 // We need some more variables
1218 ztk = (ztoolkit_t *)Mem_Alloc (fs_mempool, sizeof (*ztk));
1220 ztk->comp_length = pfile->packsize;
1222 // Initialize zlib stream
1223 ztk->zstream.next_in = ztk->input;
1224 ztk->zstream.avail_in = 0;
1226 /* From Zlib's "unzip.c":
1228 * windowBits is passed < 0 to tell that there is no zlib header.
1229 * Note that in this case inflate *requires* an extra "dummy" byte
1230 * after the compressed stream in order to complete decompression and
1231 * return Z_STREAM_END.
1232 * In unzip, i don't wait absolutely Z_STREAM_END because I known the
1233 * size of both compressed and uncompressed data
1235 if (qz_inflateInit2 (&ztk->zstream, -MAX_WBITS) != Z_OK)
1237 Con_Printf ("FS_OpenPackedFile: inflate init error (file: %s)", pfile->name);
1243 ztk->zstream.next_out = file->buff;
1244 ztk->zstream.avail_out = sizeof (file->buff);
1249 fs_filesize = pfile->realsize;
1255 ====================
1258 Return true if the path should be rejected due to one of the following:
1259 1: path elements that are non-portable
1260 2: path elements that would allow access to files outside the game directory,
1261 or are just not a good idea for a mod to be using.
1262 ====================
1264 int FS_CheckNastyPath (const char *path)
1266 // Windows: don't allow \ in filenames (windows-only), period.
1267 // (on Windows \ is a directory separator, but / is also supported)
1268 if (strstr(path, "\\"))
1269 return 1; // non-portable
1271 // Mac: don't allow Mac-only filenames - : is a directory separator
1272 // instead of /, but we rely on / working already, so there's no reason to
1273 // support a Mac-only path
1274 // Amiga and Windows: : tries to go to root of drive
1275 if (strstr(path, ":"))
1276 return 1; // non-portable attempt to go to root of drive
1278 // Amiga: // is parent directory
1279 if (strstr(path, "//"))
1280 return 1; // non-portable attempt to go to parent directory
1282 // all: don't allow going to current directory (./) or parent directory (../ or /../)
1283 if (strstr(path, "./"))
1284 return 2; // attempt to go outside the game directory
1286 // Windows and UNIXes: don't allow absolute paths
1288 return 2; // attempt to go outside the game directory
1290 // after all these checks we're pretty sure it's a / separated filename
1291 // and won't do much if any harm
1297 ====================
1300 Look for a file in the packages and in the filesystem
1302 Return the searchpath where the file was found (or NULL)
1303 and the file index in the package if relevant
1304 ====================
1306 static searchpath_t *FS_FindFile (const char *name, int* index, qboolean quiet)
1308 searchpath_t *search;
1311 // search through the path, one element at a time
1312 for (search = fs_searchpaths;search;search = search->next)
1314 // is the element a pak file?
1317 int (*strcmp_funct) (const char* str1, const char* str2);
1318 int left, right, middle;
1321 strcmp_funct = pak->ignorecase ? strcasecmp : strcmp;
1323 // Look for the file (binary search)
1325 right = pak->numfiles - 1;
1326 while (left <= right)
1330 middle = (left + right) / 2;
1331 diff = strcmp_funct (pak->files[middle].name, name);
1337 Con_DPrintf("FS_FindFile: %s in %s\n",
1338 pak->files[middle].name, pak->filename);
1345 // If we're too far in the list
1354 char netpath[MAX_OSPATH];
1355 dpsnprintf(netpath, sizeof(netpath), "%s%s", search->filename, name);
1356 if (FS_SysFileExists (netpath))
1359 Con_DPrintf("FS_FindFile: %s\n", netpath);
1369 Con_DPrintf("FS_FindFile: can't find %s\n", name);
1381 Look for a file in the search paths and open it in read-only mode
1386 qfile_t *FS_OpenReadFile (const char *filename, qboolean quiet, qboolean nonblocking)
1388 searchpath_t *search;
1391 search = FS_FindFile (filename, &pack_ind, quiet);
1400 // Found in the filesystem?
1403 char path [MAX_OSPATH];
1404 dpsnprintf (path, sizeof (path), "%s%s", search->filename, filename);
1405 return FS_SysOpen (path, "rb", nonblocking);
1408 // So, we found it in a package...
1409 return FS_OpenPackedFile (search->pack, pack_ind);
1414 =============================================================================
1416 MAIN PUBLIC FUNCTIONS
1418 =============================================================================
1422 ====================
1425 Open a file. The syntax is the same as fopen
1426 ====================
1428 qfile_t* FS_Open (const char* filepath, const char* mode, qboolean quiet, qboolean nonblocking)
1432 if (FS_CheckNastyPath(filepath))
1434 Con_Printf("FS_Open(\"%s\", \"%s\", %s): nasty filename rejected\n", filepath, mode, quiet ? "true" : "false");
1438 // If the file is opened in "write", "append", or "read/write" mode
1439 if (mode[0] == 'w' || mode[0] == 'a' || strchr (mode, '+'))
1441 char real_path [MAX_OSPATH];
1443 // Open the file on disk directly
1444 dpsnprintf (real_path, sizeof (real_path), "%s%s", fs_gamedir, filepath);
1446 // Create directories up to the file
1447 FS_CreatePath (real_path);
1449 return FS_SysOpen (real_path, mode, nonblocking);
1452 // Else, we look at the various search paths and open the file in read-only mode
1453 file = FS_OpenReadFile (filepath, quiet, nonblocking);
1455 fs_filesize = file->real_length;
1462 ====================
1466 ====================
1468 int FS_Close (qfile_t* file)
1470 if (close (file->handle))
1475 qz_inflateEnd (&file->ztk->zstream);
1476 Mem_Free (file->ztk);
1485 ====================
1488 Write "datasize" bytes into a file
1489 ====================
1491 fs_offset_t FS_Write (qfile_t* file, const void* data, size_t datasize)
1495 // If necessary, seek to the exact file position we're supposed to be
1496 if (file->buff_ind != file->buff_len)
1497 lseek (file->handle, file->buff_ind - file->buff_len, SEEK_CUR);
1499 // Purge cached data
1502 // Write the buffer and update the position
1503 result = write (file->handle, data, (fs_offset_t)datasize);
1504 file->position = lseek (file->handle, 0, SEEK_CUR);
1505 if (file->real_length < file->position)
1506 file->real_length = file->position;
1516 ====================
1519 Read up to "buffersize" bytes from a file
1520 ====================
1522 fs_offset_t FS_Read (qfile_t* file, void* buffer, size_t buffersize)
1524 fs_offset_t count, done;
1526 if (buffersize == 0)
1529 // Get rid of the ungetc character
1530 if (file->ungetc != EOF)
1532 ((char*)buffer)[0] = file->ungetc;
1540 // First, we copy as many bytes as we can from "buff"
1541 if (file->buff_ind < file->buff_len)
1543 count = file->buff_len - file->buff_ind;
1545 done += ((fs_offset_t)buffersize > count) ? count : (fs_offset_t)buffersize;
1546 memcpy (buffer, &file->buff[file->buff_ind], done);
1547 file->buff_ind += done;
1550 if (buffersize == 0)
1554 // NOTE: at this point, the read buffer is always empty
1556 // If the file isn't compressed
1557 if (! (file->flags & QFILE_FLAG_DEFLATED))
1561 // We must take care to not read after the end of the file
1562 count = file->real_length - file->position;
1564 // If we have a lot of data to get, put them directly into "buffer"
1565 if (buffersize > sizeof (file->buff) / 2)
1567 if (count > (fs_offset_t)buffersize)
1568 count = (fs_offset_t)buffersize;
1569 lseek (file->handle, file->offset + file->position, SEEK_SET);
1570 nb = read (file->handle, &((qbyte*)buffer)[done], count);
1574 file->position += nb;
1576 // Purge cached data
1582 if (count > (fs_offset_t)sizeof (file->buff))
1583 count = (fs_offset_t)sizeof (file->buff);
1584 lseek (file->handle, file->offset + file->position, SEEK_SET);
1585 nb = read (file->handle, file->buff, count);
1588 file->buff_len = nb;
1589 file->position += nb;
1591 // Copy the requested data in "buffer" (as much as we can)
1592 count = (fs_offset_t)buffersize > file->buff_len ? file->buff_len : (fs_offset_t)buffersize;
1593 memcpy (&((qbyte*)buffer)[done], file->buff, count);
1594 file->buff_ind = count;
1602 // If the file is compressed, it's more complicated...
1603 // We cycle through a few operations until we have read enough data
1604 while (buffersize > 0)
1606 ztoolkit_t *ztk = file->ztk;
1609 // NOTE: at this point, the read buffer is always empty
1611 // If "input" is also empty, we need to refill it
1612 if (ztk->in_ind == ztk->in_len)
1614 // If we are at the end of the file
1615 if (file->position == file->real_length)
1618 count = (fs_offset_t)(ztk->comp_length - ztk->in_position);
1619 if (count > (fs_offset_t)sizeof (ztk->input))
1620 count = (fs_offset_t)sizeof (ztk->input);
1621 lseek (file->handle, file->offset + (fs_offset_t)ztk->in_position, SEEK_SET);
1622 if (read (file->handle, ztk->input, count) != count)
1624 Con_Printf ("FS_Read: unexpected end of file");
1629 ztk->in_len = count;
1630 ztk->in_position += count;
1633 ztk->zstream.next_in = &ztk->input[ztk->in_ind];
1634 ztk->zstream.avail_in = (unsigned int)(ztk->in_len - ztk->in_ind);
1636 // Now that we are sure we have compressed data available, we need to determine
1637 // if it's better to inflate it in "file->buff" or directly in "buffer"
1639 // Inflate the data in "file->buff"
1640 if (buffersize < sizeof (file->buff) / 2)
1642 ztk->zstream.next_out = file->buff;
1643 ztk->zstream.avail_out = sizeof (file->buff);
1644 error = qz_inflate (&ztk->zstream, Z_SYNC_FLUSH);
1645 if (error != Z_OK && error != Z_STREAM_END)
1647 Con_Printf ("FS_Read: Can't inflate file");
1650 ztk->in_ind = ztk->in_len - ztk->zstream.avail_in;
1652 file->buff_len = (fs_offset_t)sizeof (file->buff) - ztk->zstream.avail_out;
1653 file->position += file->buff_len;
1655 // Copy the requested data in "buffer" (as much as we can)
1656 count = (fs_offset_t)buffersize > file->buff_len ? file->buff_len : (fs_offset_t)buffersize;
1657 memcpy (&((qbyte*)buffer)[done], file->buff, count);
1658 file->buff_ind = count;
1661 // Else, we inflate directly in "buffer"
1664 ztk->zstream.next_out = &((qbyte*)buffer)[done];
1665 ztk->zstream.avail_out = (unsigned int)buffersize;
1666 error = qz_inflate (&ztk->zstream, Z_SYNC_FLUSH);
1667 if (error != Z_OK && error != Z_STREAM_END)
1669 Con_Printf ("FS_Read: Can't inflate file");
1672 ztk->in_ind = ztk->in_len - ztk->zstream.avail_in;
1674 // How much data did it inflate?
1675 count = (fs_offset_t)(buffersize - ztk->zstream.avail_out);
1676 file->position += count;
1678 // Purge cached data
1683 buffersize -= count;
1691 ====================
1694 Print a string into a file
1695 ====================
1697 int FS_Print (qfile_t* file, const char *msg)
1699 return (int)FS_Write (file, msg, strlen (msg));
1703 ====================
1706 Print a string into a file
1707 ====================
1709 int FS_Printf(qfile_t* file, const char* format, ...)
1714 va_start (args, format);
1715 result = FS_VPrintf (file, format, args);
1723 ====================
1726 Print a string into a file
1727 ====================
1729 int FS_VPrintf (qfile_t* file, const char* format, va_list ap)
1732 fs_offset_t buff_size;
1733 char *tempbuff = NULL;
1736 tempbuff = (char *)Mem_Alloc (tempmempool, buff_size);
1737 len = dpvsnprintf (tempbuff, buff_size, format, ap);
1740 Mem_Free (tempbuff);
1742 tempbuff = (char *)Mem_Alloc (tempmempool, buff_size);
1743 len = dpvsnprintf (tempbuff, buff_size, format, ap);
1746 len = write (file->handle, tempbuff, len);
1747 Mem_Free (tempbuff);
1754 ====================
1757 Get the next character of a file
1758 ====================
1760 int FS_Getc (qfile_t* file)
1764 if (FS_Read (file, &c, 1) != 1)
1772 ====================
1775 Put a character back into the read buffer (only supports one character!)
1776 ====================
1778 int FS_UnGetc (qfile_t* file, unsigned char c)
1780 // If there's already a character waiting to be read
1781 if (file->ungetc != EOF)
1790 ====================
1793 Move the position index in a file
1794 ====================
1796 int FS_Seek (qfile_t* file, fs_offset_t offset, int whence)
1800 fs_offset_t buffersize;
1802 // Compute the file offset
1806 offset += file->position - file->buff_len + file->buff_ind;
1813 offset += file->real_length;
1819 if (offset < 0 || offset > (long) file->real_length)
1822 // If we have the data in our read buffer, we don't need to actually seek
1823 if (file->position - file->buff_len <= offset && offset <= file->position)
1825 file->buff_ind = offset + file->buff_len - file->position;
1829 // Purge cached data
1832 // Unpacked or uncompressed files can seek directly
1833 if (! (file->flags & QFILE_FLAG_DEFLATED))
1835 if (lseek (file->handle, file->offset + offset, SEEK_SET) == -1)
1837 file->position = offset;
1841 // Seeking in compressed files is more a hack than anything else,
1842 // but we need to support it, so here we go.
1845 // If we have to go back in the file, we need to restart from the beginning
1846 if (offset <= file->position)
1850 ztk->in_position = 0;
1852 lseek (file->handle, file->offset, SEEK_SET);
1854 // Reset the Zlib stream
1855 ztk->zstream.next_in = ztk->input;
1856 ztk->zstream.avail_in = 0;
1857 qz_inflateReset (&ztk->zstream);
1860 // We need a big buffer to force inflating into it directly
1861 buffersize = 2 * sizeof (file->buff);
1862 buffer = (qbyte *)Mem_Alloc (tempmempool, buffersize);
1864 // Skip all data until we reach the requested offset
1865 while (offset > file->position)
1867 fs_offset_t diff = offset - file->position;
1868 fs_offset_t count, len;
1870 count = (diff > buffersize) ? buffersize : diff;
1871 len = FS_Read (file, buffer, count);
1885 ====================
1888 Give the current position in a file
1889 ====================
1891 fs_offset_t FS_Tell (qfile_t* file)
1893 return file->position - file->buff_len + file->buff_ind;
1898 ====================
1901 Erases any buffered input or output data
1902 ====================
1904 void FS_Purge (qfile_t* file)
1916 Filename are relative to the quake directory.
1917 Always appends a 0 byte.
1920 qbyte *FS_LoadFile (const char *path, mempool_t *pool, qboolean quiet)
1925 file = FS_Open (path, "rb", quiet, false);
1929 buf = (qbyte *)Mem_Alloc (pool, fs_filesize + 1);
1930 buf[fs_filesize] = '\0';
1932 FS_Read (file, buf, fs_filesize);
1943 The filename will be prefixed by the current game directory
1946 qboolean FS_WriteFile (const char *filename, void *data, fs_offset_t len)
1950 file = FS_Open (filename, "wb", false, false);
1953 Con_Printf("FS_WriteFile: failed on %s\n", filename);
1957 Con_DPrintf("FS_WriteFile: %s\n", filename);
1958 FS_Write (file, data, len);
1965 =============================================================================
1967 OTHERS PUBLIC FUNCTIONS
1969 =============================================================================
1977 void FS_StripExtension (const char *in, char *out, size_t size_out)
1984 while (*in && size_out > 1)
1988 else if (*in == '/' || *in == '\\' || *in == ':')
2005 void FS_DefaultExtension (char *path, const char *extension, size_t size_path)
2009 // if path doesn't have a .EXT, append extension
2010 // (extension should include the .)
2011 src = path + strlen(path) - 1;
2013 while (*src != '/' && src != path)
2016 return; // it has an extension
2020 strlcat (path, extension, size_path);
2028 Look for a file in the packages and in the filesystem
2031 qboolean FS_FileExists (const char *filename)
2033 return (FS_FindFile (filename, NULL, true) != NULL);
2041 Look for a file in the filesystem only
2044 qboolean FS_SysFileExists (const char *path)
2049 // TODO: use another function instead, to avoid opening the file
2050 desc = open (path, O_RDONLY | O_BINARY);
2059 if (stat (path,&buf) == -1)
2066 void FS_mkdir (const char *path)
2079 Allocate and fill a search structure with information on matching filenames.
2082 fssearch_t *FS_Search(const char *pattern, int caseinsensitive, int quiet)
2085 searchpath_t *searchpath;
2087 int i, basepathlength, numfiles, numchars;
2088 stringlist_t *dir, *dirfile, *liststart, *listcurrent, *listtemp;
2089 const char *slash, *backslash, *colon, *separator;
2091 char netpath[MAX_OSPATH];
2092 char temp[MAX_OSPATH];
2094 for (i = 0;pattern[i] == '.' || pattern[i] == ':' || pattern[i] == '/' || pattern[i] == '\\';i++)
2099 Con_Printf("Don't use punctuation at the beginning of a search pattern!\n");
2107 slash = strrchr(pattern, '/');
2108 backslash = strrchr(pattern, '\\');
2109 colon = strrchr(pattern, ':');
2110 separator = max(slash, backslash);
2111 separator = max(separator, colon);
2112 basepathlength = separator ? (separator + 1 - pattern) : 0;
2113 basepath = (char *)Mem_Alloc (tempmempool, basepathlength + 1);
2115 memcpy(basepath, pattern, basepathlength);
2116 basepath[basepathlength] = 0;
2118 // search through the path, one element at a time
2119 for (searchpath = fs_searchpaths;searchpath;searchpath = searchpath->next)
2121 // is the element a pak file?
2122 if (searchpath->pack)
2124 // look through all the pak file elements
2125 pak = searchpath->pack;
2126 for (i = 0;i < pak->numfiles;i++)
2128 strcpy(temp, pak->files[i].name);
2131 if (matchpattern(temp, (char *)pattern, true))
2133 for (listtemp = liststart;listtemp;listtemp = listtemp->next)
2134 if (!strcmp(listtemp->text, temp))
2136 if (listtemp == NULL)
2138 listcurrent = stringlistappend(listcurrent, temp);
2139 if (liststart == NULL)
2140 liststart = listcurrent;
2142 Con_DPrintf("SearchPackFile: %s : %s\n", pak->filename, temp);
2145 // strip off one path element at a time until empty
2146 // this way directories are added to the listing if they match the pattern
2147 slash = strrchr(temp, '/');
2148 backslash = strrchr(temp, '\\');
2149 colon = strrchr(temp, ':');
2151 if (separator < slash)
2153 if (separator < backslash)
2154 separator = backslash;
2155 if (separator < colon)
2157 *((char *)separator) = 0;
2163 // get a directory listing and look at each name
2164 dpsnprintf(netpath, sizeof (netpath), "%s%s", searchpath->filename, basepath);
2165 if ((dir = listdirectory(netpath)))
2167 for (dirfile = dir;dirfile;dirfile = dirfile->next)
2169 dpsnprintf(temp, sizeof(temp), "%s%s", basepath, dirfile->text);
2170 if (matchpattern(temp, (char *)pattern, true))
2172 for (listtemp = liststart;listtemp;listtemp = listtemp->next)
2173 if (!strcmp(listtemp->text, temp))
2175 if (listtemp == NULL)
2177 listcurrent = stringlistappend(listcurrent, temp);
2178 if (liststart == NULL)
2179 liststart = listcurrent;
2181 Con_DPrintf("SearchDirFile: %s\n", temp);
2192 liststart = stringlistsort(liststart);
2195 for (listtemp = liststart;listtemp;listtemp = listtemp->next)
2198 numchars += (int)strlen(listtemp->text) + 1;
2200 search = (fssearch_t *)Z_Malloc(sizeof(fssearch_t) + numchars + numfiles * sizeof(char *));
2201 search->filenames = (char **)((char *)search + sizeof(fssearch_t));
2202 search->filenamesbuffer = (char *)((char *)search + sizeof(fssearch_t) + numfiles * sizeof(char *));
2203 search->numfilenames = (int)numfiles;
2206 for (listtemp = liststart;listtemp;listtemp = listtemp->next)
2208 search->filenames[numfiles] = search->filenamesbuffer + numchars;
2209 strcpy(search->filenames[numfiles], listtemp->text);
2211 numchars += (int)strlen(listtemp->text) + 1;
2214 stringlistfree(liststart);
2221 void FS_FreeSearch(fssearch_t *search)
2226 extern int con_linewidth;
2227 int FS_ListDirectory(const char *pattern, int oneperline)
2238 search = FS_Search(pattern, true, true);
2241 numfiles = search->numfilenames;
2244 // FIXME: the names could be added to one column list and then
2245 // gradually shifted into the next column if they fit, and then the
2246 // next to make a compact variable width listing but it's a lot more
2248 // find width for columns
2250 for (i = 0;i < numfiles;i++)
2252 l = (int)strlen(search->filenames[i]);
2253 if (columnwidth < l)
2256 // count the spacing character
2258 // calculate number of columns
2259 numcolumns = con_linewidth / columnwidth;
2260 // don't bother with the column printing if it's only one column
2261 if (numcolumns >= 2)
2263 numlines = (numfiles + numcolumns - 1) / numcolumns;
2264 for (i = 0;i < numlines;i++)
2267 for (k = 0;k < numcolumns;k++)
2269 l = i * numcolumns + k;
2272 name = search->filenames[l];
2273 for (j = 0;name[j] && linebufpos + 1 < (int)sizeof(linebuf);j++)
2274 linebuf[linebufpos++] = name[j];
2275 // space out name unless it's the last on the line
2276 if (k + 1 < numcolumns && l + 1 < numfiles)
2277 for (;j < columnwidth && linebufpos + 1 < (int)sizeof(linebuf);j++)
2278 linebuf[linebufpos++] = ' ';
2281 linebuf[linebufpos] = 0;
2282 Con_Printf("%s\n", linebuf);
2289 for (i = 0;i < numfiles;i++)
2290 Con_Printf("%s\n", search->filenames[i]);
2291 FS_FreeSearch(search);
2292 return (int)numfiles;
2295 static void FS_ListDirectoryCmd (const char* cmdname, int oneperline)
2297 const char *pattern;
2300 Con_Printf("usage:\n%s [path/pattern]\n", cmdname);
2303 if (Cmd_Argc() == 2)
2304 pattern = Cmd_Argv(1);
2307 if (!FS_ListDirectory(pattern, oneperline))
2308 Con_Print("No files found.\n");
2313 FS_ListDirectoryCmd("dir", true);
2318 FS_ListDirectoryCmd("ls", false);