diff --git a/code/client/client.h b/code/client/client.h index 131842a95d..b50feb9983 100644 --- a/code/client/client.h +++ b/code/client/client.h @@ -207,6 +207,15 @@ typedef struct { qhandle_t whiteShader; qhandle_t consoleShader; int consoleFont; + + // 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/code/qcommon/common.cpp b/code/qcommon/common.cpp index 41f2da9e55..0e64a80b97 100644 --- a/code/qcommon/common.cpp +++ b/code/qcommon/common.cpp @@ -1105,6 +1105,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/code/qcommon/files.cpp b/code/qcommon/files.cpp index c35d0e00f5..d105742d2f 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 ) { @@ -2704,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; @@ -2714,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 @@ -2748,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 @@ -2819,13 +2833,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 +2865,7 @@ void FS_Shutdown( void ) { Cmd_RemoveCommand( "fdir" ); Cmd_RemoveCommand( "touchFile" ); Cmd_RemoveCommand( "which" ); + Cmd_RemoveCommand( "fs_restart" ); } /* @@ -2935,6 +2951,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 +3015,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 17d75dc9bd..b1647f473b 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 39a226900a..6f678c738d 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/cgame/CMakeLists.txt b/codemp/cgame/CMakeLists.txt index 00348972a3..e9424d533d 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/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 294b9c3053..b64de77b0e 100644 --- a/codemp/client/cl_keys.cpp +++ b/codemp/client/cl_keys.cpp @@ -1361,25 +1361,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 dca664073a..0a3ac54269 100644 --- a/codemp/client/cl_main.cpp +++ b/codemp/client/cl_main.cpp @@ -106,6 +106,10 @@ cvar_t *cl_lanForcePackets; cvar_t *cl_drawRecording; +cvar_t *cl_downloadName; +cvar_t *cl_downloadPrompt; +cvar_t *cl_downloadOverlay; + cvar_t *cl_filterGames; vec3_t cl_windVec; @@ -1331,6 +1335,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) { @@ -1340,6 +1345,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 @@ -1388,13 +1394,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 ); @@ -1404,10 +1426,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(); + } } /* @@ -1421,8 +1446,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'; @@ -1487,6 +1514,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 @@ -1501,10 +1540,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; @@ -2298,6 +2342,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" ); } /* @@ -2736,7 +2783,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 ); @@ -2781,6 +2828,10 @@ void CL_Init( void ) { cl_filterGames = Cvar_Get( "cl_filterGames", "MBII MBIIOpenBeta", CVAR_ARCHIVE_ND, "List of fs_game to filter (space separated)" ); + 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" ); @@ -3742,3 +3793,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 fb1c18b884..c25fb6e966 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 59ac0c6c4e..ced59fcf44 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]; @@ -327,6 +331,15 @@ typedef struct clientStatic_s { qhandle_t whiteShader; qhandle_t consoleShader; int consoleFont; + + // Cursor + qboolean cursorActive; + qhandle_t cursorShader; + int cursorX; + int cursorY; + + // Engine menu + int menuFont; } clientStatic_t; #define CON_TEXTSIZE 0x30000 //was 32768 @@ -470,6 +483,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/codemp/game/CMakeLists.txt b/codemp/game/CMakeLists.txt index 28ae98bd01..4d653abc10 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/qcommon/common.cpp b/codemp/qcommon/common.cpp index 69061d16de..4b84c4d141 100644 --- a/codemp/qcommon/common.cpp +++ b/codemp/qcommon/common.cpp @@ -1196,6 +1196,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/codemp/qcommon/files.cpp b/codemp/qcommon/files.cpp index 7077b2d4c3..9529217d75 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. @@ -243,6 +244,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 @@ -312,6 +314,18 @@ 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; +} + +const char *get_filename_ext(const char *filename) { + const char *dot = strrchr(filename, '.'); + if (!dot || dot == filename) return ""; + return dot + 1; +} + /* ============== FS_Initialized @@ -1394,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 ) { @@ -2977,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 ) { @@ -2985,7 +3028,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); + } } /* @@ -2996,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; @@ -3006,7 +3056,7 @@ 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 // which happens on full installs @@ -3038,25 +3088,48 @@ 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] ); + filename = get_filename(pakfile); + + if ( ( pak = FS_LoadZipFile( pakfile, pakfiles[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)); + // 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); @@ -3148,9 +3221,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 } @@ -3182,41 +3258,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 { @@ -3231,7 +3319,7 @@ qboolean FS_ComparePaks( char *neededpaks, int len, qboolean dlstring ) { } } } - if ( *neededpaks ) { + if ( *neededpaks || badname ) { return qtrue; } @@ -3245,7 +3333,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; @@ -3272,7 +3360,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"); } } @@ -3297,6 +3386,7 @@ void FS_Shutdown( qboolean closemfp ) { Cmd_RemoveCommand( "fdir" ); Cmd_RemoveCommand( "touchFile" ); Cmd_RemoveCommand( "which" ); + Cmd_RemoveCommand( "fs_restart" ); #ifdef FS_MISSING if (closemfp) { @@ -3324,6 +3414,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 ) ); + } } /* @@ -3384,6 +3490,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; @@ -3405,6 +3582,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,12 +3633,29 @@ 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" ); 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 @@ -3470,6 +3666,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 @@ -3580,7 +3778,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 ) ); } } @@ -3659,7 +3857,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 ), " " ); } @@ -3827,6 +4025,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", ""); @@ -3857,10 +4056,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; @@ -3890,7 +4089,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"); @@ -4233,3 +4432,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..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 ); @@ -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" diff --git a/codemp/ui/CMakeLists.txt b/codemp/ui/CMakeLists.txt index c4ecb7bdbd..1d321430d1 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 diff --git a/shared/sdl/sdl_input.cpp b/shared/sdl/sdl_input.cpp index 59e940613b..74dce1db0b 100644 --- a/shared/sdl/sdl_input.cpp +++ b/shared/sdl/sdl_input.cpp @@ -1160,7 +1160,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( ); 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 fd103e713f..7c8dcf9127 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 ) { @@ -738,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 @@ -781,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) { diff --git a/shared/sys/sys_unix.cpp b/shared/sys/sys_unix.cpp index e3149819fc..849d11b219 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 4a8768130f..226c71e530 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)); + } } /*