From 4644db1c05e04af9764b4e473e187619a91b264e Mon Sep 17 00:00:00 2001 From: Andrey Nazarov Date: Tue, 9 Aug 2022 15:41:48 +0300 Subject: [PATCH 1/9] Move homedir/baseq2 search path before basedir/game. Files from game dir should always override baseq2. --- src/common/files.c | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/common/files.c b/src/common/files.c index f0176b904..84cd0cd96 100644 --- a/src/common/files.c +++ b/src/common/files.c @@ -3530,6 +3530,11 @@ static void setup_base_paths(void) // the GAME bit will be removed once gamedir is set, // and will be put back once gamedir is reset to basegame add_game_dir(FS_PATH_BASE | FS_PATH_GAME, "%s/"BASEGAME, sys_basedir->string); + + if (sys_homedir->string[0]) { + add_game_dir(FS_PATH_BASE | FS_PATH_GAME, "%s/"BASEGAME, sys_homedir->string); + } + fs_base_searchpaths = fs_searchpaths; } @@ -3544,7 +3549,6 @@ static void setup_game_paths(void) // home paths override system paths if (sys_homedir->string[0]) { - add_game_dir(FS_PATH_BASE, "%s/"BASEGAME, sys_homedir->string); add_game_dir(FS_PATH_GAME, "%s/%s", sys_homedir->string, fs_game->string); } @@ -3555,13 +3559,7 @@ static void setup_game_paths(void) // this var is set for compatibility with server browsers, etc Cvar_FullSet("gamedir", fs_game->string, CVAR_ROM | CVAR_SERVERINFO, FROM_CODE); - } else { - if (sys_homedir->string[0]) { - add_game_dir(FS_PATH_BASE | FS_PATH_GAME, - "%s/"BASEGAME, sys_homedir->string); - } - // add the game bit to base paths for (path = fs_base_searchpaths; path; path = path->next) { path->mode |= FS_PATH_GAME; From 1762ea60bd9cc8377839293e2961ccd0db720493 Mon Sep 17 00:00:00 2001 From: Andrey Nazarov Date: Sun, 21 Aug 2022 20:53:56 +0300 Subject: [PATCH 2/9] Fix resetting fs_gamedir after restart. Broken by 02f8ab66. --- src/common/files.c | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/common/files.c b/src/common/files.c index 84cd0cd96..aeb943f4e 100644 --- a/src/common/files.c +++ b/src/common/files.c @@ -3572,6 +3572,18 @@ static void setup_game_paths(void) Cvar_FullSet("fs_gamedir", fs_gamedir, CVAR_ROM, FROM_CODE); } +static void setup_base_gamedir(void) +{ + if (sys_homedir->string[0]) { + Q_snprintf(fs_gamedir, sizeof(fs_gamedir), "%s/"BASEGAME, sys_homedir->string); + } else { + Q_snprintf(fs_gamedir, sizeof(fs_gamedir), "%s/"BASEGAME, sys_basedir->string); + } +#ifdef _WIN32 + FS_ReplaceSeparators(fs_gamedir, '/'); +#endif +} + /* ================ FS_Restart @@ -3590,10 +3602,7 @@ void FS_Restart(bool total) } else { // just change gamedir free_game_paths(); - Q_snprintf(fs_gamedir, sizeof(fs_gamedir), "%s/"BASEGAME, sys_basedir->string); -#ifdef _WIN32 - FS_ReplaceSeparators(fs_gamedir, '/'); -#endif + setup_base_gamedir(); } setup_game_paths(); From 3c91ee283ff28dd7973dd9124b581387ae175ecd Mon Sep 17 00:00:00 2001 From: Andrey Nazarov Date: Tue, 13 Sep 2022 20:50:58 +0300 Subject: [PATCH 3/9] =?UTF-8?q?Complete=20possible=20=E2=80=98game?= =?UTF-8?q?=E2=80=99=20cvar=20values.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/common/files.c | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/src/common/files.c b/src/common/files.c index aeb943f4e..43329b883 100644 --- a/src/common/files.c +++ b/src/common/files.c @@ -3745,6 +3745,39 @@ static void fs_game_changed(cvar_t *self) Com_AddConfigFile(COM_POSTEXEC_CFG, FS_TYPE_REAL); } +static void list_dirs(genctx_t *ctx, const char *path) +{ + listfiles_t list = { + .flags = FS_SEARCH_DIRSONLY, + }; + + Sys_ListFiles_r(&list, path, 0); + + for (int i = 0; i < list.count; i++) { + char *s = list.files[i]; + + if (COM_IsPath(s)) + Prompt_AddMatch(ctx, s); + + Z_Free(s); + } + + Z_Free(list.files); +} + +static void fs_game_generator(genctx_t *ctx) +{ + ctx->ignoredups = true; +#ifdef _WIN32 + ctx->ignorecase = true; +#endif + + list_dirs(ctx, sys_basedir->string); + + if (sys_homedir->string[0]) + list_dirs(ctx, sys_homedir->string); +} + /* ================ FS_Init @@ -3768,6 +3801,7 @@ void FS_Init(void) // get the game cvar and start the filesystem fs_game = Cvar_Get("game", DEFGAME, CVAR_LATCH | CVAR_SERVERINFO); fs_game->changed = fs_game_changed; + fs_game->generator = fs_game_generator; fs_game_changed(fs_game); Com_Printf("-----------------------\n"); From ed1f07406622819decbbf0f6618e202fc76cacc2 Mon Sep 17 00:00:00 2001 From: Andrey Nazarov Date: Wed, 9 Nov 2022 17:53:15 +0300 Subject: [PATCH 4/9] Exec baseq2/autoexec.cfg after game dir change. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When changing game dir, default.cfg needs to be executed to update ‘newgame’ alias and such, so that single player menu works. Rather than skipping default.cfg and config.cfg if baseq2/autoexec.cfg exists, exec baseq2/autoexec.cfg after default.cfg and config.cfg. --- inc/common/files.h | 1 + src/common/common.c | 8 +------- src/common/files.c | 42 ++++++++++++++++++++++++++---------------- 3 files changed, 28 insertions(+), 23 deletions(-) diff --git a/inc/common/files.h b/inc/common/files.h index 78080cb44..ab528400d 100644 --- a/inc/common/files.h +++ b/inc/common/files.h @@ -107,6 +107,7 @@ typedef struct file_info_s { void FS_Init(void); void FS_Shutdown(void); void FS_Restart(bool total); +void FS_AddConfigFiles(bool init); #if USE_CLIENT int FS_RenameFile(const char *from, const char *to); diff --git a/src/common/common.c b/src/common/common.c index 07aab1e9a..ab20f15a7 100644 --- a/src/common/common.c +++ b/src/common/common.c @@ -969,13 +969,7 @@ void Qcommon_Init(int argc, char **argv) logfile_name->changed = logfile_param_changed; logfile_enable_changed(logfile_enable); - // execute configs: default.cfg and q2rtx.cfg may come from the packfile, but config.cfg - // and autoexec.cfg must be real files within the game directory - Com_AddConfigFile(COM_DEFAULT_CFG, 0); - Com_AddConfigFile(COM_Q2RTX_CFG, 0); - Com_AddConfigFile(COM_CONFIG_CFG, FS_TYPE_REAL | FS_PATH_GAME); - Com_AddConfigFile(COM_AUTOEXEC_CFG, FS_TYPE_REAL | FS_PATH_GAME); - Com_AddConfigFile(COM_POSTEXEC_CFG, FS_TYPE_REAL); + FS_AddConfigFiles(true); Com_AddEarlyCommands(true); diff --git a/src/common/files.c b/src/common/files.c index 43329b883..f612d5381 100644 --- a/src/common/files.c +++ b/src/common/files.c @@ -3680,6 +3680,31 @@ void FS_Shutdown(void) Cmd_Deregister(c_fs); } +/* +================ +FS_AddConfigFiles +================ +*/ +void FS_AddConfigFiles(bool init) +{ + int flag = init ? FS_PATH_ANY : FS_PATH_GAME; + + // default.cfg and q2rtx.cfg may come from packfile, but config.cfg and autoexec.cfg + // must be real files within the game directory. + Com_AddConfigFile(COM_DEFAULT_CFG, flag); + Com_AddConfigFile(COM_Q2RTX_CFG, FS_PATH_ANY); + Com_AddConfigFile(COM_CONFIG_CFG, FS_TYPE_REAL | flag); + + // autoexec.cfg is executed twice, first from basedir and then from + // gamedir. This ensures user settings configured in baseq2/autoexec.cfg + // override those in default.cfg and config.cfg. + if (*fs_game->string) + Com_AddConfigFile(COM_AUTOEXEC_CFG, FS_TYPE_REAL | FS_PATH_BASE); + Com_AddConfigFile(COM_AUTOEXEC_CFG, FS_TYPE_REAL | FS_PATH_GAME); + + Com_AddConfigFile(COM_POSTEXEC_CFG, FS_TYPE_REAL); +} + // this is called when local server starts up and gets it's latched variables, // client receives a serverdata packet, or user changes the game by hand while // disconnected @@ -3727,22 +3752,7 @@ static void fs_game_changed(cvar_t *self) // otherwise, restart the filesystem CL_RestartFilesystem(false); - Com_AddConfigFile(COM_DEFAULT_CFG, FS_PATH_GAME); - Com_AddConfigFile(COM_Q2RTX_CFG, 0); - Com_AddConfigFile(COM_CONFIG_CFG, FS_TYPE_REAL | FS_PATH_GAME); - - // If baseq2/autoexec.cfg exists exec it again after default.cfg and config.cfg. - // Assumes user prefers to do configuration via autoexec.cfg and hopefully - // settings and binds will be restored to their preference whenever gamedir changes after startup. - if(Q_stricmp(s, BASEGAME) && FS_FileExistsEx(COM_AUTOEXEC_CFG, FS_TYPE_REAL | FS_PATH_BASE)) { - Com_AddConfigFile(COM_AUTOEXEC_CFG, FS_TYPE_REAL | FS_PATH_BASE); - } - - // exec autoexec.cfg (must be a real file within the game directory) - Com_AddConfigFile(COM_AUTOEXEC_CFG, FS_TYPE_REAL | FS_PATH_GAME); - - // exec postexec.cfg (must be a real file) - Com_AddConfigFile(COM_POSTEXEC_CFG, FS_TYPE_REAL); + FS_AddConfigFiles(false); } static void list_dirs(genctx_t *ctx, const char *path) From 85b003be1b908cbbc106d47a3483c38b58d60b50 Mon Sep 17 00:00:00 2001 From: Andrey Nazarov Date: Thu, 10 Nov 2022 16:59:20 +0300 Subject: [PATCH 5/9] =?UTF-8?q?Reset=20=E2=80=98game=E2=80=99=20to=20empty?= =?UTF-8?q?=20string=20if=20set=20to=20BASEGAME.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Empty string means no game dir is set. Resetting to default is wrong if DEFGAME is not empty. --- src/common/files.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/files.c b/src/common/files.c index f612d5381..ef5e8b523 100644 --- a/src/common/files.c +++ b/src/common/files.c @@ -3715,7 +3715,7 @@ static void fs_game_changed(cvar_t *self) // validate it if (*s) { if (!Q_stricmp(s, BASEGAME)) { - Cvar_Reset(self); + Cvar_SetByVar(self, "", FROM_CODE); } else if (!COM_IsPath(s)) { Com_Printf("'%s' should contain characters [A-Za-z0-9_-] only.\n", self->name); Cvar_Reset(self); From 06a96b5f70ee122c356edc356e8bb6b50e7f4112 Mon Sep 17 00:00:00 2001 From: Andrey Nazarov Date: Thu, 10 Nov 2022 17:06:23 +0300 Subject: [PATCH 6/9] =?UTF-8?q?Never=20save=20=E2=80=98game=E2=80=99=20to?= =?UTF-8?q?=20config=20files.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Doing so could lead to infinite config exec loops. --- inc/common/cvar.h | 3 ++- src/common/files.c | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/inc/common/cvar.h b/inc/common/cvar.h index dad992b84..a03fe0de6 100644 --- a/inc/common/cvar.h +++ b/inc/common/cvar.h @@ -41,13 +41,14 @@ interface from being ambiguous. #define CVAR_CUSTOM (1 << 9) // created by user #define CVAR_WEAK (1 << 10) // doesn't have value #define CVAR_GAME (1 << 11) // created by game library +#define CVAR_NOARCHIVE (1 << 12) // never saved to config #define CVAR_FILES (1 << 13) // r_reload when changed #define CVAR_REFRESH (1 << 14) // vid_restart when changed #define CVAR_SOUND (1 << 15) // snd_restart when changed #define CVAR_INFOMASK (CVAR_USERINFO | CVAR_SERVERINFO) #define CVAR_MODIFYMASK (CVAR_INFOMASK | CVAR_FILES | CVAR_REFRESH | CVAR_SOUND) -#define CVAR_NOARCHIVEMASK (CVAR_NOSET | CVAR_CHEAT | CVAR_PRIVATE | CVAR_ROM) +#define CVAR_NOARCHIVEMASK (CVAR_NOSET | CVAR_CHEAT | CVAR_PRIVATE | CVAR_ROM | CVAR_NOARCHIVE) #define CVAR_EXTENDED_MASK (~31) extern cvar_t *cvar_vars; diff --git a/src/common/files.c b/src/common/files.c index ef5e8b523..2c834278d 100644 --- a/src/common/files.c +++ b/src/common/files.c @@ -3809,7 +3809,7 @@ void FS_Init(void) fs_shareware = Cvar_Get("fs_shareware", "0", CVAR_ROM); // get the game cvar and start the filesystem - fs_game = Cvar_Get("game", DEFGAME, CVAR_LATCH | CVAR_SERVERINFO); + fs_game = Cvar_Get("game", DEFGAME, CVAR_LATCH | CVAR_SERVERINFO | CVAR_NOARCHIVE); fs_game->changed = fs_game_changed; fs_game->generator = fs_game_generator; fs_game_changed(fs_game); From eb0afade4f630d680eb57bc881796fbf234bb65f Mon Sep 17 00:00:00 2001 From: Andrey Nazarov Date: Mon, 14 Nov 2022 14:44:42 +0300 Subject: [PATCH 7/9] =?UTF-8?q?Allow=20skipping=20configs=20on=20=E2=80=98?= =?UTF-8?q?game=E2=80=99=20change.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/common/files.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/common/files.c b/src/common/files.c index 2c834278d..e0d936e59 100644 --- a/src/common/files.c +++ b/src/common/files.c @@ -206,6 +206,8 @@ static int fs_count_strlwr; #define FS_COUNT_STRLWR (void)0 #endif +static cvar_t *fs_autoexec; + #if USE_DEBUG static cvar_t *fs_debug; #endif @@ -3689,6 +3691,9 @@ void FS_AddConfigFiles(bool init) { int flag = init ? FS_PATH_ANY : FS_PATH_GAME; + if (!init && !fs_autoexec->integer) + return; + // default.cfg and q2rtx.cfg may come from packfile, but config.cfg and autoexec.cfg // must be real files within the game directory. Com_AddConfigFile(COM_DEFAULT_CFG, flag); @@ -3802,6 +3807,8 @@ void FS_Init(void) Cmd_Register(c_fs); + fs_autoexec = Cvar_Get("fs_autoexec", "1", 0); + #if USE_DEBUG fs_debug = Cvar_Get("fs_debug", "0", 0); #endif From a4e9b77f51557025dc08b7cd5a2f4c2f9315ea2b Mon Sep 17 00:00:00 2001 From: Andrey Nazarov Date: Sat, 12 Aug 2023 14:12:21 +0300 Subject: [PATCH 8/9] Use FS_AllocTempMem()/FS_FreeTempMem() for PAK header data. --- src/common/files.c | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/src/common/files.c b/src/common/files.c index e0d936e59..44378db71 100644 --- a/src/common/files.c +++ b/src/common/files.c @@ -2151,39 +2151,39 @@ static pack_t *load_pak_file(const char *packfile) if (!fread(&header, sizeof(header), 1, fp)) { Com_SetLastError("reading header failed"); - goto fail; + goto fail1; } if (LittleLong(header.ident) != IDPAKHEADER) { Com_SetLastError("bad header ident"); - goto fail; + goto fail1; } header.dirlen = LittleLong(header.dirlen); if (header.dirlen > INT_MAX || header.dirlen % sizeof(dpackfile_t)) { Com_SetLastError("bad directory length"); - goto fail; + goto fail1; } num_files = header.dirlen / sizeof(dpackfile_t); if (num_files < 1) { Com_SetLastError("no files"); - goto fail; + goto fail1; } header.dirofs = LittleLong(header.dirofs); if (header.dirofs > INT_MAX) { Com_SetLastError("bad directory offset"); - goto fail; + goto fail1; } if (os_fseek(fp, header.dirofs, SEEK_SET)) { Com_SetLastError("seeking to directory failed"); - goto fail; + goto fail1; } - info = FS_Malloc(header.dirlen); + info = FS_AllocTempMem(header.dirlen); if (!fread(info, header.dirlen, 1, fp)) { Com_SetLastError("reading directory failed"); - goto fail; + goto fail2; } names_len = 0; @@ -2192,7 +2192,7 @@ static pack_t *load_pak_file(const char *packfile) dfile->filelen = LittleLong(dfile->filelen); if (dfile->filelen > INT_MAX || dfile->filepos > INT_MAX - dfile->filelen) { Com_SetLastError("file length or position too big"); - goto fail; + goto fail2; } names_len += Q_strnlen(dfile->name, sizeof(dfile->name)) + 1; } @@ -2227,11 +2227,12 @@ static pack_t *load_pak_file(const char *packfile) FS_DPrintf("%s: %u files, %u hash\n", packfile, pack->num_files, pack->hash_size); - Z_Free(info); - + FS_FreeTempMem(info); return pack; -fail: +fail2: + FS_FreeTempMem(info); +fail1: fclose(fp); Z_Free(info); return NULL; From 341c26d014bd159b8e6ee15c5bcb90426302f620 Mon Sep 17 00:00:00 2001 From: Andrey Nazarov Date: Sat, 14 Oct 2023 22:39:15 +0300 Subject: [PATCH 9/9] Skip Windows drive part in FS_CreatePath(). Apparently, mkdir("c:") is legal, but mkdir("c:/") gives EACCESS on Windows. So if path has double slashes after drive part, such as "c://foo/bar" FS_CreatePath() would attempt to mkdir("c:/") and fail. Change it to always skip the drive part (and leading slashes). Fixes #316. --- src/common/files.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/common/files.c b/src/common/files.c index 44378db71..cdb51fadd 100644 --- a/src/common/files.c +++ b/src/common/files.c @@ -637,6 +637,8 @@ int FS_CreatePath(char *path) ofs = p + 1; } } + } else if (Q_isalpha(*path) && path[1] == ':') { + ofs = path + 2; // skip drive part } #endif