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 fs_offset_t real_length; // uncompressed file size (for files opened in "read" mode)
152 fs_offset_t position; // current position in the file
153 fs_offset_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 fs_offset_t offset, fs_offset_t packsize,
248 fs_offset_t realsize, packfile_flags_t flags);
252 =============================================================================
256 =============================================================================
259 mempool_t *fs_mempool;
261 fs_offset_t fs_filesize;
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) != (fs_offset_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;
436 fs_offset_t remaining;
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 fs_offset_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 (remaining < namesize || namesize >= (int)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 fs_offset_t offset, packsize, realsize;
489 packfile_flags_t flags;
491 // Extract the name (strip it if necessary)
492 namesize = min(namesize, (int)sizeof (filename) - 1);
493 memcpy (filename, &ptr[ZIP_CDIR_CHUNK_BASE_SIZE], namesize);
494 filename[namesize] = '\0';
496 if (BuffLittleShort (&ptr[10]))
497 flags = PACKFILE_FLAG_DEFLATED;
500 offset = BuffLittleLong (&ptr[42]);
501 packsize = BuffLittleLong (&ptr[20]);
502 realsize = BuffLittleLong (&ptr[24]);
503 FS_AddFileToPack (filename, pack, offset, packsize, realsize, flags);
507 // Skip the name, additionnal field, and comment
508 // 1er uint16 : extra field length
509 // 2eme uint16 : file comment length
510 count = namesize + BuffLittleShort (&ptr[30]) + BuffLittleShort (&ptr[32]);
511 ptr += ZIP_CDIR_CHUNK_BASE_SIZE + count;
515 // If the package is empty, central_dir is NULL here
516 if (central_dir != NULL)
517 Mem_Free (central_dir);
518 return pack->numfiles;
526 Create a package entry associated with a PK3 file
529 pack_t *FS_LoadPackPK3 (const char *packfile)
532 pk3_endOfCentralDir_t eocd;
536 packhandle = open (packfile, O_RDONLY | O_BINARY);
540 if (! PK3_GetEndOfCentralDir (packfile, packhandle, &eocd))
542 Con_Printf ("%s is not a PK3 file", packfile);
547 // Multi-volume ZIP archives are NOT allowed
548 if (eocd.disknum != 0 || eocd.cdir_disknum != 0)
550 Con_Printf ("%s is a multi-volume ZIP archive", packfile);
555 // We only need to do this test if MAX_FILES_IN_PACK is lesser than 65535
556 // since eocd.nbentries is an unsigned 16 bits integer
557 #if MAX_FILES_IN_PACK < 65535
558 if (eocd.nbentries > MAX_FILES_IN_PACK)
560 Con_Printf ("%s contains too many files (%hu)", packfile, eocd.nbentries);
566 // Create a package structure in memory
567 pack = Mem_Alloc(fs_mempool, sizeof (pack_t));
568 pack->ignorecase = true; // PK3 ignores case
569 strlcpy (pack->filename, packfile, sizeof (pack->filename));
570 pack->handle = packhandle;
571 pack->numfiles = eocd.nbentries;
572 pack->files = Mem_Alloc(fs_mempool, eocd.nbentries * sizeof(packfile_t));
573 pack->next = packlist;
576 real_nb_files = PK3_BuildFileList (pack, &eocd);
577 if (real_nb_files < 0)
579 Con_Printf ("%s is not a valid PK3 file", packfile);
585 Con_Printf("Added packfile %s (%i files)\n", packfile, real_nb_files);
592 PK3_GetTrueFileOffset
594 Find where the true file data offset is
597 qboolean PK3_GetTrueFileOffset (packfile_t *pfile, pack_t *pack)
599 qbyte buffer [ZIP_LOCAL_CHUNK_BASE_SIZE];
603 if (pfile->flags & PACKFILE_FLAG_TRUEOFFS)
606 // Load the local file description
607 lseek (pack->handle, pfile->offset, SEEK_SET);
608 count = read (pack->handle, buffer, ZIP_LOCAL_CHUNK_BASE_SIZE);
609 if (count != ZIP_LOCAL_CHUNK_BASE_SIZE || BuffBigLong (buffer) != ZIP_DATA_HEADER)
611 Con_Printf ("Can't retrieve file %s in package %s", pfile->name, pack->filename);
615 // Skip name and extra field
616 pfile->offset += BuffLittleShort (&buffer[26]) + BuffLittleShort (&buffer[28]) + ZIP_LOCAL_CHUNK_BASE_SIZE;
618 pfile->flags |= PACKFILE_FLAG_TRUEOFFS;
624 =============================================================================
626 OTHER PRIVATE FUNCTIONS
628 =============================================================================
636 Add a file to the list of files contained into a package
639 static packfile_t* FS_AddFileToPack (const char* name, pack_t* pack,
640 fs_offset_t offset, fs_offset_t packsize,
641 fs_offset_t realsize, packfile_flags_t flags)
643 int (*strcmp_funct) (const char* str1, const char* str2);
644 int left, right, middle;
647 strcmp_funct = pack->ignorecase ? strcasecmp : strcmp;
649 // Look for the slot we should put that file into (binary search)
651 right = pack->numfiles - 1;
652 while (left <= right)
656 middle = (left + right) / 2;
657 diff = strcmp_funct (pack->files[middle].name, name);
659 // If we found the file, there's a problem
661 Con_Printf ("Package %s contains the file %s several times\n", pack->filename, name);
663 // If we're too far in the list
670 // We have to move the right of the list by one slot to free the one we need
671 pfile = &pack->files[left];
672 memmove (pfile + 1, pfile, (pack->numfiles - left) * sizeof (*pfile));
675 strlcpy (pfile->name, name, sizeof (pfile->name));
676 pfile->offset = offset;
677 pfile->packsize = packsize;
678 pfile->realsize = realsize;
679 pfile->flags = flags;
689 Only used for FS_Open.
692 void FS_CreatePath (char *path)
696 for (ofs = path+1 ; *ofs ; ofs++)
698 if (*ofs == '/' || *ofs == '\\')
700 // create the directory
716 void FS_Path_f (void)
720 Con_Print("Current search path:\n");
721 for (s=fs_searchpaths ; s ; s=s->next)
724 Con_Printf("%s (%i files)\n", s->pack->filename, s->pack->numfiles);
726 Con_Printf("%s\n", s->filename);
735 Takes an explicit (not game tree related) path to a pak file.
737 Loads the header and directory, adding the files at the beginning
738 of the list so they override previous pack files.
741 pack_t *FS_LoadPackPAK (const char *packfile)
743 dpackheader_t header;
749 packhandle = open (packfile, O_RDONLY | O_BINARY);
752 read (packhandle, (void *)&header, sizeof(header));
753 if (memcmp(header.id, "PACK", 4))
755 Con_Printf ("%s is not a packfile", packfile);
759 header.dirofs = LittleLong (header.dirofs);
760 header.dirlen = LittleLong (header.dirlen);
762 if (header.dirlen % sizeof(dpackfile_t))
764 Con_Printf ("%s has an invalid directory size", packfile);
769 numpackfiles = header.dirlen / sizeof(dpackfile_t);
771 if (numpackfiles > MAX_FILES_IN_PACK)
773 Con_Printf ("%s has %i files", packfile, numpackfiles);
778 pack = Mem_Alloc(fs_mempool, sizeof (pack_t));
779 pack->ignorecase = false; // PAK is case sensitive
780 strlcpy (pack->filename, packfile, sizeof (pack->filename));
781 pack->handle = packhandle;
783 pack->files = Mem_Alloc(fs_mempool, numpackfiles * sizeof(packfile_t));
784 pack->next = packlist;
787 info = Mem_Alloc(tempmempool, sizeof(*info) * numpackfiles);
788 lseek (packhandle, header.dirofs, SEEK_SET);
789 read (packhandle, (void *)info, header.dirlen);
791 // parse the directory
792 for (i = 0;i < numpackfiles;i++)
794 fs_offset_t offset = LittleLong (info[i].filepos);
795 fs_offset_t size = LittleLong (info[i].filelen);
797 FS_AddFileToPack (info[i].name, pack, offset, size, size, PACKFILE_FLAG_TRUEOFFS);
802 Con_Printf("Added packfile %s (%i files)\n", packfile, numpackfiles);
811 Sets fs_gamedir, adds the directory to the head of the path,
812 then loads and adds pak1.pak pak2.pak ...
815 void FS_AddGameDirectory (const char *dir)
817 stringlist_t *list, *current;
818 searchpath_t *search;
820 char pakfile[MAX_OSPATH];
822 strlcpy (fs_gamedir, dir, sizeof (fs_gamedir));
824 list = listdirectory(dir);
826 // add any PAK package in the directory
827 for (current = list;current;current = current->next)
829 if (matchpattern(current->text, "*.pak", true))
831 dpsnprintf (pakfile, sizeof (pakfile), "%s%s", dir, current->text);
832 pak = FS_LoadPackPAK (pakfile);
835 search = Mem_Alloc(fs_mempool, sizeof(searchpath_t));
837 search->next = fs_searchpaths;
838 fs_searchpaths = search;
841 Con_Printf("unable to load pak \"%s\"\n", pakfile);
845 // add any PK3 package in the director
846 for (current = list;current;current = current->next)
848 if (matchpattern(current->text, "*.pk3", true))
850 dpsnprintf (pakfile, sizeof (pakfile), "%s%s", dir, current->text);
851 pak = FS_LoadPackPK3 (pakfile);
854 search = Mem_Alloc(fs_mempool, sizeof(searchpath_t));
856 search->next = fs_searchpaths;
857 fs_searchpaths = search;
860 Con_Printf("unable to load pak \"%s\"\n", pakfile);
865 // Add the directory to the search path
866 // (unpacked files have the priority over packed files)
867 search = Mem_Alloc(fs_mempool, sizeof(searchpath_t));
868 strlcpy (search->filename, dir, sizeof (search->filename));
869 search->next = fs_searchpaths;
870 fs_searchpaths = search;
879 void FS_AddGameHierarchy (const char *dir)
885 // Add the common game directory
886 FS_AddGameDirectory (va("%s/%s/", fs_basedir, dir));
889 // Add the personal game directory
890 homedir = getenv ("HOME");
891 if (homedir != NULL && homedir[0] != '\0')
892 FS_AddGameDirectory (va("%s/.%s/%s/", homedir, gameuserdirname, dir));
902 static const char *FS_FileExtension (const char *in)
904 const char *separator, *backslash, *colon, *dot;
906 separator = strrchr(in, '/');
907 backslash = strrchr(in, '\\');
908 if (separator < backslash)
909 separator = backslash;
910 colon = strrchr(in, ':');
911 if (separator < colon)
914 dot = strrchr(in, '.');
915 if (dot == NULL || dot < separator)
930 searchpath_t *search;
932 fs_mempool = Mem_AllocPool("file management", 0, NULL);
934 strcpy(fs_basedir, ".");
935 strcpy(fs_gamedir, "");
938 // FIXME: is there a better way to find the directory outside the .app?
939 if (strstr(com_argv[0], ".app/"))
943 split = strstr(com_argv[0], ".app/");
944 while (split > com_argv[0] && *split != '/')
946 strlcpy(fs_basedir, com_argv[0], sizeof(fs_basedir));
947 fs_basedir[split - com_argv[0]] = 0;
954 // Overrides the system supplied base directory (under GAMENAME)
955 // 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)
956 i = COM_CheckParm ("-basedir");
957 if (i && i < com_argc-1)
959 strlcpy (fs_basedir, com_argv[i+1], sizeof (fs_basedir));
960 i = (int)strlen (fs_basedir);
961 if (i > 0 && (fs_basedir[i-1] == '\\' || fs_basedir[i-1] == '/'))
965 // -path <dir or packfile> [<dir or packfile>] ...
966 // Fully specifies the exact search path, overriding the generated one
967 // 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)
968 i = COM_CheckParm ("-path");
972 while (++i < com_argc)
974 if (!com_argv[i] || com_argv[i][0] == '+' || com_argv[i][0] == '-')
977 search = Mem_Alloc(fs_mempool, sizeof(searchpath_t));
978 if (!strcasecmp (FS_FileExtension(com_argv[i]), "pak"))
980 search->pack = FS_LoadPackPAK (com_argv[i]);
983 Con_Printf ("Couldn't load packfile: %s", com_argv[i]);
988 else if (!strcasecmp (FS_FileExtension (com_argv[i]), "pk3"))
990 search->pack = FS_LoadPackPK3 (com_argv[i]);
993 Con_Printf ("Couldn't load packfile: %s", com_argv[i]);
999 strlcpy (search->filename, com_argv[i], sizeof (search->filename));
1000 search->next = fs_searchpaths;
1001 fs_searchpaths = search;
1006 // add the game-specific paths
1007 // gamedirname1 (typically id1)
1008 FS_AddGameHierarchy (gamedirname1);
1010 // add the game-specific path, if any
1014 FS_AddGameHierarchy (gamedirname2);
1017 // set the com_modname (reported in server info)
1018 strlcpy(com_modname, gamedirname1, sizeof(com_modname));
1021 // Adds basedir/gamedir as an override game
1022 // LordHavoc: now supports multiple -game directories
1023 for (i = 1;i < com_argc;i++)
1027 if (!strcmp (com_argv[i], "-game") && i < com_argc-1)
1031 FS_AddGameHierarchy (com_argv[i]);
1032 // update the com_modname
1033 strlcpy (com_modname, com_argv[i], sizeof (com_modname));
1037 // If "-condebug" is in the command line, remove the previous log file
1038 if (COM_CheckParm ("-condebug") != 0)
1039 unlink (va("%s/qconsole.log", fs_gamedir));
1042 void FS_Init_Commands(void)
1044 Cvar_RegisterVariable (&scr_screenshot_name);
1046 Cmd_AddCommand ("path", FS_Path_f);
1047 Cmd_AddCommand ("dir", FS_Dir_f);
1048 Cmd_AddCommand ("ls", FS_Ls_f);
1050 // set the default screenshot name to either the mod name or the
1051 // gamemode screenshot name
1053 Cvar_SetQuick (&scr_screenshot_name, com_modname);
1055 Cvar_SetQuick (&scr_screenshot_name, gamescreenshotname);
1063 void FS_Shutdown (void)
1065 Mem_FreePool (&fs_mempool);
1069 ====================
1072 Internal function used to create a qfile_t and open the relevant non-packed file on disk
1073 ====================
1075 static qfile_t* FS_SysOpen (const char* filepath, const char* mode, qboolean nonblocking)
1081 // Parse the mode string
1090 opt = O_CREAT | O_TRUNC;
1094 opt = O_CREAT | O_APPEND;
1097 Con_Printf ("FS_SysOpen(%s, %s): invalid mode\n", filepath, mode);
1100 for (ind = 1; mode[ind] != '\0'; ind++)
1111 Con_Printf ("FS_SysOpen(%s, %s): unknown character in mode (%c)\n",
1112 filepath, mode, mode[ind]);
1121 file = Mem_Alloc (fs_mempool, sizeof (*file));
1122 memset (file, 0, sizeof (*file));
1125 file->handle = open (filepath, mod | opt, 0666);
1126 if (file->handle < 0)
1132 file->real_length = lseek (file->handle, 0, SEEK_END);
1134 // For files opened in append mode, we start at the end of the file
1136 file->position = file->real_length;
1138 lseek (file->handle, 0, SEEK_SET);
1148 Open a packed file using its package file descriptor
1151 qfile_t *FS_OpenPackedFile (pack_t* pack, int pack_ind)
1157 pfile = &pack->files[pack_ind];
1161 // If we don't have the true offset, get it now
1162 if (! (pfile->flags & PACKFILE_FLAG_TRUEOFFS))
1163 if (!PK3_GetTrueFileOffset (pfile, pack))
1166 // No Zlib DLL = no compressed files
1167 if (!zlib_dll && (pfile->flags & PACKFILE_FLAG_DEFLATED))
1169 Con_Printf("WARNING: can't open the compressed file %s\n"
1170 "You need the Zlib DLL to use compressed files\n",
1175 // LordHavoc: lseek affects all duplicates of a handle so we do it before
1176 // the dup() call to avoid having to close the dup_handle on error here
1177 if (lseek (pack->handle, pfile->offset, SEEK_SET) == -1)
1179 Con_Printf ("FS_OpenPackedFile: can't lseek to %s in %s (offset: %d)",
1180 pfile->name, pack->filename, pfile->offset);
1184 dup_handle = dup (pack->handle);
1187 Con_Printf ("FS_OpenPackedFile: can't dup package's handle (pack: %s)", pack->filename);
1191 file = Mem_Alloc (fs_mempool, sizeof (*file));
1192 memset (file, 0, sizeof (*file));
1193 file->handle = dup_handle;
1194 file->flags = QFILE_FLAG_PACKED;
1195 file->real_length = pfile->realsize;
1196 file->offset = pfile->offset;
1200 if (pfile->flags & PACKFILE_FLAG_DEFLATED)
1204 file->flags |= QFILE_FLAG_DEFLATED;
1206 // We need some more variables
1207 ztk = Mem_Alloc (fs_mempool, sizeof (*ztk));
1209 ztk->comp_length = pfile->packsize;
1211 // Initialize zlib stream
1212 ztk->zstream.next_in = ztk->input;
1213 ztk->zstream.avail_in = 0;
1215 /* From Zlib's "unzip.c":
1217 * windowBits is passed < 0 to tell that there is no zlib header.
1218 * Note that in this case inflate *requires* an extra "dummy" byte
1219 * after the compressed stream in order to complete decompression and
1220 * return Z_STREAM_END.
1221 * In unzip, i don't wait absolutely Z_STREAM_END because I known the
1222 * size of both compressed and uncompressed data
1224 if (qz_inflateInit2 (&ztk->zstream, -MAX_WBITS) != Z_OK)
1226 Con_Printf ("FS_OpenPackedFile: inflate init error (file: %s)", pfile->name);
1232 ztk->zstream.next_out = file->buff;
1233 ztk->zstream.avail_out = sizeof (file->buff);
1238 fs_filesize = pfile->realsize;
1244 ====================
1247 Return true if the path should be rejected due to one of the following:
1248 1: path elements that are non-portable
1249 2: path elements that would allow access to files outside the game directory,
1250 or are just not a good idea for a mod to be using.
1251 ====================
1253 int FS_CheckNastyPath (const char *path)
1255 // Windows: don't allow \ in filenames (windows-only), period.
1256 // (on Windows \ is a directory separator, but / is also supported)
1257 if (strstr(path, "\\"))
1258 return 1; // non-portable
1260 // Mac: don't allow Mac-only filenames - : is a directory separator
1261 // instead of /, but we rely on / working already, so there's no reason to
1262 // support a Mac-only path
1263 // Amiga and Windows: : tries to go to root of drive
1264 if (strstr(path, ":"))
1265 return 1; // non-portable attempt to go to root of drive
1267 // Amiga: // is parent directory
1268 if (strstr(path, "//"))
1269 return 1; // non-portable attempt to go to parent directory
1271 // all: don't allow going to current directory (./) or parent directory (../ or /../)
1272 if (strstr(path, "./"))
1273 return 2; // attempt to go outside the game directory
1275 // Windows and UNIXes: don't allow absolute paths
1277 return 2; // attempt to go outside the game directory
1279 // after all these checks we're pretty sure it's a / separated filename
1280 // and won't do much if any harm
1286 ====================
1289 Look for a file in the packages and in the filesystem
1291 Return the searchpath where the file was found (or NULL)
1292 and the file index in the package if relevant
1293 ====================
1295 static searchpath_t *FS_FindFile (const char *name, int* index, qboolean quiet)
1297 searchpath_t *search;
1300 // search through the path, one element at a time
1301 for (search = fs_searchpaths;search;search = search->next)
1303 // is the element a pak file?
1306 int (*strcmp_funct) (const char* str1, const char* str2);
1307 int left, right, middle;
1310 strcmp_funct = pak->ignorecase ? strcasecmp : strcmp;
1312 // Look for the file (binary search)
1314 right = pak->numfiles - 1;
1315 while (left <= right)
1319 middle = (left + right) / 2;
1320 diff = strcmp_funct (pak->files[middle].name, name);
1326 Con_DPrintf("FS_FindFile: %s in %s\n",
1327 pak->files[middle].name, pak->filename);
1334 // If we're too far in the list
1343 char netpath[MAX_OSPATH];
1344 dpsnprintf(netpath, sizeof(netpath), "%s%s", search->filename, name);
1345 if (FS_SysFileExists (netpath))
1348 Con_DPrintf("FS_FindFile: %s\n", netpath);
1358 Con_DPrintf("FS_FindFile: can't find %s\n", name);
1370 Look for a file in the search paths and open it in read-only mode
1375 qfile_t *FS_OpenReadFile (const char *filename, qboolean quiet, qboolean nonblocking)
1377 searchpath_t *search;
1380 search = FS_FindFile (filename, &pack_ind, quiet);
1389 // Found in the filesystem?
1392 char path [MAX_OSPATH];
1393 dpsnprintf (path, sizeof (path), "%s%s", search->filename, filename);
1394 return FS_SysOpen (path, "rb", nonblocking);
1397 // So, we found it in a package...
1398 return FS_OpenPackedFile (search->pack, pack_ind);
1403 =============================================================================
1405 MAIN PUBLIC FUNCTIONS
1407 =============================================================================
1411 ====================
1414 Open a file. The syntax is the same as fopen
1415 ====================
1417 qfile_t* FS_Open (const char* filepath, const char* mode, qboolean quiet, qboolean nonblocking)
1421 if (FS_CheckNastyPath(filepath))
1423 Con_Printf("FS_Open(\"%s\", \"%s\", %s): nasty filename rejected\n", filepath, mode, quiet ? "true" : "false");
1427 // If the file is opened in "write", "append", or "read/write" mode
1428 if (mode[0] == 'w' || mode[0] == 'a' || strchr (mode, '+'))
1430 char real_path [MAX_OSPATH];
1432 // Open the file on disk directly
1433 dpsnprintf (real_path, sizeof (real_path), "%s%s", fs_gamedir, filepath);
1435 // Create directories up to the file
1436 FS_CreatePath (real_path);
1438 return FS_SysOpen (real_path, mode, nonblocking);
1441 // Else, we look at the various search paths and open the file in read-only mode
1442 file = FS_OpenReadFile (filepath, quiet, nonblocking);
1444 fs_filesize = file->real_length;
1451 ====================
1455 ====================
1457 int FS_Close (qfile_t* file)
1459 if (close (file->handle))
1464 qz_inflateEnd (&file->ztk->zstream);
1465 Mem_Free (file->ztk);
1474 ====================
1477 Write "datasize" bytes into a file
1478 ====================
1480 fs_offset_t FS_Write (qfile_t* file, const void* data, size_t datasize)
1484 // If necessary, seek to the exact file position we're supposed to be
1485 if (file->buff_ind != file->buff_len)
1486 lseek (file->handle, file->buff_ind - file->buff_len, SEEK_CUR);
1488 // Purge cached data
1491 // Write the buffer and update the position
1492 result = write (file->handle, data, datasize);
1493 file->position = lseek (file->handle, 0, SEEK_CUR);
1494 if (file->real_length < file->position)
1495 file->real_length = file->position;
1505 ====================
1508 Read up to "buffersize" bytes from a file
1509 ====================
1511 fs_offset_t FS_Read (qfile_t* file, void* buffer, size_t buffersize)
1513 fs_offset_t count, done;
1515 if (buffersize == 0)
1518 // Get rid of the ungetc character
1519 if (file->ungetc != EOF)
1521 ((char*)buffer)[0] = file->ungetc;
1529 // First, we copy as many bytes as we can from "buff"
1530 if (file->buff_ind < file->buff_len)
1532 count = file->buff_len - file->buff_ind;
1534 done += ((fs_offset_t)buffersize > count) ? count : (fs_offset_t)buffersize;
1535 memcpy (buffer, &file->buff[file->buff_ind], done);
1536 file->buff_ind += done;
1539 if (buffersize == 0)
1543 // NOTE: at this point, the read buffer is always empty
1545 // If the file isn't compressed
1546 if (! (file->flags & QFILE_FLAG_DEFLATED))
1550 // We must take care to not read after the end of the file
1551 count = file->real_length - file->position;
1553 // If we have a lot of data to get, put them directly into "buffer"
1554 if (buffersize > sizeof (file->buff) / 2)
1556 if (count > (fs_offset_t)buffersize)
1557 count = (fs_offset_t)buffersize;
1558 lseek (file->handle, file->offset + file->position, SEEK_SET);
1559 nb = read (file->handle, &((qbyte*)buffer)[done], count);
1563 file->position += nb;
1565 // Purge cached data
1571 if (count > (fs_offset_t)sizeof (file->buff))
1572 count = (fs_offset_t)sizeof (file->buff);
1573 lseek (file->handle, file->offset + file->position, SEEK_SET);
1574 nb = read (file->handle, file->buff, count);
1577 file->buff_len = nb;
1578 file->position += nb;
1580 // Copy the requested data in "buffer" (as much as we can)
1581 count = (buffersize > file->buff_len) ? file->buff_len : buffersize;
1582 memcpy (&((qbyte*)buffer)[done], file->buff, count);
1583 file->buff_ind = count;
1591 // If the file is compressed, it's more complicated...
1592 // We cycle through a few operations until we have read enough data
1593 while (buffersize > 0)
1595 ztoolkit_t *ztk = file->ztk;
1598 // NOTE: at this point, the read buffer is always empty
1600 // If "input" is also empty, we need to refill it
1601 if (ztk->in_ind == ztk->in_len)
1603 // If we are at the end of the file
1604 if (file->position == file->real_length)
1607 count = ztk->comp_length - ztk->in_position;
1608 if (count > (fs_offset_t)sizeof (ztk->input))
1609 count = (fs_offset_t)sizeof (ztk->input);
1610 lseek (file->handle, file->offset + ztk->in_position, SEEK_SET);
1611 if (read (file->handle, ztk->input, count) != (fs_offset_t)count)
1613 Con_Printf ("FS_Read: unexpected end of file");
1618 ztk->in_len = count;
1619 ztk->in_position += count;
1622 ztk->zstream.next_in = &ztk->input[ztk->in_ind];
1623 ztk->zstream.avail_in = (unsigned int)(ztk->in_len - ztk->in_ind);
1625 // Now that we are sure we have compressed data available, we need to determine
1626 // if it's better to inflate it in "file->buff" or directly in "buffer"
1628 // Inflate the data in "file->buff"
1629 if (buffersize < sizeof (file->buff) / 2)
1631 ztk->zstream.next_out = file->buff;
1632 ztk->zstream.avail_out = sizeof (file->buff);
1633 error = qz_inflate (&ztk->zstream, Z_SYNC_FLUSH);
1634 if (error != Z_OK && error != Z_STREAM_END)
1636 Con_Printf ("FS_Read: Can't inflate file");
1639 ztk->in_ind = ztk->in_len - ztk->zstream.avail_in;
1641 file->buff_len = sizeof (file->buff) - ztk->zstream.avail_out;
1642 file->position += file->buff_len;
1644 // Copy the requested data in "buffer" (as much as we can)
1645 count = (buffersize > file->buff_len) ? file->buff_len : buffersize;
1646 memcpy (&((qbyte*)buffer)[done], file->buff, count);
1647 file->buff_ind = count;
1650 // Else, we inflate directly in "buffer"
1653 ztk->zstream.next_out = &((qbyte*)buffer)[done];
1654 ztk->zstream.avail_out = (unsigned int)buffersize;
1655 error = qz_inflate (&ztk->zstream, Z_SYNC_FLUSH);
1656 if (error != Z_OK && error != Z_STREAM_END)
1658 Con_Printf ("FS_Read: Can't inflate file");
1661 ztk->in_ind = ztk->in_len - ztk->zstream.avail_in;
1663 // How much data did it inflate?
1664 count = buffersize - ztk->zstream.avail_out;
1665 file->position += count;
1667 // Purge cached data
1672 buffersize -= count;
1680 ====================
1683 Print a string into a file
1684 ====================
1686 int FS_Print (qfile_t* file, const char *msg)
1688 return (int)FS_Write (file, msg, strlen (msg));
1692 ====================
1695 Print a string into a file
1696 ====================
1698 int FS_Printf(qfile_t* file, const char* format, ...)
1703 va_start (args, format);
1704 result = FS_VPrintf (file, format, args);
1712 ====================
1715 Print a string into a file
1716 ====================
1718 int FS_VPrintf (qfile_t* file, const char* format, va_list ap)
1721 fs_offset_t buff_size;
1722 char *tempbuff = NULL;
1725 tempbuff = Mem_Alloc (tempmempool, buff_size);
1726 len = dpvsnprintf (tempbuff, buff_size, format, ap);
1729 Mem_Free (tempbuff);
1731 tempbuff = Mem_Alloc (tempmempool, buff_size);
1732 len = dpvsnprintf (tempbuff, buff_size, format, ap);
1735 len = write (file->handle, tempbuff, len);
1736 Mem_Free (tempbuff);
1743 ====================
1746 Get the next character of a file
1747 ====================
1749 int FS_Getc (qfile_t* file)
1753 if (FS_Read (file, &c, 1) != 1)
1761 ====================
1764 Put a character back into the read buffer (only supports one character!)
1765 ====================
1767 int FS_UnGetc (qfile_t* file, unsigned char c)
1769 // If there's already a character waiting to be read
1770 if (file->ungetc != EOF)
1779 ====================
1782 Move the position index in a file
1783 ====================
1785 int FS_Seek (qfile_t* file, fs_offset_t offset, int whence)
1789 fs_offset_t buffersize;
1791 // Compute the file offset
1795 offset += (long)(file->position - file->buff_len + file->buff_ind);
1802 offset += (long)file->real_length;
1808 if (offset < 0 || offset > (long) file->real_length)
1811 // If we have the data in our read buffer, we don't need to actually seek
1812 if (file->position - (fs_offset_t)file->buff_len <= offset
1813 && offset <= file->position)
1815 file->buff_ind = offset + file->buff_len - file->position;
1819 // Purge cached data
1822 // Unpacked or uncompressed files can seek directly
1823 if (! (file->flags & QFILE_FLAG_DEFLATED))
1825 if (lseek (file->handle, file->offset + offset, SEEK_SET) == -1)
1827 file->position = offset;
1831 // Seeking in compressed files is more a hack than anything else,
1832 // but we need to support it, so here we go.
1835 // If we have to go back in the file, we need to restart from the beginning
1836 if (offset <= file->position)
1840 ztk->in_position = 0;
1842 lseek (file->handle, file->offset, SEEK_SET);
1844 // Reset the Zlib stream
1845 ztk->zstream.next_in = ztk->input;
1846 ztk->zstream.avail_in = 0;
1847 qz_inflateReset (&ztk->zstream);
1850 // We need a big buffer to force inflating into it directly
1851 buffersize = 2 * sizeof (file->buff);
1852 buffer = Mem_Alloc (tempmempool, buffersize);
1854 // Skip all data until we reach the requested offset
1855 while (offset > file->position)
1857 fs_offset_t diff = offset - file->position;
1858 fs_offset_t count, len;
1860 count = (diff > buffersize) ? buffersize : diff;
1861 len = FS_Read (file, buffer, count);
1875 ====================
1878 Give the current position in a file
1879 ====================
1881 fs_offset_t FS_Tell (qfile_t* file)
1883 return file->position - file->buff_len + file->buff_ind;
1888 ====================
1891 Erases any buffered input or output data
1892 ====================
1894 void FS_Purge (qfile_t* file)
1906 Filename are relative to the quake directory.
1907 Always appends a 0 byte.
1910 qbyte *FS_LoadFile (const char *path, mempool_t *pool, qboolean quiet)
1915 file = FS_Open (path, "rb", quiet, false);
1919 buf = Mem_Alloc (pool, fs_filesize + 1);
1920 buf[fs_filesize] = '\0';
1922 FS_Read (file, buf, fs_filesize);
1933 The filename will be prefixed by the current game directory
1936 qboolean FS_WriteFile (const char *filename, void *data, fs_offset_t len)
1940 file = FS_Open (filename, "wb", false, false);
1943 Con_Printf("FS_WriteFile: failed on %s\n", filename);
1947 Con_DPrintf("FS_WriteFile: %s\n", filename);
1948 FS_Write (file, data, len);
1955 =============================================================================
1957 OTHERS PUBLIC FUNCTIONS
1959 =============================================================================
1967 void FS_StripExtension (const char *in, char *out, size_t size_out)
1974 while (*in && size_out > 1)
1978 else if (*in == '/' || *in == '\\' || *in == ':')
1995 void FS_DefaultExtension (char *path, const char *extension, size_t size_path)
1999 // if path doesn't have a .EXT, append extension
2000 // (extension should include the .)
2001 src = path + strlen(path) - 1;
2003 while (*src != '/' && src != path)
2006 return; // it has an extension
2010 strlcat (path, extension, size_path);
2018 Look for a file in the packages and in the filesystem
2021 qboolean FS_FileExists (const char *filename)
2023 return (FS_FindFile (filename, NULL, true) != NULL);
2031 Look for a file in the filesystem only
2034 qboolean FS_SysFileExists (const char *path)
2039 // TODO: use another function instead, to avoid opening the file
2040 desc = open (path, O_RDONLY | O_BINARY);
2049 if (stat (path,&buf) == -1)
2056 void FS_mkdir (const char *path)
2069 Allocate and fill a search structure with information on matching filenames.
2072 fssearch_t *FS_Search(const char *pattern, int caseinsensitive, int quiet)
2075 searchpath_t *searchpath;
2077 int i, basepathlength, numfiles, numchars;
2078 stringlist_t *dir, *dirfile, *liststart, *listcurrent, *listtemp;
2079 const char *slash, *backslash, *colon, *separator;
2081 char netpath[MAX_OSPATH];
2082 char temp[MAX_OSPATH];
2084 for (i = 0;pattern[i] == '.' || pattern[i] == ':' || pattern[i] == '/' || pattern[i] == '\\';i++)
2089 Con_Printf("Don't use punctuation at the beginning of a search pattern!\n");
2097 slash = strrchr(pattern, '/');
2098 backslash = strrchr(pattern, '\\');
2099 colon = strrchr(pattern, ':');
2100 separator = max(slash, backslash);
2101 separator = max(separator, colon);
2102 basepathlength = separator ? (separator + 1 - pattern) : 0;
2103 basepath = Mem_Alloc (tempmempool, basepathlength + 1);
2105 memcpy(basepath, pattern, basepathlength);
2106 basepath[basepathlength] = 0;
2108 // search through the path, one element at a time
2109 for (searchpath = fs_searchpaths;searchpath;searchpath = searchpath->next)
2111 // is the element a pak file?
2112 if (searchpath->pack)
2114 // look through all the pak file elements
2115 pak = searchpath->pack;
2116 for (i = 0;i < pak->numfiles;i++)
2118 strcpy(temp, pak->files[i].name);
2121 if (matchpattern(temp, (char *)pattern, true))
2123 for (listtemp = liststart;listtemp;listtemp = listtemp->next)
2124 if (!strcmp(listtemp->text, temp))
2126 if (listtemp == NULL)
2128 listcurrent = stringlistappend(listcurrent, temp);
2129 if (liststart == NULL)
2130 liststart = listcurrent;
2132 Con_DPrintf("SearchPackFile: %s : %s\n", pak->filename, temp);
2135 // strip off one path element at a time until empty
2136 // this way directories are added to the listing if they match the pattern
2137 slash = strrchr(temp, '/');
2138 backslash = strrchr(temp, '\\');
2139 colon = strrchr(temp, ':');
2141 if (separator < slash)
2143 if (separator < backslash)
2144 separator = backslash;
2145 if (separator < colon)
2147 *((char *)separator) = 0;
2153 // get a directory listing and look at each name
2154 dpsnprintf(netpath, sizeof (netpath), "%s%s", searchpath->filename, basepath);
2155 if ((dir = listdirectory(netpath)))
2157 for (dirfile = dir;dirfile;dirfile = dirfile->next)
2159 dpsnprintf(temp, sizeof(temp), "%s%s", basepath, dirfile->text);
2160 if (matchpattern(temp, (char *)pattern, true))
2162 for (listtemp = liststart;listtemp;listtemp = listtemp->next)
2163 if (!strcmp(listtemp->text, temp))
2165 if (listtemp == NULL)
2167 listcurrent = stringlistappend(listcurrent, temp);
2168 if (liststart == NULL)
2169 liststart = listcurrent;
2171 Con_DPrintf("SearchDirFile: %s\n", temp);
2182 liststart = stringlistsort(liststart);
2185 for (listtemp = liststart;listtemp;listtemp = listtemp->next)
2188 numchars += strlen(listtemp->text) + 1;
2190 search = Z_Malloc(sizeof(fssearch_t) + numchars + numfiles * sizeof(char *));
2191 search->filenames = (char **)((char *)search + sizeof(fssearch_t));
2192 search->filenamesbuffer = (char *)((char *)search + sizeof(fssearch_t) + numfiles * sizeof(char *));
2193 search->numfilenames = (int)numfiles;
2196 for (listtemp = liststart;listtemp;listtemp = listtemp->next)
2198 search->filenames[numfiles] = search->filenamesbuffer + numchars;
2199 strcpy(search->filenames[numfiles], listtemp->text);
2201 numchars += strlen(listtemp->text) + 1;
2204 stringlistfree(liststart);
2211 void FS_FreeSearch(fssearch_t *search)
2216 extern int con_linewidth;
2217 int FS_ListDirectory(const char *pattern, int oneperline)
2228 search = FS_Search(pattern, true, true);
2231 numfiles = search->numfilenames;
2234 // FIXME: the names could be added to one column list and then
2235 // gradually shifted into the next column if they fit, and then the
2236 // next to make a compact variable width listing but it's a lot more
2238 // find width for columns
2240 for (i = 0;i < numfiles;i++)
2242 l = strlen(search->filenames[i]);
2243 if (columnwidth < l)
2246 // count the spacing character
2248 // calculate number of columns
2249 numcolumns = con_linewidth / columnwidth;
2250 // don't bother with the column printing if it's only one column
2251 if (numcolumns >= 2)
2253 numlines = (numfiles + numcolumns - 1) / numcolumns;
2254 for (i = 0;i < numlines;i++)
2257 for (k = 0;k < numcolumns;k++)
2259 l = i * numcolumns + k;
2262 name = search->filenames[l];
2263 for (j = 0;name[j] && linebufpos + 1 < (int)sizeof(linebuf);j++)
2264 linebuf[linebufpos++] = name[j];
2265 // space out name unless it's the last on the line
2266 if (k + 1 < numcolumns && l + 1 < numfiles)
2267 for (;j < columnwidth && linebufpos + 1 < (int)sizeof(linebuf);j++)
2268 linebuf[linebufpos++] = ' ';
2271 linebuf[linebufpos] = 0;
2272 Con_Printf("%s\n", linebuf);
2279 for (i = 0;i < numfiles;i++)
2280 Con_Printf("%s\n", search->filenames[i]);
2281 FS_FreeSearch(search);
2282 return (int)numfiles;
2285 static void FS_ListDirectoryCmd (const char* cmdname, int oneperline)
2287 const char *pattern;
2290 Con_Printf("usage:\n%s [path/pattern]\n", cmdname);
2293 if (Cmd_Argc() == 2)
2294 pattern = Cmd_Argv(1);
2297 if (!FS_ListDirectory(pattern, oneperline))
2298 Con_Print("No files found.\n");
2303 FS_ListDirectoryCmd("dir", true);
2308 FS_ListDirectoryCmd("ls", false);