4 Copyright (C) 2003-2005 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>
42 // Win32 requires us to add O_BINARY, but the other OSes don't have it
50 All of Quake's data access is through a hierchal file system, but the contents
51 of the file system can be transparently merged from several sources.
53 The "base directory" is the path to the directory holding the quake.exe and
54 all game directories. The sys_* files pass this to host_init in
55 quakeparms_t->basedir. This can be overridden with the "-basedir" command
56 line parm to allow code debugging in a different directory. The base
57 directory is only used during filesystem initialization.
59 The "game directory" is the first tree on the search path and directory that
60 all generated files (savegames, screenshots, demos, config files) will be
61 saved to. This can be overridden with the "-game" command line parameter.
62 The game directory can never be changed while quake is executing. This is a
63 precaution against having a malicious server instruct clients to write files
64 over areas they shouldn't.
70 =============================================================================
74 =============================================================================
77 // Magic numbers of a ZIP file (big-endian format)
78 #define ZIP_DATA_HEADER 0x504B0304 // "PK\3\4"
79 #define ZIP_CDIR_HEADER 0x504B0102 // "PK\1\2"
80 #define ZIP_END_HEADER 0x504B0506 // "PK\5\6"
82 // Other constants for ZIP files
83 #define ZIP_MAX_COMMENTS_SIZE ((unsigned short)0xFFFF)
84 #define ZIP_END_CDIR_SIZE 22
85 #define ZIP_CDIR_CHUNK_BASE_SIZE 46
86 #define ZIP_LOCAL_CHUNK_BASE_SIZE 30
88 // Zlib constants (from zlib.h)
89 #define Z_SYNC_FLUSH 2
92 #define Z_STREAM_END 1
93 #define ZLIB_VERSION "1.1.4"
97 =============================================================================
101 =============================================================================
104 // Zlib stream (from zlib.h)
105 // Warning: some pointers we don't use directly have
106 // been cast to "void*" for a matter of simplicity
109 qbyte *next_in; // next input byte
110 unsigned int avail_in; // number of bytes available at next_in
111 unsigned long total_in; // total nb of input bytes read so far
113 qbyte *next_out; // next output byte should be put there
114 unsigned int avail_out; // remaining free space at next_out
115 unsigned long total_out; // total nb of bytes output so far
117 char *msg; // last error message, NULL if no error
118 void *state; // not visible by applications
120 void *zalloc; // used to allocate the internal state
121 void *zfree; // used to free the internal state
122 void *opaque; // private data object passed to zalloc and zfree
124 int data_type; // best guess about the data type: ascii or binary
125 unsigned long adler; // adler32 value of the uncompressed data
126 unsigned long reserved; // reserved for future use
133 QFILE_FLAG_PACKED = (1 << 0), // inside a package (PAK or PK3)
134 QFILE_FLAG_DEFLATED = (1 << 1) // file is compressed using the deflate algorithm (PK3 only)
137 #define FILE_BUFF_SIZE 2048
141 size_t comp_length; // length of the compressed file
142 size_t in_ind, in_len; // input buffer current index and length
143 size_t in_position; // position in the compressed file
144 qbyte input [FILE_BUFF_SIZE];
150 int handle; // file descriptor
151 size_t real_length; // uncompressed file size (for files opened in "read" mode)
152 size_t position; // current position in the file
153 size_t offset; // offset into the package (0 if external file)
154 int ungetc; // single stored character from ungetc, cleared to EOF when read
157 size_t buff_ind, buff_len; // buffer current index and length
158 qbyte buff [FILE_BUFF_SIZE];
165 // ------ PK3 files on disk ------ //
167 // You can get the complete ZIP format description from PKWARE website
171 unsigned int signature;
172 unsigned short disknum;
173 unsigned short cdir_disknum; // number of the disk with the start of the central directory
174 unsigned short localentries; // number of entries in the central directory on this disk
175 unsigned short nbentries; // total number of entries in the central directory on this disk
176 unsigned int cdir_size; // size of the central directory
177 unsigned int cdir_offset; // with respect to the starting disk number
178 unsigned short comment_size;
179 } pk3_endOfCentralDir_t;
182 // ------ PAK files on disk ------ //
186 int filepos, filelen;
197 // Packages in memory
200 PACKFILE_FLAG_NONE = 0,
201 PACKFILE_FLAG_TRUEOFFS = (1 << 0), // the offset in packfile_t is the true contents offset
202 PACKFILE_FLAG_DEFLATED = (1 << 1) // file compressed using the deflate algorithm
207 char name [MAX_QPATH];
208 packfile_flags_t flags;
210 size_t packsize; // size in the package
211 size_t realsize; // real file size (uncompressed)
214 typedef struct pack_s
216 char filename [MAX_OSPATH];
218 int ignorecase; // PK3 ignores case
225 // Search paths for files (including packages)
226 typedef struct searchpath_s
228 // only one of filename / pack will be used
229 char filename[MAX_OSPATH];
231 struct searchpath_s *next;
236 =============================================================================
240 =============================================================================
246 static packfile_t* FS_AddFileToPack (const char* name, pack_t* pack,
247 size_t offset, size_t packsize,
248 size_t realsize, packfile_flags_t flags);
252 =============================================================================
256 =============================================================================
259 mempool_t *fs_mempool;
263 pack_t *packlist = NULL;
265 searchpath_t *fs_searchpaths = NULL;
267 #define MAX_FILES_IN_PACK 65536
269 char fs_gamedir[MAX_OSPATH];
270 char fs_basedir[MAX_OSPATH];
272 qboolean fs_modified; // set true if using non-id files
276 =============================================================================
278 PRIVATE FUNCTIONS - PK3 HANDLING
280 =============================================================================
283 // Functions exported from zlib
285 # define ZEXPORT WINAPI
290 static int (ZEXPORT *qz_inflate) (z_stream* strm, int flush);
291 static int (ZEXPORT *qz_inflateEnd) (z_stream* strm);
292 static int (ZEXPORT *qz_inflateInit2_) (z_stream* strm, int windowBits, const char *version, int stream_size);
293 static int (ZEXPORT *qz_inflateReset) (z_stream* strm);
295 #define qz_inflateInit2(strm, windowBits) \
296 qz_inflateInit2_((strm), (windowBits), ZLIB_VERSION, sizeof(z_stream))
298 static dllfunction_t zlibfuncs[] =
300 {"inflate", (void **) &qz_inflate},
301 {"inflateEnd", (void **) &qz_inflateEnd},
302 {"inflateInit2_", (void **) &qz_inflateInit2_},
303 {"inflateReset", (void **) &qz_inflateReset},
307 // Handle for Zlib DLL
308 static dllhandle_t zlib_dll = NULL;
318 void PK3_CloseLibrary (void)
320 Sys_UnloadLibrary (&zlib_dll);
328 Try to load the Zlib DLL
331 qboolean PK3_OpenLibrary (void)
333 const char* dllnames [] =
337 #elif defined(MACOSX)
351 if (! Sys_LoadLibrary (dllnames, &zlib_dll, zlibfuncs))
353 Con_Printf ("Compressed files support disabled\n");
357 Con_Printf ("Compressed files support enabled\n");
364 PK3_GetEndOfCentralDir
366 Extract the end of the central directory from a PK3 package
369 qboolean PK3_GetEndOfCentralDir (const char *packfile, int packhandle, pk3_endOfCentralDir_t *eocd)
371 long filesize, maxsize;
375 // Get the package size
376 filesize = lseek (packhandle, 0, SEEK_END);
377 if (filesize < ZIP_END_CDIR_SIZE)
380 // Load the end of the file in memory
381 if (filesize < ZIP_MAX_COMMENTS_SIZE + ZIP_END_CDIR_SIZE)
384 maxsize = ZIP_MAX_COMMENTS_SIZE + ZIP_END_CDIR_SIZE;
385 buffer = Mem_Alloc (tempmempool, maxsize);
386 lseek (packhandle, filesize - maxsize, SEEK_SET);
387 if (read (packhandle, buffer, maxsize) != (ssize_t) maxsize)
393 // Look for the end of central dir signature around the end of the file
394 maxsize -= ZIP_END_CDIR_SIZE;
395 ptr = &buffer[maxsize];
397 while (BuffBigLong (ptr) != ZIP_END_HEADER)
409 memcpy (eocd, ptr, ZIP_END_CDIR_SIZE);
410 eocd->signature = LittleLong (eocd->signature);
411 eocd->disknum = LittleShort (eocd->disknum);
412 eocd->cdir_disknum = LittleShort (eocd->cdir_disknum);
413 eocd->localentries = LittleShort (eocd->localentries);
414 eocd->nbentries = LittleShort (eocd->nbentries);
415 eocd->cdir_size = LittleLong (eocd->cdir_size);
416 eocd->cdir_offset = LittleLong (eocd->cdir_offset);
417 eocd->comment_size = LittleShort (eocd->comment_size);
429 Extract the file list from a PK3 file
432 int PK3_BuildFileList (pack_t *pack, const pk3_endOfCentralDir_t *eocd)
434 qbyte *central_dir, *ptr;
438 // Load the central directory in memory
439 central_dir = Mem_Alloc (tempmempool, eocd->cdir_size);
440 lseek (pack->handle, eocd->cdir_offset, SEEK_SET);
441 read (pack->handle, central_dir, eocd->cdir_size);
443 // Extract the files properties
444 // The parsing is done "by hand" because some fields have variable sizes and
445 // the constant part isn't 4-bytes aligned, which makes the use of structs difficult
446 remaining = eocd->cdir_size;
449 for (ind = 0; ind < eocd->nbentries; ind++)
451 size_t namesize, count;
453 // Checking the remaining size
454 if (remaining < ZIP_CDIR_CHUNK_BASE_SIZE)
456 Mem_Free (central_dir);
459 remaining -= ZIP_CDIR_CHUNK_BASE_SIZE;
462 if (BuffBigLong (ptr) != ZIP_CDIR_HEADER)
464 Mem_Free (central_dir);
468 namesize = BuffLittleShort (&ptr[28]); // filename length
470 // Check encryption, compression, and attributes
471 // 1st uint8 : general purpose bit flag
472 // Check bits 0 (encryption), 3 (data descriptor after the file), and 5 (compressed patched data (?))
473 // 2nd uint8 : external file attributes
474 // Check bits 3 (file is a directory) and 5 (file is a volume (?))
475 if ((ptr[8] & 0x29) == 0 && (ptr[38] & 0x18) == 0)
477 // Still enough bytes for the name?
478 if ((size_t) remaining < namesize || namesize >= sizeof (*pack->files))
480 Mem_Free (central_dir);
484 // WinZip doesn't use the "directory" attribute, so we need to check the name directly
485 if (ptr[ZIP_CDIR_CHUNK_BASE_SIZE + namesize - 1] != '/')
487 char filename [sizeof (pack->files[0].name)];
488 size_t offset, packsize, realsize;
489 packfile_flags_t flags;
491 // Extract the name (strip it if necessary)
492 if (namesize >= sizeof (filename))
493 namesize = sizeof (filename) - 1;
494 memcpy (filename, &ptr[ZIP_CDIR_CHUNK_BASE_SIZE], namesize);
495 filename[namesize] = '\0';
497 if (BuffLittleShort (&ptr[10]))
498 flags = PACKFILE_FLAG_DEFLATED;
501 offset = BuffLittleLong (&ptr[42]);
502 packsize = BuffLittleLong (&ptr[20]);
503 realsize = BuffLittleLong (&ptr[24]);
504 FS_AddFileToPack (filename, pack, offset, packsize, realsize, flags);
508 // Skip the name, additionnal field, and comment
509 // 1er uint16 : extra field length
510 // 2eme uint16 : file comment length
511 count = namesize + BuffLittleShort (&ptr[30]) + BuffLittleShort (&ptr[32]);
512 ptr += ZIP_CDIR_CHUNK_BASE_SIZE + count;
516 // If the package is empty, central_dir is NULL here
517 if (central_dir != NULL)
518 Mem_Free (central_dir);
519 return pack->numfiles;
527 Create a package entry associated with a PK3 file
530 pack_t *FS_LoadPackPK3 (const char *packfile)
533 pk3_endOfCentralDir_t eocd;
537 packhandle = open (packfile, O_RDONLY | O_BINARY);
541 if (! PK3_GetEndOfCentralDir (packfile, packhandle, &eocd))
542 Sys_Error ("%s is not a PK3 file", packfile);
544 // Multi-volume ZIP archives are NOT allowed
545 if (eocd.disknum != 0 || eocd.cdir_disknum != 0)
546 Sys_Error ("%s is a multi-volume ZIP archive", packfile);
548 // We only need to do this test if MAX_FILES_IN_PACK is lesser than 65535
549 // since eocd.nbentries is an unsigned 16 bits integer
550 #if MAX_FILES_IN_PACK < 65535
551 if (eocd.nbentries > MAX_FILES_IN_PACK)
552 Sys_Error ("%s contains too many files (%hu)", packfile, eocd.nbentries);
555 // Create a package structure in memory
556 pack = Mem_Alloc(fs_mempool, sizeof (pack_t));
557 pack->ignorecase = true; // PK3 ignores case
558 strlcpy (pack->filename, packfile, sizeof (pack->filename));
559 pack->handle = packhandle;
560 pack->numfiles = eocd.nbentries;
561 pack->files = Mem_Alloc(fs_mempool, eocd.nbentries * sizeof(packfile_t));
562 pack->next = packlist;
565 real_nb_files = PK3_BuildFileList (pack, &eocd);
566 if (real_nb_files < 0)
567 Sys_Error ("%s is not a valid PK3 file", packfile);
569 Con_Printf("Added packfile %s (%i files)\n", packfile, real_nb_files);
576 PK3_GetTrueFileOffset
578 Find where the true file data offset is
581 void PK3_GetTrueFileOffset (packfile_t *pfile, pack_t *pack)
583 qbyte buffer [ZIP_LOCAL_CHUNK_BASE_SIZE];
587 if (pfile->flags & PACKFILE_FLAG_TRUEOFFS)
590 // Load the local file description
591 lseek (pack->handle, pfile->offset, SEEK_SET);
592 count = read (pack->handle, buffer, ZIP_LOCAL_CHUNK_BASE_SIZE);
593 if (count != ZIP_LOCAL_CHUNK_BASE_SIZE || BuffBigLong (buffer) != ZIP_DATA_HEADER)
594 Sys_Error ("Can't retrieve file %s in package %s", pfile->name, pack->filename);
596 // Skip name and extra field
597 pfile->offset += BuffLittleShort (&buffer[26]) + BuffLittleShort (&buffer[28]) + ZIP_LOCAL_CHUNK_BASE_SIZE;
599 pfile->flags |= PACKFILE_FLAG_TRUEOFFS;
604 =============================================================================
606 OTHER PRIVATE FUNCTIONS
608 =============================================================================
616 Add a file to the list of files contained into a package
619 static packfile_t* FS_AddFileToPack (const char* name, pack_t* pack,
620 size_t offset, size_t packsize,
621 size_t realsize, packfile_flags_t flags)
623 int (*strcmp_funct) (const char* str1, const char* str2);
624 int left, right, middle;
627 strcmp_funct = pack->ignorecase ? strcasecmp : strcmp;
629 // Look for the slot we should put that file into (binary search)
631 right = pack->numfiles - 1;
632 while (left <= right)
636 middle = (left + right) / 2;
637 diff = strcmp_funct (pack->files[middle].name, name);
639 // If we found the file, there's a problem
641 Sys_Error ("Package %s contains the file %s several times\n",
642 pack->filename, name);
644 // If we're too far in the list
651 // We have to move the right of the list by one slot to free the one we need
652 pfile = &pack->files[left];
653 memmove (pfile + 1, pfile, (pack->numfiles - left) * sizeof (*pfile));
656 strlcpy (pfile->name, name, sizeof (pfile->name));
657 pfile->offset = offset;
658 pfile->packsize = packsize;
659 pfile->realsize = realsize;
660 pfile->flags = flags;
670 Only used for FS_Open.
673 void FS_CreatePath (char *path)
677 for (ofs = path+1 ; *ofs ; ofs++)
679 if (*ofs == '/' || *ofs == '\\')
681 // create the directory
697 void FS_Path_f (void)
701 Con_Print("Current search path:\n");
702 for (s=fs_searchpaths ; s ; s=s->next)
705 Con_Printf("%s (%i files)\n", s->pack->filename, s->pack->numfiles);
707 Con_Printf("%s\n", s->filename);
716 Takes an explicit (not game tree related) path to a pak file.
718 Loads the header and directory, adding the files at the beginning
719 of the list so they override previous pack files.
722 pack_t *FS_LoadPackPAK (const char *packfile)
724 dpackheader_t header;
730 packhandle = open (packfile, O_RDONLY | O_BINARY);
733 read (packhandle, (void *)&header, sizeof(header));
734 if (memcmp(header.id, "PACK", 4))
735 Sys_Error ("%s is not a packfile", packfile);
736 header.dirofs = LittleLong (header.dirofs);
737 header.dirlen = LittleLong (header.dirlen);
739 if (header.dirlen % sizeof(dpackfile_t))
740 Sys_Error ("%s has an invalid directory size", packfile);
742 numpackfiles = header.dirlen / sizeof(dpackfile_t);
744 if (numpackfiles > MAX_FILES_IN_PACK)
745 Sys_Error ("%s has %i files", packfile, numpackfiles);
747 pack = Mem_Alloc(fs_mempool, sizeof (pack_t));
748 pack->ignorecase = false; // PAK is case sensitive
749 strlcpy (pack->filename, packfile, sizeof (pack->filename));
750 pack->handle = packhandle;
752 pack->files = Mem_Alloc(fs_mempool, numpackfiles * sizeof(packfile_t));
753 pack->next = packlist;
756 info = Mem_Alloc(tempmempool, sizeof(*info) * numpackfiles);
757 lseek (packhandle, header.dirofs, SEEK_SET);
758 read (packhandle, (void *)info, header.dirlen);
760 // parse the directory
761 for (i = 0;i < numpackfiles;i++)
763 size_t offset = LittleLong (info[i].filepos);
764 size_t size = LittleLong (info[i].filelen);
766 FS_AddFileToPack (info[i].name, pack, offset, size, size, PACKFILE_FLAG_TRUEOFFS);
771 Con_Printf("Added packfile %s (%i files)\n", packfile, numpackfiles);
780 Sets fs_gamedir, adds the directory to the head of the path,
781 then loads and adds pak1.pak pak2.pak ...
784 void FS_AddGameDirectory (const char *dir)
786 stringlist_t *list, *current;
787 searchpath_t *search;
789 char pakfile[MAX_OSPATH];
791 strlcpy (fs_gamedir, dir, sizeof (fs_gamedir));
793 list = listdirectory(dir);
795 // add any PAK package in the directory
796 for (current = list;current;current = current->next)
798 if (matchpattern(current->text, "*.pak", true))
800 dpsnprintf (pakfile, sizeof (pakfile), "%s%s", dir, current->text);
801 pak = FS_LoadPackPAK (pakfile);
804 search = Mem_Alloc(fs_mempool, sizeof(searchpath_t));
806 search->next = fs_searchpaths;
807 fs_searchpaths = search;
810 Con_Printf("unable to load pak \"%s\"\n", pakfile);
814 // add any PK3 package in the director
815 for (current = list;current;current = current->next)
817 if (matchpattern(current->text, "*.pk3", true))
819 dpsnprintf (pakfile, sizeof (pakfile), "%s%s", dir, current->text);
820 pak = FS_LoadPackPK3 (pakfile);
823 search = Mem_Alloc(fs_mempool, sizeof(searchpath_t));
825 search->next = fs_searchpaths;
826 fs_searchpaths = search;
829 Con_Printf("unable to load pak \"%s\"\n", pakfile);
834 // Add the directory to the search path
835 // (unpacked files have the priority over packed files)
836 search = Mem_Alloc(fs_mempool, sizeof(searchpath_t));
837 strlcpy (search->filename, dir, sizeof (search->filename));
838 search->next = fs_searchpaths;
839 fs_searchpaths = search;
848 void FS_AddGameHierarchy (const char *dir)
854 // Add the common game directory
855 FS_AddGameDirectory (va("%s/%s/", fs_basedir, dir));
858 // Add the personal game directory
859 homedir = getenv ("HOME");
860 if (homedir != NULL && homedir[0] != '\0')
861 FS_AddGameDirectory (va("%s/.%s/%s/", homedir, gameuserdirname, dir));
871 static const char *FS_FileExtension (const char *in)
873 const char *separator, *backslash, *colon, *dot;
875 separator = strrchr(in, '/');
876 backslash = strrchr(in, '\\');
877 if (separator < backslash)
878 separator = backslash;
879 colon = strrchr(in, ':');
880 if (separator < colon)
883 dot = strrchr(in, '.');
884 if (dot == NULL || dot < separator)
899 searchpath_t *search;
901 fs_mempool = Mem_AllocPool("file management", 0, NULL);
903 strcpy(fs_basedir, ".");
904 strcpy(fs_gamedir, "");
907 // FIXME: is there a better way to find the directory outside the .app?
908 if (strstr(com_argv[0], ".app/"))
912 split = strstr(com_argv[0], ".app/");
913 while (split > com_argv[0] && *split != '/')
915 strlcpy(fs_basedir, com_argv[0], sizeof(fs_basedir));
916 fs_basedir[split - com_argv[0]] = 0;
923 // Overrides the system supplied base directory (under GAMENAME)
924 // 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)
925 i = COM_CheckParm ("-basedir");
926 if (i && i < com_argc-1)
928 strlcpy (fs_basedir, com_argv[i+1], sizeof (fs_basedir));
929 i = strlen (fs_basedir);
930 if (i > 0 && (fs_basedir[i-1] == '\\' || fs_basedir[i-1] == '/'))
934 // -path <dir or packfile> [<dir or packfile>] ...
935 // Fully specifies the exact search path, overriding the generated one
936 // 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)
937 i = COM_CheckParm ("-path");
941 while (++i < com_argc)
943 if (!com_argv[i] || com_argv[i][0] == '+' || com_argv[i][0] == '-')
946 search = Mem_Alloc(fs_mempool, sizeof(searchpath_t));
947 if (!strcasecmp (FS_FileExtension(com_argv[i]), "pak"))
949 search->pack = FS_LoadPackPAK (com_argv[i]);
951 Sys_Error ("Couldn't load packfile: %s", com_argv[i]);
953 else if (!strcasecmp (FS_FileExtension (com_argv[i]), "pk3"))
955 search->pack = FS_LoadPackPK3 (com_argv[i]);
957 Sys_Error ("Couldn't load packfile: %s", com_argv[i]);
960 strlcpy (search->filename, com_argv[i], sizeof (search->filename));
961 search->next = fs_searchpaths;
962 fs_searchpaths = search;
967 // add the game-specific paths
968 // gamedirname1 (typically id1)
969 FS_AddGameHierarchy (gamedirname1);
971 // add the game-specific path, if any
975 FS_AddGameHierarchy (gamedirname2);
978 // set the com_modname (reported in server info)
979 strlcpy(com_modname, gamedirname1, sizeof(com_modname));
982 // Adds basedir/gamedir as an override game
983 // LordHavoc: now supports multiple -game directories
984 for (i = 1;i < com_argc;i++)
988 if (!strcmp (com_argv[i], "-game") && i < com_argc-1)
992 FS_AddGameHierarchy (com_argv[i]);
993 // update the com_modname
994 strlcpy (com_modname, com_argv[i], sizeof (com_modname));
998 // If "-condebug" is in the command line, remove the previous log file
999 if (COM_CheckParm ("-condebug") != 0)
1000 unlink (va("%s/qconsole.log", fs_gamedir));
1003 void FS_Init_Commands(void)
1005 Cvar_RegisterVariable (&scr_screenshot_name);
1007 Cmd_AddCommand ("path", FS_Path_f);
1008 Cmd_AddCommand ("dir", FS_Dir_f);
1009 Cmd_AddCommand ("ls", FS_Ls_f);
1011 // set the default screenshot name to either the mod name or the
1012 // gamemode screenshot name
1014 Cvar_SetQuick (&scr_screenshot_name, com_modname);
1016 Cvar_SetQuick (&scr_screenshot_name, gamescreenshotname);
1024 void FS_Shutdown (void)
1026 Mem_FreePool (&fs_mempool);
1030 ====================
1033 Internal function used to create a qfile_t and open the relevant non-packed file on disk
1034 ====================
1036 static qfile_t* FS_SysOpen (const char* filepath, const char* mode, qboolean nonblocking)
1042 // Parse the mode string
1051 opt = O_CREAT | O_TRUNC;
1055 opt = O_CREAT | O_APPEND;
1058 Con_Printf ("FS_SysOpen(%s, %s): invalid mode\n", filepath, mode);
1061 for (ind = 1; mode[ind] != '\0'; ind++)
1072 Con_Printf ("FS_SysOpen(%s, %s): unknown character in mode (%c)\n",
1073 filepath, mode, mode[ind]);
1082 file = Mem_Alloc (fs_mempool, sizeof (*file));
1083 memset (file, 0, sizeof (*file));
1086 file->handle = open (filepath, mod | opt, 0666);
1087 if (file->handle < 0)
1093 file->real_length = lseek (file->handle, 0, SEEK_END);
1095 // For files opened in append mode, we start at the end of the file
1097 file->position = file->real_length;
1099 lseek (file->handle, 0, SEEK_SET);
1109 Open a packed file using its package file descriptor
1112 qfile_t *FS_OpenPackedFile (pack_t* pack, int pack_ind)
1118 pfile = &pack->files[pack_ind];
1120 // If we don't have the true offset, get it now
1121 if (! (pfile->flags & PACKFILE_FLAG_TRUEOFFS))
1122 PK3_GetTrueFileOffset (pfile, pack);
1124 // No Zlib DLL = no compressed files
1125 if (!zlib_dll && (pfile->flags & PACKFILE_FLAG_DEFLATED))
1127 Con_Printf("WARNING: can't open the compressed file %s\n"
1128 "You need the Zlib DLL to use compressed files\n",
1134 dup_handle = dup (pack->handle);
1136 Sys_Error ("FS_OpenPackedFile: can't dup package's handle (pack: %s)", pack->filename);
1138 file = Mem_Alloc (fs_mempool, sizeof (*file));
1139 memset (file, 0, sizeof (*file));
1140 file->handle = dup_handle;
1141 file->flags = QFILE_FLAG_PACKED;
1142 file->real_length = pfile->realsize;
1143 file->offset = pfile->offset;
1147 if (lseek (file->handle, file->offset, SEEK_SET) == -1)
1148 Sys_Error ("FS_OpenPackedFile: can't lseek to %s in %s (offset: %d)",
1149 pfile->name, pack->filename, file->offset);
1151 if (pfile->flags & PACKFILE_FLAG_DEFLATED)
1155 file->flags |= QFILE_FLAG_DEFLATED;
1157 // We need some more variables
1158 ztk = Mem_Alloc (fs_mempool, sizeof (*ztk));
1160 ztk->comp_length = pfile->packsize;
1162 // Initialize zlib stream
1163 ztk->zstream.next_in = ztk->input;
1164 ztk->zstream.avail_in = 0;
1166 /* From Zlib's "unzip.c":
1168 * windowBits is passed < 0 to tell that there is no zlib header.
1169 * Note that in this case inflate *requires* an extra "dummy" byte
1170 * after the compressed stream in order to complete decompression and
1171 * return Z_STREAM_END.
1172 * In unzip, i don't wait absolutely Z_STREAM_END because I known the
1173 * size of both compressed and uncompressed data
1175 if (qz_inflateInit2 (&ztk->zstream, -MAX_WBITS) != Z_OK)
1176 Sys_Error ("FS_OpenPackedFile: inflate init error (file: %s)", pfile->name);
1178 ztk->zstream.next_out = file->buff;
1179 ztk->zstream.avail_out = sizeof (file->buff);
1184 fs_filesize = pfile->realsize;
1190 ====================
1193 Return true if the path should be rejected due to one of the following:
1194 1: path elements that are non-portable
1195 2: path elements that would allow access to files outside the game directory,
1196 or are just not a good idea for a mod to be using.
1197 ====================
1199 int FS_CheckNastyPath (const char *path)
1201 // Windows: don't allow \ in filenames (windows-only), period.
1202 // (on Windows \ is a directory separator, but / is also supported)
1203 if (strstr(path, "\\"))
1204 return 1; // non-portable
1206 // Mac: don't allow Mac-only filenames - : is a directory separator
1207 // instead of /, but we rely on / working already, so there's no reason to
1208 // support a Mac-only path
1209 // Amiga and Windows: : tries to go to root of drive
1210 if (strstr(path, ":"))
1211 return 1; // non-portable attempt to go to root of drive
1213 // Amiga: // is parent directory
1214 if (strstr(path, "//"))
1215 return 1; // non-portable attempt to go to parent directory
1217 // all: don't allow going to current directory (./) or parent directory (../ or /../)
1218 if (strstr(path, "./"))
1219 return 2; // attempt to go outside the game directory
1221 // Windows and UNIXes: don't allow absolute paths
1223 return 2; // attempt to go outside the game directory
1225 // after all these checks we're pretty sure it's a / separated filename
1226 // and won't do much if any harm
1232 ====================
1235 Look for a file in the packages and in the filesystem
1237 Return the searchpath where the file was found (or NULL)
1238 and the file index in the package if relevant
1239 ====================
1241 static searchpath_t *FS_FindFile (const char *name, int* index, qboolean quiet)
1243 searchpath_t *search;
1246 // search through the path, one element at a time
1247 for (search = fs_searchpaths;search;search = search->next)
1249 // is the element a pak file?
1252 int (*strcmp_funct) (const char* str1, const char* str2);
1253 int left, right, middle;
1256 strcmp_funct = pak->ignorecase ? strcasecmp : strcmp;
1258 // Look for the file (binary search)
1260 right = pak->numfiles - 1;
1261 while (left <= right)
1265 middle = (left + right) / 2;
1266 diff = strcmp_funct (pak->files[middle].name, name);
1272 Con_DPrintf("FS_FindFile: %s in %s\n",
1273 pak->files[middle].name, pak->filename);
1280 // If we're too far in the list
1289 char netpath[MAX_OSPATH];
1290 dpsnprintf(netpath, sizeof(netpath), "%s%s", search->filename, name);
1291 if (FS_SysFileExists (netpath))
1294 Con_DPrintf("FS_FindFile: %s\n", netpath);
1304 Con_DPrintf("FS_FindFile: can't find %s\n", name);
1316 Look for a file in the search paths and open it in read-only mode
1321 qfile_t *FS_OpenReadFile (const char *filename, qboolean quiet, qboolean nonblocking)
1323 searchpath_t *search;
1326 search = FS_FindFile (filename, &pack_ind, quiet);
1335 // Found in the filesystem?
1338 char path [MAX_OSPATH];
1339 dpsnprintf (path, sizeof (path), "%s%s", search->filename, filename);
1340 return FS_SysOpen (path, "rb", nonblocking);
1343 // So, we found it in a package...
1344 return FS_OpenPackedFile (search->pack, pack_ind);
1349 =============================================================================
1351 MAIN PUBLIC FUNCTIONS
1353 =============================================================================
1357 ====================
1360 Open a file. The syntax is the same as fopen
1361 ====================
1363 qfile_t* FS_Open (const char* filepath, const char* mode, qboolean quiet, qboolean nonblocking)
1367 if (FS_CheckNastyPath(filepath))
1369 Con_Printf("FS_Open(\"%s\", \"%s\", %s): nasty filename rejected\n", filepath, mode, quiet ? "true" : "false");
1373 // If the file is opened in "write", "append", or "read/write" mode
1374 if (mode[0] == 'w' || mode[0] == 'a' || strchr (mode, '+'))
1376 char real_path [MAX_OSPATH];
1378 // Open the file on disk directly
1379 dpsnprintf (real_path, sizeof (real_path), "%s%s", fs_gamedir, filepath);
1381 // Create directories up to the file
1382 FS_CreatePath (real_path);
1384 return FS_SysOpen (real_path, mode, nonblocking);
1387 // Else, we look at the various search paths and open the file in read-only mode
1388 file = FS_OpenReadFile (filepath, quiet, nonblocking);
1390 fs_filesize = file->real_length;
1397 ====================
1401 ====================
1403 int FS_Close (qfile_t* file)
1405 if (close (file->handle))
1410 qz_inflateEnd (&file->ztk->zstream);
1411 Mem_Free (file->ztk);
1420 ====================
1423 Write "datasize" bytes into a file
1424 ====================
1426 size_t FS_Write (qfile_t* file, const void* data, size_t datasize)
1430 // If necessary, seek to the exact file position we're supposed to be
1431 if (file->buff_ind != file->buff_len)
1432 lseek (file->handle, file->buff_ind - file->buff_len, SEEK_CUR);
1434 // Purge cached data
1437 // Write the buffer and update the position
1438 result = write (file->handle, data, datasize);
1439 file->position = lseek (file->handle, 0, SEEK_CUR);
1440 if (file->real_length < file->position)
1441 file->real_length = file->position;
1451 ====================
1454 Read up to "buffersize" bytes from a file
1455 ====================
1457 size_t FS_Read (qfile_t* file, void* buffer, size_t buffersize)
1461 if (buffersize == 0)
1464 // Get rid of the ungetc character
1465 if (file->ungetc != EOF)
1467 ((char*)buffer)[0] = file->ungetc;
1475 // First, we copy as many bytes as we can from "buff"
1476 if (file->buff_ind < file->buff_len)
1478 count = file->buff_len - file->buff_ind;
1480 done += (buffersize > count) ? count : buffersize;
1481 memcpy (buffer, &file->buff[file->buff_ind], done);
1482 file->buff_ind += done;
1485 if (buffersize == 0)
1489 // NOTE: at this point, the read buffer is always empty
1491 // If the file isn't compressed
1492 if (! (file->flags & QFILE_FLAG_DEFLATED))
1496 // We must take care to not read after the end of the file
1497 count = file->real_length - file->position;
1499 // If we have a lot of data to get, put them directly into "buffer"
1500 if (buffersize > sizeof (file->buff) / 2)
1502 if (count > buffersize)
1504 lseek (file->handle, file->offset + file->position, SEEK_SET);
1505 nb = read (file->handle, &((qbyte*)buffer)[done], count);
1509 file->position += nb;
1511 // Purge cached data
1517 if (count > sizeof (file->buff))
1518 count = sizeof (file->buff);
1519 lseek (file->handle, file->offset + file->position, SEEK_SET);
1520 nb = read (file->handle, file->buff, count);
1523 file->buff_len = nb;
1524 file->position += nb;
1526 // Copy the requested data in "buffer" (as much as we can)
1527 count = (buffersize > file->buff_len) ? file->buff_len : buffersize;
1528 memcpy (&((qbyte*)buffer)[done], file->buff, count);
1529 file->buff_ind = count;
1537 // If the file is compressed, it's more complicated...
1538 // We cycle through a few operations until we have read enough data
1539 while (buffersize > 0)
1541 ztoolkit_t *ztk = file->ztk;
1544 // NOTE: at this point, the read buffer is always empty
1546 // If "input" is also empty, we need to refill it
1547 if (ztk->in_ind == ztk->in_len)
1549 // If we are at the end of the file
1550 if (file->position == file->real_length)
1553 count = ztk->comp_length - ztk->in_position;
1554 if (count > sizeof (ztk->input))
1555 count = sizeof (ztk->input);
1556 lseek (file->handle, file->offset + ztk->in_position, SEEK_SET);
1557 if (read (file->handle, ztk->input, count) != (ssize_t)count)
1558 Sys_Error ("FS_Read: unexpected end of file");
1561 ztk->in_len = count;
1562 ztk->in_position += count;
1565 ztk->zstream.next_in = &ztk->input[ztk->in_ind];
1566 ztk->zstream.avail_in = ztk->in_len - ztk->in_ind;
1568 // Now that we are sure we have compressed data available, we need to determine
1569 // if it's better to inflate it in "file->buff" or directly in "buffer"
1571 // Inflate the data in "file->buff"
1572 if (buffersize < sizeof (file->buff) / 2)
1574 ztk->zstream.next_out = file->buff;
1575 ztk->zstream.avail_out = sizeof (file->buff);
1576 error = qz_inflate (&ztk->zstream, Z_SYNC_FLUSH);
1577 if (error != Z_OK && error != Z_STREAM_END)
1578 Sys_Error ("Can't inflate file");
1579 ztk->in_ind = ztk->in_len - ztk->zstream.avail_in;
1581 file->buff_len = sizeof (file->buff) - ztk->zstream.avail_out;
1582 file->position += file->buff_len;
1584 // Copy the requested data in "buffer" (as much as we can)
1585 count = (buffersize > file->buff_len) ? file->buff_len : buffersize;
1586 memcpy (&((qbyte*)buffer)[done], file->buff, count);
1587 file->buff_ind = count;
1590 // Else, we inflate directly in "buffer"
1593 ztk->zstream.next_out = &((qbyte*)buffer)[done];
1594 ztk->zstream.avail_out = buffersize;
1595 error = qz_inflate (&ztk->zstream, Z_SYNC_FLUSH);
1596 if (error != Z_OK && error != Z_STREAM_END)
1597 Sys_Error ("Can't inflate file");
1598 ztk->in_ind = ztk->in_len - ztk->zstream.avail_in;
1600 // How much data did it inflate?
1601 count = buffersize - ztk->zstream.avail_out;
1602 file->position += count;
1604 // Purge cached data
1609 buffersize -= count;
1617 ====================
1620 Print a string into a file
1621 ====================
1623 int FS_Print (qfile_t* file, const char *msg)
1625 return FS_Write (file, msg, strlen (msg));
1629 ====================
1632 Print a string into a file
1633 ====================
1635 int FS_Printf(qfile_t* file, const char* format, ...)
1640 va_start (args, format);
1641 result = FS_VPrintf (file, format, args);
1649 ====================
1652 Print a string into a file
1653 ====================
1655 int FS_VPrintf (qfile_t* file, const char* format, va_list ap)
1659 char *tempbuff = NULL;
1662 tempbuff = Mem_Alloc (tempmempool, buff_size);
1663 len = dpvsnprintf (tempbuff, buff_size, format, ap);
1666 Mem_Free (tempbuff);
1668 tempbuff = Mem_Alloc (tempmempool, buff_size);
1669 len = dpvsnprintf (tempbuff, buff_size, format, ap);
1672 len = write (file->handle, tempbuff, len);
1673 Mem_Free (tempbuff);
1680 ====================
1683 Get the next character of a file
1684 ====================
1686 int FS_Getc (qfile_t* file)
1690 if (FS_Read (file, &c, 1) != 1)
1698 ====================
1701 Put a character back into the read buffer (only supports one character!)
1702 ====================
1704 int FS_UnGetc (qfile_t* file, unsigned char c)
1706 // If there's already a character waiting to be read
1707 if (file->ungetc != EOF)
1716 ====================
1719 Move the position index in a file
1720 ====================
1722 int FS_Seek (qfile_t* file, long offset, int whence)
1728 // Compute the file offset
1732 offset += file->position - file->buff_len + file->buff_ind;
1739 offset += file->real_length;
1745 if (offset < 0 || offset > (long) file->real_length)
1748 // If we have the data in our read buffer, we don't need to actually seek
1749 if (file->position - file->buff_len <= (size_t)offset &&
1750 (size_t)offset <= file->position)
1752 file->buff_ind = offset + file->buff_len - file->position;
1756 // Purge cached data
1759 // Unpacked or uncompressed files can seek directly
1760 if (! (file->flags & QFILE_FLAG_DEFLATED))
1762 if (lseek (file->handle, file->offset + offset, SEEK_SET) == -1)
1764 file->position = offset;
1768 // Seeking in compressed files is more a hack than anything else,
1769 // but we need to support it, so here we go.
1772 // If we have to go back in the file, we need to restart from the beginning
1773 if ((size_t)offset <= file->position)
1777 ztk->in_position = 0;
1779 lseek (file->handle, file->offset, SEEK_SET);
1781 // Reset the Zlib stream
1782 ztk->zstream.next_in = ztk->input;
1783 ztk->zstream.avail_in = 0;
1784 qz_inflateReset (&ztk->zstream);
1787 // We need a big buffer to force inflating into it directly
1788 buffersize = 2 * sizeof (file->buff);
1789 buffer = Mem_Alloc (tempmempool, buffersize);
1791 // Skip all data until we reach the requested offset
1792 while ((size_t)offset > file->position)
1794 size_t diff = offset - file->position;
1797 count = (diff > buffersize) ? buffersize : diff;
1798 len = FS_Read (file, buffer, count);
1812 ====================
1815 Give the current position in a file
1816 ====================
1818 long FS_Tell (qfile_t* file)
1820 return file->position - file->buff_len + file->buff_ind;
1825 ====================
1828 Erases any buffered input or output data
1829 ====================
1831 void FS_Purge (qfile_t* file)
1843 Filename are relative to the quake directory.
1844 Always appends a 0 byte.
1847 qbyte *FS_LoadFile (const char *path, mempool_t *pool, qboolean quiet)
1852 file = FS_Open (path, "rb", quiet, false);
1856 buf = Mem_Alloc (pool, fs_filesize + 1);
1857 buf[fs_filesize] = '\0';
1859 FS_Read (file, buf, fs_filesize);
1870 The filename will be prefixed by the current game directory
1873 qboolean FS_WriteFile (const char *filename, void *data, int len)
1877 file = FS_Open (filename, "wb", false, false);
1880 Con_Printf("FS_WriteFile: failed on %s\n", filename);
1884 Con_DPrintf("FS_WriteFile: %s\n", filename);
1885 FS_Write (file, data, len);
1892 =============================================================================
1894 OTHERS PUBLIC FUNCTIONS
1896 =============================================================================
1904 void FS_StripExtension (const char *in, char *out, size_t size_out)
1911 while (*in && size_out > 1)
1915 else if (*in == '/' || *in == '\\' || *in == ':')
1932 void FS_DefaultExtension (char *path, const char *extension, size_t size_path)
1936 // if path doesn't have a .EXT, append extension
1937 // (extension should include the .)
1938 src = path + strlen(path) - 1;
1940 while (*src != '/' && src != path)
1943 return; // it has an extension
1947 strlcat (path, extension, size_path);
1955 Look for a file in the packages and in the filesystem
1958 qboolean FS_FileExists (const char *filename)
1960 return (FS_FindFile (filename, NULL, true) != NULL);
1968 Look for a file in the filesystem only
1971 qboolean FS_SysFileExists (const char *path)
1976 // TODO: use another function instead, to avoid opening the file
1977 desc = open (path, O_RDONLY | O_BINARY);
1986 if (stat (path,&buf) == -1)
1993 void FS_mkdir (const char *path)
2006 Allocate and fill a search structure with information on matching filenames.
2009 fssearch_t *FS_Search(const char *pattern, int caseinsensitive, int quiet)
2012 searchpath_t *searchpath;
2014 int i, basepathlength, numfiles, numchars;
2015 stringlist_t *dir, *dirfile, *liststart, *listcurrent, *listtemp;
2016 const char *slash, *backslash, *colon, *separator;
2018 char netpath[MAX_OSPATH];
2019 char temp[MAX_OSPATH];
2021 for (i = 0;pattern[i] == '.' || pattern[i] == ':' || pattern[i] == '/' || pattern[i] == '\\';i++)
2026 Con_Printf("Don't use punctuation at the beginning of a search pattern!\n");
2034 slash = strrchr(pattern, '/');
2035 backslash = strrchr(pattern, '\\');
2036 colon = strrchr(pattern, ':');
2037 separator = max(slash, backslash);
2038 separator = max(separator, colon);
2039 basepathlength = separator ? (separator + 1 - pattern) : 0;
2040 basepath = Mem_Alloc (tempmempool, basepathlength + 1);
2042 memcpy(basepath, pattern, basepathlength);
2043 basepath[basepathlength] = 0;
2045 // search through the path, one element at a time
2046 for (searchpath = fs_searchpaths;searchpath;searchpath = searchpath->next)
2048 // is the element a pak file?
2049 if (searchpath->pack)
2051 // look through all the pak file elements
2052 pak = searchpath->pack;
2053 for (i = 0;i < pak->numfiles;i++)
2055 strcpy(temp, pak->files[i].name);
2058 if (matchpattern(temp, (char *)pattern, true))
2060 for (listtemp = liststart;listtemp;listtemp = listtemp->next)
2061 if (!strcmp(listtemp->text, temp))
2063 if (listtemp == NULL)
2065 listcurrent = stringlistappend(listcurrent, temp);
2066 if (liststart == NULL)
2067 liststart = listcurrent;
2069 Con_DPrintf("SearchPackFile: %s : %s\n", pak->filename, temp);
2072 // strip off one path element at a time until empty
2073 // this way directories are added to the listing if they match the pattern
2074 slash = strrchr(temp, '/');
2075 backslash = strrchr(temp, '\\');
2076 colon = strrchr(temp, ':');
2078 if (separator < slash)
2080 if (separator < backslash)
2081 separator = backslash;
2082 if (separator < colon)
2084 *((char *)separator) = 0;
2090 // get a directory listing and look at each name
2091 dpsnprintf(netpath, sizeof (netpath), "%s%s", searchpath->filename, basepath);
2092 if ((dir = listdirectory(netpath)))
2094 for (dirfile = dir;dirfile;dirfile = dirfile->next)
2096 dpsnprintf(temp, sizeof(temp), "%s%s", basepath, dirfile->text);
2097 if (matchpattern(temp, (char *)pattern, true))
2099 for (listtemp = liststart;listtemp;listtemp = listtemp->next)
2100 if (!strcmp(listtemp->text, temp))
2102 if (listtemp == NULL)
2104 listcurrent = stringlistappend(listcurrent, temp);
2105 if (liststart == NULL)
2106 liststart = listcurrent;
2108 Con_DPrintf("SearchDirFile: %s\n", temp);
2119 liststart = stringlistsort(liststart);
2122 for (listtemp = liststart;listtemp;listtemp = listtemp->next)
2125 numchars += strlen(listtemp->text) + 1;
2127 search = Z_Malloc(sizeof(fssearch_t) + numchars + numfiles * sizeof(char *));
2128 search->filenames = (char **)((char *)search + sizeof(fssearch_t));
2129 search->filenamesbuffer = (char *)((char *)search + sizeof(fssearch_t) + numfiles * sizeof(char *));
2130 search->numfilenames = numfiles;
2133 for (listtemp = liststart;listtemp;listtemp = listtemp->next)
2135 search->filenames[numfiles] = search->filenamesbuffer + numchars;
2136 strcpy(search->filenames[numfiles], listtemp->text);
2138 numchars += strlen(listtemp->text) + 1;
2141 stringlistfree(liststart);
2148 void FS_FreeSearch(fssearch_t *search)
2153 extern int con_linewidth;
2154 int FS_ListDirectory(const char *pattern, int oneperline)
2165 search = FS_Search(pattern, true, true);
2168 numfiles = search->numfilenames;
2171 // FIXME: the names could be added to one column list and then
2172 // gradually shifted into the next column if they fit, and then the
2173 // next to make a compact variable width listing but it's a lot more
2175 // find width for columns
2177 for (i = 0;i < numfiles;i++)
2179 l = strlen(search->filenames[i]);
2180 if (columnwidth < l)
2183 // count the spacing character
2185 // calculate number of columns
2186 numcolumns = con_linewidth / columnwidth;
2187 // don't bother with the column printing if it's only one column
2188 if (numcolumns >= 2)
2190 numlines = (numfiles + numcolumns - 1) / numcolumns;
2191 for (i = 0;i < numlines;i++)
2194 for (k = 0;k < numcolumns;k++)
2196 l = i * numcolumns + k;
2199 name = search->filenames[l];
2200 for (j = 0;name[j] && j < (int)sizeof(linebuf) - 1;j++)
2201 linebuf[linebufpos++] = name[j];
2202 // space out name unless it's the last on the line
2203 if (k < (numcolumns - 1) && l < (numfiles - 1))
2204 for (;j < columnwidth && j < (int)sizeof(linebuf) - 1;j++)
2205 linebuf[linebufpos++] = ' ';
2208 linebuf[linebufpos] = 0;
2209 Con_Printf("%s\n", linebuf);
2216 for (i = 0;i < numfiles;i++)
2217 Con_Printf("%s\n", search->filenames[i]);
2218 FS_FreeSearch(search);
2222 static void FS_ListDirectoryCmd (const char* cmdname, int oneperline)
2224 const char *pattern;
2227 Con_Printf("usage:\n%s [path/pattern]\n", cmdname);
2230 if (Cmd_Argc() == 2)
2231 pattern = Cmd_Argv(1);
2234 if (!FS_ListDirectory(pattern, oneperline))
2235 Con_Print("No files found.\n");
2240 FS_ListDirectoryCmd("dir", true);
2245 FS_ListDirectoryCmd("ls", false);