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
52 All of Quake's data access is through a hierchal file system, but the contents
53 of the file system can be transparently merged from several sources.
55 The "base directory" is the path to the directory holding the quake.exe and
56 all game directories. The sys_* files pass this to host_init in
57 quakeparms_t->basedir. This can be overridden with the "-basedir" command
58 line parm to allow code debugging in a different directory. The base
59 directory is only used during filesystem initialization.
61 The "game directory" is the first tree on the search path and directory that
62 all generated files (savegames, screenshots, demos, config files) will be
63 saved to. This can be overridden with the "-game" command line parameter.
64 The game directory can never be changed while quake is executing. This is a
65 precacution against having a malicious server instruct clients to write files
66 over areas they shouldn't.
72 =============================================================================
76 =============================================================================
79 // Magic numbers of a ZIP file (big-endian format)
80 #define ZIP_DATA_HEADER 0x504B0304 // "PK\3\4"
81 #define ZIP_CDIR_HEADER 0x504B0102 // "PK\1\2"
82 #define ZIP_END_HEADER 0x504B0506 // "PK\5\6"
84 // Other constants for ZIP files
85 #define ZIP_MAX_COMMENTS_SIZE ((unsigned short)0xFFFF)
86 #define ZIP_END_CDIR_SIZE 22
87 #define ZIP_CDIR_CHUNK_BASE_SIZE 46
88 #define ZIP_LOCAL_CHUNK_BASE_SIZE 30
90 // Zlib constants (from zlib.h)
91 #define Z_SYNC_FLUSH 2
94 #define Z_STREAM_END 1
95 #define ZLIB_VERSION "1.1.4"
99 =============================================================================
103 =============================================================================
106 // Zlib stream (from zlib.h)
107 // Warning: some pointers we don't use directly have
108 // been cast to "void*" for a matter of simplicity
111 qbyte *next_in; // next input byte
112 unsigned int avail_in; // number of bytes available at next_in
113 unsigned long total_in; // total nb of input bytes read so far
115 qbyte *next_out; // next output byte should be put there
116 unsigned int avail_out; // remaining free space at next_out
117 unsigned long total_out; // total nb of bytes output so far
119 char *msg; // last error message, NULL if no error
120 void *state; // not visible by applications
122 void *zalloc; // used to allocate the internal state
123 void *zfree; // used to free the internal state
124 void *opaque; // private data object passed to zalloc and zfree
126 int data_type; // best guess about the data type: ascii or binary
127 unsigned long adler; // adler32 value of the uncompressed data
128 unsigned long reserved; // reserved for future use
132 // Our own file structure on top of FILE
136 FS_FLAG_PACKED = (1 << 0), // inside a package (PAK or PK3)
137 FS_FLAG_DEFLATED = (1 << 1) // file is compressed using the deflate algorithm (PK3 only)
140 #define ZBUFF_SIZE 1024
144 size_t real_length; // length of the uncompressed file
145 size_t in_ind, in_max; // input buffer index and counter
146 size_t in_position; // position in the compressed file
147 size_t out_ind, out_max; // output buffer index and counter
148 size_t out_position; // how many bytes did we uncompress until now?
149 qbyte input [ZBUFF_SIZE];
150 qbyte output [ZBUFF_SIZE];
156 #ifdef FS_USESYSCALLS
161 size_t length; // file size on disk (PACKED only)
162 size_t offset; // offset into a package (PACKED only)
163 size_t position; // current position in the file (PACKED only)
164 ztoolkit_t* z; // used for inflating (DEFLATED only)
168 // ------ PK3 files on disk ------ //
170 // You can get the complete ZIP format description from PKWARE website
174 unsigned int signature;
175 unsigned short disknum;
176 unsigned short cdir_disknum; // number of the disk with the start of the central directory
177 unsigned short localentries; // number of entries in the central directory on this disk
178 unsigned short nbentries; // total number of entries in the central directory on this disk
179 unsigned int cdir_size; // size of the central directory
180 unsigned int cdir_offset; // with respect to the starting disk number
181 unsigned short comment_size;
182 } pk3_endOfCentralDir_t;
185 // ------ PAK files on disk ------ //
189 int filepos, filelen;
200 // Packages in memory
204 FILE_FLAG_TRUEOFFS = (1 << 0), // the offset in packfile_t is the true contents offset
205 FILE_FLAG_DEFLATED = (1 << 1) // file compressed using the deflate algorithm
210 char name [MAX_QPATH];
213 size_t packsize; // size in the package
214 size_t realsize; // real file size (uncompressed)
217 typedef struct pack_s
219 char filename [MAX_OSPATH];
220 #ifdef FS_USESYSCALLS
225 int ignorecase; // PK3 ignores case
233 // Search paths for files (including packages)
234 typedef struct searchpath_s
236 // only one of filename / pack will be used
237 char filename[MAX_OSPATH];
239 struct searchpath_s *next;
244 =============================================================================
248 =============================================================================
254 static packfile_t* FS_AddFileToPack (const char* name, pack_t* pack,
255 size_t offset, size_t packsize,
256 size_t realsize, file_flags_t flags);
260 =============================================================================
264 =============================================================================
267 mempool_t *fs_mempool;
268 mempool_t *pak_mempool;
272 pack_t *packlist = NULL;
274 searchpath_t *fs_searchpaths = NULL;
276 #define MAX_FILES_IN_PACK 65536
278 char fs_gamedir[MAX_OSPATH];
279 char fs_basedir[MAX_OSPATH];
281 qboolean fs_modified; // set true if using non-id files
285 =============================================================================
287 PRIVATE FUNCTIONS - PK3 HANDLING
289 =============================================================================
292 // Functions exported from zlib
294 # define ZEXPORT WINAPI
299 static int (ZEXPORT *qz_inflate) (z_stream* strm, int flush);
300 static int (ZEXPORT *qz_inflateEnd) (z_stream* strm);
301 static int (ZEXPORT *qz_inflateInit2_) (z_stream* strm, int windowBits, const char *version, int stream_size);
302 static int (ZEXPORT *qz_inflateReset) (z_stream* strm);
304 #define qz_inflateInit2(strm, windowBits) \
305 qz_inflateInit2_((strm), (windowBits), ZLIB_VERSION, sizeof(z_stream))
307 static dllfunction_t zlibfuncs[] =
309 {"inflate", (void **) &qz_inflate},
310 {"inflateEnd", (void **) &qz_inflateEnd},
311 {"inflateInit2_", (void **) &qz_inflateInit2_},
312 {"inflateReset", (void **) &qz_inflateReset},
316 // Handle for Zlib DLL
317 static dllhandle_t zlib_dll = NULL;
327 void PK3_CloseLibrary (void)
329 Sys_UnloadLibrary (&zlib_dll);
337 Try to load the Zlib DLL
340 qboolean PK3_OpenLibrary (void)
349 dllname = "zlib.dll";
355 if (! Sys_LoadLibrary (dllname, &zlib_dll, zlibfuncs))
357 Con_Printf ("Compressed files support disabled\n");
361 Con_Printf ("Compressed files support enabled\n");
368 PK3_GetEndOfCentralDir
370 Extract the end of the central directory from a PK3 package
373 #ifdef FS_USESYSCALLS
374 qboolean PK3_GetEndOfCentralDir (const char *packfile, int packhandle, pk3_endOfCentralDir_t *eocd)
376 qboolean PK3_GetEndOfCentralDir (const char *packfile, FILE *packhandle, pk3_endOfCentralDir_t *eocd)
379 long filesize, maxsize;
383 // Get the package size
384 #ifdef FS_USESYSCALLS
385 filesize = lseek (packhandle, 0, SEEK_END);
387 fseek (packhandle, 0, SEEK_END);
388 filesize = ftell(packhandle);
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 = Mem_Alloc (tempmempool, maxsize);
399 #ifdef FS_USESYSCALLS
400 lseek (packhandle, filesize - maxsize, SEEK_SET);
401 if (read (packhandle, buffer, maxsize) != (unsigned long) maxsize)
403 fseek (packhandle, filesize - maxsize, SEEK_SET);
404 if (fread (buffer, 1, maxsize, packhandle) != (unsigned long) maxsize)
411 // Look for the end of central dir signature around the end of the file
412 maxsize -= ZIP_END_CDIR_SIZE;
413 ptr = &buffer[maxsize];
415 while (BuffBigLong (ptr) != ZIP_END_HEADER)
427 memcpy (eocd, ptr, ZIP_END_CDIR_SIZE);
428 eocd->signature = LittleLong (eocd->signature);
429 eocd->disknum = LittleShort (eocd->disknum);
430 eocd->cdir_disknum = LittleShort (eocd->cdir_disknum);
431 eocd->localentries = LittleShort (eocd->localentries);
432 eocd->nbentries = LittleShort (eocd->nbentries);
433 eocd->cdir_size = LittleLong (eocd->cdir_size);
434 eocd->cdir_offset = LittleLong (eocd->cdir_offset);
435 eocd->comment_size = LittleShort (eocd->comment_size);
447 Extract the file list from a PK3 file
450 int PK3_BuildFileList (pack_t *pack, const pk3_endOfCentralDir_t *eocd)
452 qbyte *central_dir, *ptr;
456 // Load the central directory in memory
457 central_dir = Mem_Alloc (tempmempool, eocd->cdir_size);
458 #ifdef FS_USESYSCALLS
459 lseek (pack->handle, eocd->cdir_offset, SEEK_SET);
460 read (pack->handle, central_dir, eocd->cdir_size);
462 fseek (pack->handle, eocd->cdir_offset, SEEK_SET);
463 fread (central_dir, 1, eocd->cdir_size, pack->handle);
466 // Extract the files properties
467 // The parsing is done "by hand" because some fields have variable sizes and
468 // the constant part isn't 4-bytes aligned, which makes the use of structs difficult
469 remaining = eocd->cdir_size;
472 for (ind = 0; ind < eocd->nbentries; ind++)
474 size_t namesize, count;
476 // Checking the remaining size
477 if (remaining < ZIP_CDIR_CHUNK_BASE_SIZE)
479 Mem_Free (central_dir);
482 remaining -= ZIP_CDIR_CHUNK_BASE_SIZE;
485 if (BuffBigLong (ptr) != ZIP_CDIR_HEADER)
487 Mem_Free (central_dir);
491 namesize = BuffLittleShort (&ptr[28]); // filename length
493 // Check encryption, compression, and attributes
494 // 1st uint8 : general purpose bit flag
495 // Check bits 0 (encryption), 3 (data descriptor after the file), and 5 (compressed patched data (?))
496 // 2nd uint8 : external file attributes
497 // Check bits 3 (file is a directory) and 5 (file is a volume (?))
498 if ((ptr[8] & 0x29) == 0 && (ptr[38] & 0x18) == 0)
500 // Still enough bytes for the name?
501 if ((size_t) remaining < namesize || namesize >= sizeof (*pack->files))
503 Mem_Free (central_dir);
507 // WinZip doesn't use the "directory" attribute, so we need to check the name directly
508 if (ptr[ZIP_CDIR_CHUNK_BASE_SIZE + namesize - 1] != '/')
510 char filename [sizeof (pack->files[0].name)];
511 size_t offset, packsize, realsize;
514 // Extract the name (strip it if necessary)
515 if (namesize >= sizeof (filename))
516 namesize = sizeof (filename) - 1;
517 memcpy (filename, &ptr[ZIP_CDIR_CHUNK_BASE_SIZE], namesize);
518 filename[namesize] = '\0';
520 if (BuffLittleShort (&ptr[10]))
521 flags = FILE_FLAG_DEFLATED;
524 offset = BuffLittleLong (&ptr[42]);
525 packsize = BuffLittleLong (&ptr[20]);
526 realsize = BuffLittleLong (&ptr[24]);
527 FS_AddFileToPack (filename, pack, offset, packsize, realsize, flags);
531 // Skip the name, additionnal field, and comment
532 // 1er uint16 : extra field length
533 // 2eme uint16 : file comment length
534 count = namesize + BuffLittleShort (&ptr[30]) + BuffLittleShort (&ptr[32]);
535 ptr += ZIP_CDIR_CHUNK_BASE_SIZE + count;
539 Mem_Free (central_dir);
540 return pack->numfiles;
548 Create a package entry associated with a PK3 file
551 pack_t *FS_LoadPackPK3 (const char *packfile)
553 #ifdef FS_USESYSCALLS
558 pk3_endOfCentralDir_t eocd;
562 #ifdef FS_USESYSCALLS
563 packhandle = open (packfile, O_RDONLY);
567 packhandle = fopen (packfile, "rb");
572 if (! PK3_GetEndOfCentralDir (packfile, packhandle, &eocd))
573 Sys_Error ("%s is not a PK3 file", packfile);
575 // Multi-volume ZIP archives are NOT allowed
576 if (eocd.disknum != 0 || eocd.cdir_disknum != 0)
577 Sys_Error ("%s is a multi-volume ZIP archive", packfile);
579 // We only need to do this test if MAX_FILES_IN_PACK is lesser than 65535
580 // since eocd.nbentries is an unsigned 16 bits integer
581 #if MAX_FILES_IN_PACK < 65535
582 if (eocd.nbentries > MAX_FILES_IN_PACK)
583 Sys_Error ("%s contains too many files (%hu)", packfile, eocd.nbentries);
586 // Create a package structure in memory
587 pack = Mem_Alloc (pak_mempool, sizeof (pack_t));
588 pack->ignorecase = true; // PK3 ignores case
589 strlcpy (pack->filename, packfile, sizeof (pack->filename));
590 pack->handle = packhandle;
591 pack->numfiles = eocd.nbentries;
592 pack->mempool = Mem_AllocPool (packfile, 0, NULL);
593 pack->files = Mem_Alloc (pack->mempool, eocd.nbentries * sizeof(packfile_t));
594 pack->next = packlist;
597 real_nb_files = PK3_BuildFileList (pack, &eocd);
598 if (real_nb_files <= 0)
599 Sys_Error ("%s is not a valid PK3 file", packfile);
601 Con_Printf("Added packfile %s (%i files)\n", packfile, real_nb_files);
608 PK3_GetTrueFileOffset
610 Find where the true file data offset is
613 void PK3_GetTrueFileOffset (packfile_t *file, pack_t *pack)
615 qbyte buffer [ZIP_LOCAL_CHUNK_BASE_SIZE];
619 if (file->flags & FILE_FLAG_TRUEOFFS)
622 // Load the local file description
623 #ifdef FS_USESYSCALLS
624 lseek (pack->handle, file->offset, SEEK_SET);
625 count = read (pack->handle, buffer, ZIP_LOCAL_CHUNK_BASE_SIZE);
627 fseek (pack->handle, file->offset, SEEK_SET);
628 count = fread (buffer, 1, ZIP_LOCAL_CHUNK_BASE_SIZE, pack->handle);
630 if (count != ZIP_LOCAL_CHUNK_BASE_SIZE || BuffBigLong (buffer) != ZIP_DATA_HEADER)
631 Sys_Error ("Can't retrieve file %s in package %s", file->name, pack->filename);
633 // Skip name and extra field
634 file->offset += BuffLittleShort (&buffer[26]) + BuffLittleShort (&buffer[28]) + ZIP_LOCAL_CHUNK_BASE_SIZE;
636 file->flags |= FILE_FLAG_TRUEOFFS;
641 =============================================================================
643 OTHER PRIVATE FUNCTIONS
645 =============================================================================
653 Add a file to the list of files contained into a package
656 static packfile_t* FS_AddFileToPack (const char* name, pack_t* pack,
657 size_t offset, size_t packsize,
658 size_t realsize, file_flags_t flags)
660 int (*strcmp_funct) (const char* str1, const char* str2);
661 size_t left, right, middle;
665 strcmp_funct = pack->ignorecase ? strcasecmp : strcmp;
667 // Look for the slot we should put that file into (binary search)
669 right = pack->numfiles;
670 while (left != right)
672 middle = (left + right - 1) / 2;
673 diff = strcmp_funct (pack->files[middle].name, name);
675 // If we found the file, there's a problem
677 Sys_Error ("Package %s contains several time the file %s\n",
678 pack->filename, name);
680 // If we're too far in the list
687 // We have to move the right of the list by one slot to free the one we need
688 file = &pack->files[left];
689 memmove (file + 1, file, (pack->numfiles - left) * sizeof (*file));
692 strlcpy (file->name, name, sizeof (file->name));
693 file->offset = offset;
694 file->packsize = packsize;
695 file->realsize = realsize;
706 Only used for FS_Open.
709 void FS_CreatePath (char *path)
713 for (ofs = path+1 ; *ofs ; ofs++)
715 if (*ofs == '/' || *ofs == '\\')
717 // create the directory
733 void FS_Path_f (void)
737 Con_Print("Current search path:\n");
738 for (s=fs_searchpaths ; s ; s=s->next)
742 Con_Printf("%s (%i files)\n", s->pack->filename, s->pack->numfiles);
745 Con_Printf("%s\n", s->filename);
754 Takes an explicit (not game tree related) path to a pak file.
756 Loads the header and directory, adding the files at the beginning
757 of the list so they override previous pack files.
760 pack_t *FS_LoadPackPAK (const char *packfile)
762 dpackheader_t header;
764 #ifdef FS_USESYSCALLS
770 dpackfile_t *info; // temporary alloc, allowing huge pack directories
772 #ifdef FS_USESYSCALLS
773 packhandle = open (packfile, O_RDONLY);
776 read (packhandle, (void *)&header, sizeof(header));
778 packhandle = fopen (packfile, "rb");
781 fread ((void *)&header, 1, sizeof(header), packhandle);
783 if (memcmp(header.id, "PACK", 4))
784 Sys_Error ("%s is not a packfile", packfile);
785 header.dirofs = LittleLong (header.dirofs);
786 header.dirlen = LittleLong (header.dirlen);
788 if (header.dirlen % sizeof(dpackfile_t))
789 Sys_Error ("%s has an invalid directory size", packfile);
791 numpackfiles = header.dirlen / sizeof(dpackfile_t);
793 if (numpackfiles > MAX_FILES_IN_PACK)
794 Sys_Error ("%s has %i files", packfile, numpackfiles);
796 pack = Mem_Alloc(pak_mempool, sizeof (pack_t));
797 pack->ignorecase = false; // PAK is case sensitive
798 strlcpy (pack->filename, packfile, sizeof (pack->filename));
799 pack->handle = packhandle;
801 pack->mempool = Mem_AllocPool(packfile, 0, NULL);
802 pack->files = Mem_Alloc(pack->mempool, numpackfiles * sizeof(packfile_t));
803 pack->next = packlist;
806 info = Mem_Alloc(tempmempool, sizeof(*info) * numpackfiles);
807 #ifdef FS_USESYSCALLS
808 lseek (packhandle, header.dirofs, SEEK_SET);
809 read (packhandle, (void *)info, header.dirlen);
811 fseek (packhandle, header.dirofs, SEEK_SET);
812 fread ((void *)info, 1, header.dirlen, packhandle);
815 // parse the directory
816 for (i = 0;i < numpackfiles;i++)
818 size_t offset = LittleLong (info[i].filepos);
819 size_t size = LittleLong (info[i].filelen);
821 FS_AddFileToPack (info[i].name, pack, offset, size, size, FILE_FLAG_TRUEOFFS);
826 Con_Printf("Added packfile %s (%i files)\n", packfile, numpackfiles);
835 Sets fs_gamedir, adds the directory to the head of the path,
836 then loads and adds pak1.pak pak2.pak ...
839 void FS_AddGameDirectory (char *dir)
841 stringlist_t *list, *current;
842 searchpath_t *search;
844 char pakfile[MAX_OSPATH];
846 strlcpy (fs_gamedir, dir, sizeof (fs_gamedir));
848 list = listdirectory(dir);
850 // add any PAK package in the directory
851 for (current = list;current;current = current->next)
853 if (matchpattern(current->text, "*.pak", true))
855 snprintf (pakfile, sizeof (pakfile), "%s/%s", dir, current->text);
856 pak = FS_LoadPackPAK (pakfile);
859 search = Mem_Alloc(pak_mempool, sizeof(searchpath_t));
861 search->next = fs_searchpaths;
862 fs_searchpaths = search;
865 Con_Printf("unable to load pak \"%s\"\n", pakfile);
869 // add any PK3 package in the director
870 for (current = list;current;current = current->next)
872 if (matchpattern(current->text, "*.pk3", true))
874 snprintf (pakfile, sizeof (pakfile), "%s/%s", dir, current->text);
875 pak = FS_LoadPackPK3 (pakfile);
878 search = Mem_Alloc(pak_mempool, sizeof(searchpath_t));
880 search->next = fs_searchpaths;
881 fs_searchpaths = search;
884 Con_Printf("unable to load pak \"%s\"\n", pakfile);
889 // Add the directory to the search path
890 // (unpacked files have the priority over packed files)
891 search = Mem_Alloc(pak_mempool, sizeof(searchpath_t));
892 strlcpy (search->filename, dir, sizeof (search->filename));
893 search->next = fs_searchpaths;
894 fs_searchpaths = search;
903 char *FS_FileExtension (const char *in)
905 static char exten[8];
906 const char *slash, *backslash, *colon, *dot, *separator;
909 slash = strrchr(in, '/');
910 backslash = strrchr(in, '\\');
911 colon = strrchr(in, ':');
912 dot = strrchr(in, '.');
914 if (separator < backslash)
915 separator = backslash;
916 if (separator < colon)
918 if (dot == NULL || dot < separator)
921 for (i = 0;i < 7 && dot[i];i++)
936 searchpath_t *search;
938 fs_mempool = Mem_AllocPool("file management", 0, NULL);
939 pak_mempool = Mem_AllocPool("paks", 0, NULL);
941 Cvar_RegisterVariable (&scr_screenshot_name);
943 Cmd_AddCommand ("path", FS_Path_f);
944 Cmd_AddCommand ("dir", FS_Dir_f);
945 Cmd_AddCommand ("ls", FS_Ls_f);
947 strcpy(fs_basedir, ".");
948 strcpy(fs_gamedir, ".");
953 // Overrides the system supplied base directory (under GAMENAME)
954 // 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)
955 i = COM_CheckParm ("-basedir");
956 if (i && i < com_argc-1)
958 strlcpy (fs_basedir, com_argv[i+1], sizeof (fs_basedir));
959 i = strlen (fs_basedir);
960 if (i > 0 && (fs_basedir[i-1] == '\\' || fs_basedir[i-1] == '/'))
964 // -path <dir or packfile> [<dir or packfile>] ...
965 // Fully specifies the exact search path, overriding the generated one
966 // 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)
967 i = COM_CheckParm ("-path");
971 while (++i < com_argc)
973 if (!com_argv[i] || com_argv[i][0] == '+' || com_argv[i][0] == '-')
976 search = Mem_Alloc(pak_mempool, sizeof(searchpath_t));
977 if (!strcasecmp (FS_FileExtension(com_argv[i]), "pak"))
979 search->pack = FS_LoadPackPAK (com_argv[i]);
981 Sys_Error ("Couldn't load packfile: %s", com_argv[i]);
983 else if (!strcasecmp (FS_FileExtension (com_argv[i]), "pk3"))
985 search->pack = FS_LoadPackPK3 (com_argv[i]);
987 Sys_Error ("Couldn't load packfile: %s", com_argv[i]);
990 strlcpy (search->filename, com_argv[i], sizeof (search->filename));
991 search->next = fs_searchpaths;
992 fs_searchpaths = search;
997 // start up with GAMENAME by default (id1)
998 strlcpy (com_modname, GAMENAME, sizeof (com_modname));
999 FS_AddGameDirectory (va("%s/"GAMENAME, fs_basedir));
1000 Cvar_SetQuick (&scr_screenshot_name, gamescreenshotname);
1002 // add the game-specific path, if any
1006 strlcpy (com_modname, gamedirname, sizeof (com_modname));
1007 FS_AddGameDirectory (va("%s/%s", fs_basedir, gamedirname));
1011 // Adds basedir/gamedir as an override game
1012 // LordHavoc: now supports multiple -game directories
1013 for (i = 1;i < com_argc;i++)
1017 if (!strcmp (com_argv[i], "-game") && i < com_argc-1)
1021 strlcpy (com_modname, com_argv[i], sizeof (com_modname));
1022 FS_AddGameDirectory (va("%s/%s", fs_basedir, com_argv[i]));
1023 Cvar_SetQuick (&scr_screenshot_name, com_modname);
1030 ====================
1033 Internal function used to create a qfile_t and open the relevant file on disk
1034 ====================
1036 static qfile_t* FS_SysOpen (const char* filepath, const char* mode)
1040 file = Mem_Alloc (fs_mempool, sizeof (*file));
1041 memset (file, 0, sizeof (*file));
1043 #ifdef FS_USESYSCALLS
1044 if (strchr(mode, 'r'))
1045 file->stream = open (filepath, O_RDONLY);
1046 else if (strchr(mode, 'w'))
1047 file->stream = open (filepath, O_WRONLY | O_CREAT | O_TRUNC, 0666);
1048 else if (strchr(mode, 'a'))
1049 file->stream = open (filepath, O_RDWR | O_CREAT | O_APPEND, 0666);
1052 if (file->stream < 0)
1058 file->stream = fopen (filepath, mode);
1075 qfile_t *FS_OpenRead (const char *path, int offs, int len)
1079 file = FS_SysOpen (path, "rb");
1082 Sys_Error ("Couldn't open %s", path);
1087 if (offs < 0 || len < 0)
1089 // We set fs_filesize here for normal files
1090 #ifdef FS_USESYSCALLS
1091 fs_filesize = lseek (file->stream, 0, SEEK_END);
1092 lseek (file->stream, 0, SEEK_SET);
1094 fseek (file->stream, 0, SEEK_END);
1095 fs_filesize = ftell (file->stream);
1096 fseek (file->stream, 0, SEEK_SET);
1102 #ifdef FS_USESYSCALLS
1103 lseek (file->stream, offs, SEEK_SET);
1105 fseek (file->stream, offs, SEEK_SET);
1108 file->flags |= FS_FLAG_PACKED;
1110 file->offset = offs;
1119 ====================
1122 Look for a file in the packages and in the filesystem
1124 Return the searchpath where the file was found (or NULL)
1125 and the file index in the package if relevant
1126 ====================
1128 static searchpath_t *FS_FindFile (const char *name, int* index, qboolean quiet)
1130 searchpath_t *search;
1132 int (*strcmp_funct) (const char* str1, const char* str2);
1134 // search through the path, one element at a time
1135 for (search = fs_searchpaths;search;search = search->next)
1137 // is the element a pak file?
1140 size_t left, right, middle;
1143 strcmp_funct = pak->ignorecase ? strcasecmp : strcmp;
1145 // Look for the file (binary search)
1147 right = pak->numfiles;
1148 while (left != right)
1152 middle = (left + right - 1) / 2;
1153 diff = strcmp_funct (pak->files[middle].name, name);
1159 Sys_Printf("FS_FindFile: %s in %s\n",
1160 pak->files[middle].name, pak->filename);
1167 // If we're too far in the list
1176 char netpath[MAX_OSPATH];
1177 snprintf(netpath, sizeof(netpath), "%s/%s", search->filename, name);
1178 if (FS_SysFileExists (netpath))
1181 Sys_Printf("FS_FindFile: %s\n", netpath);
1191 Sys_Printf("FS_FindFile: can't find %s\n", name);
1203 If the requested file is inside a packfile, a new qfile_t* will be opened
1209 qfile_t *FS_FOpenFile (const char *filename, qboolean quiet)
1211 searchpath_t *search;
1212 packfile_t *packfile;
1216 search = FS_FindFile (filename, &i, quiet);
1225 // Found in the filesystem?
1228 char netpath[MAX_OSPATH];
1229 snprintf(netpath, sizeof(netpath), "%s/%s", search->filename, filename);
1230 return FS_OpenRead(netpath, -1, -1);
1233 // So, we found it in a package...
1234 packfile = &search->pack->files[i];
1236 // If we don't have the true offset, get it now
1237 if (! (packfile->flags & FILE_FLAG_TRUEOFFS))
1238 PK3_GetTrueFileOffset (packfile, search->pack);
1240 // No Zlib DLL = no compressed files
1241 if (!zlib_dll && (packfile->flags & FILE_FLAG_DEFLATED))
1243 Con_Printf("WARNING: can't open the compressed file %s\n"
1244 "You need the Zlib DLL to use compressed files\n",
1250 // open a new file in the pakfile
1251 file = FS_OpenRead (search->pack->filename, packfile->offset, packfile->packsize);
1252 fs_filesize = packfile->realsize;
1254 if (packfile->flags & FILE_FLAG_DEFLATED)
1258 file->flags |= FS_FLAG_DEFLATED;
1260 // We need some more variables
1261 ztk = Mem_Alloc (fs_mempool, sizeof (*file->z));
1263 ztk->real_length = packfile->realsize;
1265 // Initialize zlib stream
1266 ztk->zstream.next_in = ztk->input;
1267 ztk->zstream.avail_in = 0;
1269 /* From Zlib's "unzip.c":
1271 * windowBits is passed < 0 to tell that there is no zlib header.
1272 * Note that in this case inflate *requires* an extra "dummy" byte
1273 * after the compressed stream in order to complete decompression and
1274 * return Z_STREAM_END.
1275 * In unzip, i don't wait absolutely Z_STREAM_END because I known the
1276 * size of both compressed and uncompressed data
1278 if (qz_inflateInit2 (&ztk->zstream, -MAX_WBITS) != Z_OK)
1279 Sys_Error ("inflate init error (file: %s)", filename);
1281 ztk->zstream.next_out = ztk->output;
1282 ztk->zstream.avail_out = sizeof (ztk->output);
1292 =============================================================================
1294 MAIN PUBLIC FUNCTIONS
1296 =============================================================================
1300 ====================
1303 Open a file. The syntax is the same as fopen
1304 ====================
1306 qfile_t* FS_Open (const char* filepath, const char* mode, qboolean quiet)
1308 // If the file is opened in "write" or "append" mode
1309 if (strchr (mode, 'w') || strchr (mode, 'a'))
1311 char real_path [MAX_OSPATH];
1313 // Open the file on disk directly
1314 snprintf (real_path, sizeof (real_path), "%s/%s", fs_gamedir, filepath);
1316 // Create directories up to the file
1317 FS_CreatePath (real_path);
1319 return FS_SysOpen (real_path, mode);
1322 // Else, we look at the various search paths
1323 return FS_FOpenFile (filepath, quiet);
1328 ====================
1332 ====================
1334 int FS_Close (qfile_t* file)
1336 #ifdef FS_USESYSCALLS
1337 if (close (file->stream))
1339 if (fclose (file->stream))
1345 qz_inflateEnd (&file->z->zstream);
1355 ====================
1358 Write "datasize" bytes into a file
1359 ====================
1361 size_t FS_Write (qfile_t* file, const void* data, size_t datasize)
1363 #ifdef FS_USESYSCALLS
1364 return write (file->stream, data, datasize);
1366 return fwrite (data, 1, datasize, file->stream);
1372 ====================
1375 Read up to "buffersize" bytes from a file
1376 ====================
1378 size_t FS_Read (qfile_t* file, void* buffer, size_t buffersize)
1383 // Quick path for unpacked files
1384 if (! (file->flags & FS_FLAG_PACKED))
1385 #ifdef FS_USESYSCALLS
1386 return read (file->stream, buffer, buffersize);
1388 return fread (buffer, 1, buffersize, file->stream);
1391 // If the file isn't compressed
1392 if (! (file->flags & FS_FLAG_DEFLATED))
1394 // We must take care to not read after the end of the file
1395 count = file->length - file->position;
1396 if (buffersize > count)
1399 #ifdef FS_USESYSCALLS
1400 nb = read (file->stream, buffer, buffersize);
1402 nb = fread (buffer, 1, buffersize, file->stream);
1405 file->position += nb;
1409 // If the file is compressed, it's more complicated...
1412 // First, we copy as many bytes as we can from "output"
1413 if (ztk->out_ind < ztk->out_max)
1415 count = ztk->out_max - ztk->out_ind;
1417 nb = (buffersize > count) ? count : buffersize;
1418 memcpy (buffer, &ztk->output[ztk->out_ind], nb);
1420 file->position += nb;
1425 // We cycle through a few operations until we have inflated enough data
1426 while (nb < buffersize)
1428 // NOTE: at this point, "output" should always be empty
1430 // If "input" is also empty, we need to fill it
1431 if (ztk->in_ind == ztk->in_max)
1435 // If we are at the end of the file
1436 if (ztk->out_position == ztk->real_length)
1439 remain = file->length - ztk->in_position;
1440 count = (remain > sizeof (ztk->input)) ? sizeof (ztk->input) : remain;
1441 #ifdef FS_USESYSCALLS
1442 read (file->stream, ztk->input, count);
1444 fread (ztk->input, 1, count, file->stream);
1447 // Update indexes and counters
1449 ztk->in_max = count;
1450 ztk->in_position += count;
1453 // Now that we are sure we have compressed data available, we need to determine
1454 // if it's better to inflate it in "output" or directly in "buffer" (we are in this
1455 // case if we still need more bytes than "output" can contain)
1457 ztk->zstream.next_in = &ztk->input[ztk->in_ind];
1458 ztk->zstream.avail_in = ztk->in_max - ztk->in_ind;
1460 // If output will be able to contain at least 1 more byte than the data we need
1461 if (buffersize - nb < sizeof (ztk->output))
1465 // Inflate the data in "output"
1466 ztk->zstream.next_out = ztk->output;
1467 ztk->zstream.avail_out = sizeof (ztk->output);
1468 error = qz_inflate (&ztk->zstream, Z_SYNC_FLUSH);
1469 if (error != Z_OK && error != Z_STREAM_END)
1470 Sys_Error ("Can't inflate file");
1471 ztk->in_ind = ztk->in_max - ztk->zstream.avail_in;
1472 ztk->out_max = sizeof (ztk->output) - ztk->zstream.avail_out;
1473 ztk->out_position += ztk->out_max;
1475 // Copy the requested data in "buffer" (as much as we can)
1476 count = (buffersize - nb > ztk->out_max) ? ztk->out_max : buffersize - nb;
1477 memcpy (&((qbyte*)buffer)[nb], ztk->output, count);
1478 ztk->out_ind = count;
1481 // Else, we inflate directly in "buffer"
1486 // Inflate the data in "buffer"
1487 ztk->zstream.next_out = &((qbyte*)buffer)[nb];
1488 ztk->zstream.avail_out = buffersize - nb;
1489 error = qz_inflate (&ztk->zstream, Z_SYNC_FLUSH);
1490 if (error != Z_OK && error != Z_STREAM_END)
1491 Sys_Error ("Can't inflate file");
1492 ztk->in_ind = ztk->in_max - ztk->zstream.avail_in;
1494 // Invalidate the output data (for FS_Seek)
1498 // How much data did it inflate?
1499 count = buffersize - nb - ztk->zstream.avail_out;
1500 ztk->out_position += count;
1504 file->position += count;
1512 ====================
1515 Flush the file output stream
1516 ====================
1518 int FS_Flush (qfile_t* file)
1520 #ifdef FS_USESYSCALLS
1523 return fflush (file->stream);
1529 ====================
1532 Print a string into a file
1533 ====================
1535 int FS_Print(qfile_t* file, const char *msg)
1537 return FS_Write(file, msg, strlen(msg));
1541 ====================
1544 Print a string into a file
1545 ====================
1547 int FS_Printf(qfile_t* file, const char* format, ...)
1552 va_start (args, format);
1553 result = FS_VPrintf(file, format, args);
1561 ====================
1564 Print a string into a file
1565 ====================
1567 int FS_VPrintf(qfile_t* file, const char* format, va_list ap)
1569 #ifdef FS_USESYSCALLS
1572 char tempstring[1024];
1573 len = vsnprintf (tempstring, sizeof(tempstring), format, ap);
1574 if (len >= sizeof(tempstring))
1577 char *temp = Mem_Alloc(tempmempool, len + 1);
1578 len = vsnprintf (temp, len + 1, format, ap);
1579 result = write (file->stream, temp, len);
1584 return write (file->stream, tempstring, len);
1587 return vfprintf (file->stream, format, ap);
1593 ====================
1596 Get the next character of a file
1597 ====================
1599 int FS_Getc (qfile_t* file)
1603 if (FS_Read (file, &c, 1) != 1)
1611 ====================
1614 Move the position index in a file
1615 ====================
1617 int FS_Seek (qfile_t* file, long offset, int whence)
1619 // Quick path for unpacked files
1620 if (! (file->flags & FS_FLAG_PACKED))
1621 #ifdef FS_USESYSCALLS
1622 return lseek (file->stream, offset, whence);
1624 return fseek (file->stream, offset, whence);
1627 // Seeking in compressed files is more a hack than anything else,
1628 // but we need to support it, so here it is.
1629 if (file->flags & FS_FLAG_DEFLATED)
1631 ztoolkit_t *ztk = file->z;
1632 qbyte buffer [sizeof (ztk->output)]; // it's big to force inflating into buffer directly
1637 offset += file->position;
1644 offset += ztk->real_length;
1650 if (offset < 0 || offset > (long) ztk->real_length)
1653 // If we need to go back in the file
1654 if (offset <= (long) file->position)
1656 // If we still have the data we need in the output buffer
1657 if (file->position - offset <= ztk->out_ind)
1659 ztk->out_ind -= file->position - offset;
1660 file->position = offset;
1664 // Else, we restart from the beginning of the file
1667 ztk->in_position = 0;
1670 ztk->out_position = 0;
1672 #ifdef FS_USESYSCALLS
1673 lseek (file->stream, file->offset, SEEK_SET);
1675 fseek (file->stream, file->offset, SEEK_SET);
1678 // Reset the Zlib stream
1679 ztk->zstream.next_in = ztk->input;
1680 ztk->zstream.avail_in = 0;
1681 qz_inflateReset (&ztk->zstream);
1684 // Skip all data until we reach the requested offset
1685 while ((long) file->position < offset)
1687 size_t diff = offset - file->position;
1690 count = (diff > sizeof (buffer)) ? sizeof (buffer) : diff;
1691 len = FS_Read (file, buffer, count);
1699 // Packed files receive a special treatment too, because
1700 // we need to make sure it doesn't go outside of the file
1704 offset += file->position;
1711 offset += file->length;
1717 if (offset < 0 || offset > (long) file->length)
1720 #ifdef FS_USESYSCALLS
1721 if (lseek (file->stream, file->offset + offset, SEEK_SET) == -1)
1724 if (fseek (file->stream, file->offset + offset, SEEK_SET) == -1)
1727 file->position = offset;
1733 ====================
1736 Give the current position in a file
1737 ====================
1739 long FS_Tell (qfile_t* file)
1741 if (file->flags & FS_FLAG_PACKED)
1742 return file->position;
1744 #ifdef FS_USESYSCALLS
1745 return lseek (file->stream, 0, SEEK_CUR);
1747 return ftell (file->stream);
1753 ====================
1756 Extract a line from a file
1757 ====================
1759 char* FS_Gets (qfile_t* file, char* buffer, int buffersize)
1763 // Quick path for unpacked files
1764 #ifndef FS_USESYSCALLS
1765 if (! (file->flags & FS_FLAG_PACKED))
1766 return fgets (buffer, buffersize, file->stream);
1769 for (ind = 0; ind < (size_t) buffersize - 1; ind++)
1771 int c = FS_Getc (file);
1786 buffer[ind + 1] = '\0';
1795 buffer[buffersize - 1] = '\0';
1804 Dynamic length version of fgets. DO NOT free the buffer.
1807 char *FS_Getline (qfile_t *file)
1809 static int size = 256;
1810 static char *buf = 0;
1815 buf = Mem_Alloc (fs_mempool, size);
1817 if (!FS_Gets (file, buf, size))
1821 while (buf[len - 1] != '\n' && buf[len - 1] != '\r')
1823 t = Mem_Alloc (fs_mempool, size + 256);
1824 memcpy(t, buf, size);
1828 if (!FS_Gets (file, buf + len, size - len))
1832 while ((len = strlen(buf)) && (buf[len - 1] == '\n' || buf[len - 1] == '\r'))
1839 ====================
1842 Extract a line from a file
1843 ====================
1845 // FIXME: remove this function?
1846 int FS_Eof (qfile_t* file)
1848 if (file->flags & FS_FLAG_PACKED)
1850 if (file->flags & FS_FLAG_DEFLATED)
1851 return (file->position == file->z->real_length);
1853 return (file->position == file->length);
1856 #ifdef FS_USESYSCALLS
1857 Sys_Error("FS_Eof: not implemented using syscalls\n");
1860 return feof (file->stream);
1869 Filename are relative to the quake directory.
1870 Always appends a 0 byte.
1873 qbyte *FS_LoadFile (const char *path, mempool_t *pool, qboolean quiet)
1878 // look for it in the filesystem or pack files
1879 h = FS_Open (path, "rb", quiet);
1883 buf = Mem_Alloc(pool, fs_filesize+1);
1885 Sys_Error ("FS_LoadFile: not enough available memory for %s (size %i)", path, fs_filesize);
1887 ((qbyte *)buf)[fs_filesize] = 0;
1889 FS_Read (h, buf, fs_filesize);
1900 The filename will be prefixed by the current game directory
1903 qboolean FS_WriteFile (const char *filename, void *data, int len)
1907 handle = FS_Open (filename, "wb", false);
1910 Con_Printf("FS_WriteFile: failed on %s\n", filename);
1914 Con_DPrintf("FS_WriteFile: %s\n", filename);
1915 FS_Write (handle, data, len);
1922 =============================================================================
1924 OTHERS PUBLIC FUNCTIONS
1926 =============================================================================
1934 void FS_StripExtension (const char *in, char *out, size_t size_out)
1941 while (*in && size_out > 1)
1945 else if (*in == '/' || *in == '\\' || *in == ':')
1962 void FS_DefaultExtension (char *path, const char *extension, size_t size_path)
1966 // if path doesn't have a .EXT, append extension
1967 // (extension should include the .)
1968 src = path + strlen(path) - 1;
1970 while (*src != '/' && src != path)
1973 return; // it has an extension
1977 strlcat (path, extension, size_path);
1985 Look for a file in the packages and in the filesystem
1988 qboolean FS_FileExists (const char *filename)
1990 return (FS_FindFile (filename, NULL, true) != NULL);
1998 Look for a file in the filesystem only
2001 qboolean FS_SysFileExists (const char *path)
2006 f = fopen (path, "rb");
2017 if (stat (path,&buf) == -1)
2024 void FS_mkdir (const char *path)
2037 Allocate and fill a search structure with information on matching filenames.
2040 fssearch_t *FS_Search(const char *pattern, int caseinsensitive, int quiet)
2043 searchpath_t *searchpath;
2045 int i, basepathlength, numfiles, numchars;
2046 stringlist_t *dir, *dirfile, *liststart, *listcurrent, *listtemp;
2047 const char *slash, *backslash, *colon, *separator;
2049 char netpath[MAX_OSPATH];
2050 char temp[MAX_OSPATH];
2052 while(!strncmp(pattern, "./", 2))
2054 while(!strncmp(pattern, ".\\", 2))
2061 slash = strrchr(pattern, '/');
2062 backslash = strrchr(pattern, '\\');
2063 colon = strrchr(pattern, ':');
2064 separator = pattern;
2065 if (separator < slash)
2067 if (separator < backslash)
2068 separator = backslash;
2069 if (separator < colon)
2071 basepathlength = separator - pattern;
2072 basepath = Mem_Alloc (tempmempool, basepathlength + 1);
2074 memcpy(basepath, pattern, basepathlength);
2075 basepath[basepathlength] = 0;
2077 // search through the path, one element at a time
2078 for (searchpath = fs_searchpaths;searchpath;searchpath = searchpath->next)
2080 // is the element a pak file?
2081 if (searchpath->pack)
2083 // look through all the pak file elements
2084 pak = searchpath->pack;
2085 for (i = 0;i < pak->numfiles;i++)
2087 strcpy(temp, pak->files[i].name);
2090 if (matchpattern(temp, (char *)pattern, true))
2092 for (listtemp = liststart;listtemp;listtemp = listtemp->next)
2093 if (!strcmp(listtemp->text, temp))
2095 if (listtemp == NULL)
2097 listcurrent = stringlistappend(listcurrent, temp);
2098 if (liststart == NULL)
2099 liststart = listcurrent;
2101 Sys_Printf("SearchPackFile: %s : %s\n", pak->filename, temp);
2104 // strip off one path element at a time until empty
2105 // this way directories are added to the listing if they match the pattern
2106 slash = strrchr(temp, '/');
2107 backslash = strrchr(temp, '\\');
2108 colon = strrchr(temp, ':');
2110 if (separator < slash)
2112 if (separator < backslash)
2113 separator = backslash;
2114 if (separator < colon)
2116 *((char *)separator) = 0;
2122 // get a directory listing and look at each name
2123 snprintf(netpath, sizeof (netpath), "%s/%s", searchpath->filename, basepath);
2124 if ((dir = listdirectory(netpath)))
2126 for (dirfile = dir;dirfile;dirfile = dirfile->next)
2128 snprintf(temp, sizeof(temp), "%s/%s", basepath, dirfile->text);
2129 if (matchpattern(temp, (char *)pattern, true))
2131 for (listtemp = liststart;listtemp;listtemp = listtemp->next)
2132 if (!strcmp(listtemp->text, temp))
2134 if (listtemp == NULL)
2136 listcurrent = stringlistappend(listcurrent, temp);
2137 if (liststart == NULL)
2138 liststart = listcurrent;
2140 Sys_Printf("SearchDirFile: %s\n", temp);
2151 liststart = stringlistsort(liststart);
2154 for (listtemp = liststart;listtemp;listtemp = listtemp->next)
2157 numchars += strlen(listtemp->text) + 1;
2159 search = Z_Malloc(sizeof(fssearch_t) + numchars + numfiles * sizeof(char *));
2160 search->filenames = (char **)((char *)search + sizeof(fssearch_t));
2161 search->filenamesbuffer = (char *)((char *)search + sizeof(fssearch_t) + numfiles * sizeof(char *));
2162 search->numfilenames = numfiles;
2165 for (listtemp = liststart;listtemp;listtemp = listtemp->next)
2167 search->filenames[numfiles] = search->filenamesbuffer + numchars;
2168 strcpy(search->filenames[numfiles], listtemp->text);
2170 numchars += strlen(listtemp->text) + 1;
2173 stringlistfree(liststart);
2180 void FS_FreeSearch(fssearch_t *search)
2185 extern int con_linewidth;
2186 int FS_ListDirectory(const char *pattern, int oneperline)
2197 search = FS_Search(pattern, true, true);
2200 numfiles = search->numfilenames;
2203 // FIXME: the names could be added to one column list and then
2204 // gradually shifted into the next column if they fit, and then the
2205 // next to make a compact variable width listing but it's a lot more
2207 // find width for columns
2209 for (i = 0;i < numfiles;i++)
2211 l = strlen(search->filenames[i]);
2212 if (columnwidth < l)
2215 // count the spacing character
2217 // calculate number of columns
2218 numcolumns = con_linewidth / columnwidth;
2219 // don't bother with the column printing if it's only one column
2220 if (numcolumns >= 2)
2222 numlines = (numfiles + numcolumns - 1) / numcolumns;
2223 for (i = 0;i < numlines;i++)
2226 for (k = 0;k < numcolumns;k++)
2228 l = i * numcolumns + k;
2231 name = search->filenames[l];
2232 for (j = 0;name[j] && j < (int)sizeof(linebuf) - 1;j++)
2233 linebuf[linebufpos++] = name[j];
2234 // space out name unless it's the last on the line
2235 if (k < (numcolumns - 1) && l < (numfiles - 1))
2236 for (;j < columnwidth && j < (int)sizeof(linebuf) - 1;j++)
2237 linebuf[linebufpos++] = ' ';
2240 linebuf[linebufpos] = 0;
2241 Con_Printf("%s\n", linebuf);
2248 for (i = 0;i < numfiles;i++)
2249 Con_Printf("%s\n", search->filenames[i]);
2250 FS_FreeSearch(search);
2254 static void FS_ListDirectoryCmd (const char* cmdname, int oneperline)
2256 const char *pattern;
2259 Con_Printf("usage:\n%s [path/pattern]\n", cmdname);
2262 if (Cmd_Argc() == 2)
2263 pattern = Cmd_Argv(1);
2266 if (!FS_ListDirectory(pattern, oneperline))
2267 Con_Print("No files found.\n");
2272 FS_ListDirectoryCmd("dir", true);
2277 FS_ListDirectoryCmd("ls", false);