X-Git-Url: http://de.git.xonotic.org/?a=blobdiff_plain;f=fs.c;h=157a9c7b7cd501c6c92634efd06187b6e363c984;hb=82a3d4bc9eaf276b248205567c254bea70c97a94;hp=33ccc73c033a2f250dd89e2ab209c68f3fb2d2a4;hpb=3781ea12c92801177ab06db1a816de94c6bc696c;p=xonotic%2Fdarkplaces.git diff --git a/fs.c b/fs.c index 33ccc73c..157a9c7b 100644 --- a/fs.c +++ b/fs.c @@ -1,5 +1,5 @@ /* - Quake file system + DarkPlaces file system Copyright (C) 2003 Mathieu Olivier Copyright (C) 1999,2000 contributors of the QuakeForge project @@ -25,8 +25,6 @@ #include "quakedef.h" -#include -#include #include #include @@ -45,6 +43,17 @@ #include "fs.h" +// use syscalls instead of f* functions +#define FS_USESYSCALLS + +// Win32 requires us to add O_BINARY, but the other OSes don't have it +#ifdef FS_USESYSCALLS +# ifndef O_BINARY +# define O_BINARY 0 +# endif +#endif + + /* All of Quake's data access is through a hierchal file system, but the contents @@ -66,6 +75,33 @@ over areas they shouldn't. */ +/* +============================================================================= + +CONSTANTS + +============================================================================= +*/ + +// Magic numbers of a ZIP file (big-endian format) +#define ZIP_DATA_HEADER 0x504B0304 // "PK\3\4" +#define ZIP_CDIR_HEADER 0x504B0102 // "PK\1\2" +#define ZIP_END_HEADER 0x504B0506 // "PK\5\6" + +// Other constants for ZIP files +#define ZIP_MAX_COMMENTS_SIZE ((unsigned short)0xFFFF) +#define ZIP_END_CDIR_SIZE 22 +#define ZIP_CDIR_CHUNK_BASE_SIZE 46 +#define ZIP_LOCAL_CHUNK_BASE_SIZE 30 + +// Zlib constants (from zlib.h) +#define Z_SYNC_FLUSH 2 +#define MAX_WBITS 15 +#define Z_OK 0 +#define Z_STREAM_END 1 +#define ZLIB_VERSION "1.1.4" + + /* ============================================================================= @@ -74,25 +110,86 @@ TYPES ============================================================================= */ +// Zlib stream (from zlib.h) +// Warning: some pointers we don't use directly have +// been cast to "void*" for a matter of simplicity +typedef struct +{ + qbyte *next_in; // next input byte + unsigned int avail_in; // number of bytes available at next_in + unsigned long total_in; // total nb of input bytes read so far + + qbyte *next_out; // next output byte should be put there + unsigned int avail_out; // remaining free space at next_out + unsigned long total_out; // total nb of bytes output so far + + char *msg; // last error message, NULL if no error + void *state; // not visible by applications + + void *zalloc; // used to allocate the internal state + void *zfree; // used to free the internal state + void *opaque; // private data object passed to zalloc and zfree + + int data_type; // best guess about the data type: ascii or binary + unsigned long adler; // adler32 value of the uncompressed data + unsigned long reserved; // reserved for future use +} z_stream; + + // Our own file structure on top of FILE typedef enum { FS_FLAG_NONE = 0, - FS_FLAG_PACKED = (1 << 0) // inside a package (PAK or PK3) -// FS_FLAG_COMPRESSED = (1 << 1) // compressed (inside a PK3 file) + FS_FLAG_PACKED = (1 << 0), // inside a package (PAK or PK3) + FS_FLAG_DEFLATED = (1 << 1) // file is compressed using the deflate algorithm (PK3 only) } fs_flags_t; +#define ZBUFF_SIZE 1024 +typedef struct +{ + z_stream zstream; + size_t real_length; // length of the uncompressed file + size_t in_ind, in_max; // input buffer index and counter + size_t in_position; // position in the compressed file + size_t out_ind, out_max; // output buffer index and counter + size_t out_position; // how many bytes did we uncompress until now? + qbyte input [ZBUFF_SIZE]; + qbyte output [ZBUFF_SIZE]; +} ztoolkit_t; + struct qfile_s { fs_flags_t flags; +#ifdef FS_USESYSCALLS + int stream; +#else FILE* stream; - size_t length; // file size (PACKED only) +#endif + size_t length; // file size on disk (PACKED only) size_t offset; // offset into a package (PACKED only) size_t position; // current position in the file (PACKED only) + ztoolkit_t* z; // used for inflating (DEFLATED only) }; -// PAK files on disk +// ------ PK3 files on disk ------ // + +// You can get the complete ZIP format description from PKWARE website + +typedef struct +{ + unsigned int signature; + unsigned short disknum; + unsigned short cdir_disknum; // number of the disk with the start of the central directory + unsigned short localentries; // number of entries in the central directory on this disk + unsigned short nbentries; // total number of entries in the central directory on this disk + unsigned int cdir_size; // size of the central directory + unsigned int cdir_offset; // with respect to the starting disk number + unsigned short comment_size; +} pk3_endOfCentralDir_t; + + +// ------ PAK files on disk ------ // typedef struct { char name[56]; @@ -108,16 +205,31 @@ typedef struct // Packages in memory +typedef enum +{ + FILE_FLAG_NONE = 0, + FILE_FLAG_TRUEOFFS = (1 << 0), // the offset in packfile_t is the true contents offset + FILE_FLAG_DEFLATED = (1 << 1) // file compressed using the deflate algorithm +} file_flags_t; + typedef struct { - char name[MAX_QPATH]; - int filepos, filelen; + char name [MAX_QPATH]; + file_flags_t flags; + size_t offset; + size_t packsize; // size in the package + size_t realsize; // real file size (uncompressed) } packfile_t; typedef struct pack_s { - char filename[MAX_OSPATH]; + char filename [MAX_OSPATH]; +#ifdef FS_USESYSCALLS + int handle; +#else FILE *handle; +#endif + int ignorecase; // PK3 ignores case int numfiles; packfile_t *files; mempool_t *mempool; @@ -135,6 +247,22 @@ typedef struct searchpath_s } searchpath_t; +/* +============================================================================= + +FUNCTION PROTOTYPES + +============================================================================= +*/ + +void FS_Dir_f(void); +void FS_Ls_f(void); + +static packfile_t* FS_AddFileToPack (const char* name, pack_t* pack, + size_t offset, size_t packsize, + size_t realsize, file_flags_t flags); + + /* ============================================================================= @@ -150,10 +278,9 @@ int fs_filesize; pack_t *packlist = NULL; -searchpath_t *fs_searchpaths; +searchpath_t *fs_searchpaths = NULL; -// LordHavoc: was 2048, increased to 65536 and changed info[MAX_PACK_FILES] to a temporary alloc -#define MAX_FILES_IN_PACK 65536 +#define MAX_FILES_IN_PACK 65536 char fs_gamedir[MAX_OSPATH]; char fs_basedir[MAX_OSPATH]; @@ -164,17 +291,427 @@ qboolean fs_modified; // set true if using non-id files /* ============================================================================= -PRIVATE FUNCTIONS +PRIVATE FUNCTIONS - PK3 HANDLING ============================================================================= */ +// Functions exported from zlib +#ifdef WIN32 +# define ZEXPORT WINAPI +#else +# define ZEXPORT +#endif + +static int (ZEXPORT *qz_inflate) (z_stream* strm, int flush); +static int (ZEXPORT *qz_inflateEnd) (z_stream* strm); +static int (ZEXPORT *qz_inflateInit2_) (z_stream* strm, int windowBits, const char *version, int stream_size); +static int (ZEXPORT *qz_inflateReset) (z_stream* strm); + +#define qz_inflateInit2(strm, windowBits) \ + qz_inflateInit2_((strm), (windowBits), ZLIB_VERSION, sizeof(z_stream)) + +static dllfunction_t zlibfuncs[] = +{ + {"inflate", (void **) &qz_inflate}, + {"inflateEnd", (void **) &qz_inflateEnd}, + {"inflateInit2_", (void **) &qz_inflateInit2_}, + {"inflateReset", (void **) &qz_inflateReset}, + {NULL, NULL} +}; + +// Handle for Zlib DLL +static dllhandle_t zlib_dll = NULL; + + +/* +==================== +PK3_CloseLibrary + +Unload the Zlib DLL +==================== +*/ +void PK3_CloseLibrary (void) +{ + Sys_UnloadLibrary (&zlib_dll); +} + + +/* +==================== +PK3_OpenLibrary + +Try to load the Zlib DLL +==================== +*/ +qboolean PK3_OpenLibrary (void) +{ + const char* dllname; + + // Already loaded? + if (zlib_dll) + return true; + +#ifdef WIN32 + dllname = "zlib.dll"; +#else + dllname = "libz.so"; +#endif + + // Load the DLL + if (! Sys_LoadLibrary (dllname, &zlib_dll, zlibfuncs)) + { + Con_Printf ("Compressed files support disabled\n"); + return false; + } + + Con_Printf ("Compressed files support enabled\n"); + return true; +} + + +/* +==================== +PK3_GetEndOfCentralDir + +Extract the end of the central directory from a PK3 package +==================== +*/ +#ifdef FS_USESYSCALLS +qboolean PK3_GetEndOfCentralDir (const char *packfile, int packhandle, pk3_endOfCentralDir_t *eocd) +#else +qboolean PK3_GetEndOfCentralDir (const char *packfile, FILE *packhandle, pk3_endOfCentralDir_t *eocd) +#endif +{ + long filesize, maxsize; + qbyte *buffer, *ptr; + int ind; + + // Get the package size +#ifdef FS_USESYSCALLS + filesize = lseek (packhandle, 0, SEEK_END); +#else + fseek (packhandle, 0, SEEK_END); + filesize = ftell(packhandle); +#endif + if (filesize < ZIP_END_CDIR_SIZE) + return false; + + // Load the end of the file in memory + if (filesize < ZIP_MAX_COMMENTS_SIZE + ZIP_END_CDIR_SIZE) + maxsize = filesize; + else + maxsize = ZIP_MAX_COMMENTS_SIZE + ZIP_END_CDIR_SIZE; + buffer = Mem_Alloc (tempmempool, maxsize); +#ifdef FS_USESYSCALLS + lseek (packhandle, filesize - maxsize, SEEK_SET); + if (read (packhandle, buffer, maxsize) != (ssize_t) maxsize) +#else + fseek (packhandle, filesize - maxsize, SEEK_SET); + if (fread (buffer, 1, maxsize, packhandle) != (size_t) maxsize) +#endif + { + Mem_Free (buffer); + return false; + } + + // Look for the end of central dir signature around the end of the file + maxsize -= ZIP_END_CDIR_SIZE; + ptr = &buffer[maxsize]; + ind = 0; + while (BuffBigLong (ptr) != ZIP_END_HEADER) + { + if (ind == maxsize) + { + Mem_Free (buffer); + return false; + } + + ind++; + ptr--; + } + + memcpy (eocd, ptr, ZIP_END_CDIR_SIZE); + eocd->signature = LittleLong (eocd->signature); + eocd->disknum = LittleShort (eocd->disknum); + eocd->cdir_disknum = LittleShort (eocd->cdir_disknum); + eocd->localentries = LittleShort (eocd->localentries); + eocd->nbentries = LittleShort (eocd->nbentries); + eocd->cdir_size = LittleLong (eocd->cdir_size); + eocd->cdir_offset = LittleLong (eocd->cdir_offset); + eocd->comment_size = LittleShort (eocd->comment_size); + + Mem_Free (buffer); + + return true; +} + + +/* +==================== +PK3_BuildFileList + +Extract the file list from a PK3 file +==================== +*/ +int PK3_BuildFileList (pack_t *pack, const pk3_endOfCentralDir_t *eocd) +{ + qbyte *central_dir, *ptr; + unsigned int ind; + int remaining; + + // Load the central directory in memory + central_dir = Mem_Alloc (tempmempool, eocd->cdir_size); +#ifdef FS_USESYSCALLS + lseek (pack->handle, eocd->cdir_offset, SEEK_SET); + read (pack->handle, central_dir, eocd->cdir_size); +#else + fseek (pack->handle, eocd->cdir_offset, SEEK_SET); + fread (central_dir, 1, eocd->cdir_size, pack->handle); +#endif + + // Extract the files properties + // The parsing is done "by hand" because some fields have variable sizes and + // the constant part isn't 4-bytes aligned, which makes the use of structs difficult + remaining = eocd->cdir_size; + pack->numfiles = 0; + ptr = central_dir; + for (ind = 0; ind < eocd->nbentries; ind++) + { + size_t namesize, count; + + // Checking the remaining size + if (remaining < ZIP_CDIR_CHUNK_BASE_SIZE) + { + Mem_Free (central_dir); + return -1; + } + remaining -= ZIP_CDIR_CHUNK_BASE_SIZE; + + // Check header + if (BuffBigLong (ptr) != ZIP_CDIR_HEADER) + { + Mem_Free (central_dir); + return -1; + } + + namesize = BuffLittleShort (&ptr[28]); // filename length + + // Check encryption, compression, and attributes + // 1st uint8 : general purpose bit flag + // Check bits 0 (encryption), 3 (data descriptor after the file), and 5 (compressed patched data (?)) + // 2nd uint8 : external file attributes + // Check bits 3 (file is a directory) and 5 (file is a volume (?)) + if ((ptr[8] & 0x29) == 0 && (ptr[38] & 0x18) == 0) + { + // Still enough bytes for the name? + if ((size_t) remaining < namesize || namesize >= sizeof (*pack->files)) + { + Mem_Free (central_dir); + return -1; + } + + // WinZip doesn't use the "directory" attribute, so we need to check the name directly + if (ptr[ZIP_CDIR_CHUNK_BASE_SIZE + namesize - 1] != '/') + { + char filename [sizeof (pack->files[0].name)]; + size_t offset, packsize, realsize; + file_flags_t flags; + + // Extract the name (strip it if necessary) + if (namesize >= sizeof (filename)) + namesize = sizeof (filename) - 1; + memcpy (filename, &ptr[ZIP_CDIR_CHUNK_BASE_SIZE], namesize); + filename[namesize] = '\0'; + + if (BuffLittleShort (&ptr[10])) + flags = FILE_FLAG_DEFLATED; + else + flags = 0; + offset = BuffLittleLong (&ptr[42]); + packsize = BuffLittleLong (&ptr[20]); + realsize = BuffLittleLong (&ptr[24]); + FS_AddFileToPack (filename, pack, offset, packsize, realsize, flags); + } + } + + // Skip the name, additionnal field, and comment + // 1er uint16 : extra field length + // 2eme uint16 : file comment length + count = namesize + BuffLittleShort (&ptr[30]) + BuffLittleShort (&ptr[32]); + ptr += ZIP_CDIR_CHUNK_BASE_SIZE + count; + remaining -= count; + } + + Mem_Free (central_dir); + return pack->numfiles; +} + + +/* +==================== +FS_LoadPackPK3 + +Create a package entry associated with a PK3 file +==================== +*/ +pack_t *FS_LoadPackPK3 (const char *packfile) +{ +#ifdef FS_USESYSCALLS + int packhandle; +#else + FILE *packhandle; +#endif + pk3_endOfCentralDir_t eocd; + pack_t *pack; + int real_nb_files; + +#ifdef FS_USESYSCALLS + packhandle = open (packfile, O_RDONLY | O_BINARY); + if (packhandle < 0) + return NULL; +#else + packhandle = fopen (packfile, "rb"); + if (!packhandle) + return NULL; +#endif + + if (! PK3_GetEndOfCentralDir (packfile, packhandle, &eocd)) + Sys_Error ("%s is not a PK3 file", packfile); + + // Multi-volume ZIP archives are NOT allowed + if (eocd.disknum != 0 || eocd.cdir_disknum != 0) + Sys_Error ("%s is a multi-volume ZIP archive", packfile); + + // We only need to do this test if MAX_FILES_IN_PACK is lesser than 65535 + // since eocd.nbentries is an unsigned 16 bits integer +#if MAX_FILES_IN_PACK < 65535 + if (eocd.nbentries > MAX_FILES_IN_PACK) + Sys_Error ("%s contains too many files (%hu)", packfile, eocd.nbentries); +#endif + + // Create a package structure in memory + pack = Mem_Alloc (pak_mempool, sizeof (pack_t)); + pack->ignorecase = true; // PK3 ignores case + strlcpy (pack->filename, packfile, sizeof (pack->filename)); + pack->handle = packhandle; + pack->numfiles = eocd.nbentries; + pack->mempool = Mem_AllocPool (packfile, 0, NULL); + pack->files = Mem_Alloc (pack->mempool, eocd.nbentries * sizeof(packfile_t)); + pack->next = packlist; + packlist = pack; + + real_nb_files = PK3_BuildFileList (pack, &eocd); + if (real_nb_files <= 0) + Sys_Error ("%s is not a valid PK3 file", packfile); + + Con_Printf("Added packfile %s (%i files)\n", packfile, real_nb_files); + return pack; +} + + +/* +==================== +PK3_GetTrueFileOffset + +Find where the true file data offset is +==================== +*/ +void PK3_GetTrueFileOffset (packfile_t *file, pack_t *pack) +{ + qbyte buffer [ZIP_LOCAL_CHUNK_BASE_SIZE]; + size_t count; + + // Already found? + if (file->flags & FILE_FLAG_TRUEOFFS) + return; + + // Load the local file description +#ifdef FS_USESYSCALLS + lseek (pack->handle, file->offset, SEEK_SET); + count = read (pack->handle, buffer, ZIP_LOCAL_CHUNK_BASE_SIZE); +#else + fseek (pack->handle, file->offset, SEEK_SET); + count = fread (buffer, 1, ZIP_LOCAL_CHUNK_BASE_SIZE, pack->handle); +#endif + if (count != ZIP_LOCAL_CHUNK_BASE_SIZE || BuffBigLong (buffer) != ZIP_DATA_HEADER) + Sys_Error ("Can't retrieve file %s in package %s", file->name, pack->filename); + + // Skip name and extra field + file->offset += BuffLittleShort (&buffer[26]) + BuffLittleShort (&buffer[28]) + ZIP_LOCAL_CHUNK_BASE_SIZE; + + file->flags |= FILE_FLAG_TRUEOFFS; +} + + +/* +============================================================================= + +OTHER PRIVATE FUNCTIONS + +============================================================================= +*/ + + +/* +==================== +FS_AddFileToPack + +Add a file to the list of files contained into a package +==================== +*/ +static packfile_t* FS_AddFileToPack (const char* name, pack_t* pack, + size_t offset, size_t packsize, + size_t realsize, file_flags_t flags) +{ + int (*strcmp_funct) (const char* str1, const char* str2); + int left, right, middle; + packfile_t *file; + + strcmp_funct = pack->ignorecase ? strcasecmp : strcmp; + + // Look for the slot we should put that file into (binary search) + left = 0; + right = pack->numfiles - 1; + while (left <= right) + { + int diff; + + middle = (left + right) / 2; + diff = strcmp_funct (pack->files[middle].name, name); + + // If we found the file, there's a problem + if (!diff) + Sys_Error ("Package %s contains the file %s several times\n", + pack->filename, name); + + // If we're too far in the list + if (diff > 0) + right = middle - 1; + else + left = middle + 1; + } + + // We have to move the right of the list by one slot to free the one we need + file = &pack->files[left]; + memmove (file + 1, file, (pack->numfiles - left) * sizeof (*file)); + pack->numfiles++; + + strlcpy (file->name, name, sizeof (file->name)); + file->offset = offset; + file->packsize = packsize; + file->realsize = realsize; + file->flags = flags; + + return file; +} + /* ============ FS_CreatePath -LordHavoc: Previously only used for CopyFile, now also used for FS_WriteFile. +Only used for FS_Open. ============ */ void FS_CreatePath (char *path) @@ -205,22 +742,22 @@ void FS_Path_f (void) { searchpath_t *s; - Con_Printf ("Current search path:\n"); + Con_Print("Current search path:\n"); for (s=fs_searchpaths ; s ; s=s->next) { if (s->pack) { - Con_Printf ("%s (%i files)\n", s->pack->filename, s->pack->numfiles); + Con_Printf("%s (%i files)\n", s->pack->filename, s->pack->numfiles); } else - Con_Printf ("%s\n", s->filename); + Con_Printf("%s\n", s->filename); } } /* ================= -FS_LoadPackFile +FS_LoadPackPAK Takes an explicit (not game tree related) path to a pak file. @@ -228,20 +765,29 @@ Loads the header and directory, adding the files at the beginning of the list so they override previous pack files. ================= */ -pack_t *FS_LoadPackFile (const char *packfile) +pack_t *FS_LoadPackPAK (const char *packfile) { dpackheader_t header; int i, numpackfiles; +#ifdef FS_USESYSCALLS + int packhandle; +#else FILE *packhandle; +#endif pack_t *pack; - // LordHavoc: changed from stack array to temporary alloc, allowing huge pack directories - dpackfile_t *info; + dpackfile_t *info; // temporary alloc, allowing huge pack directories +#ifdef FS_USESYSCALLS + packhandle = open (packfile, O_RDONLY | O_BINARY); + if (packhandle < 0) + return NULL; + read (packhandle, (void *)&header, sizeof(header)); +#else packhandle = fopen (packfile, "rb"); if (!packhandle) return NULL; - fread ((void *)&header, 1, sizeof(header), packhandle); +#endif if (memcmp(header.id, "PACK", 4)) Sys_Error ("%s is not a packfile", packfile); header.dirofs = LittleLong (header.dirofs); @@ -256,29 +802,36 @@ pack_t *FS_LoadPackFile (const char *packfile) Sys_Error ("%s has %i files", packfile, numpackfiles); pack = Mem_Alloc(pak_mempool, sizeof (pack_t)); - strcpy (pack->filename, packfile); + pack->ignorecase = false; // PAK is case sensitive + strlcpy (pack->filename, packfile, sizeof (pack->filename)); pack->handle = packhandle; - pack->numfiles = numpackfiles; - pack->mempool = Mem_AllocPool(packfile); + pack->numfiles = 0; + pack->mempool = Mem_AllocPool(packfile, 0, NULL); pack->files = Mem_Alloc(pack->mempool, numpackfiles * sizeof(packfile_t)); pack->next = packlist; packlist = pack; info = Mem_Alloc(tempmempool, sizeof(*info) * numpackfiles); +#ifdef FS_USESYSCALLS + lseek (packhandle, header.dirofs, SEEK_SET); + read (packhandle, (void *)info, header.dirlen); +#else fseek (packhandle, header.dirofs, SEEK_SET); fread ((void *)info, 1, header.dirlen, packhandle); +#endif -// parse the directory + // parse the directory for (i = 0;i < numpackfiles;i++) { - strcpy (pack->files[i].name, info[i].name); - pack->files[i].filepos = LittleLong(info[i].filepos); - pack->files[i].filelen = LittleLong(info[i].filelen); + size_t offset = LittleLong (info[i].filepos); + size_t size = LittleLong (info[i].filelen); + + FS_AddFileToPack (info[i].name, pack, offset, size, size, FILE_FLAG_TRUEOFFS); } Mem_Free(info); - Con_Printf ("Added packfile %s (%i files)\n", packfile, numpackfiles); + Con_Printf("Added packfile %s (%i files)\n", packfile, numpackfiles); return pack; } @@ -298,22 +851,36 @@ void FS_AddGameDirectory (char *dir) pack_t *pak; char pakfile[MAX_OSPATH]; - strcpy (fs_gamedir, dir); - - // add the directory to the search path - search = Mem_Alloc(pak_mempool, sizeof(searchpath_t)); - strcpy (search->filename, dir); - search->next = fs_searchpaths; - fs_searchpaths = search; + strlcpy (fs_gamedir, dir, sizeof (fs_gamedir)); - // add any paks in the directory list = listdirectory(dir); + + // add any PAK package in the directory for (current = list;current;current = current->next) { if (matchpattern(current->text, "*.pak", true)) { - sprintf (pakfile, "%s/%s", dir, current->text); - pak = FS_LoadPackFile (pakfile); + snprintf (pakfile, sizeof (pakfile), "%s/%s", dir, current->text); + pak = FS_LoadPackPAK (pakfile); + if (pak) + { + search = Mem_Alloc(pak_mempool, sizeof(searchpath_t)); + search->pack = pak; + search->next = fs_searchpaths; + fs_searchpaths = search; + } + else + Con_Printf("unable to load pak \"%s\"\n", pakfile); + } + } + + // add any PK3 package in the director + for (current = list;current;current = current->next) + { + if (matchpattern(current->text, "*.pk3", true)) + { + snprintf (pakfile, sizeof (pakfile), "%s/%s", dir, current->text); + pak = FS_LoadPackPK3 (pakfile); if (pak) { search = Mem_Alloc(pak_mempool, sizeof(searchpath_t)); @@ -326,6 +893,13 @@ void FS_AddGameDirectory (char *dir) } } freedirectory(list); + + // Add the directory to the search path + // (unpacked files have the priority over packed files) + search = Mem_Alloc(pak_mempool, sizeof(searchpath_t)); + strlcpy (search->filename, dir, sizeof (search->filename)); + search->next = fs_searchpaths; + fs_searchpaths = search; } @@ -337,15 +911,23 @@ FS_FileExtension char *FS_FileExtension (const char *in) { static char exten[8]; + const char *slash, *backslash, *colon, *dot, *separator; int i; - while (*in && *in != '.') - in++; - if (!*in) + slash = strrchr(in, '/'); + backslash = strrchr(in, '\\'); + colon = strrchr(in, ':'); + dot = strrchr(in, '.'); + separator = slash; + if (separator < backslash) + separator = backslash; + if (separator < colon) + separator = colon; + if (dot == NULL || dot < separator) return ""; - in++; - for (i=0 ; i<7 && *in ; i++,in++) - exten[i] = *in; + dot++; + for (i = 0;i < 7 && dot[i];i++) + exten[i] = dot[i]; exten[i] = 0; return exten; } @@ -361,82 +943,104 @@ void FS_Init (void) int i; searchpath_t *search; - fs_mempool = Mem_AllocPool("file management"); - pak_mempool = Mem_AllocPool("paks"); + fs_mempool = Mem_AllocPool("file management", 0, NULL); + pak_mempool = Mem_AllocPool("paks", 0, NULL); + + Cvar_RegisterVariable (&scr_screenshot_name); Cmd_AddCommand ("path", FS_Path_f); + Cmd_AddCommand ("dir", FS_Dir_f); + Cmd_AddCommand ("ls", FS_Ls_f); strcpy(fs_basedir, "."); + strcpy(fs_gamedir, "."); + + PK3_OpenLibrary (); // -basedir // Overrides the system supplied base directory (under GAMENAME) +// COMMANDLINEOPTION: Filesystem: -basedir chooses what base directory the game data is in, inside this there should be a data directory for the game (for example id1) i = COM_CheckParm ("-basedir"); - if (i && i < com_argc-1) - strcpy (fs_basedir, com_argv[i+1]); - - i = strlen (fs_basedir); - if (i > 0 && (fs_basedir[i-1] == '\\' || fs_basedir[i-1] == '/')) - fs_basedir[i-1] = 0; - - // start up with GAMENAME by default (id1) - strcpy(com_modname, GAMENAME); - FS_AddGameDirectory (va("%s/"GAMENAME, fs_basedir)); - if (gamedirname[0]) - { - fs_modified = true; - strcpy(com_modname, gamedirname); - FS_AddGameDirectory (va("%s/%s", fs_basedir, gamedirname)); - } - - // -game - // Adds basedir/gamedir as an override game - i = COM_CheckParm ("-game"); if (i && i < com_argc-1) { - fs_modified = true; - strcpy(com_modname, com_argv[i+1]); - FS_AddGameDirectory (va("%s/%s", fs_basedir, com_argv[i+1])); + strlcpy (fs_basedir, com_argv[i+1], sizeof (fs_basedir)); + i = strlen (fs_basedir); + if (i > 0 && (fs_basedir[i-1] == '\\' || fs_basedir[i-1] == '/')) + fs_basedir[i-1] = 0; } // -path [] ... // Fully specifies the exact search path, overriding the generated one +// COMMANDLINEOPTION: Filesystem: -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) i = COM_CheckParm ("-path"); if (i) { fs_modified = true; - fs_searchpaths = NULL; while (++i < com_argc) { if (!com_argv[i] || com_argv[i][0] == '+' || com_argv[i][0] == '-') break; search = Mem_Alloc(pak_mempool, sizeof(searchpath_t)); - if ( !strcmp(FS_FileExtension(com_argv[i]), "pak") ) + if (!strcasecmp (FS_FileExtension(com_argv[i]), "pak")) + { + search->pack = FS_LoadPackPAK (com_argv[i]); + if (!search->pack) + Sys_Error ("Couldn't load packfile: %s", com_argv[i]); + } + else if (!strcasecmp (FS_FileExtension (com_argv[i]), "pk3")) { - search->pack = FS_LoadPackFile (com_argv[i]); + search->pack = FS_LoadPackPK3 (com_argv[i]); if (!search->pack) Sys_Error ("Couldn't load packfile: %s", com_argv[i]); } else - strcpy (search->filename, com_argv[i]); + strlcpy (search->filename, com_argv[i], sizeof (search->filename)); search->next = fs_searchpaths; fs_searchpaths = search; } + return; } -} + // start up with GAMENAME by default (id1) + strlcpy (com_modname, GAMENAME, sizeof (com_modname)); + FS_AddGameDirectory (va("%s/"GAMENAME, fs_basedir)); + Cvar_SetQuick (&scr_screenshot_name, gamescreenshotname); -/* -============================================================================= + // add the game-specific path, if any + if (gamedirname[0]) + { + fs_modified = true; + strlcpy (com_modname, gamedirname, sizeof (com_modname)); + FS_AddGameDirectory (va("%s/%s", fs_basedir, gamedirname)); + } -MAIN FUNCTIONS + // -game + // Adds basedir/gamedir as an override game + // LordHavoc: now supports multiple -game directories + for (i = 1;i < com_argc;i++) + { + if (!com_argv[i]) + continue; + if (!strcmp (com_argv[i], "-game") && i < com_argc-1) + { + i++; + fs_modified = true; + strlcpy (com_modname, com_argv[i], sizeof (com_modname)); + FS_AddGameDirectory (va("%s/%s", fs_basedir, com_argv[i])); + Cvar_SetQuick (&scr_screenshot_name, com_modname); + } + } + + // If "-condebug" is in the command line, remove the previous log file + if (COM_CheckParm ("-condebug") != 0) + unlink (va("%s/qconsole.log", fs_gamedir)); +} -============================================================================= -*/ /* ==================== -FS_Open +FS_SysOpen Internal function used to create a qfile_t and open the relevant file on disk ==================== @@ -448,12 +1052,28 @@ static qfile_t* FS_SysOpen (const char* filepath, const char* mode) file = Mem_Alloc (fs_mempool, sizeof (*file)); memset (file, 0, sizeof (*file)); +#ifdef FS_USESYSCALLS + if (strchr(mode, 'r')) + file->stream = open (filepath, O_RDONLY | O_BINARY); + else if (strchr(mode, 'w')) + file->stream = open (filepath, O_WRONLY | O_BINARY | O_CREAT | O_TRUNC, 0666); + else if (strchr(mode, 'a')) + file->stream = open (filepath, O_RDWR | O_BINARY | O_CREAT | O_APPEND, 0666); + else + file->stream = -1; + if (file->stream < 0) + { + Mem_Free (file); + return NULL; + } +#else file->stream = fopen (filepath, mode); if (!file->stream) { Mem_Free (file); return NULL; } +#endif return file; } @@ -478,14 +1098,24 @@ qfile_t *FS_OpenRead (const char *path, int offs, int len) // Normal file if (offs < 0 || len < 0) { - FS_Seek (file, 0, SEEK_END); - len = FS_Tell (file); - FS_Seek (file, 0, SEEK_SET); + // We set fs_filesize here for normal files +#ifdef FS_USESYSCALLS + fs_filesize = lseek (file->stream, 0, SEEK_END); + lseek (file->stream, 0, SEEK_SET); +#else + fseek (file->stream, 0, SEEK_END); + fs_filesize = ftell (file->stream); + fseek (file->stream, 0, SEEK_SET); +#endif } // Packed file else { - FS_Seek (file, offs, SEEK_SET); +#ifdef FS_USESYSCALLS + lseek (file->stream, offs, SEEK_SET); +#else + fseek (file->stream, offs, SEEK_SET); +#endif file->flags |= FS_FLAG_PACKED; file->length = len; @@ -493,70 +1123,191 @@ qfile_t *FS_OpenRead (const char *path, int offs, int len) file->position = 0; } - fs_filesize = len; - return file; } + /* -=========== -FS_FOpenFile +==================== +FS_FindFile -If the requested file is inside a packfile, a new qfile_t* will be opened -into the file. +Look for a file in the packages and in the filesystem -Sets fs_filesize -=========== +Return the searchpath where the file was found (or NULL) +and the file index in the package if relevant +==================== */ -qfile_t *FS_FOpenFile (const char *filename, qboolean quiet) +static searchpath_t *FS_FindFile (const char *name, int* index, qboolean quiet) { searchpath_t *search; - char netpath[MAX_OSPATH]; pack_t *pak; - int i, filenamelen; - - filenamelen = strlen (filename); // search through the path, one element at a time - search = fs_searchpaths; - - for ( ; search ; search = search->next) + for (search = fs_searchpaths;search;search = search->next) { // is the element a pak file? if (search->pack) { - // look through all the pak file elements + int (*strcmp_funct) (const char* str1, const char* str2); + int left, right, middle; + pak = search->pack; - for (i=0 ; inumfiles ; i++) - if (!strcmp (pak->files[i].name, filename)) - { // found it! + strcmp_funct = pak->ignorecase ? strcasecmp : strcmp; + + // Look for the file (binary search) + left = 0; + right = pak->numfiles - 1; + while (left <= right) + { + int diff; + + middle = (left + right) / 2; + diff = strcmp_funct (pak->files[middle].name, name); + + // Found it + if (!diff) + { if (!quiet) - Sys_Printf ("PackFile: %s : %s\n",pak->filename, pak->files[i].name); - // open a new file in the pakfile - return FS_OpenRead (pak->filename, pak->files[i].filepos, pak->files[i].filelen); + Con_DPrintf("FS_FindFile: %s in %s\n", + pak->files[middle].name, pak->filename); + + if (index != NULL) + *index = middle; + return search; } + + // If we're too far in the list + if (diff > 0) + right = middle - 1; + else + left = middle + 1; + } } else { - sprintf (netpath, "%s/%s",search->filename, filename); + char netpath[MAX_OSPATH]; + snprintf(netpath, sizeof(netpath), "%s/%s", search->filename, name); + if (FS_SysFileExists (netpath)) + { + if (!quiet) + Con_DPrintf("FS_FindFile: %s\n", netpath); + + if (index != NULL) + *index = -1; + return search; + } + } + } + + if (!quiet) + Con_DPrintf("FS_FindFile: can't find %s\n", name); + + if (index != NULL) + *index = -1; + return NULL; +} + + +/* +=========== +FS_FOpenFile + +If the requested file is inside a packfile, a new qfile_t* will be opened +into the file. + +Sets fs_filesize +=========== +*/ +qfile_t *FS_FOpenFile (const char *filename, qboolean quiet) +{ + searchpath_t *search; + packfile_t *packfile; + int i; + qfile_t *file; + + search = FS_FindFile (filename, &i, quiet); + + // Not found? + if (search == NULL) + { + fs_filesize = -1; + return NULL; + } + + // Found in the filesystem? + if (i < 0) + { + char netpath[MAX_OSPATH]; + snprintf(netpath, sizeof(netpath), "%s/%s", search->filename, filename); + return FS_OpenRead(netpath, -1, -1); + } + + // So, we found it in a package... + packfile = &search->pack->files[i]; + + // If we don't have the true offset, get it now + if (! (packfile->flags & FILE_FLAG_TRUEOFFS)) + PK3_GetTrueFileOffset (packfile, search->pack); + + // No Zlib DLL = no compressed files + if (!zlib_dll && (packfile->flags & FILE_FLAG_DEFLATED)) + { + Con_Printf("WARNING: can't open the compressed file %s\n" + "You need the Zlib DLL to use compressed files\n", + filename); + fs_filesize = -1; + return NULL; + } + + // open a new file in the pakfile + file = FS_OpenRead (search->pack->filename, packfile->offset, packfile->packsize); + fs_filesize = packfile->realsize; + + if (packfile->flags & FILE_FLAG_DEFLATED) + { + ztoolkit_t *ztk; + + file->flags |= FS_FLAG_DEFLATED; + + // We need some more variables + ztk = Mem_Alloc (fs_mempool, sizeof (*file->z)); - if (!FS_SysFileExists (netpath)) - continue; + ztk->real_length = packfile->realsize; - if (!quiet) - Sys_Printf ("FindFile: %s\n",netpath); - return FS_OpenRead (netpath, -1, -1); - } - } + // Initialize zlib stream + ztk->zstream.next_in = ztk->input; + ztk->zstream.avail_in = 0; - if (!quiet) - Sys_Printf ("FindFile: can't find %s\n", filename); + /* From Zlib's "unzip.c": + * + * windowBits is passed < 0 to tell that there is no zlib header. + * Note that in this case inflate *requires* an extra "dummy" byte + * after the compressed stream in order to complete decompression and + * return Z_STREAM_END. + * In unzip, i don't wait absolutely Z_STREAM_END because I known the + * size of both compressed and uncompressed data + */ + if (qz_inflateInit2 (&ztk->zstream, -MAX_WBITS) != Z_OK) + Sys_Error ("inflate init error (file: %s)", filename); - fs_filesize = -1; - return NULL; + ztk->zstream.next_out = ztk->output; + ztk->zstream.avail_out = sizeof (ztk->output); + + file->z = ztk; + } + + return file; } +/* +============================================================================= + +MAIN PUBLIC FUNCTIONS + +============================================================================= +*/ + /* ==================== FS_Open @@ -594,9 +1345,19 @@ Close a file */ int FS_Close (qfile_t* file) { +#ifdef FS_USESYSCALLS + if (close (file->stream)) +#else if (fclose (file->stream)) +#endif return EOF; + if (file->z) + { + qz_inflateEnd (&file->z->zstream); + Mem_Free (file->z); + } + Mem_Free (file); return 0; } @@ -611,7 +1372,11 @@ Write "datasize" bytes into a file */ size_t FS_Write (qfile_t* file, const void* data, size_t datasize) { +#ifdef FS_USESYSCALLS + return write (file->stream, data, datasize); +#else return fwrite (data, 1, datasize, file->stream); +#endif } @@ -624,22 +1389,132 @@ Read up to "buffersize" bytes from a file */ size_t FS_Read (qfile_t* file, void* buffer, size_t buffersize) { - size_t nb; + size_t count, nb; + ztoolkit_t *ztk; - // If the file belongs to a package, we must take care - // to not read after the end of the file - if (file->flags & FS_FLAG_PACKED) + // Quick path for unpacked files + if (! (file->flags & FS_FLAG_PACKED)) +#ifdef FS_USESYSCALLS + return read (file->stream, buffer, buffersize); +#else + return fread (buffer, 1, buffersize, file->stream); +#endif + + // If the file isn't compressed + if (! (file->flags & FS_FLAG_DEFLATED)) { - size_t remain = file->length - file->position; - if (buffersize > remain) - buffersize = remain; + // We must take care to not read after the end of the file + count = file->length - file->position; + if (buffersize > count) + buffersize = count; + +#ifdef FS_USESYSCALLS + nb = read (file->stream, buffer, buffersize); +#else + nb = fread (buffer, 1, buffersize, file->stream); +#endif + + file->position += nb; + return nb; } - nb = fread (buffer, 1, buffersize, file->stream); + // If the file is compressed, it's more complicated... + ztk = file->z; + + // First, we copy as many bytes as we can from "output" + if (ztk->out_ind < ztk->out_max) + { + count = ztk->out_max - ztk->out_ind; - // Update the position index if the file is packed - if ((file->flags & FS_FLAG_PACKED) && nb > 0) + nb = (buffersize > count) ? count : buffersize; + memcpy (buffer, &ztk->output[ztk->out_ind], nb); + ztk->out_ind += nb; file->position += nb; + } + else + nb = 0; + + // We cycle through a few operations until we have inflated enough data + while (nb < buffersize) + { + // NOTE: at this point, "output" should always be empty + + // If "input" is also empty, we need to fill it + if (ztk->in_ind == ztk->in_max) + { + size_t remain; + + // If we are at the end of the file + if (ztk->out_position == ztk->real_length) + return nb; + + remain = file->length - ztk->in_position; + count = (remain > sizeof (ztk->input)) ? sizeof (ztk->input) : remain; +#ifdef FS_USESYSCALLS + read (file->stream, ztk->input, count); +#else + fread (ztk->input, 1, count, file->stream); +#endif + + // Update indexes and counters + ztk->in_ind = 0; + ztk->in_max = count; + ztk->in_position += count; + } + + // Now that we are sure we have compressed data available, we need to determine + // if it's better to inflate it in "output" or directly in "buffer" (we are in this + // case if we still need more bytes than "output" can contain) + + ztk->zstream.next_in = &ztk->input[ztk->in_ind]; + ztk->zstream.avail_in = ztk->in_max - ztk->in_ind; + + // If output will be able to contain at least 1 more byte than the data we need + if (buffersize - nb < sizeof (ztk->output)) + { + int error; + + // Inflate the data in "output" + ztk->zstream.next_out = ztk->output; + ztk->zstream.avail_out = sizeof (ztk->output); + error = qz_inflate (&ztk->zstream, Z_SYNC_FLUSH); + if (error != Z_OK && error != Z_STREAM_END) + Sys_Error ("Can't inflate file"); + ztk->in_ind = ztk->in_max - ztk->zstream.avail_in; + ztk->out_max = sizeof (ztk->output) - ztk->zstream.avail_out; + ztk->out_position += ztk->out_max; + + // Copy the requested data in "buffer" (as much as we can) + count = (buffersize - nb > ztk->out_max) ? ztk->out_max : buffersize - nb; + memcpy (&((qbyte*)buffer)[nb], ztk->output, count); + ztk->out_ind = count; + } + + // Else, we inflate directly in "buffer" + else + { + int error; + + // Inflate the data in "buffer" + ztk->zstream.next_out = &((qbyte*)buffer)[nb]; + ztk->zstream.avail_out = buffersize - nb; + error = qz_inflate (&ztk->zstream, Z_SYNC_FLUSH); + if (error != Z_OK && error != Z_STREAM_END) + Sys_Error ("Can't inflate file"); + ztk->in_ind = ztk->in_max - ztk->zstream.avail_in; + + // Invalidate the output data (for FS_Seek) + ztk->out_max = 0; + ztk->out_ind = 0; + + // How much data did it inflate? + count = buffersize - nb - ztk->zstream.avail_out; + ztk->out_position += count; + } + + nb += count; + file->position += count; + } return nb; } @@ -654,10 +1529,26 @@ Flush the file output stream */ int FS_Flush (qfile_t* file) { +#ifdef FS_USESYSCALLS + return 0; +#else return fflush (file->stream); +#endif } +/* +==================== +FS_Print + +Print a string into a file +==================== +*/ +int FS_Print(qfile_t* file, const char *msg) +{ + return FS_Write(file, msg, strlen(msg)); +} + /* ==================== FS_Printf @@ -665,13 +1556,13 @@ FS_Printf Print a string into a file ==================== */ -int FS_Printf (qfile_t* file, const char* format, ...) +int FS_Printf(qfile_t* file, const char* format, ...) { int result; va_list args; va_start (args, format); - result = vfprintf (file->stream, format, args); + result = FS_VPrintf(file, format, args); va_end (args); return result; @@ -680,28 +1571,49 @@ int FS_Printf (qfile_t* file, const char* format, ...) /* ==================== -FS_Getc +FS_VPrintf -Get the next character of a file +Print a string into a file ==================== */ -int FS_Getc (qfile_t* file) +int FS_VPrintf(qfile_t* file, const char* format, va_list ap) { - int c; - - // If the file belongs to a package, we must take care - // to not read after the end of the file - if (file->flags & FS_FLAG_PACKED) +#ifdef FS_USESYSCALLS +{ + int len; + char tempstring[1024]; + len = vsnprintf (tempstring, sizeof(tempstring), format, ap); + if (len >= sizeof(tempstring)) { - if (file->position >= file->length) - return EOF; + int result; + char *temp = Mem_Alloc(tempmempool, len + 1); + len = vsnprintf (temp, len + 1, format, ap); + result = write (file->stream, temp, len); + Mem_Free(temp); + return result; } + else + return write (file->stream, tempstring, len); +} +#else + return vfprintf (file->stream, format, ap); +#endif +} - c = fgetc (file->stream); - // Update the position index if the file is packed - if ((file->flags & FS_FLAG_PACKED) && c != EOF) - file->position++; +/* +==================== +FS_Getc + +Get the next character of a file +==================== +*/ +int FS_Getc (qfile_t* file) +{ + char c; + + if (FS_Read (file, &c, 1) != 1) + return EOF; return c; } @@ -716,37 +1628,120 @@ Move the position index in a file */ int FS_Seek (qfile_t* file, long offset, int whence) { - // Packed files receive a special treatment - if (file->flags & FS_FLAG_PACKED) + // Quick path for unpacked files + if (! (file->flags & FS_FLAG_PACKED)) +#ifdef FS_USESYSCALLS + { + if (lseek (file->stream, offset, whence) == -1) + return -1; + return 0; + } +#else + return fseek (file->stream, offset, whence); +#endif + + // Seeking in compressed files is more a hack than anything else, + // but we need to support it, so here it is. + if (file->flags & FS_FLAG_DEFLATED) { + ztoolkit_t *ztk = file->z; + qbyte buffer [sizeof (ztk->output)]; // it's big to force inflating into buffer directly + switch (whence) { case SEEK_CUR: offset += file->position; - // It continues on the next case (no break) + break; case SEEK_SET: - if (offset < 0 || offset > file->length) - return -1; - if (fseek (file->stream, file->offset + offset, SEEK_SET) == -1) - return -1; - file->position = offset; - return 0; + break; case SEEK_END: - if (offset > 0 || -offset > file->length) - return -1; - if (fseek (file->stream, file->offset + file->length + offset, SEEK_SET) == -1) - return -1; - file->position = file->length + offset; - return 0; + offset += ztk->real_length; + break; default: return -1; } + if (offset < 0 || offset > (long) ztk->real_length) + return -1; + + // If we need to go back in the file + if (offset <= (long) file->position) + { + // If we still have the data we need in the output buffer + if (file->position - offset <= ztk->out_ind) + { + ztk->out_ind -= file->position - offset; + file->position = offset; + return 0; + } + + // Else, we restart from the beginning of the file + ztk->in_ind = 0; + ztk->in_max = 0; + ztk->in_position = 0; + ztk->out_ind = 0; + ztk->out_max = 0; + ztk->out_position = 0; + file->position = 0; +#ifdef FS_USESYSCALLS + lseek (file->stream, file->offset, SEEK_SET); +#else + fseek (file->stream, file->offset, SEEK_SET); +#endif + + // Reset the Zlib stream + ztk->zstream.next_in = ztk->input; + ztk->zstream.avail_in = 0; + qz_inflateReset (&ztk->zstream); + } + + // Skip all data until we reach the requested offset + while ((long) file->position < offset) + { + size_t diff = offset - file->position; + size_t count, len; + + count = (diff > sizeof (buffer)) ? sizeof (buffer) : diff; + len = FS_Read (file, buffer, count); + if (len != count) + return -1; + } + + return 0; + } + + // Packed files receive a special treatment too, because + // we need to make sure it doesn't go outside of the file + switch (whence) + { + case SEEK_CUR: + offset += file->position; + break; + + case SEEK_SET: + break; + + case SEEK_END: + offset += file->length; + break; + + default: + return -1; } + if (offset < 0 || offset > (long) file->length) + return -1; - return fseek (file->stream, offset, whence); +#ifdef FS_USESYSCALLS + if (lseek (file->stream, file->offset + offset, SEEK_SET) == -1) + return -1; +#else + if (fseek (file->stream, file->offset + offset, SEEK_SET) == -1) + return -1; +#endif + file->position = offset; + return 0; } @@ -762,7 +1757,11 @@ long FS_Tell (qfile_t* file) if (file->flags & FS_FLAG_PACKED) return file->position; +#ifdef FS_USESYSCALLS + return lseek (file->stream, 0, SEEK_CUR); +#else return ftell (file->stream); +#endif } @@ -775,24 +1774,41 @@ Extract a line from a file */ char* FS_Gets (qfile_t* file, char* buffer, int buffersize) { - if (!fgets (buffer, buffersize, file->stream)) - return NULL; + size_t ind; - // Check that we didn't read after the end of a packed file, and update the position - if (file->flags & FS_FLAG_PACKED) - { - size_t len = strlen (buffer); - size_t max = file->length - file->position; + // Quick path for unpacked files +#ifndef FS_USESYSCALLS + if (! (file->flags & FS_FLAG_PACKED)) + return fgets (buffer, buffersize, file->stream); +#endif - if (len > max) + for (ind = 0; ind < (size_t) buffersize - 1; ind++) + { + int c = FS_Getc (file); + switch (c) { - buffer[max] = '\0'; - file->position = file->length; + // End of file + case -1: + if (!ind) + return NULL; + + buffer[ind] = '\0'; + return buffer; + + // End of line + case '\r': + case '\n': + buffer[ind] = '\n'; + buffer[ind + 1] = '\0'; + return buffer; + + default: + buffer[ind] = c; } - else - file->position += len; + } + buffer[buffersize - 1] = '\0'; return buffer; } @@ -842,12 +1858,23 @@ FS_Eof Extract a line from a file ==================== */ +// FIXME: remove this function? int FS_Eof (qfile_t* file) { if (file->flags & FS_FLAG_PACKED) + { + if (file->flags & FS_FLAG_DEFLATED) + return (file->position == file->z->real_length); + return (file->position == file->length); - + } + +#ifdef FS_USESYSCALLS + Sys_Error("FS_Eof: not implemented using syscalls\n"); + return false; +#else return feof (file->stream); +#endif } @@ -859,7 +1886,7 @@ Filename are relative to the quake directory. Always appends a 0 byte. ============ */ -qbyte *FS_LoadFile (const char *path, qboolean quiet) +qbyte *FS_LoadFile (const char *path, mempool_t *pool, qboolean quiet) { qfile_t *h; qbyte *buf; @@ -869,7 +1896,7 @@ qbyte *FS_LoadFile (const char *path, qboolean quiet) if (!h) return NULL; - buf = Mem_Alloc(tempmempool, fs_filesize+1); + buf = Mem_Alloc(pool, fs_filesize+1); if (!buf) Sys_Error ("FS_LoadFile: not enough available memory for %s (size %i)", path, fs_filesize); @@ -891,24 +1918,18 @@ The filename will be prefixed by the current game directory */ qboolean FS_WriteFile (const char *filename, void *data, int len) { - FILE *handle; - char name[MAX_OSPATH]; + qfile_t *handle; - sprintf (name, "%s/%s", fs_gamedir, filename); - - // Create directories up to the file - FS_CreatePath (name); - - handle = fopen (name, "wb"); + handle = FS_Open (filename, "wb", false); if (!handle) { - Con_Printf ("FS_WriteFile: failed on %s\n", name); + Con_Printf("FS_WriteFile: failed on %s\n", filename); return false; } - Con_DPrintf ("FS_WriteFile: %s\n", name); - fwrite (data, 1, len, handle); - fclose (handle); + Con_DPrintf("FS_WriteFile: %s\n", filename); + FS_Write (handle, data, len); + FS_Close (handle); return true; } @@ -916,7 +1937,7 @@ qboolean FS_WriteFile (const char *filename, void *data, int len) /* ============================================================================= -OTHERS FUNCTIONS +OTHERS PUBLIC FUNCTIONS ============================================================================= */ @@ -926,16 +1947,21 @@ OTHERS FUNCTIONS FS_StripExtension ============ */ -void FS_StripExtension (const char *in, char *out) +void FS_StripExtension (const char *in, char *out, size_t size_out) { char *last = NULL; - while (*in) + + if (size_out == 0) + return; + + while (*in && size_out > 1) { if (*in == '.') last = out; else if (*in == '/' || *in == '\\' || *in == ':') last = NULL; *out++ = *in++; + size_out--; } if (last) *last = 0; @@ -949,7 +1975,7 @@ void FS_StripExtension (const char *in, char *out) FS_DefaultExtension ================== */ -void FS_DefaultExtension (char *path, const char *extension) +void FS_DefaultExtension (char *path, const char *extension, size_t size_path) { const char *src; @@ -964,38 +1990,30 @@ void FS_DefaultExtension (char *path, const char *extension) src--; } - strcat (path, extension); + strlcat (path, extension, size_path); } +/* +================== +FS_FileExists + +Look for a file in the packages and in the filesystem +================== +*/ qboolean FS_FileExists (const char *filename) { - searchpath_t *search; - char netpath[MAX_OSPATH]; - pack_t *pak; - int i; - - for (search = fs_searchpaths;search;search = search->next) - { - if (search->pack) - { - pak = search->pack; - for (i = 0;i < pak->numfiles;i++) - if (!strcmp (pak->files[i].name, filename)) - return true; - } - else - { - sprintf (netpath, "%s/%s",search->filename, filename); - if (FS_SysFileExists (netpath)) - return true; - } - } - - return false; + return (FS_FindFile (filename, NULL, true) != NULL); } +/* +================== +FS_SysFileExists + +Look for a file in the filesystem only +================== +*/ qboolean FS_SysFileExists (const char *path) { #if WIN32 @@ -1027,3 +2045,251 @@ void FS_mkdir (const char *path) mkdir (path, 0777); #endif } + +/* +=========== +FS_Search + +Allocate and fill a search structure with information on matching filenames. +=========== +*/ +fssearch_t *FS_Search(const char *pattern, int caseinsensitive, int quiet) +{ + fssearch_t *search; + searchpath_t *searchpath; + pack_t *pak; + int i, basepathlength, numfiles, numchars; + stringlist_t *dir, *dirfile, *liststart, *listcurrent, *listtemp; + const char *slash, *backslash, *colon, *separator; + char *basepath; + char netpath[MAX_OSPATH]; + char temp[MAX_OSPATH]; + + while(!strncmp(pattern, "./", 2)) + pattern += 2; + while(!strncmp(pattern, ".\\", 2)) + pattern += 2; + + search = NULL; + liststart = NULL; + listcurrent = NULL; + listtemp = NULL; + slash = strrchr(pattern, '/'); + backslash = strrchr(pattern, '\\'); + colon = strrchr(pattern, ':'); + separator = pattern; + if (separator < slash) + separator = slash; + if (separator < backslash) + separator = backslash; + if (separator < colon) + separator = colon; + basepathlength = separator - pattern; + basepath = Mem_Alloc (tempmempool, basepathlength + 1); + if (basepathlength) + memcpy(basepath, pattern, basepathlength); + basepath[basepathlength] = 0; + + // search through the path, one element at a time + for (searchpath = fs_searchpaths;searchpath;searchpath = searchpath->next) + { + // is the element a pak file? + if (searchpath->pack) + { + // look through all the pak file elements + pak = searchpath->pack; + for (i = 0;i < pak->numfiles;i++) + { + strcpy(temp, pak->files[i].name); + while (temp[0]) + { + if (matchpattern(temp, (char *)pattern, true)) + { + for (listtemp = liststart;listtemp;listtemp = listtemp->next) + if (!strcmp(listtemp->text, temp)) + break; + if (listtemp == NULL) + { + listcurrent = stringlistappend(listcurrent, temp); + if (liststart == NULL) + liststart = listcurrent; + if (!quiet) + Con_DPrintf("SearchPackFile: %s : %s\n", pak->filename, temp); + } + } + // strip off one path element at a time until empty + // this way directories are added to the listing if they match the pattern + slash = strrchr(temp, '/'); + backslash = strrchr(temp, '\\'); + colon = strrchr(temp, ':'); + separator = temp; + if (separator < slash) + separator = slash; + if (separator < backslash) + separator = backslash; + if (separator < colon) + separator = colon; + *((char *)separator) = 0; + } + } + } + else + { + // get a directory listing and look at each name + snprintf(netpath, sizeof (netpath), "%s/%s", searchpath->filename, basepath); + if ((dir = listdirectory(netpath))) + { + for (dirfile = dir;dirfile;dirfile = dirfile->next) + { + snprintf(temp, sizeof(temp), "%s/%s", basepath, dirfile->text); + if (matchpattern(temp, (char *)pattern, true)) + { + for (listtemp = liststart;listtemp;listtemp = listtemp->next) + if (!strcmp(listtemp->text, temp)) + break; + if (listtemp == NULL) + { + listcurrent = stringlistappend(listcurrent, temp); + if (liststart == NULL) + liststart = listcurrent; + if (!quiet) + Con_DPrintf("SearchDirFile: %s\n", temp); + } + } + } + freedirectory(dir); + } + } + } + + if (liststart) + { + liststart = stringlistsort(liststart); + numfiles = 0; + numchars = 0; + for (listtemp = liststart;listtemp;listtemp = listtemp->next) + { + numfiles++; + numchars += strlen(listtemp->text) + 1; + } + search = Z_Malloc(sizeof(fssearch_t) + numchars + numfiles * sizeof(char *)); + search->filenames = (char **)((char *)search + sizeof(fssearch_t)); + search->filenamesbuffer = (char *)((char *)search + sizeof(fssearch_t) + numfiles * sizeof(char *)); + search->numfilenames = numfiles; + numfiles = 0; + numchars = 0; + for (listtemp = liststart;listtemp;listtemp = listtemp->next) + { + search->filenames[numfiles] = search->filenamesbuffer + numchars; + strcpy(search->filenames[numfiles], listtemp->text); + numfiles++; + numchars += strlen(listtemp->text) + 1; + } + if (liststart) + stringlistfree(liststart); + } + + Mem_Free(basepath); + return search; +} + +void FS_FreeSearch(fssearch_t *search) +{ + Z_Free(search); +} + +extern int con_linewidth; +int FS_ListDirectory(const char *pattern, int oneperline) +{ + int numfiles; + int numcolumns; + int numlines; + int columnwidth; + int linebufpos; + int i, j, k, l; + const char *name; + char linebuf[4096]; + fssearch_t *search; + search = FS_Search(pattern, true, true); + if (!search) + return 0; + numfiles = search->numfilenames; + if (!oneperline) + { + // FIXME: the names could be added to one column list and then + // gradually shifted into the next column if they fit, and then the + // next to make a compact variable width listing but it's a lot more + // complicated... + // find width for columns + columnwidth = 0; + for (i = 0;i < numfiles;i++) + { + l = strlen(search->filenames[i]); + if (columnwidth < l) + columnwidth = l; + } + // count the spacing character + columnwidth++; + // calculate number of columns + numcolumns = con_linewidth / columnwidth; + // don't bother with the column printing if it's only one column + if (numcolumns >= 2) + { + numlines = (numfiles + numcolumns - 1) / numcolumns; + for (i = 0;i < numlines;i++) + { + linebufpos = 0; + for (k = 0;k < numcolumns;k++) + { + l = i * numcolumns + k; + if (l < numfiles) + { + name = search->filenames[l]; + for (j = 0;name[j] && j < (int)sizeof(linebuf) - 1;j++) + linebuf[linebufpos++] = name[j]; + // space out name unless it's the last on the line + if (k < (numcolumns - 1) && l < (numfiles - 1)) + for (;j < columnwidth && j < (int)sizeof(linebuf) - 1;j++) + linebuf[linebufpos++] = ' '; + } + } + linebuf[linebufpos] = 0; + Con_Printf("%s\n", linebuf); + } + } + else + oneperline = true; + } + if (oneperline) + for (i = 0;i < numfiles;i++) + Con_Printf("%s\n", search->filenames[i]); + FS_FreeSearch(search); + return numfiles; +} + +static void FS_ListDirectoryCmd (const char* cmdname, int oneperline) +{ + const char *pattern; + if (Cmd_Argc() > 3) + { + Con_Printf("usage:\n%s [path/pattern]\n", cmdname); + return; + } + if (Cmd_Argc() == 2) + pattern = Cmd_Argv(1); + else + pattern = "*"; + if (!FS_ListDirectory(pattern, oneperline)) + Con_Print("No files found.\n"); +} + +void FS_Dir_f(void) +{ + FS_ListDirectoryCmd("dir", true); +} + +void FS_Ls_f(void) +{ + FS_ListDirectoryCmd("ls", false); +} +