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));
+ }
}
/*