4 Copyright (C) 2003 Mathieu Olivier
5 Copyright (C) 1999,2000 contributors of the QuakeForge project
7 This program is free software; you can redistribute it and/or
8 modify it under the terms of the GNU General Public License
9 as published by the Free Software Foundation; either version 2
10 of the License, or (at your option) any later version.
12 This program is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
16 See the GNU General Public License for more details.
18 You should have received a copy of the GNU General Public License
19 along with this program; if not, write to:
21 Free Software Foundation, Inc.
22 59 Temple Place - Suite 330
23 Boston, MA 02111-1307, USA
36 # include <sys/stat.h>
46 // use syscalls instead of f* functions
47 #define FS_USESYSCALLS
49 // Win32 requires us to add O_BINARY, but the other OSes don't have it
59 All of Quake's data access is through a hierchal file system, but the contents
60 of the file system can be transparently merged from several sources.
62 The "base directory" is the path to the directory holding the quake.exe and
63 all game directories. The sys_* files pass this to host_init in
64 quakeparms_t->basedir. This can be overridden with the "-basedir" command
65 line parm to allow code debugging in a different directory. The base
66 directory is only used during filesystem initialization.
68 The "game directory" is the first tree on the search path and directory that
69 all generated files (savegames, screenshots, demos, config files) will be
70 saved to. This can be overridden with the "-game" command line parameter.
71 The game directory can never be changed while quake is executing. This is a
72 precacution against having a malicious server instruct clients to write files
73 over areas they shouldn't.
79 =============================================================================
83 =============================================================================
86 // Magic numbers of a ZIP file (big-endian format)
87 #define ZIP_DATA_HEADER 0x504B0304 // "PK\3\4"
88 #define ZIP_CDIR_HEADER 0x504B0102 // "PK\1\2"
89 #define ZIP_END_HEADER 0x504B0506 // "PK\5\6"
91 // Other constants for ZIP files
92 #define ZIP_MAX_COMMENTS_SIZE ((unsigned short)0xFFFF)
93 #define ZIP_END_CDIR_SIZE 22
94 #define ZIP_CDIR_CHUNK_BASE_SIZE 46
95 #define ZIP_LOCAL_CHUNK_BASE_SIZE 30
97 // Zlib constants (from zlib.h)
98 #define Z_SYNC_FLUSH 2
101 #define Z_STREAM_END 1
102 #define ZLIB_VERSION "1.1.4"
106 =============================================================================
110 =============================================================================
113 // Zlib stream (from zlib.h)
114 // Warning: some pointers we don't use directly have
115 // been cast to "void*" for a matter of simplicity
118 qbyte *next_in; // next input byte
119 unsigned int avail_in; // number of bytes available at next_in
120 unsigned long total_in; // total nb of input bytes read so far
122 qbyte *next_out; // next output byte should be put there
123 unsigned int avail_out; // remaining free space at next_out
124 unsigned long total_out; // total nb of bytes output so far
126 char *msg; // last error message, NULL if no error
127 void *state; // not visible by applications
129 void *zalloc; // used to allocate the internal state
130 void *zfree; // used to free the internal state
131 void *opaque; // private data object passed to zalloc and zfree
133 int data_type; // best guess about the data type: ascii or binary
134 unsigned long adler; // adler32 value of the uncompressed data
135 unsigned long reserved; // reserved for future use
139 // Our own file structure on top of FILE
143 FS_FLAG_PACKED = (1 << 0), // inside a package (PAK or PK3)
144 FS_FLAG_DEFLATED = (1 << 1) // file is compressed using the deflate algorithm (PK3 only)
147 #define ZBUFF_SIZE 1024
151 size_t real_length; // length of the uncompressed file
152 size_t in_ind, in_max; // input buffer index and counter
153 size_t in_position; // position in the compressed file
154 size_t out_ind, out_max; // output buffer index and counter
155 size_t out_position; // how many bytes did we uncompress until now?
156 qbyte input [ZBUFF_SIZE];
157 qbyte output [ZBUFF_SIZE];
163 #ifdef FS_USESYSCALLS
168 size_t length; // file size on disk (PACKED only)
169 size_t offset; // offset into a package (PACKED only)
170 size_t position; // current position in the file (PACKED only)
171 ztoolkit_t* z; // used for inflating (DEFLATED only)
175 // ------ PK3 files on disk ------ //
177 // You can get the complete ZIP format description from PKWARE website
181 unsigned int signature;
182 unsigned short disknum;
183 unsigned short cdir_disknum; // number of the disk with the start of the central directory
184 unsigned short localentries; // number of entries in the central directory on this disk
185 unsigned short nbentries; // total number of entries in the central directory on this disk
186 unsigned int cdir_size; // size of the central directory
187 unsigned int cdir_offset; // with respect to the starting disk number
188 unsigned short comment_size;
189 } pk3_endOfCentralDir_t;
192 // ------ PAK files on disk ------ //
196 int filepos, filelen;
207 // Packages in memory
211 FILE_FLAG_TRUEOFFS = (1 << 0), // the offset in packfile_t is the true contents offset
212 FILE_FLAG_DEFLATED = (1 << 1) // file compressed using the deflate algorithm
217 char name [MAX_QPATH];
220 size_t packsize; // size in the package
221 size_t realsize; // real file size (uncompressed)
224 typedef struct pack_s
226 char filename [MAX_OSPATH];
227 #ifdef FS_USESYSCALLS
232 int ignorecase; // PK3 ignores case
240 // Search paths for files (including packages)
241 typedef struct searchpath_s
243 // only one of filename / pack will be used
244 char filename[MAX_OSPATH];
246 struct searchpath_s *next;
251 =============================================================================
255 =============================================================================
261 static packfile_t* FS_AddFileToPack (const char* name, pack_t* pack,
262 size_t offset, size_t packsize,
263 size_t realsize, file_flags_t flags);
267 =============================================================================
271 =============================================================================
274 mempool_t *fs_mempool;
275 mempool_t *pak_mempool;
279 pack_t *packlist = NULL;
281 searchpath_t *fs_searchpaths = NULL;
283 #define MAX_FILES_IN_PACK 65536
285 char fs_gamedir[MAX_OSPATH];
286 char fs_basedir[MAX_OSPATH];
288 qboolean fs_modified; // set true if using non-id files
292 =============================================================================
294 PRIVATE FUNCTIONS - PK3 HANDLING
296 =============================================================================
299 // Functions exported from zlib
301 # define ZEXPORT WINAPI
306 static int (ZEXPORT *qz_inflate) (z_stream* strm, int flush);
307 static int (ZEXPORT *qz_inflateEnd) (z_stream* strm);
308 static int (ZEXPORT *qz_inflateInit2_) (z_stream* strm, int windowBits, const char *version, int stream_size);
309 static int (ZEXPORT *qz_inflateReset) (z_stream* strm);
311 #define qz_inflateInit2(strm, windowBits) \
312 qz_inflateInit2_((strm), (windowBits), ZLIB_VERSION, sizeof(z_stream))
314 static dllfunction_t zlibfuncs[] =
316 {"inflate", (void **) &qz_inflate},
317 {"inflateEnd", (void **) &qz_inflateEnd},
318 {"inflateInit2_", (void **) &qz_inflateInit2_},
319 {"inflateReset", (void **) &qz_inflateReset},
323 // Handle for Zlib DLL
324 static dllhandle_t zlib_dll = NULL;
334 void PK3_CloseLibrary (void)
336 Sys_UnloadLibrary (&zlib_dll);
344 Try to load the Zlib DLL
347 qboolean PK3_OpenLibrary (void)
356 dllname = "zlib.dll";
362 if (! Sys_LoadLibrary (dllname, &zlib_dll, zlibfuncs))
364 Con_Printf ("Compressed files support disabled\n");
368 Con_Printf ("Compressed files support enabled\n");
375 PK3_GetEndOfCentralDir
377 Extract the end of the central directory from a PK3 package
380 #ifdef FS_USESYSCALLS
381 qboolean PK3_GetEndOfCentralDir (const char *packfile, int packhandle, pk3_endOfCentralDir_t *eocd)
383 qboolean PK3_GetEndOfCentralDir (const char *packfile, FILE *packhandle, pk3_endOfCentralDir_t *eocd)
386 long filesize, maxsize;
390 // Get the package size
391 #ifdef FS_USESYSCALLS
392 filesize = lseek (packhandle, 0, SEEK_END);
394 fseek (packhandle, 0, SEEK_END);
395 filesize = ftell(packhandle);
397 if (filesize < ZIP_END_CDIR_SIZE)
400 // Load the end of the file in memory
401 if (filesize < ZIP_MAX_COMMENTS_SIZE + ZIP_END_CDIR_SIZE)
404 maxsize = ZIP_MAX_COMMENTS_SIZE + ZIP_END_CDIR_SIZE;
405 buffer = Mem_Alloc (tempmempool, maxsize);
406 #ifdef FS_USESYSCALLS
407 lseek (packhandle, filesize - maxsize, SEEK_SET);
408 if (read (packhandle, buffer, maxsize) != (unsigned long) maxsize)
410 fseek (packhandle, filesize - maxsize, SEEK_SET);
411 if (fread (buffer, 1, maxsize, packhandle) != (unsigned long) maxsize)
418 // Look for the end of central dir signature around the end of the file
419 maxsize -= ZIP_END_CDIR_SIZE;
420 ptr = &buffer[maxsize];
422 while (BuffBigLong (ptr) != ZIP_END_HEADER)
434 memcpy (eocd, ptr, ZIP_END_CDIR_SIZE);
435 eocd->signature = LittleLong (eocd->signature);
436 eocd->disknum = LittleShort (eocd->disknum);
437 eocd->cdir_disknum = LittleShort (eocd->cdir_disknum);
438 eocd->localentries = LittleShort (eocd->localentries);
439 eocd->nbentries = LittleShort (eocd->nbentries);
440 eocd->cdir_size = LittleLong (eocd->cdir_size);
441 eocd->cdir_offset = LittleLong (eocd->cdir_offset);
442 eocd->comment_size = LittleShort (eocd->comment_size);
454 Extract the file list from a PK3 file
457 int PK3_BuildFileList (pack_t *pack, const pk3_endOfCentralDir_t *eocd)
459 qbyte *central_dir, *ptr;
463 // Load the central directory in memory
464 central_dir = Mem_Alloc (tempmempool, eocd->cdir_size);
465 #ifdef FS_USESYSCALLS
466 lseek (pack->handle, eocd->cdir_offset, SEEK_SET);
467 read (pack->handle, central_dir, eocd->cdir_size);
469 fseek (pack->handle, eocd->cdir_offset, SEEK_SET);
470 fread (central_dir, 1, eocd->cdir_size, pack->handle);
473 // Extract the files properties
474 // The parsing is done "by hand" because some fields have variable sizes and
475 // the constant part isn't 4-bytes aligned, which makes the use of structs difficult
476 remaining = eocd->cdir_size;
479 for (ind = 0; ind < eocd->nbentries; ind++)
481 size_t namesize, count;
483 // Checking the remaining size
484 if (remaining < ZIP_CDIR_CHUNK_BASE_SIZE)
486 Mem_Free (central_dir);
489 remaining -= ZIP_CDIR_CHUNK_BASE_SIZE;
492 if (BuffBigLong (ptr) != ZIP_CDIR_HEADER)
494 Mem_Free (central_dir);
498 namesize = BuffLittleShort (&ptr[28]); // filename length
500 // Check encryption, compression, and attributes
501 // 1st uint8 : general purpose bit flag
502 // Check bits 0 (encryption), 3 (data descriptor after the file), and 5 (compressed patched data (?))
503 // 2nd uint8 : external file attributes
504 // Check bits 3 (file is a directory) and 5 (file is a volume (?))
505 if ((ptr[8] & 0x29) == 0 && (ptr[38] & 0x18) == 0)
507 // Still enough bytes for the name?
508 if ((size_t) remaining < namesize || namesize >= sizeof (*pack->files))
510 Mem_Free (central_dir);
514 // WinZip doesn't use the "directory" attribute, so we need to check the name directly
515 if (ptr[ZIP_CDIR_CHUNK_BASE_SIZE + namesize - 1] != '/')
517 char filename [sizeof (pack->files[0].name)];
518 size_t offset, packsize, realsize;
521 // Extract the name (strip it if necessary)
522 if (namesize >= sizeof (filename))
523 namesize = sizeof (filename) - 1;
524 memcpy (filename, &ptr[ZIP_CDIR_CHUNK_BASE_SIZE], namesize);
525 filename[namesize] = '\0';
527 if (BuffLittleShort (&ptr[10]))
528 flags = FILE_FLAG_DEFLATED;
531 offset = BuffLittleLong (&ptr[42]);
532 packsize = BuffLittleLong (&ptr[20]);
533 realsize = BuffLittleLong (&ptr[24]);
534 FS_AddFileToPack (filename, pack, offset, packsize, realsize, flags);
538 // Skip the name, additionnal field, and comment
539 // 1er uint16 : extra field length
540 // 2eme uint16 : file comment length
541 count = namesize + BuffLittleShort (&ptr[30]) + BuffLittleShort (&ptr[32]);
542 ptr += ZIP_CDIR_CHUNK_BASE_SIZE + count;
546 Mem_Free (central_dir);
547 return pack->numfiles;
555 Create a package entry associated with a PK3 file
558 pack_t *FS_LoadPackPK3 (const char *packfile)
560 #ifdef FS_USESYSCALLS
565 pk3_endOfCentralDir_t eocd;
569 #ifdef FS_USESYSCALLS
570 packhandle = open (packfile, O_RDONLY | O_BINARY);
574 packhandle = fopen (packfile, "rb");
579 if (! PK3_GetEndOfCentralDir (packfile, packhandle, &eocd))
580 Sys_Error ("%s is not a PK3 file", packfile);
582 // Multi-volume ZIP archives are NOT allowed
583 if (eocd.disknum != 0 || eocd.cdir_disknum != 0)
584 Sys_Error ("%s is a multi-volume ZIP archive", packfile);
586 // We only need to do this test if MAX_FILES_IN_PACK is lesser than 65535
587 // since eocd.nbentries is an unsigned 16 bits integer
588 #if MAX_FILES_IN_PACK < 65535
589 if (eocd.nbentries > MAX_FILES_IN_PACK)
590 Sys_Error ("%s contains too many files (%hu)", packfile, eocd.nbentries);
593 // Create a package structure in memory
594 pack = Mem_Alloc (pak_mempool, sizeof (pack_t));
595 pack->ignorecase = true; // PK3 ignores case
596 strlcpy (pack->filename, packfile, sizeof (pack->filename));
597 pack->handle = packhandle;
598 pack->numfiles = eocd.nbentries;
599 pack->mempool = Mem_AllocPool (packfile, 0, NULL);
600 pack->files = Mem_Alloc (pack->mempool, eocd.nbentries * sizeof(packfile_t));
601 pack->next = packlist;
604 real_nb_files = PK3_BuildFileList (pack, &eocd);
605 if (real_nb_files <= 0)
606 Sys_Error ("%s is not a valid PK3 file", packfile);
608 Con_Printf("Added packfile %s (%i files)\n", packfile, real_nb_files);
615 PK3_GetTrueFileOffset
617 Find where the true file data offset is
620 void PK3_GetTrueFileOffset (packfile_t *file, pack_t *pack)
622 qbyte buffer [ZIP_LOCAL_CHUNK_BASE_SIZE];
626 if (file->flags & FILE_FLAG_TRUEOFFS)
629 // Load the local file description
630 #ifdef FS_USESYSCALLS
631 lseek (pack->handle, file->offset, SEEK_SET);
632 count = read (pack->handle, buffer, ZIP_LOCAL_CHUNK_BASE_SIZE);
634 fseek (pack->handle, file->offset, SEEK_SET);
635 count = fread (buffer, 1, ZIP_LOCAL_CHUNK_BASE_SIZE, pack->handle);
637 if (count != ZIP_LOCAL_CHUNK_BASE_SIZE || BuffBigLong (buffer) != ZIP_DATA_HEADER)
638 Sys_Error ("Can't retrieve file %s in package %s", file->name, pack->filename);
640 // Skip name and extra field
641 file->offset += BuffLittleShort (&buffer[26]) + BuffLittleShort (&buffer[28]) + ZIP_LOCAL_CHUNK_BASE_SIZE;
643 file->flags |= FILE_FLAG_TRUEOFFS;
648 =============================================================================
650 OTHER PRIVATE FUNCTIONS
652 =============================================================================
660 Add a file to the list of files contained into a package
663 static packfile_t* FS_AddFileToPack (const char* name, pack_t* pack,
664 size_t offset, size_t packsize,
665 size_t realsize, file_flags_t flags)
667 int (*strcmp_funct) (const char* str1, const char* str2);
668 size_t left, right, middle;
672 strcmp_funct = pack->ignorecase ? strcasecmp : strcmp;
674 // Look for the slot we should put that file into (binary search)
676 right = pack->numfiles;
677 while (left != right)
679 middle = (left + right - 1) / 2;
680 diff = strcmp_funct (pack->files[middle].name, name);
682 // If we found the file, there's a problem
684 Sys_Error ("Package %s contains several time the file %s\n",
685 pack->filename, name);
687 // If we're too far in the list
694 // We have to move the right of the list by one slot to free the one we need
695 file = &pack->files[left];
696 memmove (file + 1, file, (pack->numfiles - left) * sizeof (*file));
699 strlcpy (file->name, name, sizeof (file->name));
700 file->offset = offset;
701 file->packsize = packsize;
702 file->realsize = realsize;
713 Only used for FS_Open.
716 void FS_CreatePath (char *path)
720 for (ofs = path+1 ; *ofs ; ofs++)
722 if (*ofs == '/' || *ofs == '\\')
724 // create the directory
740 void FS_Path_f (void)
744 Con_Print("Current search path:\n");
745 for (s=fs_searchpaths ; s ; s=s->next)
749 Con_Printf("%s (%i files)\n", s->pack->filename, s->pack->numfiles);
752 Con_Printf("%s\n", s->filename);
761 Takes an explicit (not game tree related) path to a pak file.
763 Loads the header and directory, adding the files at the beginning
764 of the list so they override previous pack files.
767 pack_t *FS_LoadPackPAK (const char *packfile)
769 dpackheader_t header;
771 #ifdef FS_USESYSCALLS
777 dpackfile_t *info; // temporary alloc, allowing huge pack directories
779 #ifdef FS_USESYSCALLS
780 packhandle = open (packfile, O_RDONLY | O_BINARY);
783 read (packhandle, (void *)&header, sizeof(header));
785 packhandle = fopen (packfile, "rb");
788 fread ((void *)&header, 1, sizeof(header), packhandle);
790 if (memcmp(header.id, "PACK", 4))
791 Sys_Error ("%s is not a packfile", packfile);
792 header.dirofs = LittleLong (header.dirofs);
793 header.dirlen = LittleLong (header.dirlen);
795 if (header.dirlen % sizeof(dpackfile_t))
796 Sys_Error ("%s has an invalid directory size", packfile);
798 numpackfiles = header.dirlen / sizeof(dpackfile_t);
800 if (numpackfiles > MAX_FILES_IN_PACK)
801 Sys_Error ("%s has %i files", packfile, numpackfiles);
803 pack = Mem_Alloc(pak_mempool, sizeof (pack_t));
804 pack->ignorecase = false; // PAK is case sensitive
805 strlcpy (pack->filename, packfile, sizeof (pack->filename));
806 pack->handle = packhandle;
808 pack->mempool = Mem_AllocPool(packfile, 0, NULL);
809 pack->files = Mem_Alloc(pack->mempool, numpackfiles * sizeof(packfile_t));
810 pack->next = packlist;
813 info = Mem_Alloc(tempmempool, sizeof(*info) * numpackfiles);
814 #ifdef FS_USESYSCALLS
815 lseek (packhandle, header.dirofs, SEEK_SET);
816 read (packhandle, (void *)info, header.dirlen);
818 fseek (packhandle, header.dirofs, SEEK_SET);
819 fread ((void *)info, 1, header.dirlen, packhandle);
822 // parse the directory
823 for (i = 0;i < numpackfiles;i++)
825 size_t offset = LittleLong (info[i].filepos);
826 size_t size = LittleLong (info[i].filelen);
828 FS_AddFileToPack (info[i].name, pack, offset, size, size, FILE_FLAG_TRUEOFFS);
833 Con_Printf("Added packfile %s (%i files)\n", packfile, numpackfiles);
842 Sets fs_gamedir, adds the directory to the head of the path,
843 then loads and adds pak1.pak pak2.pak ...
846 void FS_AddGameDirectory (char *dir)
848 stringlist_t *list, *current;
849 searchpath_t *search;
851 char pakfile[MAX_OSPATH];
853 strlcpy (fs_gamedir, dir, sizeof (fs_gamedir));
855 list = listdirectory(dir);
857 // add any PAK package in the directory
858 for (current = list;current;current = current->next)
860 if (matchpattern(current->text, "*.pak", true))
862 snprintf (pakfile, sizeof (pakfile), "%s/%s", dir, current->text);
863 pak = FS_LoadPackPAK (pakfile);
866 search = Mem_Alloc(pak_mempool, sizeof(searchpath_t));
868 search->next = fs_searchpaths;
869 fs_searchpaths = search;
872 Con_Printf("unable to load pak \"%s\"\n", pakfile);
876 // add any PK3 package in the director
877 for (current = list;current;current = current->next)
879 if (matchpattern(current->text, "*.pk3", true))
881 snprintf (pakfile, sizeof (pakfile), "%s/%s", dir, current->text);
882 pak = FS_LoadPackPK3 (pakfile);
885 search = Mem_Alloc(pak_mempool, sizeof(searchpath_t));
887 search->next = fs_searchpaths;
888 fs_searchpaths = search;
891 Con_Printf("unable to load pak \"%s\"\n", pakfile);
896 // Add the directory to the search path
897 // (unpacked files have the priority over packed files)
898 search = Mem_Alloc(pak_mempool, sizeof(searchpath_t));
899 strlcpy (search->filename, dir, sizeof (search->filename));
900 search->next = fs_searchpaths;
901 fs_searchpaths = search;
910 char *FS_FileExtension (const char *in)
912 static char exten[8];
913 const char *slash, *backslash, *colon, *dot, *separator;
916 slash = strrchr(in, '/');
917 backslash = strrchr(in, '\\');
918 colon = strrchr(in, ':');
919 dot = strrchr(in, '.');
921 if (separator < backslash)
922 separator = backslash;
923 if (separator < colon)
925 if (dot == NULL || dot < separator)
928 for (i = 0;i < 7 && dot[i];i++)
943 searchpath_t *search;
945 fs_mempool = Mem_AllocPool("file management", 0, NULL);
946 pak_mempool = Mem_AllocPool("paks", 0, NULL);
948 Cvar_RegisterVariable (&scr_screenshot_name);
950 Cmd_AddCommand ("path", FS_Path_f);
951 Cmd_AddCommand ("dir", FS_Dir_f);
952 Cmd_AddCommand ("ls", FS_Ls_f);
954 strcpy(fs_basedir, ".");
955 strcpy(fs_gamedir, ".");
960 // Overrides the system supplied base directory (under GAMENAME)
961 // 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)
962 i = COM_CheckParm ("-basedir");
963 if (i && i < com_argc-1)
965 strlcpy (fs_basedir, com_argv[i+1], sizeof (fs_basedir));
966 i = strlen (fs_basedir);
967 if (i > 0 && (fs_basedir[i-1] == '\\' || fs_basedir[i-1] == '/'))
971 // -path <dir or packfile> [<dir or packfile>] ...
972 // Fully specifies the exact search path, overriding the generated one
973 // 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)
974 i = COM_CheckParm ("-path");
978 while (++i < com_argc)
980 if (!com_argv[i] || com_argv[i][0] == '+' || com_argv[i][0] == '-')
983 search = Mem_Alloc(pak_mempool, sizeof(searchpath_t));
984 if (!strcasecmp (FS_FileExtension(com_argv[i]), "pak"))
986 search->pack = FS_LoadPackPAK (com_argv[i]);
988 Sys_Error ("Couldn't load packfile: %s", com_argv[i]);
990 else if (!strcasecmp (FS_FileExtension (com_argv[i]), "pk3"))
992 search->pack = FS_LoadPackPK3 (com_argv[i]);
994 Sys_Error ("Couldn't load packfile: %s", com_argv[i]);
997 strlcpy (search->filename, com_argv[i], sizeof (search->filename));
998 search->next = fs_searchpaths;
999 fs_searchpaths = search;
1004 // start up with GAMENAME by default (id1)
1005 strlcpy (com_modname, GAMENAME, sizeof (com_modname));
1006 FS_AddGameDirectory (va("%s/"GAMENAME, fs_basedir));
1007 Cvar_SetQuick (&scr_screenshot_name, gamescreenshotname);
1009 // add the game-specific path, if any
1013 strlcpy (com_modname, gamedirname, sizeof (com_modname));
1014 FS_AddGameDirectory (va("%s/%s", fs_basedir, gamedirname));
1018 // Adds basedir/gamedir as an override game
1019 // LordHavoc: now supports multiple -game directories
1020 for (i = 1;i < com_argc;i++)
1024 if (!strcmp (com_argv[i], "-game") && i < com_argc-1)
1028 strlcpy (com_modname, com_argv[i], sizeof (com_modname));
1029 FS_AddGameDirectory (va("%s/%s", fs_basedir, com_argv[i]));
1030 Cvar_SetQuick (&scr_screenshot_name, com_modname);
1037 ====================
1040 Internal function used to create a qfile_t and open the relevant file on disk
1041 ====================
1043 static qfile_t* FS_SysOpen (const char* filepath, const char* mode)
1047 file = Mem_Alloc (fs_mempool, sizeof (*file));
1048 memset (file, 0, sizeof (*file));
1050 #ifdef FS_USESYSCALLS
1051 if (strchr(mode, 'r'))
1052 file->stream = open (filepath, O_RDONLY | O_BINARY);
1053 else if (strchr(mode, 'w'))
1054 file->stream = open (filepath, O_WRONLY | O_BINARY | O_CREAT | O_TRUNC, 0666);
1055 else if (strchr(mode, 'a'))
1056 file->stream = open (filepath, O_RDWR | O_BINARY | O_CREAT | O_APPEND, 0666);
1059 if (file->stream < 0)
1065 file->stream = fopen (filepath, mode);
1082 qfile_t *FS_OpenRead (const char *path, int offs, int len)
1086 file = FS_SysOpen (path, "rb");
1089 Sys_Error ("Couldn't open %s", path);
1094 if (offs < 0 || len < 0)
1096 // We set fs_filesize here for normal files
1097 #ifdef FS_USESYSCALLS
1098 fs_filesize = lseek (file->stream, 0, SEEK_END);
1099 lseek (file->stream, 0, SEEK_SET);
1101 fseek (file->stream, 0, SEEK_END);
1102 fs_filesize = ftell (file->stream);
1103 fseek (file->stream, 0, SEEK_SET);
1109 #ifdef FS_USESYSCALLS
1110 lseek (file->stream, offs, SEEK_SET);
1112 fseek (file->stream, offs, SEEK_SET);
1115 file->flags |= FS_FLAG_PACKED;
1117 file->offset = offs;
1126 ====================
1129 Look for a file in the packages and in the filesystem
1131 Return the searchpath where the file was found (or NULL)
1132 and the file index in the package if relevant
1133 ====================
1135 static searchpath_t *FS_FindFile (const char *name, int* index, qboolean quiet)
1137 searchpath_t *search;
1139 int (*strcmp_funct) (const char* str1, const char* str2);
1141 // search through the path, one element at a time
1142 for (search = fs_searchpaths;search;search = search->next)
1144 // is the element a pak file?
1147 size_t left, right, middle;
1150 strcmp_funct = pak->ignorecase ? strcasecmp : strcmp;
1152 // Look for the file (binary search)
1154 right = pak->numfiles;
1155 while (left != right)
1159 middle = (left + right - 1) / 2;
1160 diff = strcmp_funct (pak->files[middle].name, name);
1166 Sys_Printf("FS_FindFile: %s in %s\n",
1167 pak->files[middle].name, pak->filename);
1174 // If we're too far in the list
1183 char netpath[MAX_OSPATH];
1184 snprintf(netpath, sizeof(netpath), "%s/%s", search->filename, name);
1185 if (FS_SysFileExists (netpath))
1188 Sys_Printf("FS_FindFile: %s\n", netpath);
1198 Sys_Printf("FS_FindFile: can't find %s\n", name);
1210 If the requested file is inside a packfile, a new qfile_t* will be opened
1216 qfile_t *FS_FOpenFile (const char *filename, qboolean quiet)
1218 searchpath_t *search;
1219 packfile_t *packfile;
1223 search = FS_FindFile (filename, &i, quiet);
1232 // Found in the filesystem?
1235 char netpath[MAX_OSPATH];
1236 snprintf(netpath, sizeof(netpath), "%s/%s", search->filename, filename);
1237 return FS_OpenRead(netpath, -1, -1);
1240 // So, we found it in a package...
1241 packfile = &search->pack->files[i];
1243 // If we don't have the true offset, get it now
1244 if (! (packfile->flags & FILE_FLAG_TRUEOFFS))
1245 PK3_GetTrueFileOffset (packfile, search->pack);
1247 // No Zlib DLL = no compressed files
1248 if (!zlib_dll && (packfile->flags & FILE_FLAG_DEFLATED))
1250 Con_Printf("WARNING: can't open the compressed file %s\n"
1251 "You need the Zlib DLL to use compressed files\n",
1257 // open a new file in the pakfile
1258 file = FS_OpenRead (search->pack->filename, packfile->offset, packfile->packsize);
1259 fs_filesize = packfile->realsize;
1261 if (packfile->flags & FILE_FLAG_DEFLATED)
1265 file->flags |= FS_FLAG_DEFLATED;
1267 // We need some more variables
1268 ztk = Mem_Alloc (fs_mempool, sizeof (*file->z));
1270 ztk->real_length = packfile->realsize;
1272 // Initialize zlib stream
1273 ztk->zstream.next_in = ztk->input;
1274 ztk->zstream.avail_in = 0;
1276 /* From Zlib's "unzip.c":
1278 * windowBits is passed < 0 to tell that there is no zlib header.
1279 * Note that in this case inflate *requires* an extra "dummy" byte
1280 * after the compressed stream in order to complete decompression and
1281 * return Z_STREAM_END.
1282 * In unzip, i don't wait absolutely Z_STREAM_END because I known the
1283 * size of both compressed and uncompressed data
1285 if (qz_inflateInit2 (&ztk->zstream, -MAX_WBITS) != Z_OK)
1286 Sys_Error ("inflate init error (file: %s)", filename);
1288 ztk->zstream.next_out = ztk->output;
1289 ztk->zstream.avail_out = sizeof (ztk->output);
1299 =============================================================================
1301 MAIN PUBLIC FUNCTIONS
1303 =============================================================================
1307 ====================
1310 Open a file. The syntax is the same as fopen
1311 ====================
1313 qfile_t* FS_Open (const char* filepath, const char* mode, qboolean quiet)
1315 // If the file is opened in "write" or "append" mode
1316 if (strchr (mode, 'w') || strchr (mode, 'a'))
1318 char real_path [MAX_OSPATH];
1320 // Open the file on disk directly
1321 snprintf (real_path, sizeof (real_path), "%s/%s", fs_gamedir, filepath);
1323 // Create directories up to the file
1324 FS_CreatePath (real_path);
1326 return FS_SysOpen (real_path, mode);
1329 // Else, we look at the various search paths
1330 return FS_FOpenFile (filepath, quiet);
1335 ====================
1339 ====================
1341 int FS_Close (qfile_t* file)
1343 #ifdef FS_USESYSCALLS
1344 if (close (file->stream))
1346 if (fclose (file->stream))
1352 qz_inflateEnd (&file->z->zstream);
1362 ====================
1365 Write "datasize" bytes into a file
1366 ====================
1368 size_t FS_Write (qfile_t* file, const void* data, size_t datasize)
1370 #ifdef FS_USESYSCALLS
1371 return write (file->stream, data, datasize);
1373 return fwrite (data, 1, datasize, file->stream);
1379 ====================
1382 Read up to "buffersize" bytes from a file
1383 ====================
1385 size_t FS_Read (qfile_t* file, void* buffer, size_t buffersize)
1390 // Quick path for unpacked files
1391 if (! (file->flags & FS_FLAG_PACKED))
1392 #ifdef FS_USESYSCALLS
1393 return read (file->stream, buffer, buffersize);
1395 return fread (buffer, 1, buffersize, file->stream);
1398 // If the file isn't compressed
1399 if (! (file->flags & FS_FLAG_DEFLATED))
1401 // We must take care to not read after the end of the file
1402 count = file->length - file->position;
1403 if (buffersize > count)
1406 #ifdef FS_USESYSCALLS
1407 nb = read (file->stream, buffer, buffersize);
1409 nb = fread (buffer, 1, buffersize, file->stream);
1412 file->position += nb;
1416 // If the file is compressed, it's more complicated...
1419 // First, we copy as many bytes as we can from "output"
1420 if (ztk->out_ind < ztk->out_max)
1422 count = ztk->out_max - ztk->out_ind;
1424 nb = (buffersize > count) ? count : buffersize;
1425 memcpy (buffer, &ztk->output[ztk->out_ind], nb);
1427 file->position += nb;
1432 // We cycle through a few operations until we have inflated enough data
1433 while (nb < buffersize)
1435 // NOTE: at this point, "output" should always be empty
1437 // If "input" is also empty, we need to fill it
1438 if (ztk->in_ind == ztk->in_max)
1442 // If we are at the end of the file
1443 if (ztk->out_position == ztk->real_length)
1446 remain = file->length - ztk->in_position;
1447 count = (remain > sizeof (ztk->input)) ? sizeof (ztk->input) : remain;
1448 #ifdef FS_USESYSCALLS
1449 read (file->stream, ztk->input, count);
1451 fread (ztk->input, 1, count, file->stream);
1454 // Update indexes and counters
1456 ztk->in_max = count;
1457 ztk->in_position += count;
1460 // Now that we are sure we have compressed data available, we need to determine
1461 // if it's better to inflate it in "output" or directly in "buffer" (we are in this
1462 // case if we still need more bytes than "output" can contain)
1464 ztk->zstream.next_in = &ztk->input[ztk->in_ind];
1465 ztk->zstream.avail_in = ztk->in_max - ztk->in_ind;
1467 // If output will be able to contain at least 1 more byte than the data we need
1468 if (buffersize - nb < sizeof (ztk->output))
1472 // Inflate the data in "output"
1473 ztk->zstream.next_out = ztk->output;
1474 ztk->zstream.avail_out = sizeof (ztk->output);
1475 error = qz_inflate (&ztk->zstream, Z_SYNC_FLUSH);
1476 if (error != Z_OK && error != Z_STREAM_END)
1477 Sys_Error ("Can't inflate file");
1478 ztk->in_ind = ztk->in_max - ztk->zstream.avail_in;
1479 ztk->out_max = sizeof (ztk->output) - ztk->zstream.avail_out;
1480 ztk->out_position += ztk->out_max;
1482 // Copy the requested data in "buffer" (as much as we can)
1483 count = (buffersize - nb > ztk->out_max) ? ztk->out_max : buffersize - nb;
1484 memcpy (&((qbyte*)buffer)[nb], ztk->output, count);
1485 ztk->out_ind = count;
1488 // Else, we inflate directly in "buffer"
1493 // Inflate the data in "buffer"
1494 ztk->zstream.next_out = &((qbyte*)buffer)[nb];
1495 ztk->zstream.avail_out = buffersize - nb;
1496 error = qz_inflate (&ztk->zstream, Z_SYNC_FLUSH);
1497 if (error != Z_OK && error != Z_STREAM_END)
1498 Sys_Error ("Can't inflate file");
1499 ztk->in_ind = ztk->in_max - ztk->zstream.avail_in;
1501 // Invalidate the output data (for FS_Seek)
1505 // How much data did it inflate?
1506 count = buffersize - nb - ztk->zstream.avail_out;
1507 ztk->out_position += count;
1511 file->position += count;
1519 ====================
1522 Flush the file output stream
1523 ====================
1525 int FS_Flush (qfile_t* file)
1527 #ifdef FS_USESYSCALLS
1530 return fflush (file->stream);
1536 ====================
1539 Print a string into a file
1540 ====================
1542 int FS_Print(qfile_t* file, const char *msg)
1544 return FS_Write(file, msg, strlen(msg));
1548 ====================
1551 Print a string into a file
1552 ====================
1554 int FS_Printf(qfile_t* file, const char* format, ...)
1559 va_start (args, format);
1560 result = FS_VPrintf(file, format, args);
1568 ====================
1571 Print a string into a file
1572 ====================
1574 int FS_VPrintf(qfile_t* file, const char* format, va_list ap)
1576 #ifdef FS_USESYSCALLS
1579 char tempstring[1024];
1580 len = vsnprintf (tempstring, sizeof(tempstring), format, ap);
1581 if (len >= sizeof(tempstring))
1584 char *temp = Mem_Alloc(tempmempool, len + 1);
1585 len = vsnprintf (temp, len + 1, format, ap);
1586 result = write (file->stream, temp, len);
1591 return write (file->stream, tempstring, len);
1594 return vfprintf (file->stream, format, ap);
1600 ====================
1603 Get the next character of a file
1604 ====================
1606 int FS_Getc (qfile_t* file)
1610 if (FS_Read (file, &c, 1) != 1)
1618 ====================
1621 Move the position index in a file
1622 ====================
1624 int FS_Seek (qfile_t* file, long offset, int whence)
1626 // Quick path for unpacked files
1627 if (! (file->flags & FS_FLAG_PACKED))
1628 #ifdef FS_USESYSCALLS
1629 return lseek (file->stream, offset, whence);
1631 return fseek (file->stream, offset, whence);
1634 // Seeking in compressed files is more a hack than anything else,
1635 // but we need to support it, so here it is.
1636 if (file->flags & FS_FLAG_DEFLATED)
1638 ztoolkit_t *ztk = file->z;
1639 qbyte buffer [sizeof (ztk->output)]; // it's big to force inflating into buffer directly
1644 offset += file->position;
1651 offset += ztk->real_length;
1657 if (offset < 0 || offset > (long) ztk->real_length)
1660 // If we need to go back in the file
1661 if (offset <= (long) file->position)
1663 // If we still have the data we need in the output buffer
1664 if (file->position - offset <= ztk->out_ind)
1666 ztk->out_ind -= file->position - offset;
1667 file->position = offset;
1671 // Else, we restart from the beginning of the file
1674 ztk->in_position = 0;
1677 ztk->out_position = 0;
1679 #ifdef FS_USESYSCALLS
1680 lseek (file->stream, file->offset, SEEK_SET);
1682 fseek (file->stream, file->offset, SEEK_SET);
1685 // Reset the Zlib stream
1686 ztk->zstream.next_in = ztk->input;
1687 ztk->zstream.avail_in = 0;
1688 qz_inflateReset (&ztk->zstream);
1691 // Skip all data until we reach the requested offset
1692 while ((long) file->position < offset)
1694 size_t diff = offset - file->position;
1697 count = (diff > sizeof (buffer)) ? sizeof (buffer) : diff;
1698 len = FS_Read (file, buffer, count);
1706 // Packed files receive a special treatment too, because
1707 // we need to make sure it doesn't go outside of the file
1711 offset += file->position;
1718 offset += file->length;
1724 if (offset < 0 || offset > (long) file->length)
1727 #ifdef FS_USESYSCALLS
1728 if (lseek (file->stream, file->offset + offset, SEEK_SET) == -1)
1731 if (fseek (file->stream, file->offset + offset, SEEK_SET) == -1)
1734 file->position = offset;
1740 ====================
1743 Give the current position in a file
1744 ====================
1746 long FS_Tell (qfile_t* file)
1748 if (file->flags & FS_FLAG_PACKED)
1749 return file->position;
1751 #ifdef FS_USESYSCALLS
1752 return lseek (file->stream, 0, SEEK_CUR);
1754 return ftell (file->stream);
1760 ====================
1763 Extract a line from a file
1764 ====================
1766 char* FS_Gets (qfile_t* file, char* buffer, int buffersize)
1770 // Quick path for unpacked files
1771 #ifndef FS_USESYSCALLS
1772 if (! (file->flags & FS_FLAG_PACKED))
1773 return fgets (buffer, buffersize, file->stream);
1776 for (ind = 0; ind < (size_t) buffersize - 1; ind++)
1778 int c = FS_Getc (file);
1793 buffer[ind + 1] = '\0';
1802 buffer[buffersize - 1] = '\0';
1811 Dynamic length version of fgets. DO NOT free the buffer.
1814 char *FS_Getline (qfile_t *file)
1816 static int size = 256;
1817 static char *buf = 0;
1822 buf = Mem_Alloc (fs_mempool, size);
1824 if (!FS_Gets (file, buf, size))
1828 while (buf[len - 1] != '\n' && buf[len - 1] != '\r')
1830 t = Mem_Alloc (fs_mempool, size + 256);
1831 memcpy(t, buf, size);
1835 if (!FS_Gets (file, buf + len, size - len))
1839 while ((len = strlen(buf)) && (buf[len - 1] == '\n' || buf[len - 1] == '\r'))
1846 ====================
1849 Extract a line from a file
1850 ====================
1852 // FIXME: remove this function?
1853 int FS_Eof (qfile_t* file)
1855 if (file->flags & FS_FLAG_PACKED)
1857 if (file->flags & FS_FLAG_DEFLATED)
1858 return (file->position == file->z->real_length);
1860 return (file->position == file->length);
1863 #ifdef FS_USESYSCALLS
1864 Sys_Error("FS_Eof: not implemented using syscalls\n");
1867 return feof (file->stream);
1876 Filename are relative to the quake directory.
1877 Always appends a 0 byte.
1880 qbyte *FS_LoadFile (const char *path, mempool_t *pool, qboolean quiet)
1885 // look for it in the filesystem or pack files
1886 h = FS_Open (path, "rb", quiet);
1890 buf = Mem_Alloc(pool, fs_filesize+1);
1892 Sys_Error ("FS_LoadFile: not enough available memory for %s (size %i)", path, fs_filesize);
1894 ((qbyte *)buf)[fs_filesize] = 0;
1896 FS_Read (h, buf, fs_filesize);
1907 The filename will be prefixed by the current game directory
1910 qboolean FS_WriteFile (const char *filename, void *data, int len)
1914 handle = FS_Open (filename, "wb", false);
1917 Con_Printf("FS_WriteFile: failed on %s\n", filename);
1921 Con_DPrintf("FS_WriteFile: %s\n", filename);
1922 FS_Write (handle, data, len);
1929 =============================================================================
1931 OTHERS PUBLIC FUNCTIONS
1933 =============================================================================
1941 void FS_StripExtension (const char *in, char *out, size_t size_out)
1948 while (*in && size_out > 1)
1952 else if (*in == '/' || *in == '\\' || *in == ':')
1969 void FS_DefaultExtension (char *path, const char *extension, size_t size_path)
1973 // if path doesn't have a .EXT, append extension
1974 // (extension should include the .)
1975 src = path + strlen(path) - 1;
1977 while (*src != '/' && src != path)
1980 return; // it has an extension
1984 strlcat (path, extension, size_path);
1992 Look for a file in the packages and in the filesystem
1995 qboolean FS_FileExists (const char *filename)
1997 return (FS_FindFile (filename, NULL, true) != NULL);
2005 Look for a file in the filesystem only
2008 qboolean FS_SysFileExists (const char *path)
2013 f = fopen (path, "rb");
2024 if (stat (path,&buf) == -1)
2031 void FS_mkdir (const char *path)
2044 Allocate and fill a search structure with information on matching filenames.
2047 fssearch_t *FS_Search(const char *pattern, int caseinsensitive, int quiet)
2050 searchpath_t *searchpath;
2052 int i, basepathlength, numfiles, numchars;
2053 stringlist_t *dir, *dirfile, *liststart, *listcurrent, *listtemp;
2054 const char *slash, *backslash, *colon, *separator;
2056 char netpath[MAX_OSPATH];
2057 char temp[MAX_OSPATH];
2059 while(!strncmp(pattern, "./", 2))
2061 while(!strncmp(pattern, ".\\", 2))
2068 slash = strrchr(pattern, '/');
2069 backslash = strrchr(pattern, '\\');
2070 colon = strrchr(pattern, ':');
2071 separator = pattern;
2072 if (separator < slash)
2074 if (separator < backslash)
2075 separator = backslash;
2076 if (separator < colon)
2078 basepathlength = separator - pattern;
2079 basepath = Mem_Alloc (tempmempool, basepathlength + 1);
2081 memcpy(basepath, pattern, basepathlength);
2082 basepath[basepathlength] = 0;
2084 // search through the path, one element at a time
2085 for (searchpath = fs_searchpaths;searchpath;searchpath = searchpath->next)
2087 // is the element a pak file?
2088 if (searchpath->pack)
2090 // look through all the pak file elements
2091 pak = searchpath->pack;
2092 for (i = 0;i < pak->numfiles;i++)
2094 strcpy(temp, pak->files[i].name);
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 Sys_Printf("SearchPackFile: %s : %s\n", pak->filename, temp);
2111 // strip off one path element at a time until empty
2112 // this way directories are added to the listing if they match the pattern
2113 slash = strrchr(temp, '/');
2114 backslash = strrchr(temp, '\\');
2115 colon = strrchr(temp, ':');
2117 if (separator < slash)
2119 if (separator < backslash)
2120 separator = backslash;
2121 if (separator < colon)
2123 *((char *)separator) = 0;
2129 // get a directory listing and look at each name
2130 snprintf(netpath, sizeof (netpath), "%s/%s", searchpath->filename, basepath);
2131 if ((dir = listdirectory(netpath)))
2133 for (dirfile = dir;dirfile;dirfile = dirfile->next)
2135 snprintf(temp, sizeof(temp), "%s/%s", basepath, dirfile->text);
2136 if (matchpattern(temp, (char *)pattern, true))
2138 for (listtemp = liststart;listtemp;listtemp = listtemp->next)
2139 if (!strcmp(listtemp->text, temp))
2141 if (listtemp == NULL)
2143 listcurrent = stringlistappend(listcurrent, temp);
2144 if (liststart == NULL)
2145 liststart = listcurrent;
2147 Sys_Printf("SearchDirFile: %s\n", temp);
2158 liststart = stringlistsort(liststart);
2161 for (listtemp = liststart;listtemp;listtemp = listtemp->next)
2164 numchars += strlen(listtemp->text) + 1;
2166 search = Z_Malloc(sizeof(fssearch_t) + numchars + numfiles * sizeof(char *));
2167 search->filenames = (char **)((char *)search + sizeof(fssearch_t));
2168 search->filenamesbuffer = (char *)((char *)search + sizeof(fssearch_t) + numfiles * sizeof(char *));
2169 search->numfilenames = numfiles;
2172 for (listtemp = liststart;listtemp;listtemp = listtemp->next)
2174 search->filenames[numfiles] = search->filenamesbuffer + numchars;
2175 strcpy(search->filenames[numfiles], listtemp->text);
2177 numchars += strlen(listtemp->text) + 1;
2180 stringlistfree(liststart);
2187 void FS_FreeSearch(fssearch_t *search)
2192 extern int con_linewidth;
2193 int FS_ListDirectory(const char *pattern, int oneperline)
2204 search = FS_Search(pattern, true, true);
2207 numfiles = search->numfilenames;
2210 // FIXME: the names could be added to one column list and then
2211 // gradually shifted into the next column if they fit, and then the
2212 // next to make a compact variable width listing but it's a lot more
2214 // find width for columns
2216 for (i = 0;i < numfiles;i++)
2218 l = strlen(search->filenames[i]);
2219 if (columnwidth < l)
2222 // count the spacing character
2224 // calculate number of columns
2225 numcolumns = con_linewidth / columnwidth;
2226 // don't bother with the column printing if it's only one column
2227 if (numcolumns >= 2)
2229 numlines = (numfiles + numcolumns - 1) / numcolumns;
2230 for (i = 0;i < numlines;i++)
2233 for (k = 0;k < numcolumns;k++)
2235 l = i * numcolumns + k;
2238 name = search->filenames[l];
2239 for (j = 0;name[j] && j < (int)sizeof(linebuf) - 1;j++)
2240 linebuf[linebufpos++] = name[j];
2241 // space out name unless it's the last on the line
2242 if (k < (numcolumns - 1) && l < (numfiles - 1))
2243 for (;j < columnwidth && j < (int)sizeof(linebuf) - 1;j++)
2244 linebuf[linebufpos++] = ' ';
2247 linebuf[linebufpos] = 0;
2248 Con_Printf("%s\n", linebuf);
2255 for (i = 0;i < numfiles;i++)
2256 Con_Printf("%s\n", search->filenames[i]);
2257 FS_FreeSearch(search);
2261 static void FS_ListDirectoryCmd (const char* cmdname, int oneperline)
2263 const char *pattern;
2266 Con_Printf("usage:\n%s [path/pattern]\n", cmdname);
2269 if (Cmd_Argc() == 2)
2270 pattern = Cmd_Argv(1);
2273 if (!FS_ListDirectory(pattern, oneperline))
2274 Con_Print("No files found.\n");
2279 FS_ListDirectoryCmd("dir", true);
2284 FS_ListDirectoryCmd("ls", false);