4 Copyright (C) 2003 Mathieu Olivier
5 Copyright (C) 1999,2000 contributors of the QuakeForge project
7 This program is free software; you can redistribute it and/or
8 modify it under the terms of the GNU General Public License
9 as published by the Free Software Foundation; either version 2
10 of the License, or (at your option) any later version.
12 This program is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
16 See the GNU General Public License for more details.
18 You should have received a copy of the GNU General Public License
19 along with this program; if not, write to:
21 Free Software Foundation, Inc.
22 59 Temple Place - Suite 330
23 Boston, MA 02111-1307, USA
36 # include <sys/stat.h>
46 // use syscalls instead of f* functions
47 #define FS_USESYSCALLS
49 // Win32 requires us to add O_BINARY, but the other OSes don't have it
59 All of Quake's data access is through a hierchal file system, but the contents
60 of the file system can be transparently merged from several sources.
62 The "base directory" is the path to the directory holding the quake.exe and
63 all game directories. The sys_* files pass this to host_init in
64 quakeparms_t->basedir. This can be overridden with the "-basedir" command
65 line parm to allow code debugging in a different directory. The base
66 directory is only used during filesystem initialization.
68 The "game directory" is the first tree on the search path and directory that
69 all generated files (savegames, screenshots, demos, config files) will be
70 saved to. This can be overridden with the "-game" command line parameter.
71 The game directory can never be changed while quake is executing. This is a
72 precacution against having a malicious server instruct clients to write files
73 over areas they shouldn't.
79 =============================================================================
83 =============================================================================
86 // Magic numbers of a ZIP file (big-endian format)
87 #define ZIP_DATA_HEADER 0x504B0304 // "PK\3\4"
88 #define ZIP_CDIR_HEADER 0x504B0102 // "PK\1\2"
89 #define ZIP_END_HEADER 0x504B0506 // "PK\5\6"
91 // Other constants for ZIP files
92 #define ZIP_MAX_COMMENTS_SIZE ((unsigned short)0xFFFF)
93 #define ZIP_END_CDIR_SIZE 22
94 #define ZIP_CDIR_CHUNK_BASE_SIZE 46
95 #define ZIP_LOCAL_CHUNK_BASE_SIZE 30
97 // Zlib constants (from zlib.h)
98 #define Z_SYNC_FLUSH 2
101 #define Z_STREAM_END 1
102 #define ZLIB_VERSION "1.1.4"
106 =============================================================================
110 =============================================================================
113 // Zlib stream (from zlib.h)
114 // Warning: some pointers we don't use directly have
115 // been cast to "void*" for a matter of simplicity
118 qbyte *next_in; // next input byte
119 unsigned int avail_in; // number of bytes available at next_in
120 unsigned long total_in; // total nb of input bytes read so far
122 qbyte *next_out; // next output byte should be put there
123 unsigned int avail_out; // remaining free space at next_out
124 unsigned long total_out; // total nb of bytes output so far
126 char *msg; // last error message, NULL if no error
127 void *state; // not visible by applications
129 void *zalloc; // used to allocate the internal state
130 void *zfree; // used to free the internal state
131 void *opaque; // private data object passed to zalloc and zfree
133 int data_type; // best guess about the data type: ascii or binary
134 unsigned long adler; // adler32 value of the uncompressed data
135 unsigned long reserved; // reserved for future use
139 // Our own file structure on top of FILE
143 FS_FLAG_PACKED = (1 << 0), // inside a package (PAK or PK3)
144 FS_FLAG_DEFLATED = (1 << 1) // file is compressed using the deflate algorithm (PK3 only)
147 #define ZBUFF_SIZE 1024
151 size_t real_length; // length of the uncompressed file
152 size_t in_ind, in_max; // input buffer index and counter
153 size_t in_position; // position in the compressed file
154 size_t out_ind, out_max; // output buffer index and counter
155 size_t out_position; // how many bytes did we uncompress until now?
156 qbyte input [ZBUFF_SIZE];
157 qbyte output [ZBUFF_SIZE];
163 #ifdef FS_USESYSCALLS
168 size_t length; // file size on disk (PACKED only)
169 size_t offset; // offset into a package (PACKED only)
170 size_t position; // current position in the file (PACKED only)
171 ztoolkit_t* z; // used for inflating (DEFLATED only)
175 // ------ PK3 files on disk ------ //
177 // You can get the complete ZIP format description from PKWARE website
181 unsigned int signature;
182 unsigned short disknum;
183 unsigned short cdir_disknum; // number of the disk with the start of the central directory
184 unsigned short localentries; // number of entries in the central directory on this disk
185 unsigned short nbentries; // total number of entries in the central directory on this disk
186 unsigned int cdir_size; // size of the central directory
187 unsigned int cdir_offset; // with respect to the starting disk number
188 unsigned short comment_size;
189 } pk3_endOfCentralDir_t;
192 // ------ PAK files on disk ------ //
196 int filepos, filelen;
207 // Packages in memory
211 FILE_FLAG_TRUEOFFS = (1 << 0), // the offset in packfile_t is the true contents offset
212 FILE_FLAG_DEFLATED = (1 << 1) // file compressed using the deflate algorithm
217 char name [MAX_QPATH];
220 size_t packsize; // size in the package
221 size_t realsize; // real file size (uncompressed)
224 typedef struct pack_s
226 char filename [MAX_OSPATH];
227 #ifdef FS_USESYSCALLS
232 int ignorecase; // PK3 ignores case
240 // Search paths for files (including packages)
241 typedef struct searchpath_s
243 // only one of filename / pack will be used
244 char filename[MAX_OSPATH];
246 struct searchpath_s *next;
251 =============================================================================
255 =============================================================================
261 static packfile_t* FS_AddFileToPack (const char* name, pack_t* pack,
262 size_t offset, size_t packsize,
263 size_t realsize, file_flags_t flags);
267 =============================================================================
271 =============================================================================
274 mempool_t *fs_mempool;
275 mempool_t *pak_mempool;
279 pack_t *packlist = NULL;
281 searchpath_t *fs_searchpaths = NULL;
283 #define MAX_FILES_IN_PACK 65536
285 char fs_gamedir[MAX_OSPATH];
286 char fs_basedir[MAX_OSPATH];
288 qboolean fs_modified; // set true if using non-id files
292 =============================================================================
294 PRIVATE FUNCTIONS - PK3 HANDLING
296 =============================================================================
299 // Functions exported from zlib
301 # define ZEXPORT WINAPI
306 static int (ZEXPORT *qz_inflate) (z_stream* strm, int flush);
307 static int (ZEXPORT *qz_inflateEnd) (z_stream* strm);
308 static int (ZEXPORT *qz_inflateInit2_) (z_stream* strm, int windowBits, const char *version, int stream_size);
309 static int (ZEXPORT *qz_inflateReset) (z_stream* strm);
311 #define qz_inflateInit2(strm, windowBits) \
312 qz_inflateInit2_((strm), (windowBits), ZLIB_VERSION, sizeof(z_stream))
314 static dllfunction_t zlibfuncs[] =
316 {"inflate", (void **) &qz_inflate},
317 {"inflateEnd", (void **) &qz_inflateEnd},
318 {"inflateInit2_", (void **) &qz_inflateInit2_},
319 {"inflateReset", (void **) &qz_inflateReset},
323 // Handle for Zlib DLL
324 static dllhandle_t zlib_dll = NULL;
334 void PK3_CloseLibrary (void)
336 Sys_UnloadLibrary (&zlib_dll);
344 Try to load the Zlib DLL
347 qboolean PK3_OpenLibrary (void)
356 dllname = "zlib.dll";
362 if (! Sys_LoadLibrary (dllname, &zlib_dll, zlibfuncs))
364 Con_Printf ("Compressed files support disabled\n");
368 Con_Printf ("Compressed files support enabled\n");
375 PK3_GetEndOfCentralDir
377 Extract the end of the central directory from a PK3 package
380 #ifdef FS_USESYSCALLS
381 qboolean PK3_GetEndOfCentralDir (const char *packfile, int packhandle, pk3_endOfCentralDir_t *eocd)
383 qboolean PK3_GetEndOfCentralDir (const char *packfile, FILE *packhandle, pk3_endOfCentralDir_t *eocd)
386 long filesize, maxsize;
390 // Get the package size
391 #ifdef FS_USESYSCALLS
392 filesize = lseek (packhandle, 0, SEEK_END);
394 fseek (packhandle, 0, SEEK_END);
395 filesize = ftell(packhandle);
397 if (filesize < ZIP_END_CDIR_SIZE)
400 // Load the end of the file in memory
401 if (filesize < ZIP_MAX_COMMENTS_SIZE + ZIP_END_CDIR_SIZE)
404 maxsize = ZIP_MAX_COMMENTS_SIZE + ZIP_END_CDIR_SIZE;
405 buffer = Mem_Alloc (tempmempool, maxsize);
406 #ifdef FS_USESYSCALLS
407 lseek (packhandle, filesize - maxsize, SEEK_SET);
408 if (read (packhandle, buffer, maxsize) != (ssize_t) maxsize)
410 fseek (packhandle, filesize - maxsize, SEEK_SET);
411 if (fread (buffer, 1, maxsize, packhandle) != (size_t) maxsize)
418 // Look for the end of central dir signature around the end of the file
419 maxsize -= ZIP_END_CDIR_SIZE;
420 ptr = &buffer[maxsize];
422 while (BuffBigLong (ptr) != ZIP_END_HEADER)
434 memcpy (eocd, ptr, ZIP_END_CDIR_SIZE);
435 eocd->signature = LittleLong (eocd->signature);
436 eocd->disknum = LittleShort (eocd->disknum);
437 eocd->cdir_disknum = LittleShort (eocd->cdir_disknum);
438 eocd->localentries = LittleShort (eocd->localentries);
439 eocd->nbentries = LittleShort (eocd->nbentries);
440 eocd->cdir_size = LittleLong (eocd->cdir_size);
441 eocd->cdir_offset = LittleLong (eocd->cdir_offset);
442 eocd->comment_size = LittleShort (eocd->comment_size);
454 Extract the file list from a PK3 file
457 int PK3_BuildFileList (pack_t *pack, const pk3_endOfCentralDir_t *eocd)
459 qbyte *central_dir, *ptr;
463 // Load the central directory in memory
464 central_dir = Mem_Alloc (tempmempool, eocd->cdir_size);
465 #ifdef FS_USESYSCALLS
466 lseek (pack->handle, eocd->cdir_offset, SEEK_SET);
467 read (pack->handle, central_dir, eocd->cdir_size);
469 fseek (pack->handle, eocd->cdir_offset, SEEK_SET);
470 fread (central_dir, 1, eocd->cdir_size, pack->handle);
473 // Extract the files properties
474 // The parsing is done "by hand" because some fields have variable sizes and
475 // the constant part isn't 4-bytes aligned, which makes the use of structs difficult
476 remaining = eocd->cdir_size;
479 for (ind = 0; ind < eocd->nbentries; ind++)
481 size_t namesize, count;
483 // Checking the remaining size
484 if (remaining < ZIP_CDIR_CHUNK_BASE_SIZE)
486 Mem_Free (central_dir);
489 remaining -= ZIP_CDIR_CHUNK_BASE_SIZE;
492 if (BuffBigLong (ptr) != ZIP_CDIR_HEADER)
494 Mem_Free (central_dir);
498 namesize = BuffLittleShort (&ptr[28]); // filename length
500 // Check encryption, compression, and attributes
501 // 1st uint8 : general purpose bit flag
502 // Check bits 0 (encryption), 3 (data descriptor after the file), and 5 (compressed patched data (?))
503 // 2nd uint8 : external file attributes
504 // Check bits 3 (file is a directory) and 5 (file is a volume (?))
505 if ((ptr[8] & 0x29) == 0 && (ptr[38] & 0x18) == 0)
507 // Still enough bytes for the name?
508 if ((size_t) remaining < namesize || namesize >= sizeof (*pack->files))
510 Mem_Free (central_dir);
514 // WinZip doesn't use the "directory" attribute, so we need to check the name directly
515 if (ptr[ZIP_CDIR_CHUNK_BASE_SIZE + namesize - 1] != '/')
517 char filename [sizeof (pack->files[0].name)];
518 size_t offset, packsize, realsize;
521 // Extract the name (strip it if necessary)
522 if (namesize >= sizeof (filename))
523 namesize = sizeof (filename) - 1;
524 memcpy (filename, &ptr[ZIP_CDIR_CHUNK_BASE_SIZE], namesize);
525 filename[namesize] = '\0';
527 if (BuffLittleShort (&ptr[10]))
528 flags = FILE_FLAG_DEFLATED;
531 offset = BuffLittleLong (&ptr[42]);
532 packsize = BuffLittleLong (&ptr[20]);
533 realsize = BuffLittleLong (&ptr[24]);
534 FS_AddFileToPack (filename, pack, offset, packsize, realsize, flags);
538 // Skip the name, additionnal field, and comment
539 // 1er uint16 : extra field length
540 // 2eme uint16 : file comment length
541 count = namesize + BuffLittleShort (&ptr[30]) + BuffLittleShort (&ptr[32]);
542 ptr += ZIP_CDIR_CHUNK_BASE_SIZE + count;
546 Mem_Free (central_dir);
547 return pack->numfiles;
555 Create a package entry associated with a PK3 file
558 pack_t *FS_LoadPackPK3 (const char *packfile)
560 #ifdef FS_USESYSCALLS
565 pk3_endOfCentralDir_t eocd;
569 #ifdef FS_USESYSCALLS
570 packhandle = open (packfile, O_RDONLY | O_BINARY);
574 packhandle = fopen (packfile, "rb");
579 if (! PK3_GetEndOfCentralDir (packfile, packhandle, &eocd))
580 Sys_Error ("%s is not a PK3 file", packfile);
582 // Multi-volume ZIP archives are NOT allowed
583 if (eocd.disknum != 0 || eocd.cdir_disknum != 0)
584 Sys_Error ("%s is a multi-volume ZIP archive", packfile);
586 // We only need to do this test if MAX_FILES_IN_PACK is lesser than 65535
587 // since eocd.nbentries is an unsigned 16 bits integer
588 #if MAX_FILES_IN_PACK < 65535
589 if (eocd.nbentries > MAX_FILES_IN_PACK)
590 Sys_Error ("%s contains too many files (%hu)", packfile, eocd.nbentries);
593 // Create a package structure in memory
594 pack = Mem_Alloc (pak_mempool, sizeof (pack_t));
595 pack->ignorecase = true; // PK3 ignores case
596 strlcpy (pack->filename, packfile, sizeof (pack->filename));
597 pack->handle = packhandle;
598 pack->numfiles = eocd.nbentries;
599 pack->mempool = Mem_AllocPool (packfile, 0, NULL);
600 pack->files = Mem_Alloc (pack->mempool, eocd.nbentries * sizeof(packfile_t));
601 pack->next = packlist;
604 real_nb_files = PK3_BuildFileList (pack, &eocd);
605 if (real_nb_files <= 0)
606 Sys_Error ("%s is not a valid PK3 file", packfile);
608 Con_Printf("Added packfile %s (%i files)\n", packfile, real_nb_files);
615 PK3_GetTrueFileOffset
617 Find where the true file data offset is
620 void PK3_GetTrueFileOffset (packfile_t *file, pack_t *pack)
622 qbyte buffer [ZIP_LOCAL_CHUNK_BASE_SIZE];
626 if (file->flags & FILE_FLAG_TRUEOFFS)
629 // Load the local file description
630 #ifdef FS_USESYSCALLS
631 lseek (pack->handle, file->offset, SEEK_SET);
632 count = read (pack->handle, buffer, ZIP_LOCAL_CHUNK_BASE_SIZE);
634 fseek (pack->handle, file->offset, SEEK_SET);
635 count = fread (buffer, 1, ZIP_LOCAL_CHUNK_BASE_SIZE, pack->handle);
637 if (count != ZIP_LOCAL_CHUNK_BASE_SIZE || BuffBigLong (buffer) != ZIP_DATA_HEADER)
638 Sys_Error ("Can't retrieve file %s in package %s", file->name, pack->filename);
640 // Skip name and extra field
641 file->offset += BuffLittleShort (&buffer[26]) + BuffLittleShort (&buffer[28]) + ZIP_LOCAL_CHUNK_BASE_SIZE;
643 file->flags |= FILE_FLAG_TRUEOFFS;
648 =============================================================================
650 OTHER PRIVATE FUNCTIONS
652 =============================================================================
660 Add a file to the list of files contained into a package
663 static packfile_t* FS_AddFileToPack (const char* name, pack_t* pack,
664 size_t offset, size_t packsize,
665 size_t realsize, file_flags_t flags)
667 int (*strcmp_funct) (const char* str1, const char* str2);
668 int left, right, middle;
671 strcmp_funct = pack->ignorecase ? strcasecmp : strcmp;
673 // Look for the slot we should put that file into (binary search)
675 right = pack->numfiles - 1;
676 while (left <= right)
680 middle = (left + right) / 2;
681 diff = strcmp_funct (pack->files[middle].name, name);
683 // If we found the file, there's a problem
685 Sys_Error ("Package %s contains the file %s several times\n",
686 pack->filename, name);
688 // If we're too far in the list
695 // We have to move the right of the list by one slot to free the one we need
696 file = &pack->files[left];
697 memmove (file + 1, file, (pack->numfiles - left) * sizeof (*file));
700 strlcpy (file->name, name, sizeof (file->name));
701 file->offset = offset;
702 file->packsize = packsize;
703 file->realsize = realsize;
714 Only used for FS_Open.
717 void FS_CreatePath (char *path)
721 for (ofs = path+1 ; *ofs ; ofs++)
723 if (*ofs == '/' || *ofs == '\\')
725 // create the directory
741 void FS_Path_f (void)
745 Con_Print("Current search path:\n");
746 for (s=fs_searchpaths ; s ; s=s->next)
750 Con_Printf("%s (%i files)\n", s->pack->filename, s->pack->numfiles);
753 Con_Printf("%s\n", s->filename);
762 Takes an explicit (not game tree related) path to a pak file.
764 Loads the header and directory, adding the files at the beginning
765 of the list so they override previous pack files.
768 pack_t *FS_LoadPackPAK (const char *packfile)
770 dpackheader_t header;
772 #ifdef FS_USESYSCALLS
778 dpackfile_t *info; // temporary alloc, allowing huge pack directories
780 #ifdef FS_USESYSCALLS
781 packhandle = open (packfile, O_RDONLY | O_BINARY);
784 read (packhandle, (void *)&header, sizeof(header));
786 packhandle = fopen (packfile, "rb");
789 fread ((void *)&header, 1, sizeof(header), packhandle);
791 if (memcmp(header.id, "PACK", 4))
792 Sys_Error ("%s is not a packfile", packfile);
793 header.dirofs = LittleLong (header.dirofs);
794 header.dirlen = LittleLong (header.dirlen);
796 if (header.dirlen % sizeof(dpackfile_t))
797 Sys_Error ("%s has an invalid directory size", packfile);
799 numpackfiles = header.dirlen / sizeof(dpackfile_t);
801 if (numpackfiles > MAX_FILES_IN_PACK)
802 Sys_Error ("%s has %i files", packfile, numpackfiles);
804 pack = Mem_Alloc(pak_mempool, sizeof (pack_t));
805 pack->ignorecase = false; // PAK is case sensitive
806 strlcpy (pack->filename, packfile, sizeof (pack->filename));
807 pack->handle = packhandle;
809 pack->mempool = Mem_AllocPool(packfile, 0, NULL);
810 pack->files = Mem_Alloc(pack->mempool, numpackfiles * sizeof(packfile_t));
811 pack->next = packlist;
814 info = Mem_Alloc(tempmempool, sizeof(*info) * numpackfiles);
815 #ifdef FS_USESYSCALLS
816 lseek (packhandle, header.dirofs, SEEK_SET);
817 read (packhandle, (void *)info, header.dirlen);
819 fseek (packhandle, header.dirofs, SEEK_SET);
820 fread ((void *)info, 1, header.dirlen, packhandle);
823 // parse the directory
824 for (i = 0;i < numpackfiles;i++)
826 size_t offset = LittleLong (info[i].filepos);
827 size_t size = LittleLong (info[i].filelen);
829 FS_AddFileToPack (info[i].name, pack, offset, size, size, FILE_FLAG_TRUEOFFS);
834 Con_Printf("Added packfile %s (%i files)\n", packfile, numpackfiles);
843 Sets fs_gamedir, adds the directory to the head of the path,
844 then loads and adds pak1.pak pak2.pak ...
847 void FS_AddGameDirectory (char *dir)
849 stringlist_t *list, *current;
850 searchpath_t *search;
852 char pakfile[MAX_OSPATH];
854 strlcpy (fs_gamedir, dir, sizeof (fs_gamedir));
856 list = listdirectory(dir);
858 // add any PAK package in the directory
859 for (current = list;current;current = current->next)
861 if (matchpattern(current->text, "*.pak", true))
863 snprintf (pakfile, sizeof (pakfile), "%s/%s", dir, current->text);
864 pak = FS_LoadPackPAK (pakfile);
867 search = Mem_Alloc(pak_mempool, sizeof(searchpath_t));
869 search->next = fs_searchpaths;
870 fs_searchpaths = search;
873 Con_Printf("unable to load pak \"%s\"\n", pakfile);
877 // add any PK3 package in the director
878 for (current = list;current;current = current->next)
880 if (matchpattern(current->text, "*.pk3", true))
882 snprintf (pakfile, sizeof (pakfile), "%s/%s", dir, current->text);
883 pak = FS_LoadPackPK3 (pakfile);
886 search = Mem_Alloc(pak_mempool, sizeof(searchpath_t));
888 search->next = fs_searchpaths;
889 fs_searchpaths = search;
892 Con_Printf("unable to load pak \"%s\"\n", pakfile);
897 // Add the directory to the search path
898 // (unpacked files have the priority over packed files)
899 search = Mem_Alloc(pak_mempool, sizeof(searchpath_t));
900 strlcpy (search->filename, dir, sizeof (search->filename));
901 search->next = fs_searchpaths;
902 fs_searchpaths = search;
911 char *FS_FileExtension (const char *in)
913 static char exten[8];
914 const char *slash, *backslash, *colon, *dot, *separator;
917 slash = strrchr(in, '/');
918 backslash = strrchr(in, '\\');
919 colon = strrchr(in, ':');
920 dot = strrchr(in, '.');
922 if (separator < backslash)
923 separator = backslash;
924 if (separator < colon)
926 if (dot == NULL || dot < separator)
929 for (i = 0;i < 7 && dot[i];i++)
944 searchpath_t *search;
946 fs_mempool = Mem_AllocPool("file management", 0, NULL);
947 pak_mempool = Mem_AllocPool("paks", 0, NULL);
949 Cvar_RegisterVariable (&scr_screenshot_name);
951 Cmd_AddCommand ("path", FS_Path_f);
952 Cmd_AddCommand ("dir", FS_Dir_f);
953 Cmd_AddCommand ("ls", FS_Ls_f);
955 strcpy(fs_basedir, ".");
956 strcpy(fs_gamedir, ".");
961 // Overrides the system supplied base directory (under GAMENAME)
962 // 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)
963 i = COM_CheckParm ("-basedir");
964 if (i && i < com_argc-1)
966 strlcpy (fs_basedir, com_argv[i+1], sizeof (fs_basedir));
967 i = strlen (fs_basedir);
968 if (i > 0 && (fs_basedir[i-1] == '\\' || fs_basedir[i-1] == '/'))
972 // -path <dir or packfile> [<dir or packfile>] ...
973 // Fully specifies the exact search path, overriding the generated one
974 // 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)
975 i = COM_CheckParm ("-path");
979 while (++i < com_argc)
981 if (!com_argv[i] || com_argv[i][0] == '+' || com_argv[i][0] == '-')
984 search = Mem_Alloc(pak_mempool, sizeof(searchpath_t));
985 if (!strcasecmp (FS_FileExtension(com_argv[i]), "pak"))
987 search->pack = FS_LoadPackPAK (com_argv[i]);
989 Sys_Error ("Couldn't load packfile: %s", com_argv[i]);
991 else if (!strcasecmp (FS_FileExtension (com_argv[i]), "pk3"))
993 search->pack = FS_LoadPackPK3 (com_argv[i]);
995 Sys_Error ("Couldn't load packfile: %s", com_argv[i]);
998 strlcpy (search->filename, com_argv[i], sizeof (search->filename));
999 search->next = fs_searchpaths;
1000 fs_searchpaths = search;
1005 // start up with GAMENAME by default (id1)
1006 strlcpy (com_modname, GAMENAME, sizeof (com_modname));
1007 FS_AddGameDirectory (va("%s/"GAMENAME, fs_basedir));
1008 Cvar_SetQuick (&scr_screenshot_name, gamescreenshotname);
1010 // add the game-specific path, if any
1014 strlcpy (com_modname, gamedirname, sizeof (com_modname));
1015 FS_AddGameDirectory (va("%s/%s", fs_basedir, gamedirname));
1019 // Adds basedir/gamedir as an override game
1020 // LordHavoc: now supports multiple -game directories
1021 for (i = 1;i < com_argc;i++)
1025 if (!strcmp (com_argv[i], "-game") && i < com_argc-1)
1029 strlcpy (com_modname, com_argv[i], sizeof (com_modname));
1030 FS_AddGameDirectory (va("%s/%s", fs_basedir, com_argv[i]));
1031 Cvar_SetQuick (&scr_screenshot_name, com_modname);
1035 // If "-condebug" is in the command line, remove the previous log file
1036 if (COM_CheckParm ("-condebug") != 0)
1037 unlink (va("%s/qconsole.log", fs_gamedir));
1042 ====================
1045 Internal function used to create a qfile_t and open the relevant file on disk
1046 ====================
1048 static qfile_t* FS_SysOpen (const char* filepath, const char* mode)
1052 file = Mem_Alloc (fs_mempool, sizeof (*file));
1053 memset (file, 0, sizeof (*file));
1055 #ifdef FS_USESYSCALLS
1056 if (strchr(mode, 'r'))
1057 file->stream = open (filepath, O_RDONLY | O_BINARY);
1058 else if (strchr(mode, 'w'))
1059 file->stream = open (filepath, O_WRONLY | O_BINARY | O_CREAT | O_TRUNC, 0666);
1060 else if (strchr(mode, 'a'))
1061 file->stream = open (filepath, O_RDWR | O_BINARY | O_CREAT | O_APPEND, 0666);
1064 if (file->stream < 0)
1070 file->stream = fopen (filepath, mode);
1087 qfile_t *FS_OpenRead (const char *path, int offs, int len)
1091 file = FS_SysOpen (path, "rb");
1094 Sys_Error ("Couldn't open %s", path);
1099 if (offs < 0 || len < 0)
1101 // We set fs_filesize here for normal files
1102 #ifdef FS_USESYSCALLS
1103 fs_filesize = lseek (file->stream, 0, SEEK_END);
1104 lseek (file->stream, 0, SEEK_SET);
1106 fseek (file->stream, 0, SEEK_END);
1107 fs_filesize = ftell (file->stream);
1108 fseek (file->stream, 0, SEEK_SET);
1114 #ifdef FS_USESYSCALLS
1115 lseek (file->stream, offs, SEEK_SET);
1117 fseek (file->stream, offs, SEEK_SET);
1120 file->flags |= FS_FLAG_PACKED;
1122 file->offset = offs;
1130 ====================
1133 Return true if the path should be rejected due to one of the following:
1134 1: path elements that are non-portable
1135 2: path elements that would allow access to files outside the game directory,
1136 or are just not a good idea for a mod to be using.
1137 ====================
1139 int FS_CheckNastyPath (const char *path)
1141 // Windows: don't allow \ in filenames (windows-only), period.
1142 // (on Windows \ is a directory separator, but / is also supported)
1143 if (strstr(path, "\\"))
1144 return 1; // non-portable
1145 // Mac: don't allow Mac-only filenames - : is a directory separator
1146 // instead of /, but we rely on / working already, so there's no reason to
1147 // support a Mac-only path
1148 // Amiga and Windows: : tries to go to root of drive
1149 if (strstr(path, ":"))
1150 return 1; // non-portable attempt to go to root of drive
1151 // Amiga: // is parent directory
1152 if (strstr(path, "//"))
1153 return 1; // non-portable attempt to go to parent directory
1154 // all: don't allow going to current directory (./) or parent directory (../ or /../)
1155 if (strstr(path, "./"))
1156 return 2; // attempt to go to parent directory
1157 // after all these checks we're pretty sure it's a / separated filename
1158 // and won't do much if any harm
1164 ====================
1167 Look for a file in the packages and in the filesystem
1169 Return the searchpath where the file was found (or NULL)
1170 and the file index in the package if relevant
1171 ====================
1173 static searchpath_t *FS_FindFile (const char *name, int* index, qboolean quiet)
1175 searchpath_t *search;
1178 // search through the path, one element at a time
1179 for (search = fs_searchpaths;search;search = search->next)
1181 // is the element a pak file?
1184 int (*strcmp_funct) (const char* str1, const char* str2);
1185 int left, right, middle;
1188 strcmp_funct = pak->ignorecase ? strcasecmp : strcmp;
1190 // Look for the file (binary search)
1192 right = pak->numfiles - 1;
1193 while (left <= right)
1197 middle = (left + right) / 2;
1198 diff = strcmp_funct (pak->files[middle].name, name);
1204 Con_DPrintf("FS_FindFile: %s in %s\n",
1205 pak->files[middle].name, pak->filename);
1212 // If we're too far in the list
1221 char netpath[MAX_OSPATH];
1222 snprintf(netpath, sizeof(netpath), "%s/%s", search->filename, name);
1223 if (FS_SysFileExists (netpath))
1226 Con_DPrintf("FS_FindFile: %s\n", netpath);
1236 Con_DPrintf("FS_FindFile: can't find %s\n", name);
1248 If the requested file is inside a packfile, a new qfile_t* will be opened
1254 qfile_t *FS_FOpenFile (const char *filename, qboolean quiet)
1256 searchpath_t *search;
1257 packfile_t *packfile;
1261 search = FS_FindFile (filename, &i, quiet);
1270 // Found in the filesystem?
1273 char netpath[MAX_OSPATH];
1274 snprintf(netpath, sizeof(netpath), "%s/%s", search->filename, filename);
1275 return FS_OpenRead(netpath, -1, -1);
1278 // So, we found it in a package...
1279 packfile = &search->pack->files[i];
1281 // If we don't have the true offset, get it now
1282 if (! (packfile->flags & FILE_FLAG_TRUEOFFS))
1283 PK3_GetTrueFileOffset (packfile, search->pack);
1285 // No Zlib DLL = no compressed files
1286 if (!zlib_dll && (packfile->flags & FILE_FLAG_DEFLATED))
1288 Con_Printf("WARNING: can't open the compressed file %s\n"
1289 "You need the Zlib DLL to use compressed files\n",
1295 // open a new file in the pakfile
1296 file = FS_OpenRead (search->pack->filename, packfile->offset, packfile->packsize);
1297 fs_filesize = packfile->realsize;
1299 if (packfile->flags & FILE_FLAG_DEFLATED)
1303 file->flags |= FS_FLAG_DEFLATED;
1305 // We need some more variables
1306 ztk = Mem_Alloc (fs_mempool, sizeof (*file->z));
1308 ztk->real_length = packfile->realsize;
1310 // Initialize zlib stream
1311 ztk->zstream.next_in = ztk->input;
1312 ztk->zstream.avail_in = 0;
1314 /* From Zlib's "unzip.c":
1316 * windowBits is passed < 0 to tell that there is no zlib header.
1317 * Note that in this case inflate *requires* an extra "dummy" byte
1318 * after the compressed stream in order to complete decompression and
1319 * return Z_STREAM_END.
1320 * In unzip, i don't wait absolutely Z_STREAM_END because I known the
1321 * size of both compressed and uncompressed data
1323 if (qz_inflateInit2 (&ztk->zstream, -MAX_WBITS) != Z_OK)
1324 Sys_Error ("inflate init error (file: %s)", filename);
1326 ztk->zstream.next_out = ztk->output;
1327 ztk->zstream.avail_out = sizeof (ztk->output);
1337 =============================================================================
1339 MAIN PUBLIC FUNCTIONS
1341 =============================================================================
1345 ====================
1348 Open a file. The syntax is the same as fopen
1349 ====================
1351 qfile_t* FS_Open (const char* filepath, const char* mode, qboolean quiet)
1353 if (FS_CheckNastyPath(filepath))
1355 Con_Printf("FS_Open(\"%s\", \"%s\", %s): nasty filename rejected\n", filepath, mode, quiet ? "true" : "false");
1359 // If the file is opened in "write" or "append" mode
1360 if (strchr (mode, 'w') || strchr (mode, 'a'))
1362 char real_path [MAX_OSPATH];
1364 // Open the file on disk directly
1365 snprintf (real_path, sizeof (real_path), "%s/%s", fs_gamedir, filepath);
1367 // Create directories up to the file
1368 FS_CreatePath (real_path);
1370 return FS_SysOpen (real_path, mode);
1373 // Else, we look at the various search paths
1374 return FS_FOpenFile (filepath, quiet);
1379 ====================
1383 ====================
1385 int FS_Close (qfile_t* file)
1387 #ifdef FS_USESYSCALLS
1388 if (close (file->stream))
1390 if (fclose (file->stream))
1396 qz_inflateEnd (&file->z->zstream);
1406 ====================
1409 Write "datasize" bytes into a file
1410 ====================
1412 size_t FS_Write (qfile_t* file, const void* data, size_t datasize)
1414 #ifdef FS_USESYSCALLS
1415 return write (file->stream, data, datasize);
1417 return fwrite (data, 1, datasize, file->stream);
1423 ====================
1426 Read up to "buffersize" bytes from a file
1427 ====================
1429 size_t FS_Read (qfile_t* file, void* buffer, size_t buffersize)
1434 // Quick path for unpacked files
1435 if (! (file->flags & FS_FLAG_PACKED))
1436 #ifdef FS_USESYSCALLS
1437 return read (file->stream, buffer, buffersize);
1439 return fread (buffer, 1, buffersize, file->stream);
1442 // If the file isn't compressed
1443 if (! (file->flags & FS_FLAG_DEFLATED))
1445 // We must take care to not read after the end of the file
1446 count = file->length - file->position;
1447 if (buffersize > count)
1450 #ifdef FS_USESYSCALLS
1451 nb = read (file->stream, buffer, buffersize);
1453 nb = fread (buffer, 1, buffersize, file->stream);
1456 file->position += nb;
1460 // If the file is compressed, it's more complicated...
1463 // First, we copy as many bytes as we can from "output"
1464 if (ztk->out_ind < ztk->out_max)
1466 count = ztk->out_max - ztk->out_ind;
1468 nb = (buffersize > count) ? count : buffersize;
1469 memcpy (buffer, &ztk->output[ztk->out_ind], nb);
1471 file->position += nb;
1476 // We cycle through a few operations until we have inflated enough data
1477 while (nb < buffersize)
1479 // NOTE: at this point, "output" should always be empty
1481 // If "input" is also empty, we need to fill it
1482 if (ztk->in_ind == ztk->in_max)
1486 // If we are at the end of the file
1487 if (ztk->out_position == ztk->real_length)
1490 remain = file->length - ztk->in_position;
1491 count = (remain > sizeof (ztk->input)) ? sizeof (ztk->input) : remain;
1492 #ifdef FS_USESYSCALLS
1493 read (file->stream, ztk->input, count);
1495 fread (ztk->input, 1, count, file->stream);
1498 // Update indexes and counters
1500 ztk->in_max = count;
1501 ztk->in_position += count;
1504 // Now that we are sure we have compressed data available, we need to determine
1505 // if it's better to inflate it in "output" or directly in "buffer" (we are in this
1506 // case if we still need more bytes than "output" can contain)
1508 ztk->zstream.next_in = &ztk->input[ztk->in_ind];
1509 ztk->zstream.avail_in = ztk->in_max - ztk->in_ind;
1511 // If output will be able to contain at least 1 more byte than the data we need
1512 if (buffersize - nb < sizeof (ztk->output))
1516 // Inflate the data in "output"
1517 ztk->zstream.next_out = ztk->output;
1518 ztk->zstream.avail_out = sizeof (ztk->output);
1519 error = qz_inflate (&ztk->zstream, Z_SYNC_FLUSH);
1520 if (error != Z_OK && error != Z_STREAM_END)
1521 Sys_Error ("Can't inflate file");
1522 ztk->in_ind = ztk->in_max - ztk->zstream.avail_in;
1523 ztk->out_max = sizeof (ztk->output) - ztk->zstream.avail_out;
1524 ztk->out_position += ztk->out_max;
1526 // Copy the requested data in "buffer" (as much as we can)
1527 count = (buffersize - nb > ztk->out_max) ? ztk->out_max : buffersize - nb;
1528 memcpy (&((qbyte*)buffer)[nb], ztk->output, count);
1529 ztk->out_ind = count;
1532 // Else, we inflate directly in "buffer"
1537 // Inflate the data in "buffer"
1538 ztk->zstream.next_out = &((qbyte*)buffer)[nb];
1539 ztk->zstream.avail_out = buffersize - nb;
1540 error = qz_inflate (&ztk->zstream, Z_SYNC_FLUSH);
1541 if (error != Z_OK && error != Z_STREAM_END)
1542 Sys_Error ("Can't inflate file");
1543 ztk->in_ind = ztk->in_max - ztk->zstream.avail_in;
1545 // Invalidate the output data (for FS_Seek)
1549 // How much data did it inflate?
1550 count = buffersize - nb - ztk->zstream.avail_out;
1551 ztk->out_position += count;
1555 file->position += count;
1563 ====================
1566 Flush the file output stream
1567 ====================
1569 int FS_Flush (qfile_t* file)
1571 #ifdef FS_USESYSCALLS
1574 return fflush (file->stream);
1580 ====================
1583 Print a string into a file
1584 ====================
1586 int FS_Print(qfile_t* file, const char *msg)
1588 return FS_Write(file, msg, strlen(msg));
1592 ====================
1595 Print a string into a file
1596 ====================
1598 int FS_Printf(qfile_t* file, const char* format, ...)
1603 va_start (args, format);
1604 result = FS_VPrintf(file, format, args);
1612 ====================
1615 Print a string into a file
1616 ====================
1618 int FS_VPrintf(qfile_t* file, const char* format, va_list ap)
1620 #ifdef FS_USESYSCALLS
1623 char tempstring[1024];
1624 len = vsnprintf (tempstring, sizeof(tempstring), format, ap);
1625 if (len >= sizeof(tempstring))
1628 char *temp = Mem_Alloc(tempmempool, len + 1);
1629 len = vsnprintf (temp, len + 1, format, ap);
1630 result = write (file->stream, temp, len);
1635 return write (file->stream, tempstring, len);
1638 return vfprintf (file->stream, format, ap);
1644 ====================
1647 Get the next character of a file
1648 ====================
1650 int FS_Getc (qfile_t* file)
1654 if (FS_Read (file, &c, 1) != 1)
1662 ====================
1665 Move the position index in a file
1666 ====================
1668 int FS_Seek (qfile_t* file, long offset, int whence)
1670 // Quick path for unpacked files
1671 if (! (file->flags & FS_FLAG_PACKED))
1672 #ifdef FS_USESYSCALLS
1674 if (lseek (file->stream, offset, whence) == -1)
1679 return fseek (file->stream, offset, whence);
1682 // Seeking in compressed files is more a hack than anything else,
1683 // but we need to support it, so here it is.
1684 if (file->flags & FS_FLAG_DEFLATED)
1686 ztoolkit_t *ztk = file->z;
1687 qbyte buffer [sizeof (ztk->output)]; // it's big to force inflating into buffer directly
1692 offset += file->position;
1699 offset += ztk->real_length;
1705 if (offset < 0 || offset > (long) ztk->real_length)
1708 // If we need to go back in the file
1709 if (offset <= (long) file->position)
1711 // If we still have the data we need in the output buffer
1712 if (file->position - offset <= ztk->out_ind)
1714 ztk->out_ind -= file->position - offset;
1715 file->position = offset;
1719 // Else, we restart from the beginning of the file
1722 ztk->in_position = 0;
1725 ztk->out_position = 0;
1727 #ifdef FS_USESYSCALLS
1728 lseek (file->stream, file->offset, SEEK_SET);
1730 fseek (file->stream, file->offset, SEEK_SET);
1733 // Reset the Zlib stream
1734 ztk->zstream.next_in = ztk->input;
1735 ztk->zstream.avail_in = 0;
1736 qz_inflateReset (&ztk->zstream);
1739 // Skip all data until we reach the requested offset
1740 while ((long) file->position < offset)
1742 size_t diff = offset - file->position;
1745 count = (diff > sizeof (buffer)) ? sizeof (buffer) : diff;
1746 len = FS_Read (file, buffer, count);
1754 // Packed files receive a special treatment too, because
1755 // we need to make sure it doesn't go outside of the file
1759 offset += file->position;
1766 offset += file->length;
1772 if (offset < 0 || offset > (long) file->length)
1775 #ifdef FS_USESYSCALLS
1776 if (lseek (file->stream, file->offset + offset, SEEK_SET) == -1)
1779 if (fseek (file->stream, file->offset + offset, SEEK_SET) == -1)
1782 file->position = offset;
1788 ====================
1791 Give the current position in a file
1792 ====================
1794 long FS_Tell (qfile_t* file)
1796 if (file->flags & FS_FLAG_PACKED)
1797 return file->position;
1799 #ifdef FS_USESYSCALLS
1800 return lseek (file->stream, 0, SEEK_CUR);
1802 return ftell (file->stream);
1808 ====================
1811 Extract a line from a file
1812 ====================
1814 char* FS_Gets (qfile_t* file, char* buffer, int buffersize)
1818 // Quick path for unpacked files
1819 #ifndef FS_USESYSCALLS
1820 if (! (file->flags & FS_FLAG_PACKED))
1821 return fgets (buffer, buffersize, file->stream);
1824 for (ind = 0; ind < (size_t) buffersize - 1; ind++)
1826 int c = FS_Getc (file);
1841 buffer[ind + 1] = '\0';
1850 buffer[buffersize - 1] = '\0';
1859 Dynamic length version of fgets. DO NOT free the buffer.
1862 char *FS_Getline (qfile_t *file)
1864 static int size = 256;
1865 static char *buf = 0;
1870 buf = Mem_Alloc (fs_mempool, size);
1872 if (!FS_Gets (file, buf, size))
1876 while (buf[len - 1] != '\n' && buf[len - 1] != '\r')
1878 t = Mem_Alloc (fs_mempool, size + 256);
1879 memcpy(t, buf, size);
1883 if (!FS_Gets (file, buf + len, size - len))
1887 while ((len = strlen(buf)) && (buf[len - 1] == '\n' || buf[len - 1] == '\r'))
1894 ====================
1897 Extract a line from a file
1898 ====================
1900 // FIXME: remove this function?
1901 int FS_Eof (qfile_t* file)
1903 if (file->flags & FS_FLAG_PACKED)
1905 if (file->flags & FS_FLAG_DEFLATED)
1906 return (file->position == file->z->real_length);
1908 return (file->position == file->length);
1911 #ifdef FS_USESYSCALLS
1912 Sys_Error("FS_Eof: not implemented using syscalls\n");
1915 return feof (file->stream);
1924 Filename are relative to the quake directory.
1925 Always appends a 0 byte.
1928 qbyte *FS_LoadFile (const char *path, mempool_t *pool, qboolean quiet)
1933 // look for it in the filesystem or pack files
1934 h = FS_Open (path, "rb", quiet);
1938 buf = Mem_Alloc(pool, fs_filesize+1);
1940 Sys_Error ("FS_LoadFile: not enough available memory for %s (size %i)", path, fs_filesize);
1942 ((qbyte *)buf)[fs_filesize] = 0;
1944 FS_Read (h, buf, fs_filesize);
1955 The filename will be prefixed by the current game directory
1958 qboolean FS_WriteFile (const char *filename, void *data, int len)
1962 handle = FS_Open (filename, "wb", false);
1965 Con_Printf("FS_WriteFile: failed on %s\n", filename);
1969 Con_DPrintf("FS_WriteFile: %s\n", filename);
1970 FS_Write (handle, data, len);
1977 =============================================================================
1979 OTHERS PUBLIC FUNCTIONS
1981 =============================================================================
1989 void FS_StripExtension (const char *in, char *out, size_t size_out)
1996 while (*in && size_out > 1)
2000 else if (*in == '/' || *in == '\\' || *in == ':')
2017 void FS_DefaultExtension (char *path, const char *extension, size_t size_path)
2021 // if path doesn't have a .EXT, append extension
2022 // (extension should include the .)
2023 src = path + strlen(path) - 1;
2025 while (*src != '/' && src != path)
2028 return; // it has an extension
2032 strlcat (path, extension, size_path);
2040 Look for a file in the packages and in the filesystem
2043 qboolean FS_FileExists (const char *filename)
2045 return (FS_FindFile (filename, NULL, true) != NULL);
2053 Look for a file in the filesystem only
2056 qboolean FS_SysFileExists (const char *path)
2061 f = fopen (path, "rb");
2072 if (stat (path,&buf) == -1)
2079 void FS_mkdir (const char *path)
2092 Allocate and fill a search structure with information on matching filenames.
2095 fssearch_t *FS_Search(const char *pattern, int caseinsensitive, int quiet)
2098 searchpath_t *searchpath;
2100 int i, basepathlength, numfiles, numchars;
2101 stringlist_t *dir, *dirfile, *liststart, *listcurrent, *listtemp;
2102 const char *slash, *backslash, *colon, *separator;
2104 char netpath[MAX_OSPATH];
2105 char temp[MAX_OSPATH];
2107 while(!strncmp(pattern, "./", 2))
2109 while(!strncmp(pattern, ".\\", 2))
2116 slash = strrchr(pattern, '/');
2117 backslash = strrchr(pattern, '\\');
2118 colon = strrchr(pattern, ':');
2119 separator = pattern;
2120 if (separator < slash)
2122 if (separator < backslash)
2123 separator = backslash;
2124 if (separator < colon)
2126 basepathlength = separator - pattern;
2127 basepath = Mem_Alloc (tempmempool, basepathlength + 1);
2129 memcpy(basepath, pattern, basepathlength);
2130 basepath[basepathlength] = 0;
2132 // search through the path, one element at a time
2133 for (searchpath = fs_searchpaths;searchpath;searchpath = searchpath->next)
2135 // is the element a pak file?
2136 if (searchpath->pack)
2138 // look through all the pak file elements
2139 pak = searchpath->pack;
2140 for (i = 0;i < pak->numfiles;i++)
2142 strcpy(temp, pak->files[i].name);
2145 if (matchpattern(temp, (char *)pattern, true))
2147 for (listtemp = liststart;listtemp;listtemp = listtemp->next)
2148 if (!strcmp(listtemp->text, temp))
2150 if (listtemp == NULL)
2152 listcurrent = stringlistappend(listcurrent, temp);
2153 if (liststart == NULL)
2154 liststart = listcurrent;
2156 Con_DPrintf("SearchPackFile: %s : %s\n", pak->filename, temp);
2159 // strip off one path element at a time until empty
2160 // this way directories are added to the listing if they match the pattern
2161 slash = strrchr(temp, '/');
2162 backslash = strrchr(temp, '\\');
2163 colon = strrchr(temp, ':');
2165 if (separator < slash)
2167 if (separator < backslash)
2168 separator = backslash;
2169 if (separator < colon)
2171 *((char *)separator) = 0;
2177 // get a directory listing and look at each name
2178 snprintf(netpath, sizeof (netpath), "%s/%s", searchpath->filename, basepath);
2179 if ((dir = listdirectory(netpath)))
2181 for (dirfile = dir;dirfile;dirfile = dirfile->next)
2183 snprintf(temp, sizeof(temp), "%s/%s", basepath, dirfile->text);
2184 if (matchpattern(temp, (char *)pattern, true))
2186 for (listtemp = liststart;listtemp;listtemp = listtemp->next)
2187 if (!strcmp(listtemp->text, temp))
2189 if (listtemp == NULL)
2191 listcurrent = stringlistappend(listcurrent, temp);
2192 if (liststart == NULL)
2193 liststart = listcurrent;
2195 Con_DPrintf("SearchDirFile: %s\n", temp);
2206 liststart = stringlistsort(liststart);
2209 for (listtemp = liststart;listtemp;listtemp = listtemp->next)
2212 numchars += strlen(listtemp->text) + 1;
2214 search = Z_Malloc(sizeof(fssearch_t) + numchars + numfiles * sizeof(char *));
2215 search->filenames = (char **)((char *)search + sizeof(fssearch_t));
2216 search->filenamesbuffer = (char *)((char *)search + sizeof(fssearch_t) + numfiles * sizeof(char *));
2217 search->numfilenames = numfiles;
2220 for (listtemp = liststart;listtemp;listtemp = listtemp->next)
2222 search->filenames[numfiles] = search->filenamesbuffer + numchars;
2223 strcpy(search->filenames[numfiles], listtemp->text);
2225 numchars += strlen(listtemp->text) + 1;
2228 stringlistfree(liststart);
2235 void FS_FreeSearch(fssearch_t *search)
2240 extern int con_linewidth;
2241 int FS_ListDirectory(const char *pattern, int oneperline)
2252 search = FS_Search(pattern, true, true);
2255 numfiles = search->numfilenames;
2258 // FIXME: the names could be added to one column list and then
2259 // gradually shifted into the next column if they fit, and then the
2260 // next to make a compact variable width listing but it's a lot more
2262 // find width for columns
2264 for (i = 0;i < numfiles;i++)
2266 l = strlen(search->filenames[i]);
2267 if (columnwidth < l)
2270 // count the spacing character
2272 // calculate number of columns
2273 numcolumns = con_linewidth / columnwidth;
2274 // don't bother with the column printing if it's only one column
2275 if (numcolumns >= 2)
2277 numlines = (numfiles + numcolumns - 1) / numcolumns;
2278 for (i = 0;i < numlines;i++)
2281 for (k = 0;k < numcolumns;k++)
2283 l = i * numcolumns + k;
2286 name = search->filenames[l];
2287 for (j = 0;name[j] && j < (int)sizeof(linebuf) - 1;j++)
2288 linebuf[linebufpos++] = name[j];
2289 // space out name unless it's the last on the line
2290 if (k < (numcolumns - 1) && l < (numfiles - 1))
2291 for (;j < columnwidth && j < (int)sizeof(linebuf) - 1;j++)
2292 linebuf[linebufpos++] = ' ';
2295 linebuf[linebufpos] = 0;
2296 Con_Printf("%s\n", linebuf);
2303 for (i = 0;i < numfiles;i++)
2304 Con_Printf("%s\n", search->filenames[i]);
2305 FS_FreeSearch(search);
2309 static void FS_ListDirectoryCmd (const char* cmdname, int oneperline)
2311 const char *pattern;
2314 Con_Printf("usage:\n%s [path/pattern]\n", cmdname);
2317 if (Cmd_Argc() == 2)
2318 pattern = Cmd_Argv(1);
2321 if (!FS_ListDirectory(pattern, oneperline))
2322 Con_Print("No files found.\n");
2327 FS_ListDirectoryCmd("dir", true);
2332 FS_ListDirectoryCmd("ls", false);