dhewm3/neo/framework/async/AsyncClient.cpp
dhewg 736ec20d4d Untangle the epic precompiled.h mess
Don't include the lazy precompiled.h everywhere, only what's
required for the compilation unit.
platform.h needs to be included instead to provide all essential
defines and types.
All includes use the relative path to the neo or the game
specific root.
Move all idlib related includes from idlib/Lib.h to precompiled.h.
precompiled.h still exists for the MFC stuff in tools/.
Add some missing header guards.
2011-12-19 23:21:47 +01:00

2336 lines
71 KiB
C++

/*
===========================================================================
Doom 3 GPL Source Code
Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company.
This file is part of the Doom 3 GPL Source Code ("Doom 3 Source Code").
Doom 3 Source Code is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Doom 3 Source Code is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Doom 3 Source Code. If not, see <http://www.gnu.org/licenses/>.
In addition, the Doom 3 Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 Source Code. If not, please request a copy in writing from id Software at the address below.
If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
===========================================================================
*/
#include "sys/platform.h"
#include "idlib/LangDict.h"
#include "framework/async/AsyncNetwork.h"
#include "framework/Licensee.h"
#include "framework/Game.h"
#include "framework/Session_local.h"
#include "sound/sound.h"
#include "framework/async/AsyncClient.h"
const int SETUP_CONNECTION_RESEND_TIME = 1000;
const int EMPTY_RESEND_TIME = 500;
const int PREDICTION_FAST_ADJUST = 4;
/*
==================
idAsyncClient::idAsyncClient
==================
*/
idAsyncClient::idAsyncClient( void ) {
guiNetMenu = NULL;
updateState = UPDATE_NONE;
Clear();
}
/*
==================
idAsyncClient::Clear
==================
*/
void idAsyncClient::Clear( void ) {
active = false;
realTime = 0;
clientTime = 0;
clientId = 0;
clientDataChecksum = 0;
clientNum = 0;
clientState = CS_DISCONNECTED;
clientPrediction = 0;
clientPredictTime = 0;
serverId = 0;
serverChallenge = 0;
serverMessageSequence = 0;
lastConnectTime = -9999;
lastEmptyTime = -9999;
lastPacketTime = -9999;
lastSnapshotTime = -9999;
snapshotGameFrame = 0;
snapshotGameTime = 0;
snapshotSequence = 0;
gameInitId = GAME_INIT_ID_INVALID;
gameFrame = 0;
gameTimeResidual = 0;
gameTime = 0;
memset( userCmds, 0, sizeof( userCmds ) );
backgroundDownload.completed = true;
lastRconTime = 0;
showUpdateMessage = false;
lastFrameDelta = 0;
dlRequest = -1;
dlCount = -1;
memset( dlChecksums, 0, sizeof( int ) * MAX_PURE_PAKS );
currentDlSize = 0;
totalDlSize = 0;
}
/*
==================
idAsyncClient::Shutdown
==================
*/
void idAsyncClient::Shutdown( void ) {
guiNetMenu = NULL;
updateMSG.Clear();
updateURL.Clear();
updateFile.Clear();
updateFallback.Clear();
backgroundDownload.url.url.Clear();
dlList.Clear();
}
/*
==================
idAsyncClient::InitPort
==================
*/
bool idAsyncClient::InitPort( void ) {
// if this is the first time we connect to a server, open the UDP port
if ( !clientPort.GetPort() ) {
if ( !clientPort.InitForPort( PORT_ANY ) ) {
common->Printf( "Couldn't open client network port.\n" );
return false;
}
}
// maintain it valid between connects and ui manager reloads
guiNetMenu = uiManager->FindGui( "guis/netmenu.gui", true, false, true );
return true;
}
/*
==================
idAsyncClient::ClosePort
==================
*/
void idAsyncClient::ClosePort( void ) {
clientPort.Close();
}
/*
==================
idAsyncClient::ClearPendingPackets
==================
*/
void idAsyncClient::ClearPendingPackets( void ) {
int size;
byte msgBuf[MAX_MESSAGE_SIZE];
netadr_t from;
while( clientPort.GetPacket( from, msgBuf, size, sizeof( msgBuf ) ) ) {
}
}
/*
==================
idAsyncClient::HandleGuiCommandInternal
==================
*/
const char* idAsyncClient::HandleGuiCommandInternal( const char *cmd ) {
if ( !idStr::Cmp( cmd, "abort" ) || !idStr::Cmp( cmd, "pure_abort" ) ) {
common->DPrintf( "connection aborted\n" );
cmdSystem->BufferCommandText( CMD_EXEC_NOW, "disconnect" );
return "";
} else {
common->DWarning( "idAsyncClient::HandleGuiCommand: unknown cmd %s", cmd );
}
return NULL;
}
/*
==================
idAsyncClient::HandleGuiCommand
==================
*/
const char* idAsyncClient::HandleGuiCommand( const char *cmd ) {
return idAsyncNetwork::client.HandleGuiCommandInternal( cmd );
}
/*
==================
idAsyncClient::ConnectToServer
==================
*/
void idAsyncClient::ConnectToServer( const netadr_t adr ) {
// shutdown any current game. that includes network disconnect
session->Stop();
if ( !InitPort() ) {
return;
}
if ( cvarSystem->GetCVarBool( "net_serverDedicated" ) ) {
common->Printf( "Can't connect to a server as dedicated\n" );
return;
}
// trash any currently pending packets
ClearPendingPackets();
serverAddress = adr;
// clear the client state
Clear();
// get a pseudo random client id, but don't use the id which is reserved for connectionless packets
clientId = Sys_Milliseconds() & CONNECTIONLESS_MESSAGE_ID_MASK;
// calculate a checksum on some of the essential data used
clientDataChecksum = declManager->GetChecksum();
// start challenging the server
clientState = CS_CHALLENGING;
active = true;
guiNetMenu = uiManager->FindGui( "guis/netmenu.gui", true, false, true );
guiNetMenu->SetStateString( "status", va( common->GetLanguageDict()->GetString( "#str_06749" ), Sys_NetAdrToString( adr ) ) );
session->SetGUI( guiNetMenu, HandleGuiCommand );
}
/*
==================
idAsyncClient::Reconnect
==================
*/
void idAsyncClient::Reconnect( void ) {
ConnectToServer( serverAddress );
}
/*
==================
idAsyncClient::ConnectToServer
==================
*/
void idAsyncClient::ConnectToServer( const char *address ) {
int serverNum;
netadr_t adr;
if ( idStr::IsNumeric( address ) ) {
serverNum = atoi( address );
if ( serverNum < 0 || serverNum >= serverList.Num() ) {
session->MessageBox( MSG_OK, va( common->GetLanguageDict()->GetString( "#str_06733" ), serverNum ), common->GetLanguageDict()->GetString( "#str_06735" ), true );
return;
}
adr = serverList[ serverNum ].adr;
} else {
if ( !Sys_StringToNetAdr( address, &adr, true ) ) {
session->MessageBox( MSG_OK, va( common->GetLanguageDict()->GetString( "#str_06734" ), address ), common->GetLanguageDict()->GetString( "#str_06735" ), true );
return;
}
}
if ( !adr.port ) {
adr.port = PORT_SERVER;
}
common->Printf( "\"%s\" resolved to %s\n", address, Sys_NetAdrToString( adr ) );
ConnectToServer( adr );
}
/*
==================
idAsyncClient::DisconnectFromServer
==================
*/
void idAsyncClient::DisconnectFromServer( void ) {
idBitMsg msg;
byte msgBuf[MAX_MESSAGE_SIZE];
if ( clientState >= CS_CONNECTED ) {
// if we were actually connected, clear the pure list
fileSystem->ClearPureChecksums();
// send reliable disconnect to server
msg.Init( msgBuf, sizeof( msgBuf ) );
msg.WriteByte( CLIENT_RELIABLE_MESSAGE_DISCONNECT );
msg.WriteString( "disconnect" );
if ( !channel.SendReliableMessage( msg ) ) {
common->Error( "client->server reliable messages overflow\n" );
}
SendEmptyToServer( true );
SendEmptyToServer( true );
SendEmptyToServer( true );
}
if ( clientState != CS_PURERESTART ) {
channel.Shutdown();
clientState = CS_DISCONNECTED;
}
active = false;
}
/*
==================
idAsyncClient::GetServerInfo
==================
*/
void idAsyncClient::GetServerInfo( const netadr_t adr ) {
idBitMsg msg;
byte msgBuf[MAX_MESSAGE_SIZE];
if ( !InitPort() ) {
return;
}
msg.Init( msgBuf, sizeof( msgBuf ) );
msg.WriteShort( CONNECTIONLESS_MESSAGE_ID );
msg.WriteString( "getInfo" );
msg.WriteLong( serverList.GetChallenge() ); // challenge
clientPort.SendPacket( adr, msg.GetData(), msg.GetSize() );
}
/*
==================
idAsyncClient::GetServerInfo
==================
*/
void idAsyncClient::GetServerInfo( const char *address ) {
netadr_t adr;
if ( address && *address != '\0' ) {
if ( !Sys_StringToNetAdr( address, &adr, true ) ) {
common->Printf( "Couldn't get server address for \"%s\"\n", address );
return;
}
} else if ( active ) {
adr = serverAddress;
} else if ( idAsyncNetwork::server.IsActive() ) {
// used to be a Sys_StringToNetAdr( "localhost", &adr, true ); and send a packet over loopback
// but this breaks with net_ip ( typically, for multi-homed servers )
idAsyncNetwork::server.PrintLocalServerInfo();
return;
} else {
common->Printf( "no server found\n" );
return;
}
if ( !adr.port ) {
adr.port = PORT_SERVER;
}
GetServerInfo( adr );
}
/*
==================
idAsyncClient::GetLANServers
==================
*/
void idAsyncClient::GetLANServers( void ) {
int i;
idBitMsg msg;
byte msgBuf[MAX_MESSAGE_SIZE];
netadr_t broadcastAddress;
if ( !InitPort() ) {
return;
}
idAsyncNetwork::LANServer.SetBool( true );
serverList.SetupLANScan();
msg.Init( msgBuf, sizeof( msgBuf ) );
msg.WriteShort( CONNECTIONLESS_MESSAGE_ID );
msg.WriteString( "getInfo" );
msg.WriteLong( serverList.GetChallenge() );
broadcastAddress.type = NA_BROADCAST;
for ( i = 0; i < MAX_SERVER_PORTS; i++ ) {
broadcastAddress.port = PORT_SERVER + i;
clientPort.SendPacket( broadcastAddress, msg.GetData(), msg.GetSize() );
}
}
/*
==================
idAsyncClient::GetNETServers
==================
*/
void idAsyncClient::GetNETServers( void ) {
idBitMsg msg;
byte msgBuf[MAX_MESSAGE_SIZE];
idAsyncNetwork::LANServer.SetBool( false );
// NetScan only clears GUI and results, not the stored list
serverList.Clear( );
serverList.NetScan( );
serverList.StartServers( true );
msg.Init( msgBuf, sizeof( msgBuf ) );
msg.WriteShort( CONNECTIONLESS_MESSAGE_ID );
msg.WriteString( "getServers" );
msg.WriteLong( ASYNC_PROTOCOL_VERSION );
msg.WriteString( cvarSystem->GetCVarString( "fs_game" ) );
msg.WriteBits( cvarSystem->GetCVarInteger( "gui_filter_password" ), 2 );
msg.WriteBits( cvarSystem->GetCVarInteger( "gui_filter_players" ), 2 );
msg.WriteBits( cvarSystem->GetCVarInteger( "gui_filter_gameType" ), 2 );
netadr_t adr;
if ( idAsyncNetwork::GetMasterAddress( 0, adr ) ) {
clientPort.SendPacket( adr, msg.GetData(), msg.GetSize() );
}
}
/*
==================
idAsyncClient::ListServers
==================
*/
void idAsyncClient::ListServers( void ) {
int i;
for ( i = 0; i < serverList.Num(); i++ ) {
common->Printf( "%3d: %s %dms (%s)\n", i, serverList[i].serverInfo.GetString( "si_name" ), serverList[ i ].ping, Sys_NetAdrToString( serverList[i].adr ) );
}
}
/*
==================
idAsyncClient::ClearServers
==================
*/
void idAsyncClient::ClearServers( void ) {
serverList.Clear();
}
/*
==================
idAsyncClient::RemoteConsole
==================
*/
void idAsyncClient::RemoteConsole( const char *command ) {
netadr_t adr;
idBitMsg msg;
byte msgBuf[MAX_MESSAGE_SIZE];
if ( !InitPort() ) {
return;
}
if ( active ) {
adr = serverAddress;
} else {
Sys_StringToNetAdr( idAsyncNetwork::clientRemoteConsoleAddress.GetString(), &adr, true );
}
if ( !adr.port ) {
adr.port = PORT_SERVER;
}
lastRconAddress = adr;
lastRconTime = realTime;
msg.Init( msgBuf, sizeof( msgBuf ) );
msg.WriteShort( CONNECTIONLESS_MESSAGE_ID );
msg.WriteString( "rcon" );
msg.WriteString( idAsyncNetwork::clientRemoteConsolePassword.GetString() );
msg.WriteString( command );
clientPort.SendPacket( adr, msg.GetData(), msg.GetSize() );
}
/*
==================
idAsyncClient::GetPrediction
==================
*/
int idAsyncClient::GetPrediction( void ) const {
if ( clientState < CS_CONNECTED ) {
return -1;
} else {
return clientPrediction;
}
}
/*
==================
idAsyncClient::GetTimeSinceLastPacket
==================
*/
int idAsyncClient::GetTimeSinceLastPacket( void ) const {
if ( clientState < CS_CONNECTED ) {
return -1;
} else {
return clientTime - lastPacketTime;
}
}
/*
==================
idAsyncClient::GetOutgoingRate
==================
*/
int idAsyncClient::GetOutgoingRate( void ) const {
if ( clientState < CS_CONNECTED ) {
return -1;
} else {
return channel.GetOutgoingRate();
}
}
/*
==================
idAsyncClient::GetIncomingRate
==================
*/
int idAsyncClient::GetIncomingRate( void ) const {
if ( clientState < CS_CONNECTED ) {
return -1;
} else {
return channel.GetIncomingRate();
}
}
/*
==================
idAsyncClient::GetOutgoingCompression
==================
*/
float idAsyncClient::GetOutgoingCompression( void ) const {
if ( clientState < CS_CONNECTED ) {
return 0.0f;
} else {
return channel.GetOutgoingCompression();
}
}
/*
==================
idAsyncClient::GetIncomingCompression
==================
*/
float idAsyncClient::GetIncomingCompression( void ) const {
if ( clientState < CS_CONNECTED ) {
return 0.0f;
} else {
return channel.GetIncomingCompression();
}
}
/*
==================
idAsyncClient::GetIncomingPacketLoss
==================
*/
float idAsyncClient::GetIncomingPacketLoss( void ) const {
if ( clientState < CS_CONNECTED ) {
return 0.0f;
} else {
return channel.GetIncomingPacketLoss();
}
}
/*
==================
idAsyncClient::DuplicateUsercmds
==================
*/
void idAsyncClient::DuplicateUsercmds( int frame, int time ) {
int i, previousIndex, currentIndex;
previousIndex = ( frame - 1 ) & ( MAX_USERCMD_BACKUP - 1 );
currentIndex = frame & ( MAX_USERCMD_BACKUP - 1 );
// duplicate previous user commands if no new commands are available for a client
for ( i = 0; i < MAX_ASYNC_CLIENTS; i++ ) {
idAsyncNetwork::DuplicateUsercmd( userCmds[previousIndex][i], userCmds[currentIndex][i], frame, time );
}
}
/*
==================
idAsyncClient::SendUserInfoToServer
==================
*/
void idAsyncClient::SendUserInfoToServer( void ) {
idBitMsg msg;
byte msgBuf[MAX_MESSAGE_SIZE];
idDict info;
if ( clientState < CS_CONNECTED ) {
return;
}
info = *cvarSystem->MoveCVarsToDict( CVAR_USERINFO );
// send reliable client info to server
msg.Init( msgBuf, sizeof( msgBuf ) );
msg.WriteByte( CLIENT_RELIABLE_MESSAGE_CLIENTINFO );
msg.WriteDeltaDict( info, &sessLocal.mapSpawnData.userInfo[ clientNum ] );
if ( !channel.SendReliableMessage( msg ) ) {
common->Error( "client->server reliable messages overflow\n" );
}
sessLocal.mapSpawnData.userInfo[clientNum] = info;
}
/*
==================
idAsyncClient::SendEmptyToServer
==================
*/
void idAsyncClient::SendEmptyToServer( bool force, bool mapLoad ) {
idBitMsg msg;
byte msgBuf[MAX_MESSAGE_SIZE];
if ( lastEmptyTime > realTime ) {
lastEmptyTime = realTime;
}
if ( !force && ( realTime - lastEmptyTime < EMPTY_RESEND_TIME ) ) {
return;
}
if ( idAsyncNetwork::verbose.GetInteger() ) {
common->Printf( "sending empty to server, gameInitId = %d\n", mapLoad ? GAME_INIT_ID_MAP_LOAD : gameInitId );
}
msg.Init( msgBuf, sizeof( msgBuf ) );
msg.WriteLong( serverMessageSequence );
msg.WriteLong( mapLoad ? GAME_INIT_ID_MAP_LOAD : gameInitId );
msg.WriteLong( snapshotSequence );
msg.WriteByte( CLIENT_UNRELIABLE_MESSAGE_EMPTY );
channel.SendMessage( clientPort, clientTime, msg );
while( channel.UnsentFragmentsLeft() ) {
channel.SendNextFragment( clientPort, clientTime );
}
lastEmptyTime = realTime;
}
/*
==================
idAsyncClient::SendPingResponseToServer
==================
*/
void idAsyncClient::SendPingResponseToServer( int time ) {
idBitMsg msg;
byte msgBuf[MAX_MESSAGE_SIZE];
if ( idAsyncNetwork::verbose.GetInteger() == 2 ) {
common->Printf( "sending ping response to server, gameInitId = %d\n", gameInitId );
}
msg.Init( msgBuf, sizeof( msgBuf ) );
msg.WriteLong( serverMessageSequence );
msg.WriteLong( gameInitId );
msg.WriteLong( snapshotSequence );
msg.WriteByte( CLIENT_UNRELIABLE_MESSAGE_PINGRESPONSE );
msg.WriteLong( time );
channel.SendMessage( clientPort, clientTime, msg );
while( channel.UnsentFragmentsLeft() ) {
channel.SendNextFragment( clientPort, clientTime );
}
}
/*
==================
idAsyncClient::SendUsercmdsToServer
==================
*/
void idAsyncClient::SendUsercmdsToServer( void ) {
int i, numUsercmds, index;
idBitMsg msg;
byte msgBuf[MAX_MESSAGE_SIZE];
usercmd_t * last;
if ( idAsyncNetwork::verbose.GetInteger() == 2 ) {
common->Printf( "sending usercmd to server: gameInitId = %d, gameFrame = %d, gameTime = %d\n", gameInitId, gameFrame, gameTime );
}
// generate user command for this client
index = gameFrame & ( MAX_USERCMD_BACKUP - 1 );
userCmds[index][clientNum] = usercmdGen->GetDirectUsercmd();
userCmds[index][clientNum].gameFrame = gameFrame;
userCmds[index][clientNum].gameTime = gameTime;
// send the user commands to the server
msg.Init( msgBuf, sizeof( msgBuf ) );
msg.WriteLong( serverMessageSequence );
msg.WriteLong( gameInitId );
msg.WriteLong( snapshotSequence );
msg.WriteByte( CLIENT_UNRELIABLE_MESSAGE_USERCMD );
msg.WriteShort( clientPrediction );
numUsercmds = idMath::ClampInt( 0, 10, idAsyncNetwork::clientUsercmdBackup.GetInteger() ) + 1;
// write the user commands
msg.WriteLong( gameFrame );
msg.WriteByte( numUsercmds );
for ( last = NULL, i = gameFrame - numUsercmds + 1; i <= gameFrame; i++ ) {
index = i & ( MAX_USERCMD_BACKUP - 1 );
idAsyncNetwork::WriteUserCmdDelta( msg, userCmds[index][clientNum], last );
last = &userCmds[index][clientNum];
}
channel.SendMessage( clientPort, clientTime, msg );
while( channel.UnsentFragmentsLeft() ) {
channel.SendNextFragment( clientPort, clientTime );
}
}
/*
==================
idAsyncClient::InitGame
==================
*/
void idAsyncClient::InitGame( int serverGameInitId, int serverGameFrame, int serverGameTime, const idDict &serverSI ) {
gameInitId = serverGameInitId;
gameFrame = snapshotGameFrame = serverGameFrame;
gameTime = snapshotGameTime = serverGameTime;
gameTimeResidual = 0;
memset( userCmds, 0, sizeof( userCmds ) );
for ( int i = 0; i < MAX_ASYNC_CLIENTS; i++ ) {
sessLocal.mapSpawnData.userInfo[ i ].Clear();
}
sessLocal.mapSpawnData.serverInfo = serverSI;
}
/*
==================
idAsyncClient::ProcessUnreliableServerMessage
==================
*/
void idAsyncClient::ProcessUnreliableServerMessage( const idBitMsg &msg ) {
int i, j, index, id, numDuplicatedUsercmds, aheadOfServer, numUsercmds, delta;
int serverGameInitId, serverGameFrame, serverGameTime;
idDict serverSI;
usercmd_t *last;
bool pureWait;
serverGameInitId = msg.ReadLong();
id = msg.ReadByte();
switch( id ) {
case SERVER_UNRELIABLE_MESSAGE_EMPTY: {
if ( idAsyncNetwork::verbose.GetInteger() ) {
common->Printf( "received empty message from server\n" );
}
break;
}
case SERVER_UNRELIABLE_MESSAGE_PING: {
if ( idAsyncNetwork::verbose.GetInteger() == 2 ) {
common->Printf( "received ping message from server\n" );
}
SendPingResponseToServer( msg.ReadLong() );
break;
}
case SERVER_UNRELIABLE_MESSAGE_GAMEINIT: {
serverGameFrame = msg.ReadLong();
serverGameTime = msg.ReadLong();
msg.ReadDeltaDict( serverSI, NULL );
pureWait = serverSI.GetBool( "si_pure" );
InitGame( serverGameInitId, serverGameFrame, serverGameTime, serverSI );
channel.ResetRate();
if ( idAsyncNetwork::verbose.GetInteger() ) {
common->Printf( "received gameinit, gameInitId = %d, gameFrame = %d, gameTime = %d\n", gameInitId, gameFrame, gameTime );
}
// mute sound
soundSystem->SetMute( true );
// ensure chat icon goes away when the GUI is changed...
//cvarSystem->SetCVarBool( "ui_chat", false );
if ( pureWait ) {
guiNetMenu = uiManager->FindGui( "guis/netmenu.gui", true, false, true );
session->SetGUI( guiNetMenu, HandleGuiCommand );
session->MessageBox( MSG_ABORT, common->GetLanguageDict()->GetString ( "#str_04317" ), common->GetLanguageDict()->GetString ( "#str_04318" ), false, "pure_abort" );
} else {
// load map
session->SetGUI( NULL, NULL );
sessLocal.ExecuteMapChange();
}
break;
}
case SERVER_UNRELIABLE_MESSAGE_SNAPSHOT: {
// if the snapshot is from a different game
if ( serverGameInitId != gameInitId ) {
if ( idAsyncNetwork::verbose.GetInteger() ) {
common->Printf( "ignoring snapshot with != gameInitId\n" );
}
break;
}
snapshotSequence = msg.ReadLong();
snapshotGameFrame = msg.ReadLong();
snapshotGameTime = msg.ReadLong();
numDuplicatedUsercmds = msg.ReadByte();
aheadOfServer = msg.ReadShort();
// read the game snapshot
game->ClientReadSnapshot( clientNum, snapshotSequence, snapshotGameFrame, snapshotGameTime, numDuplicatedUsercmds, aheadOfServer, msg );
// read user commands of other clients from the snapshot
for ( last = NULL, i = msg.ReadByte(); i < MAX_ASYNC_CLIENTS; i = msg.ReadByte() ) {
numUsercmds = msg.ReadByte();
if ( numUsercmds > MAX_USERCMD_RELAY ) {
common->Error( "snapshot %d contains too many user commands for client %d", snapshotSequence, i );
break;
}
for ( j = 0; j < numUsercmds; j++ ) {
index = ( snapshotGameFrame + j ) & ( MAX_USERCMD_BACKUP - 1 );
idAsyncNetwork::ReadUserCmdDelta( msg, userCmds[index][i], last );
userCmds[index][i].gameFrame = snapshotGameFrame + j;
userCmds[index][i].duplicateCount = 0;
last = &userCmds[index][i];
}
// clear all user commands after the ones just read from the snapshot
for ( j = numUsercmds; j < MAX_USERCMD_BACKUP; j++ ) {
index = ( snapshotGameFrame + j ) & ( MAX_USERCMD_BACKUP - 1 );
userCmds[index][i].gameFrame = 0;
userCmds[index][i].gameTime = 0;
}
}
// if this is the first snapshot after a game init was received
if ( clientState == CS_CONNECTED ) {
gameTimeResidual = 0;
clientState = CS_INGAME;
assert( !sessLocal.GetActiveMenu( ) );
if ( idAsyncNetwork::verbose.GetInteger() ) {
common->Printf( "received first snapshot, gameInitId = %d, gameFrame %d gameTime %d\n", gameInitId, snapshotGameFrame, snapshotGameTime );
}
}
// if the snapshot is newer than the clients current game time
if ( gameTime < snapshotGameTime || gameTime > snapshotGameTime + idAsyncNetwork::clientMaxPrediction.GetInteger() ) {
gameFrame = snapshotGameFrame;
gameTime = snapshotGameTime;
gameTimeResidual = idMath::ClampInt( -idAsyncNetwork::clientMaxPrediction.GetInteger(), idAsyncNetwork::clientMaxPrediction.GetInteger(), gameTimeResidual );
clientPredictTime = idMath::ClampInt( -idAsyncNetwork::clientMaxPrediction.GetInteger(), idAsyncNetwork::clientMaxPrediction.GetInteger(), clientPredictTime );
}
// adjust the client prediction time based on the snapshot time
clientPrediction -= ( 1 - ( INTSIGNBITSET( aheadOfServer - idAsyncNetwork::clientPrediction.GetInteger() ) << 1 ) );
clientPrediction = idMath::ClampInt( idAsyncNetwork::clientPrediction.GetInteger(), idAsyncNetwork::clientMaxPrediction.GetInteger(), clientPrediction );
delta = gameTime - ( snapshotGameTime + clientPrediction );
clientPredictTime -= ( delta / PREDICTION_FAST_ADJUST ) + ( 1 - ( INTSIGNBITSET( delta ) << 1 ) );
lastSnapshotTime = clientTime;
if ( idAsyncNetwork::verbose.GetInteger() == 2 ) {
common->Printf( "received snapshot, gameInitId = %d, gameFrame = %d, gameTime = %d\n", gameInitId, gameFrame, gameTime );
}
if ( numDuplicatedUsercmds && ( idAsyncNetwork::verbose.GetInteger() == 2 ) ) {
common->Printf( "server duplicated %d user commands before snapshot %d\n", numDuplicatedUsercmds, snapshotGameFrame );
}
break;
}
default: {
common->Printf( "unknown unreliable server message %d\n", id );
break;
}
}
}
/*
==================
idAsyncClient::ProcessReliableMessagePure
==================
*/
void idAsyncClient::ProcessReliableMessagePure( const idBitMsg &msg ) {
idBitMsg outMsg;
byte msgBuf[ MAX_MESSAGE_SIZE ];
int inChecksums[ MAX_PURE_PAKS ];
int i;
int gamePakChecksum;
int serverGameInitId;
session->SetGUI( NULL, NULL );
serverGameInitId = msg.ReadLong();
if ( serverGameInitId != gameInitId ) {
common->DPrintf( "ignoring pure server checksum from an outdated gameInitId (%d)\n", serverGameInitId );
return;
}
if ( !ValidatePureServerChecksums( serverAddress, msg ) ) {
return;
}
if ( idAsyncNetwork::verbose.GetInteger() ) {
common->Printf( "received new pure server info. ExecuteMapChange and report back\n" );
}
// it is now ok to load the next map with updated pure checksums
sessLocal.ExecuteMapChange( true );
// upon receiving our pure list, the server will send us SCS_INGAME and we'll start getting snapshots
fileSystem->GetPureServerChecksums( inChecksums, -1, &gamePakChecksum );
outMsg.Init( msgBuf, sizeof( msgBuf ) );
outMsg.WriteByte( CLIENT_RELIABLE_MESSAGE_PURE );
outMsg.WriteLong( gameInitId );
i = 0;
while ( inChecksums[ i ] ) {
outMsg.WriteLong( inChecksums[ i++ ] );
}
outMsg.WriteLong( 0 );
outMsg.WriteLong( gamePakChecksum );
if ( !channel.SendReliableMessage( outMsg ) ) {
common->Error( "client->server reliable messages overflow\n" );
}
}
/*
===============
idAsyncClient::ReadLocalizedServerString
===============
*/
void idAsyncClient::ReadLocalizedServerString( const idBitMsg &msg, char *out, int maxLen ) {
msg.ReadString( out, maxLen );
// look up localized string. if the message is not an #str_ format, we'll just get it back unchanged
idStr::snPrintf( out, maxLen - 1, "%s", common->GetLanguageDict()->GetString( out ) );
}
/*
==================
idAsyncClient::ProcessReliableServerMessages
==================
*/
void idAsyncClient::ProcessReliableServerMessages( void ) {
idBitMsg msg;
byte msgBuf[MAX_MESSAGE_SIZE];
byte id;
msg.Init( msgBuf, sizeof( msgBuf ) );
while ( channel.GetReliableMessage( msg ) ) {
id = msg.ReadByte();
switch( id ) {
case SERVER_RELIABLE_MESSAGE_CLIENTINFO: {
int clientNum;
clientNum = msg.ReadByte();
idDict &info = sessLocal.mapSpawnData.userInfo[ clientNum ];
bool haveBase = ( msg.ReadBits( 1 ) != 0 );
#if ID_CLIENTINFO_TAGS
int checksum = info.Checksum();
int srv_checksum = msg.ReadLong();
if ( checksum != srv_checksum ) {
common->DPrintf( "SERVER_RELIABLE_MESSAGE_CLIENTINFO %d (haveBase: %s): != checksums srv: 0x%x local: 0x%x\n", clientNum, haveBase ? "true" : "false", checksum, srv_checksum );
info.Print();
} else {
common->DPrintf( "SERVER_RELIABLE_MESSAGE_CLIENTINFO %d (haveBase: %s): checksums ok 0x%x\n", clientNum, haveBase ? "true" : "false", checksum );
}
#endif
if ( haveBase ) {
msg.ReadDeltaDict( info, &info );
} else {
msg.ReadDeltaDict( info, NULL );
}
// server forces us to a different userinfo
if ( clientNum == idAsyncClient::clientNum ) {
common->DPrintf( "local user info modified by server\n" );
cvarSystem->SetCVarsFromDict( info );
cvarSystem->ClearModifiedFlags( CVAR_USERINFO ); // don't emit back
}
game->SetUserInfo( clientNum, info, true, false );
break;
}
case SERVER_RELIABLE_MESSAGE_SYNCEDCVARS: {
idDict &info = sessLocal.mapSpawnData.syncedCVars;
msg.ReadDeltaDict( info, &info );
cvarSystem->SetCVarsFromDict( info );
if ( !idAsyncNetwork::allowCheats.GetBool() ) {
cvarSystem->ResetFlaggedVariables( CVAR_CHEAT );
}
break;
}
case SERVER_RELIABLE_MESSAGE_PRINT: {
char string[MAX_STRING_CHARS];
msg.ReadString( string, MAX_STRING_CHARS );
common->Printf( "%s\n", string );
break;
}
case SERVER_RELIABLE_MESSAGE_DISCONNECT: {
int clientNum;
char string[MAX_STRING_CHARS];
clientNum = msg.ReadLong( );
ReadLocalizedServerString( msg, string, MAX_STRING_CHARS );
if ( clientNum == idAsyncClient::clientNum ) {
session->Stop();
session->MessageBox( MSG_OK, string, common->GetLanguageDict()->GetString ( "#str_04319" ), true );
session->StartMenu();
} else {
common->Printf( "client %d %s\n", clientNum, string );
cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "addChatLine \"%s^0 %s\"", sessLocal.mapSpawnData.userInfo[ clientNum ].GetString( "ui_name" ), string ) );
sessLocal.mapSpawnData.userInfo[ clientNum ].Clear();
}
break;
}
case SERVER_RELIABLE_MESSAGE_APPLYSNAPSHOT: {
int sequence;
sequence = msg.ReadLong();
if ( !game->ClientApplySnapshot( clientNum, sequence ) ) {
session->Stop();
common->Error( "couldn't apply snapshot %d", sequence );
}
break;
}
case SERVER_RELIABLE_MESSAGE_PURE: {
ProcessReliableMessagePure( msg );
break;
}
case SERVER_RELIABLE_MESSAGE_RELOAD: {
if ( idAsyncNetwork::verbose.GetBool() ) {
common->Printf( "got MESSAGE_RELOAD from server\n" );
}
// simply reconnect, so that if the server restarts in pure mode we can get the right list and avoid spurious reloads
cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "reconnect\n" );
break;
}
case SERVER_RELIABLE_MESSAGE_ENTERGAME: {
SendUserInfoToServer();
game->SetUserInfo( clientNum, sessLocal.mapSpawnData.userInfo[ clientNum ], true, false );
cvarSystem->ClearModifiedFlags( CVAR_USERINFO );
break;
}
default: {
// pass reliable message on to game code
game->ClientProcessReliableMessage( clientNum, msg );
break;
}
}
}
}
/*
==================
idAsyncClient::ProcessChallengeResponseMessage
==================
*/
void idAsyncClient::ProcessChallengeResponseMessage( const netadr_t from, const idBitMsg &msg ) {
char serverGame[ MAX_STRING_CHARS ], serverGameBase[ MAX_STRING_CHARS ];
if ( clientState != CS_CHALLENGING ) {
common->Printf( "Unwanted challenge response received.\n" );
return;
}
serverChallenge = msg.ReadLong();
serverId = msg.ReadShort();
msg.ReadString( serverGameBase, MAX_STRING_CHARS );
msg.ReadString( serverGame, MAX_STRING_CHARS );
// the server is running a different game... we need to reload in the correct fs_game
// even pure pak checks would fail if we didn't, as there are files we may not even see atm
// NOTE: we could read the pure list from the server at the same time and set it up for the restart
// ( if the client can restart directly with the right pak order, then we avoid an extra reloadEngine later.. )
if ( idStr::Icmp( cvarSystem->GetCVarString( "fs_game_base" ), serverGameBase ) ||
idStr::Icmp( cvarSystem->GetCVarString( "fs_game" ), serverGame ) ) {
// bug #189 - if the server is running ROE and ROE is not locally installed, refuse to connect or we might crash
if ( !fileSystem->HasD3XP() && ( !idStr::Icmp( serverGameBase, "d3xp" ) || !idStr::Icmp( serverGame, "d3xp" ) ) ) {
common->Printf( "The server is running Doom3: Resurrection of Evil expansion pack. RoE is not installed on this client. Aborting the connection..\n" );
cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "disconnect\n" );
return;
}
common->Printf( "The server is running a different mod (%s-%s). Restarting..\n", serverGameBase, serverGame );
cvarSystem->SetCVarString( "fs_game_base", serverGameBase );
cvarSystem->SetCVarString( "fs_game", serverGame );
cmdSystem->BufferCommandText( CMD_EXEC_NOW, "reloadEngine" );
cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "reconnect\n" );
return;
}
common->Printf( "received challenge response 0x%x from %s\n", serverChallenge, Sys_NetAdrToString( from ) );
// start sending connect packets instead of challenge request packets
clientState = CS_CONNECTING;
lastConnectTime = -9999;
// take this address as the new server address. This allows
// a server proxy to hand off connections to multiple servers
serverAddress = from;
}
/*
==================
idAsyncClient::ProcessConnectResponseMessage
==================
*/
void idAsyncClient::ProcessConnectResponseMessage( const netadr_t from, const idBitMsg &msg ) {
int serverGameInitId, serverGameFrame, serverGameTime;
idDict serverSI;
if ( clientState >= CS_CONNECTED ) {
common->Printf( "Duplicate connect received.\n" );
return;
}
if ( clientState != CS_CONNECTING ) {
common->Printf( "Connect response packet while not connecting.\n" );
return;
}
if ( !Sys_CompareNetAdrBase( from, serverAddress ) ) {
common->Printf( "Connect response from a different server.\n" );
common->Printf( "%s should have been %s\n", Sys_NetAdrToString( from ), Sys_NetAdrToString( serverAddress ) );
return;
}
common->Printf( "received connect response from %s\n", Sys_NetAdrToString( from ) );
channel.Init( from, clientId );
clientNum = msg.ReadLong();
clientState = CS_CONNECTED;
lastPacketTime = -9999;
serverGameInitId = msg.ReadLong();
serverGameFrame = msg.ReadLong();
serverGameTime = msg.ReadLong();
msg.ReadDeltaDict( serverSI, NULL );
InitGame( serverGameInitId, serverGameFrame, serverGameTime, serverSI );
// load map
session->SetGUI( NULL, NULL );
sessLocal.ExecuteMapChange();
clientPredictTime = clientPrediction = idMath::ClampInt( 0, idAsyncNetwork::clientMaxPrediction.GetInteger(), clientTime - lastConnectTime );
}
/*
==================
idAsyncClient::ProcessDisconnectMessage
==================
*/
void idAsyncClient::ProcessDisconnectMessage( const netadr_t from, const idBitMsg &msg ) {
if ( clientState == CS_DISCONNECTED ) {
common->Printf( "Disconnect packet while not connected.\n" );
return;
}
if ( !Sys_CompareNetAdrBase( from, serverAddress ) ) {
common->Printf( "Disconnect packet from unknown server.\n" );
return;
}
session->Stop();
session->MessageBox( MSG_OK, common->GetLanguageDict()->GetString ( "#str_04320" ), NULL, true );
session->StartMenu();
}
/*
==================
idAsyncClient::ProcessInfoResponseMessage
==================
*/
void idAsyncClient::ProcessInfoResponseMessage( const netadr_t from, const idBitMsg &msg ) {
int i, protocol, index;
networkServer_t serverInfo;
bool verbose = false;
if ( from.type == NA_LOOPBACK || cvarSystem->GetCVarBool( "developer" ) ) {
verbose = true;
}
serverInfo.clients = 0;
serverInfo.adr = from;
serverInfo.challenge = msg.ReadLong(); // challenge
protocol = msg.ReadLong();
if ( protocol != ASYNC_PROTOCOL_VERSION ) {
common->Printf( "server %s ignored - protocol %d.%d, expected %d.%d\n", Sys_NetAdrToString( serverInfo.adr ), protocol >> 16, protocol & 0xffff, ASYNC_PROTOCOL_MAJOR, ASYNC_PROTOCOL_MINOR );
return;
}
msg.ReadDeltaDict( serverInfo.serverInfo, NULL );
if ( verbose ) {
common->Printf( "server IP = %s\n", Sys_NetAdrToString( serverInfo.adr ) );
serverInfo.serverInfo.Print();
}
for ( i = msg.ReadByte(); i < MAX_ASYNC_CLIENTS; i = msg.ReadByte() ) {
serverInfo.pings[ serverInfo.clients ] = msg.ReadShort();
serverInfo.rate[ serverInfo.clients ] = msg.ReadLong();
msg.ReadString( serverInfo.nickname[ serverInfo.clients ], MAX_NICKLEN );
if ( verbose ) {
common->Printf( "client %2d: %s, ping = %d, rate = %d\n", i, serverInfo.nickname[ serverInfo.clients ], serverInfo.pings[ serverInfo.clients ], serverInfo.rate[ serverInfo.clients ] );
}
serverInfo.clients++;
}
serverInfo.OSMask = msg.ReadLong();
index = serverList.InfoResponse( serverInfo );
common->Printf( "%d: server %s - protocol %d.%d - %s\n", index, Sys_NetAdrToString( serverInfo.adr ), protocol >> 16, protocol & 0xffff, serverInfo.serverInfo.GetString( "si_name" ) );
}
/*
==================
idAsyncClient::ProcessPrintMessage
==================
*/
void idAsyncClient::ProcessPrintMessage( const netadr_t from, const idBitMsg &msg ) {
char string[ MAX_STRING_CHARS ];
int opcode;
int game_opcode = ALLOW_YES;
const char *retpass;
opcode = msg.ReadLong();
if ( opcode == SERVER_PRINT_GAMEDENY ) {
game_opcode = msg.ReadLong();
}
ReadLocalizedServerString( msg, string, MAX_STRING_CHARS );
common->Printf( "%s\n", string );
guiNetMenu->SetStateString( "status", string );
if ( opcode == SERVER_PRINT_GAMEDENY ) {
if ( game_opcode == ALLOW_BADPASS ) {
retpass = session->MessageBox( MSG_PROMPT, common->GetLanguageDict()->GetString ( "#str_04321" ), string, true, "passprompt_ok" );
ClearPendingPackets();
guiNetMenu->SetStateString( "status", common->GetLanguageDict()->GetString ( "#str_04322" ));
if ( retpass ) {
// #790
cvarSystem->SetCVarString( "password", "" );
cvarSystem->SetCVarString( "password", retpass );
} else {
cmdSystem->BufferCommandText( CMD_EXEC_NOW, "disconnect" );
}
} else if ( game_opcode == ALLOW_NO ) {
session->MessageBox( MSG_OK, string, common->GetLanguageDict()->GetString ( "#str_04323" ), true );
ClearPendingPackets();
cmdSystem->BufferCommandText( CMD_EXEC_NOW, "disconnect" );
}
// ALLOW_NOTYET just keeps running as usual. The GUI has an abort button
} else if ( opcode == SERVER_PRINT_BADCHALLENGE && clientState >= CS_CONNECTING ) {
cmdSystem->BufferCommandText( CMD_EXEC_NOW, "reconnect" );
}
}
/*
==================
idAsyncClient::ProcessServersListMessage
==================
*/
void idAsyncClient::ProcessServersListMessage( const netadr_t from, const idBitMsg &msg ) {
if ( !Sys_CompareNetAdrBase( idAsyncNetwork::GetMasterAddress(), from ) ) {
common->DPrintf( "received a server list from %s - not a valid master\n", Sys_NetAdrToString( from ) );
return;
}
while ( msg.GetRemaingData() ) {
int a,b,c,d;
a = msg.ReadByte(); b = msg.ReadByte(); c = msg.ReadByte(); d = msg.ReadByte();
serverList.AddServer( serverList.Num(), va( "%i.%i.%i.%i:%i", a, b, c, d, msg.ReadShort() ) );
}
}
/*
==================
idAsyncClient::ProcessAuthKeyMessage
==================
*/
void idAsyncClient::ProcessAuthKeyMessage( const netadr_t from, const idBitMsg &msg ) {
authKeyMsg_t authMsg;
char read_string[ MAX_STRING_CHARS ];
const char *retkey;
authBadKeyStatus_t authBadStatus;
int key_index;
bool valid[ 2 ];
idStr auth_msg;
if ( clientState != CS_CONNECTING && !session->WaitingForGameAuth() ) {
common->Printf( "clientState != CS_CONNECTING, not waiting for game auth, authKey ignored\n" );
return;
}
authMsg = (authKeyMsg_t)msg.ReadByte();
if ( authMsg == AUTHKEY_BADKEY ) {
valid[ 0 ] = valid[ 1 ] = true;
key_index = 0;
authBadStatus = (authBadKeyStatus_t)msg.ReadByte();
switch ( authBadStatus ) {
case AUTHKEY_BAD_INVALID:
valid[ 0 ] = ( msg.ReadByte() == 1 );
valid[ 1 ] = ( msg.ReadByte() == 1 );
idAsyncNetwork::BuildInvalidKeyMsg( auth_msg, valid );
break;
case AUTHKEY_BAD_BANNED:
key_index = msg.ReadByte();
auth_msg = common->GetLanguageDict()->GetString( va( "#str_0719%1d", 6 + key_index ) );
auth_msg += "\n";
auth_msg += common->GetLanguageDict()->GetString( "#str_04304" );
valid[ key_index ] = false;
break;
case AUTHKEY_BAD_INUSE:
key_index = msg.ReadByte();
auth_msg = common->GetLanguageDict()->GetString( va( "#str_0719%1d", 8 + key_index ) );
auth_msg += "\n";
auth_msg += common->GetLanguageDict()->GetString( "#str_04304" );
valid[ key_index ] = false;
break;
case AUTHKEY_BAD_MSG:
// a general message explaining why this key is denied
// no specific use for this atm. let's not clear the keys either
msg.ReadString( read_string, MAX_STRING_CHARS );
auth_msg = read_string;
break;
}
common->DPrintf( "auth deny: %s\n", auth_msg.c_str() );
// keys to be cleared. applies to both net connect and game auth
session->ClearCDKey( valid );
// get rid of the bad key - at least that's gonna annoy people who stole a fake key
if ( clientState == CS_CONNECTING ) {
while ( 1 ) {
// here we use the auth status message
retkey = session->MessageBox( MSG_CDKEY, auth_msg, common->GetLanguageDict()->GetString( "#str_04325" ), true );
if ( retkey ) {
if ( session->CheckKey( retkey, true, valid ) ) {
cmdSystem->BufferCommandText( CMD_EXEC_NOW, "reconnect" );
} else {
// build a more precise message about the offline check failure
idAsyncNetwork::BuildInvalidKeyMsg( auth_msg, valid );
session->MessageBox( MSG_OK, auth_msg.c_str(), common->GetLanguageDict()->GetString( "#str_04327" ), true );
continue;
}
} else {
cmdSystem->BufferCommandText( CMD_EXEC_NOW, "disconnect" );
}
break;
}
} else {
// forward the auth status information to the session code
session->CDKeysAuthReply( false, auth_msg );
}
} else {
msg.ReadString( read_string, MAX_STRING_CHARS );
cvarSystem->SetCVarString( "com_guid", read_string );
common->Printf( "guid set to %s\n", read_string );
session->CDKeysAuthReply( true, NULL );
}
}
/*
==================
idAsyncClient::ProcessVersionMessage
==================
*/
void idAsyncClient::ProcessVersionMessage( const netadr_t from, const idBitMsg &msg ) {
char string[ MAX_STRING_CHARS ];
if ( updateState != UPDATE_SENT ) {
common->Printf( "ProcessVersionMessage: version reply, != UPDATE_SENT\n" );
return;
}
common->Printf( "A new version is available\n" );
msg.ReadString( string, MAX_STRING_CHARS );
updateMSG = string;
updateDirectDownload = ( msg.ReadByte() != 0 );
msg.ReadString( string, MAX_STRING_CHARS );
updateURL = string;
updateMime = (dlMime_t)msg.ReadByte();
msg.ReadString( string, MAX_STRING_CHARS );
updateFallback = string;
updateState = UPDATE_READY;
}
/*
==================
idAsyncClient::ValidatePureServerChecksums
==================
*/
bool idAsyncClient::ValidatePureServerChecksums( const netadr_t from, const idBitMsg &msg ) {
int i, numChecksums, numMissingChecksums;
int inChecksums[ MAX_PURE_PAKS ];
int inGamePakChecksum;
int missingChecksums[ MAX_PURE_PAKS ];
int missingGamePakChecksum;
idBitMsg dlmsg;
byte msgBuf[MAX_MESSAGE_SIZE];
// read checksums
// pak checksums, in a 0-terminated list
numChecksums = 0;
do {
i = msg.ReadLong( );
inChecksums[ numChecksums++ ] = i;
// just to make sure a broken message doesn't crash us
if ( numChecksums >= MAX_PURE_PAKS ) {
common->Warning( "MAX_PURE_PAKS ( %d ) exceeded in idAsyncClient::ProcessPureMessage\n", MAX_PURE_PAKS );
return false;
}
} while ( i );
inChecksums[ numChecksums ] = 0;
inGamePakChecksum = msg.ReadLong();
fsPureReply_t reply = fileSystem->SetPureServerChecksums( inChecksums, inGamePakChecksum, missingChecksums, &missingGamePakChecksum );
switch ( reply ) {
case PURE_RESTART:
// need to restart the filesystem with a different pure configuration
cmdSystem->BufferCommandText( CMD_EXEC_NOW, "disconnect" );
// restart with the right FS configuration and get back to the server
clientState = CS_PURERESTART;
fileSystem->SetRestartChecksums( inChecksums, inGamePakChecksum );
cmdSystem->BufferCommandText( CMD_EXEC_NOW, "reloadEngine" );
return false;
case PURE_MISSING: {
idStr checksums;
i = 0;
while ( missingChecksums[ i ] ) {
checksums += va( "0x%x ", missingChecksums[ i++ ] );
}
numMissingChecksums = i;
if ( idAsyncNetwork::clientDownload.GetInteger() == 0 ) {
// never any downloads
idStr message = va( common->GetLanguageDict()->GetString( "#str_07210" ), Sys_NetAdrToString( from ) );
if ( numMissingChecksums > 0 ) {
message += va( common->GetLanguageDict()->GetString( "#str_06751" ), numMissingChecksums, checksums.c_str() );
}
if ( missingGamePakChecksum ) {
message += va( common->GetLanguageDict()->GetString( "#str_06750" ), missingGamePakChecksum );
}
common->Printf( message );
cmdSystem->BufferCommandText( CMD_EXEC_NOW, "disconnect" );
session->MessageBox( MSG_OK, message, common->GetLanguageDict()->GetString( "#str_06735" ), true );
} else {
if ( clientState >= CS_CONNECTED ) {
// we are already connected, reconnect to negociate the paks in connectionless mode
cmdSystem->BufferCommandText( CMD_EXEC_NOW, "reconnect" );
return false;
}
// ask the server to send back download info
common->DPrintf( "missing %d paks: %s\n", numMissingChecksums + ( missingGamePakChecksum ? 1 : 0 ), checksums.c_str() );
if ( missingGamePakChecksum ) {
common->DPrintf( "game code pak: 0x%x\n", missingGamePakChecksum );
}
// store the requested downloads
GetDownloadRequest( missingChecksums, numMissingChecksums, missingGamePakChecksum );
// build the download request message
// NOTE: in a specific function?
dlmsg.Init( msgBuf, sizeof( msgBuf ) );
dlmsg.WriteShort( CONNECTIONLESS_MESSAGE_ID );
dlmsg.WriteString( "downloadRequest" );
dlmsg.WriteLong( serverChallenge );
dlmsg.WriteShort( clientId );
// used to make sure the server replies to the same download request
dlmsg.WriteLong( dlRequest );
// special case the code pak - if we have a 0 checksum then we don't need to download it
dlmsg.WriteLong( missingGamePakChecksum );
// 0-terminated list of missing paks
i = 0;
while ( missingChecksums[ i ] ) {
dlmsg.WriteLong( missingChecksums[ i++ ] );
}
dlmsg.WriteLong( 0 );
clientPort.SendPacket( from, dlmsg.GetData(), dlmsg.GetSize() );
}
return false;
}
case PURE_NODLL:
common->Printf( common->GetLanguageDict()->GetString( "#str_07211" ), Sys_NetAdrToString( from ) );
cmdSystem->BufferCommandText( CMD_EXEC_NOW, "disconnect" );
return false;
default:
return true;
}
return true;
}
/*
==================
idAsyncClient::ProcessPureMessage
==================
*/
void idAsyncClient::ProcessPureMessage( const netadr_t from, const idBitMsg &msg ) {
idBitMsg outMsg;
byte msgBuf[ MAX_MESSAGE_SIZE ];
int i;
int inChecksums[ MAX_PURE_PAKS ];
int gamePakChecksum;
if ( clientState != CS_CONNECTING ) {
common->Printf( "clientState != CS_CONNECTING, pure msg ignored\n" );
return;
}
if ( !ValidatePureServerChecksums( from, msg ) ) {
return;
}
fileSystem->GetPureServerChecksums( inChecksums, -1, &gamePakChecksum );
outMsg.Init( msgBuf, sizeof( msgBuf ) );
outMsg.WriteShort( CONNECTIONLESS_MESSAGE_ID );
outMsg.WriteString( "pureClient" );
outMsg.WriteLong( serverChallenge );
outMsg.WriteShort( clientId );
i = 0;
while ( inChecksums[ i ] ) {
outMsg.WriteLong( inChecksums[ i++ ] );
}
outMsg.WriteLong( 0 );
outMsg.WriteLong( gamePakChecksum );
clientPort.SendPacket( from, outMsg.GetData(), outMsg.GetSize() );
}
/*
==================
idAsyncClient::ConnectionlessMessage
==================
*/
void idAsyncClient::ConnectionlessMessage( const netadr_t from, const idBitMsg &msg ) {
char string[MAX_STRING_CHARS*2]; // M. Quinn - Even Balance - PB packets can go beyond 1024
msg.ReadString( string, sizeof( string ) );
// info response from a server, are accepted from any source
if ( idStr::Icmp( string, "infoResponse" ) == 0 ) {
ProcessInfoResponseMessage( from, msg );
return;
}
// from master server:
if ( Sys_CompareNetAdrBase( from, idAsyncNetwork::GetMasterAddress( ) ) ) {
// server list
if ( idStr::Icmp( string, "servers" ) == 0 ) {
ProcessServersListMessage( from, msg );
return;
}
if ( idStr::Icmp( string, "authKey" ) == 0 ) {
ProcessAuthKeyMessage( from, msg );
return;
}
if ( idStr::Icmp( string, "newVersion" ) == 0 ) {
ProcessVersionMessage( from, msg );
return;
}
}
// ignore if not from the current/last server
if ( !Sys_CompareNetAdrBase( from, serverAddress ) && ( lastRconTime + 10000 < realTime || !Sys_CompareNetAdrBase( from, lastRconAddress ) ) ) {
common->DPrintf( "got message '%s' from bad source: %s\n", string, Sys_NetAdrToString( from ) );
return;
}
// challenge response from the server we are connecting to
if ( idStr::Icmp( string, "challengeResponse" ) == 0 ) {
ProcessChallengeResponseMessage( from, msg );
return;
}
// connect response from the server we are connecting to
if ( idStr::Icmp( string, "connectResponse" ) == 0 ) {
ProcessConnectResponseMessage( from, msg );
return;
}
// a disconnect message from the server, which will happen if the server
// dropped the connection but is still getting packets from this client
if ( idStr::Icmp( string, "disconnect" ) == 0 ) {
ProcessDisconnectMessage( from, msg );
return;
}
// print request from server
if ( idStr::Icmp( string, "print" ) == 0 ) {
ProcessPrintMessage( from, msg );
return;
}
// server pure list
if ( idStr::Icmp( string, "pureServer" ) == 0 ) {
ProcessPureMessage( from, msg );
return;
}
if ( idStr::Icmp( string, "downloadInfo" ) == 0 ) {
ProcessDownloadInfoMessage( from, msg );
}
if ( idStr::Icmp( string, "authrequired" ) == 0 ) {
// server telling us that he's expecting an auth mode connect, just in case we're trying to connect in LAN mode
if ( idAsyncNetwork::LANServer.GetBool() ) {
common->Warning( "server %s requests master authorization for this client. Turning off LAN mode\n", Sys_NetAdrToString( from ) );
idAsyncNetwork::LANServer.SetBool( false );
}
}
common->DPrintf( "ignored message from %s: %s\n", Sys_NetAdrToString( from ), string );
}
/*
=================
idAsyncClient::ProcessMessage
=================
*/
void idAsyncClient::ProcessMessage( const netadr_t from, idBitMsg &msg ) {
int id;
id = msg.ReadShort();
// check for a connectionless packet
if ( id == CONNECTIONLESS_MESSAGE_ID ) {
ConnectionlessMessage( from, msg );
return;
}
if ( clientState < CS_CONNECTED ) {
return; // can't be a valid sequenced packet
}
if ( msg.GetRemaingData() < 4 ) {
common->DPrintf( "%s: tiny packet\n", Sys_NetAdrToString( from ) );
return;
}
// is this a packet from the server
if ( !Sys_CompareNetAdrBase( from, channel.GetRemoteAddress() ) || id != serverId ) {
common->DPrintf( "%s: sequenced server packet without connection\n", Sys_NetAdrToString( from ) );
return;
}
if ( !channel.Process( from, clientTime, msg, serverMessageSequence ) ) {
return; // out of order, duplicated, fragment, etc.
}
lastPacketTime = clientTime;
ProcessReliableServerMessages();
ProcessUnreliableServerMessage( msg );
}
/*
==================
idAsyncClient::SetupConnection
==================
*/
void idAsyncClient::SetupConnection( void ) {
idBitMsg msg;
byte msgBuf[MAX_MESSAGE_SIZE];
if ( clientTime - lastConnectTime < SETUP_CONNECTION_RESEND_TIME ) {
return;
}
if ( clientState == CS_CHALLENGING ) {
common->Printf( "sending challenge to %s\n", Sys_NetAdrToString( serverAddress ) );
msg.Init( msgBuf, sizeof( msgBuf ) );
msg.WriteShort( CONNECTIONLESS_MESSAGE_ID );
msg.WriteString( "challenge" );
msg.WriteLong( clientId );
clientPort.SendPacket( serverAddress, msg.GetData(), msg.GetSize() );
} else if ( clientState == CS_CONNECTING ) {
common->Printf( "sending connect to %s with challenge 0x%x\n", Sys_NetAdrToString( serverAddress ), serverChallenge );
msg.Init( msgBuf, sizeof( msgBuf ) );
msg.WriteShort( CONNECTIONLESS_MESSAGE_ID );
msg.WriteString( "connect" );
msg.WriteLong( ASYNC_PROTOCOL_VERSION );
#if ID_FAKE_PURE
// fake win32 OS - might need to adapt depending on the case
msg.WriteShort( 0 );
#else
msg.WriteShort( BUILD_OS_ID );
#endif
msg.WriteLong( clientDataChecksum );
msg.WriteLong( serverChallenge );
msg.WriteShort( clientId );
msg.WriteLong( cvarSystem->GetCVarInteger( "net_clientMaxRate" ) );
msg.WriteString( cvarSystem->GetCVarString( "com_guid" ) );
msg.WriteString( cvarSystem->GetCVarString( "password" ), -1, false );
// do not make the protocol depend on PB
msg.WriteShort( 0 );
clientPort.SendPacket( serverAddress, msg.GetData(), msg.GetSize() );
if ( idAsyncNetwork::LANServer.GetBool() ) {
common->Printf( "net_LANServer is set, connecting in LAN mode\n" );
} else {
// emit a cd key authorization request
// modified at protocol 1.37 for XP key addition
msg.BeginWriting();
msg.WriteShort( CONNECTIONLESS_MESSAGE_ID );
msg.WriteString( "clAuth" );
msg.WriteLong( ASYNC_PROTOCOL_VERSION );
msg.WriteNetadr( serverAddress );
// if we don't have a com_guid, this will request a direct reply from auth with it
msg.WriteByte( cvarSystem->GetCVarString( "com_guid" )[0] ? 1 : 0 );
// send the main key, and flag an extra byte to add XP key
msg.WriteString( session->GetCDKey( false ) );
const char *xpkey = session->GetCDKey( true );
msg.WriteByte( xpkey ? 1 : 0 );
if ( xpkey ) {
msg.WriteString( xpkey );
}
clientPort.SendPacket( idAsyncNetwork::GetMasterAddress(), msg.GetData(), msg.GetSize() );
}
} else {
return;
}
lastConnectTime = clientTime;
}
/*
==================
idAsyncClient::SendReliableGameMessage
==================
*/
void idAsyncClient::SendReliableGameMessage( const idBitMsg &msg ) {
idBitMsg outMsg;
byte msgBuf[MAX_MESSAGE_SIZE];
if ( clientState < CS_INGAME ) {
return;
}
outMsg.Init( msgBuf, sizeof( msgBuf ) );
outMsg.WriteByte( CLIENT_RELIABLE_MESSAGE_GAME );
outMsg.WriteData( msg.GetData(), msg.GetSize() );
if ( !channel.SendReliableMessage( outMsg ) ) {
common->Error( "client->server reliable messages overflow\n" );
}
}
/*
==================
idAsyncClient::Idle
==================
*/
void idAsyncClient::Idle( void ) {
// also need to read mouse for the connecting guis
usercmdGen->GetDirectUsercmd();
SendEmptyToServer();
}
/*
==================
idAsyncClient::UpdateTime
==================
*/
int idAsyncClient::UpdateTime( int clamp ) {
int time, msec;
time = Sys_Milliseconds();
msec = idMath::ClampInt( 0, clamp, time - realTime );
realTime = time;
clientTime += msec;
return msec;
}
/*
==================
idAsyncClient::RunFrame
==================
*/
void idAsyncClient::RunFrame( void ) {
int msec, size;
bool newPacket;
idBitMsg msg;
byte msgBuf[MAX_MESSAGE_SIZE];
netadr_t from;
msec = UpdateTime( 100 );
if ( !clientPort.GetPort() ) {
return;
}
// handle ongoing pk4 downloads and patch downloads
HandleDownloads();
gameTimeResidual += msec;
// spin in place processing incoming packets until enough time lapsed to run a new game frame
do {
do {
// blocking read with game time residual timeout
newPacket = clientPort.GetPacketBlocking( from, msgBuf, size, sizeof( msgBuf ), USERCMD_MSEC - ( gameTimeResidual + clientPredictTime ) - 1 );
if ( newPacket ) {
msg.Init( msgBuf, sizeof( msgBuf ) );
msg.SetSize( size );
msg.BeginReading();
ProcessMessage( from, msg );
}
msec = UpdateTime( 100 );
gameTimeResidual += msec;
} while( newPacket );
} while( gameTimeResidual + clientPredictTime < USERCMD_MSEC );
// update server list
serverList.RunFrame();
if ( clientState == CS_DISCONNECTED ) {
usercmdGen->GetDirectUsercmd();
gameTimeResidual = USERCMD_MSEC - 1;
clientPredictTime = 0;
return;
}
if ( clientState == CS_PURERESTART ) {
clientState = CS_DISCONNECTED;
Reconnect();
gameTimeResidual = USERCMD_MSEC - 1;
clientPredictTime = 0;
return;
}
// if not connected setup a connection
if ( clientState < CS_CONNECTED ) {
// also need to read mouse for the connecting guis
usercmdGen->GetDirectUsercmd();
SetupConnection();
gameTimeResidual = USERCMD_MSEC - 1;
clientPredictTime = 0;
return;
}
if ( CheckTimeout() ) {
return;
}
// if not yet in the game send empty messages to keep data flowing through the channel
if ( clientState < CS_INGAME ) {
Idle();
gameTimeResidual = 0;
return;
}
// check for user info changes
if ( cvarSystem->GetModifiedFlags() & CVAR_USERINFO ) {
game->ThrottleUserInfo( );
SendUserInfoToServer( );
game->SetUserInfo( clientNum, sessLocal.mapSpawnData.userInfo[ clientNum ], true, false );
cvarSystem->ClearModifiedFlags( CVAR_USERINFO );
}
if ( gameTimeResidual + clientPredictTime >= USERCMD_MSEC ) {
lastFrameDelta = 0;
}
// generate user commands for the predicted time
while ( gameTimeResidual + clientPredictTime >= USERCMD_MSEC ) {
// send the user commands of this client to the server
SendUsercmdsToServer();
// update time
gameFrame++;
gameTime += USERCMD_MSEC;
gameTimeResidual -= USERCMD_MSEC;
// run from the snapshot up to the local game frame
while ( snapshotGameFrame < gameFrame ) {
lastFrameDelta++;
// duplicate usercmds for clients if no new ones are available
DuplicateUsercmds( snapshotGameFrame, snapshotGameTime );
// indicate the last prediction frame before a render
bool lastPredictFrame = ( snapshotGameFrame + 1 >= gameFrame && gameTimeResidual + clientPredictTime < USERCMD_MSEC );
// run client prediction
gameReturn_t ret = game->ClientPrediction( clientNum, userCmds[ snapshotGameFrame & ( MAX_USERCMD_BACKUP - 1 ) ], lastPredictFrame );
idAsyncNetwork::ExecuteSessionCommand( ret.sessionCommand );
snapshotGameFrame++;
snapshotGameTime += USERCMD_MSEC;
}
}
}
/*
==================
idAsyncClient::PacifierUpdate
==================
*/
void idAsyncClient::PacifierUpdate( void ) {
if ( !IsActive() ) {
return;
}
realTime = Sys_Milliseconds();
SendEmptyToServer( false, true );
}
/*
==================
idAsyncClient::SendVersionCheck
==================
*/
void idAsyncClient::SendVersionCheck( bool fromMenu ) {
idBitMsg msg;
byte msgBuf[MAX_MESSAGE_SIZE];
if ( updateState != UPDATE_NONE && !fromMenu ) {
common->DPrintf( "up-to-date check was already performed\n" );
return;
}
InitPort();
msg.Init( msgBuf, sizeof( msgBuf ) );
msg.WriteShort( CONNECTIONLESS_MESSAGE_ID );
msg.WriteString( "versionCheck" );
msg.WriteLong( ASYNC_PROTOCOL_VERSION );
msg.WriteShort( BUILD_OS_ID );
msg.WriteString( cvarSystem->GetCVarString( "si_version" ) );
msg.WriteString( cvarSystem->GetCVarString( "com_guid" ) );
clientPort.SendPacket( idAsyncNetwork::GetMasterAddress(), msg.GetData(), msg.GetSize() );
common->DPrintf( "sent a version check request\n" );
updateState = UPDATE_SENT;
updateSentTime = clientTime;
showUpdateMessage = fromMenu;
}
/*
==================
idAsyncClient::SendVersionDLUpdate
sending those packets is not strictly necessary. just a way to tell the update server
about what is going on. allows the update server to have a more precise view of the overall
network load for the updates
==================
*/
void idAsyncClient::SendVersionDLUpdate( int state ) {
idBitMsg msg;
byte msgBuf[MAX_MESSAGE_SIZE];
msg.Init( msgBuf, sizeof( msgBuf ) );
msg.WriteShort( CONNECTIONLESS_MESSAGE_ID );
msg.WriteString( "versionDL" );
msg.WriteLong( ASYNC_PROTOCOL_VERSION );
msg.WriteShort( state );
clientPort.SendPacket( idAsyncNetwork::GetMasterAddress(), msg.GetData(), msg.GetSize() );
}
/*
==================
idAsyncClient::HandleDownloads
==================
*/
void idAsyncClient::HandleDownloads( void ) {
if ( updateState == UPDATE_SENT && clientTime > updateSentTime + 2000 ) {
// timing out on no reply
updateState = UPDATE_DONE;
if ( showUpdateMessage ) {
session->MessageBox( MSG_OK, common->GetLanguageDict()->GetString ( "#str_04839" ), common->GetLanguageDict()->GetString ( "#str_04837" ), true );
showUpdateMessage = false;
}
common->DPrintf( "No update available\n" );
} else if ( backgroundDownload.completed ) {
// only enter these if the download slot is free
if ( updateState == UPDATE_READY ) {
//
if ( session->MessageBox( MSG_YESNO, updateMSG, common->GetLanguageDict()->GetString ( "#str_04330" ), true, "yes" )[0] ) {
if ( !updateDirectDownload ) {
sys->OpenURL( updateURL, true );
updateState = UPDATE_DONE;
} else {
// we're just creating the file at toplevel inside fs_savepath
updateURL.ExtractFileName( updateFile );
idFile_Permanent *f = static_cast< idFile_Permanent *>( fileSystem->OpenFileWrite( updateFile ) );
dltotal = 0;
dlnow = 0;
backgroundDownload.completed = false;
backgroundDownload.opcode = DLTYPE_URL;
backgroundDownload.f = f;
backgroundDownload.url.status = DL_WAIT;
backgroundDownload.url.dlnow = 0;
backgroundDownload.url.dltotal = 0;
backgroundDownload.url.url = updateURL;
fileSystem->BackgroundDownload( &backgroundDownload );
updateState = UPDATE_DLING;
SendVersionDLUpdate( 0 );
session->DownloadProgressBox( &backgroundDownload, va( "Downloading %s\n", updateFile.c_str() ) );
updateState = UPDATE_DONE;
if ( backgroundDownload.url.status == DL_DONE ) {
SendVersionDLUpdate( 1 );
idStr fullPath = f->GetFullPath();
fileSystem->CloseFile( f );
if ( session->MessageBox( MSG_YESNO, common->GetLanguageDict()->GetString ( "#str_04331" ), common->GetLanguageDict()->GetString ( "#str_04332" ), true, "yes" )[0] ) {
if ( updateMime == FILE_EXEC ) {
sys->StartProcess( fullPath, true );
} else {
sys->OpenURL( va( "file://%s", fullPath.c_str() ), true );
}
} else {
session->MessageBox( MSG_OK, va( common->GetLanguageDict()->GetString ( "#str_04333" ), fullPath.c_str() ), common->GetLanguageDict()->GetString ( "#str_04334" ), true );
}
} else {
if ( backgroundDownload.url.dlerror[ 0 ] ) {
common->Warning( "update download failed. curl error: %s", backgroundDownload.url.dlerror );
}
SendVersionDLUpdate( 2 );
idStr name = f->GetName();
fileSystem->CloseFile( f );
fileSystem->RemoveFile( name );
session->MessageBox( MSG_OK, common->GetLanguageDict()->GetString ( "#str_04335" ), common->GetLanguageDict()->GetString ( "#str_04336" ), true );
if ( updateFallback.Length() ) {
sys->OpenURL( updateFallback.c_str(), true );
} else {
common->Printf( "no fallback URL\n" );
}
}
}
} else {
updateState = UPDATE_DONE;
}
} else if ( dlList.Num() ) {
int numPaks = dlList.Num();
int pakCount = 1;
int progress_start, progress_end;
currentDlSize = 0;
do {
if ( dlList[ 0 ].url[ 0 ] == '\0' ) {
// ignore empty files
dlList.RemoveIndex( 0 );
continue;
}
common->Printf( "start download for %s\n", dlList[ 0 ].url.c_str() );
idFile_Permanent *f = static_cast< idFile_Permanent *>( fileSystem->MakeTemporaryFile( ) );
if ( !f ) {
common->Warning( "could not create temporary file" );
dlList.Clear();
return;
}
backgroundDownload.completed = false;
backgroundDownload.opcode = DLTYPE_URL;
backgroundDownload.f = f;
backgroundDownload.url.status = DL_WAIT;
backgroundDownload.url.dlnow = 0;
backgroundDownload.url.dltotal = dlList[ 0 ].size;
backgroundDownload.url.url = dlList[ 0 ].url;
fileSystem->BackgroundDownload( &backgroundDownload );
idStr dltitle;
// "Downloading %s"
sprintf( dltitle, common->GetLanguageDict()->GetString( "#str_07213" ), dlList[ 0 ].filename.c_str() );
if ( numPaks > 1 ) {
dltitle += va( " (%d/%d)", pakCount, numPaks );
}
if ( totalDlSize ) {
progress_start = (int)( (float)currentDlSize * 100.0f / (float)totalDlSize );
progress_end = (int)( (float)( currentDlSize + dlList[ 0 ].size ) * 100.0f / (float)totalDlSize );
} else {
progress_start = 0;
progress_end = 100;
}
session->DownloadProgressBox( &backgroundDownload, dltitle, progress_start, progress_end );
if ( backgroundDownload.url.status == DL_DONE ) {
idFile *saveas;
const int CHUNK_SIZE = 1024 * 1024;
byte *buf;
int remainlen;
int readlen;
int retlen;
int checksum;
common->Printf( "file downloaded\n" );
idStr finalPath = cvarSystem->GetCVarString( "fs_savepath" );
finalPath.AppendPath( dlList[ 0 ].filename );
fileSystem->CreateOSPath( finalPath );
// do the final copy ourselves so we do by small chunks in case the file is big
saveas = fileSystem->OpenExplicitFileWrite( finalPath );
buf = (byte*)Mem_Alloc( CHUNK_SIZE );
f->Seek( 0, FS_SEEK_END );
remainlen = f->Tell();
f->Seek( 0, FS_SEEK_SET );
while ( remainlen ) {
readlen = Min( remainlen, CHUNK_SIZE );
retlen = f->Read( buf, readlen );
if ( retlen != readlen ) {
common->FatalError( "short read %d of %d in idFileSystem::HandleDownload", retlen, readlen );
}
retlen = saveas->Write( buf, readlen );
if ( retlen != readlen ) {
common->FatalError( "short write %d of %d in idFileSystem::HandleDownload", retlen, readlen );
}
remainlen -= readlen;
}
fileSystem->CloseFile( f );
fileSystem->CloseFile( saveas );
common->Printf( "saved as %s\n", finalPath.c_str() );
Mem_Free( buf );
// add that file to our paks list
checksum = fileSystem->AddZipFile( dlList[ 0 ].filename );
// verify the checksum to be what the server says
if ( !checksum || checksum != dlList[ 0 ].checksum ) {
// "pak is corrupted ( checksum 0x%x, expected 0x%x )"
session->MessageBox( MSG_OK, va( common->GetLanguageDict()->GetString( "#str_07214" ) , checksum, dlList[0].checksum ), "Download failed", true );
fileSystem->RemoveFile( dlList[ 0 ].filename );
dlList.Clear();
return;
}
currentDlSize += dlList[ 0 ].size;
} else {
common->Warning( "download failed: %s", dlList[ 0 ].url.c_str() );
if ( backgroundDownload.url.dlerror[ 0 ] ) {
common->Warning( "curl error: %s", backgroundDownload.url.dlerror );
}
// "The download failed or was cancelled"
// "Download failed"
session->MessageBox( MSG_OK, common->GetLanguageDict()->GetString( "#str_07215" ), common->GetLanguageDict()->GetString( "#str_07216" ), true );
dlList.Clear();
return;
}
pakCount++;
dlList.RemoveIndex( 0 );
} while ( dlList.Num() );
// all downloads successful - do the dew
cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "reconnect\n" );
}
}
}
/*
===============
idAsyncClient::SendAuthCheck
===============
*/
bool idAsyncClient::SendAuthCheck( const char *cdkey, const char *xpkey ) {
idBitMsg msg;
byte msgBuf[MAX_MESSAGE_SIZE];
msg.Init( msgBuf, sizeof( msgBuf ) );
msg.WriteShort( CONNECTIONLESS_MESSAGE_ID );
msg.WriteString( "gameAuth" );
msg.WriteLong( ASYNC_PROTOCOL_VERSION );
msg.WriteByte( cdkey ? 1 : 0 );
msg.WriteString( cdkey ? cdkey : "" );
msg.WriteByte( xpkey ? 1 : 0 );
msg.WriteString( xpkey ? xpkey : "" );
InitPort();
clientPort.SendPacket( idAsyncNetwork::GetMasterAddress(), msg.GetData(), msg.GetSize() );
return true;
}
/*
===============
idAsyncClient::CheckTimeout
===============
*/
bool idAsyncClient::CheckTimeout( void ) {
if ( lastPacketTime > 0 && ( lastPacketTime + idAsyncNetwork::clientServerTimeout.GetInteger()*1000 < clientTime ) ) {
session->StopBox();
session->MessageBox( MSG_OK, common->GetLanguageDict()->GetString ( "#str_04328" ), common->GetLanguageDict()->GetString ( "#str_04329" ), true );
cmdSystem->BufferCommandText( CMD_EXEC_NOW, "disconnect" );
return true;
}
return false;
}
/*
===============
idAsyncClient::ProcessDownloadInfoMessage
===============
*/
void idAsyncClient::ProcessDownloadInfoMessage( const netadr_t from, const idBitMsg &msg ) {
char buf[ MAX_STRING_CHARS ];
int srvDlRequest = msg.ReadLong();
int infoType = msg.ReadByte();
int pakDl;
int pakIndex;
pakDlEntry_t entry;
bool gotAllFiles = true;
idStr sizeStr;
bool gotGame = false;
if ( dlRequest == -1 || srvDlRequest != dlRequest ) {
common->Warning( "bad download id from server, ignored" );
return;
}
// mark the dlRequest as dead now whatever how we process it
dlRequest = -1;
if ( infoType == SERVER_DL_REDIRECT ) {
msg.ReadString( buf, MAX_STRING_CHARS );
cmdSystem->BufferCommandText( CMD_EXEC_NOW, "disconnect" );
// "You are missing required pak files to connect to this server.\nThe server gave a web page though:\n%s\nDo you want to go there now?"
// "Missing required files"
if ( session->MessageBox( MSG_YESNO, va( common->GetLanguageDict()->GetString( "#str_07217" ), buf ),
common->GetLanguageDict()->GetString( "#str_07218" ), true, "yes" )[ 0 ] ) {
sys->OpenURL( buf, true );
}
} else if ( infoType == SERVER_DL_LIST ) {
cmdSystem->BufferCommandText( CMD_EXEC_NOW, "disconnect" );
if ( dlList.Num() ) {
common->Warning( "tried to process a download list while already busy downloading things" );
return;
}
// read the URLs, check against what we requested, prompt for download
pakIndex = -1;
totalDlSize = 0;
do {
pakIndex++;
pakDl = msg.ReadByte();
if ( pakDl == SERVER_PAK_YES ) {
if ( pakIndex == 0 ) {
gotGame = true;
}
msg.ReadString( buf, MAX_STRING_CHARS );
entry.filename = buf;
msg.ReadString( buf, MAX_STRING_CHARS );
entry.url = buf;
entry.size = msg.ReadLong();
// checksums are not transmitted, we read them from the dl request we sent
entry.checksum = dlChecksums[ pakIndex ];
totalDlSize += entry.size;
dlList.Append( entry );
common->Printf( "download %s from %s ( 0x%x )\n", entry.filename.c_str(), entry.url.c_str(), entry.checksum );
} else if ( pakDl == SERVER_PAK_NO ) {
msg.ReadString( buf, MAX_STRING_CHARS );
entry.filename = buf;
entry.url = "";
entry.size = 0;
entry.checksum = 0;
dlList.Append( entry );
// first pak is game pak, only fail it if we actually requested it
if ( pakIndex != 0 || dlChecksums[ 0 ] != 0 ) {
common->Printf( "no download offered for %s ( 0x%x )\n", entry.filename.c_str(), dlChecksums[ pakIndex ] );
gotAllFiles = false;
}
} else {
assert( pakDl == SERVER_PAK_END );
}
} while ( pakDl != SERVER_PAK_END );
if ( dlList.Num() < dlCount ) {
common->Printf( "%d files were ignored by the server\n", dlCount - dlList.Num() );
gotAllFiles = false;
}
sizeStr.BestUnit( "%.2f", totalDlSize, MEASURE_SIZE );
cmdSystem->BufferCommandText( CMD_EXEC_NOW, "disconnect" );
if ( totalDlSize == 0 ) {
// was no downloadable stuff for us
// "Can't connect to the pure server: no downloads offered"
// "Missing required files"
dlList.Clear();
session->MessageBox( MSG_OK, common->GetLanguageDict()->GetString( "#str_07219" ), common->GetLanguageDict()->GetString( "#str_07218" ), true );
return;
}
bool asked = false;
if ( gotGame ) {
asked = true;
// "You need to download game code to connect to this server. Are you sure? You should only answer yes if you trust the server administrators."
// "Missing game binaries"
if ( !session->MessageBox( MSG_YESNO, common->GetLanguageDict()->GetString( "#str_07220" ), common->GetLanguageDict()->GetString( "#str_07221" ), true, "yes" )[ 0 ] ) {
dlList.Clear();
return;
}
}
if ( !gotAllFiles ) {
asked = true;
// "The server only offers to download some of the files required to connect ( %s ). Download anyway?"
// "Missing required files"
if ( !session->MessageBox( MSG_YESNO, va( common->GetLanguageDict()->GetString( "#str_07222" ), sizeStr.c_str() ),
common->GetLanguageDict()->GetString( "#str_07218" ), true, "yes" )[ 0 ] ) {
dlList.Clear();
return;
}
}
if ( !asked && idAsyncNetwork::clientDownload.GetInteger() == 1 ) {
// "You need to download some files to connect to this server ( %s ), proceed?"
// "Missing required files"
if ( !session->MessageBox( MSG_YESNO, va( common->GetLanguageDict()->GetString( "#str_07224" ), sizeStr.c_str() ),
common->GetLanguageDict()->GetString( "#str_07218" ), true, "yes" )[ 0 ] ) {
dlList.Clear();
return;
}
}
} else {
cmdSystem->BufferCommandText( CMD_EXEC_NOW, "disconnect" );
// "You are missing some files to connect to this server, and the server doesn't provide downloads."
// "Missing required files"
session->MessageBox( MSG_OK, common->GetLanguageDict()->GetString( "#str_07223" ), common->GetLanguageDict()->GetString( "#str_07218" ), true );
}
}
/*
===============
idAsyncClient::GetDownloadRequest
===============
*/
int idAsyncClient::GetDownloadRequest( const int checksums[ MAX_PURE_PAKS ], int count, int gamePakChecksum ) {
assert( !checksums[ count ] ); // 0-terminated
if ( memcmp( dlChecksums + 1, checksums, sizeof( int ) * count ) || gamePakChecksum != dlChecksums[ 0 ] ) {
idRandom newreq;
dlChecksums[ 0 ] = gamePakChecksum;
memcpy( dlChecksums + 1, checksums, sizeof( int ) * MAX_PURE_PAKS );
newreq.SetSeed( Sys_Milliseconds() );
dlRequest = newreq.RandomInt();
dlCount = count + ( gamePakChecksum ? 1 : 0 );
return dlRequest;
}
// this is the same dlRequest, we haven't heard from the server. keep the same id
return dlRequest;
}