From cfebb3e1d665aae341200224b3afd33433eb8045 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Tue, 7 Jan 2025 08:50:29 +0300 Subject: [PATCH] filesystem: massively rework how scanning game directories work * No more conversion from liblist.gam to gameinfo.txt. We are using liblist.gam directly now. gameinfo.txt being native format to Xash3D not only remains, it takes priority over liblist.gam. * Quake game directories now don't receive autogenerated gameinfo.txt. * Empty directories don't get gameinfo.txt either, finally making it easier to support HD addon folders. * If user still wishes to generate gameinfo.txt, there is now command fs_make_gameinfo that creates gameinfo.txt for currently running game. * No more creating empty folders for RoDir. They are now created on demand. --- engine/common/filesystem_engine.c | 6 + filesystem/dir.c | 6 +- filesystem/filesystem.c | 472 ++++++++++++++++++------------ filesystem/filesystem.h | 5 + filesystem/filesystem_internal.h | 2 +- 5 files changed, 293 insertions(+), 198 deletions(-) diff --git a/engine/common/filesystem_engine.c b/engine/common/filesystem_engine.c index c9c52835..40714522 100644 --- a/engine/common/filesystem_engine.c +++ b/engine/common/filesystem_engine.c @@ -84,6 +84,11 @@ static void FS_Path_f_( void ) FS_Path_f(); } +static void FS_MakeGameInfo_f( void ) +{ + g_fsapi.MakeGameInfo(); +} + static const fs_interface_t fs_memfuncs = { Con_Printf, @@ -272,6 +277,7 @@ void FS_Init( const char *basedir ) Cmd_AddRestrictedCommand( "fs_rescan", FS_Rescan_f, "rescan filesystem search pathes" ); Cmd_AddRestrictedCommand( "fs_path", FS_Path_f_, "show filesystem search pathes" ); Cmd_AddRestrictedCommand( "fs_clearpaths", FS_ClearPaths_f, "clear filesystem search pathes" ); + Cmd_AddRestrictedCommand( "fs_make_gameinfo", FS_MakeGameInfo_f, "create gameinfo.txt for current running game" ); if( !Sys_GetParmFromCmdLine( "-dll", host.gamedll )) host.gamedll[0] = 0; diff --git a/filesystem/dir.c b/filesystem/dir.c index 881f1d80..22ce835b 100644 --- a/filesystem/dir.c +++ b/filesystem/dir.c @@ -134,7 +134,7 @@ static void FS_PopulateDirEntries( dir_t *dir, const char *path ) } stringlistinit( &list ); - listdirectory( &list, path ); + listdirectory( &list, path, false ); if( !list.numstrings ) { dir->numentries = DIRENTRY_EMPTY_DIRECTORY; @@ -225,7 +225,7 @@ static int FS_MaybeUpdateDirEntries( dir_t *dir, const char *path, const char *e int ret; stringlistinit( &list ); - listdirectory( &list, path ); + listdirectory( &list, path, false ); if( list.numstrings == 0 ) // empty directory { @@ -421,7 +421,7 @@ static void FS_Search_DIR( searchpath_t *search, stringlist_t *list, const char } stringlistinit( &dirlist ); - listdirectory( &dirlist, netpath ); + listdirectory( &dirlist, netpath, false ); Q_strncpy( temp, basepath, sizeof( temp )); diff --git a/filesystem/filesystem.c b/filesystem/filesystem.c index 9caad56e..f1f3ca1e 100644 --- a/filesystem/filesystem.c +++ b/filesystem/filesystem.c @@ -225,18 +225,13 @@ static void listlowercase( stringlist_t *list ) } #endif -void listdirectory( stringlist_t *list, const char *path ) +void listdirectory( stringlist_t *list, const char *path, qboolean dirs_only ) { #if XASH_WIN32 char pattern[4096]; - struct _finddata_t n_file; - intptr_t hFile; -#else - DIR *dir; - struct dirent *entry; -#endif + struct _finddata_t n_file; + intptr_t hFile; -#if XASH_WIN32 Q_snprintf( pattern, sizeof( pattern ), "%s/*", path ); // ask for the directory listing handle @@ -247,15 +242,32 @@ void listdirectory( stringlist_t *list, const char *path ) stringlistappend( list, n_file.name ); // iterate through the directory while( _findnext( hFile, &n_file ) == 0 ) + { + if( dirs_only && !FBitSet( n_file.attrib, _A_SUBDIR )) + continue; + stringlistappend( list, n_file.name ); + } _findclose( hFile ); #else - if( !( dir = opendir( path ) ) ) + DIR *dir; + struct dirent *entry; + + dir = opendir( path ); + + if( !dir ) return; // iterate through the directory - while( ( entry = readdir( dir ) )) + while(( entry = readdir( dir ))) + { + // FIXME: this is a BSD extension, add check to wscript + if( dirs_only && entry->d_type != DT_DIR ) + continue; + stringlistappend( list, entry->d_name ); + } + closedir( dir ); #endif @@ -396,7 +408,7 @@ void FS_AddGameDirectory( const char *dir, uint flags ) int i; stringlistinit( &list ); - listdirectory( &list, dir ); + listdirectory( &list, dir, false ); stringlistsort( &list ); for( archive = g_archives; archive->ext; archive++ ) @@ -508,12 +520,13 @@ FS_WriteGameInfo assume GameInfo is valid ================ */ -static void FS_WriteGameInfo( const char *filepath, gameinfo_t *GameInfo ) +static qboolean FS_WriteGameInfo( const char *filepath, gameinfo_t *GameInfo ) { file_t *f = FS_Open( filepath, "w", false ); // we in binary-mode int i, write_ambients = false; - if( !f ) Sys_Error( "%s: can't write %s\n", __func__, filepath ); // may be disk-space is out? + if( !f ) + return false; FS_Printf( f, "// generated by " XASH_ENGINE_NAME " " XASH_VERSION "-%s (%s-%s)\n\n\n", Q_buildcommit(), Q_buildos(), Q_buildarch() ); @@ -637,25 +650,50 @@ static void FS_WriteGameInfo( const char *filepath, gameinfo_t *GameInfo ) FS_Printf( f, "demomap\t\t\"%s\"\n", GameInfo->demomap ); FS_Close( f ); // all done + + return true; } -static void FS_InitGameInfo( gameinfo_t *GameInfo, const char *gamedir ) +static void FS_MakeGameInfo( void ) +{ + if( FS_WriteGameInfo( "gameinfo.txt", FI.GameInfo )) + Con_Printf( "Successfully generated %s/gameinfo.txt\n", FI.GameInfo->gamefolder ); + else + Con_Printf( S_ERROR "Can't open %s/gameinfo.txt for write\n", FI.GameInfo->gamefolder ); +} + +static void FS_InitGameInfo( gameinfo_t *GameInfo, const char *gamedir, qboolean quake, time_t mtime ) { memset( GameInfo, 0, sizeof( *GameInfo )); // filesystem info - Q_strncpy( GameInfo->title, "New Game", sizeof( GameInfo->title )); + GameInfo->mtime = mtime; Q_strncpy( GameInfo->gamefolder, gamedir, sizeof( GameInfo->gamefolder )); - Q_strncpy( GameInfo->basedir, fs_basedir, sizeof( GameInfo->basedir )); Q_strncpy( GameInfo->sp_entity, "info_player_start", sizeof( GameInfo->sp_entity )); Q_strncpy( GameInfo->mp_entity, "info_player_deathmatch", sizeof( GameInfo->mp_entity )); - Q_strncpy( GameInfo->startmap, "newmap", sizeof( GameInfo->startmap )); - Q_strncpy( GameInfo->dll_path, "cl_dlls", sizeof( GameInfo->dll_path )); - Q_strncpy( GameInfo->game_dll, "dlls/hl.dll", sizeof( GameInfo->game_dll )); - Q_strncpy( GameInfo->game_dll_linux, "dlls/hl.so", sizeof( GameInfo->game_dll_linux )); - Q_strncpy( GameInfo->game_dll_osx, "dlls/hl.dylib", sizeof( GameInfo->game_dll_osx )); Q_strncpy( GameInfo->iconpath, "game.ico", sizeof( GameInfo->iconpath )); + if( quake ) + { + Q_strncpy( GameInfo->basedir, "id1", sizeof( GameInfo->basedir )); + Q_strncpy( GameInfo->title, gamedir, sizeof( GameInfo->title )); + Q_strncpy( GameInfo->startmap, "start", sizeof( GameInfo->startmap )); + Q_strncpy( GameInfo->dll_path, "bin", sizeof( GameInfo->dll_path )); + Q_strncpy( GameInfo->game_dll, "bin/progs.dll", sizeof( GameInfo->game_dll )); + Q_strncpy( GameInfo->game_dll_linux, "bin/progs.so", sizeof( GameInfo->game_dll_linux )); + Q_strncpy( GameInfo->game_dll_osx, "bin/progs.dylib", sizeof( GameInfo->game_dll_osx )); + } + else + { + Q_strncpy( GameInfo->basedir, fs_basedir, sizeof( GameInfo->basedir )); + Q_strncpy( GameInfo->title, "New Game", sizeof( GameInfo->title )); + Q_strncpy( GameInfo->startmap, "newmap", sizeof( GameInfo->startmap )); + Q_strncpy( GameInfo->dll_path, "cl_dlls", sizeof( GameInfo->dll_path )); + Q_strncpy( GameInfo->game_dll, "dlls/hl.dll", sizeof( GameInfo->game_dll )); + Q_strncpy( GameInfo->game_dll_linux, "dlls/hl.so", sizeof( GameInfo->game_dll_linux )); + Q_strncpy( GameInfo->game_dll_osx, "dlls/hl.dylib", sizeof( GameInfo->game_dll_osx )); + } + GameInfo->max_edicts = DEFAULT_MAX_EDICTS; // default value if not specified GameInfo->max_tents = 500; GameInfo->max_beams = 128; @@ -940,36 +978,19 @@ static void FS_ParseGenericGameInfo( gameinfo_t *GameInfo, const char *buf, cons } } -/* -================ -FS_CreateDefaultGameInfo -================ -*/ -static void FS_CreateDefaultGameInfo( const char *filename ) -{ - gameinfo_t defGI; - - FS_InitGameInfo( &defGI, fs_basedir ); - - // make simple gameinfo.txt - FS_WriteGameInfo( filename, &defGI ); -} - /* ================ FS_ParseLiblistGam ================ */ -static qboolean FS_ParseLiblistGam( const char *filename, const char *gamedir, gameinfo_t *GameInfo ) +static qboolean FS_ParseLiblistGam( const char *filename, const char *gamedir, gameinfo_t *GameInfo, time_t mtime ) { - char *afile; + char *afile = FS_LoadDirectFile( filename, NULL ); - if( !GameInfo ) return false; - afile = (char *)FS_LoadDirectFile( filename, NULL ); - if( !afile ) return false; - - FS_InitGameInfo( GameInfo, gamedir ); + if( !afile ) + return false; + FS_InitGameInfo( GameInfo, gamedir, false, mtime ); FS_ParseGenericGameInfo( GameInfo, afile, false ); Mem_Free( afile ); @@ -982,18 +1003,15 @@ static qboolean FS_ParseLiblistGam( const char *filename, const char *gamedir, g FS_ConvertGameInfo ================ */ -static qboolean 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 *gi, time_t liblist_mtime ) { - gameinfo_t GameInfo; + memset( gi, 0, sizeof( *gi )); - memset( &GameInfo, 0, sizeof( GameInfo )); - - if( FS_ParseLiblistGam( liblist_path, gamedir, &GameInfo )) + // liblist.gam to gameinfo.txt conversion is deprecated, only support for RwDir! + if( FS_ParseLiblistGam( liblist_path, gamedir, gi, liblist_mtime )) { Con_DPrintf( "Convert %s to %s\n", liblist_path, gameinfo_path ); - FS_WriteGameInfo( gameinfo_path, &GameInfo ); - - return true; + return FS_WriteGameInfo( gameinfo_path, gi ); } return false; @@ -1004,16 +1022,14 @@ static qboolean FS_ConvertGameInfo( const char *gamedir, const char *gameinfo_pa FS_ReadGameInfo ================ */ -static qboolean FS_ReadGameInfo( const char *filepath, const char *gamedir, gameinfo_t *GameInfo ) +static qboolean FS_ReadGameInfo( const char *filename, const char *gamedir, gameinfo_t *GameInfo, time_t mtime ) { - char *afile; + char *afile = FS_LoadDirectFile( filename, NULL ); - afile = (char *)FS_LoadFile( filepath, NULL, false ); if( !afile ) return false; - FS_InitGameInfo( GameInfo, gamedir ); - + FS_InitGameInfo( GameInfo, gamedir, false, mtime ); FS_ParseGenericGameInfo( GameInfo, afile, true ); Mem_Free( afile ); @@ -1029,20 +1045,32 @@ Checks if game directory resembles Quake Engine game directory (some of checks may as well work with Xash gamedirs, it's not a bug) ================ */ -static qboolean FS_CheckForQuakeGameDir( const char *gamedir, qboolean direct ) +static qboolean FS_CheckForQuakeGameDir( const char *gamedir ) { - // if directory contain config.cfg or progs.dat it's 100% gamedir - // quake mods probably always archived but can missed config.cfg before first running - const char *files[] = { "config.cfg", "progs.dat", "pak0.pak" }; + // if directory contain quake.rc or progs.dat it's 100% quake gamedir + // quake mods probably always archived, so check pak0.pak too + const char *files[] = { "progs.dat", "quake.rc" }; + char buf[MAX_SYSPATH]; int i; + // try to read pak0.pak first, most quake mods are archived + if( Q_snprintf( buf, sizeof( buf ), "%s/pak0.pak", gamedir ) > 0 ) + { + if( FS_SysFileExists( buf )) + { + if( FS_CheckForQuakePak( buf, files, sizeof( files ) / sizeof( files[0] ))) + return true; + } + } + + // search it in the filesystem for( i = 0; i < sizeof( files ) / sizeof( files[0] ); i++ ) { - char buf[MAX_VA_STRING]; - - Q_snprintf( buf, sizeof( buf ), "%s/%s", gamedir, files[i] ); - if( direct ? FS_SysFileExists( buf ) : FS_FileExists( buf, false )) - return true; + if( Q_snprintf( buf, sizeof( buf ), "%s/%s", gamedir, files[i] ) > 0 ) + { + if( FS_SysFileExists( buf )) + return true; + } } return false; @@ -1055,7 +1083,7 @@ FS_CheckForXashGameDir Checks if game directory resembles Xash3D game directory =============== */ -static qboolean FS_CheckForXashGameDir( const char *gamedir, qboolean direct ) +static qboolean FS_CheckForXashGameDir( const char *gamedir ) { // if directory contain gameinfo.txt or liblist.gam it's 100% gamedir const char *files[] = { "gameinfo.txt", "liblist.gam" }; @@ -1063,15 +1091,16 @@ static qboolean FS_CheckForXashGameDir( const char *gamedir, qboolean direct ) for( i = 0; i < sizeof( files ) / sizeof( files[0] ); i++ ) { - char buf[MAX_SYSPATH]; + char buf[MAX_SYSPATH]; - Q_snprintf( buf, sizeof( buf ), "%s/%s", gamedir, files[i] ); - if( direct ? FS_SysFileExists( buf ) : FS_FileExists( buf, false )) - return true; + if( Q_snprintf( buf, sizeof( buf ), "%s/%s", gamedir, files[i] ) > 0 ) + { + if( FS_SysFileExists( buf )) + return true; + } } return false; - } /* @@ -1079,86 +1108,74 @@ static qboolean FS_CheckForXashGameDir( const char *gamedir, qboolean direct ) FS_ParseGameInfo ================ */ -static qboolean FS_ParseGameInfo( const char *gamedir, gameinfo_t *GameInfo ) +static qboolean FS_ParseGameInfo( const char *gamedir, gameinfo_t *GameInfo, qboolean rodir ) { - string liblist_path, gameinfo_path; - string default_gameinfo_path; - qboolean haveUpdate = false; + char liblist_path[MAX_SYSPATH]; + char gameinfo_path[MAX_SYSPATH]; + char gamedir_path[MAX_SYSPATH]; + time_t liblist_mtime = -1; + time_t gameinfo_mtime = -1; - 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 ); + if( rodir ) + Q_snprintf( gamedir_path, sizeof( gamedir_path ), "%s/%s", fs_rodir, gamedir ); + else + Q_snprintf( gamedir_path, sizeof( gamedir_path ), "%s/%s", fs_rootdir, gamedir ); - // here goes some RoDir magic... - if( COM_CheckStringEmpty( fs_rodir )) + if( !FS_CheckForXashGameDir( gamedir_path )) { - string gameinfo_ro, liblist_ro; - fs_offset_t roLibListTime, roGameInfoTime, rwGameInfoTime; - - FS_AllowDirectPaths( true ); - - Q_snprintf( gameinfo_ro, sizeof( gameinfo_ro ), "%s/%s/gameinfo.txt", fs_rodir, gamedir ); - Q_snprintf( liblist_ro, sizeof( liblist_ro ), "%s/%s/liblist.gam", fs_rodir, gamedir ); - - roLibListTime = FS_SysFileTime( liblist_ro ); - roGameInfoTime = FS_SysFileTime( gameinfo_ro ); - rwGameInfoTime = FS_SysFileTime( gameinfo_path ); - - // if rodir's liblist.gam newer than rwdir's gameinfo.txt, then convert it - if( roLibListTime > rwGameInfoTime ) + // check if we need to generate gameinfo for Quake + if( FS_CheckForQuakeGameDir( gamedir_path )) { - haveUpdate = FS_ConvertGameInfo( gamedir, gameinfo_path, liblist_ro ); - } - // if rodir's gameinfo.txt newer than rwdir's gameinfo.txt, just copy the file - else if( roGameInfoTime > rwGameInfoTime ) - { - file_t *ro, *rw; - fs_offset_t ro_size; - - // read & write as binary to copy the exact file - ro = FS_SysOpen( gameinfo_ro, "rb" ); - rw = FS_SysOpen( gameinfo_path, "wb" ); - - FS_Seek( ro, 0, SEEK_END ); - ro_size = FS_Tell( ro ); - FS_Seek( ro, 0, SEEK_SET ); - - FS_FileCopy( rw, ro, ro_size ); - - FS_Close( rw ); - FS_Close( ro ); - - haveUpdate = true; + // just generate stub gameinfo in memory + FS_InitGameInfo( GameInfo, gamedir, true, -1 ); + GameInfo->rodir = rodir; + return true; } - FS_AllowDirectPaths( false ); + // don't add empty or addon directories + return false; } - // do not update gameinfo.txt, if it was just copied from rodir's - // if user change liblist.gam update the gameinfo.txt - if( !haveUpdate && FS_FileTime( liblist_path, false ) > FS_FileTime( gameinfo_path, false )) - FS_ConvertGameInfo( gamedir, gameinfo_path, liblist_path ); + Q_snprintf( gameinfo_path, sizeof( gameinfo_path ), "%s/gameinfo.txt", gamedir_path ); + Q_snprintf( liblist_path, sizeof( liblist_path ), "%s/liblist.gam", gamedir_path ); - // force to create gameinfo for specified game if missing - if(( FS_CheckForQuakeGameDir( gamedir, false ) || !Q_stricmp( fs_gamedir, gamedir )) && !FS_FileExists( gameinfo_path, false )) + liblist_mtime = FS_SysFileTime( liblist_path ); + gameinfo_mtime = FS_SysFileTime( gameinfo_path ); + + // in this function we never write new files for RoDir compatibility + // since RoDir is only FWGS feature, do gameinfo.txt conversion only + // for RwDir for those who worked with original Xash3D + if( !rodir ) { - gameinfo_t tmpGameInfo; - memset( &tmpGameInfo, 0, sizeof( tmpGameInfo )); - - if( FS_ReadGameInfo( default_gameinfo_path, gamedir, &tmpGameInfo )) + // !!!only if we have both liblist.gam and gameinfo.txt try to convert liblist.gam to gameinfo.txt if it's newer!!! + if( liblist_mtime >= 0 && gameinfo_mtime >= 0 && liblist_mtime > gameinfo_mtime ) { - // now we have copy of game info from basedir but needs to change gamedir - Con_DPrintf( "Convert %s to %s\n", default_gameinfo_path, gameinfo_path ); - Q_strncpy( tmpGameInfo.gamefolder, gamedir, sizeof( tmpGameInfo.gamefolder )); - FS_WriteGameInfo( gameinfo_path, &tmpGameInfo ); + if( FS_ConvertGameInfo( gamedir, gameinfo_path, liblist_path, GameInfo, liblist_mtime )) + return true; } - else FS_CreateDefaultGameInfo( gameinfo_path ); } - if( !GameInfo || !FS_FileExists( gameinfo_path, false )) - return false; // no dest + // can we parse gameinfo.txt? + if( gameinfo_mtime >= 0 ) + { + if( FS_ReadGameInfo( gameinfo_path, gamedir, GameInfo, gameinfo_mtime )) + { + GameInfo->rodir = rodir; + return true; + } + } - return FS_ReadGameInfo( gameinfo_path, gamedir, GameInfo ); + // can we parse liblist.gam? + if( liblist_mtime >= 0 ) + { + if( FS_ParseLiblistGam( liblist_path, gamedir, GameInfo, liblist_mtime )) + { + GameInfo->rodir = rodir; + return true; + } + } + + return false; } /* @@ -1197,7 +1214,7 @@ void FS_AddGameHierarchy( const char *dir, uint flags ) } } - if( COM_CheckStringEmpty( fs_rodir ) ) + if( COM_CheckStringEmpty( fs_rodir )) { // append new flags to rodir, except FS_GAMEDIR_PATH and FS_CUSTOM_PATH uint new_flags = FS_NOWRITE_PATH | (flags & (~FS_GAMEDIR_PATH|FS_CUSTOM_PATH)); @@ -1266,10 +1283,12 @@ void FS_LoadGameInfo( const char *rootfolder ) // lock uplevel of gamedir for read\write FS_AllowDirectPaths( false ); - if( rootfolder ) Q_strncpy( fs_gamedir, rootfolder, sizeof( fs_gamedir )); + if( rootfolder ) + Q_strncpy( fs_gamedir, rootfolder, sizeof( fs_gamedir )); + Con_Reportf( "%s( %s )\n", __func__, fs_gamedir ); - // clear any old pathes + // clear any old paths FS_ClearSearchPath(); // validate gamedir @@ -1284,6 +1303,14 @@ void FS_LoadGameInfo( const char *rootfolder ) FI.GameInfo = FI.games[i]; + if( FI.GameInfo->rodir ) + { + // ensure we have directory in rwdir + char buf[MAX_SYSPATH + MAX_QPATH + 2]; // and have plenty of space + Q_snprintf( buf, sizeof( buf ), "%s/%s/", fs_rootdir, FI.GameInfo->gamefolder ); + FS_CreatePath( buf ); + } + FS_Rescan(); // create new filesystem } @@ -1344,7 +1371,7 @@ static qboolean FS_FindLibrary( const char *dllname, qboolean directpath, fs_dll { string fixedname; searchpath_t *search; - int index, start = 0, i, len; + int index, start = 0, len; // check for bad exports if( !COM_CheckString( dllname )) @@ -1479,6 +1506,35 @@ static void *Sys_GetNativeObjectStub( const char *object ) return NULL; } +static void FS_ValidateDirectories( const char *path, qboolean *has_base_dir, qboolean *has_game_dir ) +{ + stringlist_t dirs; + int i; + + stringlistinit( &dirs ); + listdirectory( &dirs, path, true ); + stringlistsort( &dirs ); + + for( i = 0; i < dirs.numstrings; i++ ) + { +#if 0 // re-enable if target doesn't support filtering directories only (missing d_type in struct dirent) + if( !FS_SysFolderExists( dirs.strings[i] )) + continue; +#endif + + if( !Q_stricmp( fs_basedir, dirs.strings[i] )) + *has_base_dir = true; + + if( !Q_stricmp( fs_gamedir, dirs.strings[i] )) + *has_game_dir = true; + + if( *has_base_dir && *has_game_dir ) + break; + } + + stringlistfreecontents( &dirs ); +} + /* ================ FS_Init @@ -1486,11 +1542,9 @@ FS_Init */ qboolean FS_InitStdio( qboolean unused_set_to_true, const char *rootdir, const char *basedir, const char *gamedir, const char *rodir ) { - stringlist_t dirs; - qboolean hasBaseDir = false; - qboolean hasGameDir = false; - int i; - char buf[MAX_VA_STRING]; + stringlist_t dirs; + int i, rodir_num_games; + char buf[MAX_SYSPATH]; FS_InitMemory(); @@ -1503,63 +1557,40 @@ qboolean FS_InitStdio( qboolean unused_set_to_true, const char *rootdir, const c Q_strncpy( fs_basedir, basedir, sizeof( fs_basedir )); Q_strncpy( fs_rodir, rodir, sizeof( fs_rodir )); - // add readonly directories first - if( COM_CheckStringEmpty( fs_rodir )) + // validate user input + if( COM_CheckStringEmpty( fs_rodir ) && !Q_stricmp( fs_rodir, fs_rootdir )) { - if( !Q_stricmp( fs_rodir, fs_rootdir )) + Sys_Error( "RoDir and default rootdir can't point to same directory!" ); + return false; + } + + // look for game directories in RwDir first + // this whole check only required to fallback to base directory + // (i.e. default game directory hardcoded in the executable file) + { + qboolean has_base_dir = false; + qboolean has_game_dir = false; + + FS_ValidateDirectories( "./", &has_base_dir, &has_game_dir ); + + if( !has_game_dir ) { - Sys_Error( "RoDir and default rootdir can't point to same directory!" ); - return false; - } + // look for game directories in RoDir now + if( COM_CheckStringEmpty( fs_rodir )) + FS_ValidateDirectories( fs_rodir, &has_base_dir, &has_game_dir ); - stringlistinit( &dirs ); - listdirectory( &dirs, fs_rodir ); - stringlistsort( &dirs ); - - for( i = 0; i < dirs.numstrings; i++ ) - { - char roPath[MAX_SYSPATH]; - - Q_snprintf( roPath, sizeof( roPath ), "%s/%s/", fs_rodir, dirs.strings[i] ); - - // check if it's a directory - if( !FS_SysFolderExists( roPath )) - continue; - - // check if it's gamedir - if( FS_CheckForXashGameDir( roPath, true ) || FS_CheckForQuakeGameDir( roPath, true )) + if( !has_game_dir ) { - char rwPath[MAX_SYSPATH]; + Con_Printf( S_ERROR "game directory \"%s\" not exist\n", fs_gamedir ); - Q_snprintf( rwPath, sizeof( rwPath ), "%s/%s/", fs_rootdir, dirs.strings[i] ); - FS_CreatePath( rwPath ); + // revert to base game directory + if( has_base_dir ) + Q_strncpy( fs_gamedir, fs_basedir, sizeof( fs_gamedir )); } } - - stringlistfreecontents( &dirs ); } - // validate directories - stringlistinit( &dirs ); - listdirectory( &dirs, "./" ); - stringlistsort( &dirs ); - - for( i = 0; i < dirs.numstrings; i++ ) - { - if( !Q_stricmp( fs_basedir, dirs.strings[i] )) - hasBaseDir = true; - - if( !Q_stricmp( fs_gamedir, dirs.strings[i] )) - hasGameDir = true; - } - - if( !hasGameDir ) - { - Con_Printf( S_ERROR "game directory \"%s\" not exist\n", fs_gamedir ); - if( hasBaseDir ) Q_strncpy( fs_gamedir, fs_basedir, sizeof( fs_gamedir )); - } - - // build list of game directories here + // now start building first level of directory hierarchy if( COM_CheckStringEmpty( fs_rodir )) { Q_snprintf( buf, sizeof( buf ), "%s/", fs_rodir ); @@ -1567,15 +1598,66 @@ qboolean FS_InitStdio( qboolean unused_set_to_true, const char *rootdir, const c } FS_AddGameDirectory( "./", FS_STATIC_PATH ); + // but scan rodir for games first + if( COM_CheckStringEmpty( fs_rodir )) + { + stringlistinit( &dirs ); + listdirectory( &dirs, fs_rodir, true ); + stringlistsort( &dirs ); + + for( i = 0; i < dirs.numstrings; i++ ) + { +#if 0 // re-enable if target doesn't support filtering directories only (missing d_type in struct dirent) + if( !FS_SysFolderExists( dirs.strings[i] )) + continue; +#endif + + if( FI.games[FI.numgames] == NULL ) + FI.games[FI.numgames] = Mem_Malloc( fs_mempool, sizeof( *FI.games[FI.numgames] )); + + if( FS_ParseGameInfo( dirs.strings[i], FI.games[FI.numgames], true )) + FI.numgames++; + } + + stringlistfreecontents( &dirs ); + } + + rodir_num_games = FI.numgames; + + stringlistinit( &dirs ); + listdirectory( &dirs, "./", true ); + stringlistsort( &dirs ); + for( i = 0; i < dirs.numstrings; i++ ) { + int j; + +#if 0 // re-enable if target doesn't support filtering directories only (missing d_type in struct dirent) if( !FS_SysFolderExists( dirs.strings[i] )) continue; +#endif + + // update gameinfo from rwdir, if it's newer + // with rodir we should never create new gameinfos anymore, + // so this only catches possible user changes + for( j = 0; j < rodir_num_games; j++ ) + { + if( !Q_stricmp( dirs.strings[i], FI.games[j]->gamefolder )) + { + gameinfo_t gi; + if( FS_ParseGameInfo( dirs.strings[i], &gi, false )) + { + if( gi.mtime > FI.games[j]->mtime ) + *FI.games[j] = gi; + } + break; + } + } if( FI.games[FI.numgames] == NULL ) - FI.games[FI.numgames] = (gameinfo_t *)Mem_Calloc( fs_mempool, sizeof( gameinfo_t )); + FI.games[FI.numgames] = Mem_Calloc( fs_mempool, sizeof( *FI.games[FI.numgames] )); - if( FS_ParseGameInfo( dirs.strings[i], FI.games[FI.numgames] )) + if( FS_ParseGameInfo( dirs.strings[i], FI.games[FI.numgames], false )) FI.numgames++; // added } @@ -3187,6 +3269,8 @@ const fs_api_t g_api = FS_LoadFileFromArchive, FS_GetRootDirectory, + + FS_MakeGameInfo, }; int EXPORT GetFSAPI( int version, fs_api_t *api, fs_globals_t **globals, fs_interface_t *engfuncs ); diff --git a/filesystem/filesystem.h b/filesystem/filesystem.h index 874d7bcb..c82307cd 100644 --- a/filesystem/filesystem.h +++ b/filesystem/filesystem.h @@ -123,6 +123,9 @@ typedef struct gameinfo_s qboolean animated_title; char demomap[MAX_QPATH]; + + qboolean rodir; // if true, parsed from rodir + int64_t mtime; } gameinfo_t; typedef struct fs_dllinfo_t @@ -226,6 +229,8 @@ typedef struct fs_api_t // gets current root directory, set by InitStdio qboolean (*GetRootDirectory)( char *path, size_t size ); + + void (*MakeGameInfo)( void ); } fs_api_t; typedef struct fs_interface_t diff --git a/filesystem/filesystem_internal.h b/filesystem/filesystem_internal.h index 1d988fbf..47ed327d 100644 --- a/filesystem/filesystem_internal.h +++ b/filesystem/filesystem_internal.h @@ -205,7 +205,7 @@ void stringlistinit( stringlist_t *list ); void stringlistfreecontents( stringlist_t *list ); void stringlistappend( stringlist_t *list, const char *text ); void stringlistsort( stringlist_t *list ); -void listdirectory( stringlist_t *list, const char *path ); +void listdirectory( stringlist_t *list, const char *path, qboolean dirs_only ); // filesystem ops int FS_FileExists( const char *filename, int gamedironly );