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)
349 const char* dllnames [] =
353 #elif defined(MACOSX)
367 if (! Sys_LoadLibrary (dllnames, &zlib_dll, zlibfuncs))
369 Con_Printf ("Compressed files support disabled\n");
373 Con_Printf ("Compressed files support enabled\n");
380 PK3_GetEndOfCentralDir
382 Extract the end of the central directory from a PK3 package
385 #ifdef FS_USESYSCALLS
386 qboolean PK3_GetEndOfCentralDir (const char *packfile, int packhandle, pk3_endOfCentralDir_t *eocd)
388 qboolean PK3_GetEndOfCentralDir (const char *packfile, FILE *packhandle, pk3_endOfCentralDir_t *eocd)
391 long filesize, maxsize;
395 // Get the package size
396 #ifdef FS_USESYSCALLS
397 filesize = lseek (packhandle, 0, SEEK_END);
399 fseek (packhandle, 0, SEEK_END);
400 filesize = ftell(packhandle);
402 if (filesize < ZIP_END_CDIR_SIZE)
405 // Load the end of the file in memory
406 if (filesize < ZIP_MAX_COMMENTS_SIZE + ZIP_END_CDIR_SIZE)
409 maxsize = ZIP_MAX_COMMENTS_SIZE + ZIP_END_CDIR_SIZE;
410 buffer = Mem_Alloc (tempmempool, maxsize);
411 #ifdef FS_USESYSCALLS
412 lseek (packhandle, filesize - maxsize, SEEK_SET);
413 if (read (packhandle, buffer, maxsize) != (ssize_t) maxsize)
415 fseek (packhandle, filesize - maxsize, SEEK_SET);
416 if (fread (buffer, 1, maxsize, packhandle) != (size_t) maxsize)
423 // Look for the end of central dir signature around the end of the file
424 maxsize -= ZIP_END_CDIR_SIZE;
425 ptr = &buffer[maxsize];
427 while (BuffBigLong (ptr) != ZIP_END_HEADER)
439 memcpy (eocd, ptr, ZIP_END_CDIR_SIZE);
440 eocd->signature = LittleLong (eocd->signature);
441 eocd->disknum = LittleShort (eocd->disknum);
442 eocd->cdir_disknum = LittleShort (eocd->cdir_disknum);
443 eocd->localentries = LittleShort (eocd->localentries);
444 eocd->nbentries = LittleShort (eocd->nbentries);
445 eocd->cdir_size = LittleLong (eocd->cdir_size);
446 eocd->cdir_offset = LittleLong (eocd->cdir_offset);
447 eocd->comment_size = LittleShort (eocd->comment_size);
459 Extract the file list from a PK3 file
462 int PK3_BuildFileList (pack_t *pack, const pk3_endOfCentralDir_t *eocd)
464 qbyte *central_dir, *ptr;
468 // Load the central directory in memory
469 central_dir = Mem_Alloc (tempmempool, eocd->cdir_size);
470 #ifdef FS_USESYSCALLS
471 lseek (pack->handle, eocd->cdir_offset, SEEK_SET);
472 read (pack->handle, central_dir, eocd->cdir_size);
474 fseek (pack->handle, eocd->cdir_offset, SEEK_SET);
475 fread (central_dir, 1, eocd->cdir_size, pack->handle);
478 // Extract the files properties
479 // The parsing is done "by hand" because some fields have variable sizes and
480 // the constant part isn't 4-bytes aligned, which makes the use of structs difficult
481 remaining = eocd->cdir_size;
484 for (ind = 0; ind < eocd->nbentries; ind++)
486 size_t namesize, count;
488 // Checking the remaining size
489 if (remaining < ZIP_CDIR_CHUNK_BASE_SIZE)
491 Mem_Free (central_dir);
494 remaining -= ZIP_CDIR_CHUNK_BASE_SIZE;
497 if (BuffBigLong (ptr) != ZIP_CDIR_HEADER)
499 Mem_Free (central_dir);
503 namesize = BuffLittleShort (&ptr[28]); // filename length
505 // Check encryption, compression, and attributes
506 // 1st uint8 : general purpose bit flag
507 // Check bits 0 (encryption), 3 (data descriptor after the file), and 5 (compressed patched data (?))
508 // 2nd uint8 : external file attributes
509 // Check bits 3 (file is a directory) and 5 (file is a volume (?))
510 if ((ptr[8] & 0x29) == 0 && (ptr[38] & 0x18) == 0)
512 // Still enough bytes for the name?
513 if ((size_t) remaining < namesize || namesize >= sizeof (*pack->files))
515 Mem_Free (central_dir);
519 // WinZip doesn't use the "directory" attribute, so we need to check the name directly
520 if (ptr[ZIP_CDIR_CHUNK_BASE_SIZE + namesize - 1] != '/')
522 char filename [sizeof (pack->files[0].name)];
523 size_t offset, packsize, realsize;
526 // Extract the name (strip it if necessary)
527 if (namesize >= sizeof (filename))
528 namesize = sizeof (filename) - 1;
529 memcpy (filename, &ptr[ZIP_CDIR_CHUNK_BASE_SIZE], namesize);
530 filename[namesize] = '\0';
532 if (BuffLittleShort (&ptr[10]))
533 flags = FILE_FLAG_DEFLATED;
536 offset = BuffLittleLong (&ptr[42]);
537 packsize = BuffLittleLong (&ptr[20]);
538 realsize = BuffLittleLong (&ptr[24]);
539 FS_AddFileToPack (filename, pack, offset, packsize, realsize, flags);
543 // Skip the name, additionnal field, and comment
544 // 1er uint16 : extra field length
545 // 2eme uint16 : file comment length
546 count = namesize + BuffLittleShort (&ptr[30]) + BuffLittleShort (&ptr[32]);
547 ptr += ZIP_CDIR_CHUNK_BASE_SIZE + count;
551 // If the package is empty, central_dir is NULL here
552 if (central_dir != NULL)
553 Mem_Free (central_dir);
554 return pack->numfiles;
562 Create a package entry associated with a PK3 file
565 pack_t *FS_LoadPackPK3 (const char *packfile)
567 #ifdef FS_USESYSCALLS
572 pk3_endOfCentralDir_t eocd;
576 #ifdef FS_USESYSCALLS
577 packhandle = open (packfile, O_RDONLY | O_BINARY);
581 packhandle = fopen (packfile, "rb");
586 if (! PK3_GetEndOfCentralDir (packfile, packhandle, &eocd))
587 Sys_Error ("%s is not a PK3 file", packfile);
589 // Multi-volume ZIP archives are NOT allowed
590 if (eocd.disknum != 0 || eocd.cdir_disknum != 0)
591 Sys_Error ("%s is a multi-volume ZIP archive", packfile);
593 // We only need to do this test if MAX_FILES_IN_PACK is lesser than 65535
594 // since eocd.nbentries is an unsigned 16 bits integer
595 #if MAX_FILES_IN_PACK < 65535
596 if (eocd.nbentries > MAX_FILES_IN_PACK)
597 Sys_Error ("%s contains too many files (%hu)", packfile, eocd.nbentries);
600 // Create a package structure in memory
601 pack = Mem_Alloc (pak_mempool, sizeof (pack_t));
602 pack->ignorecase = true; // PK3 ignores case
603 strlcpy (pack->filename, packfile, sizeof (pack->filename));
604 pack->handle = packhandle;
605 pack->numfiles = eocd.nbentries;
606 pack->mempool = Mem_AllocPool (packfile, 0, NULL);
607 pack->files = Mem_Alloc (pack->mempool, eocd.nbentries * sizeof(packfile_t));
608 pack->next = packlist;
611 real_nb_files = PK3_BuildFileList (pack, &eocd);
612 if (real_nb_files < 0)
613 Sys_Error ("%s is not a valid PK3 file", packfile);
615 Con_Printf("Added packfile %s (%i files)\n", packfile, real_nb_files);
622 PK3_GetTrueFileOffset
624 Find where the true file data offset is
627 void PK3_GetTrueFileOffset (packfile_t *file, pack_t *pack)
629 qbyte buffer [ZIP_LOCAL_CHUNK_BASE_SIZE];
633 if (file->flags & FILE_FLAG_TRUEOFFS)
636 // Load the local file description
637 #ifdef FS_USESYSCALLS
638 lseek (pack->handle, file->offset, SEEK_SET);
639 count = read (pack->handle, buffer, ZIP_LOCAL_CHUNK_BASE_SIZE);
641 fseek (pack->handle, file->offset, SEEK_SET);
642 count = fread (buffer, 1, ZIP_LOCAL_CHUNK_BASE_SIZE, pack->handle);
644 if (count != ZIP_LOCAL_CHUNK_BASE_SIZE || BuffBigLong (buffer) != ZIP_DATA_HEADER)
645 Sys_Error ("Can't retrieve file %s in package %s", file->name, pack->filename);
647 // Skip name and extra field
648 file->offset += BuffLittleShort (&buffer[26]) + BuffLittleShort (&buffer[28]) + ZIP_LOCAL_CHUNK_BASE_SIZE;
650 file->flags |= FILE_FLAG_TRUEOFFS;
655 =============================================================================
657 OTHER PRIVATE FUNCTIONS
659 =============================================================================
667 Add a file to the list of files contained into a package
670 static packfile_t* FS_AddFileToPack (const char* name, pack_t* pack,
671 size_t offset, size_t packsize,
672 size_t realsize, file_flags_t flags)
674 int (*strcmp_funct) (const char* str1, const char* str2);
675 int left, right, middle;
678 strcmp_funct = pack->ignorecase ? strcasecmp : strcmp;
680 // Look for the slot we should put that file into (binary search)
682 right = pack->numfiles - 1;
683 while (left <= right)
687 middle = (left + right) / 2;
688 diff = strcmp_funct (pack->files[middle].name, name);
690 // If we found the file, there's a problem
692 Sys_Error ("Package %s contains the file %s several times\n",
693 pack->filename, name);
695 // If we're too far in the list
702 // We have to move the right of the list by one slot to free the one we need
703 file = &pack->files[left];
704 memmove (file + 1, file, (pack->numfiles - left) * sizeof (*file));
707 strlcpy (file->name, name, sizeof (file->name));
708 file->offset = offset;
709 file->packsize = packsize;
710 file->realsize = realsize;
721 Only used for FS_Open.
724 void FS_CreatePath (char *path)
728 for (ofs = path+1 ; *ofs ; ofs++)
730 if (*ofs == '/' || *ofs == '\\')
732 // create the directory
748 void FS_Path_f (void)
752 Con_Print("Current search path:\n");
753 for (s=fs_searchpaths ; s ; s=s->next)
757 Con_Printf("%s (%i files)\n", s->pack->filename, s->pack->numfiles);
760 Con_Printf("%s\n", s->filename);
769 Takes an explicit (not game tree related) path to a pak file.
771 Loads the header and directory, adding the files at the beginning
772 of the list so they override previous pack files.
775 pack_t *FS_LoadPackPAK (const char *packfile)
777 dpackheader_t header;
779 #ifdef FS_USESYSCALLS
785 dpackfile_t *info; // temporary alloc, allowing huge pack directories
787 #ifdef FS_USESYSCALLS
788 packhandle = open (packfile, O_RDONLY | O_BINARY);
791 read (packhandle, (void *)&header, sizeof(header));
793 packhandle = fopen (packfile, "rb");
796 fread ((void *)&header, 1, sizeof(header), packhandle);
798 if (memcmp(header.id, "PACK", 4))
799 Sys_Error ("%s is not a packfile", packfile);
800 header.dirofs = LittleLong (header.dirofs);
801 header.dirlen = LittleLong (header.dirlen);
803 if (header.dirlen % sizeof(dpackfile_t))
804 Sys_Error ("%s has an invalid directory size", packfile);
806 numpackfiles = header.dirlen / sizeof(dpackfile_t);
808 if (numpackfiles > MAX_FILES_IN_PACK)
809 Sys_Error ("%s has %i files", packfile, numpackfiles);
811 pack = Mem_Alloc(pak_mempool, sizeof (pack_t));
812 pack->ignorecase = false; // PAK is case sensitive
813 strlcpy (pack->filename, packfile, sizeof (pack->filename));
814 pack->handle = packhandle;
816 pack->mempool = Mem_AllocPool(packfile, 0, NULL);
817 pack->files = Mem_Alloc(pack->mempool, numpackfiles * sizeof(packfile_t));
818 pack->next = packlist;
821 info = Mem_Alloc(tempmempool, sizeof(*info) * numpackfiles);
822 #ifdef FS_USESYSCALLS
823 lseek (packhandle, header.dirofs, SEEK_SET);
824 read (packhandle, (void *)info, header.dirlen);
826 fseek (packhandle, header.dirofs, SEEK_SET);
827 fread ((void *)info, 1, header.dirlen, packhandle);
830 // parse the directory
831 for (i = 0;i < numpackfiles;i++)
833 size_t offset = LittleLong (info[i].filepos);
834 size_t size = LittleLong (info[i].filelen);
836 FS_AddFileToPack (info[i].name, pack, offset, size, size, FILE_FLAG_TRUEOFFS);
841 Con_Printf("Added packfile %s (%i files)\n", packfile, numpackfiles);
850 Sets fs_gamedir, adds the directory to the head of the path,
851 then loads and adds pak1.pak pak2.pak ...
854 void FS_AddGameDirectory (const char *dir)
856 stringlist_t *list, *current;
857 searchpath_t *search;
859 char pakfile[MAX_OSPATH];
861 strlcpy (fs_gamedir, dir, sizeof (fs_gamedir));
863 list = listdirectory(dir);
865 // add any PAK package in the directory
866 for (current = list;current;current = current->next)
868 if (matchpattern(current->text, "*.pak", true))
870 snprintf (pakfile, sizeof (pakfile), "%s/%s", dir, current->text);
871 pak = FS_LoadPackPAK (pakfile);
874 search = Mem_Alloc(pak_mempool, sizeof(searchpath_t));
876 search->next = fs_searchpaths;
877 fs_searchpaths = search;
880 Con_Printf("unable to load pak \"%s\"\n", pakfile);
884 // add any PK3 package in the director
885 for (current = list;current;current = current->next)
887 if (matchpattern(current->text, "*.pk3", true))
889 snprintf (pakfile, sizeof (pakfile), "%s/%s", dir, current->text);
890 pak = FS_LoadPackPK3 (pakfile);
893 search = Mem_Alloc(pak_mempool, sizeof(searchpath_t));
895 search->next = fs_searchpaths;
896 fs_searchpaths = search;
899 Con_Printf("unable to load pak \"%s\"\n", pakfile);
904 // Add the directory to the search path
905 // (unpacked files have the priority over packed files)
906 search = Mem_Alloc(pak_mempool, sizeof(searchpath_t));
907 strlcpy (search->filename, dir, sizeof (search->filename));
908 search->next = fs_searchpaths;
909 fs_searchpaths = search;
918 void FS_AddGameHierarchy (const char *dir)
922 strlcpy (com_modname, dir, sizeof (com_modname));
924 // Add the common game directory
925 FS_AddGameDirectory (va("%s/%s", fs_basedir, dir));
927 // Add the personal game directory
928 homedir = getenv ("HOME");
929 if (homedir != NULL && homedir[0] != '\0')
930 FS_AddGameDirectory (va("%s/.darkplaces/%s", homedir, dir));
939 char *FS_FileExtension (const char *in)
941 static char exten[8];
942 const char *slash, *backslash, *colon, *dot, *separator;
945 slash = strrchr(in, '/');
946 backslash = strrchr(in, '\\');
947 colon = strrchr(in, ':');
948 dot = strrchr(in, '.');
950 if (separator < backslash)
951 separator = backslash;
952 if (separator < colon)
954 if (dot == NULL || dot < separator)
957 for (i = 0;i < 7 && dot[i];i++)
972 searchpath_t *search;
974 fs_mempool = Mem_AllocPool("file management", 0, NULL);
975 pak_mempool = Mem_AllocPool("paks", 0, NULL);
977 Cvar_RegisterVariable (&scr_screenshot_name);
979 Cmd_AddCommand ("path", FS_Path_f);
980 Cmd_AddCommand ("dir", FS_Dir_f);
981 Cmd_AddCommand ("ls", FS_Ls_f);
983 strcpy(fs_basedir, ".");
984 strcpy(fs_gamedir, ".");
989 // Overrides the system supplied base directory (under GAMENAME)
990 // 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)
991 i = COM_CheckParm ("-basedir");
992 if (i && i < com_argc-1)
994 strlcpy (fs_basedir, com_argv[i+1], sizeof (fs_basedir));
995 i = strlen (fs_basedir);
996 if (i > 0 && (fs_basedir[i-1] == '\\' || fs_basedir[i-1] == '/'))
1000 // -path <dir or packfile> [<dir or packfile>] ...
1001 // Fully specifies the exact search path, overriding the generated one
1002 // 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)
1003 i = COM_CheckParm ("-path");
1007 while (++i < com_argc)
1009 if (!com_argv[i] || com_argv[i][0] == '+' || com_argv[i][0] == '-')
1012 search = Mem_Alloc(pak_mempool, sizeof(searchpath_t));
1013 if (!strcasecmp (FS_FileExtension(com_argv[i]), "pak"))
1015 search->pack = FS_LoadPackPAK (com_argv[i]);
1017 Sys_Error ("Couldn't load packfile: %s", com_argv[i]);
1019 else if (!strcasecmp (FS_FileExtension (com_argv[i]), "pk3"))
1021 search->pack = FS_LoadPackPK3 (com_argv[i]);
1023 Sys_Error ("Couldn't load packfile: %s", com_argv[i]);
1026 strlcpy (search->filename, com_argv[i], sizeof (search->filename));
1027 search->next = fs_searchpaths;
1028 fs_searchpaths = search;
1033 // start up with GAMENAME by default (id1)
1034 FS_AddGameHierarchy (GAMENAME);
1035 Cvar_SetQuick (&scr_screenshot_name, gamescreenshotname);
1037 // add the game-specific path, if any
1041 FS_AddGameHierarchy (gamedirname);
1045 // Adds basedir/gamedir as an override game
1046 // LordHavoc: now supports multiple -game directories
1047 for (i = 1;i < com_argc;i++)
1051 if (!strcmp (com_argv[i], "-game") && i < com_argc-1)
1055 FS_AddGameHierarchy (com_argv[i]);
1056 Cvar_SetQuick (&scr_screenshot_name, com_modname);
1060 // If "-condebug" is in the command line, remove the previous log file
1061 if (COM_CheckParm ("-condebug") != 0)
1062 unlink (va("%s/qconsole.log", fs_gamedir));
1070 void FS_Shutdown (void)
1072 Mem_FreePool (&pak_mempool);
1073 Mem_FreePool (&fs_mempool);
1077 ====================
1080 Internal function used to create a qfile_t and open the relevant file on disk
1081 ====================
1083 static qfile_t* FS_SysOpen (const char* filepath, const char* mode)
1087 file = Mem_Alloc (fs_mempool, sizeof (*file));
1088 memset (file, 0, sizeof (*file));
1090 #ifdef FS_USESYSCALLS
1091 if (strchr(mode, 'r'))
1092 file->stream = open (filepath, O_RDONLY | O_BINARY);
1093 else if (strchr(mode, 'w'))
1094 file->stream = open (filepath, O_WRONLY | O_BINARY | O_CREAT | O_TRUNC, 0666);
1095 else if (strchr(mode, 'a'))
1096 file->stream = open (filepath, O_RDWR | O_BINARY | O_CREAT | O_APPEND, 0666);
1099 if (file->stream < 0)
1105 file->stream = fopen (filepath, mode);
1122 qfile_t *FS_OpenRead (const char *path, int offs, int len)
1126 file = FS_SysOpen (path, "rb");
1129 Sys_Error ("Couldn't open %s", path);
1134 if (offs < 0 || len < 0)
1136 // We set fs_filesize here for normal files
1137 #ifdef FS_USESYSCALLS
1138 fs_filesize = lseek (file->stream, 0, SEEK_END);
1139 lseek (file->stream, 0, SEEK_SET);
1141 fseek (file->stream, 0, SEEK_END);
1142 fs_filesize = ftell (file->stream);
1143 fseek (file->stream, 0, SEEK_SET);
1149 #ifdef FS_USESYSCALLS
1150 lseek (file->stream, offs, SEEK_SET);
1152 fseek (file->stream, offs, SEEK_SET);
1155 file->flags |= FS_FLAG_PACKED;
1157 file->offset = offs;
1165 ====================
1168 Return true if the path should be rejected due to one of the following:
1169 1: path elements that are non-portable
1170 2: path elements that would allow access to files outside the game directory,
1171 or are just not a good idea for a mod to be using.
1172 ====================
1174 int FS_CheckNastyPath (const char *path)
1176 // Windows: don't allow \ in filenames (windows-only), period.
1177 // (on Windows \ is a directory separator, but / is also supported)
1178 if (strstr(path, "\\"))
1179 return 1; // non-portable
1180 // Mac: don't allow Mac-only filenames - : is a directory separator
1181 // instead of /, but we rely on / working already, so there's no reason to
1182 // support a Mac-only path
1183 // Amiga and Windows: : tries to go to root of drive
1184 if (strstr(path, ":"))
1185 return 1; // non-portable attempt to go to root of drive
1186 // Amiga: // is parent directory
1187 if (strstr(path, "//"))
1188 return 1; // non-portable attempt to go to parent directory
1189 // all: don't allow going to current directory (./) or parent directory (../ or /../)
1190 if (strstr(path, "./"))
1191 return 2; // attempt to go outside the game directory
1192 // Windows and UNIXes: don't allow absolute paths
1194 return 2; // attempt to go outside the game directory
1195 // after all these checks we're pretty sure it's a / separated filename
1196 // and won't do much if any harm
1202 ====================
1205 Look for a file in the packages and in the filesystem
1207 Return the searchpath where the file was found (or NULL)
1208 and the file index in the package if relevant
1209 ====================
1211 static searchpath_t *FS_FindFile (const char *name, int* index, qboolean quiet)
1213 searchpath_t *search;
1216 // search through the path, one element at a time
1217 for (search = fs_searchpaths;search;search = search->next)
1219 // is the element a pak file?
1222 int (*strcmp_funct) (const char* str1, const char* str2);
1223 int left, right, middle;
1226 strcmp_funct = pak->ignorecase ? strcasecmp : strcmp;
1228 // Look for the file (binary search)
1230 right = pak->numfiles - 1;
1231 while (left <= right)
1235 middle = (left + right) / 2;
1236 diff = strcmp_funct (pak->files[middle].name, name);
1242 Con_DPrintf("FS_FindFile: %s in %s\n",
1243 pak->files[middle].name, pak->filename);
1250 // If we're too far in the list
1259 char netpath[MAX_OSPATH];
1260 snprintf(netpath, sizeof(netpath), "%s/%s", search->filename, name);
1261 if (FS_SysFileExists (netpath))
1264 Con_DPrintf("FS_FindFile: %s\n", netpath);
1274 Con_DPrintf("FS_FindFile: can't find %s\n", name);
1286 If the requested file is inside a packfile, a new qfile_t* will be opened
1292 qfile_t *FS_FOpenFile (const char *filename, qboolean quiet)
1294 searchpath_t *search;
1295 packfile_t *packfile;
1299 search = FS_FindFile (filename, &i, quiet);
1308 // Found in the filesystem?
1311 char netpath[MAX_OSPATH];
1312 snprintf(netpath, sizeof(netpath), "%s/%s", search->filename, filename);
1313 return FS_OpenRead(netpath, -1, -1);
1316 // So, we found it in a package...
1317 packfile = &search->pack->files[i];
1319 // If we don't have the true offset, get it now
1320 if (! (packfile->flags & FILE_FLAG_TRUEOFFS))
1321 PK3_GetTrueFileOffset (packfile, search->pack);
1323 // No Zlib DLL = no compressed files
1324 if (!zlib_dll && (packfile->flags & FILE_FLAG_DEFLATED))
1326 Con_Printf("WARNING: can't open the compressed file %s\n"
1327 "You need the Zlib DLL to use compressed files\n",
1333 // open a new file in the pakfile
1334 file = FS_OpenRead (search->pack->filename, packfile->offset, packfile->packsize);
1335 fs_filesize = packfile->realsize;
1337 if (packfile->flags & FILE_FLAG_DEFLATED)
1341 file->flags |= FS_FLAG_DEFLATED;
1343 // We need some more variables
1344 ztk = Mem_Alloc (fs_mempool, sizeof (*file->z));
1346 ztk->real_length = packfile->realsize;
1348 // Initialize zlib stream
1349 ztk->zstream.next_in = ztk->input;
1350 ztk->zstream.avail_in = 0;
1352 /* From Zlib's "unzip.c":
1354 * windowBits is passed < 0 to tell that there is no zlib header.
1355 * Note that in this case inflate *requires* an extra "dummy" byte
1356 * after the compressed stream in order to complete decompression and
1357 * return Z_STREAM_END.
1358 * In unzip, i don't wait absolutely Z_STREAM_END because I known the
1359 * size of both compressed and uncompressed data
1361 if (qz_inflateInit2 (&ztk->zstream, -MAX_WBITS) != Z_OK)
1362 Sys_Error ("inflate init error (file: %s)", filename);
1364 ztk->zstream.next_out = ztk->output;
1365 ztk->zstream.avail_out = sizeof (ztk->output);
1375 =============================================================================
1377 MAIN PUBLIC FUNCTIONS
1379 =============================================================================
1383 ====================
1386 Open a file. The syntax is the same as fopen
1387 ====================
1389 qfile_t* FS_Open (const char* filepath, const char* mode, qboolean quiet)
1391 if (FS_CheckNastyPath(filepath))
1393 Con_Printf("FS_Open(\"%s\", \"%s\", %s): nasty filename rejected\n", filepath, mode, quiet ? "true" : "false");
1397 // If the file is opened in "write" or "append" mode
1398 if (strchr (mode, 'w') || strchr (mode, 'a'))
1400 char real_path [MAX_OSPATH];
1402 // Open the file on disk directly
1403 snprintf (real_path, sizeof (real_path), "%s/%s", fs_gamedir, filepath);
1405 // Create directories up to the file
1406 FS_CreatePath (real_path);
1408 return FS_SysOpen (real_path, mode);
1411 // Else, we look at the various search paths
1412 return FS_FOpenFile (filepath, quiet);
1417 ====================
1421 ====================
1423 int FS_Close (qfile_t* file)
1425 #ifdef FS_USESYSCALLS
1426 if (close (file->stream))
1428 if (fclose (file->stream))
1434 qz_inflateEnd (&file->z->zstream);
1444 ====================
1447 Write "datasize" bytes into a file
1448 ====================
1450 size_t FS_Write (qfile_t* file, const void* data, size_t datasize)
1452 #ifdef FS_USESYSCALLS
1453 return write (file->stream, data, datasize);
1455 return fwrite (data, 1, datasize, file->stream);
1461 ====================
1464 Read up to "buffersize" bytes from a file
1465 ====================
1467 size_t FS_Read (qfile_t* file, void* buffer, size_t buffersize)
1472 // Quick path for unpacked files
1473 if (! (file->flags & FS_FLAG_PACKED))
1474 #ifdef FS_USESYSCALLS
1475 return read (file->stream, buffer, buffersize);
1477 return fread (buffer, 1, buffersize, file->stream);
1480 // If the file isn't compressed
1481 if (! (file->flags & FS_FLAG_DEFLATED))
1483 // We must take care to not read after the end of the file
1484 count = file->length - file->position;
1485 if (buffersize > count)
1488 #ifdef FS_USESYSCALLS
1489 nb = read (file->stream, buffer, buffersize);
1491 nb = fread (buffer, 1, buffersize, file->stream);
1494 file->position += nb;
1498 // If the file is compressed, it's more complicated...
1501 // First, we copy as many bytes as we can from "output"
1502 if (ztk->out_ind < ztk->out_max)
1504 count = ztk->out_max - ztk->out_ind;
1506 nb = (buffersize > count) ? count : buffersize;
1507 memcpy (buffer, &ztk->output[ztk->out_ind], nb);
1509 file->position += nb;
1514 // We cycle through a few operations until we have inflated enough data
1515 while (nb < buffersize)
1517 // NOTE: at this point, "output" should always be empty
1519 // If "input" is also empty, we need to fill it
1520 if (ztk->in_ind == ztk->in_max)
1524 // If we are at the end of the file
1525 if (ztk->out_position == ztk->real_length)
1528 remain = file->length - ztk->in_position;
1529 count = (remain > sizeof (ztk->input)) ? sizeof (ztk->input) : remain;
1530 #ifdef FS_USESYSCALLS
1531 read (file->stream, ztk->input, count);
1533 fread (ztk->input, 1, count, file->stream);
1536 // Update indexes and counters
1538 ztk->in_max = count;
1539 ztk->in_position += count;
1542 // Now that we are sure we have compressed data available, we need to determine
1543 // if it's better to inflate it in "output" or directly in "buffer" (we are in this
1544 // case if we still need more bytes than "output" can contain)
1546 ztk->zstream.next_in = &ztk->input[ztk->in_ind];
1547 ztk->zstream.avail_in = ztk->in_max - ztk->in_ind;
1549 // If output will be able to contain at least 1 more byte than the data we need
1550 if (buffersize - nb < sizeof (ztk->output))
1554 // Inflate the data in "output"
1555 ztk->zstream.next_out = ztk->output;
1556 ztk->zstream.avail_out = sizeof (ztk->output);
1557 error = qz_inflate (&ztk->zstream, Z_SYNC_FLUSH);
1558 if (error != Z_OK && error != Z_STREAM_END)
1559 Sys_Error ("Can't inflate file");
1560 ztk->in_ind = ztk->in_max - ztk->zstream.avail_in;
1561 ztk->out_max = sizeof (ztk->output) - ztk->zstream.avail_out;
1562 ztk->out_position += ztk->out_max;
1564 // Copy the requested data in "buffer" (as much as we can)
1565 count = (buffersize - nb > ztk->out_max) ? ztk->out_max : buffersize - nb;
1566 memcpy (&((qbyte*)buffer)[nb], ztk->output, count);
1567 ztk->out_ind = count;
1570 // Else, we inflate directly in "buffer"
1575 // Inflate the data in "buffer"
1576 ztk->zstream.next_out = &((qbyte*)buffer)[nb];
1577 ztk->zstream.avail_out = buffersize - nb;
1578 error = qz_inflate (&ztk->zstream, Z_SYNC_FLUSH);
1579 if (error != Z_OK && error != Z_STREAM_END)
1580 Sys_Error ("Can't inflate file");
1581 ztk->in_ind = ztk->in_max - ztk->zstream.avail_in;
1583 // Invalidate the output data (for FS_Seek)
1587 // How much data did it inflate?
1588 count = buffersize - nb - ztk->zstream.avail_out;
1589 ztk->out_position += count;
1593 file->position += count;
1601 ====================
1604 Flush the file output stream
1605 ====================
1607 int FS_Flush (qfile_t* file)
1609 #ifdef FS_USESYSCALLS
1612 return fflush (file->stream);
1618 ====================
1621 Print a string into a file
1622 ====================
1624 int FS_Print(qfile_t* file, const char *msg)
1626 return FS_Write(file, msg, strlen(msg));
1630 ====================
1633 Print a string into a file
1634 ====================
1636 int FS_Printf(qfile_t* file, const char* format, ...)
1641 va_start (args, format);
1642 result = FS_VPrintf(file, format, args);
1650 ====================
1653 Print a string into a file
1654 ====================
1656 int FS_VPrintf(qfile_t* file, const char* format, va_list ap)
1658 #ifdef FS_USESYSCALLS
1661 char tempstring[1024];
1662 len = vsnprintf (tempstring, sizeof(tempstring), format, ap);
1663 if (len >= sizeof(tempstring))
1666 char *temp = Mem_Alloc(tempmempool, len + 1);
1667 len = vsnprintf (temp, len + 1, format, ap);
1668 result = write (file->stream, temp, len);
1673 return write (file->stream, tempstring, len);
1676 return vfprintf (file->stream, format, ap);
1682 ====================
1685 Get the next character of a file
1686 ====================
1688 int FS_Getc (qfile_t* file)
1692 if (FS_Read (file, &c, 1) != 1)
1700 ====================
1703 Move the position index in a file
1704 ====================
1706 int FS_Seek (qfile_t* file, long offset, int whence)
1708 // Quick path for unpacked files
1709 if (! (file->flags & FS_FLAG_PACKED))
1710 #ifdef FS_USESYSCALLS
1712 if (lseek (file->stream, offset, whence) == -1)
1717 return fseek (file->stream, offset, whence);
1720 // Seeking in compressed files is more a hack than anything else,
1721 // but we need to support it, so here it is.
1722 if (file->flags & FS_FLAG_DEFLATED)
1724 ztoolkit_t *ztk = file->z;
1725 qbyte buffer [sizeof (ztk->output)]; // it's big to force inflating into buffer directly
1730 offset += file->position;
1737 offset += ztk->real_length;
1743 if (offset < 0 || offset > (long) ztk->real_length)
1746 // If we need to go back in the file
1747 if (offset <= (long) file->position)
1749 // If we still have the data we need in the output buffer
1750 if (file->position - offset <= ztk->out_ind)
1752 ztk->out_ind -= file->position - offset;
1753 file->position = offset;
1757 // Else, we restart from the beginning of the file
1760 ztk->in_position = 0;
1763 ztk->out_position = 0;
1765 #ifdef FS_USESYSCALLS
1766 lseek (file->stream, file->offset, SEEK_SET);
1768 fseek (file->stream, file->offset, SEEK_SET);
1771 // Reset the Zlib stream
1772 ztk->zstream.next_in = ztk->input;
1773 ztk->zstream.avail_in = 0;
1774 qz_inflateReset (&ztk->zstream);
1777 // Skip all data until we reach the requested offset
1778 while ((long) file->position < offset)
1780 size_t diff = offset - file->position;
1783 count = (diff > sizeof (buffer)) ? sizeof (buffer) : diff;
1784 len = FS_Read (file, buffer, count);
1792 // Packed files receive a special treatment too, because
1793 // we need to make sure it doesn't go outside of the file
1797 offset += file->position;
1804 offset += file->length;
1810 if (offset < 0 || offset > (long) file->length)
1813 #ifdef FS_USESYSCALLS
1814 if (lseek (file->stream, file->offset + offset, SEEK_SET) == -1)
1817 if (fseek (file->stream, file->offset + offset, SEEK_SET) == -1)
1820 file->position = offset;
1826 ====================
1829 Give the current position in a file
1830 ====================
1832 long FS_Tell (qfile_t* file)
1834 if (file->flags & FS_FLAG_PACKED)
1835 return file->position;
1837 #ifdef FS_USESYSCALLS
1838 return lseek (file->stream, 0, SEEK_CUR);
1840 return ftell (file->stream);
1846 ====================
1849 Extract a line from a file
1850 ====================
1852 char* FS_Gets (qfile_t* file, char* buffer, int buffersize)
1856 // Quick path for unpacked files
1857 #ifndef FS_USESYSCALLS
1858 if (! (file->flags & FS_FLAG_PACKED))
1859 return fgets (buffer, buffersize, file->stream);
1862 for (ind = 0; ind < (size_t) buffersize - 1; ind++)
1864 int c = FS_Getc (file);
1879 buffer[ind + 1] = '\0';
1888 buffer[buffersize - 1] = '\0';
1897 Dynamic length version of fgets. DO NOT free the buffer.
1900 char *FS_Getline (qfile_t *file)
1902 static int size = 256;
1903 static char *buf = 0;
1908 buf = Mem_Alloc (fs_mempool, size);
1910 if (!FS_Gets (file, buf, size))
1914 while (buf[len - 1] != '\n' && buf[len - 1] != '\r')
1916 t = Mem_Alloc (fs_mempool, size + 256);
1917 memcpy(t, buf, size);
1921 if (!FS_Gets (file, buf + len, size - len))
1925 while ((len = strlen(buf)) && (buf[len - 1] == '\n' || buf[len - 1] == '\r'))
1932 ====================
1935 Extract a line from a file
1936 ====================
1938 // FIXME: remove this function?
1939 int FS_Eof (qfile_t* file)
1941 if (file->flags & FS_FLAG_PACKED)
1943 if (file->flags & FS_FLAG_DEFLATED)
1944 return (file->position == file->z->real_length);
1946 return (file->position == file->length);
1949 #ifdef FS_USESYSCALLS
1950 Sys_Error("FS_Eof: not implemented using syscalls\n");
1953 return feof (file->stream);
1962 Filename are relative to the quake directory.
1963 Always appends a 0 byte.
1966 qbyte *FS_LoadFile (const char *path, mempool_t *pool, qboolean quiet)
1971 // look for it in the filesystem or pack files
1972 h = FS_Open (path, "rb", quiet);
1976 buf = Mem_Alloc(pool, fs_filesize+1);
1978 Sys_Error ("FS_LoadFile: not enough available memory for %s (size %i)", path, fs_filesize);
1980 ((qbyte *)buf)[fs_filesize] = 0;
1982 FS_Read (h, buf, fs_filesize);
1993 The filename will be prefixed by the current game directory
1996 qboolean FS_WriteFile (const char *filename, void *data, int len)
2000 handle = FS_Open (filename, "wb", false);
2003 Con_Printf("FS_WriteFile: failed on %s\n", filename);
2007 Con_DPrintf("FS_WriteFile: %s\n", filename);
2008 FS_Write (handle, data, len);
2015 =============================================================================
2017 OTHERS PUBLIC FUNCTIONS
2019 =============================================================================
2027 void FS_StripExtension (const char *in, char *out, size_t size_out)
2034 while (*in && size_out > 1)
2038 else if (*in == '/' || *in == '\\' || *in == ':')
2055 void FS_DefaultExtension (char *path, const char *extension, size_t size_path)
2059 // if path doesn't have a .EXT, append extension
2060 // (extension should include the .)
2061 src = path + strlen(path) - 1;
2063 while (*src != '/' && src != path)
2066 return; // it has an extension
2070 strlcat (path, extension, size_path);
2078 Look for a file in the packages and in the filesystem
2081 qboolean FS_FileExists (const char *filename)
2083 return (FS_FindFile (filename, NULL, true) != NULL);
2091 Look for a file in the filesystem only
2094 qboolean FS_SysFileExists (const char *path)
2099 f = fopen (path, "rb");
2110 if (stat (path,&buf) == -1)
2117 void FS_mkdir (const char *path)
2130 Allocate and fill a search structure with information on matching filenames.
2133 fssearch_t *FS_Search(const char *pattern, int caseinsensitive, int quiet)
2136 searchpath_t *searchpath;
2138 int i, basepathlength, numfiles, numchars;
2139 stringlist_t *dir, *dirfile, *liststart, *listcurrent, *listtemp;
2140 const char *slash, *backslash, *colon, *separator;
2142 char netpath[MAX_OSPATH];
2143 char temp[MAX_OSPATH];
2145 while(!strncmp(pattern, "./", 2))
2147 while(!strncmp(pattern, ".\\", 2))
2154 slash = strrchr(pattern, '/');
2155 backslash = strrchr(pattern, '\\');
2156 colon = strrchr(pattern, ':');
2157 separator = pattern;
2158 if (separator < slash)
2160 if (separator < backslash)
2161 separator = backslash;
2162 if (separator < colon)
2164 basepathlength = separator - pattern;
2165 basepath = Mem_Alloc (tempmempool, basepathlength + 1);
2167 memcpy(basepath, pattern, basepathlength);
2168 basepath[basepathlength] = 0;
2170 // search through the path, one element at a time
2171 for (searchpath = fs_searchpaths;searchpath;searchpath = searchpath->next)
2173 // is the element a pak file?
2174 if (searchpath->pack)
2176 // look through all the pak file elements
2177 pak = searchpath->pack;
2178 for (i = 0;i < pak->numfiles;i++)
2180 strcpy(temp, pak->files[i].name);
2183 if (matchpattern(temp, (char *)pattern, true))
2185 for (listtemp = liststart;listtemp;listtemp = listtemp->next)
2186 if (!strcmp(listtemp->text, temp))
2188 if (listtemp == NULL)
2190 listcurrent = stringlistappend(listcurrent, temp);
2191 if (liststart == NULL)
2192 liststart = listcurrent;
2194 Con_DPrintf("SearchPackFile: %s : %s\n", pak->filename, temp);
2197 // strip off one path element at a time until empty
2198 // this way directories are added to the listing if they match the pattern
2199 slash = strrchr(temp, '/');
2200 backslash = strrchr(temp, '\\');
2201 colon = strrchr(temp, ':');
2203 if (separator < slash)
2205 if (separator < backslash)
2206 separator = backslash;
2207 if (separator < colon)
2209 *((char *)separator) = 0;
2215 // get a directory listing and look at each name
2216 snprintf(netpath, sizeof (netpath), "%s/%s", searchpath->filename, basepath);
2217 if ((dir = listdirectory(netpath)))
2219 for (dirfile = dir;dirfile;dirfile = dirfile->next)
2221 snprintf(temp, sizeof(temp), "%s/%s", basepath, dirfile->text);
2222 if (matchpattern(temp, (char *)pattern, true))
2224 for (listtemp = liststart;listtemp;listtemp = listtemp->next)
2225 if (!strcmp(listtemp->text, temp))
2227 if (listtemp == NULL)
2229 listcurrent = stringlistappend(listcurrent, temp);
2230 if (liststart == NULL)
2231 liststart = listcurrent;
2233 Con_DPrintf("SearchDirFile: %s\n", temp);
2244 liststart = stringlistsort(liststart);
2247 for (listtemp = liststart;listtemp;listtemp = listtemp->next)
2250 numchars += strlen(listtemp->text) + 1;
2252 search = Z_Malloc(sizeof(fssearch_t) + numchars + numfiles * sizeof(char *));
2253 search->filenames = (char **)((char *)search + sizeof(fssearch_t));
2254 search->filenamesbuffer = (char *)((char *)search + sizeof(fssearch_t) + numfiles * sizeof(char *));
2255 search->numfilenames = numfiles;
2258 for (listtemp = liststart;listtemp;listtemp = listtemp->next)
2260 search->filenames[numfiles] = search->filenamesbuffer + numchars;
2261 strcpy(search->filenames[numfiles], listtemp->text);
2263 numchars += strlen(listtemp->text) + 1;
2266 stringlistfree(liststart);
2273 void FS_FreeSearch(fssearch_t *search)
2278 extern int con_linewidth;
2279 int FS_ListDirectory(const char *pattern, int oneperline)
2290 search = FS_Search(pattern, true, true);
2293 numfiles = search->numfilenames;
2296 // FIXME: the names could be added to one column list and then
2297 // gradually shifted into the next column if they fit, and then the
2298 // next to make a compact variable width listing but it's a lot more
2300 // find width for columns
2302 for (i = 0;i < numfiles;i++)
2304 l = strlen(search->filenames[i]);
2305 if (columnwidth < l)
2308 // count the spacing character
2310 // calculate number of columns
2311 numcolumns = con_linewidth / columnwidth;
2312 // don't bother with the column printing if it's only one column
2313 if (numcolumns >= 2)
2315 numlines = (numfiles + numcolumns - 1) / numcolumns;
2316 for (i = 0;i < numlines;i++)
2319 for (k = 0;k < numcolumns;k++)
2321 l = i * numcolumns + k;
2324 name = search->filenames[l];
2325 for (j = 0;name[j] && j < (int)sizeof(linebuf) - 1;j++)
2326 linebuf[linebufpos++] = name[j];
2327 // space out name unless it's the last on the line
2328 if (k < (numcolumns - 1) && l < (numfiles - 1))
2329 for (;j < columnwidth && j < (int)sizeof(linebuf) - 1;j++)
2330 linebuf[linebufpos++] = ' ';
2333 linebuf[linebufpos] = 0;
2334 Con_Printf("%s\n", linebuf);
2341 for (i = 0;i < numfiles;i++)
2342 Con_Printf("%s\n", search->filenames[i]);
2343 FS_FreeSearch(search);
2347 static void FS_ListDirectoryCmd (const char* cmdname, int oneperline)
2349 const char *pattern;
2352 Con_Printf("usage:\n%s [path/pattern]\n", cmdname);
2355 if (Cmd_Argc() == 2)
2356 pattern = Cmd_Argv(1);
2359 if (!FS_ListDirectory(pattern, oneperline))
2360 Con_Print("No files found.\n");
2365 FS_ListDirectoryCmd("dir", true);
2370 FS_ListDirectoryCmd("ls", false);