4 Copyright (C) 2003-2005 Mathieu Olivier
6 This program is free software; you can redistribute it and/or
7 modify it under the terms of the GNU General Public License
8 as published by the Free Software Foundation; either version 2
9 of the License, or (at your option) any later version.
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
15 See the GNU General Public License for more details.
17 You should have received a copy of the GNU General Public License
18 along with this program; if not, write to:
20 Free Software Foundation, Inc.
21 59 Temple Place - Suite 330
22 Boston, MA 02111-1307, USA
35 # include <sys/stat.h>
41 // Win32 requires us to add O_BINARY, but the other OSes don't have it
46 // In case the system doesn't support the O_NONBLOCK flag
54 All of Quake's data access is through a hierchal file system, but the contents
55 of the file system can be transparently merged from several sources.
57 The "base directory" is the path to the directory holding the quake.exe and
58 all game directories. The sys_* files pass this to host_init in
59 quakeparms_t->basedir. This can be overridden with the "-basedir" command
60 line parm to allow code debugging in a different directory. The base
61 directory is only used during filesystem initialization.
63 The "game directory" is the first tree on the search path and directory that
64 all generated files (savegames, screenshots, demos, config files) will be
65 saved to. This can be overridden with the "-game" command line parameter.
66 The game directory can never be changed while quake is executing. This is a
67 precaution against having a malicious server instruct clients to write files
68 over areas they shouldn't.
74 =============================================================================
78 =============================================================================
81 // Magic numbers of a ZIP file (big-endian format)
82 #define ZIP_DATA_HEADER 0x504B0304 // "PK\3\4"
83 #define ZIP_CDIR_HEADER 0x504B0102 // "PK\1\2"
84 #define ZIP_END_HEADER 0x504B0506 // "PK\5\6"
86 // Other constants for ZIP files
87 #define ZIP_MAX_COMMENTS_SIZE ((unsigned short)0xFFFF)
88 #define ZIP_END_CDIR_SIZE 22
89 #define ZIP_CDIR_CHUNK_BASE_SIZE 46
90 #define ZIP_LOCAL_CHUNK_BASE_SIZE 30
92 // Zlib constants (from zlib.h)
93 #define Z_SYNC_FLUSH 2
96 #define Z_STREAM_END 1
97 #define ZLIB_VERSION "1.2.3"
99 // Uncomment the following line if the zlib DLL you have still uses
100 // the 1.1.x series calling convention on Win32 (WINAPI)
101 //#define ZLIB_USES_WINAPI
105 =============================================================================
109 =============================================================================
112 // Zlib stream (from zlib.h)
113 // Warning: some pointers we don't use directly have
114 // been cast to "void*" for a matter of simplicity
117 qbyte *next_in; // next input byte
118 unsigned int avail_in; // number of bytes available at next_in
119 unsigned long total_in; // total nb of input bytes read so far
121 qbyte *next_out; // next output byte should be put there
122 unsigned int avail_out; // remaining free space at next_out
123 unsigned long total_out; // total nb of bytes output so far
125 char *msg; // last error message, NULL if no error
126 void *state; // not visible by applications
128 void *zalloc; // used to allocate the internal state
129 void *zfree; // used to free the internal state
130 void *opaque; // private data object passed to zalloc and zfree
132 int data_type; // best guess about the data type: ascii or binary
133 unsigned long adler; // adler32 value of the uncompressed data
134 unsigned long reserved; // reserved for future use
141 QFILE_FLAG_PACKED = (1 << 0), // inside a package (PAK or PK3)
142 QFILE_FLAG_DEFLATED = (1 << 1) // file is compressed using the deflate algorithm (PK3 only)
145 #define FILE_BUFF_SIZE 2048
149 size_t comp_length; // length of the compressed file
150 size_t in_ind, in_len; // input buffer current index and length
151 size_t in_position; // position in the compressed file
152 qbyte input [FILE_BUFF_SIZE];
158 int handle; // file descriptor
159 fs_offset_t real_length; // uncompressed file size (for files opened in "read" mode)
160 fs_offset_t position; // current position in the file
161 fs_offset_t offset; // offset into the package (0 if external file)
162 int ungetc; // single stored character from ungetc, cleared to EOF when read
165 fs_offset_t buff_ind, buff_len; // buffer current index and length
166 qbyte buff [FILE_BUFF_SIZE];
173 // ------ PK3 files on disk ------ //
175 // You can get the complete ZIP format description from PKWARE website
179 unsigned int signature;
180 unsigned short disknum;
181 unsigned short cdir_disknum; // number of the disk with the start of the central directory
182 unsigned short localentries; // number of entries in the central directory on this disk
183 unsigned short nbentries; // total number of entries in the central directory on this disk
184 unsigned int cdir_size; // size of the central directory
185 unsigned int cdir_offset; // with respect to the starting disk number
186 unsigned short comment_size;
187 } pk3_endOfCentralDir_t;
190 // ------ PAK files on disk ------ //
194 int filepos, filelen;
205 // Packages in memory
208 PACKFILE_FLAG_NONE = 0,
209 PACKFILE_FLAG_TRUEOFFS = (1 << 0), // the offset in packfile_t is the true contents offset
210 PACKFILE_FLAG_DEFLATED = (1 << 1) // file compressed using the deflate algorithm
215 char name [MAX_QPATH];
216 packfile_flags_t flags;
218 fs_offset_t packsize; // size in the package
219 fs_offset_t realsize; // real file size (uncompressed)
222 typedef struct pack_s
224 char filename [MAX_OSPATH];
226 int ignorecase; // PK3 ignores case
233 // Search paths for files (including packages)
234 typedef struct searchpath_s
236 // only one of filename / pack will be used
237 char filename[MAX_OSPATH];
239 struct searchpath_s *next;
244 =============================================================================
248 =============================================================================
254 static packfile_t* FS_AddFileToPack (const char* name, pack_t* pack,
255 fs_offset_t offset, fs_offset_t packsize,
256 fs_offset_t realsize, packfile_flags_t flags);
260 =============================================================================
264 =============================================================================
267 mempool_t *fs_mempool;
269 fs_offset_t fs_filesize;
271 pack_t *packlist = NULL;
273 searchpath_t *fs_searchpaths = NULL;
275 #define MAX_FILES_IN_PACK 65536
277 char fs_gamedir[MAX_OSPATH];
278 char fs_basedir[MAX_OSPATH];
280 qboolean fs_modified; // set true if using non-id files
282 cvar_t scr_screenshot_name = {0, "scr_screenshot_name","dp"};
286 =============================================================================
288 PRIVATE FUNCTIONS - PK3 HANDLING
290 =============================================================================
293 // Functions exported from zlib
294 #if defined(WIN32) && defined(ZLIB_USES_WINAPI)
295 # define ZEXPORT WINAPI
300 static int (ZEXPORT *qz_inflate) (z_stream* strm, int flush);
301 static int (ZEXPORT *qz_inflateEnd) (z_stream* strm);
302 static int (ZEXPORT *qz_inflateInit2_) (z_stream* strm, int windowBits, const char *version, int stream_size);
303 static int (ZEXPORT *qz_inflateReset) (z_stream* strm);
305 #define qz_inflateInit2(strm, windowBits) \
306 qz_inflateInit2_((strm), (windowBits), ZLIB_VERSION, sizeof(z_stream))
308 static dllfunction_t zlibfuncs[] =
310 {"inflate", (void **) &qz_inflate},
311 {"inflateEnd", (void **) &qz_inflateEnd},
312 {"inflateInit2_", (void **) &qz_inflateInit2_},
313 {"inflateReset", (void **) &qz_inflateReset},
317 // Handle for Zlib DLL
318 static dllhandle_t zlib_dll = NULL;
328 void PK3_CloseLibrary (void)
330 Sys_UnloadLibrary (&zlib_dll);
338 Try to load the Zlib DLL
341 qboolean PK3_OpenLibrary (void)
343 const char* dllnames [] =
348 # ifdef ZLIB_USES_WINAPI
354 #elif defined(MACOSX)
368 if (! Sys_LoadLibrary (dllnames, &zlib_dll, zlibfuncs))
370 Con_Printf ("Compressed files support disabled\n");
374 Con_Printf ("Compressed files support enabled\n");
381 PK3_GetEndOfCentralDir
383 Extract the end of the central directory from a PK3 package
386 qboolean PK3_GetEndOfCentralDir (const char *packfile, int packhandle, pk3_endOfCentralDir_t *eocd)
388 long filesize, maxsize;
392 // Get the package size
393 filesize = lseek (packhandle, 0, SEEK_END);
394 if (filesize < ZIP_END_CDIR_SIZE)
397 // Load the end of the file in memory
398 if (filesize < ZIP_MAX_COMMENTS_SIZE + ZIP_END_CDIR_SIZE)
401 maxsize = ZIP_MAX_COMMENTS_SIZE + ZIP_END_CDIR_SIZE;
402 buffer = Mem_Alloc (tempmempool, maxsize);
403 lseek (packhandle, filesize - maxsize, SEEK_SET);
404 if (read (packhandle, buffer, maxsize) != (fs_offset_t) maxsize)
410 // Look for the end of central dir signature around the end of the file
411 maxsize -= ZIP_END_CDIR_SIZE;
412 ptr = &buffer[maxsize];
414 while (BuffBigLong (ptr) != ZIP_END_HEADER)
426 memcpy (eocd, ptr, ZIP_END_CDIR_SIZE);
427 eocd->signature = LittleLong (eocd->signature);
428 eocd->disknum = LittleShort (eocd->disknum);
429 eocd->cdir_disknum = LittleShort (eocd->cdir_disknum);
430 eocd->localentries = LittleShort (eocd->localentries);
431 eocd->nbentries = LittleShort (eocd->nbentries);
432 eocd->cdir_size = LittleLong (eocd->cdir_size);
433 eocd->cdir_offset = LittleLong (eocd->cdir_offset);
434 eocd->comment_size = LittleShort (eocd->comment_size);
446 Extract the file list from a PK3 file
449 int PK3_BuildFileList (pack_t *pack, const pk3_endOfCentralDir_t *eocd)
451 qbyte *central_dir, *ptr;
453 fs_offset_t remaining;
455 // Load the central directory in memory
456 central_dir = Mem_Alloc (tempmempool, eocd->cdir_size);
457 lseek (pack->handle, eocd->cdir_offset, SEEK_SET);
458 read (pack->handle, central_dir, eocd->cdir_size);
460 // Extract the files properties
461 // The parsing is done "by hand" because some fields have variable sizes and
462 // the constant part isn't 4-bytes aligned, which makes the use of structs difficult
463 remaining = eocd->cdir_size;
466 for (ind = 0; ind < eocd->nbentries; ind++)
468 fs_offset_t namesize, count;
470 // Checking the remaining size
471 if (remaining < ZIP_CDIR_CHUNK_BASE_SIZE)
473 Mem_Free (central_dir);
476 remaining -= ZIP_CDIR_CHUNK_BASE_SIZE;
479 if (BuffBigLong (ptr) != ZIP_CDIR_HEADER)
481 Mem_Free (central_dir);
485 namesize = BuffLittleShort (&ptr[28]); // filename length
487 // Check encryption, compression, and attributes
488 // 1st uint8 : general purpose bit flag
489 // Check bits 0 (encryption), 3 (data descriptor after the file), and 5 (compressed patched data (?))
490 // 2nd uint8 : external file attributes
491 // Check bits 3 (file is a directory) and 5 (file is a volume (?))
492 if ((ptr[8] & 0x29) == 0 && (ptr[38] & 0x18) == 0)
494 // Still enough bytes for the name?
495 if (remaining < namesize || namesize >= (int)sizeof (*pack->files))
497 Mem_Free (central_dir);
501 // WinZip doesn't use the "directory" attribute, so we need to check the name directly
502 if (ptr[ZIP_CDIR_CHUNK_BASE_SIZE + namesize - 1] != '/')
504 char filename [sizeof (pack->files[0].name)];
505 fs_offset_t offset, packsize, realsize;
506 packfile_flags_t flags;
508 // Extract the name (strip it if necessary)
509 namesize = min(namesize, (int)sizeof (filename) - 1);
510 memcpy (filename, &ptr[ZIP_CDIR_CHUNK_BASE_SIZE], namesize);
511 filename[namesize] = '\0';
513 if (BuffLittleShort (&ptr[10]))
514 flags = PACKFILE_FLAG_DEFLATED;
517 offset = BuffLittleLong (&ptr[42]);
518 packsize = BuffLittleLong (&ptr[20]);
519 realsize = BuffLittleLong (&ptr[24]);
520 FS_AddFileToPack (filename, pack, offset, packsize, realsize, flags);
524 // Skip the name, additionnal field, and comment
525 // 1er uint16 : extra field length
526 // 2eme uint16 : file comment length
527 count = namesize + BuffLittleShort (&ptr[30]) + BuffLittleShort (&ptr[32]);
528 ptr += ZIP_CDIR_CHUNK_BASE_SIZE + count;
532 // If the package is empty, central_dir is NULL here
533 if (central_dir != NULL)
534 Mem_Free (central_dir);
535 return pack->numfiles;
543 Create a package entry associated with a PK3 file
546 pack_t *FS_LoadPackPK3 (const char *packfile)
549 pk3_endOfCentralDir_t eocd;
553 packhandle = open (packfile, O_RDONLY | O_BINARY);
557 if (! PK3_GetEndOfCentralDir (packfile, packhandle, &eocd))
559 Con_Printf ("%s is not a PK3 file", packfile);
564 // Multi-volume ZIP archives are NOT allowed
565 if (eocd.disknum != 0 || eocd.cdir_disknum != 0)
567 Con_Printf ("%s is a multi-volume ZIP archive", packfile);
572 // We only need to do this test if MAX_FILES_IN_PACK is lesser than 65535
573 // since eocd.nbentries is an unsigned 16 bits integer
574 #if MAX_FILES_IN_PACK < 65535
575 if (eocd.nbentries > MAX_FILES_IN_PACK)
577 Con_Printf ("%s contains too many files (%hu)", packfile, eocd.nbentries);
583 // Create a package structure in memory
584 pack = Mem_Alloc(fs_mempool, sizeof (pack_t));
585 pack->ignorecase = true; // PK3 ignores case
586 strlcpy (pack->filename, packfile, sizeof (pack->filename));
587 pack->handle = packhandle;
588 pack->numfiles = eocd.nbentries;
589 pack->files = Mem_Alloc(fs_mempool, eocd.nbentries * sizeof(packfile_t));
590 pack->next = packlist;
593 real_nb_files = PK3_BuildFileList (pack, &eocd);
594 if (real_nb_files < 0)
596 Con_Printf ("%s is not a valid PK3 file", packfile);
602 Con_Printf("Added packfile %s (%i files)\n", packfile, real_nb_files);
609 PK3_GetTrueFileOffset
611 Find where the true file data offset is
614 qboolean PK3_GetTrueFileOffset (packfile_t *pfile, pack_t *pack)
616 qbyte buffer [ZIP_LOCAL_CHUNK_BASE_SIZE];
620 if (pfile->flags & PACKFILE_FLAG_TRUEOFFS)
623 // Load the local file description
624 lseek (pack->handle, pfile->offset, SEEK_SET);
625 count = read (pack->handle, buffer, ZIP_LOCAL_CHUNK_BASE_SIZE);
626 if (count != ZIP_LOCAL_CHUNK_BASE_SIZE || BuffBigLong (buffer) != ZIP_DATA_HEADER)
628 Con_Printf ("Can't retrieve file %s in package %s", pfile->name, pack->filename);
632 // Skip name and extra field
633 pfile->offset += BuffLittleShort (&buffer[26]) + BuffLittleShort (&buffer[28]) + ZIP_LOCAL_CHUNK_BASE_SIZE;
635 pfile->flags |= PACKFILE_FLAG_TRUEOFFS;
641 =============================================================================
643 OTHER PRIVATE FUNCTIONS
645 =============================================================================
653 Add a file to the list of files contained into a package
656 static packfile_t* FS_AddFileToPack (const char* name, pack_t* pack,
657 fs_offset_t offset, fs_offset_t packsize,
658 fs_offset_t realsize, packfile_flags_t flags)
660 int (*strcmp_funct) (const char* str1, const char* str2);
661 int left, right, middle;
664 strcmp_funct = pack->ignorecase ? strcasecmp : strcmp;
666 // Look for the slot we should put that file into (binary search)
668 right = pack->numfiles - 1;
669 while (left <= right)
673 middle = (left + right) / 2;
674 diff = strcmp_funct (pack->files[middle].name, name);
676 // If we found the file, there's a problem
678 Con_Printf ("Package %s contains the file %s several times\n", pack->filename, name);
680 // If we're too far in the list
687 // We have to move the right of the list by one slot to free the one we need
688 pfile = &pack->files[left];
689 memmove (pfile + 1, pfile, (pack->numfiles - left) * sizeof (*pfile));
692 strlcpy (pfile->name, name, sizeof (pfile->name));
693 pfile->offset = offset;
694 pfile->packsize = packsize;
695 pfile->realsize = realsize;
696 pfile->flags = flags;
706 Only used for FS_Open.
709 void FS_CreatePath (char *path)
713 for (ofs = path+1 ; *ofs ; ofs++)
715 if (*ofs == '/' || *ofs == '\\')
717 // create the directory
733 void FS_Path_f (void)
737 Con_Print("Current search path:\n");
738 for (s=fs_searchpaths ; s ; s=s->next)
741 Con_Printf("%s (%i files)\n", s->pack->filename, s->pack->numfiles);
743 Con_Printf("%s\n", s->filename);
752 Takes an explicit (not game tree related) path to a pak file.
754 Loads the header and directory, adding the files at the beginning
755 of the list so they override previous pack files.
758 pack_t *FS_LoadPackPAK (const char *packfile)
760 dpackheader_t header;
766 packhandle = open (packfile, O_RDONLY | O_BINARY);
769 read (packhandle, (void *)&header, sizeof(header));
770 if (memcmp(header.id, "PACK", 4))
772 Con_Printf ("%s is not a packfile", packfile);
776 header.dirofs = LittleLong (header.dirofs);
777 header.dirlen = LittleLong (header.dirlen);
779 if (header.dirlen % sizeof(dpackfile_t))
781 Con_Printf ("%s has an invalid directory size", packfile);
786 numpackfiles = header.dirlen / sizeof(dpackfile_t);
788 if (numpackfiles > MAX_FILES_IN_PACK)
790 Con_Printf ("%s has %i files", packfile, numpackfiles);
795 pack = Mem_Alloc(fs_mempool, sizeof (pack_t));
796 pack->ignorecase = false; // PAK is case sensitive
797 strlcpy (pack->filename, packfile, sizeof (pack->filename));
798 pack->handle = packhandle;
800 pack->files = Mem_Alloc(fs_mempool, numpackfiles * sizeof(packfile_t));
801 pack->next = packlist;
804 info = Mem_Alloc(tempmempool, sizeof(*info) * numpackfiles);
805 lseek (packhandle, header.dirofs, SEEK_SET);
806 read (packhandle, (void *)info, header.dirlen);
808 // parse the directory
809 for (i = 0;i < numpackfiles;i++)
811 fs_offset_t offset = LittleLong (info[i].filepos);
812 fs_offset_t size = LittleLong (info[i].filelen);
814 FS_AddFileToPack (info[i].name, pack, offset, size, size, PACKFILE_FLAG_TRUEOFFS);
819 Con_Printf("Added packfile %s (%i files)\n", packfile, numpackfiles);
828 Sets fs_gamedir, adds the directory to the head of the path,
829 then loads and adds pak1.pak pak2.pak ...
832 void FS_AddGameDirectory (const char *dir)
834 stringlist_t *list, *current;
835 searchpath_t *search;
837 char pakfile[MAX_OSPATH];
839 strlcpy (fs_gamedir, dir, sizeof (fs_gamedir));
841 list = listdirectory(dir);
843 // add any PAK package in the directory
844 for (current = list;current;current = current->next)
846 if (matchpattern(current->text, "*.pak", true))
848 dpsnprintf (pakfile, sizeof (pakfile), "%s%s", dir, current->text);
849 pak = FS_LoadPackPAK (pakfile);
852 search = Mem_Alloc(fs_mempool, sizeof(searchpath_t));
854 search->next = fs_searchpaths;
855 fs_searchpaths = search;
858 Con_Printf("unable to load pak \"%s\"\n", pakfile);
862 // add any PK3 package in the director
863 for (current = list;current;current = current->next)
865 if (matchpattern(current->text, "*.pk3", true))
867 dpsnprintf (pakfile, sizeof (pakfile), "%s%s", dir, current->text);
868 pak = FS_LoadPackPK3 (pakfile);
871 search = Mem_Alloc(fs_mempool, sizeof(searchpath_t));
873 search->next = fs_searchpaths;
874 fs_searchpaths = search;
877 Con_Printf("unable to load pak \"%s\"\n", pakfile);
882 // Add the directory to the search path
883 // (unpacked files have the priority over packed files)
884 search = Mem_Alloc(fs_mempool, sizeof(searchpath_t));
885 strlcpy (search->filename, dir, sizeof (search->filename));
886 search->next = fs_searchpaths;
887 fs_searchpaths = search;
896 void FS_AddGameHierarchy (const char *dir)
902 // Add the common game directory
903 FS_AddGameDirectory (va("%s/%s/", fs_basedir, dir));
906 // Add the personal game directory
907 homedir = getenv ("HOME");
908 if (homedir != NULL && homedir[0] != '\0')
909 FS_AddGameDirectory (va("%s/.%s/%s/", homedir, gameuserdirname, dir));
919 static const char *FS_FileExtension (const char *in)
921 const char *separator, *backslash, *colon, *dot;
923 separator = strrchr(in, '/');
924 backslash = strrchr(in, '\\');
925 if (separator < backslash)
926 separator = backslash;
927 colon = strrchr(in, ':');
928 if (separator < colon)
931 dot = strrchr(in, '.');
932 if (dot == NULL || dot < separator)
947 searchpath_t *search;
949 fs_mempool = Mem_AllocPool("file management", 0, NULL);
951 strcpy(fs_basedir, ".");
952 strcpy(fs_gamedir, "");
955 // FIXME: is there a better way to find the directory outside the .app?
956 if (strstr(com_argv[0], ".app/"))
960 split = strstr(com_argv[0], ".app/");
961 while (split > com_argv[0] && *split != '/')
963 strlcpy(fs_basedir, com_argv[0], sizeof(fs_basedir));
964 fs_basedir[split - com_argv[0]] = 0;
971 // Overrides the system supplied base directory (under GAMENAME)
972 // 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)
973 i = COM_CheckParm ("-basedir");
974 if (i && i < com_argc-1)
976 strlcpy (fs_basedir, com_argv[i+1], sizeof (fs_basedir));
977 i = (int)strlen (fs_basedir);
978 if (i > 0 && (fs_basedir[i-1] == '\\' || fs_basedir[i-1] == '/'))
982 // -path <dir or packfile> [<dir or packfile>] ...
983 // Fully specifies the exact search path, overriding the generated one
984 // 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)
985 i = COM_CheckParm ("-path");
989 while (++i < com_argc)
991 if (!com_argv[i] || com_argv[i][0] == '+' || com_argv[i][0] == '-')
994 search = Mem_Alloc(fs_mempool, sizeof(searchpath_t));
995 if (!strcasecmp (FS_FileExtension(com_argv[i]), "pak"))
997 search->pack = FS_LoadPackPAK (com_argv[i]);
1000 Con_Printf ("Couldn't load packfile: %s", com_argv[i]);
1005 else if (!strcasecmp (FS_FileExtension (com_argv[i]), "pk3"))
1007 search->pack = FS_LoadPackPK3 (com_argv[i]);
1010 Con_Printf ("Couldn't load packfile: %s", com_argv[i]);
1016 strlcpy (search->filename, com_argv[i], sizeof (search->filename));
1017 search->next = fs_searchpaths;
1018 fs_searchpaths = search;
1023 // add the game-specific paths
1024 // gamedirname1 (typically id1)
1025 FS_AddGameHierarchy (gamedirname1);
1027 // add the game-specific path, if any
1031 FS_AddGameHierarchy (gamedirname2);
1034 // set the com_modname (reported in server info)
1035 strlcpy(com_modname, gamedirname1, sizeof(com_modname));
1038 // Adds basedir/gamedir as an override game
1039 // LordHavoc: now supports multiple -game directories
1040 for (i = 1;i < com_argc;i++)
1044 if (!strcmp (com_argv[i], "-game") && i < com_argc-1)
1048 FS_AddGameHierarchy (com_argv[i]);
1049 // update the com_modname
1050 strlcpy (com_modname, com_argv[i], sizeof (com_modname));
1054 // If "-condebug" is in the command line, remove the previous log file
1055 if (COM_CheckParm ("-condebug") != 0)
1056 unlink (va("%s/qconsole.log", fs_gamedir));
1059 void FS_Init_Commands(void)
1061 Cvar_RegisterVariable (&scr_screenshot_name);
1063 Cmd_AddCommand ("path", FS_Path_f);
1064 Cmd_AddCommand ("dir", FS_Dir_f);
1065 Cmd_AddCommand ("ls", FS_Ls_f);
1067 // set the default screenshot name to either the mod name or the
1068 // gamemode screenshot name
1070 Cvar_SetQuick (&scr_screenshot_name, com_modname);
1072 Cvar_SetQuick (&scr_screenshot_name, gamescreenshotname);
1080 void FS_Shutdown (void)
1082 Mem_FreePool (&fs_mempool);
1086 ====================
1089 Internal function used to create a qfile_t and open the relevant non-packed file on disk
1090 ====================
1092 static qfile_t* FS_SysOpen (const char* filepath, const char* mode, qboolean nonblocking)
1098 // Parse the mode string
1107 opt = O_CREAT | O_TRUNC;
1111 opt = O_CREAT | O_APPEND;
1114 Con_Printf ("FS_SysOpen(%s, %s): invalid mode\n", filepath, mode);
1117 for (ind = 1; mode[ind] != '\0'; ind++)
1128 Con_Printf ("FS_SysOpen(%s, %s): unknown character in mode (%c)\n",
1129 filepath, mode, mode[ind]);
1136 file = Mem_Alloc (fs_mempool, sizeof (*file));
1137 memset (file, 0, sizeof (*file));
1140 file->handle = open (filepath, mod | opt, 0666);
1141 if (file->handle < 0)
1147 file->real_length = lseek (file->handle, 0, SEEK_END);
1149 // For files opened in append mode, we start at the end of the file
1151 file->position = file->real_length;
1153 lseek (file->handle, 0, SEEK_SET);
1163 Open a packed file using its package file descriptor
1166 qfile_t *FS_OpenPackedFile (pack_t* pack, int pack_ind)
1172 pfile = &pack->files[pack_ind];
1176 // If we don't have the true offset, get it now
1177 if (! (pfile->flags & PACKFILE_FLAG_TRUEOFFS))
1178 if (!PK3_GetTrueFileOffset (pfile, pack))
1181 // No Zlib DLL = no compressed files
1182 if (!zlib_dll && (pfile->flags & PACKFILE_FLAG_DEFLATED))
1184 Con_Printf("WARNING: can't open the compressed file %s\n"
1185 "You need the Zlib DLL to use compressed files\n",
1190 // LordHavoc: lseek affects all duplicates of a handle so we do it before
1191 // the dup() call to avoid having to close the dup_handle on error here
1192 if (lseek (pack->handle, pfile->offset, SEEK_SET) == -1)
1194 Con_Printf ("FS_OpenPackedFile: can't lseek to %s in %s (offset: %d)",
1195 pfile->name, pack->filename, pfile->offset);
1199 dup_handle = dup (pack->handle);
1202 Con_Printf ("FS_OpenPackedFile: can't dup package's handle (pack: %s)", pack->filename);
1206 file = Mem_Alloc (fs_mempool, sizeof (*file));
1207 memset (file, 0, sizeof (*file));
1208 file->handle = dup_handle;
1209 file->flags = QFILE_FLAG_PACKED;
1210 file->real_length = pfile->realsize;
1211 file->offset = pfile->offset;
1215 if (pfile->flags & PACKFILE_FLAG_DEFLATED)
1219 file->flags |= QFILE_FLAG_DEFLATED;
1221 // We need some more variables
1222 ztk = Mem_Alloc (fs_mempool, sizeof (*ztk));
1224 ztk->comp_length = pfile->packsize;
1226 // Initialize zlib stream
1227 ztk->zstream.next_in = ztk->input;
1228 ztk->zstream.avail_in = 0;
1230 /* From Zlib's "unzip.c":
1232 * windowBits is passed < 0 to tell that there is no zlib header.
1233 * Note that in this case inflate *requires* an extra "dummy" byte
1234 * after the compressed stream in order to complete decompression and
1235 * return Z_STREAM_END.
1236 * In unzip, i don't wait absolutely Z_STREAM_END because I known the
1237 * size of both compressed and uncompressed data
1239 if (qz_inflateInit2 (&ztk->zstream, -MAX_WBITS) != Z_OK)
1241 Con_Printf ("FS_OpenPackedFile: inflate init error (file: %s)", pfile->name);
1247 ztk->zstream.next_out = file->buff;
1248 ztk->zstream.avail_out = sizeof (file->buff);
1253 fs_filesize = pfile->realsize;
1259 ====================
1262 Return true if the path should be rejected due to one of the following:
1263 1: path elements that are non-portable
1264 2: path elements that would allow access to files outside the game directory,
1265 or are just not a good idea for a mod to be using.
1266 ====================
1268 int FS_CheckNastyPath (const char *path)
1270 // Windows: don't allow \ in filenames (windows-only), period.
1271 // (on Windows \ is a directory separator, but / is also supported)
1272 if (strstr(path, "\\"))
1273 return 1; // non-portable
1275 // Mac: don't allow Mac-only filenames - : is a directory separator
1276 // instead of /, but we rely on / working already, so there's no reason to
1277 // support a Mac-only path
1278 // Amiga and Windows: : tries to go to root of drive
1279 if (strstr(path, ":"))
1280 return 1; // non-portable attempt to go to root of drive
1282 // Amiga: // is parent directory
1283 if (strstr(path, "//"))
1284 return 1; // non-portable attempt to go to parent directory
1286 // all: don't allow going to current directory (./) or parent directory (../ or /../)
1287 if (strstr(path, "./"))
1288 return 2; // attempt to go outside the game directory
1290 // Windows and UNIXes: don't allow absolute paths
1292 return 2; // attempt to go outside the game directory
1294 // after all these checks we're pretty sure it's a / separated filename
1295 // and won't do much if any harm
1301 ====================
1304 Look for a file in the packages and in the filesystem
1306 Return the searchpath where the file was found (or NULL)
1307 and the file index in the package if relevant
1308 ====================
1310 static searchpath_t *FS_FindFile (const char *name, int* index, qboolean quiet)
1312 searchpath_t *search;
1315 // search through the path, one element at a time
1316 for (search = fs_searchpaths;search;search = search->next)
1318 // is the element a pak file?
1321 int (*strcmp_funct) (const char* str1, const char* str2);
1322 int left, right, middle;
1325 strcmp_funct = pak->ignorecase ? strcasecmp : strcmp;
1327 // Look for the file (binary search)
1329 right = pak->numfiles - 1;
1330 while (left <= right)
1334 middle = (left + right) / 2;
1335 diff = strcmp_funct (pak->files[middle].name, name);
1341 Con_DPrintf("FS_FindFile: %s in %s\n",
1342 pak->files[middle].name, pak->filename);
1349 // If we're too far in the list
1358 char netpath[MAX_OSPATH];
1359 dpsnprintf(netpath, sizeof(netpath), "%s%s", search->filename, name);
1360 if (FS_SysFileExists (netpath))
1363 Con_DPrintf("FS_FindFile: %s\n", netpath);
1373 Con_DPrintf("FS_FindFile: can't find %s\n", name);
1385 Look for a file in the search paths and open it in read-only mode
1390 qfile_t *FS_OpenReadFile (const char *filename, qboolean quiet, qboolean nonblocking)
1392 searchpath_t *search;
1395 search = FS_FindFile (filename, &pack_ind, quiet);
1404 // Found in the filesystem?
1407 char path [MAX_OSPATH];
1408 dpsnprintf (path, sizeof (path), "%s%s", search->filename, filename);
1409 return FS_SysOpen (path, "rb", nonblocking);
1412 // So, we found it in a package...
1413 return FS_OpenPackedFile (search->pack, pack_ind);
1418 =============================================================================
1420 MAIN PUBLIC FUNCTIONS
1422 =============================================================================
1426 ====================
1429 Open a file. The syntax is the same as fopen
1430 ====================
1432 qfile_t* FS_Open (const char* filepath, const char* mode, qboolean quiet, qboolean nonblocking)
1436 if (FS_CheckNastyPath(filepath))
1438 Con_Printf("FS_Open(\"%s\", \"%s\", %s): nasty filename rejected\n", filepath, mode, quiet ? "true" : "false");
1442 // If the file is opened in "write", "append", or "read/write" mode
1443 if (mode[0] == 'w' || mode[0] == 'a' || strchr (mode, '+'))
1445 char real_path [MAX_OSPATH];
1447 // Open the file on disk directly
1448 dpsnprintf (real_path, sizeof (real_path), "%s%s", fs_gamedir, filepath);
1450 // Create directories up to the file
1451 FS_CreatePath (real_path);
1453 return FS_SysOpen (real_path, mode, nonblocking);
1456 // Else, we look at the various search paths and open the file in read-only mode
1457 file = FS_OpenReadFile (filepath, quiet, nonblocking);
1459 fs_filesize = file->real_length;
1466 ====================
1470 ====================
1472 int FS_Close (qfile_t* file)
1474 if (close (file->handle))
1479 qz_inflateEnd (&file->ztk->zstream);
1480 Mem_Free (file->ztk);
1489 ====================
1492 Write "datasize" bytes into a file
1493 ====================
1495 fs_offset_t FS_Write (qfile_t* file, const void* data, size_t datasize)
1499 // If necessary, seek to the exact file position we're supposed to be
1500 if (file->buff_ind != file->buff_len)
1501 lseek (file->handle, file->buff_ind - file->buff_len, SEEK_CUR);
1503 // Purge cached data
1506 // Write the buffer and update the position
1507 result = write (file->handle, data, (fs_offset_t)datasize);
1508 file->position = lseek (file->handle, 0, SEEK_CUR);
1509 if (file->real_length < file->position)
1510 file->real_length = file->position;
1520 ====================
1523 Read up to "buffersize" bytes from a file
1524 ====================
1526 fs_offset_t FS_Read (qfile_t* file, void* buffer, size_t buffersize)
1528 fs_offset_t count, done;
1530 if (buffersize == 0)
1533 // Get rid of the ungetc character
1534 if (file->ungetc != EOF)
1536 ((char*)buffer)[0] = file->ungetc;
1544 // First, we copy as many bytes as we can from "buff"
1545 if (file->buff_ind < file->buff_len)
1547 count = file->buff_len - file->buff_ind;
1549 done += ((fs_offset_t)buffersize > count) ? count : (fs_offset_t)buffersize;
1550 memcpy (buffer, &file->buff[file->buff_ind], done);
1551 file->buff_ind += done;
1554 if (buffersize == 0)
1558 // NOTE: at this point, the read buffer is always empty
1560 // If the file isn't compressed
1561 if (! (file->flags & QFILE_FLAG_DEFLATED))
1565 // We must take care to not read after the end of the file
1566 count = file->real_length - file->position;
1568 // If we have a lot of data to get, put them directly into "buffer"
1569 if (buffersize > sizeof (file->buff) / 2)
1571 if (count > (fs_offset_t)buffersize)
1572 count = (fs_offset_t)buffersize;
1573 lseek (file->handle, file->offset + file->position, SEEK_SET);
1574 nb = read (file->handle, &((qbyte*)buffer)[done], count);
1578 file->position += nb;
1580 // Purge cached data
1586 if (count > (fs_offset_t)sizeof (file->buff))
1587 count = (fs_offset_t)sizeof (file->buff);
1588 lseek (file->handle, file->offset + file->position, SEEK_SET);
1589 nb = read (file->handle, file->buff, count);
1592 file->buff_len = nb;
1593 file->position += nb;
1595 // Copy the requested data in "buffer" (as much as we can)
1596 count = (fs_offset_t)buffersize > file->buff_len ? file->buff_len : (fs_offset_t)buffersize;
1597 memcpy (&((qbyte*)buffer)[done], file->buff, count);
1598 file->buff_ind = count;
1606 // If the file is compressed, it's more complicated...
1607 // We cycle through a few operations until we have read enough data
1608 while (buffersize > 0)
1610 ztoolkit_t *ztk = file->ztk;
1613 // NOTE: at this point, the read buffer is always empty
1615 // If "input" is also empty, we need to refill it
1616 if (ztk->in_ind == ztk->in_len)
1618 // If we are at the end of the file
1619 if (file->position == file->real_length)
1622 count = (fs_offset_t)(ztk->comp_length - ztk->in_position);
1623 if (count > (fs_offset_t)sizeof (ztk->input))
1624 count = (fs_offset_t)sizeof (ztk->input);
1625 lseek (file->handle, file->offset + (fs_offset_t)ztk->in_position, SEEK_SET);
1626 if (read (file->handle, ztk->input, count) != count)
1628 Con_Printf ("FS_Read: unexpected end of file");
1633 ztk->in_len = count;
1634 ztk->in_position += count;
1637 ztk->zstream.next_in = &ztk->input[ztk->in_ind];
1638 ztk->zstream.avail_in = (unsigned int)(ztk->in_len - ztk->in_ind);
1640 // Now that we are sure we have compressed data available, we need to determine
1641 // if it's better to inflate it in "file->buff" or directly in "buffer"
1643 // Inflate the data in "file->buff"
1644 if (buffersize < sizeof (file->buff) / 2)
1646 ztk->zstream.next_out = file->buff;
1647 ztk->zstream.avail_out = sizeof (file->buff);
1648 error = qz_inflate (&ztk->zstream, Z_SYNC_FLUSH);
1649 if (error != Z_OK && error != Z_STREAM_END)
1651 Con_Printf ("FS_Read: Can't inflate file");
1654 ztk->in_ind = ztk->in_len - ztk->zstream.avail_in;
1656 file->buff_len = (fs_offset_t)sizeof (file->buff) - ztk->zstream.avail_out;
1657 file->position += file->buff_len;
1659 // Copy the requested data in "buffer" (as much as we can)
1660 count = (fs_offset_t)buffersize > file->buff_len ? file->buff_len : (fs_offset_t)buffersize;
1661 memcpy (&((qbyte*)buffer)[done], file->buff, count);
1662 file->buff_ind = count;
1665 // Else, we inflate directly in "buffer"
1668 ztk->zstream.next_out = &((qbyte*)buffer)[done];
1669 ztk->zstream.avail_out = (unsigned int)buffersize;
1670 error = qz_inflate (&ztk->zstream, Z_SYNC_FLUSH);
1671 if (error != Z_OK && error != Z_STREAM_END)
1673 Con_Printf ("FS_Read: Can't inflate file");
1676 ztk->in_ind = ztk->in_len - ztk->zstream.avail_in;
1678 // How much data did it inflate?
1679 count = (fs_offset_t)(buffersize - ztk->zstream.avail_out);
1680 file->position += count;
1682 // Purge cached data
1687 buffersize -= count;
1695 ====================
1698 Print a string into a file
1699 ====================
1701 int FS_Print (qfile_t* file, const char *msg)
1703 return (int)FS_Write (file, msg, strlen (msg));
1707 ====================
1710 Print a string into a file
1711 ====================
1713 int FS_Printf(qfile_t* file, const char* format, ...)
1718 va_start (args, format);
1719 result = FS_VPrintf (file, format, args);
1727 ====================
1730 Print a string into a file
1731 ====================
1733 int FS_VPrintf (qfile_t* file, const char* format, va_list ap)
1736 fs_offset_t buff_size;
1737 char *tempbuff = NULL;
1740 tempbuff = Mem_Alloc (tempmempool, buff_size);
1741 len = dpvsnprintf (tempbuff, buff_size, format, ap);
1744 Mem_Free (tempbuff);
1746 tempbuff = Mem_Alloc (tempmempool, buff_size);
1747 len = dpvsnprintf (tempbuff, buff_size, format, ap);
1750 len = write (file->handle, tempbuff, len);
1751 Mem_Free (tempbuff);
1758 ====================
1761 Get the next character of a file
1762 ====================
1764 int FS_Getc (qfile_t* file)
1768 if (FS_Read (file, &c, 1) != 1)
1776 ====================
1779 Put a character back into the read buffer (only supports one character!)
1780 ====================
1782 int FS_UnGetc (qfile_t* file, unsigned char c)
1784 // If there's already a character waiting to be read
1785 if (file->ungetc != EOF)
1794 ====================
1797 Move the position index in a file
1798 ====================
1800 int FS_Seek (qfile_t* file, fs_offset_t offset, int whence)
1804 fs_offset_t buffersize;
1806 // Compute the file offset
1810 offset += file->position - file->buff_len + file->buff_ind;
1817 offset += file->real_length;
1823 if (offset < 0 || offset > (long) file->real_length)
1826 // If we have the data in our read buffer, we don't need to actually seek
1827 if (file->position - file->buff_len <= offset && offset <= file->position)
1829 file->buff_ind = offset + file->buff_len - file->position;
1833 // Purge cached data
1836 // Unpacked or uncompressed files can seek directly
1837 if (! (file->flags & QFILE_FLAG_DEFLATED))
1839 if (lseek (file->handle, file->offset + offset, SEEK_SET) == -1)
1841 file->position = offset;
1845 // Seeking in compressed files is more a hack than anything else,
1846 // but we need to support it, so here we go.
1849 // If we have to go back in the file, we need to restart from the beginning
1850 if (offset <= file->position)
1854 ztk->in_position = 0;
1856 lseek (file->handle, file->offset, SEEK_SET);
1858 // Reset the Zlib stream
1859 ztk->zstream.next_in = ztk->input;
1860 ztk->zstream.avail_in = 0;
1861 qz_inflateReset (&ztk->zstream);
1864 // We need a big buffer to force inflating into it directly
1865 buffersize = 2 * sizeof (file->buff);
1866 buffer = Mem_Alloc (tempmempool, buffersize);
1868 // Skip all data until we reach the requested offset
1869 while (offset > file->position)
1871 fs_offset_t diff = offset - file->position;
1872 fs_offset_t count, len;
1874 count = (diff > buffersize) ? buffersize : diff;
1875 len = FS_Read (file, buffer, count);
1889 ====================
1892 Give the current position in a file
1893 ====================
1895 fs_offset_t FS_Tell (qfile_t* file)
1897 return file->position - file->buff_len + file->buff_ind;
1902 ====================
1905 Erases any buffered input or output data
1906 ====================
1908 void FS_Purge (qfile_t* file)
1920 Filename are relative to the quake directory.
1921 Always appends a 0 byte.
1924 qbyte *FS_LoadFile (const char *path, mempool_t *pool, qboolean quiet)
1929 file = FS_Open (path, "rb", quiet, false);
1933 buf = Mem_Alloc (pool, fs_filesize + 1);
1934 buf[fs_filesize] = '\0';
1936 FS_Read (file, buf, fs_filesize);
1947 The filename will be prefixed by the current game directory
1950 qboolean FS_WriteFile (const char *filename, void *data, fs_offset_t len)
1954 file = FS_Open (filename, "wb", false, false);
1957 Con_Printf("FS_WriteFile: failed on %s\n", filename);
1961 Con_DPrintf("FS_WriteFile: %s\n", filename);
1962 FS_Write (file, data, len);
1969 =============================================================================
1971 OTHERS PUBLIC FUNCTIONS
1973 =============================================================================
1981 void FS_StripExtension (const char *in, char *out, size_t size_out)
1988 while (*in && size_out > 1)
1992 else if (*in == '/' || *in == '\\' || *in == ':')
2009 void FS_DefaultExtension (char *path, const char *extension, size_t size_path)
2013 // if path doesn't have a .EXT, append extension
2014 // (extension should include the .)
2015 src = path + strlen(path) - 1;
2017 while (*src != '/' && src != path)
2020 return; // it has an extension
2024 strlcat (path, extension, size_path);
2032 Look for a file in the packages and in the filesystem
2035 qboolean FS_FileExists (const char *filename)
2037 return (FS_FindFile (filename, NULL, true) != NULL);
2045 Look for a file in the filesystem only
2048 qboolean FS_SysFileExists (const char *path)
2053 // TODO: use another function instead, to avoid opening the file
2054 desc = open (path, O_RDONLY | O_BINARY);
2063 if (stat (path,&buf) == -1)
2070 void FS_mkdir (const char *path)
2083 Allocate and fill a search structure with information on matching filenames.
2086 fssearch_t *FS_Search(const char *pattern, int caseinsensitive, int quiet)
2089 searchpath_t *searchpath;
2091 int i, basepathlength, numfiles, numchars;
2092 stringlist_t *dir, *dirfile, *liststart, *listcurrent, *listtemp;
2093 const char *slash, *backslash, *colon, *separator;
2095 char netpath[MAX_OSPATH];
2096 char temp[MAX_OSPATH];
2098 for (i = 0;pattern[i] == '.' || pattern[i] == ':' || pattern[i] == '/' || pattern[i] == '\\';i++)
2103 Con_Printf("Don't use punctuation at the beginning of a search pattern!\n");
2111 slash = strrchr(pattern, '/');
2112 backslash = strrchr(pattern, '\\');
2113 colon = strrchr(pattern, ':');
2114 separator = max(slash, backslash);
2115 separator = max(separator, colon);
2116 basepathlength = separator ? (separator + 1 - pattern) : 0;
2117 basepath = Mem_Alloc (tempmempool, basepathlength + 1);
2119 memcpy(basepath, pattern, basepathlength);
2120 basepath[basepathlength] = 0;
2122 // search through the path, one element at a time
2123 for (searchpath = fs_searchpaths;searchpath;searchpath = searchpath->next)
2125 // is the element a pak file?
2126 if (searchpath->pack)
2128 // look through all the pak file elements
2129 pak = searchpath->pack;
2130 for (i = 0;i < pak->numfiles;i++)
2132 strcpy(temp, pak->files[i].name);
2135 if (matchpattern(temp, (char *)pattern, true))
2137 for (listtemp = liststart;listtemp;listtemp = listtemp->next)
2138 if (!strcmp(listtemp->text, temp))
2140 if (listtemp == NULL)
2142 listcurrent = stringlistappend(listcurrent, temp);
2143 if (liststart == NULL)
2144 liststart = listcurrent;
2146 Con_DPrintf("SearchPackFile: %s : %s\n", pak->filename, temp);
2149 // strip off one path element at a time until empty
2150 // this way directories are added to the listing if they match the pattern
2151 slash = strrchr(temp, '/');
2152 backslash = strrchr(temp, '\\');
2153 colon = strrchr(temp, ':');
2155 if (separator < slash)
2157 if (separator < backslash)
2158 separator = backslash;
2159 if (separator < colon)
2161 *((char *)separator) = 0;
2167 // get a directory listing and look at each name
2168 dpsnprintf(netpath, sizeof (netpath), "%s%s", searchpath->filename, basepath);
2169 if ((dir = listdirectory(netpath)))
2171 for (dirfile = dir;dirfile;dirfile = dirfile->next)
2173 dpsnprintf(temp, sizeof(temp), "%s%s", basepath, dirfile->text);
2174 if (matchpattern(temp, (char *)pattern, true))
2176 for (listtemp = liststart;listtemp;listtemp = listtemp->next)
2177 if (!strcmp(listtemp->text, temp))
2179 if (listtemp == NULL)
2181 listcurrent = stringlistappend(listcurrent, temp);
2182 if (liststart == NULL)
2183 liststart = listcurrent;
2185 Con_DPrintf("SearchDirFile: %s\n", temp);
2196 liststart = stringlistsort(liststart);
2199 for (listtemp = liststart;listtemp;listtemp = listtemp->next)
2202 numchars += (int)strlen(listtemp->text) + 1;
2204 search = Z_Malloc(sizeof(fssearch_t) + numchars + numfiles * sizeof(char *));
2205 search->filenames = (char **)((char *)search + sizeof(fssearch_t));
2206 search->filenamesbuffer = (char *)((char *)search + sizeof(fssearch_t) + numfiles * sizeof(char *));
2207 search->numfilenames = (int)numfiles;
2210 for (listtemp = liststart;listtemp;listtemp = listtemp->next)
2212 search->filenames[numfiles] = search->filenamesbuffer + numchars;
2213 strcpy(search->filenames[numfiles], listtemp->text);
2215 numchars += (int)strlen(listtemp->text) + 1;
2218 stringlistfree(liststart);
2225 void FS_FreeSearch(fssearch_t *search)
2230 extern int con_linewidth;
2231 int FS_ListDirectory(const char *pattern, int oneperline)
2242 search = FS_Search(pattern, true, true);
2245 numfiles = search->numfilenames;
2248 // FIXME: the names could be added to one column list and then
2249 // gradually shifted into the next column if they fit, and then the
2250 // next to make a compact variable width listing but it's a lot more
2252 // find width for columns
2254 for (i = 0;i < numfiles;i++)
2256 l = (int)strlen(search->filenames[i]);
2257 if (columnwidth < l)
2260 // count the spacing character
2262 // calculate number of columns
2263 numcolumns = con_linewidth / columnwidth;
2264 // don't bother with the column printing if it's only one column
2265 if (numcolumns >= 2)
2267 numlines = (numfiles + numcolumns - 1) / numcolumns;
2268 for (i = 0;i < numlines;i++)
2271 for (k = 0;k < numcolumns;k++)
2273 l = i * numcolumns + k;
2276 name = search->filenames[l];
2277 for (j = 0;name[j] && linebufpos + 1 < (int)sizeof(linebuf);j++)
2278 linebuf[linebufpos++] = name[j];
2279 // space out name unless it's the last on the line
2280 if (k + 1 < numcolumns && l + 1 < numfiles)
2281 for (;j < columnwidth && linebufpos + 1 < (int)sizeof(linebuf);j++)
2282 linebuf[linebufpos++] = ' ';
2285 linebuf[linebufpos] = 0;
2286 Con_Printf("%s\n", linebuf);
2293 for (i = 0;i < numfiles;i++)
2294 Con_Printf("%s\n", search->filenames[i]);
2295 FS_FreeSearch(search);
2296 return (int)numfiles;
2299 static void FS_ListDirectoryCmd (const char* cmdname, int oneperline)
2301 const char *pattern;
2304 Con_Printf("usage:\n%s [path/pattern]\n", cmdname);
2307 if (Cmd_Argc() == 2)
2308 pattern = Cmd_Argv(1);
2311 if (!FS_ListDirectory(pattern, oneperline))
2312 Con_Print("No files found.\n");
2317 FS_ListDirectoryCmd("dir", true);
2322 FS_ListDirectoryCmd("ls", false);