diff --git a/engine/common/common.h b/engine/common/common.h index 3eae988a..bb9adff9 100644 --- a/engine/common/common.h +++ b/engine/common/common.h @@ -199,10 +199,13 @@ typedef enum #define MAX_STATIC_ENTITIES 3096 // static entities that moved on the client when level is spawn // filesystem flags -#define FS_STATIC_PATH 1 // FS_ClearSearchPath will be ignore this path -#define FS_NOWRITE_PATH 2 // default behavior - last added gamedir set as writedir. This flag disables it -#define FS_GAMEDIR_PATH 4 // just a marker for gamedir path -#define FS_CUSTOM_PATH 8 // custom directory +#define FS_STATIC_PATH ( 1U << 0 ) // FS_ClearSearchPath will be ignore this path +#define FS_NOWRITE_PATH ( 1U << 1 ) // default behavior - last added gamedir set as writedir. This flag disables it +#define FS_GAMEDIR_PATH ( 1U << 2 ) // just a marker for gamedir path +#define FS_CUSTOM_PATH ( 1U << 3 ) // custom directory +#define FS_GAMERODIR_PATH ( 1U << 4 ) // caseinsensitive + +#define FS_GAMEDIRONLY_SEARCH_FLAGS ( FS_GAMEDIR_PATH | FS_CUSTOM_PATH | FS_GAMERODIR_PATH ) #define GI SI.GameInfo #define FS_Gamedir() SI.GameInfo->gamefolder @@ -287,6 +290,8 @@ typedef struct gameinfo_s char game_dll_linux[64]; // custom path for game.dll char game_dll_osx[64]; // custom path for game.dll char client_lib[64]; // custom name of client library + + qboolean added; } gameinfo_t; typedef enum @@ -493,6 +498,7 @@ typedef struct host_parm_s qboolean renderinfo_changed; char rootdir[256]; // member root directory + char rodir[256]; // readonly root char gamefolder[MAX_QPATH]; // it's a default gamefolder byte *imagepool; // imagelib mempool byte *soundpool; // soundlib mempool @@ -520,8 +526,8 @@ void FS_Rescan( void ); void FS_Shutdown( void ); void FS_ClearSearchPath( void ); void FS_AllowDirectPaths( qboolean enable ); -void FS_AddGameDirectory( const char *dir, int flags ); -void FS_AddGameHierarchy( const char *dir, int flags ); +void FS_AddGameDirectory( const char *dir, uint flags ); +void FS_AddGameHierarchy( const char *dir, uint flags ); void FS_LoadGameInfo( const char *rootfolder ); void COM_FileBase( const char *in, char *out ); const char *COM_FileExtension( const char *in ); diff --git a/engine/common/filesystem.c b/engine/common/filesystem.c index 0a6ba6c3..c0d20ed2 100644 --- a/engine/common/filesystem.c +++ b/engine/common/filesystem.c @@ -330,7 +330,9 @@ static const char *FS_FixFileCase( const char *path ) if( !fs_caseinsensitive ) return path; - Q_snprintf( path2, sizeof( path2 ), "./%s", path ); + if( path[0] != '/' ) + Q_snprintf( path2, sizeof( path2 ), "./%s", path ); + else Q_strncpy( path2, path, PATH_MAX ); fname = Q_strrchr( path2, '/' ); @@ -465,9 +467,13 @@ void FS_Path_f( void ) else if( s->wad ) Con_Printf( "%s (%i files)", s->wad->filename, s->wad->numlumps ); else Con_Printf( "%s", s->filename ); - if( FBitSet( s->flags, FS_GAMEDIR_PATH )) - Con_Printf( " ^2gamedir^7\n" ); - else Con_Printf( "\n" ); + if( s->flags & FS_GAMERODIR_PATH ) Con_Printf( " ^2rodir^7" ); + if( s->flags & FS_GAMEDIR_PATH ) Con_Printf( " ^2gamedir^7" ); + if( s->flags & FS_CUSTOM_PATH ) Con_Printf( " ^2custom^7" ); + if( s->flags & FS_NOWRITE_PATH ) Con_Printf( " ^2nowrite^7" ); + if( s->flags & FS_STATIC_PATH ) Con_Printf( " ^2static^7" ); + + Con_Printf( "\n" ); } } @@ -703,7 +709,7 @@ Sets fs_writedir, adds the directory to the head of the path, then loads and adds pak1.pak pak2.pak ... ================ */ -void FS_AddGameDirectory( const char *dir, int flags ) +void FS_AddGameDirectory( const char *dir, uint flags ) { stringlist_t list; searchpath_t *search; @@ -756,11 +762,51 @@ void FS_AddGameDirectory( const char *dir, int flags ) FS_AddGameHierarchy ================ */ -void FS_AddGameHierarchy( const char *dir, int flags ) +void FS_AddGameHierarchy( const char *dir, uint flags ) { - // Add the common game directory - if( COM_CheckString( dir )) - FS_AddGameDirectory( va( "%s/", dir ), flags ); + int i; + qboolean isGameDir = flags & FS_GAMEDIR_PATH; + + GI->added = true; + + if( !COM_CheckString( dir )) + return; + + // add the common game directory + + // recursive gamedirs + // for example, czeror->czero->cstrike->valve + for( i = 0; i < SI.numgames; i++ ) + { + if( !Q_strnicmp( SI.games[i]->gamefolder, dir, 64 )) + { + MsgDev( D_NOTE, "FS_AddGameHierarchy: %d %s %s\n", i, SI.games[i]->gamefolder, SI.games[i]->basedir ); + if( !SI.games[i]->added && Q_stricmp( SI.games[i]->gamefolder, SI.games[i]->basedir ) ) + { + SI.games[i]->added = true; + FS_AddGameHierarchy( SI.games[i]->basedir, flags & (~FS_GAMEDIR_PATH) ); + } + break; + } + } + + if( host.rodir[0] ) + { + // append new flags to rodir, except FS_GAMEDIR_PATH and FS_CUSTOM_PATH + uint newFlags = FS_NOWRITE_PATH | (flags & (~FS_GAMEDIR_PATH|FS_CUSTOM_PATH)); + if( isGameDir ) + newFlags |= FS_GAMERODIR_PATH; + + FS_AllowDirectPaths( true ); + FS_AddGameDirectory( va( "%s/%s/", host.rodir, dir ), newFlags ); + FS_AllowDirectPaths( false ); + } + + if( isGameDir ) + FS_AddGameDirectory( va( "%s/downloaded/", dir ), FS_NOWRITE_PATH | FS_CUSTOM_PATH ); + FS_AddGameDirectory( va( "%s/", dir ), flags ); + if( isGameDir ) + FS_AddGameDirectory( va( "%s/custom/", dir ), FS_NOWRITE_PATH | FS_CUSTOM_PATH ); } /* @@ -855,6 +901,25 @@ void FS_Rescan( void ) FS_ClearSearchPath(); +#ifdef __ANDROID__ + char *str; + if( str = getenv("XASH3D_EXTRAS_PAK1") ) + FS_AddPack_Fullpath( str, NULL, false, FS_NOWRITE_PATH | FS_CUSTOM_PATH ); + if( str = getenv("XASH3D_EXTRAS_PAK2") ) + FS_AddPack_Fullpath( str, NULL, false, FS_NOWRITE_PATH | FS_CUSTOM_PATH ); + //FS_AddPack_Fullpath( "/data/data/in.celest.xash3d.hl.test/files/pak.pak", NULL, false, FS_NOWRITE_PATH | FS_CUSTOM_PATH ); +#elif TARGET_OS_IPHONE + { + FS_AddPack_Fullpath( va( "%sextras.pak", SDL_GetBasePath() ), NULL, false, FS_NOWRITE_PATH | FS_CUSTOM_PATH ); + FS_AddPack_Fullpath( va( "%sextras_%s.pak", SDL_GetBasePath(), GI->gamefolder ), NULL, false, FS_NOWRITE_PATH | FS_CUSTOM_PATH ); + } +#elif defined(__SAILFISH__) + { + FS_AddPack_Fullpath( va( SHAREPATH"/extras.pak" ), NULL, false, FS_NOWRITE_PATH | FS_CUSTOM_PATH ); + FS_AddPack_Fullpath( va( SHAREPATH"/%s/extras.pak", GI->gamefolder ), NULL, false, FS_NOWRITE_PATH | FS_CUSTOM_PATH ); + } +#endif + if( Q_stricmp( GI->basedir, GI->gamefolder )) FS_AddGameHierarchy( GI->basedir, 0 ); if( Q_stricmp( GI->basedir, GI->falldir ) && Q_stricmp( GI->gamefolder, GI->falldir )) @@ -1265,7 +1330,7 @@ static qboolean FS_ParseLiblistGam( const char *filename, const char *gamedir, g FS_ConvertGameInfo ================ */ -void FS_ConvertGameInfo( const char *gamedir, const char *gameinfo_path, const char *liblist_path ) +static qboolean FS_ConvertGameInfo( const char *gamedir, const char *gameinfo_path, const char *liblist_path ) { gameinfo_t GameInfo; @@ -1275,7 +1340,11 @@ void FS_ConvertGameInfo( const char *gamedir, const char *gameinfo_path, const c { Con_DPrintf( "Convert %s to %s\n", liblist_path, gameinfo_path ); FS_WriteGameInfo( gameinfo_path, &GameInfo ); + + return true; } + + return false; } /* @@ -1333,11 +1402,47 @@ static qboolean FS_ParseGameInfo( const char *gamedir, gameinfo_t *GameInfo ) string liblist_path, gameinfo_path; string default_gameinfo_path; gameinfo_t tmpGameInfo; + qboolean haveUpdate = false; Q_snprintf( default_gameinfo_path, sizeof( default_gameinfo_path ), "%s/gameinfo.txt", fs_basedir ); Q_snprintf( gameinfo_path, sizeof( gameinfo_path ), "%s/gameinfo.txt", gamedir ); Q_snprintf( liblist_path, sizeof( liblist_path ), "%s/liblist.gam", gamedir ); + // here goes some RoDir magic... + if( host.rodir[0] ) + { + string filepath_ro, liblist_ro; + fs_offset_t roLibListTime, roGameInfoTime, rwGameInfoTime; + + Q_snprintf( filepath_ro, sizeof( filepath_ro ), "%s/%s/gameinfo.txt", host.rodir, gamedir ); + Q_snprintf( liblist_ro, sizeof( liblist_ro ), "%s/%s/liblist.gam", host.rodir, gamedir ); + + roLibListTime = FS_SysFileTime( liblist_ro ); + roGameInfoTime = FS_SysFileTime( filepath_ro ); + rwGameInfoTime = FS_SysFileTime( gameinfo_path ); + + if( roLibListTime > rwGameInfoTime ) + { + haveUpdate = FS_ConvertGameInfo( gamedir, gameinfo_path, liblist_ro ); + } + else if( roGameInfoTime > rwGameInfoTime ) + { + char *afile_ro = FS_LoadDirectFile( filepath_ro, NULL ); + + if( afile_ro ) + { + gameinfo_t gi; + + haveUpdate = true; + + FS_InitGameInfo( &gi, gamedir ); + FS_ParseGenericGameInfo( &gi, afile_ro, true ); + FS_WriteGameInfo( gameinfo_path, &gi ); + Mem_Free( afile_ro ); + } + } + } + // if user change liblist.gam update the gameinfo.txt if( FS_FileTime( liblist_path, false ) > FS_FileTime( gameinfo_path, false )) FS_ConvertGameInfo( gamedir, gameinfo_path, liblist_path ); @@ -1443,12 +1548,24 @@ void FS_Init( void ) #ifndef _WIN32 if( Sys_CheckParm( "-casesensitive" ) ) fs_caseinsensitive = false; + + if( !fs_caseinsensitive ) + { + if( host.rodir[0] && !Q_strcmp( host.rodir, host.rootdir ) ) + { + Sys_Error( "RoDir and default rootdir can't point to same directory!" ); + } + } + else #endif + { + if( host.rodir[0] && !Q_stricmp( host.rodir, host.rootdir ) ) + { + Sys_Error( "RoDir and default rootdir can't point to same directory!" ); + } + } // ignore commandlineoption "-game" for other stuff - stringlistinit( &dirs ); - listdirectory( &dirs, "./", false ); - stringlistsort( &dirs ); SI.numgames = 0; Q_strncpy( fs_basedir, SI.basedirName, sizeof( fs_basedir )); // default dir @@ -1468,7 +1585,32 @@ void FS_Init( void ) Q_strncpy( fs_gamedir, fs_basedir, sizeof( fs_gamedir )); // default dir } + // add readonly directories first + if( host.rodir[0] ) + { + stringlistinit( &dirs ); + listdirectory( &dirs, host.rodir, false ); + stringlistsort( &dirs ); + + for( i = 0; i < dirs.numstrings; i++ ) + { + if( !FS_SysFolderExists( dirs.strings[i] ) || ( !Q_stricmp( dirs.strings[i], ".." ) && !fs_ext_path )) + continue; + + // magic here is that dirs.strings don't contain full path + // so code below checks and creates folders in current directory(host.rootdir) + if( !FS_SysFolderExists( dirs.strings[i] ) ) + FS_CreatePath( dirs.strings[i] ); + } + + stringlistfreecontents( &dirs ); + } + // validate directories + stringlistinit( &dirs ); + listdirectory( &dirs, "./", false ); + stringlistsort( &dirs ); + for( i = 0; i < dirs.numstrings; i++ ) { if( !Q_stricmp( fs_basedir, dirs.strings[i] )) @@ -1749,7 +1891,7 @@ static searchpath_t *FS_FindFile( const char *name, int *index, qboolean gamedir // search through the path, one element at a time for( search = fs_searchpaths; search; search = search->next ) { - if( gamedironly & !FBitSet( search->flags, FS_GAMEDIR_PATH )) + if( gamedironly & !FBitSet( search->flags, FS_GAMEDIRONLY_SEARCH_FLAGS )) continue; // is the element a pak file? @@ -2738,7 +2880,7 @@ search_t *FS_Search( const char *pattern, int caseinsensitive, int gamedironly ) // search through the path, one element at a time for( searchpath = fs_searchpaths; searchpath; searchpath = searchpath->next ) { - if( gamedironly && !FBitSet( searchpath->flags, FS_GAMEDIR_PATH )) + if( gamedironly && !FBitSet( searchpath->flags, FS_GAMEDIRONLY_SEARCH_FLAGS )) continue; // is the element a pak file? @@ -3221,17 +3363,24 @@ open the wad for reading & writing wfile_t *W_Open( const char *filename, int *error ) { wfile_t *wad = (wfile_t *)Mem_Calloc( fs_mempool, sizeof( wfile_t )); + const char *basename; int i, lumpcount; dlumpinfo_t *srclumps; size_t lat_size; dwadinfo_t header; // NOTE: FS_Open is load wad file from the first pak in the list (while fs_ext_path is false) - if( fs_ext_path ) wad->handle = FS_Open( filename, "rb", false ); - else wad->handle = FS_Open( COM_FileWithoutPath( filename ), "rb", false ); + if( fs_ext_path ) basename = filename; + else basename = COM_FileWithoutPath( filename ); + + wad->handle = FS_Open( basename, "rb", false ); + + // HACKHACK: try to open WAD by full path for RoDir, when searchpaths are not ready + if( host.rodir[0] && fs_ext_path && wad->handle == NULL ) + wad->handle = FS_SysOpen( filename, "rb" ); if( wad->handle == NULL ) - { + { MsgDev( D_ERROR, "W_Open: couldn't open %s\n", filename ); if( error ) *error = WAD_LOAD_COULDNT_OPEN; W_Close( wad ); diff --git a/engine/common/host.c b/engine/common/host.c index 8896f7d5..70d68046 100644 --- a/engine/common/host.c +++ b/engine/common/host.c @@ -742,6 +742,20 @@ void Host_InitCommon( int argc, char **argv, const char *progname, qboolean bCha if( host.rootdir[Q_strlen( host.rootdir ) - 1] == '/' ) host.rootdir[Q_strlen( host.rootdir ) - 1] = 0; + // get readonly root. The order is: check for arg, then env. + // if still not got it, rodir is disabled. + host.rodir[0] = 0; + if( !Sys_GetParmFromCmdLine( "-rodir", host.rodir )) + { + char *roDir; + + if(( roDir = getenv( "XASH3D_RODIR" ))) + Q_strncpy( host.rodir, roDir, sizeof( host.rodir )); + } + + if( host.rodir[0] && host.rodir[Q_strlen( host.rodir ) - 1] == '/' ) + host.rodir[Q_strlen( host.rodir ) - 1] = 0; + host.enabledll = !Sys_CheckParm( "-nodll" ); #ifdef DLL_LOADER