#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(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 ""; }