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 fs_offset_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 fs_offset_t packsize; // size in the package
211 fs_offset_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 [] =
339 #elif defined(MACOSX)
353 if (! Sys_LoadLibrary (dllnames, &zlib_dll, zlibfuncs))
355 Con_Printf ("Compressed files support disabled\n");
359 Con_Printf ("Compressed files support enabled\n");
366 PK3_GetEndOfCentralDir
368 Extract the end of the central directory from a PK3 package
371 qboolean PK3_GetEndOfCentralDir (const char *packfile, int packhandle, pk3_endOfCentralDir_t *eocd)
373 long filesize, maxsize;
377 // Get the package size
378 filesize = lseek (packhandle, 0, SEEK_END);
379 if (filesize < ZIP_END_CDIR_SIZE)
382 // Load the end of the file in memory
383 if (filesize < ZIP_MAX_COMMENTS_SIZE + ZIP_END_CDIR_SIZE)
386 maxsize = ZIP_MAX_COMMENTS_SIZE + ZIP_END_CDIR_SIZE;
387 buffer = Mem_Alloc (tempmempool, maxsize);
388 lseek (packhandle, filesize - maxsize, SEEK_SET);
389 if (read (packhandle, buffer, maxsize) != (fs_offset_t) maxsize)
395 // Look for the end of central dir signature around the end of the file
396 maxsize -= ZIP_END_CDIR_SIZE;
397 ptr = &buffer[maxsize];
399 while (BuffBigLong (ptr) != ZIP_END_HEADER)
411 memcpy (eocd, ptr, ZIP_END_CDIR_SIZE);
412 eocd->signature = LittleLong (eocd->signature);
413 eocd->disknum = LittleShort (eocd->disknum);
414 eocd->cdir_disknum = LittleShort (eocd->cdir_disknum);
415 eocd->localentries = LittleShort (eocd->localentries);
416 eocd->nbentries = LittleShort (eocd->nbentries);
417 eocd->cdir_size = LittleLong (eocd->cdir_size);
418 eocd->cdir_offset = LittleLong (eocd->cdir_offset);
419 eocd->comment_size = LittleShort (eocd->comment_size);
431 Extract the file list from a PK3 file
434 int PK3_BuildFileList (pack_t *pack, const pk3_endOfCentralDir_t *eocd)
436 qbyte *central_dir, *ptr;
438 fs_offset_t remaining;
440 // Load the central directory in memory
441 central_dir = Mem_Alloc (tempmempool, eocd->cdir_size);
442 lseek (pack->handle, eocd->cdir_offset, SEEK_SET);
443 read (pack->handle, central_dir, eocd->cdir_size);
445 // Extract the files properties
446 // The parsing is done "by hand" because some fields have variable sizes and
447 // the constant part isn't 4-bytes aligned, which makes the use of structs difficult
448 remaining = eocd->cdir_size;
451 for (ind = 0; ind < eocd->nbentries; ind++)
453 fs_offset_t namesize, count;
455 // Checking the remaining size
456 if (remaining < ZIP_CDIR_CHUNK_BASE_SIZE)
458 Mem_Free (central_dir);
461 remaining -= ZIP_CDIR_CHUNK_BASE_SIZE;
464 if (BuffBigLong (ptr) != ZIP_CDIR_HEADER)
466 Mem_Free (central_dir);
470 namesize = BuffLittleShort (&ptr[28]); // filename length
472 // Check encryption, compression, and attributes
473 // 1st uint8 : general purpose bit flag
474 // Check bits 0 (encryption), 3 (data descriptor after the file), and 5 (compressed patched data (?))
475 // 2nd uint8 : external file attributes
476 // Check bits 3 (file is a directory) and 5 (file is a volume (?))
477 if ((ptr[8] & 0x29) == 0 && (ptr[38] & 0x18) == 0)
479 // Still enough bytes for the name?
480 if (remaining < namesize || namesize >= (int)sizeof (*pack->files))
482 Mem_Free (central_dir);
486 // WinZip doesn't use the "directory" attribute, so we need to check the name directly
487 if (ptr[ZIP_CDIR_CHUNK_BASE_SIZE + namesize - 1] != '/')
489 char filename [sizeof (pack->files[0].name)];
490 fs_offset_t offset, packsize, realsize;
491 packfile_flags_t flags;
493 // Extract the name (strip it if necessary)
494 namesize = min(namesize, (int)sizeof (filename) - 1);
495 memcpy (filename, &ptr[ZIP_CDIR_CHUNK_BASE_SIZE], namesize);
496 filename[namesize] = '\0';
498 if (BuffLittleShort (&ptr[10]))
499 flags = PACKFILE_FLAG_DEFLATED;
502 offset = BuffLittleLong (&ptr[42]);
503 packsize = BuffLittleLong (&ptr[20]);
504 realsize = BuffLittleLong (&ptr[24]);
505 FS_AddFileToPack (filename, pack, offset, packsize, realsize, flags);
509 // Skip the name, additionnal field, and comment
510 // 1er uint16 : extra field length
511 // 2eme uint16 : file comment length
512 count = namesize + BuffLittleShort (&ptr[30]) + BuffLittleShort (&ptr[32]);
513 ptr += ZIP_CDIR_CHUNK_BASE_SIZE + count;
517 // If the package is empty, central_dir is NULL here
518 if (central_dir != NULL)
519 Mem_Free (central_dir);
520 return pack->numfiles;
528 Create a package entry associated with a PK3 file
531 pack_t *FS_LoadPackPK3 (const char *packfile)
534 pk3_endOfCentralDir_t eocd;
538 packhandle = open (packfile, O_RDONLY | O_BINARY);
542 if (! PK3_GetEndOfCentralDir (packfile, packhandle, &eocd))
544 Con_Printf ("%s is not a PK3 file", packfile);
549 // Multi-volume ZIP archives are NOT allowed
550 if (eocd.disknum != 0 || eocd.cdir_disknum != 0)
552 Con_Printf ("%s is a multi-volume ZIP archive", packfile);
557 // We only need to do this test if MAX_FILES_IN_PACK is lesser than 65535
558 // since eocd.nbentries is an unsigned 16 bits integer
559 #if MAX_FILES_IN_PACK < 65535
560 if (eocd.nbentries > MAX_FILES_IN_PACK)
562 Con_Printf ("%s contains too many files (%hu)", packfile, eocd.nbentries);
568 // Create a package structure in memory
569 pack = Mem_Alloc(fs_mempool, sizeof (pack_t));
570 pack->ignorecase = true; // PK3 ignores case
571 strlcpy (pack->filename, packfile, sizeof (pack->filename));
572 pack->handle = packhandle;
573 pack->numfiles = eocd.nbentries;
574 pack->files = Mem_Alloc(fs_mempool, eocd.nbentries * sizeof(packfile_t));
575 pack->next = packlist;
578 real_nb_files = PK3_BuildFileList (pack, &eocd);
579 if (real_nb_files < 0)
581 Con_Printf ("%s is not a valid PK3 file", packfile);
587 Con_Printf("Added packfile %s (%i files)\n", packfile, real_nb_files);
594 PK3_GetTrueFileOffset
596 Find where the true file data offset is
599 qboolean PK3_GetTrueFileOffset (packfile_t *pfile, pack_t *pack)
601 qbyte buffer [ZIP_LOCAL_CHUNK_BASE_SIZE];
605 if (pfile->flags & PACKFILE_FLAG_TRUEOFFS)
608 // Load the local file description
609 lseek (pack->handle, pfile->offset, SEEK_SET);
610 count = read (pack->handle, buffer, ZIP_LOCAL_CHUNK_BASE_SIZE);
611 if (count != ZIP_LOCAL_CHUNK_BASE_SIZE || BuffBigLong (buffer) != ZIP_DATA_HEADER)
613 Con_Printf ("Can't retrieve file %s in package %s", pfile->name, pack->filename);
617 // Skip name and extra field
618 pfile->offset += BuffLittleShort (&buffer[26]) + BuffLittleShort (&buffer[28]) + ZIP_LOCAL_CHUNK_BASE_SIZE;
620 pfile->flags |= PACKFILE_FLAG_TRUEOFFS;
626 =============================================================================
628 OTHER PRIVATE FUNCTIONS
630 =============================================================================
638 Add a file to the list of files contained into a package
641 static packfile_t* FS_AddFileToPack (const char* name, pack_t* pack,
642 fs_offset_t offset, fs_offset_t packsize,
643 fs_offset_t realsize, packfile_flags_t flags)
645 int (*strcmp_funct) (const char* str1, const char* str2);
646 int left, right, middle;
649 strcmp_funct = pack->ignorecase ? strcasecmp : strcmp;
651 // Look for the slot we should put that file into (binary search)
653 right = pack->numfiles - 1;
654 while (left <= right)
658 middle = (left + right) / 2;
659 diff = strcmp_funct (pack->files[middle].name, name);
661 // If we found the file, there's a problem
663 Con_Printf ("Package %s contains the file %s several times\n", pack->filename, name);
665 // If we're too far in the list
672 // We have to move the right of the list by one slot to free the one we need
673 pfile = &pack->files[left];
674 memmove (pfile + 1, pfile, (pack->numfiles - left) * sizeof (*pfile));
677 strlcpy (pfile->name, name, sizeof (pfile->name));
678 pfile->offset = offset;
679 pfile->packsize = packsize;
680 pfile->realsize = realsize;
681 pfile->flags = flags;
691 Only used for FS_Open.
694 void FS_CreatePath (char *path)
698 for (ofs = path+1 ; *ofs ; ofs++)
700 if (*ofs == '/' || *ofs == '\\')
702 // create the directory
718 void FS_Path_f (void)
722 Con_Print("Current search path:\n");
723 for (s=fs_searchpaths ; s ; s=s->next)
726 Con_Printf("%s (%i files)\n", s->pack->filename, s->pack->numfiles);
728 Con_Printf("%s\n", s->filename);
737 Takes an explicit (not game tree related) path to a pak file.
739 Loads the header and directory, adding the files at the beginning
740 of the list so they override previous pack files.
743 pack_t *FS_LoadPackPAK (const char *packfile)
745 dpackheader_t header;
751 packhandle = open (packfile, O_RDONLY | O_BINARY);
754 read (packhandle, (void *)&header, sizeof(header));
755 if (memcmp(header.id, "PACK", 4))
757 Con_Printf ("%s is not a packfile", packfile);
761 header.dirofs = LittleLong (header.dirofs);
762 header.dirlen = LittleLong (header.dirlen);
764 if (header.dirlen % sizeof(dpackfile_t))
766 Con_Printf ("%s has an invalid directory size", packfile);
771 numpackfiles = header.dirlen / sizeof(dpackfile_t);
773 if (numpackfiles > MAX_FILES_IN_PACK)
775 Con_Printf ("%s has %i files", packfile, numpackfiles);
780 pack = Mem_Alloc(fs_mempool, sizeof (pack_t));
781 pack->ignorecase = false; // PAK is case sensitive
782 strlcpy (pack->filename, packfile, sizeof (pack->filename));
783 pack->handle = packhandle;
785 pack->files = Mem_Alloc(fs_mempool, numpackfiles * sizeof(packfile_t));
786 pack->next = packlist;
789 info = Mem_Alloc(tempmempool, sizeof(*info) * numpackfiles);
790 lseek (packhandle, header.dirofs, SEEK_SET);
791 read (packhandle, (void *)info, header.dirlen);
793 // parse the directory
794 for (i = 0;i < numpackfiles;i++)
796 fs_offset_t offset = LittleLong (info[i].filepos);
797 fs_offset_t size = LittleLong (info[i].filelen);
799 FS_AddFileToPack (info[i].name, pack, offset, size, size, PACKFILE_FLAG_TRUEOFFS);
804 Con_Printf("Added packfile %s (%i files)\n", packfile, numpackfiles);
813 Sets fs_gamedir, adds the directory to the head of the path,
814 then loads and adds pak1.pak pak2.pak ...
817 void FS_AddGameDirectory (const char *dir)
819 stringlist_t *list, *current;
820 searchpath_t *search;
822 char pakfile[MAX_OSPATH];
824 strlcpy (fs_gamedir, dir, sizeof (fs_gamedir));
826 list = listdirectory(dir);
828 // add any PAK package in the directory
829 for (current = list;current;current = current->next)
831 if (matchpattern(current->text, "*.pak", true))
833 dpsnprintf (pakfile, sizeof (pakfile), "%s%s", dir, current->text);
834 pak = FS_LoadPackPAK (pakfile);
837 search = Mem_Alloc(fs_mempool, sizeof(searchpath_t));
839 search->next = fs_searchpaths;
840 fs_searchpaths = search;
843 Con_Printf("unable to load pak \"%s\"\n", pakfile);
847 // add any PK3 package in the director
848 for (current = list;current;current = current->next)
850 if (matchpattern(current->text, "*.pk3", true))
852 dpsnprintf (pakfile, sizeof (pakfile), "%s%s", dir, current->text);
853 pak = FS_LoadPackPK3 (pakfile);
856 search = Mem_Alloc(fs_mempool, sizeof(searchpath_t));
858 search->next = fs_searchpaths;
859 fs_searchpaths = search;
862 Con_Printf("unable to load pak \"%s\"\n", pakfile);
867 // Add the directory to the search path
868 // (unpacked files have the priority over packed files)
869 search = Mem_Alloc(fs_mempool, sizeof(searchpath_t));
870 strlcpy (search->filename, dir, sizeof (search->filename));
871 search->next = fs_searchpaths;
872 fs_searchpaths = search;
881 void FS_AddGameHierarchy (const char *dir)
887 // Add the common game directory
888 FS_AddGameDirectory (va("%s/%s/", fs_basedir, dir));
891 // Add the personal game directory
892 homedir = getenv ("HOME");
893 if (homedir != NULL && homedir[0] != '\0')
894 FS_AddGameDirectory (va("%s/.%s/%s/", homedir, gameuserdirname, dir));
904 static const char *FS_FileExtension (const char *in)
906 const char *separator, *backslash, *colon, *dot;
908 separator = strrchr(in, '/');
909 backslash = strrchr(in, '\\');
910 if (separator < backslash)
911 separator = backslash;
912 colon = strrchr(in, ':');
913 if (separator < colon)
916 dot = strrchr(in, '.');
917 if (dot == NULL || dot < separator)
932 searchpath_t *search;
934 fs_mempool = Mem_AllocPool("file management", 0, NULL);
936 strcpy(fs_basedir, ".");
937 strcpy(fs_gamedir, "");
940 // FIXME: is there a better way to find the directory outside the .app?
941 if (strstr(com_argv[0], ".app/"))
945 split = strstr(com_argv[0], ".app/");
946 while (split > com_argv[0] && *split != '/')
948 strlcpy(fs_basedir, com_argv[0], sizeof(fs_basedir));
949 fs_basedir[split - com_argv[0]] = 0;
956 // Overrides the system supplied base directory (under GAMENAME)
957 // 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)
958 i = COM_CheckParm ("-basedir");
959 if (i && i < com_argc-1)
961 strlcpy (fs_basedir, com_argv[i+1], sizeof (fs_basedir));
962 i = (int)strlen (fs_basedir);
963 if (i > 0 && (fs_basedir[i-1] == '\\' || fs_basedir[i-1] == '/'))
967 // -path <dir or packfile> [<dir or packfile>] ...
968 // Fully specifies the exact search path, overriding the generated one
969 // 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)
970 i = COM_CheckParm ("-path");
974 while (++i < com_argc)
976 if (!com_argv[i] || com_argv[i][0] == '+' || com_argv[i][0] == '-')
979 search = Mem_Alloc(fs_mempool, sizeof(searchpath_t));
980 if (!strcasecmp (FS_FileExtension(com_argv[i]), "pak"))
982 search->pack = FS_LoadPackPAK (com_argv[i]);
985 Con_Printf ("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]);
995 Con_Printf ("Couldn't load packfile: %s", com_argv[i]);
1001 strlcpy (search->filename, com_argv[i], sizeof (search->filename));
1002 search->next = fs_searchpaths;
1003 fs_searchpaths = search;
1008 // add the game-specific paths
1009 // gamedirname1 (typically id1)
1010 FS_AddGameHierarchy (gamedirname1);
1012 // add the game-specific path, if any
1016 FS_AddGameHierarchy (gamedirname2);
1019 // set the com_modname (reported in server info)
1020 strlcpy(com_modname, gamedirname1, sizeof(com_modname));
1023 // Adds basedir/gamedir as an override game
1024 // LordHavoc: now supports multiple -game directories
1025 for (i = 1;i < com_argc;i++)
1029 if (!strcmp (com_argv[i], "-game") && i < com_argc-1)
1033 FS_AddGameHierarchy (com_argv[i]);
1034 // update the com_modname
1035 strlcpy (com_modname, com_argv[i], sizeof (com_modname));
1039 // If "-condebug" is in the command line, remove the previous log file
1040 if (COM_CheckParm ("-condebug") != 0)
1041 unlink (va("%s/qconsole.log", fs_gamedir));
1044 void FS_Init_Commands(void)
1046 Cvar_RegisterVariable (&scr_screenshot_name);
1048 Cmd_AddCommand ("path", FS_Path_f);
1049 Cmd_AddCommand ("dir", FS_Dir_f);
1050 Cmd_AddCommand ("ls", FS_Ls_f);
1052 // set the default screenshot name to either the mod name or the
1053 // gamemode screenshot name
1055 Cvar_SetQuick (&scr_screenshot_name, com_modname);
1057 Cvar_SetQuick (&scr_screenshot_name, gamescreenshotname);
1065 void FS_Shutdown (void)
1067 Mem_FreePool (&fs_mempool);
1071 ====================
1074 Internal function used to create a qfile_t and open the relevant non-packed file on disk
1075 ====================
1077 static qfile_t* FS_SysOpen (const char* filepath, const char* mode, qboolean nonblocking)
1083 // Parse the mode string
1092 opt = O_CREAT | O_TRUNC;
1096 opt = O_CREAT | O_APPEND;
1099 Con_Printf ("FS_SysOpen(%s, %s): invalid mode\n", filepath, mode);
1102 for (ind = 1; mode[ind] != '\0'; ind++)
1113 Con_Printf ("FS_SysOpen(%s, %s): unknown character in mode (%c)\n",
1114 filepath, mode, mode[ind]);
1123 file = Mem_Alloc (fs_mempool, sizeof (*file));
1124 memset (file, 0, sizeof (*file));
1127 file->handle = open (filepath, mod | opt, 0666);
1128 if (file->handle < 0)
1134 file->real_length = lseek (file->handle, 0, SEEK_END);
1136 // For files opened in append mode, we start at the end of the file
1138 file->position = file->real_length;
1140 lseek (file->handle, 0, SEEK_SET);
1150 Open a packed file using its package file descriptor
1153 qfile_t *FS_OpenPackedFile (pack_t* pack, int pack_ind)
1159 pfile = &pack->files[pack_ind];
1163 // If we don't have the true offset, get it now
1164 if (! (pfile->flags & PACKFILE_FLAG_TRUEOFFS))
1165 if (!PK3_GetTrueFileOffset (pfile, pack))
1168 // No Zlib DLL = no compressed files
1169 if (!zlib_dll && (pfile->flags & PACKFILE_FLAG_DEFLATED))
1171 Con_Printf("WARNING: can't open the compressed file %s\n"
1172 "You need the Zlib DLL to use compressed files\n",
1177 // LordHavoc: lseek affects all duplicates of a handle so we do it before
1178 // the dup() call to avoid having to close the dup_handle on error here
1179 if (lseek (pack->handle, pfile->offset, SEEK_SET) == -1)
1181 Con_Printf ("FS_OpenPackedFile: can't lseek to %s in %s (offset: %d)",
1182 pfile->name, pack->filename, pfile->offset);
1186 dup_handle = dup (pack->handle);
1189 Con_Printf ("FS_OpenPackedFile: can't dup package's handle (pack: %s)", pack->filename);
1193 file = Mem_Alloc (fs_mempool, sizeof (*file));
1194 memset (file, 0, sizeof (*file));
1195 file->handle = dup_handle;
1196 file->flags = QFILE_FLAG_PACKED;
1197 file->real_length = pfile->realsize;
1198 file->offset = pfile->offset;
1202 if (pfile->flags & PACKFILE_FLAG_DEFLATED)
1206 file->flags |= QFILE_FLAG_DEFLATED;
1208 // We need some more variables
1209 ztk = Mem_Alloc (fs_mempool, sizeof (*ztk));
1211 ztk->comp_length = pfile->packsize;
1213 // Initialize zlib stream
1214 ztk->zstream.next_in = ztk->input;
1215 ztk->zstream.avail_in = 0;
1217 /* From Zlib's "unzip.c":
1219 * windowBits is passed < 0 to tell that there is no zlib header.
1220 * Note that in this case inflate *requires* an extra "dummy" byte
1221 * after the compressed stream in order to complete decompression and
1222 * return Z_STREAM_END.
1223 * In unzip, i don't wait absolutely Z_STREAM_END because I known the
1224 * size of both compressed and uncompressed data
1226 if (qz_inflateInit2 (&ztk->zstream, -MAX_WBITS) != Z_OK)
1228 Con_Printf ("FS_OpenPackedFile: inflate init error (file: %s)", pfile->name);
1234 ztk->zstream.next_out = file->buff;
1235 ztk->zstream.avail_out = sizeof (file->buff);
1240 fs_filesize = pfile->realsize;
1246 ====================
1249 Return true if the path should be rejected due to one of the following:
1250 1: path elements that are non-portable
1251 2: path elements that would allow access to files outside the game directory,
1252 or are just not a good idea for a mod to be using.
1253 ====================
1255 int FS_CheckNastyPath (const char *path)
1257 // Windows: don't allow \ in filenames (windows-only), period.
1258 // (on Windows \ is a directory separator, but / is also supported)
1259 if (strstr(path, "\\"))
1260 return 1; // non-portable
1262 // Mac: don't allow Mac-only filenames - : is a directory separator
1263 // instead of /, but we rely on / working already, so there's no reason to
1264 // support a Mac-only path
1265 // Amiga and Windows: : tries to go to root of drive
1266 if (strstr(path, ":"))
1267 return 1; // non-portable attempt to go to root of drive
1269 // Amiga: // is parent directory
1270 if (strstr(path, "//"))
1271 return 1; // non-portable attempt to go to parent directory
1273 // all: don't allow going to current directory (./) or parent directory (../ or /../)
1274 if (strstr(path, "./"))
1275 return 2; // attempt to go outside the game directory
1277 // Windows and UNIXes: don't allow absolute paths
1279 return 2; // attempt to go outside the game directory
1281 // after all these checks we're pretty sure it's a / separated filename
1282 // and won't do much if any harm
1288 ====================
1291 Look for a file in the packages and in the filesystem
1293 Return the searchpath where the file was found (or NULL)
1294 and the file index in the package if relevant
1295 ====================
1297 static searchpath_t *FS_FindFile (const char *name, int* index, qboolean quiet)
1299 searchpath_t *search;
1302 // search through the path, one element at a time
1303 for (search = fs_searchpaths;search;search = search->next)
1305 // is the element a pak file?
1308 int (*strcmp_funct) (const char* str1, const char* str2);
1309 int left, right, middle;
1312 strcmp_funct = pak->ignorecase ? strcasecmp : strcmp;
1314 // Look for the file (binary search)
1316 right = pak->numfiles - 1;
1317 while (left <= right)
1321 middle = (left + right) / 2;
1322 diff = strcmp_funct (pak->files[middle].name, name);
1328 Con_DPrintf("FS_FindFile: %s in %s\n",
1329 pak->files[middle].name, pak->filename);
1336 // If we're too far in the list
1345 char netpath[MAX_OSPATH];
1346 dpsnprintf(netpath, sizeof(netpath), "%s%s", search->filename, name);
1347 if (FS_SysFileExists (netpath))
1350 Con_DPrintf("FS_FindFile: %s\n", netpath);
1360 Con_DPrintf("FS_FindFile: can't find %s\n", name);
1372 Look for a file in the search paths and open it in read-only mode
1377 qfile_t *FS_OpenReadFile (const char *filename, qboolean quiet, qboolean nonblocking)
1379 searchpath_t *search;
1382 search = FS_FindFile (filename, &pack_ind, quiet);
1391 // Found in the filesystem?
1394 char path [MAX_OSPATH];
1395 dpsnprintf (path, sizeof (path), "%s%s", search->filename, filename);
1396 return FS_SysOpen (path, "rb", nonblocking);
1399 // So, we found it in a package...
1400 return FS_OpenPackedFile (search->pack, pack_ind);
1405 =============================================================================
1407 MAIN PUBLIC FUNCTIONS
1409 =============================================================================
1413 ====================
1416 Open a file. The syntax is the same as fopen
1417 ====================
1419 qfile_t* FS_Open (const char* filepath, const char* mode, qboolean quiet, qboolean nonblocking)
1423 if (FS_CheckNastyPath(filepath))
1425 Con_Printf("FS_Open(\"%s\", \"%s\", %s): nasty filename rejected\n", filepath, mode, quiet ? "true" : "false");
1429 // If the file is opened in "write", "append", or "read/write" mode
1430 if (mode[0] == 'w' || mode[0] == 'a' || strchr (mode, '+'))
1432 char real_path [MAX_OSPATH];
1434 // Open the file on disk directly
1435 dpsnprintf (real_path, sizeof (real_path), "%s%s", fs_gamedir, filepath);
1437 // Create directories up to the file
1438 FS_CreatePath (real_path);
1440 return FS_SysOpen (real_path, mode, nonblocking);
1443 // Else, we look at the various search paths and open the file in read-only mode
1444 file = FS_OpenReadFile (filepath, quiet, nonblocking);
1446 fs_filesize = file->real_length;
1453 ====================
1457 ====================
1459 int FS_Close (qfile_t* file)
1461 if (close (file->handle))
1466 qz_inflateEnd (&file->ztk->zstream);
1467 Mem_Free (file->ztk);
1476 ====================
1479 Write "datasize" bytes into a file
1480 ====================
1482 fs_offset_t FS_Write (qfile_t* file, const void* data, size_t datasize)
1486 // If necessary, seek to the exact file position we're supposed to be
1487 if (file->buff_ind != file->buff_len)
1488 lseek (file->handle, file->buff_ind - file->buff_len, SEEK_CUR);
1490 // Purge cached data
1493 // Write the buffer and update the position
1494 result = write (file->handle, data, (fs_offset_t)datasize);
1495 file->position = lseek (file->handle, 0, SEEK_CUR);
1496 if (file->real_length < file->position)
1497 file->real_length = file->position;
1507 ====================
1510 Read up to "buffersize" bytes from a file
1511 ====================
1513 fs_offset_t FS_Read (qfile_t* file, void* buffer, size_t buffersize)
1515 fs_offset_t count, done;
1517 if (buffersize == 0)
1520 // Get rid of the ungetc character
1521 if (file->ungetc != EOF)
1523 ((char*)buffer)[0] = file->ungetc;
1531 // First, we copy as many bytes as we can from "buff"
1532 if (file->buff_ind < file->buff_len)
1534 count = file->buff_len - file->buff_ind;
1536 done += ((fs_offset_t)buffersize > count) ? count : (fs_offset_t)buffersize;
1537 memcpy (buffer, &file->buff[file->buff_ind], done);
1538 file->buff_ind += done;
1541 if (buffersize == 0)
1545 // NOTE: at this point, the read buffer is always empty
1547 // If the file isn't compressed
1548 if (! (file->flags & QFILE_FLAG_DEFLATED))
1552 // We must take care to not read after the end of the file
1553 count = file->real_length - file->position;
1555 // If we have a lot of data to get, put them directly into "buffer"
1556 if (buffersize > sizeof (file->buff) / 2)
1558 if (count > (fs_offset_t)buffersize)
1559 count = (fs_offset_t)buffersize;
1560 lseek (file->handle, file->offset + file->position, SEEK_SET);
1561 nb = read (file->handle, &((qbyte*)buffer)[done], count);
1565 file->position += nb;
1567 // Purge cached data
1573 if (count > (fs_offset_t)sizeof (file->buff))
1574 count = (fs_offset_t)sizeof (file->buff);
1575 lseek (file->handle, file->offset + file->position, SEEK_SET);
1576 nb = read (file->handle, file->buff, count);
1579 file->buff_len = nb;
1580 file->position += nb;
1582 // Copy the requested data in "buffer" (as much as we can)
1583 count = (fs_offset_t)buffersize > file->buff_len ? file->buff_len : (fs_offset_t)buffersize;
1584 memcpy (&((qbyte*)buffer)[done], file->buff, count);
1585 file->buff_ind = count;
1593 // If the file is compressed, it's more complicated...
1594 // We cycle through a few operations until we have read enough data
1595 while (buffersize > 0)
1597 ztoolkit_t *ztk = file->ztk;
1600 // NOTE: at this point, the read buffer is always empty
1602 // If "input" is also empty, we need to refill it
1603 if (ztk->in_ind == ztk->in_len)
1605 // If we are at the end of the file
1606 if (file->position == file->real_length)
1609 count = (fs_offset_t)(ztk->comp_length - ztk->in_position);
1610 if (count > (fs_offset_t)sizeof (ztk->input))
1611 count = (fs_offset_t)sizeof (ztk->input);
1612 lseek (file->handle, file->offset + (fs_offset_t)ztk->in_position, SEEK_SET);
1613 if (read (file->handle, ztk->input, count) != count)
1615 Con_Printf ("FS_Read: unexpected end of file");
1620 ztk->in_len = count;
1621 ztk->in_position += count;
1624 ztk->zstream.next_in = &ztk->input[ztk->in_ind];
1625 ztk->zstream.avail_in = (unsigned int)(ztk->in_len - ztk->in_ind);
1627 // Now that we are sure we have compressed data available, we need to determine
1628 // if it's better to inflate it in "file->buff" or directly in "buffer"
1630 // Inflate the data in "file->buff"
1631 if (buffersize < sizeof (file->buff) / 2)
1633 ztk->zstream.next_out = file->buff;
1634 ztk->zstream.avail_out = sizeof (file->buff);
1635 error = qz_inflate (&ztk->zstream, Z_SYNC_FLUSH);
1636 if (error != Z_OK && error != Z_STREAM_END)
1638 Con_Printf ("FS_Read: Can't inflate file");
1641 ztk->in_ind = ztk->in_len - ztk->zstream.avail_in;
1643 file->buff_len = (fs_offset_t)sizeof (file->buff) - ztk->zstream.avail_out;
1644 file->position += file->buff_len;
1646 // Copy the requested data in "buffer" (as much as we can)
1647 count = (fs_offset_t)buffersize > file->buff_len ? file->buff_len : (fs_offset_t)buffersize;
1648 memcpy (&((qbyte*)buffer)[done], file->buff, count);
1649 file->buff_ind = count;
1652 // Else, we inflate directly in "buffer"
1655 ztk->zstream.next_out = &((qbyte*)buffer)[done];
1656 ztk->zstream.avail_out = (unsigned int)buffersize;
1657 error = qz_inflate (&ztk->zstream, Z_SYNC_FLUSH);
1658 if (error != Z_OK && error != Z_STREAM_END)
1660 Con_Printf ("FS_Read: Can't inflate file");
1663 ztk->in_ind = ztk->in_len - ztk->zstream.avail_in;
1665 // How much data did it inflate?
1666 count = (fs_offset_t)(buffersize - ztk->zstream.avail_out);
1667 file->position += count;
1669 // Purge cached data
1674 buffersize -= count;
1682 ====================
1685 Print a string into a file
1686 ====================
1688 int FS_Print (qfile_t* file, const char *msg)
1690 return (int)FS_Write (file, msg, strlen (msg));
1694 ====================
1697 Print a string into a file
1698 ====================
1700 int FS_Printf(qfile_t* file, const char* format, ...)
1705 va_start (args, format);
1706 result = FS_VPrintf (file, format, args);
1714 ====================
1717 Print a string into a file
1718 ====================
1720 int FS_VPrintf (qfile_t* file, const char* format, va_list ap)
1723 fs_offset_t buff_size;
1724 char *tempbuff = NULL;
1727 tempbuff = Mem_Alloc (tempmempool, buff_size);
1728 len = dpvsnprintf (tempbuff, buff_size, format, ap);
1731 Mem_Free (tempbuff);
1733 tempbuff = Mem_Alloc (tempmempool, buff_size);
1734 len = dpvsnprintf (tempbuff, buff_size, format, ap);
1737 len = write (file->handle, tempbuff, len);
1738 Mem_Free (tempbuff);
1745 ====================
1748 Get the next character of a file
1749 ====================
1751 int FS_Getc (qfile_t* file)
1755 if (FS_Read (file, &c, 1) != 1)
1763 ====================
1766 Put a character back into the read buffer (only supports one character!)
1767 ====================
1769 int FS_UnGetc (qfile_t* file, unsigned char c)
1771 // If there's already a character waiting to be read
1772 if (file->ungetc != EOF)
1781 ====================
1784 Move the position index in a file
1785 ====================
1787 int FS_Seek (qfile_t* file, fs_offset_t offset, int whence)
1791 fs_offset_t buffersize;
1793 // Compute the file offset
1797 offset += file->position - file->buff_len + file->buff_ind;
1804 offset += file->real_length;
1810 if (offset < 0 || offset > (long) file->real_length)
1813 // If we have the data in our read buffer, we don't need to actually seek
1814 if (file->position - file->buff_len <= offset && offset <= file->position)
1816 file->buff_ind = offset + file->buff_len - file->position;
1820 // Purge cached data
1823 // Unpacked or uncompressed files can seek directly
1824 if (! (file->flags & QFILE_FLAG_DEFLATED))
1826 if (lseek (file->handle, file->offset + offset, SEEK_SET) == -1)
1828 file->position = offset;
1832 // Seeking in compressed files is more a hack than anything else,
1833 // but we need to support it, so here we go.
1836 // If we have to go back in the file, we need to restart from the beginning
1837 if (offset <= file->position)
1841 ztk->in_position = 0;
1843 lseek (file->handle, file->offset, SEEK_SET);
1845 // Reset the Zlib stream
1846 ztk->zstream.next_in = ztk->input;
1847 ztk->zstream.avail_in = 0;
1848 qz_inflateReset (&ztk->zstream);
1851 // We need a big buffer to force inflating into it directly
1852 buffersize = 2 * sizeof (file->buff);
1853 buffer = Mem_Alloc (tempmempool, buffersize);
1855 // Skip all data until we reach the requested offset
1856 while (offset > file->position)
1858 fs_offset_t diff = offset - file->position;
1859 fs_offset_t count, len;
1861 count = (diff > buffersize) ? buffersize : diff;
1862 len = FS_Read (file, buffer, count);
1876 ====================
1879 Give the current position in a file
1880 ====================
1882 fs_offset_t FS_Tell (qfile_t* file)
1884 return file->position - file->buff_len + file->buff_ind;
1889 ====================
1892 Erases any buffered input or output data
1893 ====================
1895 void FS_Purge (qfile_t* file)
1907 Filename are relative to the quake directory.
1908 Always appends a 0 byte.
1911 qbyte *FS_LoadFile (const char *path, mempool_t *pool, qboolean quiet)
1916 file = FS_Open (path, "rb", quiet, false);
1920 buf = Mem_Alloc (pool, fs_filesize + 1);
1921 buf[fs_filesize] = '\0';
1923 FS_Read (file, buf, fs_filesize);
1934 The filename will be prefixed by the current game directory
1937 qboolean FS_WriteFile (const char *filename, void *data, fs_offset_t len)
1941 file = FS_Open (filename, "wb", false, false);
1944 Con_Printf("FS_WriteFile: failed on %s\n", filename);
1948 Con_DPrintf("FS_WriteFile: %s\n", filename);
1949 FS_Write (file, data, len);
1956 =============================================================================
1958 OTHERS PUBLIC FUNCTIONS
1960 =============================================================================
1968 void FS_StripExtension (const char *in, char *out, size_t size_out)
1975 while (*in && size_out > 1)
1979 else if (*in == '/' || *in == '\\' || *in == ':')
1996 void FS_DefaultExtension (char *path, const char *extension, size_t size_path)
2000 // if path doesn't have a .EXT, append extension
2001 // (extension should include the .)
2002 src = path + strlen(path) - 1;
2004 while (*src != '/' && src != path)
2007 return; // it has an extension
2011 strlcat (path, extension, size_path);
2019 Look for a file in the packages and in the filesystem
2022 qboolean FS_FileExists (const char *filename)
2024 return (FS_FindFile (filename, NULL, true) != NULL);
2032 Look for a file in the filesystem only
2035 qboolean FS_SysFileExists (const char *path)
2040 // TODO: use another function instead, to avoid opening the file
2041 desc = open (path, O_RDONLY | O_BINARY);
2050 if (stat (path,&buf) == -1)
2057 void FS_mkdir (const char *path)
2070 Allocate and fill a search structure with information on matching filenames.
2073 fssearch_t *FS_Search(const char *pattern, int caseinsensitive, int quiet)
2076 searchpath_t *searchpath;
2078 int i, basepathlength, numfiles, numchars;
2079 stringlist_t *dir, *dirfile, *liststart, *listcurrent, *listtemp;
2080 const char *slash, *backslash, *colon, *separator;
2082 char netpath[MAX_OSPATH];
2083 char temp[MAX_OSPATH];
2085 for (i = 0;pattern[i] == '.' || pattern[i] == ':' || pattern[i] == '/' || pattern[i] == '\\';i++)
2090 Con_Printf("Don't use punctuation at the beginning of a search pattern!\n");
2098 slash = strrchr(pattern, '/');
2099 backslash = strrchr(pattern, '\\');
2100 colon = strrchr(pattern, ':');
2101 separator = max(slash, backslash);
2102 separator = max(separator, colon);
2103 basepathlength = separator ? (separator + 1 - pattern) : 0;
2104 basepath = Mem_Alloc (tempmempool, basepathlength + 1);
2106 memcpy(basepath, pattern, basepathlength);
2107 basepath[basepathlength] = 0;
2109 // search through the path, one element at a time
2110 for (searchpath = fs_searchpaths;searchpath;searchpath = searchpath->next)
2112 // is the element a pak file?
2113 if (searchpath->pack)
2115 // look through all the pak file elements
2116 pak = searchpath->pack;
2117 for (i = 0;i < pak->numfiles;i++)
2119 strcpy(temp, pak->files[i].name);
2122 if (matchpattern(temp, (char *)pattern, true))
2124 for (listtemp = liststart;listtemp;listtemp = listtemp->next)
2125 if (!strcmp(listtemp->text, temp))
2127 if (listtemp == NULL)
2129 listcurrent = stringlistappend(listcurrent, temp);
2130 if (liststart == NULL)
2131 liststart = listcurrent;
2133 Con_DPrintf("SearchPackFile: %s : %s\n", pak->filename, temp);
2136 // strip off one path element at a time until empty
2137 // this way directories are added to the listing if they match the pattern
2138 slash = strrchr(temp, '/');
2139 backslash = strrchr(temp, '\\');
2140 colon = strrchr(temp, ':');
2142 if (separator < slash)
2144 if (separator < backslash)
2145 separator = backslash;
2146 if (separator < colon)
2148 *((char *)separator) = 0;
2154 // get a directory listing and look at each name
2155 dpsnprintf(netpath, sizeof (netpath), "%s%s", searchpath->filename, basepath);
2156 if ((dir = listdirectory(netpath)))
2158 for (dirfile = dir;dirfile;dirfile = dirfile->next)
2160 dpsnprintf(temp, sizeof(temp), "%s%s", basepath, dirfile->text);
2161 if (matchpattern(temp, (char *)pattern, true))
2163 for (listtemp = liststart;listtemp;listtemp = listtemp->next)
2164 if (!strcmp(listtemp->text, temp))
2166 if (listtemp == NULL)
2168 listcurrent = stringlistappend(listcurrent, temp);
2169 if (liststart == NULL)
2170 liststart = listcurrent;
2172 Con_DPrintf("SearchDirFile: %s\n", temp);
2183 liststart = stringlistsort(liststart);
2186 for (listtemp = liststart;listtemp;listtemp = listtemp->next)
2189 numchars += (int)strlen(listtemp->text) + 1;
2191 search = Z_Malloc(sizeof(fssearch_t) + numchars + numfiles * sizeof(char *));
2192 search->filenames = (char **)((char *)search + sizeof(fssearch_t));
2193 search->filenamesbuffer = (char *)((char *)search + sizeof(fssearch_t) + numfiles * sizeof(char *));
2194 search->numfilenames = (int)numfiles;
2197 for (listtemp = liststart;listtemp;listtemp = listtemp->next)
2199 search->filenames[numfiles] = search->filenamesbuffer + numchars;
2200 strcpy(search->filenames[numfiles], listtemp->text);
2202 numchars += (int)strlen(listtemp->text) + 1;
2205 stringlistfree(liststart);
2212 void FS_FreeSearch(fssearch_t *search)
2217 extern int con_linewidth;
2218 int FS_ListDirectory(const char *pattern, int oneperline)
2229 search = FS_Search(pattern, true, true);
2232 numfiles = search->numfilenames;
2235 // FIXME: the names could be added to one column list and then
2236 // gradually shifted into the next column if they fit, and then the
2237 // next to make a compact variable width listing but it's a lot more
2239 // find width for columns
2241 for (i = 0;i < numfiles;i++)
2243 l = (int)strlen(search->filenames[i]);
2244 if (columnwidth < l)
2247 // count the spacing character
2249 // calculate number of columns
2250 numcolumns = con_linewidth / columnwidth;
2251 // don't bother with the column printing if it's only one column
2252 if (numcolumns >= 2)
2254 numlines = (numfiles + numcolumns - 1) / numcolumns;
2255 for (i = 0;i < numlines;i++)
2258 for (k = 0;k < numcolumns;k++)
2260 l = i * numcolumns + k;
2263 name = search->filenames[l];
2264 for (j = 0;name[j] && linebufpos + 1 < (int)sizeof(linebuf);j++)
2265 linebuf[linebufpos++] = name[j];
2266 // space out name unless it's the last on the line
2267 if (k + 1 < numcolumns && l + 1 < numfiles)
2268 for (;j < columnwidth && linebufpos + 1 < (int)sizeof(linebuf);j++)
2269 linebuf[linebufpos++] = ' ';
2272 linebuf[linebufpos] = 0;
2273 Con_Printf("%s\n", linebuf);
2280 for (i = 0;i < numfiles;i++)
2281 Con_Printf("%s\n", search->filenames[i]);
2282 FS_FreeSearch(search);
2283 return (int)numfiles;
2286 static void FS_ListDirectoryCmd (const char* cmdname, int oneperline)
2288 const char *pattern;
2291 Con_Printf("usage:\n%s [path/pattern]\n", cmdname);
2294 if (Cmd_Argc() == 2)
2295 pattern = Cmd_Argv(1);
2298 if (!FS_ListDirectory(pattern, oneperline))
2299 Con_Print("No files found.\n");
2304 FS_ListDirectoryCmd("dir", true);
2309 FS_ListDirectoryCmd("ls", false);