From 086cb6aab814a1c84182e1d93814898947292c45 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Tue, 5 May 2026 04:10:33 +0500 Subject: [PATCH 1/3] engine: move {gamedir}/downloaded directory to {gamedir}_downloads for compatibility Some mods like Sven-Coop 4.8 stupidly open game files directly and need this to load downloaded soundcache. --- engine/client/cl_main.c | 6 ------ engine/common/com_strings.h | 4 ++-- engine/common/http/net_http_xash.c | 31 ++++++++++++++++++++++++------ engine/common/net_chan.c | 29 ++++++++++++++++------------ filesystem/VFileSystem009.cpp | 2 +- filesystem/filesystem.c | 2 +- 6 files changed, 46 insertions(+), 28 deletions(-) diff --git a/engine/client/cl_main.c b/engine/client/cl_main.c index c15c0f7c..cb7aa9c2 100644 --- a/engine/client/cl_main.c +++ b/engine/client/cl_main.c @@ -2956,12 +2956,6 @@ void CL_ProcessFile( qboolean successfully_received, const char *filename ) { if( filename[0] != '!' ) Con_Printf( "processing %s\n", filename ); - - if( !Q_strnicmp( filename, DEFAULT_DOWNLOADED_DIRECTORY, sizeof( DEFAULT_DOWNLOADED_DIRECTORY ) - 1 )) - { - // skip "downloaded/" part to avoid mismatch with needed resources list - filename += sizeof( DEFAULT_DOWNLOADED_DIRECTORY ) - 1; - } } else if( !successfully_received ) { diff --git a/engine/common/com_strings.h b/engine/common/com_strings.h index d9614117..74ca70e0 100644 --- a/engine/common/com_strings.h +++ b/engine/common/com_strings.h @@ -58,8 +58,8 @@ GNU General Public License for more details. // path to saved games #define DEFAULT_SAVE_DIRECTORY "save/" -// path to download games -#define DEFAULT_DOWNLOADED_DIRECTORY "downloaded/" +// goldsrc compatible suffix for downloads directory +#define DEFAULT_DOWNLOADED_DIRECTORY_SUFFIX "_downloads" // path to user mod directory #define DEFAULT_CUSTOM_DIRECTORY "custom/" diff --git a/engine/common/http/net_http_xash.c b/engine/common/http/net_http_xash.c index 24f8156e..ba2f32f4 100644 --- a/engine/common/http/net_http_xash.c +++ b/engine/common/http/net_http_xash.c @@ -101,6 +101,13 @@ static int HTTP_FileResolveNS( httpfile_t *file ); static int HTTP_FileSendRequest( httpfile_t *file ); static int HTTP_FileDecompress( httpfile_t *file ); +static const char *HTTP_DownloadPath( char *buf, size_t buflen, const char *path, qboolean incomplete ) +{ + Q_snprintf( buf, buflen, "../%s" DEFAULT_DOWNLOADED_DIRECTORY_SUFFIX "/%s%s", + GI->gamefolder, path, incomplete ? ".incomplete" : "" ); + return buf; +} + /* ============== HTTP_FreeFile @@ -110,7 +117,7 @@ Skip to next server/file */ static void HTTP_FreeFile( httpfile_t *file, qboolean error ) { - char incname[MAX_SYSPATH + 64]; // plus downloaded/ plus .incomplete + char incname[MAX_SYSPATH + 64]; // plus ../{gamedir}_downloads/ plus .incomplete qboolean was_open = false; file->blocktime = 0; @@ -132,7 +139,7 @@ static void HTTP_FreeFile( httpfile_t *file, qboolean error ) file->socket = -1; - Q_snprintf( incname, sizeof( incname ), DEFAULT_DOWNLOADED_DIRECTORY "%s.incomplete", file->path ); + HTTP_DownloadPath( incname, sizeof( incname ), file->path, true ); if( error ) { @@ -149,7 +156,9 @@ static void HTTP_FreeFile( httpfile_t *file, qboolean error ) if( http_autoremove.value == 1 ) // remove broken file { Con_Printf( S_ERROR "no servers to download %s\n", file->path ); + FS_AllowDirectPaths( true ); FS_Delete( incname ); + FS_AllowDirectPaths( false ); } else // autoremove disabled, keep file { @@ -161,14 +170,18 @@ static void HTTP_FreeFile( httpfile_t *file, qboolean error ) { if( file->compressed ) { + FS_AllowDirectPaths( true ); FS_Delete( incname ); + FS_AllowDirectPaths( false ); } else { // Success, rename and process file char name[MAX_SYSPATH]; - Q_snprintf( name, sizeof( name ), DEFAULT_DOWNLOADED_DIRECTORY "%s", file->path ); + HTTP_DownloadPath( name, sizeof( name ), file->path, false ); + FS_AllowDirectPaths( true ); FS_Rename( incname, name ); + FS_AllowDirectPaths( false ); } } @@ -195,9 +208,13 @@ static int HTTP_FileQueue( httpfile_t *file ) } Con_Reportf( "HTTP: Starting download %s from %s:%d\n", file->path, file->server->host, file->server->port ); - Q_snprintf( name, sizeof( name ), DEFAULT_DOWNLOADED_DIRECTORY "%s.incomplete", file->path ); + HTTP_DownloadPath( name, sizeof( name ), file->path, true ); - if( !( file->file = FS_Open( name, "wb+", true ))) + FS_AllowDirectPaths( true ); + file->file = FS_Open( name, "wb+", true ); + FS_AllowDirectPaths( false ); + + if( !file->file ) { Con_Printf( S_ERROR "HTTP: cannot open %s!\n", name ); HTTP_FreeFile( file, true ); @@ -458,7 +475,7 @@ static int HTTP_FileDecompress( httpfile_t *file ) data_in = Mem_Malloc( host.mempool, compressed_len + 1 ); data_out = Mem_Malloc( host.mempool, decompressed_len + 1 ); - Q_snprintf( name, sizeof( name ), DEFAULT_DOWNLOADED_DIRECTORY "%s", file->path ); + HTTP_DownloadPath( name, sizeof( name ), file->path, false ); memset( &decompress_stream, 0, sizeof( decompress_stream )); decompress_stream.total_in = decompress_stream.avail_in = compressed_len; @@ -483,7 +500,9 @@ static int HTTP_FileDecompress( httpfile_t *file ) if( zlib_result == Z_OK || zlib_result == Z_STREAM_END ) { + FS_AllowDirectPaths( true ); g_fsapi.WriteFile( name, data_out, decompressed_len ); + FS_AllowDirectPaths( false ); HTTP_FreeFile( file, false ); } else HTTP_FreeFile( file, true ); diff --git a/engine/common/net_chan.c b/engine/common/net_chan.c index 008c0266..fcff5bdb 100644 --- a/engine/common/net_chan.c +++ b/engine/common/net_chan.c @@ -1240,20 +1240,24 @@ qboolean Netchan_CopyFileFragments( netchan_t *chan, sizebuf_t *msg ) return false; } - if( filename[0] != '!' ) - { - string temp_filename; - Q_snprintf( temp_filename, sizeof( temp_filename ), DEFAULT_DOWNLOADED_DIRECTORY "%s", filename ); - Q_strncpy( filename, temp_filename, sizeof( filename )); - } - Q_strncpy( chan->incomingfilename, filename, sizeof( chan->incomingfilename )); - if( filename[0] != '!' && FS_FileExists( filename, false )) + if( filename[0] != '!' ) { - Con_Printf( S_ERROR "can't download %s, already exists\n", filename ); - Netchan_FlushIncoming( chan, FRAG_FILE_STREAM ); - return true; + string write_path; + Q_snprintf( write_path, sizeof( write_path ), "../%s" DEFAULT_DOWNLOADED_DIRECTORY_SUFFIX "/%s", GI->gamefolder, filename ); + Q_strncpy( filename, write_path, sizeof( filename )); + + FS_AllowDirectPaths( true ); + qboolean exists = FS_FileExists( filename, false ); + FS_AllowDirectPaths( false ); + + if( exists ) + { + Con_Printf( S_ERROR "can't download %s, already exists\n", filename ); + Netchan_FlushIncoming( chan, FRAG_FILE_STREAM ); + return true; + } } // create file from buffers @@ -1339,8 +1343,9 @@ qboolean Netchan_CopyFileFragments( netchan_t *chan, sizebuf_t *msg ) } else { - // g-cont. it's will be stored downloaded files directly into game folder + FS_AllowDirectPaths( true ); FS_WriteFile( filename, buffer, nsize ); + FS_AllowDirectPaths( false ); Mem_Free( buffer ); } diff --git a/filesystem/VFileSystem009.cpp b/filesystem/VFileSystem009.cpp index 5d183e4f..1d6fad64 100644 --- a/filesystem/VFileSystem009.cpp +++ b/filesystem/VFileSystem009.cpp @@ -58,7 +58,7 @@ static inline const char *IdToDir( char *dir, size_t size, const char *id ) if( !Q_strcmp( id, "GAMEDOWNLOAD" )) { - Q_snprintf( dir, size, "%s/" DEFAULT_DOWNLOADED_DIRECTORY , GI->gamefolder ); + Q_snprintf( dir, size, "%s" DEFAULT_DOWNLOADED_DIRECTORY_SUFFIX, GI->gamefolder ); return dir; } diff --git a/filesystem/filesystem.c b/filesystem/filesystem.c index e69265b9..42d390c9 100644 --- a/filesystem/filesystem.c +++ b/filesystem/filesystem.c @@ -1314,7 +1314,7 @@ void FS_AddGameHierarchy( const char *dir, uint flags ) if( isGameDir ) { - Q_snprintf( buf, sizeof( buf ), "%s/" DEFAULT_DOWNLOADED_DIRECTORY, dir ); + Q_snprintf( buf, sizeof( buf ), "%s" DEFAULT_DOWNLOADED_DIRECTORY_SUFFIX "/", dir ); FS_AddGameDirectory( buf, FS_NOWRITE_PATH|FS_CUSTOM_PATH ); } Q_snprintf( buf, sizeof( buf ), "%s/", dir ); From 95cc6c44457ed1f3c3ceb12743647e204db0962d Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Tue, 5 May 2026 14:12:01 +0500 Subject: [PATCH 2/3] filesystem: small refactoring, remove double nullptr checks --- filesystem/filesystem.c | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/filesystem/filesystem.c b/filesystem/filesystem.c index 42d390c9..5e008a09 100644 --- a/filesystem/filesystem.c +++ b/filesystem/filesystem.c @@ -3204,16 +3204,6 @@ qboolean FS_Rename( const char *oldname, const char *newname ) if( FS_CheckNastyPath( oldname ) || FS_CheckNastyPath( newname )) return false; - if( !fs_writepath ) - return false; - - if( COM_StringEmptyOrNULL( oldname ) || COM_StringEmptyOrNULL( newname )) - return false; - - // no work done - if( !Q_stricmp( oldname, newname )) - return true; - // fix up slashes Q_strncpy( oldname2, oldname, sizeof( oldname2 )); Q_strncpy( newname2, newname, sizeof( newname2 )); @@ -3221,6 +3211,14 @@ qboolean FS_Rename( const char *oldname, const char *newname ) COM_FixSlashes( oldname2 ); COM_FixSlashes( newname2 ); + // no work done + if( !Q_stricmp( oldname2, newname2 )) + return true; + + // no writing directory is set, no changes should be made + if( !fs_writepath ) + return false; + // file does not exist if( !FS_FixFileCase( fs_writepath->dir, oldname2, oldpath, sizeof( oldpath ), false )) return false; @@ -3256,7 +3254,7 @@ qboolean GAME_EXPORT FS_Delete( const char *path ) if( FS_CheckNastyPath( path )) return false; - if( !fs_writepath || COM_StringEmptyOrNULL( path )) + if( !fs_writepath ) return false; Q_strncpy( path2, path, sizeof( path2 )); From e9e885534a800978de2129ce9b3654279a8f4c10 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Tue, 5 May 2026 14:12:41 +0500 Subject: [PATCH 3/3] filesystem: dir: hand off file existence check to the OS if path starts with .. or ../ --- filesystem/dir.c | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/filesystem/dir.c b/filesystem/dir.c index 46a3022f..b6f3d4a0 100644 --- a/filesystem/dir.c +++ b/filesystem/dir.c @@ -292,8 +292,6 @@ static inline qboolean FS_AppendToPath( char *dst, size_t *pi, const size_t len, qboolean FS_FixFileCase( dir_t *dir, const char *path, char *dst, const size_t len, qboolean createpath ) { - const char *prev; - const char *next; size_t i = 0; if( !FS_AppendToPath( dst, &i, len, dir->name, path, "init" )) @@ -303,7 +301,19 @@ qboolean FS_FixFileCase( dir_t *dir, const char *path, char *dst, const size_t l if( COM_StringEmpty( path )) return true; - for( prev = path, next = Q_strchrnul( prev, '/' ); + // we can't and shouldn't track parent directories to not track the whole filesystem + // exit early for this case + // FIXME: track the path to catch other cases + if( !Q_strncmp( path, "..", 2 ) && ( path[2] == '\0' || path[2] == '/' )) + { + if( !FS_AppendToPath( dst, &i, len, path, path, "escape to parent directory" )) + return false; + + // check file existense + return createpath ? true : FS_SysFileOrFolderExists( dst ); + } + + for( const char *prev = path, *next = Q_strchrnul( prev, '/' ); ; prev = next + 1, next = Q_strchrnul( prev, '/' )) {