123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606 |
- /*****************************************************************************
- * name: files.c
- *
- * desc: handle based filesystem for Quake III Arena
- *
- *
- *****************************************************************************/
- #include "../game/q_shared.h"
- #include "qcommon.h"
- #include "files.h"
- /*
- =============================================================================
- 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_QPATH is 64 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 relative 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 ds_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 "base game" is the directory under the paths where data comes from by default, and
- can be either "base" or "demo".
- 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 "asset0.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 demo/asset0.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 jaconfig.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.
- (If set to 2, copying will only take place if the two filetimes are NOT EQUAL)
- The qpath "sound/newstuff/test.wav" would be searched for in the following places:
- 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
- base path + base game's zip files
- base path + base game's directory
- cd path + base game's zip files
- cd path + base game's directory
- server download, to be written to base 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 jaconfig.cfg isn't present in it,
- or configs will never get loaded from disk!
- todo:
- downloading (outside fs?)
- game directory passing and restarting
- =============================================================================
- */
- // 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
- //#define PRE_RELEASE_DEMO
- char fs_gamedir[MAX_OSPATH]; // this will be a single file name with no separators
- cvar_t *fs_debug;
- cvar_t *fs_basepath;
- cvar_t *fs_cdpath;
- cvar_t *fs_copyfiles;
- cvar_t *fs_gamedirvar;
- cvar_t *fs_restrict;
- searchpath_t *fs_searchpaths;
- int fs_readCount; // total bytes read
- int fs_loadCount; // total files read
- int fs_packFiles; // total number of files in packs
- qboolean initialized = qfalse;
- fileHandleData_t fsh[MAX_FILE_HANDLES];
- void FS_CheckInit(void)
- {
- if (!initialized)
- {
- Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
- }
- }
- /*
- ==============
- FS_Initialized
- ==============
- */
- qboolean FS_Initialized() {
- return (qboolean)(fs_searchpaths != NULL);
- }
- fileHandle_t FS_HandleForFile(void) {
- int i;
- for ( i = 1 ; i < MAX_FILE_HANDLES ; i++ ) {
- #ifdef _XBOX
- if ( !fsh[i].used ) {
- #else
- if ( fsh[i].handleFiles.file.o == NULL ) {
- #endif
- return i;
- }
- }
- Com_Printf( "FS_HandleForFile: all handles taken:\n" );
- for ( i = 1 ; i < MAX_FILE_HANDLES ; i++ ) {
- Com_Printf( "%d. %s\n", i, fsh[i].name);
- }
- Com_Error( ERR_DROP, "FS_HandleForFile: none free" );
- return 0;
- }
- /*
- ====================
- FS_ReplaceSeparators
- Fix things up differently for win/unix/mac
- ====================
- */
- void FS_ReplaceSeparators( char *path ) {
- char *s;
- for ( s = path ; *s ; s++ ) {
- if ( *s == '/' || *s == '\\' ) {
- *s = PATH_SEP;
- }
- }
- }
- /*
- ===================
- FS_BuildOSPath
- Qpath may have either forward or backwards slashes
- ===================
- */
- char *FS_BuildOSPath( const char *qpath )
- {
- char temp[MAX_OSPATH];
- static char ospath[2][MAX_OSPATH];
- static int toggle;
-
- toggle ^= 1; // flip-flop to allow two returns without clash
- Com_sprintf( temp, sizeof(temp), "/%s/%s", fs_gamedirvar->string, qpath );
- FS_ReplaceSeparators( temp );
- Com_sprintf( ospath[toggle], sizeof( ospath[0] ), "%s%s",
- fs_basepath->string, temp );
-
- return ospath[toggle];
- }
- char *FS_BuildOSPathUnMapped( const char *qpath )
- {
- char temp[MAX_OSPATH];
- static char ospath[2][MAX_OSPATH];
- static int toggle;
-
- toggle ^= 1; // flip-flop to allow two returns without clash
- Com_sprintf( temp, sizeof(temp), "/%s/%s", fs_gamedirvar->string, qpath );
- FS_ReplaceSeparators( temp );
- Com_sprintf( ospath[toggle], sizeof( ospath[0] ), "%s%s",
- "d:", temp );
-
- return ospath[toggle];
- }
- #ifndef _XBOX
- char *FS_BuildOSPath( const char *base, const char *game, const char *qpath ) {
- char temp[MAX_OSPATH];
- static char ospath[4][MAX_OSPATH];
- static int toggle;
-
- toggle = (++toggle)&3; // allows four returns without clash (increased from 2 during fs_copyfiles 2 enhancement)
- Com_sprintf( temp, sizeof(temp), "/%s/%s", game, qpath );
- FS_ReplaceSeparators( temp );
- Com_sprintf( ospath[toggle], sizeof( ospath[0] ), "%s%s", base, temp );
-
- return ospath[toggle];
- }
- #endif
- /*
- ============
- FS_CreatePath
- Creates any directories needed to store the given filename
- ============
- */
- void 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;
- }
- strlwr(OSPath);
- for (ofs = OSPath+1 ; *ofs ; ofs++) {
- if (*ofs == PATH_SEP) {
- // create the directory
- *ofs = 0;
- Sys_Mkdir (OSPath);
- *ofs = PATH_SEP;
- }
- }
- }
- /*
- ===========
- FS_SV_FOpenFileRead
- ===========
- */
- int FS_SV_FOpenFileRead( const char *filename, fileHandle_t *fp ) {
- char *ospath;
- fileHandle_t f;
- if ( !fs_searchpaths ) {
- Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
- }
- f = FS_HandleForFile();
- fsh[f].zipFile = qfalse;
- Q_strncpyz( fsh[f].name, filename, sizeof( fsh[f].name ) );
- // don't let sound stutter
- S_ClearSoundBuffer();
- #ifdef _XBOX
- ospath = FS_BuildOSPath( filename );
- #else
- ospath = FS_BuildOSPath( fs_basepath->string, filename, "" );
- #endif
- // remove trailing slash
- ospath[strlen(ospath)-1] = '\0';
- if ( fs_debug->integer ) {
- Com_Printf( "FS_SV_FOpenFileRead: %s\n", ospath );
- }
- fsh[f].handleFiles.file.o = fopen( ospath, "rb" );
- fsh[f].handleSync = qfalse;
- if (!fsh[f].handleFiles.file.o) {
- f = 0;
- }
-
- *fp = f;
- if (f) {
- return FS_filelength(f);
- }
- return 0;
- }
- /*
- ===========
- FS_FOpenFileAppend
- ===========
- */
- fileHandle_t FS_FOpenFileAppend( const char *filename ) {
- char *ospath;
- fileHandle_t f;
- if ( !fs_searchpaths ) {
- Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
- }
- f = FS_HandleForFile();
- fsh[f].zipFile = qfalse;
- Q_strncpyz( fsh[f].name, filename, sizeof( fsh[f].name ) );
- // don't let sound stutter
- S_ClearSoundBuffer();
- #ifdef _XBOX
- ospath = FS_BuildOSPath( filename );
- #else
- ospath = FS_BuildOSPath( fs_basepath->string, fs_gamedir, filename );
- #endif
- if ( fs_debug->integer ) {
- Com_Printf( "FS_FOpenFileAppend: %s\n", ospath );
- }
- FS_CreatePath( ospath );
- fsh[f].handleFiles.file.o = fopen( ospath, "ab" );
- fsh[f].handleSync = qfalse;
- if (!fsh[f].handleFiles.file.o) {
- f = 0;
- }
- return f;
- }
- /*
- ===========
- FS_FilenameCompare
- Ignore case and seprator char distinctions
- ===========
- */
- qboolean FS_FilenameCompare( const char *s1, const char *s2 ) {
- int c1, c2;
-
- do {
- c1 = *s1++;
- c2 = *s2++;
- if ( Q_islower(c1) ) {
- c1 -= ('a' - 'A');
- }
- if ( Q_islower(c2) ) {
- c2 -= ('a' - 'A');
- }
- if ( c1 == '\\' || c1 == ':' ) {
- c1 = '/';
- }
- if ( c2 == '\\' || c2 == ':' ) {
- c2 = '/';
- }
-
- if (c1 != c2) {
- return -1; // strings not equal
- }
- } while (c1);
-
- return 0; // strings are equal
- }
-
- #define MAXPRINTMSG 4096
- void QDECL FS_Printf( fileHandle_t h, const char *fmt, ... ) {
- va_list argptr;
- char msg[MAXPRINTMSG];
- va_start (argptr,fmt);
- vsprintf (msg,fmt,argptr);
- va_end (argptr);
- FS_Write(msg, strlen(msg), h);
- }
- /*
- ============
- FS_WriteFile
- Filename are relative to the quake search path
- ============
- */
- void FS_WriteFile( const char *qpath, const void *buffer, int size ) {
- fileHandle_t f;
- if ( !fs_searchpaths ) {
- 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 );
- }
- /*
- ================
- FS_Shutdown
- Frees all resources and closes all files
- ================
- */
- void FS_Shutdown( void ) {
- searchpath_t *p, *next;
- int i;
- for(i = 0; i < MAX_FILE_HANDLES; i++) {
- if (fsh[i].fileSize) {
- FS_FCloseFile(i);
- }
- }
- // free everything
- for ( p = fs_searchpaths ; p ; p = next ) {
- next = p->next;
- if ( p->pack ) {
- #ifndef _XBOX
- unzClose(p->pack->handle);
- #endif
- Z_Free( p->pack->buildBuffer );
- Z_Free( p->pack );
- }
- if ( p->dir ) {
- Z_Free( p->dir );
- }
- Z_Free( p );
- }
- // any FS_ calls will now be an error until reinitialized
- fs_searchpaths = NULL;
- Cmd_RemoveCommand( "path" );
- Cmd_RemoveCommand( "dir" );
- Cmd_RemoveCommand( "touchFile" );
- initialized = qfalse;
- }
- /*
- ================
- 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 don't have to specially handle this, because normal command
- // line variable sets happen before the filesystem
- // has been initialized
- //
- // UPDATE: BTO (VV)
- // 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_game" );
- Com_StartupVariable( "fs_copyfiles" );
- Com_StartupVariable( "fs_restrict" );
- // try to start up normally
- FS_Startup( BASEGAME );
- initialized = qtrue;
- // see if we are going to allow add-ons
- FS_SetRestrictions();
- // 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 ) {
- Com_Error( ERR_FATAL, "Couldn't load default.cfg" );
- }
- }
- void FS_Flush( fileHandle_t f ) {
- fflush(fsh[f].handleFiles.file.o);
- }
|