2013-04-19 02:52:48 +00:00
|
|
|
|
|
|
|
#include "../game/q_shared.h"
|
|
|
|
#include "qcommon.h"
|
|
|
|
#include "files.h"
|
|
|
|
#include "../win32/win_file.h"
|
|
|
|
#include "../zlib/zlib.h"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static cvar_t *fs_openorder;
|
|
|
|
|
|
|
|
|
|
|
|
// Zlib Tech Ref says decompression should use about 44kb. I'll
|
|
|
|
// go with 64kb as a safety factor...
|
|
|
|
#define ZI_STACKSIZE (64*1024)
|
|
|
|
|
|
|
|
static char* zi_stackTop = NULL;
|
|
|
|
static char* zi_stackBase = NULL;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//GOB stuff
|
|
|
|
//===========================================================================
|
|
|
|
|
|
|
|
struct gi_handleTable
|
|
|
|
{
|
|
|
|
wfhandle_t file;
|
|
|
|
bool used;
|
|
|
|
};
|
|
|
|
|
|
|
|
static gi_handleTable *gi_handles = NULL;
|
|
|
|
static int gi_cacheHandle = 0;
|
|
|
|
|
|
|
|
static GOBFSHandle gi_open(GOBChar* name, GOBAccessType type)
|
|
|
|
{
|
|
|
|
if (type != GOBACCESS_READ) return (GOBFSHandle)0xFFFFFFFF;
|
|
|
|
|
|
|
|
int f;
|
|
|
|
for (f = 0; f < MAX_FILE_HANDLES; ++f)
|
|
|
|
{
|
|
|
|
if (!gi_handles[f].used) break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (f == MAX_FILE_HANDLES) return (GOBFSHandle)0xFFFFFFFF;
|
|
|
|
|
|
|
|
gi_handles[f].file = WF_Open(name, true, strstr(name, "assets.gob") ? true : false);
|
|
|
|
if (gi_handles[f].file < 0) return (GOBFSHandle)0xFFFFFFFF;
|
|
|
|
gi_handles[f].used = true;
|
|
|
|
|
|
|
|
return (GOBFSHandle)f;
|
|
|
|
}
|
|
|
|
|
|
|
|
static GOBBool gi_close(GOBFSHandle* handle)
|
|
|
|
{
|
|
|
|
WF_Close(gi_handles[(int)*handle].file);
|
|
|
|
gi_handles[(int)*handle].used = false;
|
|
|
|
return GOB_TRUE;
|
|
|
|
}
|
|
|
|
|
|
|
|
static GOBInt32 gi_read(GOBFSHandle handle, GOBVoid* buffer, GOBInt32 size)
|
|
|
|
{
|
|
|
|
return WF_Read(buffer, size, gi_handles[(int)handle].file);
|
|
|
|
}
|
|
|
|
|
|
|
|
static GOBInt32 gi_seek(GOBFSHandle handle, GOBInt32 offset, GOBSeekType type)
|
|
|
|
{
|
|
|
|
int _type;
|
|
|
|
switch (type) {
|
|
|
|
case GOBSEEK_START: _type = SEEK_SET; break;
|
|
|
|
case GOBSEEK_CURRENT: _type = SEEK_CUR; break;
|
|
|
|
case GOBSEEK_END: _type = SEEK_END; break;
|
|
|
|
default: assert(0); _type = SEEK_SET; break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return WF_Seek(offset, _type, gi_handles[(int)handle].file);
|
|
|
|
}
|
|
|
|
|
|
|
|
static GOBVoid* gi_alloc(GOBUInt32 size)
|
|
|
|
{
|
|
|
|
return Z_Malloc(size, TAG_FILESYS, qfalse, 32);
|
|
|
|
}
|
|
|
|
|
|
|
|
static GOBVoid gi_free(GOBVoid* ptr)
|
|
|
|
{
|
|
|
|
Z_Free(ptr);
|
|
|
|
}
|
|
|
|
|
|
|
|
static GOBBool cache_open(GOBUInt32 size)
|
|
|
|
{
|
|
|
|
for (gi_cacheHandle = 0; gi_cacheHandle < MAX_FILE_HANDLES; ++gi_cacheHandle)
|
|
|
|
{
|
|
|
|
if (!gi_handles[gi_cacheHandle].used) break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (gi_cacheHandle == MAX_FILE_HANDLES) return GOB_FALSE;
|
|
|
|
|
|
|
|
gi_handles[gi_cacheHandle].file = WF_Open("z:\\jedi.swap", false, true);
|
|
|
|
if (gi_handles[gi_cacheHandle].file < 0) return GOB_FALSE;
|
|
|
|
|
|
|
|
if (!WF_Resize(size, gi_handles[gi_cacheHandle].file))
|
|
|
|
{
|
|
|
|
WF_Close(gi_handles[gi_cacheHandle].file);
|
|
|
|
return GOB_FALSE;
|
|
|
|
}
|
|
|
|
|
|
|
|
gi_handles[gi_cacheHandle].used = true;
|
|
|
|
|
|
|
|
return GOB_TRUE;
|
|
|
|
}
|
|
|
|
|
|
|
|
static GOBBool cache_close(GOBVoid)
|
|
|
|
{
|
|
|
|
WF_Close(gi_handles[gi_cacheHandle].file);
|
|
|
|
gi_handles[gi_cacheHandle].used = false;
|
|
|
|
return GOB_TRUE;
|
|
|
|
}
|
|
|
|
|
|
|
|
static GOBInt32 cache_read(GOBVoid* buffer, GOBInt32 size)
|
|
|
|
{
|
|
|
|
return WF_Read(buffer, size, gi_handles[gi_cacheHandle].file);
|
|
|
|
}
|
|
|
|
|
|
|
|
static GOBInt32 cache_write(GOBVoid* buffer, GOBInt32 size)
|
|
|
|
{
|
|
|
|
return WF_Write(buffer, size, gi_handles[gi_cacheHandle].file);
|
|
|
|
}
|
|
|
|
|
|
|
|
static GOBInt32 cache_seek(GOBInt32 offset)
|
|
|
|
{
|
|
|
|
return WF_Seek(offset, SEEK_SET, gi_handles[gi_cacheHandle].file);
|
|
|
|
}
|
|
|
|
|
|
|
|
static voidpf zi_alloc(voidpf opaque, uInt items, uInt size)
|
|
|
|
{
|
|
|
|
voidpf ret = zi_stackTop;
|
|
|
|
|
|
|
|
zi_stackTop += items * size;
|
|
|
|
assert(zi_stackTop < zi_stackBase + ZI_STACKSIZE);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void zi_free(voidpf opaque, voidpf address)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
static GOBInt32 gi_decompress_zlib(GOBVoid* source, GOBUInt32 sourceLen,
|
|
|
|
GOBVoid* dest, GOBUInt32* destLen)
|
|
|
|
{
|
|
|
|
// Copied and modified version of zlib's uncompress()...
|
|
|
|
|
|
|
|
z_stream stream;
|
|
|
|
int err;
|
|
|
|
|
|
|
|
stream.next_in = (Bytef*)source;
|
|
|
|
stream.avail_in = (uInt)sourceLen;
|
|
|
|
|
|
|
|
stream.next_out = (Bytef*)dest;
|
|
|
|
stream.avail_out = (uInt)*destLen;
|
|
|
|
if ((uLong)stream.avail_out != *destLen) return Z_BUF_ERROR;
|
|
|
|
|
|
|
|
stream.zalloc = zi_alloc;
|
|
|
|
stream.zfree = zi_free;
|
|
|
|
zi_stackTop = zi_stackBase;
|
|
|
|
|
|
|
|
err = inflateInit(&stream);
|
|
|
|
if (err != Z_OK) return err;
|
|
|
|
|
|
|
|
err = inflate(&stream, Z_FINISH);
|
|
|
|
if (err != Z_STREAM_END) {
|
|
|
|
inflateEnd(&stream);
|
|
|
|
return err == Z_OK ? Z_BUF_ERROR : err;
|
|
|
|
}
|
|
|
|
*destLen = stream.total_out;
|
|
|
|
|
|
|
|
err = inflateEnd(&stream);
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
|
|
|
GOBInt32 gi_decompress_null(GOBVoid* source, GOBUInt32 sourceLen,
|
|
|
|
GOBVoid* dest, GOBUInt32* destLen)
|
|
|
|
{
|
|
|
|
if (sourceLen > *destLen) return -1;
|
|
|
|
*destLen = sourceLen;
|
|
|
|
|
|
|
|
memcpy(dest, source, sourceLen);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
#ifdef GOB_PROFILE
|
|
|
|
static GOBVoid gi_profileread(GOBUInt32 code)
|
|
|
|
{
|
|
|
|
code = LittleLong(code);
|
|
|
|
Sys_Log("gob-prof.dat", &code, sizeof(code), true);
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static void FS_CheckUsed(fileHandle_t f)
|
|
|
|
{
|
|
|
|
if (!fsh[f].used)
|
|
|
|
{
|
|
|
|
Com_Error( ERR_FATAL, "Filesystem call attempting to use invalid handle\n" );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int FS_filelength( fileHandle_t f )
|
|
|
|
{
|
|
|
|
FS_CheckInit();
|
|
|
|
FS_CheckUsed(f);
|
|
|
|
|
|
|
|
if (fsh[f].gob)
|
|
|
|
{
|
|
|
|
GOBUInt32 cur, end, crap;
|
|
|
|
GOBSeek(fsh[f].ghandle, 0, GOBSEEK_CURRENT, &cur);
|
|
|
|
GOBSeek(fsh[f].ghandle, 0, GOBSEEK_END, &end);
|
|
|
|
GOBSeek(fsh[f].ghandle, cur, GOBSEEK_START, &crap);
|
|
|
|
|
|
|
|
return end;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
int pos = WF_Tell(fsh[f].whandle);
|
|
|
|
WF_Seek(0, SEEK_END, fsh[f].whandle);
|
|
|
|
int end = WF_Tell(fsh[f].whandle);
|
|
|
|
WF_Seek(pos, SEEK_SET, fsh[f].whandle);
|
|
|
|
|
|
|
|
return end;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void FS_FCloseFile( fileHandle_t f )
|
|
|
|
{
|
|
|
|
FS_CheckInit();
|
|
|
|
FS_CheckUsed(f);
|
|
|
|
|
|
|
|
if (fsh[f].gob)
|
|
|
|
GOBClose(fsh[f].ghandle);
|
|
|
|
else
|
|
|
|
WF_Close(fsh[f].whandle);
|
|
|
|
|
|
|
|
fsh[f].used = qfalse;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fileHandle_t FS_FOpenFileWrite( const char *filename )
|
|
|
|
{
|
|
|
|
FS_CheckInit();
|
|
|
|
|
|
|
|
fileHandle_t f = FS_HandleForFile();
|
|
|
|
|
|
|
|
char* osname = FS_BuildOSPath( filename );
|
|
|
|
fsh[f].whandle = WF_Open(osname, false, false);
|
|
|
|
if (fsh[f].whandle >= 0)
|
|
|
|
{
|
|
|
|
fsh[f].used = qtrue;
|
|
|
|
fsh[f].gob = qfalse;
|
|
|
|
return f;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
===========
|
|
|
|
FS_FOpenFileRead
|
|
|
|
|
|
|
|
Finds the file in the search path.
|
|
|
|
Returns filesize and an open FILE pointer.
|
|
|
|
Used for streaming data out of either a
|
|
|
|
separate file or a ZIP file.
|
|
|
|
===========
|
|
|
|
*/
|
|
|
|
|
|
|
|
static int FS_FOpenFileReadOS( const char *filename, fileHandle_t f )
|
|
|
|
{
|
|
|
|
if (Sys_GetFileCode(filename) != -1)
|
|
|
|
{
|
|
|
|
char* osname = FS_BuildOSPath( filename );
|
|
|
|
fsh[f].whandle = WF_Open(osname, true, false);
|
|
|
|
if (fsh[f].whandle >= 0)
|
|
|
|
{
|
|
|
|
fsh[f].used = qtrue;
|
|
|
|
fsh[f].gob = qfalse;
|
|
|
|
return FS_filelength(f);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
===================
|
|
|
|
FS_BuildGOBPath
|
|
|
|
|
|
|
|
Qpath may have either forward or backwards slashes
|
|
|
|
===================
|
|
|
|
*/
|
|
|
|
static char *FS_BuildGOBPath(const char *qpath )
|
|
|
|
{
|
|
|
|
static char path[2][MAX_OSPATH];
|
|
|
|
static int toggle;
|
|
|
|
|
|
|
|
toggle ^= 1; // flip-flop to allow two returns without clash
|
|
|
|
|
|
|
|
if (qpath[0] == '\\' || qpath[0] == '/')
|
|
|
|
{
|
|
|
|
Com_sprintf( path[toggle], sizeof( path[0] ), ".%s", qpath );
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
Com_sprintf( path[toggle], sizeof( path[0] ), ".\\%s", qpath );
|
|
|
|
}
|
|
|
|
|
|
|
|
// FS_ReplaceSeparators( path[toggle], '\\' );
|
|
|
|
FS_ReplaceSeparators( path[toggle] );
|
|
|
|
|
|
|
|
return path[toggle];
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static int FS_FOpenFileReadGOB( const char *filename, fileHandle_t f )
|
|
|
|
{
|
|
|
|
char* gobname = FS_BuildGOBPath( filename );
|
|
|
|
if (GOBOpen(gobname, &fsh[f].ghandle) == GOBERR_OK)
|
|
|
|
{
|
|
|
|
fsh[f].used = qtrue;
|
|
|
|
fsh[f].gob = qtrue;
|
|
|
|
return FS_filelength(f);
|
|
|
|
}
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
===========
|
|
|
|
FS_FOpenFileRead
|
|
|
|
|
|
|
|
Finds the file in the search path.
|
|
|
|
Returns filesize and an open FILE pointer.
|
|
|
|
Used for streaming data out of either a
|
|
|
|
separate file or a ZIP file.
|
|
|
|
===========
|
|
|
|
*/
|
|
|
|
int FS_FOpenFileRead( const char *filename, fileHandle_t *file, qboolean uniqueFILE )
|
|
|
|
{
|
|
|
|
FS_CheckInit();
|
|
|
|
|
|
|
|
if ( file == NULL ) {
|
|
|
|
Com_Error( ERR_FATAL, "FS_FOpenFileRead: NULL 'file' parameter passed\n" );
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( !filename ) {
|
|
|
|
Com_Error( ERR_FATAL, "FS_FOpenFileRead: NULL 'filename' parameter passed\n" );
|
|
|
|
}
|
|
|
|
|
|
|
|
*file = FS_HandleForFile();
|
|
|
|
|
|
|
|
int len;
|
|
|
|
|
|
|
|
if (fs_openorder->integer == 0)
|
|
|
|
{
|
|
|
|
// Release mode -- read from GOB first
|
|
|
|
len = FS_FOpenFileReadGOB(filename, *file);
|
|
|
|
if (len < 0) len = FS_FOpenFileReadOS(filename, *file);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// Debug mode -- external files override GOB
|
|
|
|
len = FS_FOpenFileReadOS(filename, *file);
|
|
|
|
if (len < 0) len = FS_FOpenFileReadGOB(filename, *file);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (len >= 0) return len;
|
|
|
|
|
|
|
|
Com_DPrintf ("Can't find %s\n", filename);
|
|
|
|
|
|
|
|
*file = 0;
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
=================
|
|
|
|
FS_Read
|
|
|
|
|
|
|
|
Properly handles partial reads
|
|
|
|
=================
|
|
|
|
*/
|
|
|
|
int FS_Read( void *buffer, int len, fileHandle_t f )
|
|
|
|
{
|
|
|
|
FS_CheckInit();
|
|
|
|
FS_CheckUsed(f);
|
|
|
|
|
|
|
|
if ( !f )
|
|
|
|
{
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( f <= 0 || f >= MAX_FILE_HANDLES )
|
|
|
|
{
|
|
|
|
Com_Error( ERR_FATAL, "FS_Read: Invalid handle %d\n", f );
|
|
|
|
}
|
|
|
|
|
|
|
|
if (fsh[f].gob)
|
|
|
|
{
|
|
|
|
GOBUInt32 size = GOBRead(buffer, len, fsh[f].ghandle);
|
|
|
|
if (size == GOB_INVALID_SIZE)
|
|
|
|
{
|
|
|
|
#if defined(FINAL_BUILD)
|
|
|
|
extern void ERR_DiscFail(bool);
|
|
|
|
ERR_DiscFail(false);
|
|
|
|
#else
|
|
|
|
Com_Error( ERR_FATAL, "Failed to read from GOB" );
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
return size;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
return WF_Read(buffer, len, fsh[f].whandle);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
MP has FS_Read2 which is supposed to do some extra logic.
|
|
|
|
We don't care, and just call FS_Read()
|
|
|
|
*/
|
|
|
|
int FS_Read2( void *buffer, int len, fileHandle_t f )
|
|
|
|
{
|
|
|
|
return FS_Read(buffer, len, f);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
=================
|
|
|
|
FS_Write
|
|
|
|
=================
|
|
|
|
*/
|
|
|
|
int FS_Write( const void *buffer, int len, fileHandle_t f )
|
|
|
|
{
|
|
|
|
FS_CheckInit();
|
|
|
|
FS_CheckUsed(f);
|
|
|
|
|
|
|
|
if ( !f )
|
|
|
|
{
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( f <= 0 || f >= MAX_FILE_HANDLES )
|
|
|
|
{
|
|
|
|
Com_Error( ERR_FATAL, "FS_Read: Invalid handle %d\n", f );
|
|
|
|
}
|
|
|
|
|
|
|
|
if (fsh[f].gob)
|
|
|
|
{
|
|
|
|
Com_Error( ERR_FATAL, "FS_Write: Cannot write to GOB files %d\n", f );
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
return WF_Write(buffer, len, fsh[f].whandle);
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
=================
|
|
|
|
FS_Seek
|
|
|
|
|
|
|
|
=================
|
|
|
|
*/
|
|
|
|
int FS_Seek( fileHandle_t f, long offset, int origin )
|
|
|
|
{
|
|
|
|
FS_CheckInit();
|
|
|
|
FS_CheckUsed(f);
|
|
|
|
|
|
|
|
if (fsh[f].gob)
|
|
|
|
{
|
|
|
|
int _origin;
|
|
|
|
switch( origin ) {
|
|
|
|
case FS_SEEK_CUR: _origin = GOBSEEK_CURRENT; break;
|
|
|
|
case FS_SEEK_END: _origin = GOBSEEK_END; break;
|
|
|
|
case FS_SEEK_SET: _origin = GOBSEEK_START; break;
|
|
|
|
default:
|
|
|
|
_origin = GOBSEEK_CURRENT;
|
|
|
|
Com_Error( ERR_FATAL, "Bad origin in FS_Seek\n" );
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
GOBUInt32 pos;
|
|
|
|
GOBSeek(fsh[f].ghandle, offset, _origin, &pos);
|
|
|
|
return pos;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
int _origin;
|
|
|
|
switch( origin ) {
|
|
|
|
case FS_SEEK_CUR: _origin = SEEK_CUR; break;
|
|
|
|
case FS_SEEK_END: _origin = SEEK_END; break;
|
|
|
|
case FS_SEEK_SET: _origin = SEEK_SET; break;
|
|
|
|
default:
|
|
|
|
_origin = SEEK_CUR;
|
|
|
|
Com_Error( ERR_FATAL, "Bad origin in FS_Seek\n" );
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return WF_Seek(offset, _origin, fsh[f].whandle);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
=================
|
|
|
|
FS_Access
|
|
|
|
=================
|
|
|
|
*/
|
|
|
|
qboolean FS_Access( const char *filename )
|
|
|
|
{
|
|
|
|
GOBBool status;
|
|
|
|
|
|
|
|
FS_CheckInit();
|
|
|
|
|
|
|
|
char* gobname = FS_BuildGOBPath( filename );
|
|
|
|
if (GOBAccess(gobname, &status) != GOBERR_OK || status != GOB_TRUE)
|
|
|
|
{
|
|
|
|
return Sys_GetFileCode( filename ) != -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return qtrue;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
======================================================================================
|
|
|
|
|
|
|
|
CONVENIENCE FUNCTIONS FOR ENTIRE FILES
|
|
|
|
|
|
|
|
======================================================================================
|
|
|
|
*/
|
|
|
|
|
|
|
|
#ifdef _JK2MP
|
|
|
|
int FS_FileIsInPAK(const char *filename, int *pChecksum)
|
|
|
|
#else
|
|
|
|
int FS_FileIsInPAK(const char *filename)
|
|
|
|
#endif
|
|
|
|
{
|
|
|
|
FS_CheckInit();
|
|
|
|
|
|
|
|
if ( !filename ) {
|
|
|
|
Com_Error( ERR_FATAL, "FS_FOpenFileRead: NULL 'filename' parameter passed\n" );
|
|
|
|
}
|
|
|
|
|
|
|
|
GOBBool exists;
|
|
|
|
GOBAccess(const_cast<GOBChar*>(filename), &exists);
|
|
|
|
|
|
|
|
#ifdef _JK2MP
|
|
|
|
*pChecksum = 0;
|
|
|
|
#endif
|
|
|
|
|
|
|
|
return exists ? 1 : -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
============
|
|
|
|
FS_ReadFile
|
|
|
|
|
|
|
|
Filename are relative to the quake search path
|
|
|
|
a null buffer will just return the file length without loading
|
|
|
|
============
|
|
|
|
*/
|
|
|
|
int FS_ReadFile( const char *qpath, void **buffer )
|
|
|
|
{
|
|
|
|
FS_CheckInit();
|
|
|
|
|
|
|
|
if ( !qpath || !qpath[0] ) {
|
|
|
|
Com_Error( ERR_FATAL, "FS_ReadFile with empty name\n" );
|
|
|
|
}
|
|
|
|
|
|
|
|
// stop sounds from repeating
|
|
|
|
S_ClearSoundBuffer();
|
|
|
|
|
|
|
|
fileHandle_t h;
|
|
|
|
int len = FS_FOpenFileRead( qpath, &h, qfalse );
|
|
|
|
if ( h == 0 )
|
|
|
|
{
|
|
|
|
if ( buffer ) *buffer = NULL;
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( !buffer )
|
|
|
|
{
|
|
|
|
FS_FCloseFile(h);
|
|
|
|
return len;
|
|
|
|
}
|
|
|
|
|
|
|
|
// assume temporary....
|
|
|
|
byte* buf = (byte*)Z_Malloc( len+1, TAG_TEMP_WORKSPACE, qfalse, 32);
|
|
|
|
buf[len]='\0';
|
|
|
|
|
|
|
|
// Z_Label(buf, qpath);
|
|
|
|
|
|
|
|
FS_Read(buf, len, h);
|
|
|
|
|
|
|
|
// guarantee that it will have a trailing 0 for string operations
|
|
|
|
buf[len] = 0;
|
|
|
|
FS_FCloseFile( h );
|
|
|
|
|
|
|
|
*buffer = buf;
|
|
|
|
return len;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
=============
|
|
|
|
FS_FreeFile
|
|
|
|
=============
|
|
|
|
*/
|
|
|
|
void FS_FreeFile( void *buffer )
|
|
|
|
{
|
|
|
|
FS_CheckInit();
|
|
|
|
|
|
|
|
if ( !buffer ) {
|
|
|
|
Com_Error( ERR_FATAL, "FS_FreeFile( NULL )" );
|
|
|
|
}
|
|
|
|
|
|
|
|
Z_Free( buffer );
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int FS_FOpenFileByMode( const char *qpath, fileHandle_t *f, fsMode_t mode )
|
|
|
|
{
|
|
|
|
FS_CheckInit();
|
|
|
|
|
|
|
|
if (mode != FS_READ)
|
|
|
|
{
|
|
|
|
Com_Error( ERR_FATAL, "FSH_FOpenFile: bad mode" );
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return FS_FOpenFileRead( qpath, f, qtrue );
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int FS_FTell( fileHandle_t f )
|
|
|
|
{
|
|
|
|
FS_CheckInit();
|
|
|
|
FS_CheckUsed(f);
|
|
|
|
|
|
|
|
if (fsh[f].gob)
|
|
|
|
{
|
|
|
|
GOBUInt32 pos;
|
|
|
|
GOBSeek(fsh[f].ghandle, 0, GOBSEEK_CURRENT, &pos);
|
|
|
|
return pos;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
return WF_Tell(fsh[f].whandle);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
================
|
|
|
|
FS_Startup
|
|
|
|
================
|
|
|
|
*/
|
|
|
|
void FS_Startup( const char *gameName )
|
|
|
|
{
|
|
|
|
Com_Printf( "----- FS_Startup -----\n" );
|
|
|
|
|
|
|
|
fs_openorder = Cvar_Get( "fs_openorder", "0", 0 );
|
|
|
|
fs_debug = Cvar_Get( "fs_debug", "0", 0 );
|
|
|
|
fs_copyfiles = Cvar_Get( "fs_copyfiles", "0", CVAR_INIT );
|
|
|
|
fs_cdpath = Cvar_Get ("fs_cdpath", Sys_DefaultCDPath(), CVAR_INIT );
|
|
|
|
fs_basepath = Cvar_Get ("fs_basepath", Sys_DefaultBasePath(), CVAR_INIT );
|
|
|
|
fs_gamedirvar = Cvar_Get ("fs_game", "base", CVAR_INIT|CVAR_SERVERINFO );
|
|
|
|
fs_restrict = Cvar_Get ("fs_restrict", "", CVAR_INIT );
|
|
|
|
|
|
|
|
gi_handles = new gi_handleTable[MAX_FILE_HANDLES];
|
|
|
|
for (int f = 0; f < MAX_FILE_HANDLES; ++f)
|
|
|
|
{
|
|
|
|
fsh[f].used = false;
|
|
|
|
gi_handles[f].used = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
zi_stackBase = (char*)Z_Malloc(ZI_STACKSIZE, TAG_FILESYS, qfalse);
|
|
|
|
|
|
|
|
GOBMemoryFuncSet mem;
|
|
|
|
mem.alloc = gi_alloc;
|
|
|
|
mem.free = gi_free;
|
|
|
|
|
|
|
|
GOBFileSysFuncSet file;
|
|
|
|
file.close = gi_close;
|
|
|
|
file.open = gi_open;
|
|
|
|
file.read = gi_read;
|
|
|
|
file.seek = gi_seek;
|
|
|
|
file.write = NULL;
|
|
|
|
|
|
|
|
GOBCacheFileFuncSet cache;
|
|
|
|
cache.close = cache_close;
|
|
|
|
cache.open = cache_open;
|
|
|
|
cache.read = cache_read;
|
|
|
|
cache.seek = cache_seek;
|
|
|
|
cache.write = cache_write;
|
|
|
|
|
|
|
|
GOBCodecFuncSet codec = {
|
|
|
|
2, // codecs
|
|
|
|
{
|
|
|
|
{ // Codec 0 - zlib
|
|
|
|
'z', GOB_INFINITE_RATIO, // tag, ratio (ratio is meaningless for decomp)
|
|
|
|
NULL,
|
|
|
|
gi_decompress_zlib,
|
|
|
|
},
|
|
|
|
{ // Codec 1 - null
|
|
|
|
'0', GOB_INFINITE_RATIO, // tag, ratio (ratio is meaningless for decomp)
|
|
|
|
NULL,
|
|
|
|
gi_decompress_null,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
if (
|
|
|
|
#ifdef _XBOX
|
|
|
|
GOBInit(&mem, &file, &codec, &cache)
|
|
|
|
#else
|
|
|
|
GOBInit(&mem, &file, &codec, NULL)
|
|
|
|
#endif
|
|
|
|
!= GOBERR_OK)
|
|
|
|
{
|
|
|
|
Com_Error( ERR_FATAL, "Could not initialize GOB" );
|
|
|
|
}
|
|
|
|
|
|
|
|
char* archive = FS_BuildOSPath( "assets" );
|
|
|
|
if (GOBArchiveOpen(archive, GOBACCESS_READ, GOB_FALSE, GOB_TRUE) != GOBERR_OK)
|
|
|
|
{
|
|
|
|
#if defined(FINAL_BUILD)
|
|
|
|
/*
|
|
|
|
extern void ERR_DiscFail(bool);
|
|
|
|
ERR_DiscFail(false);
|
|
|
|
*/
|
|
|
|
#else
|
|
|
|
//Com_Error( ERR_FATAL, "Could not initialize GOB" );
|
|
|
|
Cvar_Set("fs_openorder", "1");
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
GOBSetCacheSize(1);
|
|
|
|
GOBSetReadBufferSize(32 * 1024);
|
|
|
|
|
|
|
|
#ifdef GOB_PROFILE
|
|
|
|
GOBProfileFuncSet profile = {
|
|
|
|
gi_profileread
|
|
|
|
};
|
|
|
|
GOBSetProfileFuncs(&profile);
|
|
|
|
GOBStartProfile();
|
|
|
|
#endif
|
|
|
|
|
|
|
|
Com_Printf( "----------------------\n" );
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
============================
|
|
|
|
|
|
|
|
DIRECTORY SCANNING FUCNTIONS
|
|
|
|
|
|
|
|
============================
|
|
|
|
*/
|
|
|
|
|
|
|
|
#define MAX_FOUND_FILES 0x1000
|
|
|
|
|
|
|
|
/*
|
|
|
|
==================
|
|
|
|
FS_AddFileToList
|
|
|
|
==================
|
|
|
|
*/
|
|
|
|
static int FS_AddFileToList( char *name, char *list[MAX_FOUND_FILES], int nfiles ) {
|
|
|
|
int i;
|
|
|
|
|
|
|
|
if ( nfiles == MAX_FOUND_FILES - 1 ) {
|
|
|
|
return nfiles;
|
|
|
|
}
|
|
|
|
for ( i = 0 ; i < nfiles ; i++ ) {
|
|
|
|
if ( !stricmp( name, list[i] ) ) {
|
|
|
|
return nfiles; // allready in list
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// list[nfiles] = CopyString( name );
|
|
|
|
list[nfiles] = (char *) Z_Malloc( strlen(name) + 1, TAG_LISTFILES, qfalse );
|
|
|
|
strcpy(list[nfiles], name);
|
|
|
|
nfiles++;
|
|
|
|
|
|
|
|
return nfiles;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
===============
|
|
|
|
FS_ListFiles
|
|
|
|
|
|
|
|
Returns a uniqued list of files that match the given criteria
|
|
|
|
from all search paths
|
|
|
|
===============
|
|
|
|
*/
|
|
|
|
char **FS_ListFiles( const char *path, const char *extension, int *numfiles )
|
|
|
|
{
|
|
|
|
char *netpath;
|
|
|
|
int numSysFiles;
|
|
|
|
char **sysFiles;
|
|
|
|
char *name;
|
|
|
|
int nfiles = 0;
|
|
|
|
char **listCopy;
|
|
|
|
char *list[MAX_FOUND_FILES];
|
|
|
|
int i;
|
|
|
|
|
|
|
|
FS_CheckInit();
|
|
|
|
|
|
|
|
if ( !path ) {
|
|
|
|
*numfiles = 0;
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
// We don't do any fancy searchpath magic here, it's all in the meta-file
|
|
|
|
// that Sys_ListFiles will return
|
|
|
|
netpath = FS_BuildOSPath( path );
|
|
|
|
#ifdef _JK2MP
|
|
|
|
sysFiles = Sys_ListFiles( netpath, extension, NULL, &numSysFiles, qfalse );
|
|
|
|
#else
|
|
|
|
sysFiles = Sys_ListFiles( netpath, extension, &numSysFiles, qfalse );
|
|
|
|
#endif
|
|
|
|
for ( i = 0 ; i < numSysFiles ; i++ ) {
|
|
|
|
// unique the match
|
|
|
|
name = sysFiles[i];
|
|
|
|
nfiles = FS_AddFileToList( name, list, nfiles );
|
|
|
|
}
|
|
|
|
Sys_FreeFileList( sysFiles );
|
|
|
|
|
|
|
|
// return a copy of the list
|
|
|
|
*numfiles = nfiles;
|
|
|
|
|
|
|
|
if ( !nfiles ) {
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
listCopy = (char**)Z_Malloc( ( nfiles + 1 ) * sizeof( *listCopy ), TAG_LISTFILES, qfalse);
|
|
|
|
for ( i = 0 ; i < nfiles ; i++ ) {
|
|
|
|
listCopy[i] = list[i];
|
|
|
|
}
|
|
|
|
listCopy[i] = NULL;
|
|
|
|
|
|
|
|
return listCopy;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
=================
|
|
|
|
FS_FreeFileList
|
|
|
|
=================
|
|
|
|
*/
|
|
|
|
void FS_FreeFileList( char **filelist )
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
|
|
|
|
FS_CheckInit();
|
|
|
|
|
|
|
|
if ( !filelist ) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
for ( i = 0 ; filelist[i] ; i++ ) {
|
|
|
|
Z_Free( filelist[i] );
|
|
|
|
}
|
|
|
|
|
|
|
|
Z_Free( filelist );
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
===============
|
|
|
|
FS_AddFileToListBuf
|
|
|
|
===============
|
|
|
|
*/
|
|
|
|
static int FS_AddFileToListBuf( char *name, char *listbuf, int bufsize, int nfiles )
|
|
|
|
{
|
|
|
|
char *p;
|
|
|
|
|
|
|
|
if ( nfiles == MAX_FOUND_FILES - 1 ) {
|
|
|
|
return nfiles;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (name[0] == '/' || name[0] == '\\') {
|
|
|
|
name++;
|
|
|
|
}
|
|
|
|
|
|
|
|
p = listbuf;
|
|
|
|
while ( *p ) {
|
|
|
|
if ( !stricmp( name, p ) ) {
|
|
|
|
return nfiles; // already in list
|
|
|
|
}
|
|
|
|
p += strlen( p ) + 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( ( p + strlen( name ) + 2 - listbuf ) > bufsize ) {
|
|
|
|
return nfiles; // list is full
|
|
|
|
}
|
|
|
|
|
|
|
|
strcpy( p, name );
|
|
|
|
p += strlen( p ) + 1;
|
|
|
|
*p = 0;
|
|
|
|
|
|
|
|
return nfiles + 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
================
|
|
|
|
FS_GetFileList
|
|
|
|
|
|
|
|
Returns a uniqued list of files that match the given criteria
|
|
|
|
from all search paths
|
|
|
|
================
|
|
|
|
*/
|
|
|
|
int FS_GetFileList( const char *path, const char *extension, char *listbuf, int bufsize )
|
|
|
|
{
|
|
|
|
int nfiles = 0;
|
|
|
|
int i;
|
|
|
|
char *netpath;
|
|
|
|
int numSysFiles;
|
|
|
|
char **sysFiles;
|
|
|
|
char *name;
|
|
|
|
|
|
|
|
FS_CheckInit();
|
|
|
|
|
|
|
|
if ( !path ) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
if ( !extension ) {
|
|
|
|
extension = "";
|
|
|
|
}
|
|
|
|
|
|
|
|
// Prime the file list buffer
|
|
|
|
listbuf[0] = '\0';
|
|
|
|
netpath = FS_BuildOSPath( path );
|
|
|
|
#ifdef _JK2MP
|
|
|
|
sysFiles = Sys_ListFiles( netpath, extension, NULL, &numSysFiles, qfalse );
|
|
|
|
#else
|
|
|
|
sysFiles = Sys_ListFiles( netpath, extension, &numSysFiles, qfalse );
|
|
|
|
#endif
|
|
|
|
for ( i = 0 ; i < numSysFiles ; i++ ) {
|
|
|
|
// unique the match
|
|
|
|
name = sysFiles[i];
|
|
|
|
nfiles = FS_AddFileToListBuf( name, listbuf, bufsize, nfiles );
|
|
|
|
}
|
|
|
|
Sys_FreeFileList( sysFiles );
|
|
|
|
|
|
|
|
return nfiles;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
=================
|
|
|
|
Filesytem STUBS
|
|
|
|
=================
|
|
|
|
*/
|
|
|
|
|
|
|
|
qboolean FS_ConditionalRestart(int checksumFeed)
|
|
|
|
{
|
|
|
|
return qfalse;
|
|
|
|
}
|
|
|
|
|
|
|
|
void FS_ClearPakReferences(int flags)
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const char *FS_LoadedPakNames(void)
|
|
|
|
{
|
|
|
|
return "";
|
|
|
|
}
|
|
|
|
|
|
|
|
const char *FS_ReferencedPakNames(void)
|
|
|
|
{
|
|
|
|
return "";
|
|
|
|
}
|
|
|
|
|
|
|
|
void FS_SetRestrictions(void)
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
#ifdef _JK2MP
|
|
|
|
void FS_Restart(int checksumFeed)
|
|
|
|
#else
|
|
|
|
void FS_Restart(void)
|
|
|
|
#endif
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
qboolean FS_FileExists(const char *file)
|
|
|
|
{
|
|
|
|
assert(!"FS_FileExists not implemented on Xbox");
|
|
|
|
return qfalse;
|
|
|
|
}
|
|
|
|
|
|
|
|
void FS_UpdateGamedir(void)
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
void FS_PureServerSetReferencedPaks(const char *pakSums, const char *pakNames)
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
void FS_PureServerSetLoadedPaks(const char *pakSums, const char *pakNames)
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const char *FS_ReferencedPakChecksums(void)
|
|
|
|
{
|
|
|
|
return "";
|
|
|
|
}
|
|
|
|
|
|
|
|
const char *FS_LoadedPakChecksums(void)
|
|
|
|
{
|
|
|
|
return "";
|
|
|
|
}
|