From c24eecec72b4cc94deeb65d2bba0a58f3344e1f2 Mon Sep 17 00:00:00 2001 From: Daggolin Date: Mon, 6 Nov 2023 17:40:42 +0100 Subject: [PATCH 01/11] [MP] Add fs_forcegame cvar to override fs_game. --- codemp/qcommon/files.cpp | 38 +++++++++++++++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/codemp/qcommon/files.cpp b/codemp/qcommon/files.cpp index 7077b2d4c3..cf9ed5b48b 100644 --- a/codemp/qcommon/files.cpp +++ b/codemp/qcommon/files.cpp @@ -243,6 +243,7 @@ static cvar_t *fs_cdpath; static cvar_t *fs_copyfiles; static cvar_t *fs_gamedirvar; static cvar_t *fs_dirbeforepak; //rww - when building search path, keep directories at top and insert pk3's under them +static cvar_t *fs_forcegame; static searchpath_t *fs_searchpaths; static int fs_readCount; // total bytes read static int fs_loadCount; // total files read @@ -3324,6 +3325,22 @@ void FS_UpdateGamedir(void) FS_AddGameDirectory(fs_homepath->string, fs_gamedirvar->string); } } + + // forcegame allows users to override any fs_game settings + if ( fs_forcegame->string[0] && Q_stricmp(fs_forcegame->string, fs_gamedir) ) { + if ( !fs_basegame->string[0] || Q_stricmp(fs_forcegame->string, fs_basegame->string) ) { + if (fs_cdpath->string[0]) { + FS_AddGameDirectory(fs_cdpath->string, fs_forcegame->string); + } + if (fs_basepath->string[0]) { + FS_AddGameDirectory(fs_basepath->string, fs_forcegame->string); + } + if (fs_homepath->string[0] && Q_stricmp(fs_homepath->string,fs_basepath->string)) { + FS_AddGameDirectory(fs_homepath->string, fs_forcegame->string); + } + } + Q_strncpyz( fs_gamedir, fs_forcegame->string, sizeof( fs_gamedir ) ); + } } /* @@ -3405,6 +3422,8 @@ void FS_Startup( const char *gameName ) { fs_dirbeforepak = Cvar_Get("fs_dirbeforepak", "0", CVAR_INIT|CVAR_PROTECTED, "Prioritize directories before paks if not pure" ); + fs_forcegame = Cvar_Get ("fs_forcegame", "", CVAR_INIT, "Folder to use for overriding of fs_game (can not be set by the server)." ); + // add search path elements in reverse priority order (lowest priority first) if (fs_cdpath->string[0]) { FS_AddGameDirectory( fs_cdpath->string, gameName ); @@ -3454,6 +3473,22 @@ void FS_Startup( const char *gameName ) { } } + // forcegame allows users to override any fs_game settings + if ( fs_forcegame->string[0] && Q_stricmp(fs_forcegame->string, fs_gamedir) ) { + if ( !fs_basegame->string[0] || Q_stricmp(fs_forcegame->string, fs_basegame->string) ) { + if (fs_cdpath->string[0]) { + FS_AddGameDirectory(fs_cdpath->string, fs_forcegame->string); + } + if (fs_basepath->string[0]) { + FS_AddGameDirectory(fs_basepath->string, fs_forcegame->string); + } + if (fs_homepath->string[0] && Q_stricmp(fs_homepath->string,fs_basepath->string)) { + FS_AddGameDirectory(fs_homepath->string, fs_forcegame->string); + } + } + Q_strncpyz( fs_gamedir, fs_forcegame->string, sizeof( fs_gamedir ) ); + } + // add our commands Cmd_AddCommand ("path", FS_Path_f, "Lists search paths" ); Cmd_AddCommand ("dir", FS_Dir_f, "Lists a folder" ); @@ -3827,6 +3862,7 @@ void FS_InitFilesystem( void ) { #ifdef MACOS_X Com_StartupVariable( "fs_apppath" ); #endif + Com_StartupVariable( "fs_forcegame" ); if(!FS_FilenameCompare(Cvar_VariableString("fs_game"), BASEGAME)) Cvar_Set("fs_game", ""); @@ -3890,7 +3926,7 @@ void FS_Restart( int checksumFeed ) { Com_Error( ERR_FATAL, "Couldn't load mpdefault.cfg" ); } - if ( Q_stricmp(fs_gamedirvar->string, lastValidGame) ) { + if ( Q_stricmp(fs_gamedirvar->string, lastValidGame) && !fs_forcegame->string[0] ) { // skip the jampconfig.cfg if "safe" is on the command line if ( !Com_SafeMode() ) { Cbuf_AddText ("exec " Q3CONFIG_CFG "\n"); From 4539be30dac6e5246b94a6c5c1b7ab60e58435fe Mon Sep 17 00:00:00 2001 From: Daggolin Date: Mon, 6 Nov 2023 18:22:48 +0100 Subject: [PATCH 02/11] [MP] Prefix downloaded pk3s with the string "dl_" and only load downloaded pk3s when referenced by the server. Ported from jk2mv. --- codemp/qcommon/files.cpp | 99 +++++++++++++++++++++++++++++++--------- 1 file changed, 77 insertions(+), 22 deletions(-) diff --git a/codemp/qcommon/files.cpp b/codemp/qcommon/files.cpp index cf9ed5b48b..e9ad7bcc55 100644 --- a/codemp/qcommon/files.cpp +++ b/codemp/qcommon/files.cpp @@ -313,6 +313,12 @@ FILE* missingFiles = NULL; # endif #endif +const char *get_filename(const char *path) { + const char *slash = strrchr(path, PATH_SEP); + if (!slash || slash == path) return ""; + return slash + 1; +} + /* ============== FS_Initialized @@ -2986,7 +2992,15 @@ static int QDECL paksort( const void *a, const void *b ) { aa = *(char **)a; bb = *(char **)b; - return FS_PathCmp( aa, bb ); + // downloaded files have priority + // this is needed because otherwise even if a clientside was downloaded, there is no gurantee it is actually used. + if (!Q_stricmpn(aa, "dl_", 3) && Q_stricmpn(bb, "dl_", 3)) { + return 1; + } else if (Q_stricmpn(aa, "dl_", 3) && !Q_stricmpn(bb, "dl_", 3)) { + return -1; + } else { + return FS_PathCmp(aa, bb); + } } /* @@ -3008,6 +3022,7 @@ static void FS_AddGameDirectory( const char *path, const char *dir ) { int numfiles; char **pakfiles; char *sorted[MAX_PAKFILES]; + const char *filename; // this fixes the case where fs_basepath is the same as fs_cdpath // which happens on full installs @@ -3052,8 +3067,33 @@ static void FS_AddGameDirectory( const char *path, const char *dir ) { for ( i = 0 ; i < numfiles ; i++ ) { pakfile = FS_BuildOSPath( path, dir, sorted[i] ); + filename = get_filename(pakfile); + if ( ( pak = FS_LoadZipFile( pakfile, sorted[i] ) ) == 0 ) continue; + + // files beginning with "dl_" are only loaded when referenced by the server + if (!Q_stricmpn(filename, "dl_", 3)) { + int j; + qboolean found = qfalse; + + for (j = 0; j < fs_numServerReferencedPaks; j++) { + if (pak->checksum == fs_serverReferencedPaks[j]) { + // server wants it! + found = qtrue; + break; + } + } + + if (!found) { + // server has no interest in the file + unzClose(pak->handle); + Z_Free(pak->buildBuffer); + Z_Free(pak); + continue; + } + } + Q_strncpyz(pak->pakPathname, curpath, sizeof(pak->pakPathname)); // store the game name for downloading Q_strncpyz(pak->pakGamename, dir, sizeof(pak->pakGamename)); @@ -3149,9 +3189,12 @@ we are not interested in a download string format, we want something human-reada qboolean FS_ComparePaks( char *neededpaks, int len, qboolean dlstring ) { searchpath_t *sp; qboolean havepak; - char *origpos = neededpaks; int i; + char moddir[MAX_ZPATH], filename[MAX_ZPATH]; // If the sum of them exceed 255 characters the game won't accept them + qboolean badname = qfalse; + int read; + if ( !fs_numServerReferencedPaks ) { return qfalse; // Server didn't send any pack information along } @@ -3183,41 +3226,53 @@ qboolean FS_ComparePaks( char *neededpaks, int len, qboolean dlstring ) { if ( !havepak && fs_serverReferencedPakNames[i] && *fs_serverReferencedPakNames[i] ) { // Don't got it + read = sscanf(fs_serverReferencedPakNames[i], "%255[^'/']/%255s", moddir, filename); + + if ( read != 2 ) { + Com_Printf( "WARNING: Unable to parse pak name: %s\n", fs_serverReferencedPakNames[i] ); + badname = qtrue; + continue; + } if (dlstring) { - // We need this to make sure we won't hit the end of the buffer or the server could - // overwrite non-pk3 files on clients by writing so much crap into neededpaks that - // Q_strcat cuts off the .pk3 extension. + // To make sure we don't cut anything off we build the string for the current pak in a separate buffer, + // which must be able to hold fs_serverReferencedPakNames[i], the result of st and 6 more characters. As + // st is at least 8 characters longer than fs_serverReferencedPakNames[i] the doubled size of st should + // be enough. + char currentPak[MAX_ZPATH*2]; + + // Don't allow '@' in file names, because the download code splits by them + if ( strchr(fs_serverReferencedPakNames[i], '@') ) { + badname = qtrue; + continue; + } - origpos += strlen(origpos); + *currentPak = 0; // Remote name - Q_strcat( neededpaks, len, "@"); - Q_strcat( neededpaks, len, fs_serverReferencedPakNames[i] ); - Q_strcat( neededpaks, len, ".pk3" ); + Q_strcat( currentPak, sizeof(currentPak), "@"); + Q_strcat( currentPak, sizeof(currentPak), fs_serverReferencedPakNames[i] ); + Q_strcat( currentPak, sizeof(currentPak), ".pk3" ); // Local name - Q_strcat( neededpaks, len, "@"); + Q_strcat( currentPak, sizeof(currentPak), "@"); // Do we have one with the same name? - if ( FS_SV_FileExists( va( "%s.pk3", fs_serverReferencedPakNames[i] ) ) ) { - char st[MAX_ZPATH]; + if (FS_SV_FileExists(va("%s/dl_%s.pk3", moddir, filename))) { // We already have one called this, we need to download it to another name // Make something up with the checksum in it - Com_sprintf( st, sizeof( st ), "%s.%08x.pk3", fs_serverReferencedPakNames[i], fs_serverReferencedPaks[i] ); - Q_strcat( neededpaks, len, st ); + Q_strcat( currentPak, sizeof(currentPak), va("%s/dl_%s.%08x.pk3", moddir, filename, fs_serverReferencedPaks[i]) ); } else { - Q_strcat( neededpaks, len, fs_serverReferencedPakNames[i] ); - Q_strcat( neededpaks, len, ".pk3" ); + Q_strcat( currentPak, sizeof(currentPak), va("%s/dl_%s.pk3", moddir, filename) ); } - // Find out whether it might have overflowed the buffer and don't add this file to the - // list if that is the case. - if(strlen(origpos) + (origpos - neededpaks) >= (unsigned)(len - 1)) - { - *origpos = '\0'; + // If the currentPak doesn't fit the neededpaks buffer we are likely running into issues + if ( strlen(neededpaks) + strlen(currentPak) >= (size_t)len ) { + Com_Printf( S_COLOR_YELLOW "WARNING (FS_ComparePaks): referenced pk3 files cut off due to too long total length\n" ); break; } + + Q_strcat( neededpaks, len, currentPak ); } else { @@ -3232,7 +3287,7 @@ qboolean FS_ComparePaks( char *neededpaks, int len, qboolean dlstring ) { } } } - if ( *neededpaks ) { + if ( *neededpaks || badname ) { return qtrue; } From 7ff631688ca85f83110fb8de86b371f65ce7473c Mon Sep 17 00:00:00 2001 From: Daggolin Date: Mon, 6 Nov 2023 19:25:40 +0100 Subject: [PATCH 03/11] [MP] Add reflists and adjust reference conditions (ported from jk2mv). Add ref_whitelist.txt, ref_blacklist.txt and ref_forcelist.txt. Refactor reference handling to be more predictable (by making the reference conditions more explicit). --- codemp/qcommon/files.cpp | 188 ++++++++++++++++++++++++++++++------ codemp/qcommon/qcommon.h | 1 + codemp/server/sv_client.cpp | 61 +----------- 3 files changed, 164 insertions(+), 86 deletions(-) diff --git a/codemp/qcommon/files.cpp b/codemp/qcommon/files.cpp index e9ad7bcc55..13cf61ad36 100644 --- a/codemp/qcommon/files.cpp +++ b/codemp/qcommon/files.cpp @@ -210,6 +210,7 @@ typedef struct pack_s { int pure_checksum; // checksum for pure int numfiles; // number of files in pk3 int referenced; // referenced file flags + qboolean noref; // file is blacklisted for referencing int hashSize; // hash table size (power of 2) fileInPack_t* *hashTable; // hash table fileInPack_t* buildBuffer; // buffer with the filenames etc. @@ -319,6 +320,12 @@ const char *get_filename(const char *path) { return slash + 1; } +const char *get_filename_ext(const char *filename) { + const char *dot = strrchr(filename, '.'); + if (!dot || dot == filename) return ""; + return dot + 1; +} + /* ============== FS_Initialized @@ -1401,41 +1408,47 @@ long FS_FOpenFileRead( const char *filename, fileHandle_t *file, qboolean unique // The x86.dll suffixes are needed in order for sv_pure to continue to // work on non-x86/windows systems... - l = strlen( filename ); - if ( !(pak->referenced & FS_GENERAL_REF)) { - if( !FS_IsExt(filename, ".shader", l) && - !FS_IsExt(filename, ".txt", l) && - !FS_IsExt(filename, ".str", l) && - !FS_IsExt(filename, ".cfg", l) && - !FS_IsExt(filename, ".config", l) && - !FS_IsExt(filename, ".bot", l) && - !FS_IsExt(filename, ".arena", l) && - !FS_IsExt(filename, ".menu", l) && - !FS_IsExt(filename, ".fcf", l) && - Q_stricmp(filename, "jampgamex86.dll") != 0 && - //Q_stricmp(filename, "vm/qagame.qvm") != 0 && - !strstr(filename, "levelshots")) - { + // reference lists + if ( !pak->noref ) { + // JK2MV automatically references pk3's in three cases: + // 1. A .bsp file is loaded from it (and thus it is expected to be a map) + // 2. cgame.qvm or ui.qvm is loaded from it (expected to be a clientside) + // 3. pk3 is located in fs_game != base (standard jk2 behavior) + // All others need to be referenced manually by the use of reflists. + + if (!Q_stricmp(get_filename_ext(filename), "bsp")) { pak->referenced |= FS_GENERAL_REF; } - } - if (!(pak->referenced & FS_CGAME_REF)) - { - if ( Q_stricmp( filename, "cgame.qvm" ) == 0 || - Q_stricmp( filename, "cgamex86.dll" ) == 0 ) - { + if (!Q_stricmp(filename, "vm/cgame.qvm") || !Q_stricmp( filename, "cgamex86.dll" )) { pak->referenced |= FS_CGAME_REF; } - } - if (!(pak->referenced & FS_UI_REF)) - { - if ( Q_stricmp( filename, "ui.qvm" ) == 0 || - Q_stricmp( filename, "uix86.dll" ) == 0 ) - { + if (!Q_stricmp(filename, "vm/ui.qvm") || !Q_stricmp( filename, "uix86.dll" )) { pak->referenced |= FS_UI_REF; } + + // OLD Ref: + /* + l = strlen( filename ); + if ( !(pak->referenced & FS_GENERAL_REF)) { + if( !FS_IsExt(filename, ".shader", l) && + !FS_IsExt(filename, ".txt", l) && + !FS_IsExt(filename, ".str", l) && + !FS_IsExt(filename, ".cfg", l) && + !FS_IsExt(filename, ".config", l) && + !FS_IsExt(filename, ".bot", l) && + !FS_IsExt(filename, ".arena", l) && + !FS_IsExt(filename, ".menu", l) && + !FS_IsExt(filename, ".fcf", l) && + Q_stricmp(filename, "jampgamex86.dll") != 0 && + //Q_stricmp(filename, "vm/qagame.qvm") != 0 && + !strstr(filename, "levelshots")) + { + pak->referenced |= FS_GENERAL_REF; + } + } + */ } if ( uniqueFILE ) { @@ -3098,6 +3111,11 @@ static void FS_AddGameDirectory( const char *path, const char *dir ) { // store the game name for downloading Q_strncpyz(pak->pakGamename, dir, sizeof(pak->pakGamename)); + // if the pk3 is not in base, always reference it (standard jk2 behaviour) + if (Q_stricmpn(pak->pakGamename, BASEGAME, (int)strlen(BASEGAME))) { + pak->referenced |= FS_GENERAL_REF; + } + fs_packFiles += pak->numfiles; search = (searchpath_s *)Z_Malloc (sizeof(searchpath_t), TAG_FILESYS, qtrue); @@ -3456,6 +3474,77 @@ static void FS_ReorderPurePaks() @param gameName Name of the default folder (i.e. always BASEGAME = "base" in OpenJK) */ +void FS_LoadReflists( void ) { + char *mv_whitelist = NULL, *mv_blacklist = NULL, *mv_forcelist = NULL; + fileHandle_t f_w, f_b, f_f; + int f_wl, f_bl, f_fl; + int s; + searchpath_t *search; + + char packstr[MAX_OSPATH]; + + // reference lists + f_wl = FS_FOpenFileRead("ref_whitelist.txt", &f_w, qfalse); + f_bl = FS_FOpenFileRead("ref_blacklist.txt", &f_b, qfalse); + f_fl = FS_FOpenFileRead("ref_forcelist.txt", &f_f, qfalse); + + if (f_w) { + Com_Printf("using whitelist for referenced files...\n"); + + mv_whitelist = (char *)Hunk_AllocateTempMemory(1 + f_wl + 1); + mv_whitelist[0] = '\n'; + s = FS_Read(mv_whitelist + 1, f_wl, f_w); + mv_whitelist[s + 1] = 0; + } + + if (f_b) { + Com_Printf("using blacklist for referenced files...\n"); + + mv_blacklist = (char *)Hunk_AllocateTempMemory(1 + f_bl + 1); + mv_blacklist[0] = '\n'; + s = FS_Read(mv_blacklist + 1, f_bl, f_b); + mv_blacklist[s + 1] = 0; + } + + if (f_f) { + Com_Printf("using forcelist for referenced files...\n"); + + mv_forcelist = (char *)Hunk_AllocateTempMemory(1 + f_fl + 1); + mv_forcelist[0] = '\n'; + s = FS_Read(mv_forcelist + 1, f_fl, f_f); + mv_forcelist[s + 1] = 0; + } + + for (search = fs_searchpaths; search; search = search->next) { + if (search->pack) { + Com_sprintf(packstr, sizeof(packstr), "\n%s/%s.pk3", search->pack->pakGamename, search->pack->pakBasename); + + if (f_w && !Q_stristr(mv_whitelist, packstr)) { + search->pack->noref = qtrue; + search->pack->referenced = 0; + } else if (f_b && Q_stristr(mv_blacklist, packstr)) { + search->pack->noref = qtrue; + search->pack->referenced = 0; + } else if (f_f && Q_stristr(mv_forcelist, packstr)) { + search->pack->referenced |= FS_GENERAL_REF; + } + } + } + + if (f_w) { + FS_FCloseFile(f_w); + Hunk_FreeTempMemory(mv_whitelist); + } + if (f_b) { + FS_FCloseFile(f_b); + Hunk_FreeTempMemory(mv_blacklist); + } + if (f_f) { + FS_FCloseFile(f_f); + Hunk_FreeTempMemory(mv_forcelist); + } +} + void FS_Startup( const char *gameName ) { const char *homePath; @@ -3560,6 +3649,8 @@ void FS_Startup( const char *gameName ) { fs_gamedirvar->modified = qfalse; // We just loaded, it's not modified + FS_LoadReflists(); + Com_Printf( "----------------------\n" ); #ifdef FS_MISSING @@ -3670,7 +3761,7 @@ const char *FS_ReferencedPakChecksums( void ) { for ( search = fs_searchpaths ; search ; search = search->next ) { // is the element a pak file? if ( search->pack ) { - if (search->pack->referenced || Q_stricmpn(search->pack->pakGamename, BASEGAME, strlen(BASEGAME))) { + if (search->pack->referenced) { Q_strcat( info, sizeof( info ), va("%i ", search->pack->checksum ) ); } } @@ -3749,7 +3840,7 @@ const char *FS_ReferencedPakNames( void ) { for ( search = fs_searchpaths ; search ; search = search->next ) { // is the element a pak file? if ( search->pack ) { - if (search->pack->referenced || Q_stricmpn(search->pack->pakGamename, BASEGAME, strlen(BASEGAME))) { + if (search->pack->referenced) { if (*info) { Q_strcat(info, sizeof( info ), " " ); } @@ -4324,3 +4415,42 @@ qboolean FS_WriteToTemporaryFile( const void *data, size_t dataLength, char **te return qfalse; } + +// only referenced pk3 files can be downloaded +// returns the path to the GameData directory of the requested file. +const char *FS_MV_VerifyDownloadPath(const char *pk3file) { + char path[MAX_OSPATH]; + searchpath_t *search; + + for (search = fs_searchpaths; search; search = search->next) { + if (!search->pack) + continue; + + Com_sprintf(path, sizeof(path), "%s/%s", search->pack->pakGamename, search->pack->pakBasename); + if (FS_idPak(path, BASEGAME)) + continue; + + Q_strcat(path, sizeof(path), ".pk3"); + + if (!Q_stricmp(path, pk3file)) { + if (search->pack->noref) + return NULL; + + if (search->pack->referenced) { + static char gameDataPath[MAX_OSPATH]; + Q_strncpyz(gameDataPath, search->pack->pakFilename, sizeof(gameDataPath)); + + char *sp = strrchr(gameDataPath, PATH_SEP); + if ( sp ) *sp = 0; + else return NULL; + sp = strrchr(gameDataPath, PATH_SEP); + if ( sp ) *sp = 0; + else return NULL; + + return gameDataPath; + } + } + } + + return NULL; +} diff --git a/codemp/qcommon/qcommon.h b/codemp/qcommon/qcommon.h index c4dbdacfb9..47d53681ab 100644 --- a/codemp/qcommon/qcommon.h +++ b/codemp/qcommon/qcommon.h @@ -706,6 +706,7 @@ qboolean FS_ComparePaks( char *neededpaks, int len, qboolean dlstring ); void FS_Rename( const char *from, const char *to ); qboolean FS_WriteToTemporaryFile( const void *data, size_t dataLength, char **tempFileName ); +const char *FS_MV_VerifyDownloadPath(const char *pk3file); /* diff --git a/codemp/server/sv_client.cpp b/codemp/server/sv_client.cpp index 073fbbe6a4..fb2d4418ca 100644 --- a/codemp/server/sv_client.cpp +++ b/codemp/server/sv_client.cpp @@ -716,80 +716,27 @@ void SV_WriteDownloadToClient(client_t *cl, msg_t *msg) int curindex; int rate; int blockspersnap; - int unreferenced = 1; char errorMessage[1024]; - char pakbuf[MAX_QPATH], *pakptr; - int numRefPaks; if (!*cl->downloadName) return; // Nothing being downloaded if(!cl->download) { - qboolean idPack = qfalse; - qboolean missionPack = qfalse; - - // Chop off filename extension. - Com_sprintf(pakbuf, sizeof(pakbuf), "%s", cl->downloadName); - pakptr = strrchr(pakbuf, '.'); - - if(pakptr) - { - *pakptr = '\0'; - - // Check for pk3 filename extension - if(!Q_stricmp(pakptr + 1, "pk3")) - { - const char *referencedPaks = FS_ReferencedPakNames(); - - // Check whether the file appears in the list of referenced - // paks to prevent downloading of arbitrary files. - Cmd_TokenizeStringIgnoreQuotes(referencedPaks); - numRefPaks = Cmd_Argc(); - - for(curindex = 0; curindex < numRefPaks; curindex++) - { - if(!FS_FilenameCompare(Cmd_Argv(curindex), pakbuf)) - { - unreferenced = 0; - - // now that we know the file is referenced, - // check whether it's legal to download it. - missionPack = FS_idPak(pakbuf, "missionpack"); - idPack = missionPack; - idPack = (qboolean)(idPack || FS_idPak(pakbuf, BASEGAME)); - - break; - } - } - } - } + qboolean allowDownload = FS_MV_VerifyDownloadPath( cl->downloadName ) ? qtrue : qfalse; cl->download = 0; // We open the file here if ( !sv_allowDownload->integer || - idPack || unreferenced || + !allowDownload || ( cl->downloadSize = FS_SV_FOpenFileRead( cl->downloadName, &cl->download ) ) < 0 ) { // cannot auto-download file - if(unreferenced) + if( !allowDownload ) { Com_Printf("clientDownload: %d : \"%s\" is not referenced and cannot be downloaded.\n", (int) (cl - svs.clients), cl->downloadName); Com_sprintf(errorMessage, sizeof(errorMessage), "File \"%s\" is not referenced and cannot be downloaded.", cl->downloadName); - } - else if (idPack) { - Com_Printf("clientDownload: %d : \"%s\" cannot download id pk3 files\n", (int) (cl - svs.clients), cl->downloadName); - if(missionPack) - { - Com_sprintf(errorMessage, sizeof(errorMessage), "Cannot autodownload Team Arena file \"%s\"\n" - "The Team Arena mission pack can be found in your local game store.", cl->downloadName); - } - else - { - Com_sprintf(errorMessage, sizeof(errorMessage), "Cannot autodownload id pk3 file \"%s\"", cl->downloadName); - } - } - else if ( !sv_allowDownload->integer ) { + } else if ( !sv_allowDownload->integer ) { Com_Printf("clientDownload: %d : \"%s\" download disabled\n", (int) (cl - svs.clients), cl->downloadName); if (sv_pure->integer) { Com_sprintf(errorMessage, sizeof(errorMessage), "Could not download \"%s\" because autodownloading is disabled on the server.\n\n" From e3930aba64380bdb833354a880abbe92b4571a16 Mon Sep 17 00:00:00 2001 From: Daggolin Date: Mon, 6 Nov 2023 21:40:02 +0100 Subject: [PATCH 04/11] [Shared] Don't unpack/load native libraries from pk3s by default. As many users don't review the content of pk3s when installing mods and as cl_allowDownload can download pk3s from the server the pk3 files in the gamne folder should not be considered trustworthy enough to run native libraries from. In case a user really needs it the unpacking of native libraries can be reenabled with a protected init cvar: com_unpackLibraries. --- shared/sys/sys_main.cpp | 59 +++++++++++++++++++++++------------------ 1 file changed, 33 insertions(+), 26 deletions(-) diff --git a/shared/sys/sys_main.cpp b/shared/sys/sys_main.cpp index fd103e713f..eae2c29d6c 100644 --- a/shared/sys/sys_main.cpp +++ b/shared/sys/sys_main.cpp @@ -40,6 +40,7 @@ cvar_t *com_unfocused; cvar_t *com_maxfps; cvar_t *com_maxfpsMinimized; cvar_t *com_maxfpsUnfocused; +cvar_t *com_unpackLibraries; /* ================= @@ -164,6 +165,8 @@ void Sys_Init( void ) { #endif com_maxfpsUnfocused = Cvar_Get( "com_maxfpsUnfocused", "0", CVAR_ARCHIVE_ND ); com_maxfpsMinimized = Cvar_Get( "com_maxfpsMinimized", "50", CVAR_ARCHIVE_ND ); + + com_unpackLibraries = Cvar_Get( "com_unpackLibraries", "0", CVAR_INIT|CVAR_PROTECTED ); } static void NORETURN Sys_Exit( int ex ) { @@ -473,22 +476,24 @@ void *Sys_LoadLegacyGameDll( const char *name, VMMainProc **vmMain, SystemCallPr if ( !libHandle ) #endif { - UnpackDLLResult unpackResult = Sys_UnpackDLL(filename); - if ( !unpackResult.succeeded ) - { - if ( Sys_DLLNeedsUnpacking() ) + if ( com_unpackLibraries->integer ) { + UnpackDLLResult unpackResult = Sys_UnpackDLL(filename); + if ( !unpackResult.succeeded ) { - FreeUnpackDLLResult(&unpackResult); - Com_DPrintf( "Sys_LoadLegacyGameDll: Failed to unpack %s from PK3.\n", filename ); - return NULL; + if ( Sys_DLLNeedsUnpacking() ) + { + FreeUnpackDLLResult(&unpackResult); + Com_DPrintf( "Sys_LoadLegacyGameDll: Failed to unpack %s from PK3.\n", filename ); + return NULL; + } + } + else + { + libHandle = Sys_LoadLibrary(unpackResult.tempDLLPath); } - } - else - { - libHandle = Sys_LoadLibrary(unpackResult.tempDLLPath); - } - FreeUnpackDLLResult(&unpackResult); + FreeUnpackDLLResult(&unpackResult); + } if ( !libHandle ) { @@ -604,22 +609,24 @@ void *Sys_LoadGameDll( const char *name, GetModuleAPIProc **moduleAPI ) if ( !libHandle ) #endif { - UnpackDLLResult unpackResult = Sys_UnpackDLL(filename); - if ( !unpackResult.succeeded ) - { - if ( Sys_DLLNeedsUnpacking() ) + if ( com_unpackLibraries->integer ) { + UnpackDLLResult unpackResult = Sys_UnpackDLL(filename); + if ( !unpackResult.succeeded ) { - FreeUnpackDLLResult(&unpackResult); - Com_DPrintf( "Sys_LoadLegacyGameDll: Failed to unpack %s from PK3.\n", filename ); - return NULL; + if ( Sys_DLLNeedsUnpacking() ) + { + FreeUnpackDLLResult(&unpackResult); + Com_DPrintf( "Sys_LoadLegacyGameDll: Failed to unpack %s from PK3.\n", filename ); + return NULL; + } + } + else + { + libHandle = Sys_LoadLibrary(unpackResult.tempDLLPath); } - } - else - { - libHandle = Sys_LoadLibrary(unpackResult.tempDLLPath); - } - FreeUnpackDLLResult(&unpackResult); + FreeUnpackDLLResult(&unpackResult); + } if ( !libHandle ) { From 8690f739691a39c5e9ee3469988e9c173c56006d Mon Sep 17 00:00:00 2001 From: Daggolin Date: Mon, 6 Nov 2023 21:51:08 +0100 Subject: [PATCH 05/11] [Shared] Workaround for high kernel load when using many pk3s. As the filesystem is initialized before the network socket the file descriptors of the ip_socket turns out to be very high if many pk3s are loaded. When calling NET_Sleep the select call is used to sleep unless something arrives on the network socket. Due to its high number the highestfd value given to the select call is very high and covers alls open files, leading to high load on some systems. As a workaround this commit calls NET_Init before initializing the filesystem. --- code/qcommon/common.cpp | 3 +++ codemp/qcommon/common.cpp | 3 +++ shared/sys/sys_main.cpp | 2 -- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/code/qcommon/common.cpp b/code/qcommon/common.cpp index cb4fe06de2..7087c82557 100644 --- a/code/qcommon/common.cpp +++ b/code/qcommon/common.cpp @@ -1075,6 +1075,9 @@ void Com_Init( char *commandLine ) { com_homepath = Cvar_Get("com_homepath", "", CVAR_INIT); + // Init network before filesystem + NET_Init(); + FS_InitFilesystem (); //uses z_malloc //re.R_InitWorldEffects(); // this doesn't do much but I want to be sure certain variables are intialized. diff --git a/codemp/qcommon/common.cpp b/codemp/qcommon/common.cpp index 8cceb640e9..0d0cca1ae2 100644 --- a/codemp/qcommon/common.cpp +++ b/codemp/qcommon/common.cpp @@ -1166,6 +1166,9 @@ void Com_Init( char *commandLine ) { com_homepath = Cvar_Get("com_homepath", "", CVAR_INIT); + // Init network before filesystem + NET_Init(); + FS_InitFilesystem (); Com_InitJournaling(); diff --git a/shared/sys/sys_main.cpp b/shared/sys/sys_main.cpp index eae2c29d6c..541a83e082 100644 --- a/shared/sys/sys_main.cpp +++ b/shared/sys/sys_main.cpp @@ -788,8 +788,6 @@ int main ( int argc, char* argv[] ) Com_Printf( "SDL Version Linked: %d.%d.%d\n", linked.major, linked.minor, linked.patch ); #endif - NET_Init(); - // main game loop while (1) { From 19c162ca4a81630ae8272c34f10a4705f379d853 Mon Sep 17 00:00:00 2001 From: Daggolin Date: Mon, 6 Nov 2023 22:38:23 +0100 Subject: [PATCH 06/11] [Shared] Add fs_restart command to restart filesystem if no module holds a handle for a file inside of a pk3. --- code/qcommon/files.cpp | 34 ++++++++++++++++++++++++++++++---- code/qcommon/qcommon.h | 4 +++- code/ui/ui_main.cpp | 2 +- codemp/qcommon/files.cpp | 34 ++++++++++++++++++++++++++++++---- codemp/qcommon/qcommon.h | 4 ++-- 5 files changed, 66 insertions(+), 12 deletions(-) diff --git a/code/qcommon/files.cpp b/code/qcommon/files.cpp index c35d0e00f5..ab1ebbbd22 100644 --- a/code/qcommon/files.cpp +++ b/code/qcommon/files.cpp @@ -2685,6 +2685,29 @@ void FS_Which_f( void ) { Com_Printf( "File not found: \"%s\"\n", filename ); } +/* +============ +FS_Restart_f +============ +*/ +static qboolean FS_CanRestartInPlace( void ) { + int i; + for ( i = 1; i < MAX_FILE_HANDLES; i++ ) { + // If we have an active filehandle for a module that references a zip file we cannot restart. + if ( fsh[i].fileSize ) { + if ( fsh[i].zipFile ) return qfalse; + } + } + return qtrue; +} +static void FS_Restart_f( void ) { + if ( !FS_CanRestartInPlace() ) { + Com_Printf( "^3WARNING: Cannot restart file system due to active file handles for pk3 files inside of modules.\n" ); + return; + } + FS_Restart( qtrue ); +} + //=========================================================================== static int QDECL paksort( const void *a, const void *b ) { @@ -2819,13 +2842,14 @@ FS_Shutdown Frees all resources and closes all files ================ */ -void FS_Shutdown( void ) { +void FS_Shutdown( qboolean keepModuleFiles ) { searchpath_t *p, *next; int i; for(i = 0; i < MAX_FILE_HANDLES; i++) { if (fsh[i].fileSize) { - FS_FCloseFile(i); + if ( !keepModuleFiles ) FS_FCloseFile(i); + else if ( fsh[i].zipFile ) Com_Error(ERR_FATAL, "FS_Shutdown: tried to keep module files when at least one module file is inside of a pak"); } } @@ -2850,6 +2874,7 @@ void FS_Shutdown( void ) { Cmd_RemoveCommand( "fdir" ); Cmd_RemoveCommand( "touchFile" ); Cmd_RemoveCommand( "which" ); + Cmd_RemoveCommand( "fs_restart" ); } /* @@ -2935,6 +2960,7 @@ void FS_Startup( const char *gameName ) { Cmd_AddCommand ("fdir", FS_NewDir_f ); Cmd_AddCommand ("touchFile", FS_TouchFile_f ); Cmd_AddCommand ("which", FS_Which_f ); + Cmd_AddCommand ("fs_restart", FS_Restart_f ); // print the current search paths FS_Path_f(); @@ -2998,10 +3024,10 @@ void FS_InitFilesystem( void ) { FS_Restart ================ */ -void FS_Restart( void ) { +void FS_Restart( qboolean inPlace ) { // free anything we currently have loaded - FS_Shutdown(); + FS_Shutdown( inPlace ); // try to start up normally FS_Startup( BASEGAME ); diff --git a/code/qcommon/qcommon.h b/code/qcommon/qcommon.h index 70580daf9d..cc71c86a0a 100644 --- a/code/qcommon/qcommon.h +++ b/code/qcommon/qcommon.h @@ -445,7 +445,9 @@ issues. qboolean FS_Initialized(); void FS_InitFilesystem (void); -void FS_Shutdown( void ); +void FS_Shutdown( qboolean inPlace = qfalse ); + +void FS_Restart( qboolean inPlace = qfalse ); qboolean FS_ConditionalRestart( void ); diff --git a/code/ui/ui_main.cpp b/code/ui/ui_main.cpp index b6d4fed156..30f22ec16d 100644 --- a/code/ui/ui_main.cpp +++ b/code/ui/ui_main.cpp @@ -994,7 +994,7 @@ static qboolean UI_RunMenuScript ( const char **args ) if (uiInfo.modList[uiInfo.modIndex].modName) { Cvar_Set( "fs_game", uiInfo.modList[uiInfo.modIndex].modName); - extern void FS_Restart( void ); + extern void FS_Restart( qboolean inPlace = qfalse ); FS_Restart(); Cbuf_ExecuteText( EXEC_APPEND, "vid_restart;" ); } diff --git a/codemp/qcommon/files.cpp b/codemp/qcommon/files.cpp index 13cf61ad36..6892aca854 100644 --- a/codemp/qcommon/files.cpp +++ b/codemp/qcommon/files.cpp @@ -2997,6 +2997,29 @@ void FS_Which_f( void ) { Com_Printf( "File not found: \"%s\"\n", filename ); } +/* +============ +FS_Restart_f +============ +*/ +static qboolean FS_CanRestartInPlace( void ) { + int i; + for ( i = 1; i < MAX_FILE_HANDLES; i++ ) { + // If we have an active filehandle for a module that references a zip file we cannot restart. + if ( fsh[i].fileSize ) { + if ( fsh[i].zipFile ) return qfalse; + } + } + return qtrue; +} +static void FS_Restart_f( void ) { + if ( !FS_CanRestartInPlace() ) { + Com_Printf( "^3WARNING: Cannot restart file system due to active file handles for pk3 files inside of modules.\n" ); + return; + } + FS_Restart( fs_checksumFeed, qtrue ); +} + //=========================================================================== static int QDECL paksort( const void *a, const void *b ) { @@ -3319,7 +3342,7 @@ FS_Shutdown Frees all resources and closes all files ================ */ -void FS_Shutdown( qboolean closemfp ) { +void FS_Shutdown( qboolean closemfp, qboolean keepModuleFiles ) { searchpath_t *p, *next; int i; @@ -3346,7 +3369,8 @@ void FS_Shutdown( qboolean closemfp ) { for(i = 0; i < MAX_FILE_HANDLES; i++) { if (fsh[i].fileSize) { - FS_FCloseFile(i); + if ( !keepModuleFiles ) FS_FCloseFile(i); + else if ( fsh[i].zipFile ) Com_Error(ERR_FATAL, "FS_Shutdown: tried to keep module files when at least one module file is inside of a pak"); } } @@ -3371,6 +3395,7 @@ void FS_Shutdown( qboolean closemfp ) { Cmd_RemoveCommand( "fdir" ); Cmd_RemoveCommand( "touchFile" ); Cmd_RemoveCommand( "which" ); + Cmd_RemoveCommand( "fs_restart" ); #ifdef FS_MISSING if (closemfp) { @@ -3639,6 +3664,7 @@ void FS_Startup( const char *gameName ) { Cmd_AddCommand ("fdir", FS_NewDir_f, "Lists a folder with filters" ); Cmd_AddCommand ("touchFile", FS_TouchFile_f, "Touches a file" ); Cmd_AddCommand ("which", FS_Which_f, "Determines which search path a file was loaded from" ); + Cmd_AddCommand ("fs_restart", FS_Restart_f, "Restarts the filesystem if no module is currently using files from a pk3" ); // https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=506 // reorder the pure pk3 files according to server order @@ -4039,10 +4065,10 @@ void FS_InitFilesystem( void ) { FS_Restart ================ */ -void FS_Restart( int checksumFeed ) { +void FS_Restart( int checksumFeed, qboolean inPlace ) { // free anything we currently have loaded - FS_Shutdown(qfalse); + FS_Shutdown(qfalse, inPlace); // set the checksum feed fs_checksumFeed = checksumFeed; diff --git a/codemp/qcommon/qcommon.h b/codemp/qcommon/qcommon.h index 47d53681ab..1b5c0324c9 100644 --- a/codemp/qcommon/qcommon.h +++ b/codemp/qcommon/qcommon.h @@ -577,10 +577,10 @@ issues. qboolean FS_Initialized(); void FS_InitFilesystem (void); -void FS_Shutdown( qboolean closemfp ); +void FS_Shutdown( qboolean closemfp, qboolean keepModuleFiles = qfalse ); qboolean FS_ConditionalRestart( int checksumFeed ); -void FS_Restart( int checksumFeed ); +void FS_Restart( int checksumFeed, qboolean inPlace = qfalse ); // shutdown and restart the filesystem so changes to fs_gamedir can take effect char **FS_ListFiles( const char *directory, const char *extension, int *numfiles ); From fb888be098aaaadd2db3c304d8eb857ab3697537 Mon Sep 17 00:00:00 2001 From: Daggolin Date: Mon, 6 Nov 2023 22:52:52 +0100 Subject: [PATCH 07/11] [Shared] Remove hardcoded limit of 1024 pk3s. Try to increase the maximum allowed file descriptors to 4096 on start (can be overriden with "-maxfds" on launch). Ported from jk2mv. --- code/qcommon/files.cpp | 17 ++++------------- codemp/qcommon/files.cpp | 17 ++++------------- shared/sys/sys_local.h | 2 +- shared/sys/sys_main.cpp | 2 +- shared/sys/sys_unix.cpp | 26 +++++++++++++++++++++++++- shared/sys/sys_win32.cpp | 22 +++++++++++++++++++++- 6 files changed, 56 insertions(+), 30 deletions(-) diff --git a/code/qcommon/files.cpp b/code/qcommon/files.cpp index ab1ebbbd22..d105742d2f 100644 --- a/code/qcommon/files.cpp +++ b/code/qcommon/files.cpp @@ -2727,7 +2727,6 @@ Sets fs_gamedir, adds the directory to the head of the path, then loads the zip headers ================ */ -#define MAX_PAKFILES 1024 static void FS_AddGameDirectory( const char *path, const char *dir ) { searchpath_t *sp; int i; @@ -2737,7 +2736,6 @@ static void FS_AddGameDirectory( const char *path, const char *dir ) { char curpath[MAX_OSPATH + 1], *pakfile; int numfiles; char **pakfiles; - char *sorted[MAX_PAKFILES]; // this fixes the case where fs_basepath is the same as fs_cdpath // which happens on full installs @@ -2771,20 +2769,13 @@ static void FS_AddGameDirectory( const char *path, const char *dir ) { pakfiles = Sys_ListFiles( curpath, ".pk3", NULL, &numfiles, qfalse ); - // sort them so that later alphabetic matches override - // earlier ones. This makes pak1.pk3 override pak0.pk3 - if ( numfiles > MAX_PAKFILES ) { - numfiles = MAX_PAKFILES; + if ( numfiles > 1 ) { + qsort( pakfiles, numfiles, sizeof(char*), paksort ); } - for ( i = 0 ; i < numfiles ; i++ ) { - sorted[i] = pakfiles[i]; - } - - qsort( sorted, numfiles, sizeof(char*), paksort ); for ( i = 0 ; i < numfiles ; i++ ) { - pakfile = FS_BuildOSPath( path, dir, sorted[i] ); - if ( ( pak = FS_LoadZipFile( pakfile, sorted[i] ) ) == 0 ) + pakfile = FS_BuildOSPath( path, dir, pakfiles[i] ); + if ( ( pak = FS_LoadZipFile( pakfile, pakfiles[i] ) ) == 0 ) continue; Q_strncpyz(pak->pakPathname, curpath, sizeof(pak->pakPathname)); // store the game name for downloading diff --git a/codemp/qcommon/files.cpp b/codemp/qcommon/files.cpp index 6892aca854..9529217d75 100644 --- a/codemp/qcommon/files.cpp +++ b/codemp/qcommon/files.cpp @@ -3047,7 +3047,6 @@ Sets fs_gamedir, adds the directory to the head of the path, then loads the zip headers ================ */ -#define MAX_PAKFILES 1024 static void FS_AddGameDirectory( const char *path, const char *dir ) { searchpath_t *sp; int i; @@ -3057,7 +3056,6 @@ static void FS_AddGameDirectory( const char *path, const char *dir ) { char curpath[MAX_OSPATH + 1], *pakfile; int numfiles; char **pakfiles; - char *sorted[MAX_PAKFILES]; const char *filename; // this fixes the case where fs_basepath is the same as fs_cdpath @@ -3090,22 +3088,15 @@ static void FS_AddGameDirectory( const char *path, const char *dir ) { pakfiles = Sys_ListFiles( curpath, ".pk3", NULL, &numfiles, qfalse ); - // sort them so that later alphabetic matches override - // earlier ones. This makes pak1.pk3 override pak0.pk3 - if ( numfiles > MAX_PAKFILES ) { - numfiles = MAX_PAKFILES; + if ( numfiles > 1 ) { + qsort( pakfiles, numfiles, sizeof(char*), paksort ); } - for ( i = 0 ; i < numfiles ; i++ ) { - sorted[i] = pakfiles[i]; - } - - qsort( sorted, numfiles, sizeof(char*), paksort ); for ( i = 0 ; i < numfiles ; i++ ) { - pakfile = FS_BuildOSPath( path, dir, sorted[i] ); + pakfile = FS_BuildOSPath( path, dir, pakfiles[i] ); filename = get_filename(pakfile); - if ( ( pak = FS_LoadZipFile( pakfile, sorted[i] ) ) == 0 ) + if ( ( pak = FS_LoadZipFile( pakfile, pakfiles[i] ) ) == 0 ) continue; // files beginning with "dl_" are only loaded when referenced by the server diff --git a/shared/sys/sys_local.h b/shared/sys/sys_local.h index 815adc5eff..2a404f4c05 100644 --- a/shared/sys/sys_local.h +++ b/shared/sys/sys_local.h @@ -28,7 +28,7 @@ void IN_Frame( void ); void IN_Shutdown( void ); void IN_Restart( void ); -void Sys_PlatformInit( void ); +void Sys_PlatformInit( int argc, char *argv[] ); void Sys_PlatformExit( void ); qboolean Sys_GetPacket( netadr_t *net_from, msg_t *net_message ); char *Sys_ConsoleInput( void ); diff --git a/shared/sys/sys_main.cpp b/shared/sys/sys_main.cpp index 541a83e082..7c8dcf9127 100644 --- a/shared/sys/sys_main.cpp +++ b/shared/sys/sys_main.cpp @@ -745,7 +745,7 @@ int main ( int argc, char* argv[] ) int i; char commandLine[ MAX_STRING_CHARS ] = { 0 }; - Sys_PlatformInit(); + Sys_PlatformInit( argc, argv ); CON_Init(); // get the initial time base diff --git a/shared/sys/sys_unix.cpp b/shared/sys/sys_unix.cpp index 96f5b7aab3..aec3becab8 100644 --- a/shared/sys/sys_unix.cpp +++ b/shared/sys/sys_unix.cpp @@ -32,6 +32,7 @@ along with this program; if not, see . #include #include #include +#include #include "qcommon/qcommon.h" #include "qcommon/q_shared.h" @@ -42,7 +43,11 @@ qboolean stdinIsATTY = qfalse; // Used to determine where to store user-specific files static char homePath[ MAX_OSPATH ] = { 0 }; -void Sys_PlatformInit( void ) +// Max open file descriptors. Mostly used by pk3 files with +// MAX_SEARCH_PATHS limit. +#define MAX_OPEN_FILES 4096 + +void Sys_PlatformInit( int argc, char *argv[] ) { const char* term = getenv( "TERM" ); @@ -56,6 +61,25 @@ void Sys_PlatformInit( void ) stdinIsATTY = qtrue; else stdinIsATTY = qfalse; + + // raise open file limit to allow more pk3 files + int retval; + struct rlimit rlim; + rlim_t maxfds = MAX_OPEN_FILES; + + for (int i = 1; i + 1 < argc; i++) { + if (!Q_stricmp(argv[i], "-maxfds")) { + maxfds = atoi(argv[i + 1]); + } + } + + getrlimit(RLIMIT_NOFILE, &rlim); + rlim.rlim_cur = Q_min(maxfds, rlim.rlim_max); + retval = setrlimit(RLIMIT_NOFILE, &rlim); + + if (retval == -1) { + Com_Printf("Warning: Failed to raise open file limit. %s\n", strerror(errno)); + } } void Sys_PlatformExit( void ) diff --git a/shared/sys/sys_win32.cpp b/shared/sys/sys_win32.cpp index 5b6c234cf5..829b9b7467 100644 --- a/shared/sys/sys_win32.cpp +++ b/shared/sys/sys_win32.cpp @@ -542,7 +542,12 @@ Sys_PlatformInit Platform-specific initialization ================ */ -void Sys_PlatformInit( void ) { + +// Max open file descriptors. Mostly used by pk3 files with +// MAX_SEARCH_PATHS limit. +#define MAX_OPEN_FILES 4096 + +void Sys_PlatformInit( int argc, char *argv[] ) { TIMECAPS ptc; if ( timeGetDevCaps( &ptc, sizeof( ptc ) ) == MMSYSERR_NOERROR ) { @@ -558,6 +563,21 @@ void Sys_PlatformInit( void ) { } else timerResolution = 0; + + // raise open file limit to allow more pk3 files + int maxfds = MAX_OPEN_FILES; + + for (int i = 1; i + 1 < argc; i++) { + if (!Q_stricmp(argv[i], "-maxfds")) { + maxfds = atoi(argv[i + 1]); + } + } + + maxfds = _setmaxstdio(maxfds); + + if (maxfds == -1) { + Com_Printf("Warning: Failed to increase open file limit. %s\n", strerror(errno)); + } } /* From f536ef9da5b7fa622af43e33e7b9a1425b0f4ae5 Mon Sep 17 00:00:00 2001 From: Daggolin Date: Sat, 11 Nov 2023 06:24:52 +0100 Subject: [PATCH 08/11] [MP] Add download overlay. If cl_downloadPrompt is enabled users have to confirm each pk3 download now. If cl_downloadOverlay is enabled the window used for the confirmation prompt is used to display a progress bar and details on the current download. --- code/client/client.h | 9 + codemp/client/cl_input.cpp | 4 +- codemp/client/cl_keys.cpp | 11 +- codemp/client/cl_main.cpp | 355 ++++++++++++++++++++++++++++++++++++- codemp/client/cl_scrn.cpp | 3 + codemp/client/client.h | 17 ++ shared/sdl/sdl_input.cpp | 2 +- 7 files changed, 388 insertions(+), 13 deletions(-) diff --git a/code/client/client.h b/code/client/client.h index 178fe2a2b3..3e143cbbd5 100644 --- a/code/client/client.h +++ b/code/client/client.h @@ -206,6 +206,15 @@ typedef struct { qhandle_t charSetShader; qhandle_t whiteShader; qhandle_t consoleShader; + + // Cursor + qboolean cursorActive; + qhandle_t cursorShader; + int cursorX; + int cursorY; + + // Engine menu + int menuFont; } clientStatic_t; #define CON_TEXTSIZE 0x30000 //was 32768 diff --git a/codemp/client/cl_input.cpp b/codemp/client/cl_input.cpp index fbbf624281..647efee0f8 100644 --- a/codemp/client/cl_input.cpp +++ b/codemp/client/cl_input.cpp @@ -922,7 +922,9 @@ CL_MouseEvent ================= */ void CL_MouseEvent( int dx, int dy, int time ) { - if (g_clAutoMapMode && cls.cgameStarted) + if (cls.cursorActive) { + CL_UpdateCursorPosition( dx, dy ); + } else if (g_clAutoMapMode && cls.cgameStarted) { //automap input autoMapInput_t *data = (autoMapInput_t *)cl.mSharedMemory; diff --git a/codemp/client/cl_keys.cpp b/codemp/client/cl_keys.cpp index 02966bdf28..1bd9831a3a 100644 --- a/codemp/client/cl_keys.cpp +++ b/codemp/client/cl_keys.cpp @@ -1360,25 +1360,28 @@ void CL_KeyDownEvent( int key, unsigned time ) return; } - UIVM_KeyEvent( key, qtrue ); + if ( !cls.cursorActive ) UIVM_KeyEvent( key, qtrue ); return; } // send the bound action - CL_ParseBinding( key, qtrue, time ); + if ( !cls.cursorActive ) CL_ParseBinding( key, qtrue, time ); // distribute the key down event to the appropriate handler // console if ( Key_GetCatcher() & KEYCATCH_CONSOLE ) Console_Key( key ); + else if ( cls.cursorActive ) { + CL_CursorButton( key ); + } // ui else if ( Key_GetCatcher() & KEYCATCH_UI ) { - if ( cls.uiStarted ) + if ( cls.uiStarted && !cls.cursorActive ) UIVM_KeyEvent( key, qtrue ); } // cgame else if ( Key_GetCatcher() & KEYCATCH_CGAME ) { - if ( cls.cgameStarted ) + if ( cls.cgameStarted && !cls.cursorActive ) CGVM_KeyEvent( key, qtrue ); } // chatbox diff --git a/codemp/client/cl_main.cpp b/codemp/client/cl_main.cpp index b5b07c6fa9..4a0b59394f 100644 --- a/codemp/client/cl_main.cpp +++ b/codemp/client/cl_main.cpp @@ -105,6 +105,10 @@ cvar_t *cl_lanForcePackets; cvar_t *cl_drawRecording; +cvar_t *cl_downloadName; +cvar_t *cl_downloadPrompt; +cvar_t *cl_downloadOverlay; + vec3_t cl_windVec; @@ -1328,6 +1332,7 @@ Called when all downloading has been completed ================= */ void CL_DownloadsComplete( void ) { + clc.downloadMenuActive = qfalse; // if we downloaded files we need to restart the file system if (clc.downloadRestart) { @@ -1337,6 +1342,7 @@ void CL_DownloadsComplete( void ) { // inform the server so we get new gamestate info CL_AddReliableCommand( "donedl", qfalse ); + clc.downloadFinished = qtrue; // by sending the donedl command we request a new gamestate // so we don't want to load stuff yet @@ -1385,13 +1391,29 @@ game directory. ================= */ -void CL_BeginDownload( const char *localName, const char *remoteName ) { +void CL_BeginDownloadConfirm( void ) { + clc.downloadWaitingOnUser = qfalse; + + if ( !cl_downloadOverlay->integer ) { + clc.downloadMenuActive = qfalse; + } Com_DPrintf("***** CL_BeginDownload *****\n" "Localname: %s\n" "Remotename: %s\n" - "****************************\n", localName, remoteName); + "****************************\n", clc.downloadName, cl_downloadName->string); + + clc.downloadBlock = 0; // Starting new file + clc.downloadCount = 0; + clc.downloadTime = cls.realtime; + // Set current time to make sure the module knows the real start time after the delay + Cvar_SetValue( "cl_downloadTime", (float) cls.realtime ); + + CL_AddReliableCommand( va("download %s", cl_downloadName->string), qfalse ); +} + +void CL_BeginDownload( const char *localName, const char *remoteName ) { Q_strncpyz ( clc.downloadName, localName, sizeof(clc.downloadName) ); Com_sprintf( clc.downloadTempName, sizeof(clc.downloadTempName), "%s.tmp", localName ); @@ -1401,10 +1423,13 @@ void CL_BeginDownload( const char *localName, const char *remoteName ) { Cvar_Set( "cl_downloadCount", "0" ); Cvar_SetValue( "cl_downloadTime", (float) cls.realtime ); - clc.downloadBlock = 0; // Starting new file - clc.downloadCount = 0; - - CL_AddReliableCommand( va("download %s", remoteName), qfalse ); + // Prompt the user (unless they disabled it) + if ( cl_downloadPrompt->integer ) { + clc.downloadMenuActive = qtrue; + clc.downloadWaitingOnUser = qtrue; + } else { + CL_BeginDownloadConfirm(); + } } /* @@ -1418,8 +1443,10 @@ void CL_NextDownload(void) { char *s; char *remoteName, *localName; + clc.downloadWaitingOnUser = qfalse; + // A download has finished, check whether this matches a referenced checksum - if(*clc.downloadName) + if(*clc.downloadName && clc.downloadSize) { char *zippath = FS_BuildOSPath(Cvar_VariableString("fs_homepath"), clc.downloadName, ""); zippath[strlen(zippath)-1] = '\0'; @@ -1484,6 +1511,18 @@ and determine if we need to download them void CL_InitDownloads(void) { char missingfiles[1024]; + if ( clc.downloadFinished ) { + // If we just finished a download with a "donedl" we are getting another gamestate and we would be asked to + // download skipped files again. To avoid this we just skip this one... + clc.downloadFinished = qfalse; + CL_DownloadsComplete(); + return; + } + + if ( cl_downloadOverlay->integer ) { + clc.downloadMenuActive = qtrue; + } + if ( !cl_allowDownload->integer ) { // autodownload is disabled on the client @@ -2294,6 +2333,9 @@ void CL_InitRenderer( void ) { cls.consoleShader = re->RegisterShader( "console" ); g_console_field_width = cls.glconfig.vidWidth / SMALLCHAR_WIDTH - 2; g_consoleField.widthInChars = g_console_field_width; + + cls.cursorShader = re->RegisterShaderNoMip("cursor"); + cls.menuFont = re->RegisterFont( "ocr_a" ); } /* @@ -2774,6 +2816,10 @@ void CL_Init( void ) { cl_consoleKeys = Cvar_Get( "cl_consoleKeys", "~ ` 0x7e 0x60 0xb2", CVAR_ARCHIVE, "Which keys are used to toggle the console"); cl_consoleUseScanCode = Cvar_Get( "cl_consoleUseScanCode", "1", CVAR_ARCHIVE, "Use native console key detection" ); + cl_downloadName = Cvar_Get( "cl_downloadName", "", CVAR_INTERNAL ); + cl_downloadPrompt = Cvar_Get( "cl_downloadPrompt", "1", CVAR_ARCHIVE, "Confirm pk3 downloads from the server" ); + cl_downloadOverlay = Cvar_Get( "cl_downloadOverlay", "1", CVAR_ARCHIVE, "Draw download info overlay" ); + // userinfo Cvar_Get ("name", "Padawan", CVAR_USERINFO | CVAR_ARCHIVE_ND, "Player name" ); Cvar_Get ("rate", "25000", CVAR_USERINFO | CVAR_ARCHIVE, "Data rate" ); @@ -3718,3 +3764,298 @@ void CL_ShowIP_f(void) { Sys_ShowIP(); } +/* +================== + Internal Menu +================== +*/ + +void CL_DrawMenuRect( float x, float y, float width, float height, float borderSize, vec4_t elementBackgroundColor, vec4_t elementBorderColor ) { + // Draw the background + if ( elementBackgroundColor ) { + re->SetColor( elementBackgroundColor ); + re->DrawStretchPic( x, y, width, height, 0, 0, 0, 0, cls.whiteShader ); + } + + // Draw the borders + if ( elementBorderColor ) { + re->SetColor( elementBorderColor ); + re->DrawStretchPic( x, y, width, borderSize, 0, 0, 0, 0, cls.whiteShader ); + re->DrawStretchPic( x, y + height - borderSize, width, borderSize, 0, 0, 0, 0, cls.whiteShader ); + re->DrawStretchPic( x, y, borderSize, height, 0, 0, 0, 0, cls.whiteShader ); + re->DrawStretchPic( x + width - borderSize, y, borderSize, height, 0, 0, 0, 0, cls.whiteShader ); + } + + // Prevent color from leaking + re->SetColor( NULL ); +} + +void CL_DrawCenterStringAt( int x, int y, const char *str, int font, float scale ) { + int lenX = re->Font_StrLenPixels( str, font, scale ); + int lenY = re->Font_HeightPixels( font, scale ); + re->Font_DrawString( x - (lenX/2), y - (lenY/2), str, colorWhite, font, -1, scale ); +} + +typedef struct menuButton_s { + int x; + int y; + float width; + float height; + const char *text; + int font; + float scale; + void (*action)( void ); +} menuButton_t; +static menuButton_t menuButtons[16]; // No need for dynamic lists. We currently got exactly one menu with max. 3 buttons +static int menuButtonsActive = 0; + +static menuButton_t *CL_RegisterMenuButtonArea( int x, int y, float width, float height, const char *text, int font, float scale, void (*action)(void) ) { + // Too many buttons? Silently discard it to avoid console spam + if ( menuButtonsActive >= (int)ARRAY_LEN(menuButtons) ) return NULL; + + // Set the values + menuButtons[menuButtonsActive].x = x; + menuButtons[menuButtonsActive].y = y; + menuButtons[menuButtonsActive].width = width; + menuButtons[menuButtonsActive].height = height; + menuButtons[menuButtonsActive].text = text; + menuButtons[menuButtonsActive].font = font; + menuButtons[menuButtonsActive].scale = scale; + menuButtons[menuButtonsActive].action = action; + + // Increment counter + return &menuButtons[menuButtonsActive++]; +} + +static qboolean CL_IsCursorOnMenuButton( menuButton_t *button ) { + if ( cls.cursorX >= button->x && cls.cursorX <= button->x+button->width && cls.cursorY >= button->y && cls.cursorY <= button->y+button->height ) + return qtrue; + return qfalse; +} + +static void CL_DrawMenuButton( menuButton_t *button, vec4_t buttonBackgroundColor, vec4_t buttonBorderColor ) { + if ( !button ) return; + CL_DrawMenuRect( button->x, button->y, button->width, button->height, 3, buttonBackgroundColor, buttonBorderColor ); + CL_DrawCenterStringAt( button->x + (button->width/2), button->y + 5, button->text, button->font, button->scale ); +} + +static const char *CL_ByteCountToHumanString( int byteCount ) +{ + #define GB_BYTES (1024 * 1024 * 1024) + #define MB_BYTES (1024 * 1024) + #define KB_BYTES (1024) + + if ( byteCount > GB_BYTES ) return va( "%.02f GB", (float)byteCount / GB_BYTES ); + else if ( byteCount > MB_BYTES ) return va( "%.02f MB", (float)byteCount / MB_BYTES ); + else if ( byteCount > KB_BYTES ) return va( "%.1f KB", (float)byteCount / KB_BYTES ); + else return va( "%i B", byteCount ); +} + +static const char *CL_DurationSecToString( int duration ) +{ // SkyMod: Duration seconds to string + #define TIME_YEAR (60 * 60 * 24 * 365) + #define TIME_WEEK (60 * 60 * 24 * 7) + #define TIME_DAY (60 * 60 * 24) + #define TIME_HOUR (60 * 60) + #define TIME_MINUTE (60) + + static int call; + static char bufs[2][128]; + char *durationStr = bufs[call&1]; + int years = 0, weeks = 0, days = 0, hours = 0, minutes = 0, seconds = 0; + call++; + + while ( duration ) + { + if ( duration >= TIME_YEAR ) + { + duration -= TIME_YEAR; + years++; + } + else if ( duration >= TIME_WEEK ) + { + duration -= TIME_WEEK; + weeks++; + } + else if ( duration >= TIME_DAY ) + { + duration -= TIME_DAY; + days++; + } + else if ( duration >= TIME_HOUR ) + { + duration -= TIME_HOUR; + hours++; + } + else if ( duration >= TIME_MINUTE ) + { + duration -= TIME_MINUTE; + minutes++; + } + else + { + seconds = duration; + duration = 0; + } + } + + *durationStr = 0; + if ( years ) Q_strcat( durationStr, sizeof(bufs[0]), va("%iy ", years) ); + if ( weeks ) Q_strcat( durationStr, sizeof(bufs[0]), va("%iw ", weeks) ); + if ( days ) Q_strcat( durationStr, sizeof(bufs[0]), va("%id ", days) ); + if ( hours ) Q_strcat( durationStr, sizeof(bufs[0]), va("%ih ", hours) ); + if ( minutes ) Q_strcat( durationStr, sizeof(bufs[0]), va("%im ", minutes) ); + if ( seconds ) Q_strcat( durationStr, sizeof(bufs[0]), va("%is ", seconds) ); + + if ( *durationStr ) + { // Strip tailing space + char *ptr = durationStr; + while ( *ptr ) ptr++; + if ( *(ptr-1) == ' ' ) *(ptr-1) = 0; + } + + return durationStr; +} + +void CL_DrawDownloadRequest( void ) { + // Menu values + static vec4_t backgroundColor = { 0.1f, 0.2f, 0.45f, 0.9f }; + static vec4_t borderColor = { 0.05f, 0.1f, 0.35f, 1.0f }; + static float width = SCREEN_WIDTH / 6; + static float height = SCREEN_HEIGHT / 4; + + static float centerX = SCREEN_WIDTH / 2; + static float centerY = SCREEN_HEIGHT / 2; + + static float scale = 1.0f; + + // Button values + static vec4_t buttonBackgroundColor = { 0.1f, 0.1f, 0.4f, 0.9f }; + static vec4_t buttonBorderColor = { 0.05f, 0.05f, 0.2f, 1.0f }; + menuButton_t *button; + + // Draw frame + CL_DrawMenuRect( width, height, width*4, height*2, 6, backgroundColor, borderColor ); + + // Header + CL_DrawCenterStringAt( centerX, height + 10, "^1[ ^7File Download ^1]", cls.menuFont, scale ); + + // File name + CL_DrawCenterStringAt( centerX, centerY - (height/2), cl_downloadName->string, cls.menuFont, scale ); + + if ( clc.downloadWaitingOnUser ) { + // Tell user that we're waiting on their decision + CL_DrawCenterStringAt( centerX, centerY-10, "Do you want to download this file?", cls.menuFont, scale ); + CL_DrawCenterStringAt( centerX, centerY+10, "Please select an option", cls.menuFont, scale ); + } else { + // Draw Progress Bar + static vec4_t barBackgroundColor = { 0.1f, 0.8f, 0.4f, 0.9f }; + static vec4_t barBorderColor = { 0.05f, 0.6f, 0.2f, 1.0f }; + float dlFrac = clc.downloadSize ? (float)clc.downloadCount / clc.downloadSize : 0.0f; + + // Download speed + int dlTime = (float)(cls.realtime - clc.downloadTime) / 1000.0f; + static int dlRate; + static int dlLastTime; + static int dlLastCount; + + // Bar with percentage + CL_DrawCenterStringAt( centerX, centerY - 10, "Progress:", cls.menuFont, scale ); + CL_DrawMenuRect( centerX - width*2 + 10, centerY+5, (width * 4 - 20 - 3) * dlFrac, 20, 3, barBackgroundColor, NULL ); + CL_DrawMenuRect( centerX - width*2 + 10, centerY+5, width * 4 - 20 - 3, 20, 3, NULL, barBorderColor ); + CL_DrawCenterStringAt( centerX, centerY+10, va("%.02f%%", dlFrac * 100), cls.menuFont, scale ); + + // Draw size info + re->Font_DrawString( width + 10, centerY+30, va("File Size: %s", CL_ByteCountToHumanString(clc.downloadSize)) , colorWhite, cls.menuFont, -1, scale ); + re->Font_DrawString( width + 10, centerY+50, va("Downloaded: %s", CL_ByteCountToHumanString(clc.downloadCount)) , colorWhite, cls.menuFont, -1, scale ); + + // Download Speed + if ( dlTime >= 1 ) { + if ( dlTime != dlLastTime ) { + // Second passed, update measured values + dlRate = clc.downloadCount - dlLastCount; + dlLastTime = dlTime; + dlLastCount = clc.downloadCount; + } + + // Draw info texts + re->Font_DrawString( width + 10, centerY+70, va("Transfer Rate: %s/sec", CL_ByteCountToHumanString(dlRate)) , colorWhite, cls.menuFont, -1, scale ); + re->Font_DrawString( width + 10, centerY+90, va("Estimated Time Left: %s", dlRate ? CL_DurationSecToString(clc.downloadSize/dlRate - dlLastCount/dlRate) : "unknown" ) , colorWhite, cls.menuFont, -1, scale ); + } else { + // Set defaults - if we somehow don't get here and start with dlTime >= 1 we get incorrect values for one second, but that's okay + dlLastCount = clc.downloadCount; + dlLastTime = 0; + dlRate = 0; + + re->Font_DrawString( width + 10, centerY+70, "Transfer Rate: estimating" , colorWhite, cls.menuFont, -1, scale ); + re->Font_DrawString( width + 10, centerY+90, "Estimated Time Left: estimating" , colorWhite, cls.menuFont, -1, scale ); + } + } + + // Draw Buttons + if ( clc.downloadWaitingOnUser ) { + // Only show yes/no if we're waiting on a user decision + button = CL_RegisterMenuButtonArea( width + 10, (height * 3) - 30, 40, 20, "Yes", cls.menuFont, scale, CL_BeginDownloadConfirm ); + CL_DrawMenuButton( button, buttonBackgroundColor, buttonBorderColor ); + + button = CL_RegisterMenuButtonArea( width + 60, (height * 3) - 30, 40, 20, "No", cls.menuFont, scale, CL_NextDownload ); + CL_DrawMenuButton( button, buttonBackgroundColor, buttonBorderColor ); + } + + // The user can always abort if they change their mind + button = CL_RegisterMenuButtonArea( SCREEN_WIDTH - width - 13 - 80, (height * 3) - 30, 80, 20, "Abort", cls.menuFont, scale, CL_Disconnect_f ); + CL_DrawMenuButton( button, buttonBackgroundColor, buttonBorderColor ); +} + +void CL_DrawEngineMenus( void ) { + if ( clc.downloadMenuActive ) { + // Accept cursor movement + cls.cursorActive = qtrue; + } else { + // Disable movement + cls.cursorActive = qfalse; + } + + // Reset all menu buttons, the menu set them if required + menuButtonsActive = 0; + + // Draw menus + if ( clc.downloadMenuActive ) { + CL_DrawDownloadRequest(); + } + + // Draw cursor + if ( cls.cursorActive ) { + int i; + + // Re-draw the buttons the user is hovering over, but use a different color + static vec4_t buttonHoverBackground = { 0.75f, 0.5f, 0.0f, 1.0f }; + static vec4_t buttonHoverFrame = { 0.50f, 0.25f, 0.0f, 1.0f }; + for ( i = 0; i < menuButtonsActive; i++ ) { + if ( CL_IsCursorOnMenuButton(&menuButtons[i]) ) { + CL_DrawMenuButton( &menuButtons[i], buttonHoverBackground, buttonHoverFrame ); + } + } + + // Draw the actual cursor + re->DrawStretchPic( cls.cursorX, cls.cursorY, 48, 48, 0, 0, 1, 1, cls.cursorShader ); + } +} + +void CL_UpdateCursorPosition( int dx, int dy ) { + if ( Key_GetCatcher( ) & KEYCATCH_CONSOLE ) return; + + cls.cursorX = Com_Clampi( 0, SCREEN_WIDTH, cls.cursorX + dx ); + cls.cursorY = Com_Clampi( 0, SCREEN_HEIGHT, cls.cursorY + dy ); +} + +void CL_CursorButton( int key ) { + int i; + if ( key == A_MOUSE1 ) { + for ( i = 0; i < menuButtonsActive; i++ ) { + if ( CL_IsCursorOnMenuButton(&menuButtons[i]) ) { + menuButtons[i].action(); + } + } + } +} diff --git a/codemp/client/cl_scrn.cpp b/codemp/client/cl_scrn.cpp index d82d6c7c8a..0ad8bca3bc 100644 --- a/codemp/client/cl_scrn.cpp +++ b/codemp/client/cl_scrn.cpp @@ -472,6 +472,9 @@ void SCR_DrawScreenField( stereoFrame_t stereoFrame ) { UIVM_Refresh( cls.realtime ); } + // Engine internal menu + CL_DrawEngineMenus(); + // console draws next Con_DrawConsole (); diff --git a/codemp/client/client.h b/codemp/client/client.h index 4584b2d18a..98c441ecc3 100644 --- a/codemp/client/client.h +++ b/codemp/client/client.h @@ -223,6 +223,10 @@ typedef struct clientConnection_s { int downloadSize; // how many bytes we got char downloadList[MAX_INFO_STRING]; // list of paks we need to download qboolean downloadRestart; // if true, we need to do another FS_Restart because we downloaded a pak + qboolean downloadMenuActive; + qboolean downloadWaitingOnUser; + qboolean downloadFinished; + int downloadTime; // demo information char demoName[MAX_QPATH]; @@ -326,6 +330,15 @@ typedef struct clientStatic_s { qhandle_t charSetShader; qhandle_t whiteShader; qhandle_t consoleShader; + + // Cursor + qboolean cursorActive; + qhandle_t cursorShader; + int cursorX; + int cursorY; + + // Engine menu + int menuFont; } clientStatic_t; #define CON_TEXTSIZE 0x30000 //was 32768 @@ -456,6 +469,10 @@ int CL_ServerStatus( const char *serverAddress, char *serverStatusString, int ma qboolean CL_CheckPaused(void); +void CL_DrawEngineMenus( void ); +void CL_UpdateCursorPosition( int dx, int dy ); +void CL_CursorButton( int key ); + // // cl_input // diff --git a/shared/sdl/sdl_input.cpp b/shared/sdl/sdl_input.cpp index 5eab5e458f..389c623f29 100644 --- a/shared/sdl/sdl_input.cpp +++ b/shared/sdl/sdl_input.cpp @@ -1147,7 +1147,7 @@ void IN_Frame (void) { // Console is down in windowed mode IN_DeactivateMouse( ); } - else if( !cls.glconfig.isFullscreen && loading ) + else if( !cls.glconfig.isFullscreen && loading && !cls.cursorActive ) { // Loading in windowed mode IN_DeactivateMouse( ); From f0b68d1e6edb092647b8170fe1ee3e3d79e2afcf Mon Sep 17 00:00:00 2001 From: Daggolin Date: Sat, 11 Nov 2023 16:23:44 +0100 Subject: [PATCH 09/11] [MP] Default cl_allowDownload to 1, because cl_downloadPrompt is enabled by default and requires user confirmation. --- codemp/client/cl_main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codemp/client/cl_main.cpp b/codemp/client/cl_main.cpp index 4a0b59394f..a1c7e47a9a 100644 --- a/codemp/client/cl_main.cpp +++ b/codemp/client/cl_main.cpp @@ -2774,7 +2774,7 @@ void CL_Init( void ) { cl_showMouseRate = Cvar_Get ("cl_showmouserate", "0", 0); cl_framerate = Cvar_Get ("cl_framerate", "0", CVAR_TEMP); - cl_allowDownload = Cvar_Get ("cl_allowDownload", "0", CVAR_ARCHIVE_ND, "Allow downloading custom paks from server"); + cl_allowDownload = Cvar_Get ("cl_allowDownload", "1", CVAR_ARCHIVE_ND, "Allow downloading custom paks from server"); cl_allowAltEnter = Cvar_Get ("cl_allowAltEnter", "1", CVAR_ARCHIVE_ND, "Enables use of ALT+ENTER keyboard combo to toggle fullscreen" ); cl_autolodscale = Cvar_Get( "cl_autolodscale", "1", CVAR_ARCHIVE_ND ); From 718c31c241854e10d0cacbf0eb93aba282a8c418 Mon Sep 17 00:00:00 2001 From: Daggolin Date: Tue, 28 Nov 2023 20:05:32 +0100 Subject: [PATCH 10/11] [MP] Skip downloads if the server has disabled sv_allowDownload. --- codemp/client/cl_main.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/codemp/client/cl_main.cpp b/codemp/client/cl_main.cpp index a1c7e47a9a..9908e7f71a 100644 --- a/codemp/client/cl_main.cpp +++ b/codemp/client/cl_main.cpp @@ -1537,10 +1537,15 @@ void CL_InitDownloads(void) { } } else if ( FS_ComparePaks( clc.downloadList, sizeof( clc.downloadList ) , qtrue ) ) { + const char *serverInfo = cl.gameState.stringData + cl.gameState.stringOffsets[ CS_SERVERINFO ]; + const char *serverAllowDownloads = Info_ValueForKey( serverInfo, "sv_allowDownload" ); Com_Printf("Need paks: %s\n", clc.downloadList ); - if ( *clc.downloadList ) { + if ( serverAllowDownloads[0] && !atoi(serverAllowDownloads) ) { + // The server has an "sv_allowDownload" value set, but it's 0 + Com_Printf("Skipping downloads, because the server does not allow downloads\n"); + } else if ( *clc.downloadList ) { // if autodownloading is not enabled on the server cls.state = CA_CONNECTED; From 4fabac9f2b194cc926c920a7724e5e3552b52bcd Mon Sep 17 00:00:00 2001 From: Daggolin Date: Tue, 28 Nov 2023 20:23:03 +0100 Subject: [PATCH 11/11] [MP] Install OpenJK modules to base for win32. Since com_unpackLibraries defaults to 0 the game is not going to unpack the default libraries from the assets anymore, thus the OpenJK ones should now be used as default. --- codemp/cgame/CMakeLists.txt | 14 ++++++-------- codemp/game/CMakeLists.txt | 14 ++++++-------- codemp/ui/CMakeLists.txt | 14 ++++++-------- 3 files changed, 18 insertions(+), 24 deletions(-) diff --git a/codemp/cgame/CMakeLists.txt b/codemp/cgame/CMakeLists.txt index 3bb751c74a..22d61b3643 100644 --- a/codemp/cgame/CMakeLists.txt +++ b/codemp/cgame/CMakeLists.txt @@ -160,14 +160,12 @@ elseif(WIN32) RUNTIME DESTINATION "${JKAInstallDir}/OpenJK" COMPONENT ${JKAMPCoreComponent}) - if (WIN64) - # Don't do this on 32-bit Windows to avoid overwriting - # vanilla JKA's DLLs - install(TARGETS ${MPCGame} - RUNTIME - DESTINATION "${JKAInstallDir}/base" - COMPONENT ${JKAMPCoreComponent}) - endif() + + # Use OpenJK modules as default + install(TARGETS ${MPCGame} + RUNTIME + DESTINATION "${JKAInstallDir}/base" + COMPONENT ${JKAMPCoreComponent}) else() install(TARGETS ${MPCGame} LIBRARY diff --git a/codemp/game/CMakeLists.txt b/codemp/game/CMakeLists.txt index c1d4ae39ba..a550f7243d 100644 --- a/codemp/game/CMakeLists.txt +++ b/codemp/game/CMakeLists.txt @@ -229,14 +229,12 @@ elseif(WIN32) RUNTIME DESTINATION "${JKAInstallDir}/OpenJK" COMPONENT ${JKAMPCoreComponent}) - if (WIN64) - # Don't do this on 32-bit Windows to avoid overwriting - # vanilla JKA's DLLs - install(TARGETS ${MPGame} - RUNTIME - DESTINATION "${JKAInstallDir}/base" - COMPONENT ${JKAMPCoreComponent}) - endif() + + # Use OpenJK modules as default + install(TARGETS ${MPGame} + RUNTIME + DESTINATION "${JKAInstallDir}/base" + COMPONENT ${JKAMPCoreComponent}) else() install(TARGETS ${MPGame} LIBRARY diff --git a/codemp/ui/CMakeLists.txt b/codemp/ui/CMakeLists.txt index f44d4fcf4e..224a6be34c 100644 --- a/codemp/ui/CMakeLists.txt +++ b/codemp/ui/CMakeLists.txt @@ -113,14 +113,12 @@ elseif(WIN32) RUNTIME DESTINATION "${JKAInstallDir}/OpenJK" COMPONENT ${JKAMPCoreComponent}) - if (WIN64) - # Don't do this on 32-bit Windows to avoid overwriting - # vanilla JKA's DLLs - install(TARGETS ${MPUI} - RUNTIME - DESTINATION "${JKAInstallDir}/base" - COMPONENT ${JKAMPCoreComponent}) - endif() + + # Use OpenJK modules as default + install(TARGETS ${MPUI} + RUNTIME + DESTINATION "${JKAInstallDir}/base" + COMPONENT ${JKAMPCoreComponent}) else() install(TARGETS ${MPUI} LIBRARY