fteqw/engine/common/fs.c
Spoike 503eff6421 Reworked the filesystem. We now support a virtual filesystem. Many places accept stream usage, although many formats do not support this.
I'm not sure if this will break anything. It shouldn't do, but it might.

Not everything is ported over yet. Ideally there would be no more use of fopen anywhere else in the engine, and com_gamedir would be made static to fs.c
There are a couple of other changes too.

http/ftp stuff is currently disabled.


git-svn-id: https://svn.code.sf.net/p/fteqw/code/trunk@1728 fc73d0e0-1445-4013-8a0c-d673dee63da5
2005-12-21 03:07:33 +00:00

2704 lines
58 KiB
C

#include "quakedef.h"
//#define com_gamedir com__gamedir
#include <ctype.h>
#include "hash.h"
hashtable_t filesystemhash;
qboolean com_fschanged = true;
extern cvar_t com_fs_cache;
int active_fs_cachetype;
typedef struct {
void (*PrintPath)(void *handle);
void (*ClosePath)(void *handle);
void (*BuildHash)(void *handle);
qboolean (*FindFile)(void *handle, flocation_t *loc, char *name, void *hashedresult); //true if found (hashedresult can be NULL)
//note that if rawfile and offset are set, many Com_FileOpens will read the raw file
//otherwise ReadFile will be called instead.
void (*ReadFile)(void *handle, flocation_t *loc, char *buffer); //reads the entire file
int (*EnumerateFiles)(void *handle, char *match, int (*func)(char *, int, void *), void *parm);
void *(*OpenNew)(vfsfile_t *file, char *desc); //returns a handle to a new pak/path
int (*GeneratePureCRC) (void *handle, int seed, int usepure);
vfsfile_t *(*OpenVFS)(void *handle, flocation_t *loc, char *mode);
} searchpathfuncs_t;
vfsfile_t *FS_OpenVFSLoc(flocation_t *loc, char *mode);
char gamedirfile[MAX_OSPATH];
//the various COM_LoadFiles set these on return
int com_filesize;
qboolean com_file_copyprotected;
//
// in memory
//
typedef struct
{
char name[MAX_QPATH];
int filepos, filelen;
bucket_t bucket;
} packfile_t;
typedef struct pack_s
{
char descname[MAX_OSPATH];
vfsfile_t *handle;
unsigned int filepos; //the pos the subfiles left it at (to optimize calls to vfs_seek)
int numfiles;
packfile_t *files;
int references; //seeing as all vfiles from a pak file use the parent's vfsfile, we need to keep the parent open until all subfiles are closed.
} pack_t;
//
// on disk
//
typedef struct
{
char name[56];
int filepos, filelen;
} dpackfile_t;
typedef struct
{
int filepos, filelen;
char name[8];
} dwadfile_t;
typedef struct
{
char id[4];
int dirofs;
int dirlen;
} dpackheader_t;
typedef struct
{
char id[4];
int dirlen;
int dirofs;
} dwadheader_t;
#define MAX_FILES_IN_PACK 2048
char com_gamedir[MAX_OSPATH]; //the os path where we write files
//char *com_basedir; //obsolete
char com_quakedir[MAX_OSPATH];
char com_homedir[MAX_OSPATH];
char com_configdir[MAX_OSPATH]; //homedir/fte/configs
int fs_hash_dups;
int fs_hash_files;
int COM_FileOpenRead (char *path, FILE **hndl);
#define ENFORCEFOPENMODE(mode) {if (strcmp(mode, "r") && strcmp(mode, "w")/* && strcmp(mode, "rw")*/)Sys_Error("fs mode %s is not permitted here\n");}
//======================================================================================================
//STDIO files (OS)
typedef struct {
vfsfile_t funcs;
FILE *handle;
} vfsosfile_t;
int VFSOS_ReadBytes (struct vfsfile_s *file, void *buffer, int bytestoread)
{
vfsosfile_t *intfile = (vfsosfile_t*)file;
return fread(buffer, 1, bytestoread, intfile->handle);
}
int VFSOS_WriteBytes (struct vfsfile_s *file, void *buffer, int bytestoread)
{
vfsosfile_t *intfile = (vfsosfile_t*)file;
return fwrite(buffer, 1, bytestoread, intfile->handle);
}
qboolean VFSOS_Seek (struct vfsfile_s *file, unsigned long pos)
{
vfsosfile_t *intfile = (vfsosfile_t*)file;
return fseek(intfile->handle, pos, SEEK_SET) == 0;
}
unsigned long VFSOS_Tell (struct vfsfile_s *file)
{
vfsosfile_t *intfile = (vfsosfile_t*)file;
return ftell(intfile->handle);
}
unsigned long VFSOS_GetSize (struct vfsfile_s *file)
{
vfsosfile_t *intfile = (vfsosfile_t*)file;
unsigned int curpos;
unsigned int maxlen;
curpos = ftell(intfile->handle);
fseek(intfile->handle, 0, SEEK_END);
maxlen = ftell(intfile->handle);
fseek(intfile->handle, curpos, SEEK_SET);
return maxlen;
}
void VFSOS_Close(vfsfile_t *file)
{
vfsosfile_t *intfile = (vfsosfile_t*)file;
fclose(intfile->handle);
}
vfsfile_t *VFSOS_Open(char *osname, char *mode)
{
FILE *f;
vfsosfile_t *file;
qboolean read = !!strchr(mode, 'r');
qboolean write = !!strchr(mode, 'w');
qboolean text = !!strchr(mode, 't');
char newmode[3];
int modec = 0;
if (read)
newmode[modec++] = 'r';
if (write)
newmode[modec++] = 'w';
if (text)
newmode[modec++] = 't';
else
newmode[modec++] = 'b';
newmode[modec++] = '\0';
f = fopen(osname, newmode);
if (!f)
return NULL;
file = Z_Malloc(sizeof(vfsosfile_t));
file->funcs.ReadBytes = strchr(mode, 'r')?VFSOS_ReadBytes:NULL;
file->funcs.WriteBytes = strchr(mode, 'w')?VFSOS_WriteBytes:NULL;
file->funcs.Seek = VFSOS_Seek;
file->funcs.Tell = VFSOS_Tell;
file->funcs.GetLen = VFSOS_GetSize;
file->funcs.Close = VFSOS_Close;
file->handle = f;
return (vfsfile_t*)file;
}
vfsfile_t *FSOS_OpenVFS(void *handle, flocation_t *loc, char *mode)
{
char diskname[MAX_OSPATH];
_snprintf(diskname, sizeof(diskname), "%s/%s", handle, loc->rawname);
return VFSOS_Open(diskname, mode);
}
void FSOS_PrintPath(void *handle)
{
Con_Printf("%s\n", handle);
}
void FSOS_ClosePath(void *handle)
{
Z_Free(handle);
}
int FSOS_RebuildFSHash(char *filename, int filesize, void *data)
{
if (filename[strlen(filename)-1] == '/')
{ //this is actually a directory
char childpath[256];
sprintf(childpath, "%s*", filename);
Sys_EnumerateFiles((char*)data, childpath, FSOS_RebuildFSHash, data);
return true;
}
if (!Hash_GetInsensative(&filesystemhash, filename))
{
bucket_t *bucket = (bucket_t*)BZ_Malloc(sizeof(bucket_t) + strlen(filename)+1);
strcpy((char *)(bucket+1), filename);
#ifdef _WIN32
Q_strlwr((char *)(bucket+1));
#endif
Hash_AddInsensative(&filesystemhash, (char *)(bucket+1), data, bucket);
fs_hash_files++;
}
else
fs_hash_dups++;
return true;
}
void FSOS_BuildHash(void *handle)
{
Sys_EnumerateFiles(handle, "*", FSOS_RebuildFSHash, handle);
}
qboolean FSOS_FLocate(void *handle, flocation_t *loc, char *filename, void *hashedresult)
{
FILE *f;
int len;
char netpath[MAX_OSPATH];
if (hashedresult && (void *)hashedresult != handle)
return false;
/*
if (!static_registered)
{ // if not a registered version, don't ever go beyond base
if ( strchr (filename, '/') || strchr (filename,'\\'))
continue;
}
*/
// check a file in the directory tree
_snprintf (netpath, sizeof(netpath)-1, "%s/%s",(char*)handle, filename);
f = fopen(netpath, "rb");
if (!f)
return false;
fseek(f, 0, SEEK_END);
len = ftell(f);
fclose(f);
if (loc)
{
loc->len = len;
loc->offset = 0;
loc->index = 0;
Q_strncpyz(loc->rawname, filename, sizeof(loc->rawname));
}
return true;
}
void FSOS_ReadFile(void *handle, flocation_t *loc, char *buffer)
{
FILE *f;
f = fopen(loc->rawname, "rb");
if (!f) //err...
return;
fseek(f, loc->offset, SEEK_SET);
fread(buffer, 1, loc->len, f);
fclose(f);
}
searchpathfuncs_t osfilefuncs = {
FSOS_PrintPath,
FSOS_ClosePath,
FSOS_BuildHash,
FSOS_FLocate,
FSOS_ReadFile,
Sys_EnumerateFiles,
NULL,
NULL,
FSOS_OpenVFS
};
//======================================================================================================
//PACK files (*.pak)
void FSPAK_PrintPath(void *handle)
{
pack_t *pak = handle;
if (pak->references != 1)
Con_Printf("%s (%i)\n", pak->descname, pak->references-1);
else
Con_Printf("%s\n", pak->descname);
}
void FSPAK_ClosePath(void *handle)
{
pack_t *pak = handle;
pak->references--;
if (pak->references > 0)
return; //not free yet
VFS_CLOSE (pak->handle);
if (pak->files)
Z_Free(pak->files);
Z_Free(pak);
}
void FSPAK_BuildHash(void *handle)
{
pack_t *pak = handle;
int i;
for (i = 0; i < pak->numfiles; i++)
{
if (!Hash_GetInsensative(&filesystemhash, pak->files[i].name))
{
fs_hash_files++;
Hash_AddInsensative(&filesystemhash, pak->files[i].name, &pak->files[i], &pak->files[i].bucket);
}
else
fs_hash_dups++;
}
}
qboolean FSPAK_FLocate(void *handle, flocation_t *loc, char *filename, void *hashedresult)
{
packfile_t *pf = hashedresult;
int i, len;
pack_t *pak = handle;
// look through all the pak file elements
if (pf)
{ //is this a pointer to a file in this pak?
if (pf < pak->files || pf > pak->files + pak->numfiles)
return false; //was found in a different path
}
else
{
for (i=0 ; i<pak->numfiles ; i++) //look for the file
{
if (!strcmp (pak->files[i].name, filename))
{
pf = &pak->files[i];
break;
}
}
}
if (pf)
{
len = pf->filelen;
if (loc)
{
loc->index = pf - pak->files;
_snprintf(loc->rawname, sizeof(loc->rawname), "%s/%s", pak->descname, filename);
loc->offset = pf->filepos;
loc->len = pf->filelen;
}
return true;
}
return false;
}
int FSPAK_EnumerateFiles (void *handle, char *match, int (*func)(char *, int, void *), void *parm)
{
pack_t *pak = handle;
int num;
for (num = 0; num<(int)pak->numfiles; num++)
{
if (wildcmp(match, pak->files[num].name))
{
if (!func(pak->files[num].name, pak->files[num].filelen, parm))
return false;
}
}
return true;
}
/*
=================
COM_LoadPackFile
Takes an explicit (not game tree related) path to a pak file.
Loads the header and directory, adding the files at the beginning
of the list so they override previous pack files.
=================
*/
void *FSPAK_LoadPackFile (vfsfile_t *file, char *desc)
{
dpackheader_t header;
int i;
// int j;
packfile_t *newfiles;
int numpackfiles;
pack_t *pack;
vfsfile_t *packhandle;
dpackfile_t info;
int read;
// unsigned short crc;
packhandle = file;
if (packhandle == NULL)
return NULL;
VFS_READ(packhandle, &header, sizeof(header));
if (header.id[0] != 'P' || header.id[1] != 'A'
|| header.id[2] != 'C' || header.id[3] != 'K')
{
return NULL;
// Sys_Error ("%s is not a packfile", packfile);
}
header.dirofs = LittleLong (header.dirofs);
header.dirlen = LittleLong (header.dirlen);
numpackfiles = header.dirlen / sizeof(dpackfile_t);
// if (numpackfiles > MAX_FILES_IN_PACK)
// Sys_Error ("%s has %i files", packfile, numpackfiles);
// if (numpackfiles != PAK0_COUNT)
// com_modified = true; // not the original file
newfiles = (packfile_t*)Z_Malloc (numpackfiles * sizeof(packfile_t));
VFS_SEEK(packhandle, header.dirofs);
// fread (&info, 1, header.dirlen, packhandle);
// crc the directory to check for modifications
// crc = QCRC_Block((qbyte *)info, header.dirlen);
// QCRC_Init (&crc);
pack = (pack_t*)Z_Malloc (sizeof (pack_t));
// parse the directory
for (i=0 ; i<numpackfiles ; i++)
{
*info.name = '\0';
read = VFS_READ(packhandle, &info, sizeof(info));
/*
for (j=0 ; j<sizeof(info) ; j++)
CRC_ProcessByte(&crc, ((qbyte *)&info)[j]);
*/
strcpy (newfiles[i].name, info.name);
COM_CleanUpPath(newfiles[i].name); //blooming tanks.
newfiles[i].filepos = LittleLong(info.filepos);
newfiles[i].filelen = LittleLong(info.filelen);
}
/*
if (crc != PAK0_CRC)
com_modified = true;
*/
strcpy (pack->descname, desc);
pack->handle = packhandle;
pack->numfiles = numpackfiles;
pack->files = newfiles;
pack->filepos = 0;
VFS_SEEK(packhandle, pack->filepos);
pack->references++;
Con_TPrintf (TL_ADDEDPACKFILE, desc, numpackfiles);
return pack;
}
typedef struct {
vfsfile_t funcs;
pack_t *parentpak;
unsigned long startpos;
unsigned long length;
unsigned long currentpos;
} vfspack_t;
int VFSPAK_ReadBytes (struct vfsfile_s *vfs, void *buffer, int bytestoread)
{
vfspack_t *vfsp = (vfspack_t*)vfs;
int read;
if (vfsp->currentpos - vfsp->startpos + bytestoread > vfsp->length)
bytestoread = vfsp->length - (vfsp->currentpos - vfsp->startpos + bytestoread);
if (bytestoread <= 0)
return -1;
if (vfsp->parentpak->filepos != vfsp->currentpos)
VFS_SEEK(vfsp->parentpak->handle, vfsp->currentpos);
read = VFS_READ(vfsp->parentpak->handle, buffer, bytestoread);
vfsp->currentpos += read;
vfsp->parentpak->filepos = vfsp->currentpos;
return read;
}
int VFSPAK_WriteBytes (struct vfsfile_s *vfs, void *buffer, int bytestoread)
{ //not supported.
Sys_Error("Cannot write to pak files\n");
return 0;
}
qboolean VFSPAK_Seek (struct vfsfile_s *vfs, unsigned long pos)
{
vfspack_t *vfsp = (vfspack_t*)vfs;
if (pos < 0 || pos > vfsp->length)
return false;
vfsp->currentpos = pos + vfsp->startpos;
return true;
}
unsigned long VFSPAK_Tell (struct vfsfile_s *vfs)
{
vfspack_t *vfsp = (vfspack_t*)vfs;
return vfsp->currentpos - vfsp->startpos;
}
unsigned long VFSPAK_GetLen (struct vfsfile_s *vfs)
{
vfspack_t *vfsp = (vfspack_t*)vfs;
return vfsp->length;
}
void VFSPAK_Close(vfsfile_t *vfs)
{
vfspack_t *vfsp = (vfspack_t*)vfs;
FSPAK_ClosePath(vfsp->parentpak); //tell the parent that we don't need it open any more (reference counts)
Z_Free(vfsp); //free ourselves.
}
vfsfile_t *FSPAK_OpenVFS(void *handle, flocation_t *loc, char *mode)
{
pack_t *pack = (pack_t*)handle;
vfspack_t *vfs = Z_Malloc(sizeof(vfspack_t));
vfs->parentpak = ((pack_t*)handle);
vfs->parentpak->references++;
vfs->startpos = loc->offset;
vfs->length = loc->len;
vfs->currentpos = vfs->startpos;
vfs->funcs.Close = VFSPAK_Close;
vfs->funcs.GetLen = VFSPAK_GetLen;
vfs->funcs.ReadBytes = VFSPAK_ReadBytes;
vfs->funcs.Seek = VFSPAK_Seek;
vfs->funcs.Tell = VFSPAK_Tell;
vfs->funcs.WriteBytes = VFSPAK_WriteBytes; //not supported
return (vfsfile_t *)vfs;
}
searchpathfuncs_t packfilefuncs = {
FSPAK_PrintPath,
FSPAK_ClosePath,
FSPAK_BuildHash,
FSPAK_FLocate,
FSOS_ReadFile,
FSPAK_EnumerateFiles,
FSPAK_LoadPackFile,
NULL,
FSPAK_OpenVFS
};
//======================================================================================================
//ZIP files (*.zip *.pk3)
void *com_pathforfile; //fread and stuff is preferable if null
#define ZEXPORT VARGS
#ifdef AVAIL_ZLIB
#ifdef _WIN32
#pragma comment( lib, "../libs/zlib.lib" )
#endif
//#define uShort ZLIBuShort
//#define uLong ZLIBuLong
#include <zlib.h>
#include "unzip.c"
typedef struct zipfile_s
{
char filename[MAX_QPATH];
unzFile handle;
int numfiles;
packfile_t *files;
#ifdef HASH_FILESYSTEM
hashtable_t hash;
#endif
vfsfile_t *raw;
vfsfile_t *currentfile; //our unzip.c can only handle one active file at any one time
//so we have to keep closing and switching.
//slow, but it works. most of the time we'll only have a single file open anyway.
int references; //and a reference count
} zipfile_t;
static void FSZIP_PrintPath(void *handle)
{
zipfile_t *zip = handle;
if (zip->references != 1)
Con_Printf("%s (%i)\n", zip->filename, zip->references-1);
else
Con_Printf("%s\n", zip->filename);
}
static void FSZIP_ClosePath(void *handle)
{
zipfile_t *zip = handle;
if (--zip->references > 0)
return; //not yet time
unzClose(zip->handle);
if (zip->files)
Z_Free(zip->files);
Z_Free(zip);
}
static void FSZIP_BuildHash(void *handle)
{
zipfile_t *zip = handle;
int i;
for (i = 0; i < zip->numfiles; i++)
{
if (!Hash_GetInsensative(&filesystemhash, zip->files[i].name))
{
fs_hash_files++;
Hash_AddInsensative(&filesystemhash, zip->files[i].name, &zip->files[i], &zip->files[i].bucket);
}
else
fs_hash_dups++;
}
}
static qboolean FSZIP_FLocate(void *handle, flocation_t *loc, char *filename, void *hashedresult)
{
packfile_t *pf = hashedresult;
int i, len;
zipfile_t *zip = handle;
// look through all the pak file elements
if (pf)
{ //is this a pointer to a file in this pak?
if (pf < zip->files || pf >= zip->files + zip->numfiles)
return false; //was found in a different path
}
else
{
for (i=0 ; i<zip->numfiles ; i++) //look for the file
{
if (!strcmp (zip->files[i].name, filename))
{
pf = &zip->files[i];
break;
}
}
}
if (pf)
{
len = pf->filelen;
if (loc)
{
loc->index = pf - zip->files;
strcpy(loc->rawname, zip->filename);
loc->offset = pf->filepos;
loc->len = pf->filelen;
unzLocateFileMy (zip->handle, loc->index, zip->files[loc->index].filepos);
loc->offset = unzGetCurrentFileUncompressedPos(zip->handle);
// if (loc->offset<0)
// { //file not found, or is compressed.
// *loc->rawname = '\0';
// loc->offset=0;
// }
}
return true;
}
return false;
}
static void FSZIP_ReadFile(void *handle, flocation_t *loc, char *buffer)
{
zipfile_t *zip = handle;
int err;
unzLocateFileMy (zip->handle, loc->index, zip->files[loc->index].filepos);
unzOpenCurrentFile (zip->handle);
err = unzReadCurrentFile (zip->handle, buffer, zip->files[loc->index].filelen);
unzCloseCurrentFile (zip->handle);
if (err!=zip->files[loc->index].filelen)
{
Con_Printf ("Can't extract file \"%s:%s\" (corrupt)\n", zip->filename, zip->files[loc->index].name);
return;
}
return;
}
static int FSZIP_EnumerateFiles (void *handle, char *match, int (*func)(char *, int, void *), void *parm)
{
zipfile_t *zip = handle;
int num;
for (num = 0; num<(int)zip->numfiles; num++)
{
if (wildcmp(match, zip->files[num].name))
{
if (!func(zip->files[num].name, zip->files[num].filelen, parm))
return false;
}
}
return true;
}
/*
=================
COM_LoadZipFile
Takes an explicit (not game tree related) path to a pak file.
Loads the header and directory, adding the files at the beginning
of the list so they override previous pack files.
=================
*/
static void *FSZIP_LoadZipFile (vfsfile_t *packhandle, char *desc)
{
int i;
zipfile_t *zip;
packfile_t *newfiles;
unz_global_info globalinf;
unz_file_info file_info;
zip = Z_Malloc(sizeof(zipfile_t));
Q_strncpyz(zip->filename, desc, sizeof(zip->filename));
zip->handle = unzOpen ((zip->raw = packhandle));
if (!zip->handle)
{
Z_Free(zip);
Con_TPrintf (TL_COULDNTOPENZIP, desc);
return NULL;
}
unzGetGlobalInfo (zip->handle, &globalinf);
zip->numfiles = globalinf.number_entry;
zip->files = newfiles = Z_Malloc (zip->numfiles * sizeof(packfile_t));
for (i = 0; i < zip->numfiles; i++)
{
unzGetCurrentFileInfo (zip->handle, &file_info, newfiles[i].name, sizeof(newfiles[i].name), NULL, 0, NULL, 0);
Q_strlwr(newfiles[i].name);
newfiles[i].filelen = file_info.uncompressed_size;
newfiles[i].filepos = file_info.c_offset;
unzGoToNextFile (zip->handle);
}
zip->references = 1;
zip->currentfile = NULL;
Con_TPrintf (TL_ADDEDZIPFILE, desc, zip->numfiles);
return zip;
}
int FSZIP_GeneratePureCRC(void *handle, int seed, int crctype)
{
zipfile_t *zip = handle;
unz_file_info file_info;
int *filecrcs;
int numcrcs=0;
int i;
filecrcs = BZ_Malloc((zip->numfiles+1)*sizeof(int));
filecrcs[numcrcs++] = seed;
unzGoToFirstFile(zip->handle);
for (i = 0; i < zip->numfiles; i++)
{
if (zip->files[i].filelen>0)
{
unzGetCurrentFileInfo (zip->handle, &file_info, NULL, 0, NULL, 0, NULL, 0);
filecrcs[numcrcs++] = file_info.crc;
}
unzGoToNextFile (zip->handle);
}
if (crctype)
return Com_BlockChecksum(filecrcs, numcrcs*sizeof(int));
else
return Com_BlockChecksum(filecrcs+1, (numcrcs-1)*sizeof(int));
}
typedef struct {
vfsfile_t funcs;
//in case we're forced away.
zipfile_t *parent;
qboolean iscompressed;
int pos;
int length; //try and optimise some things
int index;
int startpos;
} vfszip_t;
void VFSZIP_MakeActive(vfszip_t *vfsz)
{
int i;
char buffer[8192]; //must be power of two
if ((vfszip_t*)vfsz->parent->currentfile == vfsz)
return; //already us
if (vfsz->parent->currentfile)
unzCloseCurrentFile(vfsz->parent->handle);
unzLocateFileMy(vfsz->parent->handle, vfsz->index, vfsz->startpos);
unzOpenCurrentFile(vfsz->parent->handle);
if (vfsz->pos > 0)
{
Con_DPrintf("VFSZIP_MakeActive: Shockingly inefficient\n");
//now we need to seek up to where we had previously gotten to.
for (i = 0; i < vfsz->pos-sizeof(buffer); i++)
unzReadCurrentFile(vfsz->parent->handle, buffer, sizeof(buffer));
unzReadCurrentFile(vfsz->parent->handle, buffer, vfsz->pos - i);
}
vfsz->parent->currentfile = (vfsfile_t*)vfsz;
}
int VFSZIP_ReadBytes (struct vfsfile_s *file, void *buffer, int bytestoread)
{
int read;
vfszip_t *vfsz = (vfszip_t*)file;
if (vfsz->iscompressed)
{
VFSZIP_MakeActive(vfsz);
read = unzReadCurrentFile(vfsz->parent->handle, buffer, bytestoread);
}
else
{
if (vfsz->parent->currentfile != file)
{
unzCloseCurrentFile(vfsz->parent->handle);
VFS_SEEK(vfsz->parent->raw, vfsz->pos+vfsz->startpos);
vfsz->parent->currentfile = file;
}
read = VFS_READ(vfsz->parent->raw, buffer, bytestoread);
}
vfsz->pos += read;
return read;
}
int VFSZIP_WriteBytes (struct vfsfile_s *file, void *buffer, int bytestoread)
{
Sys_Error("VFSZIP_WriteBytes: Not supported\n");
return 0;
}
qboolean VFSZIP_Seek (struct vfsfile_s *file, unsigned long pos)
{
vfszip_t *vfsz = (vfszip_t*)file;
//This is *really* inefficient
if (vfsz->parent->currentfile == file)
{
unzCloseCurrentFile(vfsz->parent->handle);
vfsz->parent->currentfile = NULL; //make it not us
}
if (pos < 0 || pos > vfsz->length)
return false;
vfsz->pos = pos;
return true;
}
unsigned long VFSZIP_Tell (struct vfsfile_s *file)
{
vfszip_t *vfsz = (vfszip_t*)file;
return vfsz->pos;
}
unsigned long VFSZIP_GetLen (struct vfsfile_s *file)
{
vfszip_t *vfsz = (vfszip_t*)file;
return vfsz->length;
}
void VFSZIP_Close (struct vfsfile_s *file)
{
vfszip_t *vfsz = (vfszip_t*)file;
if (vfsz->parent->currentfile == file)
vfsz->parent->currentfile = NULL; //make it not us
FSZIP_ClosePath(vfsz->parent);
Z_Free(vfsz);
}
vfsfile_t *FSZIP_OpenVFS(void *handle, flocation_t *loc, char *mode)
{
int rawofs;
zipfile_t *zip = handle;
vfszip_t *vfsz;
if (strchr(mode, 'w'))
return NULL;
vfsz = Z_Malloc(sizeof(vfszip_t));
vfsz->parent = zip;
vfsz->index = loc->index;
vfsz->startpos = zip->files[loc->index].filepos;
vfsz->length = loc->len;
vfsz->funcs.Close = VFSZIP_Close;
vfsz->funcs.GetLen = VFSZIP_GetLen;
vfsz->funcs.ReadBytes = VFSZIP_ReadBytes;
vfsz->funcs.Seek = VFSZIP_Seek;
vfsz->funcs.Tell = VFSZIP_Tell;
vfsz->funcs.WriteBytes = NULL;
unzLocateFileMy(vfsz->parent->handle, vfsz->index, vfsz->startpos);
rawofs = unzGetCurrentFileUncompressedPos(zip->handle);
vfsz->iscompressed = rawofs<0;
if (!vfsz->iscompressed)
{
vfsz->startpos = rawofs;
VFS_SEEK(zip->raw, vfsz->startpos);
}
vfsz->parent->currentfile = (vfsfile_t*)vfsz;
zip->references++;
return (vfsfile_t*)vfsz;
}
searchpathfuncs_t zipfilefuncs = {
FSZIP_PrintPath,
FSZIP_ClosePath,
FSZIP_BuildHash,
FSZIP_FLocate,
FSZIP_ReadFile,
FSZIP_EnumerateFiles,
FSZIP_LoadZipFile,
FSZIP_GeneratePureCRC,
FSZIP_OpenVFS
};
#endif
//======================================================================================================
typedef struct searchpath_s
{
searchpathfuncs_t *funcs;
qboolean copyprotected; //don't allow downloads from here.
qboolean istemporary;
void *handle;
int crc_check; //client sorts packs according to this checksum
int crc_reply; //client sends a different crc back to the server, for the paks it's actually loaded.
struct searchpath_s *next;
struct searchpath_s *nextpure;
} searchpath_t;
searchpath_t *com_searchpaths;
searchpath_t *com_purepaths;
searchpath_t *com_base_searchpaths; // without gamedirs
static void COM_AddDataFiles(char *pathto, searchpath_t *search, char *extension, searchpathfuncs_t *funcs);
searchpath_t *COM_AddPathHandle(char *probablepath, searchpathfuncs_t *funcs, void *handle, qboolean copyprotect, qboolean istemporary)
{
searchpath_t *search;
search = (searchpath_t*)Z_Malloc (sizeof(searchpath_t));
search->copyprotected = copyprotect;
search->istemporary = istemporary;
search->handle = handle;
search->funcs = funcs;
search->next = com_searchpaths;
com_searchpaths = search;
com_fschanged = true;
//add any data files too
// if (loadstuff & 2)
COM_AddDataFiles(probablepath, search, "pak", &packfilefuncs);//q1/hl/h2/q2
//pk2s never existed.
#ifdef AVAIL_ZLIB
// if (loadstuff & 4)
COM_AddDataFiles(probablepath, search, "pk3", &zipfilefuncs); //q3 + offspring
// if (loadstuff & 8)
COM_AddDataFiles(probablepath, search, "pk4", &zipfilefuncs); //q4
//we could easily add zip, but it's friendlier not to
#endif
return search;
}
/*
================
COM_filelength
================
*/
int COM_filelength (FILE *f)
{
int pos;
int end;
pos = ftell (f);
fseek (f, 0, SEEK_END);
end = ftell (f);
fseek (f, pos, SEEK_SET);
return end;
}
int COM_FileOpenRead (char *path, FILE **hndl)
{
FILE *f;
f = fopen(path, "rb");
if (!f)
{
*hndl = NULL;
return -1;
}
*hndl = f;
return COM_filelength(f);
}
int COM_FileSize(char *path)
{
int len;
flocation_t loc;
len = FS_FLocateFile(path, FSLFRT_LENGTH, &loc);
return len;
}
/*
============
COM_Path_f
============
*/
void COM_Path_f (void)
{
searchpath_t *s;
Con_TPrintf (TL_CURRENTSEARCHPATH);
if (com_purepaths)
{
for (s=com_purepaths ; s ; s=s->nextpure)
{
s->funcs->PrintPath(s->handle);
}
Con_Printf ("----------\n");
}
for (s=com_searchpaths ; s ; s=s->next)
{
if (s == com_base_searchpaths)
Con_Printf ("----------\n");
s->funcs->PrintPath(s->handle);
}
}
/*
============
COM_Dir_f
============
*/
static int COM_Dir_List(char *name, int size, void *parm)
{
Con_Printf("%s (%i)\n", name, size);
return 1;
}
void COM_Dir_f (void)
{
char match[MAX_QPATH];
Q_strncpyz(match, Cmd_Argv(1), sizeof(match));
if (Cmd_Argc()>2)
{
strncat(match, "/*.", sizeof(match)-1);
match[sizeof(match)-1] = '\0';
strncat(match, Cmd_Argv(2), sizeof(match)-1);
match[sizeof(match)-1] = '\0';
}
else
strncat(match, "/*", sizeof(match)-1);
COM_EnumerateFiles(match, COM_Dir_List, NULL);
}
/*
============
COM_Locate_f
============
*/
void COM_Locate_f (void)
{
flocation_t loc;
if (FS_FLocateFile(Cmd_Argv(1), FSLFRT_LENGTH, &loc)>=0)
{
if (!*loc.rawname)
{
Con_Printf("File is compressed inside ");
loc.search->funcs->PrintPath(loc.search->handle);
}
else
Con_Printf("Inside %s\n", loc.rawname);
}
else
Con_Printf("Not found\n");
}
/*
============
COM_WriteFile
The filename will be prefixed by the current game directory
============
*/
void COM_WriteFile (char *filename, void *data, int len)
{
FILE *f;
char name[MAX_OSPATH];
sprintf (name, "%s/%s", com_gamedir, filename);
COM_CreatePath(name);
f = fopen (name, "wb");
if (!f)
{
Sys_mkdir(com_gamedir);
f = fopen (name, "wb");
if (!f)
{
Con_Printf("Error opening %s for writing\n", filename);
return;
}
}
Sys_Printf ("COM_WriteFile: %s\n", name);
fwrite (data, 1, len, f);
fclose (f);
com_fschanged=true;
}
FILE *COM_WriteFileOpen (char *filename) //like fopen, but based around quake's paths.
{
FILE *f;
char name[MAX_OSPATH];
sprintf (name, "%s/%s", com_gamedir, filename);
COM_CreatePath(name);
f = fopen (name, "wb");
return f;
}
/*
============
COM_CreatePath
Only used for CopyFile and download
============
*/
void COM_CreatePath (char *path)
{
char *ofs;
for (ofs = path+1 ; *ofs ; ofs++)
{
if (*ofs == '/')
{ // create the directory
*ofs = 0;
Sys_mkdir (path);
*ofs = '/';
}
}
}
/*
===========
COM_CopyFile
Copies a file over from the net to the local cache, creating any directories
needed. This is for the convenience of developers using ISDN from home.
===========
*/
void COM_CopyFile (char *netpath, char *cachepath)
{
FILE *in, *out;
int remaining, count;
char buf[4096];
remaining = COM_FileOpenRead (netpath, &in);
COM_CreatePath (cachepath); // create directories up to the cache file
out = fopen(cachepath, "wb");
if (!out)
Sys_Error ("Error opening %s", cachepath);
while (remaining)
{
if (remaining < sizeof(buf))
count = remaining;
else
count = sizeof(buf);
fread (buf, 1, count, in);
fwrite (buf, 1, count, out);
remaining -= count;
}
fclose (in);
fclose (out);
}
int fs_hash_dups;
int fs_hash_files;
void FS_FlushFSHash(void)
{
if (filesystemhash.numbuckets)
{
int i;
bucket_t *bucket, *next;
for (i = 0; i < filesystemhash.numbuckets; i++)
{
bucket = filesystemhash.bucket[i];
filesystemhash.bucket[i] = NULL;
while(bucket)
{
next = bucket->next;
if (bucket->keystring == (char*)(bucket+1))
Z_Free(bucket);
bucket = next;
}
}
}
com_fschanged = true;
}
void FS_RebuildFSHash(void)
{
searchpath_t *search;
if (!filesystemhash.numbuckets)
{
filesystemhash.numbuckets = 1024;
filesystemhash.bucket = (bucket_t**)BZ_Malloc(Hash_BytesForBuckets(filesystemhash.numbuckets));
}
else
{
FS_FlushFSHash();
}
Hash_InitTable(&filesystemhash, filesystemhash.numbuckets, filesystemhash.bucket);
fs_hash_dups = 0;
fs_hash_files = 0;
if (com_purepaths)
{ //go for the pure paths first.
for (search = com_purepaths; search; search = search->nextpure)
{
search->funcs->BuildHash(search->handle);
}
}
for (search = com_searchpaths ; search ; search = search->next)
{
search->funcs->BuildHash(search->handle);
}
com_fschanged = false;
Con_Printf("%i unique files, %i duplicates\n", fs_hash_files, fs_hash_dups);
}
/*
===========
COM_FindFile
Finds the file in the search path.
Sets com_filesize and one of handle or file
===========
*/
//if loc is valid, loc->search is always filled in, the others are filled on success.
//returns -1 if couldn't find.
int FS_FLocateFile(char *filename, FSLF_ReturnType_e returntype, flocation_t *loc)
{
int depth=0, len;
searchpath_t *search;
void *pf;
//Con_Printf("Finding %s: ", filename);
if (com_fs_cache.value)
{
if (com_fschanged)
FS_RebuildFSHash();
pf = Hash_GetInsensative(&filesystemhash, filename);
if (!pf)
goto fail;
}
else
pf = NULL;
if (com_purepaths)
{
for (search = com_purepaths ; search ; search = search->nextpure)
{
if (search->funcs->FindFile(search->handle, loc, filename, pf))
{
if (loc)
{
loc->search = search;
len = loc->len;
}
else
len = 0;
goto out;
}
depth += (search->funcs != &osfilefuncs || returntype == FSLFRT_DEPTH_ANYPATH);
}
}
//
// search through the path, one element at a time
//
for (search = com_searchpaths ; search ; search = search->next)
{
if (search->funcs->FindFile(search->handle, loc, filename, pf))
{
if (loc)
{
loc->search = search;
len = loc->len;
}
else
len = 1;
goto out;
}
depth += (search->funcs != &osfilefuncs || returntype == FSLFRT_DEPTH_ANYPATH);
}
fail:
if (loc)
loc->search = NULL;
depth = 0x7fffffff;
len = -1;
out:
/* if (len>=0)
{
if (loc)
Con_Printf("Found %s:%i\n", loc->rawname, loc->len);
else
Con_Printf("Found %s\n", filename);
}
else
Con_Printf("Failed\n");
*/ if (returntype == FSLFRT_LENGTH)
return len;
else
return depth;
}
#if 0
int COM_FOpenLocationFILE(flocation_t *loc, FILE **file)
{
if (!*loc->rawname)
{
if (!loc->len)
{
*file = NULL;
return -1;
}
if (loc->search->funcs->ReadFile)
{//create a new, temp file, bung the contents of the compressed file into it, then continue.
char *buf;
FILE *f = tmpfile();
buf = BZ_Malloc(loc->len);
loc->search->funcs->ReadFile(loc->search->handle, loc, buf);
fwrite(buf, 1, loc->len, f);
BZ_Free(buf);
fseek(f, 0, SEEK_SET);
*file = f;
com_pathforfile = loc->search;
return loc->len;
}
return -1;
}
// Con_Printf("Opening %s\n", loc->rawname);
*file = fopen(loc->rawname, "rb");
if (!*file)
return -1;
fseek(*file, loc->offset, SEEK_SET);
com_pathforfile = loc->search;
return loc->len;
}
int COM_FOpenFile(char *filename, FILE **file)
{
flocation_t loc;
Con_Printf("^1COM_FOpenFile is obsolete\n");
FS_FLocateFile(filename, FSLFRT_LENGTH, &loc);
com_filesize = -1;
if (loc.search)
{
com_file_copyprotected = loc.search->copyprotected;
com_filesize = COM_FOpenLocationFILE(&loc, file);
}
else
*file = NULL;
return com_filesize;
}
/*
int COM_FOpenWriteFile(char *filename, FILE **file)
{
COM_CreatePath(filename);
*file = fopen(filename, "wb");
return !!*file;
}
*/
#endif
//int COM_FOpenFile (char *filename, FILE **file) {file_from_pak=0;return COM_FOpenFile2 (filename, file, false);} //FIXME: TEMPORARY
//true if protection kicks in
qboolean Sys_PathProtection(char *pattern)
{
if (strchr(pattern, '\\'))
{
char *s;
while(s = strchr(pattern, '\\'))
*s = '/';
Con_Printf("Warning: \\ charactures in filename %s\n", pattern);
}
if (strstr(pattern, ".."))
Con_Printf("Error: '..' charactures in filename %s\n", pattern);
else if (pattern[0] == '/')
Con_Printf("Error: absolute path in filename %s\n", pattern);
else if (strstr(pattern, ":")) //win32 drive seperator (or mac path seperator, but / works there and they're used to it)
Con_Printf("Error: absolute path in filename %s\n", pattern);
else
return false;
return true;
}
vfsfile_t *FS_OpenVFS(char *filename, char *mode, int relativeto)
{
char fullname[MAX_OSPATH];
flocation_t loc;
vfsfile_t *vfs;
//eventually, this function will be the *ONLY* way to get at files
//blanket-bans
if (Sys_PathProtection(filename))
return NULL;
switch (relativeto)
{
case FS_GAMEONLY: //OS access only, no paks
if (*com_homedir)
{
_snprintf(fullname, sizeof(fullname), "%s/%s/%s", com_homedir, gamedirfile, filename);
vfs = VFSOS_Open(fullname, mode);
if (vfs)
return vfs;
}
_snprintf(fullname, sizeof(fullname), "%s/%s/%s", com_quakedir, gamedirfile, filename);
return VFSOS_Open(fullname, mode);
case FS_GAME:
if (*com_homedir)
_snprintf(fullname, sizeof(fullname), "%s/%s/%s", com_homedir, gamedirfile, filename);
else
_snprintf(fullname, sizeof(fullname), "%s/%s/%s", com_quakedir, gamedirfile, filename);
break;
case FS_BASE:
if (*com_homedir)
{
_snprintf(fullname, sizeof(fullname), "%s/%s", com_homedir, filename);
vfs = VFSOS_Open(fullname, mode);
if (vfs)
return vfs;
}
_snprintf(fullname, sizeof(fullname), "%s/%s", com_quakedir, filename);
return VFSOS_Open(fullname, mode);
case FS_CONFIGONLY:
if (*com_homedir)
{
_snprintf(fullname, sizeof(fullname), "%s/fte/%s", com_homedir, filename);
vfs = VFSOS_Open(fullname, mode);
if (vfs)
return vfs;
}
_snprintf(fullname, sizeof(fullname), "%s/fte/%s", com_quakedir, filename);
return VFSOS_Open(fullname, mode);
default:
Sys_Error("FS_CreatePath: Bad relative path");
break;
}
FS_FLocateFile(filename, FSLFRT_IFFOUND, &loc);
if (loc.search)
{
com_file_copyprotected = loc.search->copyprotected;
return loc.search->funcs->OpenVFS(loc.search->handle, &loc, mode);
}
return NULL;
}
void FS_Rename(char *oldf, char *newf, int relativeto)
{
rename(oldf, newf);
}
void FS_Remove(char *fname, int relativeto)
{
unlink (fname);
}
void FS_CreatePath(char *pname, int relativeto)
{
char fullname[MAX_OSPATH];
switch (relativeto)
{
case FS_GAMEONLY:
case FS_GAME:
_snprintf(fullname, sizeof(fullname), "%s/%s", com_gamedir, pname);
break;
case FS_BASE:
_snprintf(fullname, sizeof(fullname), "%s/%s", com_homedir, pname);
break;
case FS_CONFIGONLY:
_snprintf(fullname, sizeof(fullname), "%s/fte/%s", com_homedir, pname);
break;
default:
Sys_Error("FS_CreatePath: Bad relative path");
break;
}
COM_CreatePath(fullname);
}
static cache_user_t *loadcache;
static qbyte *loadbuf;
static int loadsize;
/*
============
COM_LoadFile
Filename are reletive to the quake directory.
Always appends a 0 qbyte to the loaded data.
============
*/
qbyte *COM_LoadFile (char *path, int usehunk)
{
vfsfile_t *f;
qbyte *buf;
int len;
char base[32];
flocation_t loc;
FS_FLocateFile(path, FSLFRT_LENGTH, &loc);
if (!loc.search)
return NULL; //wasn't found
f = loc.search->funcs->OpenVFS(loc.search->handle, &loc, "r");
if (!f)
return NULL;
com_filesize = len = VFS_GETLEN(f);
// extract the filename base name for hunk tag
COM_FileBase (path, base);
if (usehunk == 0)
buf = (qbyte*)Z_Malloc (len+1);
else if (usehunk == 1)
buf = (qbyte*)Hunk_AllocName (len+1, base);
else if (usehunk == 2)
buf = (qbyte*)Hunk_TempAlloc (len+1);
else if (usehunk == 3)
buf = (qbyte*)Cache_Alloc (loadcache, len+1, base);
else if (usehunk == 4)
{
if (len+1 > loadsize)
buf = (qbyte*)Hunk_TempAlloc (len+1);
else
buf = loadbuf;
}
else if (usehunk == 5)
buf = (qbyte*)BZ_Malloc(len+1);
else if (usehunk == 6)
buf = (qbyte*)Hunk_TempAllocMore (len+1);
else
{
Sys_Error ("COM_LoadFile: bad usehunk");
buf = NULL;
}
if (!buf)
Sys_Error ("COM_LoadFile: not enough space for %s", path);
((qbyte *)buf)[len] = 0;
#ifndef SERVERONLY
if (qrenderer)
if (Draw_BeginDisc)
Draw_BeginDisc ();
#endif
VFS_READ(f, buf, len);
VFS_CLOSE(f);
#ifndef SERVERONLY
if (qrenderer)
if (Draw_EndDisc)
Draw_EndDisc ();
#endif
return buf;
}
qbyte *COM_LoadMallocFile (char *path) //used for temp info along side temp hunk
{
return COM_LoadFile (path, 5);
}
qbyte *COM_LoadHunkFile (char *path)
{
return COM_LoadFile (path, 1);
}
qbyte *COM_LoadTempFile (char *path)
{
return COM_LoadFile (path, 2);
}
qbyte *COM_LoadTempFile2 (char *path)
{
return COM_LoadFile (path, 6);
}
void COM_LoadCacheFile (char *path, struct cache_user_s *cu)
{
loadcache = cu;
COM_LoadFile (path, 3);
}
// uses temp hunk if larger than bufsize
qbyte *COM_LoadStackFile (char *path, void *buffer, int bufsize)
{
qbyte *buf;
loadbuf = (qbyte *)buffer;
loadsize = bufsize;
buf = COM_LoadFile (path, 4);
return buf;
}
void COM_EnumerateFiles (char *match, int (*func)(char *, int, void *), void *parm)
{
searchpath_t *search;
for (search = com_searchpaths; search ; search = search->next)
{
// is the element a pak file?
if (!search->funcs->EnumerateFiles(search->handle, match, func, parm))
break;
}
}
void COM_FlushTempoaryPacks(void)
{
searchpath_t *next;
while (com_searchpaths && com_searchpaths->istemporary)
{
com_searchpaths->funcs->ClosePath(com_searchpaths->handle);
next = com_searchpaths->next;
Z_Free (com_searchpaths);
com_searchpaths = next;
com_fschanged = true;
}
}
qboolean COM_LoadMapPackFile (char *filename, int ofs)
{
return false;
/*
dpackheader_t header;
int i;
packfile_t *newfiles;
int numpackfiles;
pack_t *pack;
FILE *packhandle;
dpackfile_t info;
int fstart;
char *ballsup;
flocation_t loc;
FS_FLocateFile(filename, FSLFRT_LENGTH, &loc);
if (!loc.search)
{
Con_Printf("Couldn't refind file\n");
return false;
}
if (!*loc.rawname)
{
Con_Printf("File %s is compressed\n");
return false;
}
packhandle = fopen(loc.rawname, "rb");
if (!packhandle)
{
Con_Printf("Couldn't reopen file\n");
return false;
}
fseek(packhandle, loc.offset, SEEK_SET);
fstart = loc.offset;
fseek(packhandle, ofs+fstart, SEEK_SET);
fread (&header, 1, sizeof(header), packhandle);
if (header.id[0] != 'P' || header.id[1] != 'A'
|| header.id[2] != 'C' || header.id[3] != 'K')
{
return false;
}
header.dirofs = LittleLong (header.dirofs);
header.dirlen = LittleLong (header.dirlen);
numpackfiles = header.dirlen / sizeof(dpackfile_t);
newfiles = (packfile_t*)Z_Malloc (numpackfiles * sizeof(packfile_t));
fseek (packhandle, header.dirofs+fstart, SEEK_SET);
pack = (pack_t*)Z_Malloc (sizeof (pack_t));
// parse the directory
for (i=0 ; i<numpackfiles ; i++)
{
fread (&info, 1, sizeof(info), packhandle);
strcpy (newfiles[i].name, info.name);
Q_strlwr(newfiles[i].name);
while ((ballsup = strchr(newfiles[i].name, '\\')))
*ballsup = '/';
newfiles[i].filepos = LittleLong(info.filepos)+fstart;
newfiles[i].filelen = LittleLong(info.filelen);
}
strcpy (pack->filename, loc.rawname);
pack->handle = packhandle;
pack->numfiles = numpackfiles;
pack->files = newfiles;
Con_TPrintf (TL_ADDEDPACKFILE, filename, numpackfiles);
COM_AddPathHandle(&packfilefuncs, pack, true, true);
return true;
*/
}
#ifdef DOOMWADS
qboolean COM_LoadWadFile (char *wadname)
{
dwadheader_t header;
int i;
packfile_t *newfiles;
int numpackfiles;
pack_t *pack;
FILE *packhandle;
dwadfile_t info;
int fstart;
int section=0;
char sectionname[MAX_QPATH];
char filename[52];
char neatwadname[52];
searchpath_t *search;
flocation_t loc;
FS_FLocateFile(wadname, FSLFRT_LENGTH, &loc);
if (!loc.search)
{
return false;
}
if (!*loc.rawname)
{
Con_Printf("File %s is compressed\n");
return false;
}
packhandle = fopen(loc.rawname, "rb");
if (!packhandle)
{
Con_Printf("Couldn't open file\n");
return false;
}
fseek(packhandle, loc.offset, SEEK_SET);
fstart = loc.offset;
fseek(packhandle, fstart, SEEK_SET);
fread (&header, 1, sizeof(header), packhandle);
if (header.id[1] != 'W'
|| header.id[2] != 'A' || header.id[3] != 'D')
{
return false;
}
if (header.id[0] == 'I')
*neatwadname = '\0';
else if (header.id[0] == 'P')
{
COM_StripExtension(wadname, neatwadname);
strcat(neatwadname, ":");
}
else
return false;
header.dirofs = LittleLong (header.dirofs);
header.dirlen = LittleLong (header.dirlen);
numpackfiles = header.dirlen;
newfiles = (packfile_t*)Z_Malloc (numpackfiles * sizeof(packfile_t));
fseek (packhandle, header.dirofs+fstart, SEEK_SET);
pack = (pack_t*)Z_Malloc (sizeof (pack_t));
#ifdef HASH_FILESYSTEM
Hash_InitTable(&pack->hash, numpackfiles+1, Z_Malloc(Hash_BytesForBuckets(numpackfiles+1)));
#endif
// parse the directory
for (i=0 ; i<numpackfiles ; i++)
{
fread (&info, 1, sizeof(info), packhandle);
strcpy (filename, info.name);
filename[8] = '\0';
Q_strlwr(filename);
newfiles[i].filepos = LittleLong(info.filepos)+fstart;
newfiles[i].filelen = LittleLong(info.filelen);
switch(section) //be prepared to remap filenames.
{
newsection:
case 0:
if (info.filelen == 0)
{ //marker for something...
if (!strcmp(filename, "s_start"))
{
section = 2;
sprintf (newfiles[i].name, "sprites/%s", filename);
break;
}
if (!strcmp(filename, "p_start"))
{
section = 3;
sprintf (newfiles[i].name, "patches/%s", filename);
break;
}
if (!strcmp(filename, "f_start"))
{
section = 4;
sprintf (newfiles[i].name, "flats/%s", filename);
break;
}
if ((filename[0] == 'e' && filename[2] == 'm') || !strncmp(filename, "map", 3))
{ //this is the start of a beutiful new map
section = 1;
strcpy(sectionname, filename);
sprintf (newfiles[i].name, "maps/%s%s.bsp", neatwadname, filename);
newfiles[i].filepos = fstart;
newfiles[i].filelen = 4;
break;
}
if (!strncmp(filename, "gl_", 3) && ((filename[4] == 'e' && filename[5] == 'm') || !strncmp(filename+3, "map", 3)))
{ //this is the start of a beutiful new map
section = 5;
strcpy(sectionname, filename+3);
break;
}
}
sprintf (newfiles[i].name, "wad/%s", filename);
break;
case 1: //map section
if (strcmp(filename, "things") &&
strcmp(filename, "linedefs") &&
strcmp(filename, "sidedefs") &&
strcmp(filename, "vertexes") &&
strcmp(filename, "segs") &&
strcmp(filename, "ssectors") &&
strcmp(filename, "nodes") &&
strcmp(filename, "sectors") &&
strcmp(filename, "reject") &&
strcmp(filename, "blockmap"))
{
section = 0;
goto newsection;
}
sprintf (newfiles[i].name, "maps/%s%s.%s", neatwadname, sectionname, filename);
break;
case 5: //glbsp output section
if (strcmp(filename, "gl_vert") &&
strcmp(filename, "gl_segs") &&
strcmp(filename, "gl_ssect") &&
strcmp(filename, "gl_pvs") &&
strcmp(filename, "gl_nodes"))
{
section = 0;
goto newsection;
}
sprintf (newfiles[i].name, "maps/%s%s.%s", neatwadname, sectionname, filename);
break;
case 2: //sprite section
if (!strcmp(filename, "s_end"))
{
section = 0;
goto newsection;
}
sprintf (newfiles[i].name, "sprites/%s", filename);
break;
case 3: //patches section
if (!strcmp(filename, "p_end"))
{
section = 0;
goto newsection;
}
sprintf (newfiles[i].name, "patches/%s", filename);
break;
case 4: //flats section
if (!strcmp(filename, "f_end"))
{
section = 0;
goto newsection;
}
sprintf (newfiles[i].name, "flats/%s", filename);
break;
}
#ifdef HASH_FILESYSTEM
Hash_AddInsensative(&pack->hash, newfiles[i].name, &newfiles[i], &newfiles[i].bucket);
#endif
}
strcpy (pack->filename, loc.rawname);
pack->handle = packhandle;
pack->numfiles = numpackfiles;
pack->files = newfiles;
Con_Printf ("Added wad file %s\n", wadname, numpackfiles);
COM_AddPathHandle(&packfilefuncs, pack, true, false);
COM_StripExtension(wadname, sectionname);
strcat(sectionname, ".gwa");
if (strcmp(sectionname, wadname))
COM_LoadWadFile(sectionname);
return true;
}
#endif
#ifdef DOOMWADS
static int COM_AddWad (char *descriptor)
{
searchpath_t *search;
char pakfile[MAX_OSPATH];
sprintf (pakfile, descriptor, com_gamedir);
for (search = com_searchpaths; search; search = search->next)
{
if (!stricmp(search->filename, pakfile))
return true; //already loaded (base paths?)
}
return COM_LoadWadFile (descriptor);
}
#endif
typedef struct {
searchpathfuncs_t *funcs;
searchpath_t *parentpath;
char *parentdesc;
} wildpaks_t;
static int COM_AddWildDataFiles (char *descriptor, int size, void *vparam)
{
wildpaks_t *param = vparam;
vfsfile_t *vfs;
searchpathfuncs_t *funcs = param->funcs;
searchpath_t *search;
pack_t *pak;
char pakfile[MAX_OSPATH];
flocation_t loc;
sprintf (pakfile, "%s%s", param->parentdesc, descriptor);
for (search = com_searchpaths; search; search = search->next)
{
if (search->funcs != funcs)
continue;
if (!stricmp((char*)search->handle, pakfile)) //assumption: first member of structure is a char array
return true; //already loaded (base paths?)
}
search = param->parentpath;
if (!search->funcs->FindFile(search->handle, &loc, descriptor, NULL))
return true; //not found..
vfs = search->funcs->OpenVFS(search->handle, &loc, "r");
pak = funcs->OpenNew (vfs, pakfile);
if (!pak)
return true;
sprintf (pakfile, "%s%s/", param->parentdesc, descriptor);
COM_AddPathHandle(pakfile, funcs, pak, true, false);
return true;
}
static void COM_AddDataFiles(char *pathto, searchpath_t *search, char *extension, searchpathfuncs_t *funcs)
{
//search is the parent
int i;
void *handle;
char pakfile[MAX_OSPATH];
vfsfile_t *vfs;
flocation_t loc;
wildpaks_t wp;
for (i=0 ; ; i++)
{
_snprintf (pakfile, sizeof(pakfile), "pak%i.%s", i, extension);
if (!search->funcs->FindFile(search->handle, &loc, pakfile, NULL))
break; //not found..
_snprintf (pakfile, sizeof(pakfile), "%spak%i.%s", pathto, i, extension);
vfs = search->funcs->OpenVFS(search->handle, &loc, "r");
if (!vfs)
break;
Con_Printf("Opened %s\n", pakfile);
handle = funcs->OpenNew (vfs, pakfile);
if (!handle)
break;
_snprintf (pakfile, sizeof(pakfile), "%spak%i.%s/", pathto, i, extension);
COM_AddPathHandle(pakfile, funcs, handle, true, false);
}
sprintf (pakfile, "*.%s", extension);
wp.funcs = funcs;
wp.parentdesc = pathto;
wp.parentpath = search;
search->funcs->EnumerateFiles(search->handle, pakfile, COM_AddWildDataFiles, &wp);
}
void COM_RefreshFSCache_f(void)
{
com_fschanged=true;
}
void COM_FlushFSCache(void)
{
if (com_fs_cache.value != 2)
com_fschanged=true;
}
/*
================
COM_AddGameDirectory
Sets com_gamedir, adds the directory to the head of the path,
then loads and adds pak1.pak pak2.pak ...
================
*/
void COM_AddGameDirectory (char *dir, unsigned int loadstuff)
{
searchpath_t *search;
char *p;
if ((p = strrchr(dir, '/')) != NULL)
strcpy(gamedirfile, ++p);
else
strcpy(gamedirfile, p);
strcpy (com_gamedir, dir);
for (search = com_searchpaths; search; search = search->next)
{
if (search->funcs != &osfilefuncs)
continue;
if (!stricmp(search->handle, com_gamedir))
return; //already loaded (base paths?)
}
//
// add the directory to the search path
//
p = Z_Malloc(strlen(dir)+1);
strcpy(p, dir);
COM_AddPathHandle(va("%s/", dir), &osfilefuncs, p, false, false);
}
char *COM_NextPath (char *prevpath)
{
searchpath_t *s;
char *prev;
if (!prevpath)
return com_gamedir;
prev = com_gamedir;
for (s=com_searchpaths ; s ; s=s->next)
{
if (s->funcs != &osfilefuncs)
continue;
if (prevpath == prev)
return s->handle;
prev = s->handle;
}
return NULL;
}
#ifndef CLIENTONLY
char *COM_GetPathInfo (int i, int *crc)
{
#ifdef WEBSERVER
extern cvar_t httpserver;
#endif
searchpath_t *s;
static char name[MAX_OSPATH];
char *protocol;
for (s=com_searchpaths ; s ; s=s->next)
{
i--;
if (!i)
break;
}
if (i) //too high.
return NULL;
#ifdef WEBSERVER
if (httpserver.value)
protocol = va("http://%s/", NET_AdrToString (net_local_sv_ipadr));
else
#endif
protocol = "qw://";
*crc = 0;//s->crc;
strcpy(name, "FIXME");
// Q_strncpyz(name, va("%s%s", protocol, COM_SkipPath(s->filename)), sizeof(name));
return name;
}
#endif
/*
================
COM_Gamedir
Sets the gamedir and path to a different directory.
================
*/
void COM_Gamedir (char *dir)
{
searchpath_t *next;
if (strstr(dir, "..") || strstr(dir, "/")
|| strstr(dir, "\\") || strstr(dir, ":") )
{
Con_TPrintf (TL_GAMEDIRAINTPATH);
return;
}
if (!strcmp(gamedirfile, dir))
return; // still the same
FS_ForceToPure(NULL, NULL, 0);
#ifndef SERVERONLY
Host_WriteConfiguration(); //before we change anything.
#endif
strcpy (gamedirfile, dir);
#ifndef CLIENTONLY
sv.gamedirchanged = true;
#endif
#ifndef SERVERONLY
cl.gamedirchanged = true;
#endif
FS_FlushFSHash();
//
// free up any current game dir info
//
while (com_searchpaths != com_base_searchpaths)
{
com_searchpaths->funcs->ClosePath(com_searchpaths->handle);
next = com_searchpaths->next;
Z_Free (com_searchpaths);
com_searchpaths = next;
}
com_fschanged = true;
//
// flush all data, so it will be forced to reload
//
Cache_Flush ();
COM_AddGameDirectory(va("%s/%s", com_quakedir, dir), (unsigned int)-1);
if (*com_homedir)
COM_AddGameDirectory(va("%s/%s", com_homedir, dir), (unsigned int)-1);
#ifndef SERVERONLY
{
char fn[MAX_OSPATH];
FILE *f;
// if (qrenderer) //only do this if we have already started the renderer
// Cbuf_InsertText("vid_restart\n", RESTRICT_LOCAL);
sprintf(fn, "%s/%s", com_gamedir, "config.cfg");
if ((f = fopen(fn, "r")) != NULL)
{
fclose(f);
Cbuf_InsertText("cl_warncmd 0\n"
"exec config.cfg\n"
"exec fte.cfg\n"
"exec frontend.cfg\n"
"cl_warncmd 1\n", RESTRICT_LOCAL);
}
}
#ifdef Q3SHADERS
{
extern void Shader_Init(void);
Shader_Init(); //FIXME!
}
#endif
Validation_FlushFileList(); //prevent previous hacks from making a difference.
//FIXME: load new palette, if different cause a vid_restart.
#endif
}
typedef struct {
char *file;
char *path;
} potentialgamepath_t;
potentialgamepath_t pgp[] = {
{"%s/id1/pak0.pak", "%s/id1"}, //quake1
{"%s/baseq2/pak0.pak", "%s/baseq2"}, //quake2
{"%s/data1/pak0.pak", "%s/data1"}, //hexen2
{"%s/data/data.pk3", "%s/data"}, //nexuiz
{"%s/baseq3/pak0.pk3", "%s/baseq3"}, //quake3
{"%s/base/assets0.pk3", "%s/base"} //jk2
};
typedef struct {
char *gamename; //sent to the master server when this is the current gamemode.
char *exename; //used if the exe name contains this
char *argname; //used if this was used as a parameter.
char *auniquefile; //used if this file is relative from the gamedir
char *dir1;
char *dir2;
char *dir3;
char *dir4;
} gamemode_info_t;
gamemode_info_t gamemode_info[] = {
//note that there is no basic 'fte' gamemode, this is because we aim for network compatability. Darkplaces-Quake is the closest we get.
//this is to avoid having too many gamemodes anyway.
{"Darkplaces-Quake", "darkplaces", "-quake", "id1/pak0.pak", "id1", "qw", "fte"},
{"Darkplaces-Hipnotic", "hipnotic", "-hipnotic", NULL/*"hipnotic/pak0.pak"*/,"id1", "qw", "hipnotic", "fte"},
{"Darkplaces-Rogue", "rogue", "-rogue", NULL/*"rogue/pak0.pak", "id1"*/, "qw", "rogue", "fte"},
{"Nexuiz", "nexuiz", "-nexuiz", "data/cvars.txt", "id1", "qw", "data", "fte"},
//supported commercial mods (some are currently only partially supported)
{"FTE-Hexen2", "hexen", "-hexen2", "data1/pak0.pak", "data1", "fte"},
{"FTE-Quake2", "q2", "-q2", "baseq2/pak0.pak", "baseq2", "fte"},
{"FTE-Quake3", "q3", "-q3", "baseq3/pak0.pk3", "baseq3", "fte"},
{"FTE-JK2", "jk2", "-jk2", "base/assets0.pk3", "base", "fte"},
{NULL}
};
//space-seperate pk3 names followed by space-seperated crcs
//note that we'll need to reorder and filter out files that don't match the crc.
void FS_ForceToPure(char *str, char *crcs, int seed)
{
//pure files are more important than non-pure.
searchpath_t *sp;
searchpath_t *lastpure = NULL;
int crc;
if (!str)
{ //pure isn't in use.
com_purepaths = NULL;
FS_FlushFSHash();
return;
}
for (sp = com_searchpaths; sp; sp = sp->next)
{
if (sp->funcs->GeneratePureCRC)
{
sp->nextpure = (void*)0x1;
sp->crc_check = sp->funcs->GeneratePureCRC(sp->handle, seed, 0);
sp->crc_reply = sp->funcs->GeneratePureCRC(sp->handle, seed, 1);
}
else
{
sp->crc_check = 0;
sp->crc_reply = 0;
}
}
while(crcs)
{
crcs = COM_Parse(crcs);
crc = atoi(com_token);
if (!crc)
continue;
for (sp = com_searchpaths; sp; sp = sp->next)
{
if (sp->nextpure == (void*)0x1) //don't add twice.
if (sp->crc_check == crc)
{
if (lastpure)
lastpure->nextpure = sp;
else
com_purepaths = sp;
sp->nextpure = NULL;
lastpure = sp;
break;
}
}
if (!sp)
Con_Printf("Pure crc %i wasn't found\n", crc);
}
/* don't add any extras.
for (sp = com_searchpaths; sp; sp = sp->next)
{
if (sp->nextpure == (void*)0x1)
{
if (lastpure)
lastpure->nextpure = sp;
sp->nextpure = NULL;
lastpure = sp;
}
}
*/
FS_FlushFSHash();
}
char *FS_GenerateClientPacksList(char *buffer, int maxlen, int basechecksum)
{
flocation_t loc;
int numpaks = 0;
searchpath_t *sp;
FS_FLocateFile("vm/cgame.qvm", FSLFRT_LENGTH, &loc);
Q_strncatz(buffer, va("%i ", loc.search->crc_reply), maxlen);
basechecksum ^= loc.search->crc_reply;
FS_FLocateFile("vm/ui.qvm", FSLFRT_LENGTH, &loc);
Q_strncatz(buffer, va("%i ", loc.search->crc_reply), maxlen);
basechecksum ^= loc.search->crc_reply;
Q_strncatz(buffer, "@ ", maxlen);
for (sp = com_purepaths; sp; sp = sp->nextpure)
{
if (sp->crc_reply)
{
Q_strncatz(buffer, va("%i ", sp->crc_reply), maxlen);
basechecksum ^= sp->crc_reply;
numpaks++;
}
}
basechecksum ^= numpaks;
Q_strncatz(buffer, va("%i ", basechecksum), maxlen);
return buffer;
}
/*
================
FS_ReloadPackFiles
================
Called when the client has downloaded a new pak/pk3 file
*/
void FS_ReloadPackFilesFlags(unsigned int reloadflags)
{
searchpath_t *oldpaths;
searchpath_t *oldbase;
searchpath_t *next;
//a lame way to fix pure paks
#ifndef SERVERONLY
if (cls.state)
{
CL_Disconnect_f();
CL_Reconnect_f();
}
#endif
FS_FlushFSHash();
oldpaths = com_searchpaths;
com_searchpaths = NULL;
com_purepaths = NULL;
oldbase = com_base_searchpaths;
com_base_searchpaths = NULL;
//invert the order
next = NULL;
while(oldpaths)
{
oldpaths->nextpure = next;
next = oldpaths;
oldpaths = oldpaths->next;
}
oldpaths = next;
com_base_searchpaths = NULL;
while(oldpaths)
{
next = oldpaths->nextpure;
if (oldbase == oldpaths)
com_base_searchpaths = com_searchpaths;
if (oldpaths->funcs == &osfilefuncs)
COM_AddGameDirectory(oldpaths->handle, reloadflags);
oldpaths->funcs->ClosePath(oldpaths->handle);
Z_Free(oldpaths);
oldpaths = next;
}
if (!com_base_searchpaths)
com_base_searchpaths = com_searchpaths;
}
void FS_ReloadPackFiles(void)
{
FS_ReloadPackFilesFlags((unsigned int)-1);
}
void FS_ReloadPackFiles_f(void)
{
if (atoi(Cmd_Argv(1)))
FS_ReloadPackFilesFlags(atoi(Cmd_Argv(1)));
else
FS_ReloadPackFilesFlags((unsigned int)-1);
}
/*
================
COM_InitFilesystem
================
*/
void COM_InitFilesystem (void)
{
FILE *f;
int i;
char *ev;
int gamenum=-1;
Cmd_AddCommand("fs_restart", FS_ReloadPackFiles_f);
//
// -basedir <path>
// Overrides the system supplied base directory (under id1)
//
i = COM_CheckParm ("-basedir");
if (i && i < com_argc-1)
strcpy (com_quakedir, com_argv[i+1]);
else
strcpy (com_quakedir, host_parms.basedir);
Cvar_Register(&com_gamename, "evil hacks");
//identify the game from a telling file
for (i = 0; gamemode_info[i].gamename; i++)
{
if (!gamemode_info[i].auniquefile)
continue; //no more
f = fopen(va("%s/%s", com_quakedir, gamemode_info[i].auniquefile), "rb");
if (f)
{
fclose(f);
gamenum = i;
break;
}
}
//use the game based on an exe name over the filesystem one.
for (i = 0; gamemode_info[i].gamename; i++)
{
if (strstr(com_argv[0], gamemode_info[i].exename))
gamenum = i;
}
//use the game based on an parameter over all else.
for (i = 0; gamemode_info[i].gamename; i++)
{
if (COM_CheckParm(gamemode_info[i].argname))
gamenum = i;
}
if (gamenum<0)
{
for (i = 0; gamemode_info[i].gamename; i++)
{
if (!strcmp(gamemode_info[i].argname, "-quake"))
gamenum = i;
}
}
Cvar_Set(&com_gamename, gamemode_info[gamenum].gamename);
#ifdef _WIN32
{ //win32 sucks.
ev = getenv("HOMEDRIVE");
if (ev)
strcpy(com_homedir, ev);
else
strcpy(com_homedir, "");
ev = getenv("HOMEPATH");
if (ev)
strcat(com_homedir, ev);
else
strcat(com_homedir, "/");
}
#else
//yay for unix!.
ev = getenv("HOME");
if (ev)
Q_strncpyz(com_homedir, ev, sizeof(com_homedir));
else
*com_homedir = '\0';
#endif
if (!COM_CheckParm("-usehome"))
*com_homedir = '\0';
if (COM_CheckParm("-nohome"))
*com_homedir = '\0';
if (*com_homedir)
{
Con_Printf("Using home directory \"%s\"\n", com_homedir);
strcat(com_homedir, "/.fte");
}
//
// start up with id1 by default
//
i = COM_CheckParm ("-basegame");
if (i && i < com_argc-1)
{
do //use multiple -basegames
{
COM_AddGameDirectory (va("%s/%s", com_quakedir, com_argv[i+1]), (unsigned int)-1);
i = COM_CheckNextParm ("-basegame", i);
}
while (i && i < com_argc-1);
}
else
{
if (gamemode_info[gamenum].dir1)
COM_AddGameDirectory (va("%s/%s", com_quakedir, gamemode_info[gamenum].dir1), (unsigned int)-1);
if (gamemode_info[gamenum].dir2)
COM_AddGameDirectory (va("%s/%s", com_quakedir, gamemode_info[gamenum].dir2), (unsigned int)-1);
if (gamemode_info[gamenum].dir3)
COM_AddGameDirectory (va("%s/%s", com_quakedir, gamemode_info[gamenum].dir3), (unsigned int)-1);
if (gamemode_info[gamenum].dir4)
COM_AddGameDirectory (va("%s/%s", com_quakedir, gamemode_info[gamenum].dir4), (unsigned int)-1);
}
if (*com_homedir)
COM_AddGameDirectory (va("%s/fte", com_homedir), (unsigned int)-1);
// any set gamedirs will be freed up to here
com_base_searchpaths = com_searchpaths;
i = COM_CheckParm ("-game"); //effectivly replace with +gamedir x (But overridable)
if (i && i < com_argc-1)
{
COM_AddGameDirectory (va("%s/%s", com_quakedir, com_argv[i+1]), (unsigned int)-1);
#ifndef CLIENTONLY
Info_SetValueForStarKey (svs.info, "*gamedir", com_argv[i+1], MAX_SERVERINFO_STRING);
#endif
}
}