From 9c6589fa036f994701a73c4b099b1fba230d6d20 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Mon, 1 Sep 2025 01:40:11 +0500 Subject: [PATCH] engine: introduce vfs.cfg to properly control user set addon folders vfs.cfg executed before game directory is mounted, this way we can know which folders must be mounted before engine initialization. This commit also removes buggy hot reload of the addon folders. --- Documentation/extensions/addon-folders.md | 16 ++++ engine/client/cl_main.c | 3 - engine/common/common.h | 10 +-- engine/common/con_utils.c | 42 +++++----- engine/common/filesystem_engine.c | 95 +++++++++++++++++------ engine/common/host.c | 4 +- filesystem/filesystem.c | 25 ++++-- filesystem/filesystem.h | 7 +- filesystem/filesystem_internal.h | 3 - filesystem/fscallback.h | 3 - 10 files changed, 134 insertions(+), 74 deletions(-) create mode 100644 Documentation/extensions/addon-folders.md diff --git a/Documentation/extensions/addon-folders.md b/Documentation/extensions/addon-folders.md new file mode 100644 index 00000000..0611c2d5 --- /dev/null +++ b/Documentation/extensions/addon-folders.md @@ -0,0 +1,16 @@ +# Addon folders in Xash3D FWGS + +Xash3D FWGS supports both GoldSource-style addon folders and has few own. Each directory can have it's own archives that will be mounted with lower priority than directory itself. + +Below is the mounts map, in order of precedence from least important to most important. + +|--------------------|------| +| Directory | Note | +|--------------------|------| +| `$game/downloaded` | Always added. Used to store server downloads.| +| `$game` | This is the game directory. | +| `$game/custom` | Always added. Used for user modifications content. | +| `$game_hd` | Added with `fs_mount_hd` set to non-zero value. Used for high definition content, similar to GoldSrc.. | +| `$game_addon` | Added with `fs_mount_addon` set to non-zero value. Used for user modifications content, similar to GoldSrc. | +| `$game_lv` | Added with `fs_mount_lv` set to non-zero value. Used for low-violence content, similar to GoldSrc. | +| `$game_$language` | Added with `fs_mount_l10n` set to non-zero value. Language is controlled with `ui_language` cvar or `-language` command line switch. Used for localization content, similar to GoldSrc. | diff --git a/engine/client/cl_main.c b/engine/client/cl_main.c index 2ac23e79..ff62956e 100644 --- a/engine/client/cl_main.c +++ b/engine/client/cl_main.c @@ -3127,9 +3127,6 @@ static qboolean CL_ShouldRescanFilesystem( void ) } } - if( FBitSet( fs_mount_lv.flags|fs_mount_hd.flags|fs_mount_addon.flags|fs_mount_l10n.flags|ui_language.flags, FCVAR_CHANGED )) - retval = true; - return retval; } diff --git a/engine/common/common.h b/engine/common/common.h index dee756a2..38657f73 100644 --- a/engine/common/common.h +++ b/engine/common/common.h @@ -167,11 +167,6 @@ extern convar_t cl_filterstuffcmd; extern convar_t rcon_password; extern convar_t hpk_custom_file; extern convar_t con_gamemaps; -extern convar_t fs_mount_lv; -extern convar_t fs_mount_hd; -extern convar_t fs_mount_addon; -extern convar_t fs_mount_l10n; -extern convar_t ui_language; // historically used for UI, but now controls mounted localization directory #define Mod_AllowMaterials() ( host_allow_materials.value != 0.0f && !FBitSet( host.features, ENGINE_DISABLE_HDTEXTURES )) @@ -416,7 +411,8 @@ byte *FS_LoadFile( const char *path, fs_offset_t *filesizeptr, qboolean gamediro byte *FS_LoadDirectFile( const char *path, fs_offset_t *filesizeptr ) MALLOC_LIKE( _Mem_Free, 1 ) WARN_UNUSED_RESULT; void FS_Rescan_f( void ); -void FS_CheckConfig( void ); +void FS_LoadGameInfo( void ); +void FS_SaveVFSConfig( void ); // // cmd.c @@ -687,6 +683,8 @@ void pfnResetTutorMessageDecayData( void ); void Con_CompleteCommand( field_t *field ); void Cmd_AutoComplete( char *complete_string ); void Cmd_AutoCompleteClear( void ); +void Host_InitializeConfig( file_t *f, const char *config, const char *description ); +void Host_FinalizeConfig( file_t *f, const char *config ); // // custom.c diff --git a/engine/common/con_utils.c b/engine/common/con_utils.c index e54dbfa4..06cea9db 100644 --- a/engine/common/con_utils.c +++ b/engine/common/con_utils.c @@ -1403,8 +1403,15 @@ static void Cmd_WriteOpenGLVariables( file_t *f ) Cvar_LookupVars( FCVAR_GLCONFIG, NULL, f, (setpair_t)Cmd_WriteOpenGLCvar ); } -#if !XASH_DEDICATED -static void Host_FinalizeConfig( file_t *f, const char *config ) +void Host_InitializeConfig( file_t *f, const char *config, const char *description ) +{ + FS_Printf( f, "//=======================================================================\n"); + FS_Printf( f, "//\tGenerated by "XASH_ENGINE_NAME" (%i, %s, %s, %s-%s)\n", Q_buildnum(), g_buildcommit, g_buildbranch, Q_buildos(), Q_buildarch()); + FS_Printf( f, "//\t\t%s - %s\n", config, description ); + FS_Printf( f, "//=======================================================================\n" ); +} + +void Host_FinalizeConfig( file_t *f, const char *config ) { string backup, newcfg; @@ -1418,6 +1425,8 @@ static void Host_FinalizeConfig( file_t *f, const char *config ) FS_Rename( newcfg, config ); } + +#if !XASH_DEDICATED /* =============== Host_WriteConfig @@ -1438,10 +1447,7 @@ void Host_WriteConfig( void ) if( f ) { Con_Reportf( "%s()\n", __func__ ); - FS_Printf( f, "//=======================================================================\n"); - FS_Printf( f, "//\tGenerated by "XASH_ENGINE_NAME" (%i, %s, %s, %s-%s)\n", Q_buildnum(), g_buildcommit, g_buildbranch, Q_buildos(), Q_buildarch()); - FS_Printf( f, "//\t\tconfig.cfg - archive of cvars\n" ); - FS_Printf( f, "//=======================================================================\n" ); + Host_InitializeConfig( f, "config.cfg", "archive of cvars" ); Key_WriteBindings( f ); Cvar_WriteVariables( f, FCVAR_ARCHIVE ); Info_WriteVars( f ); @@ -1465,6 +1471,8 @@ void Host_WriteConfig( void ) else Con_DPrintf( S_ERROR "Couldn't write config.cfg.\n" ); NET_SaveMasters(); + + FS_SaveVFSConfig(); } /* @@ -1486,10 +1494,7 @@ void GAME_EXPORT Host_WriteServerConfig( const char *name ) if(( f = FS_Open( newconfigfile, "w", false )) != NULL ) { - FS_Printf( f, "//=======================================================================\n" ); - FS_Printf( f, "//\tGenerated by "XASH_ENGINE_NAME" (%i, %s, %s, %s-%s)\n", Q_buildnum(), g_buildcommit, g_buildbranch, Q_buildos(), Q_buildarch()); - FS_Printf( f, "//\t\tgame.cfg - multiplayer server temporare config\n" ); - FS_Printf( f, "//=======================================================================\n" ); + Host_InitializeConfig( f, "game.cfg", "multiplayer server temporary config" ); Cvar_WriteVariables( f, FCVAR_SERVER ); CSCR_WriteGameCVars( f, "settings.scr" ); @@ -1521,11 +1526,7 @@ void Host_WriteOpenGLConfig( void ) if( f ) { Con_Reportf( "%s()\n", __func__ ); - FS_Printf( f, "//=======================================================================\n" ); - FS_Printf( f, "//\tGenerated by "XASH_ENGINE_NAME" (%i, %s, %s, %s-%s)\n", Q_buildnum(), g_buildcommit, g_buildbranch, Q_buildos(), Q_buildarch()); - FS_Printf( f, "//\t\t%s - archive of renderer implementation cvars\n", name ); - FS_Printf( f, "//=======================================================================\n" ); - FS_Printf( f, "\n" ); + Host_InitializeConfig( f, name, "archive of renderer implementation cvars" ); Cmd_WriteOpenGLVariables( f ); Host_FinalizeConfig( f, name ); @@ -1551,10 +1552,7 @@ void Host_WriteVideoConfig( void ) if( f ) { Con_Reportf( "%s()\n", __func__ ); - FS_Printf( f, "//=======================================================================\n" ); - FS_Printf( f, "//\tGenerated by "XASH_ENGINE_NAME" (%i, %s, %s, %s-%s)\n", Q_buildnum(), g_buildcommit, g_buildbranch, Q_buildos(), Q_buildarch()); - FS_Printf( f, "//\t\tvideo.cfg - archive of renderer variables\n"); - FS_Printf( f, "//=======================================================================\n" ); + Host_InitializeConfig( f, "video.cfg", "archive of renderer variables" ); Cvar_WriteVariables( f, FCVAR_RENDERINFO ); Host_FinalizeConfig( f, "video.cfg" ); } @@ -1577,11 +1575,7 @@ void Key_EnumCmds_f( void ) f = FS_Open( "../help.txt", "w", false ); if( f ) { - FS_Printf( f, "//=======================================================================\n"); - FS_Printf( f, "//\tGenerated by "XASH_ENGINE_NAME" (%i, %s, %s, %s-%s)\n", Q_buildnum(), g_buildcommit, g_buildbranch, Q_buildos(), Q_buildarch()); - FS_Printf( f, "//\t\thelp.txt - xash commands and console variables\n"); - FS_Printf( f, "//=======================================================================\n"); - + Host_InitializeConfig( f, "help.txt", "xash commands and console variables" ); FS_Printf( f, "\n\n\t\t\tconsole variables\n\n"); Cvar_LookupVars( 0, NULL, f, (setpair_t)Cmd_WriteHelp ); FS_Printf( f, "\n\n\t\t\tconsole commands\n\n"); diff --git a/engine/common/filesystem_engine.c b/engine/common/filesystem_engine.c index fb46014b..6001c3e8 100644 --- a/engine/common/filesystem_engine.c +++ b/engine/common/filesystem_engine.c @@ -27,11 +27,11 @@ GNU General Public License for more details. #include "library.h" #include "platform/platform.h" -CVAR_DEFINE_AUTO( fs_mount_hd, "0", FCVAR_ARCHIVE|FCVAR_PRIVILEGED|FCVAR_LATCH, "mount high definition content folder" ); -CVAR_DEFINE_AUTO( fs_mount_lv, "0", FCVAR_ARCHIVE|FCVAR_PRIVILEGED|FCVAR_LATCH, "mount low violence models content folder" ); -CVAR_DEFINE_AUTO( fs_mount_addon, "0", FCVAR_ARCHIVE|FCVAR_PRIVILEGED|FCVAR_LATCH, "mount addon content folder" ); -CVAR_DEFINE_AUTO( fs_mount_l10n, "0", FCVAR_ARCHIVE|FCVAR_PRIVILEGED|FCVAR_LATCH, "mount localization content folder" ); -CVAR_DEFINE_AUTO( ui_language, "english", FCVAR_ARCHIVE|FCVAR_PRIVILEGED|FCVAR_LATCH, "selected game language" ); +static CVAR_DEFINE_AUTO( fs_mount_hd, "0", FCVAR_PRIVILEGED, "mount high definition content folder" ); +static CVAR_DEFINE_AUTO( fs_mount_lv, "0", FCVAR_PRIVILEGED, "mount low violence models content folder" ); +static CVAR_DEFINE_AUTO( fs_mount_addon, "0", FCVAR_PRIVILEGED, "mount addon content folder" ); +static CVAR_DEFINE_AUTO( fs_mount_l10n, "0", FCVAR_PRIVILEGED, "mount localization content folder" ); +static CVAR_DEFINE_AUTO( ui_language, "english", FCVAR_PRIVILEGED, "selected game language" ); fs_api_t g_fsapi; fs_globals_t *FI; @@ -81,7 +81,7 @@ void *FS_GetNativeObject( const char *obj ) return NULL; } -void FS_Rescan_f( void ) +static uint32_t FS_MountFlags( void ) { uint32_t flags = 0; @@ -91,15 +91,76 @@ void FS_Rescan_f( void ) if( fs_mount_addon.value ) SetBits( flags, FS_MOUNT_ADDON ); if( fs_mount_l10n.value ) SetBits( flags, FS_MOUNT_L10N ); - g_fsapi.Rescan( flags, ui_language.string ); + return flags; +} + +void FS_Rescan_f( void ) +{ + g_fsapi.Rescan( FS_MountFlags(), ui_language.string ); +} + +static void FS_LoadVFSConfig( const char *gamedir ) +{ + string parm; + + if( Host_IsDedicated( )) + return; + + Cbuf_AddTextf( "exec %s/vfs.cfg\n", gamedir ); + Cbuf_Execute(); + + if( Sys_GetParmFromCmdLine( "-language", parm )) + { + Cvar_DirectSet( &ui_language, parm ); + Cvar_DirectSet( &fs_mount_l10n, "1" ); + } - ClearBits( fs_mount_lv.flags, FCVAR_CHANGED ); ClearBits( fs_mount_hd.flags, FCVAR_CHANGED ); - ClearBits( fs_mount_addon.flags, FCVAR_CHANGED ); + ClearBits( fs_mount_lv.flags, FCVAR_CHANGED ); ClearBits( fs_mount_l10n.flags, FCVAR_CHANGED ); + ClearBits( fs_mount_addon.flags, FCVAR_CHANGED ); ClearBits( ui_language.flags, FCVAR_CHANGED ); } +void FS_SaveVFSConfig( void ) +{ + file_t *f; + + if( !FBitSet( fs_mount_hd.flags|fs_mount_lv.flags|fs_mount_l10n.flags|fs_mount_addon.flags|ui_language.flags, FCVAR_CHANGED )) + { + Con_Reportf( "%s: no need to save vfs.cfg\n", __func__ ); + return; + } + + f = FS_Open( "vfs.cfg.new", "w", true ); + if( !f ) + { + Con_Printf( S_ERROR "%s: couldn't open vfs.cfg for write\n", __func__ ); + return; + } + + FS_Printf( f, "%s \"%d\"\n", fs_mount_hd.name, (int)fs_mount_hd.value ); + FS_Printf( f, "%s \"%d\"\n", fs_mount_lv.name, (int)fs_mount_lv.value ); + FS_Printf( f, "%s \"%d\"\n", fs_mount_l10n.name, (int)fs_mount_l10n.value ); + FS_Printf( f, "%s \"%d\"\n", fs_mount_addon.name, (int)fs_mount_addon.value ); + FS_Printf( f, "%s \"%s\"\n", ui_language.name, ui_language.string ); + + Host_FinalizeConfig( f, "vfs.cfg" ); + + ClearBits( fs_mount_hd.flags, FCVAR_CHANGED ); + ClearBits( fs_mount_lv.flags, FCVAR_CHANGED ); + ClearBits( fs_mount_l10n.flags, FCVAR_CHANGED ); + ClearBits( fs_mount_addon.flags, FCVAR_CHANGED ); + ClearBits( ui_language.flags, FCVAR_CHANGED ); +} + +void FS_LoadGameInfo( void ) +{ + FS_LoadVFSConfig( g_fsapi.Gamedir( )); + + g_fsapi.LoadGameInfo( FS_MountFlags(), ui_language.string ); +} + static void FS_ClearPaths_f( void ) { FS_ClearSearchPath(); @@ -196,10 +257,7 @@ static qboolean FS_DetermineRootDirectory( char *out, size_t size ) return true; } -#if XASH_EMSCRIPTEN - Q_strncpy( out, "/rwdir", size ); - return true; -#elif TARGET_OS_IOS +#if TARGET_OS_IOS Q_strncpy( out, IOS_GetDocsDir(), size ); return true; #elif XASH_ANDROID && XASH_SDL @@ -264,20 +322,9 @@ static qboolean FS_DetermineReadOnlyRootDirectory( char *out, size_t size ) return true; } -#if XASH_EMSCRIPTEN - Q_strncpy( out, "/rodir", size ); - return true; -#endif - return false; } -void FS_CheckConfig( void ) -{ - if( fs_mount_lv.value || fs_mount_hd.value || fs_mount_addon.value || fs_mount_l10n.value ) - FS_Rescan_f(); -} - /* ================ FS_Init diff --git a/engine/common/host.c b/engine/common/host.c index 375c0729..b5ee1fe4 100644 --- a/engine/common/host.c +++ b/engine/common/host.c @@ -148,6 +148,7 @@ static void Sys_PrintUsage( const char *exename ) O("-bugcomp [opts] ", "enable precise bug compatibility") O(" ", "will break games that don't require it") O(" ", "refer to engine documentation for more info") + O("-language ", "mount localization game directory") O("-disablehelp ", "disable this message") #if !XASH_DEDICATED O("-dedicated ", "run engine in dedicated mode") @@ -1158,7 +1159,7 @@ static void Host_InitCommon( int argc, char **argv, const char *progname, qboole Host_RunTests( 1 ); #endif - FS_LoadGameInfo( NULL ); + FS_LoadGameInfo(); Cvar_PostFSInit(); Image_CheckPaletteQ1 (); @@ -1322,7 +1323,6 @@ int EXPORT Host_Main( int argc, char **argv, const char *progname, int bChangeGa Cmd_RemoveCommand( "setgl" ); Cbuf_ExecStuffCmds(); // execute stuffcmds (commandline) SCR_CheckStartupVids(); // must be last - FS_CheckConfig(); if( Sys_GetParmFromCmdLine( "-timedemo", demoname )) Cbuf_AddTextf( "timedemo %s\n", demoname ); diff --git a/filesystem/filesystem.c b/filesystem/filesystem.c index 417a6dd0..9d2048fa 100644 --- a/filesystem/filesystem.c +++ b/filesystem/filesystem.c @@ -1368,6 +1368,21 @@ void FS_Rescan( uint32_t flags, const char *language ) FS_AddGameHierarchy( GI->gamefolder, FS_GAMEDIR_PATH | flags ); } +/* +=============== +FS_Gamedir + +Allows engine to know game directory before gameinfo is initialized +=============== +*/ +static const char *FS_Gamedir( void ) +{ + if( GI ) + return GI->gamefolder; + + return fs_gamedir; +} + /* ================ FS_LoadGameInfo @@ -1375,17 +1390,14 @@ FS_LoadGameInfo can be passed null arg ================ */ -void FS_LoadGameInfo( const char *rootfolder ) +static void FS_LoadGameInfo( uint32_t flags, const char *language ) { int i; // lock uplevel of gamedir for read\write FS_AllowDirectPaths( false ); - if( rootfolder ) - Q_strncpy( fs_gamedir, rootfolder, sizeof( fs_gamedir )); - - Con_Reportf( "%s( %s )\n", __func__, fs_gamedir ); + Con_Reportf( "%s( %s, 0x%x, %s )\n", __func__, fs_gamedir, flags, language ); // clear any old paths FS_ClearSearchPath(); @@ -1410,7 +1422,7 @@ void FS_LoadGameInfo( const char *rootfolder ) FS_CreatePath( buf ); } - FS_Rescan( 0, NULL ); // create new filesystem + FS_Rescan( flags, language ); // create new filesystem } /* @@ -3461,6 +3473,7 @@ const fs_api_t g_api = FS_Path_f, // gameinfo utils + FS_Gamedir, FS_LoadGameInfo, // file ops diff --git a/filesystem/filesystem.h b/filesystem/filesystem.h index 2de28919..46621cc6 100644 --- a/filesystem/filesystem.h +++ b/filesystem/filesystem.h @@ -32,8 +32,8 @@ extern "C" { #endif // __cplusplus -#define FS_API_VERSION 3 // not stable yet! -#define FS_API_CREATEINTERFACE_TAG "XashFileSystem002" // follow FS_API_VERSION!!! +#define FS_API_VERSION 4 // not stable yet! +#define FS_API_CREATEINTERFACE_TAG "XashFileSystem004" // follow FS_API_VERSION!!! #define FILESYSTEM_INTERFACE_VERSION "VFileSystem009" // never change this! // search path flags @@ -167,7 +167,8 @@ typedef struct fs_api_t void (*Path_f)( void ); // gameinfo utils - void (*LoadGameInfo)( const char *rootfolder ); + const char *(*Gamedir)( void ); + void (*LoadGameInfo)( uint32_t flags, const char *language ); // file ops file_t *(*Open)( const char *filepath, const char *mode, qboolean gamedironly ); diff --git a/filesystem/filesystem_internal.h b/filesystem/filesystem_internal.h index ed7c241e..39023dfe 100644 --- a/filesystem/filesystem_internal.h +++ b/filesystem/filesystem_internal.h @@ -166,9 +166,6 @@ int FS_SetCurrentDirectory( const char *path ); qboolean FS_GetRootDirectory( char *path, size_t size ); void FS_Path_f( void ); -// gameinfo utils -void FS_LoadGameInfo( const char *rootfolder ); - // file ops int FS_Close( file_t *file ); file_t *FS_Open( const char *filepath, const char *mode, qboolean gamedironly ) diff --git a/filesystem/fscallback.h b/filesystem/fscallback.h index 6fb70e5c..d33d9e11 100644 --- a/filesystem/fscallback.h +++ b/filesystem/fscallback.h @@ -42,9 +42,6 @@ extern fs_globals_t *FI; #define FS_SetCurrentDirectory (*g_fsapi.SetCurrentDirectory) #define FS_Path_f (*g_fsapi.Path_f) -// gameinfo utils -#define FS_LoadGameInfo (*g_fsapi.LoadGameInfo) - // file ops #ifndef FSCALLBACK_OVERRIDE_MALLOC_LIKE #define FS_Open (*g_fsapi.Open)