2109 lines
55 KiB
C
2109 lines
55 KiB
C
|
/*
|
||
|
===========================================================================
|
||
|
Copyright (C) 1999-2005 Id Software, Inc.
|
||
|
Copyright (C) 2007 HermitWorks Entertainment Corporation
|
||
|
|
||
|
This file is part of the Space Trader source code.
|
||
|
|
||
|
The Space Trader source code is free software; you can redistribute it
|
||
|
and/or modify it under the terms of the GNU General Public License as
|
||
|
published by the Free Software Foundation; either version 2 of the License,
|
||
|
or (at your option) any later version.
|
||
|
|
||
|
The Space Trader source code is distributed in the hope that it will be
|
||
|
useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
|
GNU General Public License for more details.
|
||
|
|
||
|
You should have received a copy of the GNU General Public License
|
||
|
along with the Space Trader source code; if not, write to the Free Software
|
||
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||
|
===========================================================================
|
||
|
*/
|
||
|
/*****************************************************************************
|
||
|
* name: files.c
|
||
|
*
|
||
|
* desc: handle based filesystem for Quake III Arena
|
||
|
*
|
||
|
* $Archive: /MissionPack/code/qcommon/files.c $
|
||
|
*
|
||
|
*****************************************************************************/
|
||
|
|
||
|
|
||
|
#include "q_shared.h"
|
||
|
#include "qcommon.h"
|
||
|
#include "unzip.h"
|
||
|
|
||
|
#include "../sql/sql.h"
|
||
|
|
||
|
extern sqlInfo_t com_db;
|
||
|
|
||
|
/*
|
||
|
=============================================================================
|
||
|
|
||
|
QUAKE3 FILESYSTEM
|
||
|
|
||
|
All of Quake's data access is through a hierarchical file system, but the contents of
|
||
|
the file system can be transparently merged from several sources.
|
||
|
|
||
|
A "qpath" is a reference to game file data. MAX_ZPATH is 256 characters, which must include
|
||
|
a terminating zero. "..", "\\", and ":" are explicitly illegal in qpaths to prevent any
|
||
|
references outside the quake directory system.
|
||
|
|
||
|
The "base path" is the path to the directory holding all the game directories and usually
|
||
|
the executable. It defaults to ".", but can be overridden with a "+set fs_basepath c:\quake3"
|
||
|
command line to allow code debugging in a different directory. Basepath cannot
|
||
|
be modified at all after startup. Any files that are created (demos, screenshots,
|
||
|
etc) will be created reletive to the base path, so base path should usually be writable.
|
||
|
|
||
|
The "cd path" is the path to an alternate hierarchy that will be searched if a file
|
||
|
is not located in the base path. A user can do a partial install that copies some
|
||
|
data to a base path created on their hard drive and leave the rest on the cd. Files
|
||
|
are never writen to the cd path. It defaults to a value set by the installer, like
|
||
|
"e:\quake3", but it can be overridden with "+set fs_cdpath g:\quake3".
|
||
|
|
||
|
If a user runs the game directly from a CD, the base path would be on the CD. This
|
||
|
should still function correctly, but all file writes will fail (harmlessly).
|
||
|
|
||
|
The "home path" is the path used for all write access. On win32 systems we have "base path"
|
||
|
== "home path", but on *nix systems the base installation is usually readonly, and
|
||
|
"home path" points to ~/.q3a or similar
|
||
|
|
||
|
The user can also install custom mods and content in "home path", so it should be searched
|
||
|
along with "home path" and "cd path" for game content.
|
||
|
|
||
|
|
||
|
The "base game" is the directory under the paths where data comes from by default, and
|
||
|
can be either "baseq3" or "demoq3".
|
||
|
|
||
|
The "current game" may be the same as the base game, or it may be the name of another
|
||
|
directory under the paths that should be searched for files before looking in the base game.
|
||
|
This is the basis for addons.
|
||
|
|
||
|
Clients automatically set the game directory after receiving a gamestate from a server,
|
||
|
so only servers need to worry about +set fs_game.
|
||
|
|
||
|
No other directories outside of the base game and current game will ever be referenced by
|
||
|
filesystem functions.
|
||
|
|
||
|
To save disk space and speed loading, directory trees can be collapsed into zip files.
|
||
|
The files use a ".pk3" extension to prevent users from unzipping them accidentally, but
|
||
|
otherwise the are simply normal uncompressed zip files. A game directory can have multiple
|
||
|
zip files of the form "pak0.pk3", "pak1.pk3", etc. Zip files are searched in decending order
|
||
|
from the highest number to the lowest, and will always take precedence over the filesystem.
|
||
|
This allows a pk3 distributed as a patch to override all existing data.
|
||
|
|
||
|
Because we will have updated executables freely available online, there is no point to
|
||
|
trying to RESTRICT demo / oem versions of the game with code changes. Demo / oem versions
|
||
|
should be exactly the same executables as release versions, but with different data that
|
||
|
automatically restricts where game media can come from to prevent add-ons from working.
|
||
|
|
||
|
After the paths are initialized, quake will look for the product.txt file. If not
|
||
|
found and verified, the game will run in restricted mode. In restricted mode, only
|
||
|
files contained in demoq3/pak0.pk3 will be available for loading, and only if the zip header is
|
||
|
verified to not have been modified. A single exception is made for q3config.cfg. Files
|
||
|
can still be written out in restricted mode, so screenshots and demos are allowed.
|
||
|
Restricted mode can be tested by setting "+set fs_restrict 1" on the command line, even
|
||
|
if there is a valid product.txt under the basepath or cdpath.
|
||
|
|
||
|
If not running in restricted mode, and a file is not found in any local filesystem,
|
||
|
an attempt will be made to download it and save it under the base path.
|
||
|
|
||
|
If the "fs_copyfiles" cvar is set to 1, then every time a file is sourced from the cd
|
||
|
path, it will be copied over to the base path. This is a development aid to help build
|
||
|
test releases and to copy working sets over slow network links.
|
||
|
|
||
|
File search order: when FS_FOpenFileRead gets called it will go through the fs_searchpaths
|
||
|
structure and stop on the first successful hit. fs_searchpaths is built with successive
|
||
|
calls to FS_AddGameDirectory
|
||
|
|
||
|
Additionaly, we search in several subdirectories:
|
||
|
current game is the current mode
|
||
|
base game is a variable to allow mods based on other mods
|
||
|
(such as baseq3 + missionpack content combination in a mod for instance)
|
||
|
BASEGAME is the hardcoded base game ("baseq3")
|
||
|
|
||
|
e.g. the qpath "sound/newstuff/test.ogg" would be searched for in the following places:
|
||
|
|
||
|
home path + current game's zip files
|
||
|
home path + current game's directory
|
||
|
base path + current game's zip files
|
||
|
base path + current game's directory
|
||
|
cd path + current game's zip files
|
||
|
cd path + current game's directory
|
||
|
|
||
|
home path + base game's zip file
|
||
|
home path + base game's directory
|
||
|
base path + base game's zip file
|
||
|
base path + base game's directory
|
||
|
cd path + base game's zip file
|
||
|
cd path + base game's directory
|
||
|
|
||
|
home path + BASEGAME's zip file
|
||
|
home path + BASEGAME's directory
|
||
|
base path + BASEGAME's zip file
|
||
|
base path + BASEGAME's directory
|
||
|
cd path + BASEGAME's zip file
|
||
|
cd path + BASEGAME's directory
|
||
|
|
||
|
server download, to be written to home path + current game's directory
|
||
|
|
||
|
|
||
|
The filesystem can be safely shutdown and reinitialized with different
|
||
|
basedir / cddir / game combinations, but all other subsystems that rely on it
|
||
|
(sound, video) must also be forced to restart.
|
||
|
|
||
|
Because the same files are loaded by both the clip model (CM_) and renderer (TR_)
|
||
|
subsystems, a simple single-file caching scheme is used. The CM_ subsystems will
|
||
|
load the file with a request to cache. Only one file will be kept cached at a time,
|
||
|
so any models that are going to be referenced by both subsystems should alternate
|
||
|
between the CM_ load function and the ref load function.
|
||
|
|
||
|
TODO: A qpath that starts with a leading slash will always refer to the base game, even if another
|
||
|
game is currently active. This allows character models, skins, and sounds to be downloaded
|
||
|
to a common directory no matter which game is active.
|
||
|
|
||
|
How to prevent downloading zip files?
|
||
|
Pass pk3 file names in systeminfo, and download before FS_Restart()?
|
||
|
|
||
|
Aborting a download disconnects the client from the server.
|
||
|
|
||
|
How to mark files as downloadable? Commercial add-ons won't be downloadable.
|
||
|
|
||
|
Non-commercial downloads will want to download the entire zip file.
|
||
|
the game would have to be reset to actually read the zip in
|
||
|
|
||
|
Auto-update information
|
||
|
|
||
|
Path separators
|
||
|
|
||
|
Casing
|
||
|
|
||
|
separate server gamedir and client gamedir, so if the user starts
|
||
|
a local game after having connected to a network game, it won't stick
|
||
|
with the network game.
|
||
|
|
||
|
allow menu options for game selection?
|
||
|
|
||
|
Read / write config to floppy option.
|
||
|
|
||
|
Different version coexistance?
|
||
|
|
||
|
When building a pak file, make sure a q3config.cfg isn't present in it,
|
||
|
or configs will never get loaded from disk!
|
||
|
|
||
|
todo:
|
||
|
|
||
|
downloading (outside fs?)
|
||
|
game directory passing and restarting
|
||
|
|
||
|
=============================================================================
|
||
|
|
||
|
*/
|
||
|
|
||
|
|
||
|
#define DEMOGAME "demo"
|
||
|
|
||
|
// every time a new demo pk3 file is built, this checksum must be updated.
|
||
|
// the easiest way to get it is to just run the game and see what it spits out
|
||
|
#define DEMO_PAK_CHECKSUM 1656324795u
|
||
|
|
||
|
// if this is defined, the executable positively won't work with any paks other
|
||
|
// than the demo pak, even if productid is present. This is only used for our
|
||
|
// last demo release to prevent the mac and linux users from using the demo
|
||
|
// executable with the production windows pak before the mac/linux products
|
||
|
// hit the shelves a little later
|
||
|
// NOW defined in build files
|
||
|
//#define PRE_RELEASE_DEMO
|
||
|
|
||
|
#define MAX_ZPATH 256
|
||
|
|
||
|
|
||
|
static char fs_gamedir[MAX_OSPATH]; // this will be a single file name with no separators
|
||
|
static cvar_t *fs_debug;
|
||
|
static cvar_t *fs_homepath;
|
||
|
static cvar_t *fs_basepath;
|
||
|
static cvar_t *fs_basegame;
|
||
|
static cvar_t *fs_cdpath;
|
||
|
static cvar_t *fs_gamedirvar;
|
||
|
static cvar_t *fs_restart;
|
||
|
static cvar_t *fs_restrict;
|
||
|
static int fs_initialized;
|
||
|
static int fs_readCount; // total bytes read
|
||
|
static int fs_loadCount; // total files read
|
||
|
static int fs_loadStack; // total files in memory
|
||
|
static int fs_packFiles; // total number of files in packs
|
||
|
|
||
|
static int fs_checksumFeed;
|
||
|
|
||
|
typedef union qfile_gus {
|
||
|
FILE* o;
|
||
|
unzFile z;
|
||
|
} qfile_gut;
|
||
|
|
||
|
typedef struct qfile_us {
|
||
|
qfile_gut file;
|
||
|
qboolean unique;
|
||
|
} qfile_ut;
|
||
|
|
||
|
typedef struct {
|
||
|
int id;
|
||
|
qfile_ut handleFiles;
|
||
|
int zipFilePos;
|
||
|
qboolean zipFile;
|
||
|
} fileHandleData_t;
|
||
|
|
||
|
static fileHandleData_t fsh[MAX_FILE_HANDLES];
|
||
|
|
||
|
void FS_AddZipFile( const char * filename, int length, qboolean mount );
|
||
|
|
||
|
// productId: This file is copyright 2007 HermitWorks Entertainment Corporation, and may not be duplicated except during a licensed installation of the full commercial version of Space Trader
|
||
|
static byte fs_scrambledProductId[177] = {
|
||
|
220, 129, 255, 108, 244, 163, 171, 55, 133, 65, 199, 36, 140, 222, 53, 99, 65, 171, 175, 232, 236, 193, 210, 249, 160, 97, 233, 231, 20, 200, 248, 238, 129, 189, 161, 144, 70, 206, 81, 27, 5, 47, 122, 82, 126, 233, 219, 154, 246, 212, 67, 1, 144, 181, 17, 196, 130, 65, 81, 213, 221, 249, 131, 12, 38, 133, 118, 190, 250, 225, 162, 118, 193, 88, 78, 121, 3, 9, 58, 177, 157, 185, 226, 58, 52, 25, 219, 232, 49, 101, 251, 227, 60, 8, 50, 32, 205, 249, 194, 159, 144, 16, 144, 146, 110, 102, 238, 150, 236, 49, 19, 208, 61, 23, 149, 74, 192, 117, 123, 5, 195, 133, 159, 11, 16, 44, 222, 74, 103, 7, 54, 240, 50, 101, 54, 179, 5, 193, 72, 162, 64, 81, 250, 240, 215, 52, 43, 106, 118, 86, 27, 42, 124, 241, 40, 34, 174, 94, 99, 108, 6, 105, 25, 25, 148, 118, 210, 218, 163, 164, 174, 227, 254, 124, 94, 22, 106,
|
||
|
};
|
||
|
|
||
|
/*
|
||
|
==============
|
||
|
FS_Initialized
|
||
|
==============
|
||
|
*/
|
||
|
|
||
|
qboolean FS_Initialized( void ) {
|
||
|
return fs_initialized;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
=================
|
||
|
FS_LoadStack
|
||
|
return load stack
|
||
|
=================
|
||
|
*/
|
||
|
int FS_LoadStack( void )
|
||
|
{
|
||
|
return fs_loadStack;
|
||
|
}
|
||
|
|
||
|
static fileHandle_t FS_HandleForFile(void) {
|
||
|
int i;
|
||
|
|
||
|
for ( i = 1 ; i < MAX_FILE_HANDLES ; i++ ) {
|
||
|
if ( fsh[i].handleFiles.file.o == NULL ) {
|
||
|
return i;
|
||
|
}
|
||
|
}
|
||
|
Com_Error( ERR_DROP, "FS_HandleForFile: none free" );
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static FILE *FS_FileForHandle( fileHandle_t f ) {
|
||
|
if ( f < 0 || f > MAX_FILE_HANDLES ) {
|
||
|
Com_Error( ERR_DROP, "FS_FileForHandle: out of reange" );
|
||
|
}
|
||
|
if (fsh[f].zipFile == qtrue) {
|
||
|
Com_Error( ERR_DROP, "FS_FileForHandle: can't get FILE on zip file" );
|
||
|
}
|
||
|
if ( ! fsh[f].handleFiles.file.o ) {
|
||
|
Com_Error( ERR_DROP, "FS_FileForHandle: NULL" );
|
||
|
}
|
||
|
|
||
|
return fsh[f].handleFiles.file.o;
|
||
|
}
|
||
|
|
||
|
void FS_ForceFlush( fileHandle_t f ) {
|
||
|
FILE *file;
|
||
|
|
||
|
file = FS_FileForHandle(f);
|
||
|
setvbuf( file, NULL, _IONBF, 0 );
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
====================
|
||
|
FS_ReplaceSeparators
|
||
|
|
||
|
Fix things up differently for win/unix/mac
|
||
|
====================
|
||
|
*/
|
||
|
static char * FS_ReplaceSeparators( char *path ) {
|
||
|
char *s;
|
||
|
|
||
|
for ( s = path ; *s ; s++ ) {
|
||
|
if ( *s == '/' || *s == '\\' ) {
|
||
|
*s = PATH_SEP;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return path;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
===================
|
||
|
FS_BuildOSPath
|
||
|
|
||
|
Qpath may have either forward or backwards slashes
|
||
|
===================
|
||
|
*/
|
||
|
void FS_BuildOSPath( char * ospath, int size, const char *base, const char *game, const char *qpath ) {
|
||
|
|
||
|
if( !game || !game[0] ) {
|
||
|
game = fs_gamedir;
|
||
|
}
|
||
|
|
||
|
Com_sprintf( ospath, size, "%s/%s/%s", base, game, qpath );
|
||
|
FS_ReplaceSeparators( ospath );
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
=====================
|
||
|
FS_BuildOSHomePath
|
||
|
|
||
|
* return a path to a file in the users homepath
|
||
|
=====================
|
||
|
*/
|
||
|
void FS_BuildOSHomePath( char * ospath, int size, const char *qpath ) {
|
||
|
Com_sprintf( ospath, size, "%s/%s/%s", fs_homepath->string, fs_gamedir, qpath );
|
||
|
FS_ReplaceSeparators( ospath );
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
============
|
||
|
FS_CreatePath
|
||
|
|
||
|
Creates any directories needed to store the given filename
|
||
|
============
|
||
|
*/
|
||
|
static qboolean FS_CreatePath (char *OSPath) {
|
||
|
char *ofs;
|
||
|
|
||
|
// make absolutely sure that it can't back up the path
|
||
|
// FIXME: is c: allowed???
|
||
|
if ( strstr( OSPath, ".." ) || strstr( OSPath, "::" ) ) {
|
||
|
Com_Printf( "WARNING: refusing to create relative path \"%s\"\n", OSPath );
|
||
|
return qtrue;
|
||
|
}
|
||
|
|
||
|
for (ofs = OSPath+1 ; *ofs ; ofs++) {
|
||
|
if (*ofs == PATH_SEP) {
|
||
|
// create the directory
|
||
|
*ofs = 0;
|
||
|
Sys_Mkdir (OSPath);
|
||
|
*ofs = PATH_SEP;
|
||
|
}
|
||
|
}
|
||
|
return qfalse;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
================
|
||
|
FS_FileExists
|
||
|
|
||
|
Tests if the file exists in the current gamedir, this DOES NOT
|
||
|
search the paths. This is to determine if opening a file to write
|
||
|
(which always goes into the current gamedir) will cause any overwrites.
|
||
|
NOTE TTimo: this goes with FS_FOpenFileWrite for opening the file afterwards
|
||
|
================
|
||
|
*/
|
||
|
qboolean Q_EXTERNAL_CALL FS_FileExists( const char *file )
|
||
|
{
|
||
|
FILE *f;
|
||
|
char testpath[ MAX_OSPATH ];
|
||
|
|
||
|
FS_BuildOSPath( testpath, sizeof(testpath), fs_homepath->string, fs_gamedir, file );
|
||
|
|
||
|
f = fopen( testpath, "rb" );
|
||
|
if (f) {
|
||
|
fclose( f );
|
||
|
return qtrue;
|
||
|
}
|
||
|
return qfalse;
|
||
|
}
|
||
|
|
||
|
static void fs_db_open( fileHandle_t f, const char * fullname, const char * filename, int resume_from ) {
|
||
|
|
||
|
int path_id = -1;
|
||
|
|
||
|
// attempt to find the path id
|
||
|
sql_prepare ( &com_db, "SELECT id FROM paths WHERE (path||$2) like ($1);" );
|
||
|
sql_bindtext( &com_db, 1, fullname );
|
||
|
sql_bindtext( &com_db, 2, filename );
|
||
|
|
||
|
if ( sql_step( &com_db ) ) {
|
||
|
path_id = sql_columnasint( &com_db, 0 );
|
||
|
}
|
||
|
|
||
|
sql_done ( &com_db );
|
||
|
|
||
|
|
||
|
sql_prepare ( &com_db, "INSERT INTO openfiles(f,path,time,xfer,name) VALUES(?,?,SYS_TIME,?,$);" );
|
||
|
sql_bindint ( &com_db, 1, f );
|
||
|
sql_bindint ( &com_db, 2, path_id );
|
||
|
sql_bindint ( &com_db, 3, resume_from );
|
||
|
sql_bindtext( &com_db, 4, filename );
|
||
|
sql_step ( &com_db );
|
||
|
sql_done ( &com_db );
|
||
|
}
|
||
|
|
||
|
static void fs_db_close( fileHandle_t f ) {
|
||
|
|
||
|
sql_prepare ( &com_db, "SELECT name, path FROM openfiles SEARCH f ?1 WHERE pak==0;" );
|
||
|
sql_bindint ( &com_db, 1, f );
|
||
|
|
||
|
// check to see if the file being closed was being written to and is not a pak file
|
||
|
if ( sql_step( &com_db ) ) {
|
||
|
|
||
|
const char * fullname = sql_columnastext( &com_db, 0 );
|
||
|
char path [ MAX_QPATH ];
|
||
|
char name [ MAX_QPATH ];
|
||
|
char ext [ 16 ];
|
||
|
int length;
|
||
|
int path_id = sql_columnasint( &com_db, 1 );
|
||
|
|
||
|
Sys_SplitPath( fullname, path, sizeof(path), name, sizeof(name), ext, sizeof(ext) );
|
||
|
|
||
|
fseek( fsh[f].handleFiles.file.o, 0, SEEK_END );
|
||
|
length = ftell( fsh[f].handleFiles.file.o );
|
||
|
|
||
|
// update the fat to reflect new file
|
||
|
sql_prepare ( &com_db, "UPDATE OR INSERT files SET id=#+1, path_id=?5, path=$1,name=$2,ext=$3, length=?4, pak_id=-1, fullname=path||name||ext SEARCH name $2 WHERE path like $1 AND ext like $3;" );
|
||
|
sql_bindtext( &com_db, 1, path );
|
||
|
sql_bindtext( &com_db, 2, name );
|
||
|
sql_bindtext( &com_db, 3, ext );
|
||
|
sql_bindint ( &com_db, 4, length );
|
||
|
sql_bindint ( &com_db, 5, path_id );
|
||
|
sql_step ( &com_db );
|
||
|
sql_done ( &com_db );
|
||
|
|
||
|
sql_prepare ( &com_db, "DELETE FROM openfiles WHERE f = ?1;" );
|
||
|
sql_bindint ( &com_db, 1, f );
|
||
|
sql_step ( &com_db );
|
||
|
sql_done ( &com_db );
|
||
|
}
|
||
|
|
||
|
sql_done( &com_db );
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
==============
|
||
|
FS_FCloseFile
|
||
|
|
||
|
If the FILE pointer is an open pak file, leave it open.
|
||
|
|
||
|
For some reason, other dll's can't just cal fclose()
|
||
|
on files returned by FS_FOpenFile...
|
||
|
==============
|
||
|
*/
|
||
|
void Q_EXTERNAL_CALL FS_FCloseFile( fileHandle_t f )
|
||
|
{
|
||
|
if ( !fs_initialized ) {
|
||
|
Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
|
||
|
}
|
||
|
|
||
|
if (fsh[f].zipFile == qtrue) {
|
||
|
unzCloseCurrentFile( fsh[f].handleFiles.file.z );
|
||
|
if ( fsh[f].handleFiles.unique ) {
|
||
|
unzClose( fsh[f].handleFiles.file.z );
|
||
|
}
|
||
|
Com_Memset( &fsh[f], 0, sizeof( fsh[f] ) );
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// we didn't find it as a pak, so close it as a unique file
|
||
|
if (fsh[f].handleFiles.file.o) {
|
||
|
|
||
|
fs_db_close( f );
|
||
|
|
||
|
fclose (fsh[f].handleFiles.file.o);
|
||
|
|
||
|
// check if this was a temporary file
|
||
|
sql_prepare ( &com_db, "SELECT name,xfer,length,pak FROM openfiles SEARCH f ?1 WHERE pak=1;" );
|
||
|
sql_bindint ( &com_db, 1, f );
|
||
|
if ( sql_step( &com_db ) ) {
|
||
|
char source[ MAX_OSPATH ];
|
||
|
char target[ MAX_OSPATH ];
|
||
|
int xfer = sql_columnasint( &com_db, 1 );
|
||
|
int length = sql_columnasint( &com_db, 2 );
|
||
|
|
||
|
FS_BuildOSPath( source, sizeof(source), fs_basepath->string, fs_gamedir, sql_columnastext( &com_db, 0 ) );
|
||
|
|
||
|
if ( xfer > 0 ) {
|
||
|
|
||
|
// if transfer was completed
|
||
|
if ( xfer == length ) {
|
||
|
|
||
|
char *t;
|
||
|
Q_strncpyz( target, source, sizeof(target) );
|
||
|
t = strstr( target, "~" );
|
||
|
if ( t ) {
|
||
|
*t = 0;
|
||
|
}
|
||
|
|
||
|
remove( target );
|
||
|
|
||
|
if ( rename( source, target ) != 0 ) {
|
||
|
Com_Error( ERR_FATAL, "could not update pak file %s. File may be in use.\n", target );
|
||
|
}
|
||
|
|
||
|
fs_restart->integer = 1;
|
||
|
|
||
|
|
||
|
// auto load paks.
|
||
|
if ( sql_columnasint( &com_db, 3 ) == 1 ) {
|
||
|
FS_AddZipFile( target, length, qtrue );
|
||
|
}
|
||
|
}
|
||
|
} else {
|
||
|
// transfer did nothing, remove temp file
|
||
|
remove( source );
|
||
|
}
|
||
|
|
||
|
sql_prepare ( &com_db, "DELETE FROM openfiles WHERE f = ?1;" );
|
||
|
sql_bindint ( &com_db, 1, f );
|
||
|
sql_step ( &com_db );
|
||
|
sql_done ( &com_db );
|
||
|
}
|
||
|
sql_done ( &com_db );
|
||
|
}
|
||
|
Com_Memset( &fsh[f], 0, sizeof( fsh[f] ) );
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
===========
|
||
|
FS_FOpenFileWrite
|
||
|
|
||
|
===========
|
||
|
*/
|
||
|
fileHandle_t Q_EXTERNAL_CALL FS_FOpenFileWrite( const char *filename )
|
||
|
{
|
||
|
char ospath[ MAX_OSPATH ];
|
||
|
fileHandle_t f;
|
||
|
|
||
|
if ( !fs_initialized ) {
|
||
|
Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
|
||
|
}
|
||
|
|
||
|
f = FS_HandleForFile();
|
||
|
fsh[f].zipFile = qfalse;
|
||
|
|
||
|
if ( filename[ 0 ] == '~' && filename[ 1 ] == '/' ) {
|
||
|
filename += 2;
|
||
|
FS_BuildOSPath( ospath, sizeof(ospath), fs_basepath->string, fs_gamedir, filename );
|
||
|
} else {
|
||
|
FS_BuildOSPath( ospath, sizeof(ospath), fs_homepath->string, fs_gamedir, filename );
|
||
|
}
|
||
|
|
||
|
fs_db_open( f, ospath, filename, 0 );
|
||
|
|
||
|
if ( fs_debug->integer ) {
|
||
|
Com_Printf( "FS_FOpenFileWrite: %s\n", ospath );
|
||
|
}
|
||
|
|
||
|
if( FS_CreatePath( ospath ) ) {
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
// enabling the following line causes a recursive function call loop
|
||
|
// when running with +set logfile 1 +set developer 1
|
||
|
//Com_DPrintf( "writing to: %s\n", ospath );
|
||
|
fsh[f].handleFiles.file.o = fopen( ospath, "wb" );
|
||
|
|
||
|
if (!fsh[f].handleFiles.file.o) {
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
return f;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
===========
|
||
|
FS_FOpenFileWrite
|
||
|
|
||
|
===========
|
||
|
*/
|
||
|
int Q_EXTERNAL_CALL FS_FOpenFileDirect( const char *filename, fileHandle_t * f )
|
||
|
{
|
||
|
int r;
|
||
|
char ospath[ MAX_OSPATH ];
|
||
|
|
||
|
if ( !fs_initialized ) {
|
||
|
Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
|
||
|
}
|
||
|
|
||
|
*f = FS_HandleForFile();
|
||
|
fsh[*f].zipFile = qfalse;
|
||
|
|
||
|
FS_BuildOSPath( ospath, sizeof(ospath), fs_homepath->string, fs_gamedir, filename );
|
||
|
|
||
|
if ( fs_debug->integer ) {
|
||
|
Com_Printf( "FS_FOpenFileDirect: %s\n", ospath );
|
||
|
}
|
||
|
|
||
|
// enabling the following line causes a recursive function call loop
|
||
|
// when running with +set logfile 1 +set developer 1
|
||
|
//Com_DPrintf( "writing to: %s\n", ospath );
|
||
|
fsh[*f].handleFiles.file.o = fopen( ospath, "rb" );
|
||
|
|
||
|
if (!fsh[*f].handleFiles.file.o) {
|
||
|
*f = 0;
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
fseek( fsh[*f].handleFiles.file.o, 0, SEEK_END );
|
||
|
r = ftell( fsh[*f].handleFiles.file.o );
|
||
|
fseek( fsh[*f].handleFiles.file.o, 0, SEEK_SET );
|
||
|
|
||
|
return r;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
===========
|
||
|
FS_FOpenFileAppend
|
||
|
|
||
|
===========
|
||
|
*/
|
||
|
int FS_FOpenFileAppend( const char *filename, fileHandle_t *f ) {
|
||
|
char ospath[ MAX_OSPATH ];
|
||
|
int r;
|
||
|
|
||
|
if ( !fs_initialized ) {
|
||
|
Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
|
||
|
}
|
||
|
|
||
|
*f = FS_HandleForFile();
|
||
|
fsh[*f].zipFile = qfalse;
|
||
|
|
||
|
// don't let sound stutter
|
||
|
S_ClearSoundBuffer();
|
||
|
|
||
|
if ( filename[ 0 ] == '~' && filename[ 1 ] == '/' ) {
|
||
|
filename += 2;
|
||
|
FS_BuildOSPath( ospath, sizeof(ospath), fs_basepath->string, fs_gamedir, filename );
|
||
|
} else {
|
||
|
FS_BuildOSPath( ospath, sizeof(ospath), fs_homepath->string, fs_gamedir, filename );
|
||
|
}
|
||
|
|
||
|
if ( fs_debug->integer ) {
|
||
|
Com_Printf( "FS_FOpenFileAppend: %s\n", ospath );
|
||
|
}
|
||
|
|
||
|
if( FS_CreatePath( ospath ) ) {
|
||
|
*f = 0;
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
fsh[*f].handleFiles.file.o = fopen( ospath, "ab" );
|
||
|
if (!fsh[*f].handleFiles.file.o) {
|
||
|
*f = 0;
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
fseek( fsh[*f].handleFiles.file.o, 0, SEEK_END );
|
||
|
r = ftell( fsh[*f].handleFiles.file.o );
|
||
|
|
||
|
fs_db_open( *f, ospath, filename, r );
|
||
|
|
||
|
return r;
|
||
|
}
|
||
|
|
||
|
#ifdef DEVELOPER
|
||
|
/*
|
||
|
===========
|
||
|
FS_FOpenFileUpdate
|
||
|
|
||
|
===========
|
||
|
*/
|
||
|
fileHandle_t FS_FOpenFileUpdate( const char *filename, int * length ) {
|
||
|
char ospath[ MAX_OSPATH ];
|
||
|
fileHandle_t f;
|
||
|
|
||
|
if ( !fs_initialized ) {
|
||
|
Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
|
||
|
}
|
||
|
|
||
|
f = FS_HandleForFile();
|
||
|
fsh[f].zipFile = qfalse;
|
||
|
|
||
|
FS_BuildOSPath( ospath, sizeof(ospath), fs_basepath->string, fs_gamedir, filename );
|
||
|
|
||
|
if ( fs_debug->integer ) {
|
||
|
Com_Printf( "FS_FOpenFileWrite: %s\n", ospath );
|
||
|
}
|
||
|
|
||
|
if( FS_CreatePath( ospath ) ) {
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
fs_db_open( f, ospath, filename, 0 );
|
||
|
|
||
|
// enabling the following line causes a recursive function call loop
|
||
|
// when running with +set logfile 1 +set developer 1
|
||
|
//Com_DPrintf( "writing to: %s\n", ospath );
|
||
|
fsh[f].handleFiles.file.o = fopen( ospath, "wb" );
|
||
|
|
||
|
if (!fsh[f].handleFiles.file.o) {
|
||
|
f = 0;
|
||
|
}
|
||
|
return f;
|
||
|
}
|
||
|
|
||
|
#endif
|
||
|
|
||
|
/*
|
||
|
===========
|
||
|
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.
|
||
|
===========
|
||
|
*/
|
||
|
extern qboolean com_fullyInitialized;
|
||
|
|
||
|
int FS_FOpenFileRead( const char *filename, fileHandle_t *file, qboolean uniqueFILE ) {
|
||
|
|
||
|
if ( !fs_initialized ) {
|
||
|
Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
|
||
|
}
|
||
|
|
||
|
if ( !filename ) {
|
||
|
Com_Error( ERR_FATAL, "FS_FOpenFileRead: NULL 'filename' parameter passed\n" );
|
||
|
}
|
||
|
|
||
|
// qpaths are not supposed to have a leading slash
|
||
|
if ( filename[0] == '/' || filename[0] == '\\' ) {
|
||
|
filename++;
|
||
|
}
|
||
|
|
||
|
// make absolutely sure that it can't back up the path.
|
||
|
// The searchpaths do guarantee that something will always
|
||
|
// be prepended, so we don't need to worry about "c:" or "//limbo"
|
||
|
if ( strstr( filename, ".." ) || strstr( filename, "::" ) ) {
|
||
|
*file = 0;
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
// make sure the stkey file is only readable by the quake3.exe at initialization
|
||
|
// any other time the key should only be accessed in memory using the provided functions
|
||
|
if( com_fullyInitialized && strstr( filename, "stkey" ) ) {
|
||
|
*file = 0;
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// search through the path, one element at a time
|
||
|
//
|
||
|
sql_prepare ( &com_db, "SELECT paths.id[ path_id ]^path || '/' || fullname, length, offset, pak_id, id FROM files SEARCH fullname $1;" );
|
||
|
sql_bindtext( &com_db, 1, filename );
|
||
|
|
||
|
if ( sql_step( &com_db ) ) {
|
||
|
const char * file_path = sql_columnastext ( &com_db, 0 );
|
||
|
int file_length = sql_columnasint ( &com_db, 1 );
|
||
|
int file_offset = sql_columnasint ( &com_db, 2 );
|
||
|
int file_pakid = sql_columnasint ( &com_db, 3 );
|
||
|
int file_id = sql_columnasint ( &com_db, 4 );
|
||
|
|
||
|
sql_done( &com_db );
|
||
|
|
||
|
if ( !file ) {
|
||
|
return file_length;
|
||
|
}
|
||
|
|
||
|
*file = FS_HandleForFile();
|
||
|
|
||
|
fsh[*file].id = file_id;
|
||
|
fsh[*file].handleFiles.unique = uniqueFILE;
|
||
|
|
||
|
if ( file_pakid >= 1 ) {
|
||
|
|
||
|
const char * pakFilename;
|
||
|
unzFile pakHandle;
|
||
|
unz_s *zfi;
|
||
|
FILE *temp;
|
||
|
|
||
|
sql_prepare( &com_db, "SELECT handle, name FROM pakfiles SEARCH id ?1;" );
|
||
|
sql_bindint( &com_db, 1, file_pakid );
|
||
|
|
||
|
if ( !sql_step( &com_db ) ) {
|
||
|
Com_Error( ERR_FATAL, "can't find pak file" );
|
||
|
}
|
||
|
|
||
|
pakHandle = (unzFile)sql_columnasint ( &com_db, 0 );
|
||
|
pakFilename = sql_columnastext ( &com_db, 1 );
|
||
|
|
||
|
sql_done( &com_db );
|
||
|
|
||
|
if ( uniqueFILE ) {
|
||
|
// open a new file on the pakfile
|
||
|
fsh[*file].handleFiles.file.z = unzReOpen (pakFilename, pakHandle);
|
||
|
if (fsh[*file].handleFiles.file.z == NULL) {
|
||
|
Com_Error (ERR_FATAL, "Couldn't reopen %s", pakFilename);
|
||
|
}
|
||
|
|
||
|
} else {
|
||
|
fsh[*file].handleFiles.file.z = pakHandle;
|
||
|
}
|
||
|
fsh[*file].zipFile = qtrue;
|
||
|
zfi = (unz_s *)fsh[*file].handleFiles.file.z;
|
||
|
// in case the file was new
|
||
|
temp = zfi->file;
|
||
|
// set the file position in the zip file (also sets the current file info)
|
||
|
unzSetCurrentFileInfoPosition(pakHandle, file_offset);
|
||
|
// copy the file info into the unzip structure
|
||
|
Com_Memcpy( zfi, pakHandle, sizeof(unz_s) );
|
||
|
// we copy this back into the structure
|
||
|
zfi->file = temp;
|
||
|
// open the file in the zip
|
||
|
unzOpenCurrentFile( fsh[*file].handleFiles.file.z );
|
||
|
fsh[*file].zipFilePos = file_offset;
|
||
|
|
||
|
if ( fs_debug->integer ) {
|
||
|
Com_Printf( "FS_FOpenFileRead: %s (found in '%s')\n",
|
||
|
filename, pakFilename );
|
||
|
}
|
||
|
|
||
|
|
||
|
} else {
|
||
|
fsh[*file].handleFiles.file.o = fopen (file_path, "rb");
|
||
|
if ( !fsh[*file].handleFiles.file.o ) {
|
||
|
*file = 0;
|
||
|
return -1;
|
||
|
}
|
||
|
fsh[*file].zipFile = qfalse;
|
||
|
}
|
||
|
|
||
|
#ifdef DEVELOPER
|
||
|
sql_prepare ( &com_db, "INSERT INTO access(id,time,path) VALUES(?,SYS_TIME,$);" );
|
||
|
sql_bindint ( &com_db, 1, file_id );
|
||
|
sql_bindtext( &com_db, 2, filename );
|
||
|
sql_step ( &com_db );
|
||
|
sql_done ( &com_db );
|
||
|
#endif
|
||
|
|
||
|
return file_length;
|
||
|
}
|
||
|
|
||
|
sql_done( &com_db );
|
||
|
|
||
|
if ( !file )
|
||
|
return 0;
|
||
|
|
||
|
*file = 0;
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
int Q_EXTERNAL_CALL FS_Read( void *buffer, int len, fileHandle_t f ) {
|
||
|
int block, remaining;
|
||
|
int read;
|
||
|
byte *buf;
|
||
|
int tries;
|
||
|
|
||
|
if ( !fs_initialized ) {
|
||
|
Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
|
||
|
}
|
||
|
|
||
|
if ( !f ) {
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
buf = (byte *)buffer;
|
||
|
fs_readCount += len;
|
||
|
|
||
|
if (fsh[f].zipFile == qfalse) {
|
||
|
remaining = len;
|
||
|
tries = 0;
|
||
|
while (remaining) {
|
||
|
block = remaining;
|
||
|
read = fread (buf, 1, block, fsh[f].handleFiles.file.o);
|
||
|
if (read == 0) {
|
||
|
// we might have been trying to read from a CD, which
|
||
|
// sometimes returns a 0 read on windows
|
||
|
if (!tries) {
|
||
|
tries = 1;
|
||
|
} else {
|
||
|
return len-remaining; //Com_Error (ERR_FATAL, "FS_Read: 0 bytes read");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (read == -1) {
|
||
|
Com_Error (ERR_FATAL, "FS_Read: -1 bytes read");
|
||
|
}
|
||
|
|
||
|
remaining -= read;
|
||
|
buf += read;
|
||
|
}
|
||
|
return len;
|
||
|
} else {
|
||
|
return unzReadCurrentFile(fsh[f].handleFiles.file.z, buffer, len);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
=================
|
||
|
FS_Write
|
||
|
|
||
|
Properly handles partial writes
|
||
|
=================
|
||
|
*/
|
||
|
int Q_EXTERNAL_CALL FS_Write( const void *buffer, int len, fileHandle_t h )
|
||
|
{
|
||
|
int block, remaining;
|
||
|
int written;
|
||
|
byte *buf;
|
||
|
int tries;
|
||
|
FILE *f;
|
||
|
|
||
|
if ( !fs_initialized ) {
|
||
|
Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
|
||
|
}
|
||
|
|
||
|
if ( !h ) {
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
f = FS_FileForHandle(h);
|
||
|
buf = (byte *)buffer;
|
||
|
|
||
|
remaining = len;
|
||
|
tries = 0;
|
||
|
while (remaining) {
|
||
|
block = remaining;
|
||
|
written = fwrite (buf, 1, block, f);
|
||
|
if (written == 0) {
|
||
|
if (!tries) {
|
||
|
tries = 1;
|
||
|
} else {
|
||
|
Com_Printf( "FS_Write: 0 bytes written\n" );
|
||
|
return 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (written == -1) {
|
||
|
Com_Printf( "FS_Write: -1 bytes written\n" );
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
remaining -= written;
|
||
|
buf += written;
|
||
|
}
|
||
|
|
||
|
sql_prepare ( &com_db, "UPDATE openfiles SET xfer=xfer+?2 SEARCH f ?1;" );
|
||
|
sql_bindint ( &com_db, 1, h );
|
||
|
sql_bindint ( &com_db, 2, len );
|
||
|
sql_step ( &com_db );
|
||
|
sql_done ( &com_db );
|
||
|
|
||
|
return len;
|
||
|
}
|
||
|
|
||
|
void QDECL FS_Printf( fileHandle_t h, const char *fmt, ... ) {
|
||
|
va_list argptr;
|
||
|
char msg[MAXPRINTMSG];
|
||
|
|
||
|
va_start (argptr,fmt);
|
||
|
Q_vsnprintf( msg, sizeof( msg ) - 1, fmt, argptr );
|
||
|
msg[sizeof( msg ) - 1] = 0;
|
||
|
va_end (argptr);
|
||
|
|
||
|
FS_Write(msg, strlen(msg), h);
|
||
|
}
|
||
|
|
||
|
#define PK3_SEEK_BUFFER_SIZE 65536
|
||
|
|
||
|
/*
|
||
|
=================
|
||
|
FS_Seek
|
||
|
|
||
|
=================
|
||
|
*/
|
||
|
int Q_EXTERNAL_CALL FS_Seek( fileHandle_t f, long offset, int origin )
|
||
|
{
|
||
|
int _origin;
|
||
|
|
||
|
if ( !fs_initialized ) {
|
||
|
Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
if (fsh[f].zipFile == qtrue) {
|
||
|
//FIXME: this is incomplete and really, really
|
||
|
//crappy (but better than what was here before)
|
||
|
byte buffer[PK3_SEEK_BUFFER_SIZE];
|
||
|
int remainder = offset;
|
||
|
|
||
|
if( offset < 0 || origin == FS_SEEK_END ) {
|
||
|
Com_Error( ERR_FATAL, "Negative offsets and FS_SEEK_END not implemented "
|
||
|
"for FS_Seek on pk3 file contents\n" );
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
switch( origin ) {
|
||
|
case FS_SEEK_SET:
|
||
|
unzSetCurrentFileInfoPosition(fsh[f].handleFiles.file.z, fsh[f].zipFilePos);
|
||
|
unzOpenCurrentFile(fsh[f].handleFiles.file.z);
|
||
|
//fallthrough
|
||
|
|
||
|
case FS_SEEK_CUR:
|
||
|
while( remainder > PK3_SEEK_BUFFER_SIZE ) {
|
||
|
FS_Read( buffer, PK3_SEEK_BUFFER_SIZE, f );
|
||
|
remainder -= PK3_SEEK_BUFFER_SIZE;
|
||
|
}
|
||
|
FS_Read( buffer, remainder, f );
|
||
|
return offset;
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
Com_Error( ERR_FATAL, "Bad origin in FS_Seek\n" );
|
||
|
return -1;
|
||
|
break;
|
||
|
}
|
||
|
} else {
|
||
|
FILE *file;
|
||
|
file = FS_FileForHandle(f);
|
||
|
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 fseek( file, offset, _origin );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
======================================================================================
|
||
|
|
||
|
CONVENIENCE FUNCTIONS FOR ENTIRE FILES
|
||
|
|
||
|
======================================================================================
|
||
|
*/
|
||
|
|
||
|
/*
|
||
|
============
|
||
|
FS_ReadFile
|
||
|
|
||
|
Filename are relative to the quake search path
|
||
|
a null buffer will just return the file length without loading
|
||
|
============
|
||
|
*/
|
||
|
int Q_EXTERNAL_CALL FS_ReadFile( const char *qpath, void **buffer ) {
|
||
|
fileHandle_t h;
|
||
|
byte* buf;
|
||
|
qboolean isConfig;
|
||
|
int len;
|
||
|
|
||
|
if ( !fs_initialized ) {
|
||
|
Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
|
||
|
}
|
||
|
|
||
|
if ( !qpath || !qpath[0] ) {
|
||
|
Com_Error( ERR_FATAL, "FS_ReadFile with empty name\n" );
|
||
|
}
|
||
|
|
||
|
buf = NULL; // quiet compiler warning
|
||
|
|
||
|
// if this is a .cfg file and we are playing back a journal, read
|
||
|
// it from the journal file
|
||
|
if ( strstr( qpath, ".cfg" ) ) {
|
||
|
isConfig = qtrue;
|
||
|
if ( com_journal && com_journal->integer == 2 ) {
|
||
|
int r;
|
||
|
|
||
|
Com_DPrintf( "Loading %s from journal file.\n", qpath );
|
||
|
r = FS_Read( &len, sizeof( len ), com_journalDataFile );
|
||
|
if ( r != sizeof( len ) ) {
|
||
|
if (buffer != NULL) *buffer = NULL;
|
||
|
return -1;
|
||
|
}
|
||
|
// if the file didn't exist when the journal was created
|
||
|
if (!len) {
|
||
|
if (buffer == NULL) {
|
||
|
return 1; // hack for old journal files
|
||
|
}
|
||
|
*buffer = NULL;
|
||
|
return -1;
|
||
|
}
|
||
|
if (buffer == NULL) {
|
||
|
return len;
|
||
|
}
|
||
|
|
||
|
buf = Hunk_AllocateTempMemory(len+1);
|
||
|
*buffer = buf;
|
||
|
|
||
|
r = FS_Read( buf, len, com_journalDataFile );
|
||
|
if ( r != len ) {
|
||
|
Com_Error( ERR_FATAL, "Read from journalDataFile failed" );
|
||
|
}
|
||
|
|
||
|
fs_loadCount++;
|
||
|
fs_loadStack++;
|
||
|
|
||
|
// guarantee that it will have a trailing 0 for string operations
|
||
|
buf[len] = 0;
|
||
|
|
||
|
return len;
|
||
|
}
|
||
|
} else {
|
||
|
isConfig = qfalse;
|
||
|
}
|
||
|
|
||
|
// look for it in the filesystem or pack files
|
||
|
len = FS_FOpenFileRead( qpath, &h, qfalse );
|
||
|
if ( h == 0 ) {
|
||
|
if ( buffer ) {
|
||
|
*buffer = NULL;
|
||
|
}
|
||
|
// if we are journalling and it is a config file, write a zero to the journal file
|
||
|
if ( isConfig && com_journal && com_journal->integer == 1 ) {
|
||
|
Com_DPrintf( "Writing zero for %s to journal file.\n", qpath );
|
||
|
len = 0;
|
||
|
FS_Write( &len, sizeof( len ), com_journalDataFile );
|
||
|
FS_Flush( com_journalDataFile );
|
||
|
}
|
||
|
#ifdef DEVELOPER
|
||
|
sql_prepare ( &com_db, "INSERT INTO missing(path) VALUES($);" );
|
||
|
sql_bindtext( &com_db, 1, qpath );
|
||
|
sql_step ( &com_db );
|
||
|
sql_done ( &com_db );
|
||
|
#endif
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
if ( !buffer ) {
|
||
|
if ( isConfig && com_journal && com_journal->integer == 1 ) {
|
||
|
Com_DPrintf( "Writing len for %s to journal file.\n", qpath );
|
||
|
FS_Write( &len, sizeof( len ), com_journalDataFile );
|
||
|
FS_Flush( com_journalDataFile );
|
||
|
}
|
||
|
FS_FCloseFile( h);
|
||
|
return len;
|
||
|
}
|
||
|
|
||
|
fs_loadCount++;
|
||
|
fs_loadStack++;
|
||
|
|
||
|
buf = Hunk_AllocateTempMemory(len+1);
|
||
|
*buffer = buf;
|
||
|
|
||
|
FS_Read (buf, len, h);
|
||
|
|
||
|
// guarantee that it will have a trailing 0 for string operations
|
||
|
buf[len] = 0;
|
||
|
FS_FCloseFile( h );
|
||
|
|
||
|
// if we are journalling and it is a config file, write it to the journal file
|
||
|
if ( isConfig && com_journal && com_journal->integer == 1 ) {
|
||
|
Com_DPrintf( "Writing %s to journal file.\n", qpath );
|
||
|
FS_Write( &len, sizeof( len ), com_journalDataFile );
|
||
|
FS_Write( buf, len, com_journalDataFile );
|
||
|
FS_Flush( com_journalDataFile );
|
||
|
}
|
||
|
return len;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
=============
|
||
|
FS_FreeFile
|
||
|
=============
|
||
|
*/
|
||
|
void Q_EXTERNAL_CALL FS_FreeFile( void *buffer ) {
|
||
|
if ( !fs_initialized ) {
|
||
|
Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
|
||
|
}
|
||
|
if ( !buffer ) {
|
||
|
Com_Error( ERR_FATAL, "FS_FreeFile( NULL )" );
|
||
|
}
|
||
|
fs_loadStack--;
|
||
|
|
||
|
Hunk_FreeTempMemory( buffer );
|
||
|
|
||
|
// if all of our temp files are free, clear all of our space
|
||
|
if ( fs_loadStack == 0 ) {
|
||
|
Hunk_ClearTempMemory();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
============
|
||
|
FS_WriteFile
|
||
|
|
||
|
Filename are reletive to the quake search path
|
||
|
============
|
||
|
*/
|
||
|
void Q_EXTERNAL_CALL FS_WriteFile( const char *qpath, const void *buffer, int size ) {
|
||
|
fileHandle_t f;
|
||
|
|
||
|
if ( !fs_initialized ) {
|
||
|
Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
|
||
|
}
|
||
|
|
||
|
if ( !qpath || !buffer ) {
|
||
|
Com_Error( ERR_FATAL, "FS_WriteFile: NULL parameter" );
|
||
|
}
|
||
|
|
||
|
f = FS_FOpenFileWrite( qpath );
|
||
|
if ( !f ) {
|
||
|
Com_Printf( "Failed to open %s\n", qpath );
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
FS_Write( buffer, size, f );
|
||
|
|
||
|
FS_FCloseFile( f );
|
||
|
}
|
||
|
|
||
|
#ifdef USE_WEBHOST
|
||
|
const char * FS_GetMD5AndLength( const char * name ) {
|
||
|
|
||
|
const char * info = Cvar_VariableString( "sv_paks" );
|
||
|
|
||
|
for ( ;; )
|
||
|
{
|
||
|
char * n = COM_Parse( &info );
|
||
|
if ( *n == '\0' ) {
|
||
|
// pak wasn't found in server lists
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
if ( !Q_stricmp( name, n ) ) {
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
COM_Parse( &info ); // skip md5
|
||
|
COM_Parse( &info ); // skip length
|
||
|
}
|
||
|
|
||
|
return info;
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
|
||
|
#ifdef USE_WEBHOST
|
||
|
/*
|
||
|
=================
|
||
|
FS_IsPakPure
|
||
|
|
||
|
checks to see if the local pak file is the same as the web host's version
|
||
|
=================
|
||
|
*/
|
||
|
int FS_PakIsPure( const char * filename ) {
|
||
|
|
||
|
char sv_md5[ 33 ];
|
||
|
char cl_md5[ 33 ];
|
||
|
int sv_length;
|
||
|
int cl_length;
|
||
|
const char * info;
|
||
|
int sv_pure;
|
||
|
|
||
|
sv_pure = Cvar_VariableIntegerValue( "sv_pure" );
|
||
|
|
||
|
info = FS_GetMD5AndLength( filename );
|
||
|
|
||
|
// pak wasn't found in server lists
|
||
|
if ( !info && sv_pure ) {
|
||
|
return 0; // if pure server then don't allow this pak to be loaded
|
||
|
}
|
||
|
|
||
|
cl_length = FS_FOpenFileRead( filename, 0, qfalse );
|
||
|
|
||
|
if ( cl_length <= 0 ) {
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
// allow any pak found matching name load
|
||
|
if ( sv_pure == 0 ) {
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
// get the MD5 of the pak on the website
|
||
|
Q_strncpyz( sv_md5, COM_Parse( &info ), sizeof(sv_md5) );
|
||
|
sv_length = atoi( COM_Parse( &info ) );
|
||
|
|
||
|
if ( cl_length == sv_length ) {
|
||
|
|
||
|
// get the MD5 of the local copy
|
||
|
Com_MD5File( cl_md5, filename );
|
||
|
|
||
|
// return if they're the same
|
||
|
if ( !Q_stricmp( sv_md5, cl_md5 ) ) {
|
||
|
return 1;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
|
||
|
/*
|
||
|
=================
|
||
|
FS_LoadZipFile
|
||
|
|
||
|
loads the contents of the zip file into the FAT.
|
||
|
=================
|
||
|
*/
|
||
|
void FS_LoadZipFile( const char * filename, const char * basename, const char * ext )
|
||
|
{
|
||
|
unzFile uf;
|
||
|
int err;
|
||
|
unz_global_info gi;
|
||
|
unz_file_info file_info;
|
||
|
int i;
|
||
|
int pak_id = -1;
|
||
|
int checksum;
|
||
|
int fs_numHeaderLongs;
|
||
|
int *fs_headerLongs;
|
||
|
|
||
|
fs_numHeaderLongs = 0;
|
||
|
|
||
|
// find the id of the pak file
|
||
|
sql_prepare ( &com_db, "SELECT id FROM files SEARCH name $1 WHERE ext like $2;" );
|
||
|
sql_bindtext( &com_db, 1, basename );
|
||
|
sql_bindtext( &com_db, 2, ext );
|
||
|
if ( sql_step( &com_db ) ) {
|
||
|
pak_id = sql_columnasint( &com_db, 0 );
|
||
|
}
|
||
|
sql_done ( &com_db );
|
||
|
|
||
|
sql_bindint ( &com_db, 7, pak_id );
|
||
|
|
||
|
// open pak
|
||
|
uf = unzOpen( filename );
|
||
|
err = unzGetGlobalInfo(uf,&gi);
|
||
|
|
||
|
if (err != UNZ_OK)
|
||
|
return;
|
||
|
|
||
|
fs_packFiles += gi.number_entry;
|
||
|
|
||
|
fs_headerLongs = Z_Malloc( gi.number_entry * sizeof(int) );
|
||
|
|
||
|
unzGoToFirstFile(uf);
|
||
|
for (i = 0; i < gi.number_entry; i++)
|
||
|
{
|
||
|
char fullname [ MAX_ZPATH ];
|
||
|
char path [ MAX_QPATH ];
|
||
|
char name [ MAX_QPATH ];
|
||
|
char ext [ 16 ];
|
||
|
unsigned long pos;
|
||
|
|
||
|
err = unzGetCurrentFileInfo ( uf, &file_info, fullname, sizeof(fullname), NULL, 0, NULL, 0);
|
||
|
if (err != UNZ_OK) {
|
||
|
break;
|
||
|
}
|
||
|
if (file_info.uncompressed_size > 0) {
|
||
|
fs_headerLongs[fs_numHeaderLongs++] = LittleLong(file_info.crc);
|
||
|
}
|
||
|
unzGetCurrentFileInfoPosition ( uf, &pos );
|
||
|
|
||
|
Sys_SplitPath( fullname, path, sizeof(path), name, sizeof(name), ext, sizeof(ext) );
|
||
|
|
||
|
sql_bindtext( &com_db, 2, path );
|
||
|
sql_bindtext( &com_db, 3, name );
|
||
|
sql_bindtext( &com_db, 4, ext );
|
||
|
sql_bindint ( &com_db, 5, file_info.uncompressed_size );
|
||
|
sql_bindint ( &com_db, 6, (int)pos );
|
||
|
|
||
|
sql_step ( &com_db );
|
||
|
|
||
|
unzGoToNextFile(uf);
|
||
|
}
|
||
|
|
||
|
checksum = Com_BlockChecksum( fs_headerLongs, 4 * fs_numHeaderLongs );
|
||
|
checksum = LittleLong( checksum );
|
||
|
|
||
|
Z_Free(fs_headerLongs);
|
||
|
|
||
|
// create entry in pak table
|
||
|
sql_prepare ( &com_db, "INSERT INTO pakfiles(id,handle,name,checksum) VALUES(?1,?2,$3,?4);" );
|
||
|
sql_bindint ( &com_db, 1, pak_id );
|
||
|
sql_bindint ( &com_db, 2, (int)uf );
|
||
|
sql_bindtext( &com_db, 3, filename );
|
||
|
sql_bindint ( &com_db, 4, checksum );
|
||
|
sql_step ( &com_db );
|
||
|
sql_done ( &com_db );
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
===============
|
||
|
FS_AddZipFile
|
||
|
|
||
|
adds the contents of the zip file to FAT. this function is used for zip files that are outside
|
||
|
of the search paths
|
||
|
|
||
|
===============
|
||
|
*/
|
||
|
void FS_AddZipFile( const char * filename, int length, qboolean mount ) {
|
||
|
|
||
|
char ext[ 16 ];
|
||
|
char name[ MAX_ZPATH ];
|
||
|
char path[ MAX_ZPATH ];
|
||
|
int path_id;
|
||
|
|
||
|
Sys_SplitPath( filename, path, MAX_ZPATH, name, MAX_ZPATH, ext, 16 );
|
||
|
FS_ReplaceSeparators( path );
|
||
|
|
||
|
sql_prepare ( &com_db, "SELECT id FROM paths SEARCH path $1;" );
|
||
|
sql_bindtext( &com_db, 1, path );
|
||
|
|
||
|
// pak files can only exist in the root of one of the search paths
|
||
|
if ( sql_step( &com_db ) ) {
|
||
|
path_id = sql_columnasint( &com_db, 0 );
|
||
|
} else {
|
||
|
path_id = 1;
|
||
|
}
|
||
|
|
||
|
sql_done ( &com_db );
|
||
|
|
||
|
sql_prepare ( &com_db, "UPDATE OR INSERT files SET id=#+1, path_id=?1, path=$2,name=$3,ext=$4, length=?5, offset=?6, pak_id=?7, fullname=path||name||ext SEARCH name $3 WHERE path like $2 AND ext like $4;" );
|
||
|
sql_bindint ( &com_db, 1, path_id );
|
||
|
sql_bindtext( &com_db, 2, "" );
|
||
|
sql_bindtext( &com_db, 3, name );
|
||
|
sql_bindtext( &com_db, 4, ext );
|
||
|
sql_bindint ( &com_db, 5, length );
|
||
|
sql_bindint ( &com_db, 6, 0 );
|
||
|
sql_bindint ( &com_db, 7, -1 );
|
||
|
|
||
|
sql_step ( &com_db );
|
||
|
|
||
|
if ( mount == qtrue ) {
|
||
|
FS_LoadZipFile( filename, name, ext );
|
||
|
}
|
||
|
|
||
|
sql_done ( &com_db );
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
===============
|
||
|
FS_ListFiles
|
||
|
|
||
|
Returns a uniqued list of files that match the given criteria
|
||
|
from all search paths
|
||
|
===============
|
||
|
*/
|
||
|
#define MAX_LISTFILES_BUFFER_SIZE 8192
|
||
|
char **Q_EXTERNAL_CALL FS_ListFiles( const char *path, const char *extension, int *numfiles ) {
|
||
|
|
||
|
char * buffer = Z_Malloc( MAX_LISTFILES_BUFFER_SIZE );
|
||
|
|
||
|
if ( !fs_initialized ) {
|
||
|
Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
|
||
|
}
|
||
|
|
||
|
if ( !path ) {
|
||
|
*numfiles = 0;
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
if ( !extension ) {
|
||
|
extension = "";
|
||
|
}
|
||
|
|
||
|
*numfiles = sql_select( &com_db, "SELECT fullname FROM files SEARCH path $1 WHERE (ext like $2) OR ($2 like '');", buffer, buffer, MAX_LISTFILES_BUFFER_SIZE, path, extension );
|
||
|
|
||
|
if ( *numfiles == 0 ) {
|
||
|
Z_Free( buffer );
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
return (char**)buffer;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
=================
|
||
|
FS_FreeFileList
|
||
|
=================
|
||
|
*/
|
||
|
void Q_EXTERNAL_CALL FS_FreeFileList( char **list ) {
|
||
|
|
||
|
if ( !fs_initialized ) {
|
||
|
Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
|
||
|
}
|
||
|
|
||
|
if ( !list ) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
Z_Free( list );
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
================
|
||
|
FS_GetFileList
|
||
|
================
|
||
|
*/
|
||
|
int FS_GetFileList( const char *path, const char *extension, char * segment, char * buffer, int size ) {
|
||
|
|
||
|
return sql_select( &com_db, "SELECT fullname FROM files SEARCH path $1 WHERE (ext like $2) OR ($2 like '');", segment, buffer, size, path, extension );
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
================
|
||
|
FS_Shutdown
|
||
|
|
||
|
Frees all resources and closes all files
|
||
|
================
|
||
|
*/
|
||
|
void FS_Shutdown( qboolean closemfp ) {
|
||
|
|
||
|
int i;
|
||
|
|
||
|
// close all files that are in the file system.
|
||
|
if ( closemfp ) {
|
||
|
for(i = 0; i < MAX_FILE_HANDLES; i++) {
|
||
|
if ( fsh[i].handleFiles.file.o != 0 ) {
|
||
|
FS_FCloseFile(i);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// free everything
|
||
|
sql_prepare ( &com_db, "SELECT handle FROM pakfiles;" );
|
||
|
while ( sql_step( &com_db ) ) {
|
||
|
|
||
|
unzFile handle = (unzFile)sql_columnasint( &com_db, 0 );
|
||
|
unzClose(handle);
|
||
|
}
|
||
|
sql_done ( &com_db );
|
||
|
|
||
|
#ifdef DEVELOPER
|
||
|
Cmd_RemoveCommand( "fsdump" );
|
||
|
Cmd_RemoveCommand( "fsmissing" );
|
||
|
Cmd_RemoveCommand( "sql_f" );
|
||
|
#endif
|
||
|
|
||
|
// any FS_ calls will now be an error until reinitialized
|
||
|
fs_initialized = 0;
|
||
|
}
|
||
|
|
||
|
static void add_directory( const char * base, const char * path ) {
|
||
|
|
||
|
if ( base && base[0] ) {
|
||
|
Com_Printf( "mounting '%s/%s'...\n", base, path );
|
||
|
sql_bindtext ( &com_db, 1, base );
|
||
|
sql_bindtext ( &com_db, 2, path );
|
||
|
sql_step ( &com_db );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void Q_EXTERNAL_CALL FS_sql_f( void )
|
||
|
{
|
||
|
sql_prompt( &com_db, Cmd_Argv(1) );
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
================
|
||
|
FS_Dump_f
|
||
|
|
||
|
Save the console contents out to a file
|
||
|
================
|
||
|
*/
|
||
|
#ifdef DEVELOPER
|
||
|
void Q_EXTERNAL_CALL FS_Dump_f (void)
|
||
|
{
|
||
|
fileHandle_t f;
|
||
|
char * filename;
|
||
|
|
||
|
if (Cmd_Argc() != 2)
|
||
|
{
|
||
|
char * boss = Cvar_VariableString( "boss" );
|
||
|
|
||
|
if ( boss && boss[0] ) {
|
||
|
filename = va( "%s_%s_%s.dump", Cvar_VariableString( "sv_missionname" ), Cvar_VariableString( "mapname" ), boss );
|
||
|
} else {
|
||
|
filename = va( "%s_%s.dump", Cvar_VariableString( "sv_missionname" ), Cvar_VariableString( "mapname" ) );
|
||
|
}
|
||
|
} else {
|
||
|
filename = Cmd_Argv(1);
|
||
|
}
|
||
|
|
||
|
Com_Printf ("Dumped file access log to %s.\n", filename );
|
||
|
|
||
|
f = FS_FOpenFileWrite( filename );
|
||
|
if (!f)
|
||
|
{
|
||
|
Com_Printf ("ERROR: couldn't open.\n");
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
sql_prepare ( &com_db, "SELECT id FROM access SEARCH path UNIQUE;" );
|
||
|
|
||
|
while( sql_step( &com_db ) ) {
|
||
|
|
||
|
int file_id = sql_columnasint( &com_db, 0 );
|
||
|
int len;
|
||
|
char buffer[ 1024 ];
|
||
|
|
||
|
sql_prepare( &com_db, "SELECT access.id[?1]^path;" );
|
||
|
sql_bindint( &com_db, 1, file_id );
|
||
|
|
||
|
if ( sql_step( &com_db ) ) {
|
||
|
const char * file = sql_columnastext ( &com_db, 0 );
|
||
|
len = Com_sprintf( buffer, sizeof(buffer), "%s\n", file );
|
||
|
FS_Write( buffer, len, f );
|
||
|
}
|
||
|
sql_done( &com_db );
|
||
|
}
|
||
|
|
||
|
sql_done ( &com_db );
|
||
|
|
||
|
FS_FCloseFile( f );
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
/*
|
||
|
================
|
||
|
FS_Missing_f
|
||
|
|
||
|
Dump files we tried to load but couldn't to a file
|
||
|
================
|
||
|
*/
|
||
|
#ifdef DEVELOPER
|
||
|
void Q_EXTERNAL_CALL FS_Missing_f (void)
|
||
|
{
|
||
|
fileHandle_t f;
|
||
|
|
||
|
if (Cmd_Argc() != 2)
|
||
|
{
|
||
|
Com_Printf ("usage: fsmissing <filename>\n");
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
Com_Printf ("Dumped file missing log to %s.\n", Cmd_Argv(1) );
|
||
|
|
||
|
f = FS_FOpenFileWrite( Cmd_Argv( 1 ) );
|
||
|
if (!f)
|
||
|
{
|
||
|
Com_Printf ("ERROR: couldn't open.\n");
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
sql_prepare ( &com_db, "SELECT path FROM missing SEARCH path;" );
|
||
|
|
||
|
while( sql_step( &com_db ) ) {
|
||
|
|
||
|
const char * file = sql_columnastext ( &com_db, 0 );
|
||
|
|
||
|
int len;
|
||
|
char buffer[ 1024 ];
|
||
|
|
||
|
len = Com_sprintf( buffer, sizeof(buffer), "%s\n", file );
|
||
|
FS_Write( buffer, len, f );
|
||
|
}
|
||
|
|
||
|
sql_done ( &com_db );
|
||
|
|
||
|
FS_FCloseFile( f );
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
|
||
|
/*
|
||
|
================
|
||
|
FS_Startup
|
||
|
================
|
||
|
*/
|
||
|
static void FS_Startup( const char *game ) {
|
||
|
|
||
|
char tmp[ MAX_OSPATH ];
|
||
|
|
||
|
Com_Printf( "----- FS_Startup -----\n" );
|
||
|
|
||
|
sql_exec( &com_db, "CREATE TABLE paths "
|
||
|
"("
|
||
|
"id INTEGER, "
|
||
|
"path STRING "
|
||
|
");"
|
||
|
"CREATE TABLE files "
|
||
|
"("
|
||
|
"id INTEGER, "
|
||
|
"path_id INTEGER, "
|
||
|
"fullname STRING, "
|
||
|
"path STRING, "
|
||
|
"name STRING, "
|
||
|
"ext STRING, "
|
||
|
"offset INTEGER, "
|
||
|
"length INTEGER, "
|
||
|
"pak_id INTEGER "
|
||
|
");"
|
||
|
|
||
|
"CREATE TABLE pakfiles "
|
||
|
"("
|
||
|
"id INTEGER, "
|
||
|
"handle INTEGER, "
|
||
|
"name STRING, "
|
||
|
"checksum INTEGER "
|
||
|
");"
|
||
|
|
||
|
"CREATE TABLE IF NOT EXISTS openfiles "
|
||
|
"("
|
||
|
"f INTEGER, " // handle to file
|
||
|
"time INTEGER, " // time file was opened
|
||
|
"xfer INTEGER, " // number of bytes written
|
||
|
"pak INTEGER, " // this is a pak file being downloaded
|
||
|
"length INTEGER, " // the intended length of this file
|
||
|
"path INTEGER, "
|
||
|
"name STRING "
|
||
|
");"
|
||
|
|
||
|
#ifdef DEVELOPER
|
||
|
"CREATE TABLE access "
|
||
|
"("
|
||
|
"id INTEGER, "
|
||
|
"time INTEGER, "
|
||
|
"path STRING "
|
||
|
");"
|
||
|
"CREATE TABLE missing "
|
||
|
"("
|
||
|
"path STRING "
|
||
|
");"
|
||
|
#endif
|
||
|
);
|
||
|
|
||
|
fs_initialized = 1;
|
||
|
|
||
|
fs_debug = Cvar_Get( "fs_debug", "0", 0 );
|
||
|
fs_cdpath = Cvar_Get( "fs_cdpath", FS_ReplaceSeparators( Sys_DefaultCDPath(tmp, sizeof( tmp )) ), CVAR_INIT );
|
||
|
fs_basepath = Cvar_Get( "fs_basepath", FS_ReplaceSeparators( Sys_DefaultInstallPath(tmp, sizeof(tmp)) ), CVAR_INIT );
|
||
|
fs_basegame = Cvar_Get( "fs_basegame", "", CVAR_INIT );
|
||
|
fs_homepath = Cvar_Get( "fs_homepath", FS_ReplaceSeparators( Sys_DefaultHomePath( tmp, sizeof(tmp) ) ), CVAR_INIT );
|
||
|
fs_gamedirvar = Cvar_Get( "fs_game", "", CVAR_INIT|CVAR_SYSTEMINFO );
|
||
|
fs_restrict = Cvar_Get( "fs_restrict", "", CVAR_INIT );
|
||
|
fs_restart = Cvar_Get( "fs_restart", "0", 0 );
|
||
|
|
||
|
fs_packFiles = 0; //reset counter
|
||
|
|
||
|
//
|
||
|
// add paths to search table
|
||
|
//
|
||
|
sql_prepare ( &com_db, "INSERT INTO paths(id,path) VALUES(#+1,$1||'/'||$2||'/');" );
|
||
|
|
||
|
add_directory( fs_cdpath->string, game );
|
||
|
add_directory( fs_basepath->string, game );
|
||
|
add_directory( fs_homepath->string, game );
|
||
|
|
||
|
// check for additional game folder for mods
|
||
|
if ( fs_gamedirvar->string[0] && Q_stricmp( fs_gamedirvar->string, game ) ) {
|
||
|
add_directory( fs_basepath->string, fs_gamedirvar->string );
|
||
|
Q_strncpyz( fs_gamedir, fs_gamedirvar->string, sizeof(fs_gamedir) );
|
||
|
} else {
|
||
|
Q_strncpyz( fs_gamedir, game, sizeof(fs_gamedir) );
|
||
|
}
|
||
|
|
||
|
sql_done ( &com_db );
|
||
|
|
||
|
fs_gamedirvar->modified = qfalse; // We just loaded, it's not modified
|
||
|
|
||
|
//
|
||
|
// scan search paths and create file table
|
||
|
//
|
||
|
sql_prepare( &com_db, "SELECT id, path FROM paths;" );
|
||
|
|
||
|
while( sql_step( &com_db ) ) {
|
||
|
|
||
|
const char * path_name;
|
||
|
int path_id;
|
||
|
|
||
|
path_id = sql_columnasint ( &com_db, 0 );
|
||
|
path_name = sql_columnastext ( &com_db, 1 );
|
||
|
|
||
|
sql_prepare ( &com_db, "UPDATE OR INSERT files SET id=#+1, path_id=?1, path=$2,name=$3,ext=$4, length=?5, offset=?6, pak_id=?7, fullname=path||name||ext SEARCH name $3 WHERE path like $2 AND ext like $4;" );
|
||
|
sql_bindint ( &com_db, 1, path_id );
|
||
|
sql_bindint ( &com_db, 6, 0 );
|
||
|
sql_bindint ( &com_db, 7, -1 );
|
||
|
|
||
|
Sys_AddFiles( path_name, "", ".hwp" );
|
||
|
|
||
|
// add any pak files found in the root, that are not being downloaded
|
||
|
{
|
||
|
char buffer[ 4096 ];
|
||
|
char ** paks;
|
||
|
int i,n;
|
||
|
|
||
|
n = sql_select( &com_db, "SELECT name, ext,path||name||ext FROM files SEARCH path_id ?1 SORT 1 WHERE ext like '.hwp' AND openfiles.name[ path||name||ext ].f = -1;", buffer, buffer, sizeof(buffer), path_id );
|
||
|
paks = (char**)buffer;
|
||
|
for ( i=0; i<n; i++ ) {
|
||
|
const char * name = paks[ i*3+0 ];
|
||
|
const char * ext = paks[ i*3+1 ];
|
||
|
|
||
|
#ifdef USE_WEBHOST
|
||
|
if ( !FS_PakIsPure( paks[ i*3+2 ] ) ) {
|
||
|
continue;
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
FS_LoadZipFile( va( "%s/%s%s", path_name, name, ext ), name, ext );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
Sys_AddFiles( path_name, "", NULL );
|
||
|
|
||
|
sql_done ( &com_db );
|
||
|
}
|
||
|
|
||
|
sql_done( &com_db );
|
||
|
|
||
|
#ifdef DEVELOPER
|
||
|
Cmd_AddCommand( "fsdump", FS_Dump_f );
|
||
|
Cmd_AddCommand( "fsmissing", FS_Missing_f );
|
||
|
Cmd_AddCommand( "sql_f", FS_sql_f );
|
||
|
#endif
|
||
|
|
||
|
Com_Printf( "----------------------\n" );
|
||
|
Com_Printf( "%d files in pk3 files\n", fs_packFiles );
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
===================
|
||
|
FS_SetRestrictions
|
||
|
|
||
|
Looks for product keys and restricts media add on ability
|
||
|
if the full version is not found
|
||
|
===================
|
||
|
*/
|
||
|
static void FS_SetRestrictions( void ) {
|
||
|
|
||
|
#ifndef PRE_RELEASE_DEMO
|
||
|
char *productId;
|
||
|
|
||
|
// if fs_restrict is set, don't even look for the id file,
|
||
|
// which allows the demo release to be tested even if
|
||
|
// the full game is present
|
||
|
if ( !fs_restrict->integer ) {
|
||
|
// look for the full game id
|
||
|
FS_ReadFile( "productid.txt", (void **)&productId );
|
||
|
if ( productId ) {
|
||
|
// check against the hardcoded string
|
||
|
int seed, i;
|
||
|
|
||
|
seed = 5000;
|
||
|
for ( i = 0 ; i < sizeof( fs_scrambledProductId ) ; i++ ) {
|
||
|
if ( ( fs_scrambledProductId[i] ^ (seed&255) ) != productId[i] ) {
|
||
|
break;
|
||
|
}
|
||
|
seed = (69069 * seed + 1);
|
||
|
}
|
||
|
|
||
|
FS_FreeFile( productId );
|
||
|
|
||
|
if ( i == sizeof( fs_scrambledProductId ) ) {
|
||
|
return; // no restrictions
|
||
|
}
|
||
|
Com_Error( ERR_FATAL, "Invalid product identification" );
|
||
|
}
|
||
|
}
|
||
|
#endif
|
||
|
Cvar_Set( "fs_restrict", "1" );
|
||
|
|
||
|
Com_Printf( "\nRunning in restricted demo mode.\n\n" );
|
||
|
|
||
|
// restart the filesystem with just the demo directory
|
||
|
FS_Shutdown(qfalse);
|
||
|
FS_Startup( DEMOGAME );
|
||
|
|
||
|
sql_prepare( &com_db, "SELECT checksum FROM pakfiles;" );
|
||
|
while ( sql_step( &com_db ) ) {
|
||
|
int checksum = sql_columnasint( &com_db, 0 );
|
||
|
// a tiny attempt to keep the checksum from being scannable from the exe
|
||
|
if ( (checksum ^ 0x02261994u) != (DEMO_PAK_CHECKSUM ^ 0x02261994u) ) {
|
||
|
Com_Error( ERR_FATAL, "Corrupted pak0.pk3: %u", checksum );
|
||
|
}
|
||
|
}
|
||
|
sql_done( &com_db );
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
================
|
||
|
FS_InitFilesystem
|
||
|
|
||
|
Called only at inital startup, not when the filesystem
|
||
|
is resetting due to a game change
|
||
|
================
|
||
|
*/
|
||
|
void FS_InitFilesystem( void ) {
|
||
|
// allow command line parms to override our defaults
|
||
|
// we have to specially handle this, because normal command
|
||
|
// line variable sets don't happen until after the filesystem
|
||
|
// has already been initialized
|
||
|
Com_StartupVariable( "fs_cdpath" );
|
||
|
Com_StartupVariable( "fs_basepath" );
|
||
|
Com_StartupVariable( "fs_homepath" );
|
||
|
Com_StartupVariable( "fs_game" );
|
||
|
Com_StartupVariable( "fs_restrict" );
|
||
|
|
||
|
Cvar_Set( "fs_noconfig", "0" );
|
||
|
|
||
|
// try to start up normally
|
||
|
FS_Startup( BASEGAME );
|
||
|
|
||
|
// see if we are going to allow add-ons
|
||
|
#ifndef DEVELOPER
|
||
|
FS_SetRestrictions();
|
||
|
#endif
|
||
|
|
||
|
// if we can't find default.cfg, assume that the paths are
|
||
|
// busted and error out now, rather than getting an unreadable
|
||
|
// graphics screen when the font fails to load
|
||
|
if ( FS_ReadFile( "default.cfg", NULL ) <= 0 ) {
|
||
|
#ifndef USE_BOOTWITHNOFILES
|
||
|
Com_Error( ERR_FATAL, "Couldn't load default.cfg" );
|
||
|
#endif
|
||
|
// bk001208 - SafeMode see below, FIXME?
|
||
|
Cvar_Set( "fs_noconfig", "1" );
|
||
|
}
|
||
|
|
||
|
// bk001208 - SafeMode see below, FIXME?
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
================
|
||
|
FS_Restart
|
||
|
================
|
||
|
*/
|
||
|
void FS_Restart( int checksumFeed )
|
||
|
{
|
||
|
|
||
|
// free anything we currently have loaded
|
||
|
FS_Shutdown(qfalse);
|
||
|
|
||
|
// set the checksum feed
|
||
|
fs_checksumFeed = checksumFeed;
|
||
|
|
||
|
// try to start up normally
|
||
|
FS_Startup( BASEGAME );
|
||
|
|
||
|
// see if we are going to allow add-ons
|
||
|
#ifndef DEVELOPER
|
||
|
FS_SetRestrictions();
|
||
|
#endif
|
||
|
|
||
|
// if we can't find default.cfg, assume that the paths are
|
||
|
// busted and error out now, rather than getting an unreadable
|
||
|
// graphics screen when the font fails to load
|
||
|
if ( FS_ReadFile( "default.cfg", NULL ) <= 0 ) {
|
||
|
#ifndef USE_BOOTWITHNOFILES
|
||
|
Com_Error( ERR_FATAL, "Couldn't load default.cfg" );
|
||
|
#endif
|
||
|
Cvar_Set( "fs_noconfig", "1" );
|
||
|
} else {
|
||
|
Cvar_Set("fs_noconfig", "0");
|
||
|
}
|
||
|
|
||
|
Cbuf_AddText ( "exec default.cfg\n" );
|
||
|
|
||
|
// skip the st.cfg if "safe" is on the command line
|
||
|
if ( !Com_SafeMode() ) {
|
||
|
Cbuf_AddText ("exec st.cfg\n");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
=================
|
||
|
FS_ConditionalRestart
|
||
|
restart if necessary
|
||
|
=================
|
||
|
*/
|
||
|
qboolean FS_ConditionalRestart( int checksumFeed ) {
|
||
|
if( fs_gamedirvar->modified || fs_restart->integer || checksumFeed != fs_checksumFeed ) {
|
||
|
FS_Restart( checksumFeed );
|
||
|
Cvar_Set( "fs_restart", "0" );
|
||
|
return qtrue;
|
||
|
}
|
||
|
return qfalse;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
========================================================================================
|
||
|
|
||
|
Handle based file calls for virtual machines
|
||
|
|
||
|
========================================================================================
|
||
|
*/
|
||
|
|
||
|
int Q_EXTERNAL_CALL FS_FOpenFileByMode( const char *qpath, fileHandle_t *f, fsMode_t mode )
|
||
|
{
|
||
|
int r;
|
||
|
|
||
|
switch( mode ) {
|
||
|
case FS_READ:
|
||
|
r = FS_FOpenFileRead( qpath, f, qfalse );
|
||
|
break;
|
||
|
case FS_WRITE:
|
||
|
*f = FS_FOpenFileWrite( qpath );
|
||
|
r = 0;
|
||
|
if (*f == 0) {
|
||
|
r = -1;
|
||
|
}
|
||
|
break;
|
||
|
case FS_READ_DIRECT:
|
||
|
r = FS_FOpenFileDirect( qpath, f );
|
||
|
break;
|
||
|
|
||
|
case FS_APPEND:
|
||
|
r = FS_FOpenFileAppend( qpath, f );
|
||
|
if (*f == 0) {
|
||
|
r = -1;
|
||
|
}
|
||
|
break;
|
||
|
#ifdef DEVELOPER
|
||
|
case FS_UPDATE:
|
||
|
*f = FS_FOpenFileUpdate( qpath, &r );
|
||
|
r = 0;
|
||
|
if (*f == 0) {
|
||
|
r = -1;
|
||
|
}
|
||
|
break;
|
||
|
#endif
|
||
|
default:
|
||
|
Com_Error( ERR_FATAL, "FSH_FOpenFile: bad mode" );
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
if (!f) {
|
||
|
return r;
|
||
|
}
|
||
|
|
||
|
return r;
|
||
|
}
|
||
|
|
||
|
int FS_FTell( fileHandle_t f ) {
|
||
|
int pos;
|
||
|
if (fsh[f].zipFile == qtrue) {
|
||
|
pos = unztell(fsh[f].handleFiles.file.z);
|
||
|
} else {
|
||
|
pos = ftell(fsh[f].handleFiles.file.o);
|
||
|
}
|
||
|
return pos;
|
||
|
}
|
||
|
|
||
|
void FS_Flush( fileHandle_t f ) {
|
||
|
fflush(fsh[f].handleFiles.file.o);
|
||
|
}
|
||
|
|
||
|
void FS_FilenameCompletion( const char *dir, const char *ext, qboolean stripExt, void(*callback)(const char *s) ) {
|
||
|
|
||
|
sql_prepare ( &com_db, "SELECT name,name||ext FROM files SEARCH path $1 WHERE (ext like $2) OR ($2 like '');" );
|
||
|
sql_bindtext( &com_db, 1, dir );
|
||
|
sql_bindtext( &com_db, 2, ext );
|
||
|
|
||
|
while( sql_step( &com_db ) ) {
|
||
|
callback( sql_columnastext( &com_db, (stripExt)?0:1 ) );
|
||
|
}
|
||
|
|
||
|
sql_done ( &com_db );
|
||
|
}
|