2 ===========================================================================
5 Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company.
7 This file is part of the Doom 3 GPL Source Code (?Doom 3 Source Code?).
9 Doom 3 Source Code is free software: you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation, either version 3 of the License, or
12 (at your option) any later version.
14 Doom 3 Source Code is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 GNU General Public License for more details.
19 You should have received a copy of the GNU General Public License
20 along with Doom 3 Source Code. If not, see <http://www.gnu.org/licenses/>.
22 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.
24 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.
26 ===========================================================================
29 #include "../../idlib/precompiled.h"
32 #include "AsyncNetwork.h"
34 #include "../Session_local.h"
36 const int SETUP_CONNECTION_RESEND_TIME = 1000;
37 const int EMPTY_RESEND_TIME = 500;
38 const int PREDICTION_FAST_ADJUST = 4;
43 idAsyncClient::idAsyncClient
46 idAsyncClient::idAsyncClient( void ) {
48 updateState = UPDATE_NONE;
57 void idAsyncClient::Clear( void ) {
62 clientDataChecksum = 0;
64 clientState = CS_DISCONNECTED;
66 clientPredictTime = 0;
69 serverMessageSequence = 0;
70 lastConnectTime = -9999;
71 lastEmptyTime = -9999;
72 lastPacketTime = -9999;
73 lastSnapshotTime = -9999;
74 snapshotGameFrame = 0;
77 gameInitId = GAME_INIT_ID_INVALID;
81 memset( userCmds, 0, sizeof( userCmds ) );
82 backgroundDownload.completed = true;
84 showUpdateMessage = false;
89 memset( dlChecksums, 0, sizeof( int ) * MAX_PURE_PAKS );
96 idAsyncClient::Shutdown
99 void idAsyncClient::Shutdown( void ) {
104 updateFallback.Clear();
105 backgroundDownload.url.url.Clear();
111 idAsyncClient::InitPort
114 bool idAsyncClient::InitPort( void ) {
115 // if this is the first time we connect to a server, open the UDP port
116 if ( !clientPort.GetPort() ) {
117 if ( !clientPort.InitForPort( PORT_ANY ) ) {
118 common->Printf( "Couldn't open client network port.\n" );
122 // maintain it valid between connects and ui manager reloads
123 guiNetMenu = uiManager->FindGui( "guis/netmenu.gui", true, false, true );
130 idAsyncClient::ClosePort
133 void idAsyncClient::ClosePort( void ) {
139 idAsyncClient::ClearPendingPackets
142 void idAsyncClient::ClearPendingPackets( void ) {
144 byte msgBuf[MAX_MESSAGE_SIZE];
147 while( clientPort.GetPacket( from, msgBuf, size, sizeof( msgBuf ) ) ) {
153 idAsyncClient::HandleGuiCommandInternal
156 const char* idAsyncClient::HandleGuiCommandInternal( const char *cmd ) {
157 if ( !idStr::Cmp( cmd, "abort" ) || !idStr::Cmp( cmd, "pure_abort" ) ) {
158 common->DPrintf( "connection aborted\n" );
159 cmdSystem->BufferCommandText( CMD_EXEC_NOW, "disconnect" );
162 common->DWarning( "idAsyncClient::HandleGuiCommand: unknown cmd %s", cmd );
169 idAsyncClient::HandleGuiCommand
172 const char* idAsyncClient::HandleGuiCommand( const char *cmd ) {
173 return idAsyncNetwork::client.HandleGuiCommandInternal( cmd );
178 idAsyncClient::ConnectToServer
181 void idAsyncClient::ConnectToServer( const netadr_t adr ) {
182 // shutdown any current game. that includes network disconnect
189 if ( cvarSystem->GetCVarBool( "net_serverDedicated" ) ) {
190 common->Printf( "Can't connect to a server as dedicated\n" );
194 // trash any currently pending packets
195 ClearPendingPackets();
199 // clear the client state
202 // get a pseudo random client id, but don't use the id which is reserved for connectionless packets
203 clientId = Sys_Milliseconds() & CONNECTIONLESS_MESSAGE_ID_MASK;
205 // calculate a checksum on some of the essential data used
206 clientDataChecksum = declManager->GetChecksum();
208 // start challenging the server
209 clientState = CS_CHALLENGING;
213 guiNetMenu = uiManager->FindGui( "guis/netmenu.gui", true, false, true );
214 guiNetMenu->SetStateString( "status", va( common->GetLanguageDict()->GetString( "#str_06749" ), Sys_NetAdrToString( adr ) ) );
215 session->SetGUI( guiNetMenu, HandleGuiCommand );
220 idAsyncClient::Reconnect
223 void idAsyncClient::Reconnect( void ) {
224 ConnectToServer( serverAddress );
229 idAsyncClient::ConnectToServer
232 void idAsyncClient::ConnectToServer( const char *address ) {
236 if ( idStr::IsNumeric( address ) ) {
237 serverNum = atoi( address );
238 if ( serverNum < 0 || serverNum >= serverList.Num() ) {
239 session->MessageBox( MSG_OK, va( common->GetLanguageDict()->GetString( "#str_06733" ), serverNum ), common->GetLanguageDict()->GetString( "#str_06735" ), true );
242 adr = serverList[ serverNum ].adr;
244 if ( !Sys_StringToNetAdr( address, &adr, true ) ) {
245 session->MessageBox( MSG_OK, va( common->GetLanguageDict()->GetString( "#str_06734" ), address ), common->GetLanguageDict()->GetString( "#str_06735" ), true );
250 adr.port = PORT_SERVER;
253 common->Printf( "\"%s\" resolved to %s\n", address, Sys_NetAdrToString( adr ) );
255 ConnectToServer( adr );
260 idAsyncClient::DisconnectFromServer
263 void idAsyncClient::DisconnectFromServer( void ) {
265 byte msgBuf[MAX_MESSAGE_SIZE];
267 if ( clientState >= CS_CONNECTED ) {
268 // if we were actually connected, clear the pure list
269 fileSystem->ClearPureChecksums();
271 // send reliable disconnect to server
272 msg.Init( msgBuf, sizeof( msgBuf ) );
273 msg.WriteByte( CLIENT_RELIABLE_MESSAGE_DISCONNECT );
274 msg.WriteString( "disconnect" );
276 if ( !channel.SendReliableMessage( msg ) ) {
277 common->Error( "client->server reliable messages overflow\n" );
280 SendEmptyToServer( true );
281 SendEmptyToServer( true );
282 SendEmptyToServer( true );
285 if ( clientState != CS_PURERESTART ) {
287 clientState = CS_DISCONNECTED;
295 idAsyncClient::GetServerInfo
298 void idAsyncClient::GetServerInfo( const netadr_t adr ) {
300 byte msgBuf[MAX_MESSAGE_SIZE];
306 msg.Init( msgBuf, sizeof( msgBuf ) );
307 msg.WriteShort( CONNECTIONLESS_MESSAGE_ID );
308 msg.WriteString( "getInfo" );
309 msg.WriteLong( serverList.GetChallenge() ); // challenge
311 clientPort.SendPacket( adr, msg.GetData(), msg.GetSize() );
316 idAsyncClient::GetServerInfo
319 void idAsyncClient::GetServerInfo( const char *address ) {
322 if ( address && *address != '\0' ) {
323 if ( !Sys_StringToNetAdr( address, &adr, true ) ) {
324 common->Printf( "Couldn't get server address for \"%s\"\n", address );
327 } else if ( active ) {
329 } else if ( idAsyncNetwork::server.IsActive() ) {
330 // used to be a Sys_StringToNetAdr( "localhost", &adr, true ); and send a packet over loopback
331 // but this breaks with net_ip ( typically, for multi-homed servers )
332 idAsyncNetwork::server.PrintLocalServerInfo();
335 common->Printf( "no server found\n" );
340 adr.port = PORT_SERVER;
343 GetServerInfo( adr );
348 idAsyncClient::GetLANServers
351 void idAsyncClient::GetLANServers( void ) {
354 byte msgBuf[MAX_MESSAGE_SIZE];
355 netadr_t broadcastAddress;
361 idAsyncNetwork::LANServer.SetBool( true );
363 serverList.SetupLANScan();
365 msg.Init( msgBuf, sizeof( msgBuf ) );
366 msg.WriteShort( CONNECTIONLESS_MESSAGE_ID );
367 msg.WriteString( "getInfo" );
368 msg.WriteLong( serverList.GetChallenge() );
370 broadcastAddress.type = NA_BROADCAST;
371 for ( i = 0; i < MAX_SERVER_PORTS; i++ ) {
372 broadcastAddress.port = PORT_SERVER + i;
373 clientPort.SendPacket( broadcastAddress, msg.GetData(), msg.GetSize() );
379 idAsyncClient::GetNETServers
382 void idAsyncClient::GetNETServers( void ) {
384 byte msgBuf[MAX_MESSAGE_SIZE];
386 idAsyncNetwork::LANServer.SetBool( false );
388 // NetScan only clears GUI and results, not the stored list
390 serverList.NetScan( );
391 serverList.StartServers( true );
393 msg.Init( msgBuf, sizeof( msgBuf ) );
394 msg.WriteShort( CONNECTIONLESS_MESSAGE_ID );
395 msg.WriteString( "getServers" );
396 msg.WriteLong( ASYNC_PROTOCOL_VERSION );
397 msg.WriteString( cvarSystem->GetCVarString( "fs_game" ) );
398 msg.WriteBits( cvarSystem->GetCVarInteger( "gui_filter_password" ), 2 );
399 msg.WriteBits( cvarSystem->GetCVarInteger( "gui_filter_players" ), 2 );
400 msg.WriteBits( cvarSystem->GetCVarInteger( "gui_filter_gameType" ), 2 );
403 if ( idAsyncNetwork::GetMasterAddress( 0, adr ) ) {
404 clientPort.SendPacket( adr, msg.GetData(), msg.GetSize() );
410 idAsyncClient::ListServers
413 void idAsyncClient::ListServers( void ) {
416 for ( i = 0; i < serverList.Num(); i++ ) {
417 common->Printf( "%3d: %s %dms (%s)\n", i, serverList[i].serverInfo.GetString( "si_name" ), serverList[ i ].ping, Sys_NetAdrToString( serverList[i].adr ) );
423 idAsyncClient::ClearServers
426 void idAsyncClient::ClearServers( void ) {
432 idAsyncClient::RemoteConsole
435 void idAsyncClient::RemoteConsole( const char *command ) {
438 byte msgBuf[MAX_MESSAGE_SIZE];
447 Sys_StringToNetAdr( idAsyncNetwork::clientRemoteConsoleAddress.GetString(), &adr, true );
451 adr.port = PORT_SERVER;
454 lastRconAddress = adr;
455 lastRconTime = realTime;
457 msg.Init( msgBuf, sizeof( msgBuf ) );
458 msg.WriteShort( CONNECTIONLESS_MESSAGE_ID );
459 msg.WriteString( "rcon" );
460 msg.WriteString( idAsyncNetwork::clientRemoteConsolePassword.GetString() );
461 msg.WriteString( command );
463 clientPort.SendPacket( adr, msg.GetData(), msg.GetSize() );
468 idAsyncClient::GetPrediction
471 int idAsyncClient::GetPrediction( void ) const {
472 if ( clientState < CS_CONNECTED ) {
475 return clientPrediction;
481 idAsyncClient::GetTimeSinceLastPacket
484 int idAsyncClient::GetTimeSinceLastPacket( void ) const {
485 if ( clientState < CS_CONNECTED ) {
488 return clientTime - lastPacketTime;
494 idAsyncClient::GetOutgoingRate
497 int idAsyncClient::GetOutgoingRate( void ) const {
498 if ( clientState < CS_CONNECTED ) {
501 return channel.GetOutgoingRate();
507 idAsyncClient::GetIncomingRate
510 int idAsyncClient::GetIncomingRate( void ) const {
511 if ( clientState < CS_CONNECTED ) {
514 return channel.GetIncomingRate();
520 idAsyncClient::GetOutgoingCompression
523 float idAsyncClient::GetOutgoingCompression( void ) const {
524 if ( clientState < CS_CONNECTED ) {
527 return channel.GetOutgoingCompression();
533 idAsyncClient::GetIncomingCompression
536 float idAsyncClient::GetIncomingCompression( void ) const {
537 if ( clientState < CS_CONNECTED ) {
540 return channel.GetIncomingCompression();
546 idAsyncClient::GetIncomingPacketLoss
549 float idAsyncClient::GetIncomingPacketLoss( void ) const {
550 if ( clientState < CS_CONNECTED ) {
553 return channel.GetIncomingPacketLoss();
559 idAsyncClient::DuplicateUsercmds
562 void idAsyncClient::DuplicateUsercmds( int frame, int time ) {
563 int i, previousIndex, currentIndex;
565 previousIndex = ( frame - 1 ) & ( MAX_USERCMD_BACKUP - 1 );
566 currentIndex = frame & ( MAX_USERCMD_BACKUP - 1 );
568 // duplicate previous user commands if no new commands are available for a client
569 for ( i = 0; i < MAX_ASYNC_CLIENTS; i++ ) {
570 idAsyncNetwork::DuplicateUsercmd( userCmds[previousIndex][i], userCmds[currentIndex][i], frame, time );
576 idAsyncClient::SendUserInfoToServer
579 void idAsyncClient::SendUserInfoToServer( void ) {
581 byte msgBuf[MAX_MESSAGE_SIZE];
584 if ( clientState < CS_CONNECTED ) {
588 info = *cvarSystem->MoveCVarsToDict( CVAR_USERINFO );
590 // send reliable client info to server
591 msg.Init( msgBuf, sizeof( msgBuf ) );
592 msg.WriteByte( CLIENT_RELIABLE_MESSAGE_CLIENTINFO );
593 msg.WriteDeltaDict( info, &sessLocal.mapSpawnData.userInfo[ clientNum ] );
595 if ( !channel.SendReliableMessage( msg ) ) {
596 common->Error( "client->server reliable messages overflow\n" );
599 sessLocal.mapSpawnData.userInfo[clientNum] = info;
604 idAsyncClient::SendEmptyToServer
607 void idAsyncClient::SendEmptyToServer( bool force, bool mapLoad ) {
609 byte msgBuf[MAX_MESSAGE_SIZE];
611 if ( lastEmptyTime > realTime ) {
612 lastEmptyTime = realTime;
615 if ( !force && ( realTime - lastEmptyTime < EMPTY_RESEND_TIME ) ) {
619 if ( idAsyncNetwork::verbose.GetInteger() ) {
620 common->Printf( "sending empty to server, gameInitId = %d\n", mapLoad ? GAME_INIT_ID_MAP_LOAD : gameInitId );
623 msg.Init( msgBuf, sizeof( msgBuf ) );
624 msg.WriteLong( serverMessageSequence );
625 msg.WriteLong( mapLoad ? GAME_INIT_ID_MAP_LOAD : gameInitId );
626 msg.WriteLong( snapshotSequence );
627 msg.WriteByte( CLIENT_UNRELIABLE_MESSAGE_EMPTY );
629 channel.SendMessage( clientPort, clientTime, msg );
631 while( channel.UnsentFragmentsLeft() ) {
632 channel.SendNextFragment( clientPort, clientTime );
635 lastEmptyTime = realTime;
640 idAsyncClient::SendPingResponseToServer
643 void idAsyncClient::SendPingResponseToServer( int time ) {
645 byte msgBuf[MAX_MESSAGE_SIZE];
647 if ( idAsyncNetwork::verbose.GetInteger() == 2 ) {
648 common->Printf( "sending ping response to server, gameInitId = %d\n", gameInitId );
651 msg.Init( msgBuf, sizeof( msgBuf ) );
652 msg.WriteLong( serverMessageSequence );
653 msg.WriteLong( gameInitId );
654 msg.WriteLong( snapshotSequence );
655 msg.WriteByte( CLIENT_UNRELIABLE_MESSAGE_PINGRESPONSE );
656 msg.WriteLong( time );
658 channel.SendMessage( clientPort, clientTime, msg );
659 while( channel.UnsentFragmentsLeft() ) {
660 channel.SendNextFragment( clientPort, clientTime );
666 idAsyncClient::SendUsercmdsToServer
669 void idAsyncClient::SendUsercmdsToServer( void ) {
670 int i, numUsercmds, index;
672 byte msgBuf[MAX_MESSAGE_SIZE];
675 if ( idAsyncNetwork::verbose.GetInteger() == 2 ) {
676 common->Printf( "sending usercmd to server: gameInitId = %d, gameFrame = %d, gameTime = %d\n", gameInitId, gameFrame, gameTime );
679 // generate user command for this client
680 index = gameFrame & ( MAX_USERCMD_BACKUP - 1 );
681 userCmds[index][clientNum] = usercmdGen->GetDirectUsercmd();
682 userCmds[index][clientNum].gameFrame = gameFrame;
683 userCmds[index][clientNum].gameTime = gameTime;
685 // send the user commands to the server
686 msg.Init( msgBuf, sizeof( msgBuf ) );
687 msg.WriteLong( serverMessageSequence );
688 msg.WriteLong( gameInitId );
689 msg.WriteLong( snapshotSequence );
690 msg.WriteByte( CLIENT_UNRELIABLE_MESSAGE_USERCMD );
691 msg.WriteShort( clientPrediction );
693 numUsercmds = idMath::ClampInt( 0, 10, idAsyncNetwork::clientUsercmdBackup.GetInteger() ) + 1;
695 // write the user commands
696 msg.WriteLong( gameFrame );
697 msg.WriteByte( numUsercmds );
698 for ( last = NULL, i = gameFrame - numUsercmds + 1; i <= gameFrame; i++ ) {
699 index = i & ( MAX_USERCMD_BACKUP - 1 );
700 idAsyncNetwork::WriteUserCmdDelta( msg, userCmds[index][clientNum], last );
701 last = &userCmds[index][clientNum];
704 channel.SendMessage( clientPort, clientTime, msg );
705 while( channel.UnsentFragmentsLeft() ) {
706 channel.SendNextFragment( clientPort, clientTime );
712 idAsyncClient::InitGame
715 void idAsyncClient::InitGame( int serverGameInitId, int serverGameFrame, int serverGameTime, const idDict &serverSI ) {
716 gameInitId = serverGameInitId;
717 gameFrame = snapshotGameFrame = serverGameFrame;
718 gameTime = snapshotGameTime = serverGameTime;
719 gameTimeResidual = 0;
720 memset( userCmds, 0, sizeof( userCmds ) );
722 for ( int i = 0; i < MAX_ASYNC_CLIENTS; i++ ) {
723 sessLocal.mapSpawnData.userInfo[ i ].Clear();
726 sessLocal.mapSpawnData.serverInfo = serverSI;
731 idAsyncClient::ProcessUnreliableServerMessage
734 void idAsyncClient::ProcessUnreliableServerMessage( const idBitMsg &msg ) {
735 int i, j, index, id, numDuplicatedUsercmds, aheadOfServer, numUsercmds, delta;
736 int serverGameInitId, serverGameFrame, serverGameTime;
741 serverGameInitId = msg.ReadLong();
745 case SERVER_UNRELIABLE_MESSAGE_EMPTY: {
746 if ( idAsyncNetwork::verbose.GetInteger() ) {
747 common->Printf( "received empty message from server\n" );
751 case SERVER_UNRELIABLE_MESSAGE_PING: {
752 if ( idAsyncNetwork::verbose.GetInteger() == 2 ) {
753 common->Printf( "received ping message from server\n" );
755 SendPingResponseToServer( msg.ReadLong() );
758 case SERVER_UNRELIABLE_MESSAGE_GAMEINIT: {
759 serverGameFrame = msg.ReadLong();
760 serverGameTime = msg.ReadLong();
761 msg.ReadDeltaDict( serverSI, NULL );
762 pureWait = serverSI.GetBool( "si_pure" );
764 InitGame( serverGameInitId, serverGameFrame, serverGameTime, serverSI );
768 if ( idAsyncNetwork::verbose.GetInteger() ) {
769 common->Printf( "received gameinit, gameInitId = %d, gameFrame = %d, gameTime = %d\n", gameInitId, gameFrame, gameTime );
773 soundSystem->SetMute( true );
775 // ensure chat icon goes away when the GUI is changed...
776 //cvarSystem->SetCVarBool( "ui_chat", false );
779 guiNetMenu = uiManager->FindGui( "guis/netmenu.gui", true, false, true );
780 session->SetGUI( guiNetMenu, HandleGuiCommand );
781 session->MessageBox( MSG_ABORT, common->GetLanguageDict()->GetString ( "#str_04317" ), common->GetLanguageDict()->GetString ( "#str_04318" ), false, "pure_abort" );
784 session->SetGUI( NULL, NULL );
785 sessLocal.ExecuteMapChange();
790 case SERVER_UNRELIABLE_MESSAGE_SNAPSHOT: {
791 // if the snapshot is from a different game
792 if ( serverGameInitId != gameInitId ) {
793 if ( idAsyncNetwork::verbose.GetInteger() ) {
794 common->Printf( "ignoring snapshot with != gameInitId\n" );
799 snapshotSequence = msg.ReadLong();
800 snapshotGameFrame = msg.ReadLong();
801 snapshotGameTime = msg.ReadLong();
802 numDuplicatedUsercmds = msg.ReadByte();
803 aheadOfServer = msg.ReadShort();
805 // read the game snapshot
806 game->ClientReadSnapshot( clientNum, snapshotSequence, snapshotGameFrame, snapshotGameTime, numDuplicatedUsercmds, aheadOfServer, msg );
808 // read user commands of other clients from the snapshot
809 for ( last = NULL, i = msg.ReadByte(); i < MAX_ASYNC_CLIENTS; i = msg.ReadByte() ) {
810 numUsercmds = msg.ReadByte();
811 if ( numUsercmds > MAX_USERCMD_RELAY ) {
812 common->Error( "snapshot %d contains too many user commands for client %d", snapshotSequence, i );
815 for ( j = 0; j < numUsercmds; j++ ) {
816 index = ( snapshotGameFrame + j ) & ( MAX_USERCMD_BACKUP - 1 );
817 idAsyncNetwork::ReadUserCmdDelta( msg, userCmds[index][i], last );
818 userCmds[index][i].gameFrame = snapshotGameFrame + j;
819 userCmds[index][i].duplicateCount = 0;
820 last = &userCmds[index][i];
822 // clear all user commands after the ones just read from the snapshot
823 for ( j = numUsercmds; j < MAX_USERCMD_BACKUP; j++ ) {
824 index = ( snapshotGameFrame + j ) & ( MAX_USERCMD_BACKUP - 1 );
825 userCmds[index][i].gameFrame = 0;
826 userCmds[index][i].gameTime = 0;
830 // if this is the first snapshot after a game init was received
831 if ( clientState == CS_CONNECTED ) {
832 gameTimeResidual = 0;
833 clientState = CS_INGAME;
834 assert( !sessLocal.GetActiveMenu( ) );
835 if ( idAsyncNetwork::verbose.GetInteger() ) {
836 common->Printf( "received first snapshot, gameInitId = %d, gameFrame %d gameTime %d\n", gameInitId, snapshotGameFrame, snapshotGameTime );
840 // if the snapshot is newer than the clients current game time
841 if ( gameTime < snapshotGameTime || gameTime > snapshotGameTime + idAsyncNetwork::clientMaxPrediction.GetInteger() ) {
842 gameFrame = snapshotGameFrame;
843 gameTime = snapshotGameTime;
844 gameTimeResidual = idMath::ClampInt( -idAsyncNetwork::clientMaxPrediction.GetInteger(), idAsyncNetwork::clientMaxPrediction.GetInteger(), gameTimeResidual );
845 clientPredictTime = idMath::ClampInt( -idAsyncNetwork::clientMaxPrediction.GetInteger(), idAsyncNetwork::clientMaxPrediction.GetInteger(), clientPredictTime );
848 // adjust the client prediction time based on the snapshot time
849 clientPrediction -= ( 1 - ( INTSIGNBITSET( aheadOfServer - idAsyncNetwork::clientPrediction.GetInteger() ) << 1 ) );
850 clientPrediction = idMath::ClampInt( idAsyncNetwork::clientPrediction.GetInteger(), idAsyncNetwork::clientMaxPrediction.GetInteger(), clientPrediction );
851 delta = gameTime - ( snapshotGameTime + clientPrediction );
852 clientPredictTime -= ( delta / PREDICTION_FAST_ADJUST ) + ( 1 - ( INTSIGNBITSET( delta ) << 1 ) );
854 lastSnapshotTime = clientTime;
856 if ( idAsyncNetwork::verbose.GetInteger() == 2 ) {
857 common->Printf( "received snapshot, gameInitId = %d, gameFrame = %d, gameTime = %d\n", gameInitId, gameFrame, gameTime );
860 if ( numDuplicatedUsercmds && ( idAsyncNetwork::verbose.GetInteger() == 2 ) ) {
861 common->Printf( "server duplicated %d user commands before snapshot %d\n", numDuplicatedUsercmds, snapshotGameFrame );
866 common->Printf( "unknown unreliable server message %d\n", id );
874 idAsyncClient::ProcessReliableMessagePure
877 void idAsyncClient::ProcessReliableMessagePure( const idBitMsg &msg ) {
879 byte msgBuf[ MAX_MESSAGE_SIZE ];
880 int inChecksums[ MAX_PURE_PAKS ];
883 int serverGameInitId;
885 session->SetGUI( NULL, NULL );
887 serverGameInitId = msg.ReadLong();
889 if ( serverGameInitId != gameInitId ) {
890 common->DPrintf( "ignoring pure server checksum from an outdated gameInitId (%d)\n", serverGameInitId );
894 if ( !ValidatePureServerChecksums( serverAddress, msg ) ) {
899 if ( idAsyncNetwork::verbose.GetInteger() ) {
900 common->Printf( "received new pure server info. ExecuteMapChange and report back\n" );
903 // it is now ok to load the next map with updated pure checksums
904 sessLocal.ExecuteMapChange( true );
906 // upon receiving our pure list, the server will send us SCS_INGAME and we'll start getting snapshots
907 fileSystem->GetPureServerChecksums( inChecksums, -1, &gamePakChecksum );
908 outMsg.Init( msgBuf, sizeof( msgBuf ) );
909 outMsg.WriteByte( CLIENT_RELIABLE_MESSAGE_PURE );
911 outMsg.WriteLong( gameInitId );
914 while ( inChecksums[ i ] ) {
915 outMsg.WriteLong( inChecksums[ i++ ] );
917 outMsg.WriteLong( 0 );
918 outMsg.WriteLong( gamePakChecksum );
920 if ( !channel.SendReliableMessage( outMsg ) ) {
921 common->Error( "client->server reliable messages overflow\n" );
927 idAsyncClient::ReadLocalizedServerString
930 void idAsyncClient::ReadLocalizedServerString( const idBitMsg &msg, char *out, int maxLen ) {
931 msg.ReadString( out, maxLen );
932 // look up localized string. if the message is not an #str_ format, we'll just get it back unchanged
933 idStr::snPrintf( out, maxLen - 1, "%s", common->GetLanguageDict()->GetString( out ) );
938 idAsyncClient::ProcessReliableServerMessages
941 void idAsyncClient::ProcessReliableServerMessages( void ) {
943 byte msgBuf[MAX_MESSAGE_SIZE];
946 msg.Init( msgBuf, sizeof( msgBuf ) );
948 while ( channel.GetReliableMessage( msg ) ) {
951 case SERVER_RELIABLE_MESSAGE_CLIENTINFO: {
953 clientNum = msg.ReadByte();
955 idDict &info = sessLocal.mapSpawnData.userInfo[ clientNum ];
956 bool haveBase = ( msg.ReadBits( 1 ) != 0 );
958 #if ID_CLIENTINFO_TAGS
959 int checksum = info.Checksum();
960 int srv_checksum = msg.ReadLong();
961 if ( checksum != srv_checksum ) {
962 common->DPrintf( "SERVER_RELIABLE_MESSAGE_CLIENTINFO %d (haveBase: %s): != checksums srv: 0x%x local: 0x%x\n", clientNum, haveBase ? "true" : "false", checksum, srv_checksum );
965 common->DPrintf( "SERVER_RELIABLE_MESSAGE_CLIENTINFO %d (haveBase: %s): checksums ok 0x%x\n", clientNum, haveBase ? "true" : "false", checksum );
970 msg.ReadDeltaDict( info, &info );
972 msg.ReadDeltaDict( info, NULL );
975 // server forces us to a different userinfo
976 if ( clientNum == idAsyncClient::clientNum ) {
977 common->DPrintf( "local user info modified by server\n" );
978 cvarSystem->SetCVarsFromDict( info );
979 cvarSystem->ClearModifiedFlags( CVAR_USERINFO ); // don't emit back
981 game->SetUserInfo( clientNum, info, true, false );
984 case SERVER_RELIABLE_MESSAGE_SYNCEDCVARS: {
985 idDict &info = sessLocal.mapSpawnData.syncedCVars;
986 msg.ReadDeltaDict( info, &info );
987 cvarSystem->SetCVarsFromDict( info );
988 if ( !idAsyncNetwork::allowCheats.GetBool() ) {
989 cvarSystem->ResetFlaggedVariables( CVAR_CHEAT );
993 case SERVER_RELIABLE_MESSAGE_PRINT: {
994 char string[MAX_STRING_CHARS];
995 msg.ReadString( string, MAX_STRING_CHARS );
996 common->Printf( "%s\n", string );
999 case SERVER_RELIABLE_MESSAGE_DISCONNECT: {
1001 char string[MAX_STRING_CHARS];
1002 clientNum = msg.ReadLong( );
1003 ReadLocalizedServerString( msg, string, MAX_STRING_CHARS );
1004 if ( clientNum == idAsyncClient::clientNum ) {
1006 session->MessageBox( MSG_OK, string, common->GetLanguageDict()->GetString ( "#str_04319" ), true );
1007 session->StartMenu();
1009 common->Printf( "client %d %s\n", clientNum, string );
1010 cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "addChatLine \"%s^0 %s\"", sessLocal.mapSpawnData.userInfo[ clientNum ].GetString( "ui_name" ), string ) );
1011 sessLocal.mapSpawnData.userInfo[ clientNum ].Clear();
1015 case SERVER_RELIABLE_MESSAGE_APPLYSNAPSHOT: {
1017 sequence = msg.ReadLong();
1018 if ( !game->ClientApplySnapshot( clientNum, sequence ) ) {
1020 common->Error( "couldn't apply snapshot %d", sequence );
1024 case SERVER_RELIABLE_MESSAGE_PURE: {
1025 ProcessReliableMessagePure( msg );
1028 case SERVER_RELIABLE_MESSAGE_RELOAD: {
1029 if ( idAsyncNetwork::verbose.GetBool() ) {
1030 common->Printf( "got MESSAGE_RELOAD from server\n" );
1032 // simply reconnect, so that if the server restarts in pure mode we can get the right list and avoid spurious reloads
1033 cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "reconnect\n" );
1036 case SERVER_RELIABLE_MESSAGE_ENTERGAME: {
1037 SendUserInfoToServer();
1038 game->SetUserInfo( clientNum, sessLocal.mapSpawnData.userInfo[ clientNum ], true, false );
1039 cvarSystem->ClearModifiedFlags( CVAR_USERINFO );
1043 // pass reliable message on to game code
1044 game->ClientProcessReliableMessage( clientNum, msg );
1053 idAsyncClient::ProcessChallengeResponseMessage
1056 void idAsyncClient::ProcessChallengeResponseMessage( const netadr_t from, const idBitMsg &msg ) {
1057 char serverGame[ MAX_STRING_CHARS ], serverGameBase[ MAX_STRING_CHARS ];
1059 if ( clientState != CS_CHALLENGING ) {
1060 common->Printf( "Unwanted challenge response received.\n" );
1064 serverChallenge = msg.ReadLong();
1065 serverId = msg.ReadShort();
1066 msg.ReadString( serverGameBase, MAX_STRING_CHARS );
1067 msg.ReadString( serverGame, MAX_STRING_CHARS );
1069 // the server is running a different game... we need to reload in the correct fs_game
1070 // even pure pak checks would fail if we didn't, as there are files we may not even see atm
1071 // NOTE: we could read the pure list from the server at the same time and set it up for the restart
1072 // ( if the client can restart directly with the right pak order, then we avoid an extra reloadEngine later.. )
1073 if ( idStr::Icmp( cvarSystem->GetCVarString( "fs_game_base" ), serverGameBase ) ||
1074 idStr::Icmp( cvarSystem->GetCVarString( "fs_game" ), serverGame ) ) {
1075 // bug #189 - if the server is running ROE and ROE is not locally installed, refuse to connect or we might crash
1076 if ( !fileSystem->HasD3XP() && ( !idStr::Icmp( serverGameBase, "d3xp" ) || !idStr::Icmp( serverGame, "d3xp" ) ) ) {
1077 common->Printf( "The server is running Doom3: Resurrection of Evil expansion pack. RoE is not installed on this client. Aborting the connection..\n" );
1078 cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "disconnect\n" );
1081 common->Printf( "The server is running a different mod (%s-%s). Restarting..\n", serverGameBase, serverGame );
1082 cvarSystem->SetCVarString( "fs_game_base", serverGameBase );
1083 cvarSystem->SetCVarString( "fs_game", serverGame );
1084 cmdSystem->BufferCommandText( CMD_EXEC_NOW, "reloadEngine" );
1085 cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "reconnect\n" );
1089 common->Printf( "received challenge response 0x%x from %s\n", serverChallenge, Sys_NetAdrToString( from ) );
1091 // start sending connect packets instead of challenge request packets
1092 clientState = CS_CONNECTING;
1093 lastConnectTime = -9999;
1095 // take this address as the new server address. This allows
1096 // a server proxy to hand off connections to multiple servers
1097 serverAddress = from;
1102 idAsyncClient::ProcessConnectResponseMessage
1105 void idAsyncClient::ProcessConnectResponseMessage( const netadr_t from, const idBitMsg &msg ) {
1106 int serverGameInitId, serverGameFrame, serverGameTime;
1109 if ( clientState >= CS_CONNECTED ) {
1110 common->Printf( "Duplicate connect received.\n" );
1113 if ( clientState != CS_CONNECTING ) {
1114 common->Printf( "Connect response packet while not connecting.\n" );
1117 if ( !Sys_CompareNetAdrBase( from, serverAddress ) ) {
1118 common->Printf( "Connect response from a different server.\n" );
1119 common->Printf( "%s should have been %s\n", Sys_NetAdrToString( from ), Sys_NetAdrToString( serverAddress ) );
1123 common->Printf( "received connect response from %s\n", Sys_NetAdrToString( from ) );
1125 channel.Init( from, clientId );
1126 clientNum = msg.ReadLong();
1127 clientState = CS_CONNECTED;
1128 lastPacketTime = -9999;
1130 serverGameInitId = msg.ReadLong();
1131 serverGameFrame = msg.ReadLong();
1132 serverGameTime = msg.ReadLong();
1133 msg.ReadDeltaDict( serverSI, NULL );
1135 InitGame( serverGameInitId, serverGameFrame, serverGameTime, serverSI );
1138 session->SetGUI( NULL, NULL );
1139 sessLocal.ExecuteMapChange();
1141 clientPredictTime = clientPrediction = idMath::ClampInt( 0, idAsyncNetwork::clientMaxPrediction.GetInteger(), clientTime - lastConnectTime );
1146 idAsyncClient::ProcessDisconnectMessage
1149 void idAsyncClient::ProcessDisconnectMessage( const netadr_t from, const idBitMsg &msg ) {
1150 if ( clientState == CS_DISCONNECTED ) {
1151 common->Printf( "Disconnect packet while not connected.\n" );
1154 if ( !Sys_CompareNetAdrBase( from, serverAddress ) ) {
1155 common->Printf( "Disconnect packet from unknown server.\n" );
1159 session->MessageBox( MSG_OK, common->GetLanguageDict()->GetString ( "#str_04320" ), NULL, true );
1160 session->StartMenu();
1165 idAsyncClient::ProcessInfoResponseMessage
1168 void idAsyncClient::ProcessInfoResponseMessage( const netadr_t from, const idBitMsg &msg ) {
1169 int i, protocol, index;
1170 networkServer_t serverInfo;
1171 bool verbose = false;
1173 if ( from.type == NA_LOOPBACK || cvarSystem->GetCVarBool( "developer" ) ) {
1177 serverInfo.clients = 0;
1178 serverInfo.adr = from;
1179 serverInfo.challenge = msg.ReadLong(); // challenge
1180 protocol = msg.ReadLong();
1181 if ( protocol != ASYNC_PROTOCOL_VERSION ) {
1182 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 );
1185 msg.ReadDeltaDict( serverInfo.serverInfo, NULL );
1188 common->Printf( "server IP = %s\n", Sys_NetAdrToString( serverInfo.adr ) );
1189 serverInfo.serverInfo.Print();
1191 for ( i = msg.ReadByte(); i < MAX_ASYNC_CLIENTS; i = msg.ReadByte() ) {
1192 serverInfo.pings[ serverInfo.clients ] = msg.ReadShort();
1193 serverInfo.rate[ serverInfo.clients ] = msg.ReadLong();
1194 msg.ReadString( serverInfo.nickname[ serverInfo.clients ], MAX_NICKLEN );
1196 common->Printf( "client %2d: %s, ping = %d, rate = %d\n", i, serverInfo.nickname[ serverInfo.clients ], serverInfo.pings[ serverInfo.clients ], serverInfo.rate[ serverInfo.clients ] );
1198 serverInfo.clients++;
1200 serverInfo.OSMask = msg.ReadLong();
1201 index = serverList.InfoResponse( serverInfo );
1203 common->Printf( "%d: server %s - protocol %d.%d - %s\n", index, Sys_NetAdrToString( serverInfo.adr ), protocol >> 16, protocol & 0xffff, serverInfo.serverInfo.GetString( "si_name" ) );
1208 idAsyncClient::ProcessPrintMessage
1211 void idAsyncClient::ProcessPrintMessage( const netadr_t from, const idBitMsg &msg ) {
1212 char string[ MAX_STRING_CHARS ];
1214 int game_opcode = ALLOW_YES;
1215 const char *retpass;
1217 opcode = msg.ReadLong();
1218 if ( opcode == SERVER_PRINT_GAMEDENY ) {
1219 game_opcode = msg.ReadLong();
1221 ReadLocalizedServerString( msg, string, MAX_STRING_CHARS );
1222 common->Printf( "%s\n", string );
1223 guiNetMenu->SetStateString( "status", string );
1224 if ( opcode == SERVER_PRINT_GAMEDENY ) {
1225 if ( game_opcode == ALLOW_BADPASS ) {
1226 retpass = session->MessageBox( MSG_PROMPT, common->GetLanguageDict()->GetString ( "#str_04321" ), string, true, "passprompt_ok" );
1227 ClearPendingPackets();
1228 guiNetMenu->SetStateString( "status", common->GetLanguageDict()->GetString ( "#str_04322" ));
1231 cvarSystem->SetCVarString( "password", "" );
1232 cvarSystem->SetCVarString( "password", retpass );
1234 cmdSystem->BufferCommandText( CMD_EXEC_NOW, "disconnect" );
1236 } else if ( game_opcode == ALLOW_NO ) {
1237 session->MessageBox( MSG_OK, string, common->GetLanguageDict()->GetString ( "#str_04323" ), true );
1238 ClearPendingPackets();
1239 cmdSystem->BufferCommandText( CMD_EXEC_NOW, "disconnect" );
1241 // ALLOW_NOTYET just keeps running as usual. The GUI has an abort button
1242 } else if ( opcode == SERVER_PRINT_BADCHALLENGE && clientState >= CS_CONNECTING ) {
1243 cmdSystem->BufferCommandText( CMD_EXEC_NOW, "reconnect" );
1249 idAsyncClient::ProcessServersListMessage
1252 void idAsyncClient::ProcessServersListMessage( const netadr_t from, const idBitMsg &msg ) {
1253 if ( !Sys_CompareNetAdrBase( idAsyncNetwork::GetMasterAddress(), from ) ) {
1254 common->DPrintf( "received a server list from %s - not a valid master\n", Sys_NetAdrToString( from ) );
1257 while ( msg.GetRemaingData() ) {
1259 a = msg.ReadByte(); b = msg.ReadByte(); c = msg.ReadByte(); d = msg.ReadByte();
1260 serverList.AddServer( serverList.Num(), va( "%i.%i.%i.%i:%i", a, b, c, d, msg.ReadShort() ) );
1266 idAsyncClient::ProcessAuthKeyMessage
1269 void idAsyncClient::ProcessAuthKeyMessage( const netadr_t from, const idBitMsg &msg ) {
1270 authKeyMsg_t authMsg;
1271 char read_string[ MAX_STRING_CHARS ];
1273 authBadKeyStatus_t authBadStatus;
1278 if ( clientState != CS_CONNECTING && !session->WaitingForGameAuth() ) {
1279 common->Printf( "clientState != CS_CONNECTING, not waiting for game auth, authKey ignored\n" );
1283 authMsg = (authKeyMsg_t)msg.ReadByte();
1284 if ( authMsg == AUTHKEY_BADKEY ) {
1285 valid[ 0 ] = valid[ 1 ] = true;
1287 authBadStatus = (authBadKeyStatus_t)msg.ReadByte();
1288 switch ( authBadStatus ) {
1289 case AUTHKEY_BAD_INVALID:
1290 valid[ 0 ] = ( msg.ReadByte() == 1 );
1291 valid[ 1 ] = ( msg.ReadByte() == 1 );
1292 idAsyncNetwork::BuildInvalidKeyMsg( auth_msg, valid );
1294 case AUTHKEY_BAD_BANNED:
1295 key_index = msg.ReadByte();
1296 auth_msg = common->GetLanguageDict()->GetString( va( "#str_0719%1d", 6 + key_index ) );
1298 auth_msg += common->GetLanguageDict()->GetString( "#str_04304" );
1299 valid[ key_index ] = false;
1301 case AUTHKEY_BAD_INUSE:
1302 key_index = msg.ReadByte();
1303 auth_msg = common->GetLanguageDict()->GetString( va( "#str_0719%1d", 8 + key_index ) );
1305 auth_msg += common->GetLanguageDict()->GetString( "#str_04304" );
1306 valid[ key_index ] = false;
1308 case AUTHKEY_BAD_MSG:
1309 // a general message explaining why this key is denied
1310 // no specific use for this atm. let's not clear the keys either
1311 msg.ReadString( read_string, MAX_STRING_CHARS );
1312 auth_msg = read_string;
1315 common->DPrintf( "auth deny: %s\n", auth_msg.c_str() );
1317 // keys to be cleared. applies to both net connect and game auth
1318 session->ClearCDKey( valid );
1320 // get rid of the bad key - at least that's gonna annoy people who stole a fake key
1321 if ( clientState == CS_CONNECTING ) {
1323 // here we use the auth status message
1324 retkey = session->MessageBox( MSG_CDKEY, auth_msg, common->GetLanguageDict()->GetString( "#str_04325" ), true );
1326 if ( session->CheckKey( retkey, true, valid ) ) {
1327 cmdSystem->BufferCommandText( CMD_EXEC_NOW, "reconnect" );
1329 // build a more precise message about the offline check failure
1330 idAsyncNetwork::BuildInvalidKeyMsg( auth_msg, valid );
1331 session->MessageBox( MSG_OK, auth_msg.c_str(), common->GetLanguageDict()->GetString( "#str_04327" ), true );
1335 cmdSystem->BufferCommandText( CMD_EXEC_NOW, "disconnect" );
1340 // forward the auth status information to the session code
1341 session->CDKeysAuthReply( false, auth_msg );
1344 msg.ReadString( read_string, MAX_STRING_CHARS );
1345 cvarSystem->SetCVarString( "com_guid", read_string );
1346 common->Printf( "guid set to %s\n", read_string );
1347 session->CDKeysAuthReply( true, NULL );
1353 idAsyncClient::ProcessVersionMessage
1356 void idAsyncClient::ProcessVersionMessage( const netadr_t from, const idBitMsg &msg ) {
1357 char string[ MAX_STRING_CHARS ];
1359 if ( updateState != UPDATE_SENT ) {
1360 common->Printf( "ProcessVersionMessage: version reply, != UPDATE_SENT\n" );
1364 common->Printf( "A new version is available\n" );
1365 msg.ReadString( string, MAX_STRING_CHARS );
1367 updateDirectDownload = ( msg.ReadByte() != 0 );
1368 msg.ReadString( string, MAX_STRING_CHARS );
1370 updateMime = (dlMime_t)msg.ReadByte();
1371 msg.ReadString( string, MAX_STRING_CHARS );
1372 updateFallback = string;
1373 updateState = UPDATE_READY;
1378 idAsyncClient::ValidatePureServerChecksums
1381 bool idAsyncClient::ValidatePureServerChecksums( const netadr_t from, const idBitMsg &msg ) {
1382 int i, numChecksums, numMissingChecksums;
1383 int inChecksums[ MAX_PURE_PAKS ];
1384 int inGamePakChecksum;
1385 int missingChecksums[ MAX_PURE_PAKS ];
1386 int missingGamePakChecksum;
1388 byte msgBuf[MAX_MESSAGE_SIZE];
1391 // pak checksums, in a 0-terminated list
1394 i = msg.ReadLong( );
1395 inChecksums[ numChecksums++ ] = i;
1396 // just to make sure a broken message doesn't crash us
1397 if ( numChecksums >= MAX_PURE_PAKS ) {
1398 common->Warning( "MAX_PURE_PAKS ( %d ) exceeded in idAsyncClient::ProcessPureMessage\n", MAX_PURE_PAKS );
1402 inChecksums[ numChecksums ] = 0;
1403 inGamePakChecksum = msg.ReadLong();
1405 fsPureReply_t reply = fileSystem->SetPureServerChecksums( inChecksums, inGamePakChecksum, missingChecksums, &missingGamePakChecksum );
1408 // need to restart the filesystem with a different pure configuration
1409 cmdSystem->BufferCommandText( CMD_EXEC_NOW, "disconnect" );
1410 // restart with the right FS configuration and get back to the server
1411 clientState = CS_PURERESTART;
1412 fileSystem->SetRestartChecksums( inChecksums, inGamePakChecksum );
1413 cmdSystem->BufferCommandText( CMD_EXEC_NOW, "reloadEngine" );
1415 case PURE_MISSING: {
1420 while ( missingChecksums[ i ] ) {
1421 checksums += va( "0x%x ", missingChecksums[ i++ ] );
1423 numMissingChecksums = i;
1425 if ( idAsyncNetwork::clientDownload.GetInteger() == 0 ) {
1426 // never any downloads
1427 idStr message = va( common->GetLanguageDict()->GetString( "#str_07210" ), Sys_NetAdrToString( from ) );
1429 if ( numMissingChecksums > 0 ) {
1430 message += va( common->GetLanguageDict()->GetString( "#str_06751" ), numMissingChecksums, checksums.c_str() );
1432 if ( missingGamePakChecksum ) {
1433 message += va( common->GetLanguageDict()->GetString( "#str_06750" ), missingGamePakChecksum );
1436 common->Printf( message );
1437 cmdSystem->BufferCommandText( CMD_EXEC_NOW, "disconnect" );
1438 session->MessageBox( MSG_OK, message, common->GetLanguageDict()->GetString( "#str_06735" ), true );
1440 if ( clientState >= CS_CONNECTED ) {
1441 // we are already connected, reconnect to negociate the paks in connectionless mode
1442 cmdSystem->BufferCommandText( CMD_EXEC_NOW, "reconnect" );
1445 // ask the server to send back download info
1446 common->DPrintf( "missing %d paks: %s\n", numMissingChecksums + ( missingGamePakChecksum ? 1 : 0 ), checksums.c_str() );
1447 if ( missingGamePakChecksum ) {
1448 common->DPrintf( "game code pak: 0x%x\n", missingGamePakChecksum );
1450 // store the requested downloads
1451 GetDownloadRequest( missingChecksums, numMissingChecksums, missingGamePakChecksum );
1452 // build the download request message
1453 // NOTE: in a specific function?
1454 dlmsg.Init( msgBuf, sizeof( msgBuf ) );
1455 dlmsg.WriteShort( CONNECTIONLESS_MESSAGE_ID );
1456 dlmsg.WriteString( "downloadRequest" );
1457 dlmsg.WriteLong( serverChallenge );
1458 dlmsg.WriteShort( clientId );
1459 // used to make sure the server replies to the same download request
1460 dlmsg.WriteLong( dlRequest );
1461 // special case the code pak - if we have a 0 checksum then we don't need to download it
1462 dlmsg.WriteLong( missingGamePakChecksum );
1463 // 0-terminated list of missing paks
1465 while ( missingChecksums[ i ] ) {
1466 dlmsg.WriteLong( missingChecksums[ i++ ] );
1468 dlmsg.WriteLong( 0 );
1469 clientPort.SendPacket( from, dlmsg.GetData(), dlmsg.GetSize() );
1475 common->Printf( common->GetLanguageDict()->GetString( "#str_07211" ), Sys_NetAdrToString( from ) );
1476 cmdSystem->BufferCommandText( CMD_EXEC_NOW, "disconnect" );
1486 idAsyncClient::ProcessPureMessage
1489 void idAsyncClient::ProcessPureMessage( const netadr_t from, const idBitMsg &msg ) {
1491 byte msgBuf[ MAX_MESSAGE_SIZE ];
1493 int inChecksums[ MAX_PURE_PAKS ];
1494 int gamePakChecksum;
1496 if ( clientState != CS_CONNECTING ) {
1497 common->Printf( "clientState != CS_CONNECTING, pure msg ignored\n" );
1501 if ( !ValidatePureServerChecksums( from, msg ) ) {
1505 fileSystem->GetPureServerChecksums( inChecksums, -1, &gamePakChecksum );
1506 outMsg.Init( msgBuf, sizeof( msgBuf ) );
1507 outMsg.WriteShort( CONNECTIONLESS_MESSAGE_ID );
1508 outMsg.WriteString( "pureClient" );
1509 outMsg.WriteLong( serverChallenge );
1510 outMsg.WriteShort( clientId );
1512 while ( inChecksums[ i ] ) {
1513 outMsg.WriteLong( inChecksums[ i++ ] );
1515 outMsg.WriteLong( 0 );
1516 outMsg.WriteLong( gamePakChecksum );
1517 clientPort.SendPacket( from, outMsg.GetData(), outMsg.GetSize() );
1522 idAsyncClient::ConnectionlessMessage
1525 void idAsyncClient::ConnectionlessMessage( const netadr_t from, const idBitMsg &msg ) {
1526 char string[MAX_STRING_CHARS*2]; // M. Quinn - Even Balance - PB packets can go beyond 1024
1528 msg.ReadString( string, sizeof( string ) );
1530 // info response from a server, are accepted from any source
1531 if ( idStr::Icmp( string, "infoResponse" ) == 0 ) {
1532 ProcessInfoResponseMessage( from, msg );
1536 // from master server:
1537 if ( Sys_CompareNetAdrBase( from, idAsyncNetwork::GetMasterAddress( ) ) ) {
1539 if ( idStr::Icmp( string, "servers" ) == 0 ) {
1540 ProcessServersListMessage( from, msg );
1544 if ( idStr::Icmp( string, "authKey" ) == 0 ) {
1545 ProcessAuthKeyMessage( from, msg );
1549 if ( idStr::Icmp( string, "newVersion" ) == 0 ) {
1550 ProcessVersionMessage( from, msg );
1555 // ignore if not from the current/last server
1556 if ( !Sys_CompareNetAdrBase( from, serverAddress ) && ( lastRconTime + 10000 < realTime || !Sys_CompareNetAdrBase( from, lastRconAddress ) ) ) {
1557 common->DPrintf( "got message '%s' from bad source: %s\n", string, Sys_NetAdrToString( from ) );
1561 // challenge response from the server we are connecting to
1562 if ( idStr::Icmp( string, "challengeResponse" ) == 0 ) {
1563 ProcessChallengeResponseMessage( from, msg );
1567 // connect response from the server we are connecting to
1568 if ( idStr::Icmp( string, "connectResponse" ) == 0 ) {
1569 ProcessConnectResponseMessage( from, msg );
1573 // a disconnect message from the server, which will happen if the server
1574 // dropped the connection but is still getting packets from this client
1575 if ( idStr::Icmp( string, "disconnect" ) == 0 ) {
1576 ProcessDisconnectMessage( from, msg );
1580 // print request from server
1581 if ( idStr::Icmp( string, "print" ) == 0 ) {
1582 ProcessPrintMessage( from, msg );
1587 if ( idStr::Icmp( string, "pureServer" ) == 0 ) {
1588 ProcessPureMessage( from, msg );
1592 if ( idStr::Icmp( string, "downloadInfo" ) == 0 ) {
1593 ProcessDownloadInfoMessage( from, msg );
1596 if ( idStr::Icmp( string, "authrequired" ) == 0 ) {
1597 // server telling us that he's expecting an auth mode connect, just in case we're trying to connect in LAN mode
1598 if ( idAsyncNetwork::LANServer.GetBool() ) {
1599 common->Warning( "server %s requests master authorization for this client. Turning off LAN mode\n", Sys_NetAdrToString( from ) );
1600 idAsyncNetwork::LANServer.SetBool( false );
1604 common->DPrintf( "ignored message from %s: %s\n", Sys_NetAdrToString( from ), string );
1609 idAsyncClient::ProcessMessage
1612 void idAsyncClient::ProcessMessage( const netadr_t from, idBitMsg &msg ) {
1615 id = msg.ReadShort();
1617 // check for a connectionless packet
1618 if ( id == CONNECTIONLESS_MESSAGE_ID ) {
1619 ConnectionlessMessage( from, msg );
1623 if ( clientState < CS_CONNECTED ) {
1624 return; // can't be a valid sequenced packet
1627 if ( msg.GetRemaingData() < 4 ) {
1628 common->DPrintf( "%s: tiny packet\n", Sys_NetAdrToString( from ) );
1632 // is this a packet from the server
1633 if ( !Sys_CompareNetAdrBase( from, channel.GetRemoteAddress() ) || id != serverId ) {
1634 common->DPrintf( "%s: sequenced server packet without connection\n", Sys_NetAdrToString( from ) );
1638 if ( !channel.Process( from, clientTime, msg, serverMessageSequence ) ) {
1639 return; // out of order, duplicated, fragment, etc.
1642 lastPacketTime = clientTime;
1643 ProcessReliableServerMessages();
1644 ProcessUnreliableServerMessage( msg );
1649 idAsyncClient::SetupConnection
1652 void idAsyncClient::SetupConnection( void ) {
1654 byte msgBuf[MAX_MESSAGE_SIZE];
1656 if ( clientTime - lastConnectTime < SETUP_CONNECTION_RESEND_TIME ) {
1660 if ( clientState == CS_CHALLENGING ) {
1661 common->Printf( "sending challenge to %s\n", Sys_NetAdrToString( serverAddress ) );
1662 msg.Init( msgBuf, sizeof( msgBuf ) );
1663 msg.WriteShort( CONNECTIONLESS_MESSAGE_ID );
1664 msg.WriteString( "challenge" );
1665 msg.WriteLong( clientId );
1666 clientPort.SendPacket( serverAddress, msg.GetData(), msg.GetSize() );
1667 } else if ( clientState == CS_CONNECTING ) {
1668 common->Printf( "sending connect to %s with challenge 0x%x\n", Sys_NetAdrToString( serverAddress ), serverChallenge );
1669 msg.Init( msgBuf, sizeof( msgBuf ) );
1670 msg.WriteShort( CONNECTIONLESS_MESSAGE_ID );
1671 msg.WriteString( "connect" );
1672 msg.WriteLong( ASYNC_PROTOCOL_VERSION );
1674 // fake win32 OS - might need to adapt depending on the case
1675 msg.WriteShort( 0 );
1677 msg.WriteShort( BUILD_OS_ID );
1679 msg.WriteLong( clientDataChecksum );
1680 msg.WriteLong( serverChallenge );
1681 msg.WriteShort( clientId );
1682 msg.WriteLong( cvarSystem->GetCVarInteger( "net_clientMaxRate" ) );
1683 msg.WriteString( cvarSystem->GetCVarString( "com_guid" ) );
1684 msg.WriteString( cvarSystem->GetCVarString( "password" ), -1, false );
1685 // do not make the protocol depend on PB
1686 msg.WriteShort( 0 );
1687 clientPort.SendPacket( serverAddress, msg.GetData(), msg.GetSize() );
1689 if ( idAsyncNetwork::LANServer.GetBool() ) {
1690 common->Printf( "net_LANServer is set, connecting in LAN mode\n" );
1692 // emit a cd key authorization request
1693 // modified at protocol 1.37 for XP key addition
1695 msg.WriteShort( CONNECTIONLESS_MESSAGE_ID );
1696 msg.WriteString( "clAuth" );
1697 msg.WriteLong( ASYNC_PROTOCOL_VERSION );
1698 msg.WriteNetadr( serverAddress );
1699 // if we don't have a com_guid, this will request a direct reply from auth with it
1700 msg.WriteByte( cvarSystem->GetCVarString( "com_guid" )[0] ? 1 : 0 );
1701 // send the main key, and flag an extra byte to add XP key
1702 msg.WriteString( session->GetCDKey( false ) );
1703 const char *xpkey = session->GetCDKey( true );
1704 msg.WriteByte( xpkey ? 1 : 0 );
1706 msg.WriteString( xpkey );
1708 clientPort.SendPacket( idAsyncNetwork::GetMasterAddress(), msg.GetData(), msg.GetSize() );
1714 lastConnectTime = clientTime;
1719 idAsyncClient::SendReliableGameMessage
1722 void idAsyncClient::SendReliableGameMessage( const idBitMsg &msg ) {
1724 byte msgBuf[MAX_MESSAGE_SIZE];
1726 if ( clientState < CS_INGAME ) {
1730 outMsg.Init( msgBuf, sizeof( msgBuf ) );
1731 outMsg.WriteByte( CLIENT_RELIABLE_MESSAGE_GAME );
1732 outMsg.WriteData( msg.GetData(), msg.GetSize() );
1733 if ( !channel.SendReliableMessage( outMsg ) ) {
1734 common->Error( "client->server reliable messages overflow\n" );
1743 void idAsyncClient::Idle( void ) {
1744 // also need to read mouse for the connecting guis
1745 usercmdGen->GetDirectUsercmd();
1747 SendEmptyToServer();
1752 idAsyncClient::UpdateTime
1755 int idAsyncClient::UpdateTime( int clamp ) {
1758 time = Sys_Milliseconds();
1759 msec = idMath::ClampInt( 0, clamp, time - realTime );
1767 idAsyncClient::RunFrame
1770 void idAsyncClient::RunFrame( void ) {
1774 byte msgBuf[MAX_MESSAGE_SIZE];
1777 msec = UpdateTime( 100 );
1779 if ( !clientPort.GetPort() ) {
1783 // handle ongoing pk4 downloads and patch downloads
1786 gameTimeResidual += msec;
1788 // spin in place processing incoming packets until enough time lapsed to run a new game frame
1793 // blocking read with game time residual timeout
1794 newPacket = clientPort.GetPacketBlocking( from, msgBuf, size, sizeof( msgBuf ), USERCMD_MSEC - ( gameTimeResidual + clientPredictTime ) - 1 );
1796 msg.Init( msgBuf, sizeof( msgBuf ) );
1797 msg.SetSize( size );
1799 ProcessMessage( from, msg );
1802 msec = UpdateTime( 100 );
1803 gameTimeResidual += msec;
1805 } while( newPacket );
1807 } while( gameTimeResidual + clientPredictTime < USERCMD_MSEC );
1809 // update server list
1810 serverList.RunFrame();
1812 if ( clientState == CS_DISCONNECTED ) {
1813 usercmdGen->GetDirectUsercmd();
1814 gameTimeResidual = USERCMD_MSEC - 1;
1815 clientPredictTime = 0;
1819 if ( clientState == CS_PURERESTART ) {
1820 clientState = CS_DISCONNECTED;
1822 gameTimeResidual = USERCMD_MSEC - 1;
1823 clientPredictTime = 0;
1827 // if not connected setup a connection
1828 if ( clientState < CS_CONNECTED ) {
1829 // also need to read mouse for the connecting guis
1830 usercmdGen->GetDirectUsercmd();
1832 gameTimeResidual = USERCMD_MSEC - 1;
1833 clientPredictTime = 0;
1837 if ( CheckTimeout() ) {
1841 // if not yet in the game send empty messages to keep data flowing through the channel
1842 if ( clientState < CS_INGAME ) {
1844 gameTimeResidual = 0;
1848 // check for user info changes
1849 if ( cvarSystem->GetModifiedFlags() & CVAR_USERINFO ) {
1850 game->ThrottleUserInfo( );
1851 SendUserInfoToServer( );
1852 game->SetUserInfo( clientNum, sessLocal.mapSpawnData.userInfo[ clientNum ], true, false );
1853 cvarSystem->ClearModifiedFlags( CVAR_USERINFO );
1856 if ( gameTimeResidual + clientPredictTime >= USERCMD_MSEC ) {
1860 // generate user commands for the predicted time
1861 while ( gameTimeResidual + clientPredictTime >= USERCMD_MSEC ) {
1863 // send the user commands of this client to the server
1864 SendUsercmdsToServer();
1868 gameTime += USERCMD_MSEC;
1869 gameTimeResidual -= USERCMD_MSEC;
1871 // run from the snapshot up to the local game frame
1872 while ( snapshotGameFrame < gameFrame ) {
1876 // duplicate usercmds for clients if no new ones are available
1877 DuplicateUsercmds( snapshotGameFrame, snapshotGameTime );
1879 // indicate the last prediction frame before a render
1880 bool lastPredictFrame = ( snapshotGameFrame + 1 >= gameFrame && gameTimeResidual + clientPredictTime < USERCMD_MSEC );
1882 // run client prediction
1883 gameReturn_t ret = game->ClientPrediction( clientNum, userCmds[ snapshotGameFrame & ( MAX_USERCMD_BACKUP - 1 ) ], lastPredictFrame );
1885 idAsyncNetwork::ExecuteSessionCommand( ret.sessionCommand );
1887 snapshotGameFrame++;
1888 snapshotGameTime += USERCMD_MSEC;
1895 idAsyncClient::PacifierUpdate
1898 void idAsyncClient::PacifierUpdate( void ) {
1899 if ( !IsActive() ) {
1902 realTime = Sys_Milliseconds();
1903 SendEmptyToServer( false, true );
1908 idAsyncClient::SendVersionCheck
1911 void idAsyncClient::SendVersionCheck( bool fromMenu ) {
1913 byte msgBuf[MAX_MESSAGE_SIZE];
1915 if ( updateState != UPDATE_NONE && !fromMenu ) {
1916 common->DPrintf( "up-to-date check was already performed\n" );
1921 msg.Init( msgBuf, sizeof( msgBuf ) );
1922 msg.WriteShort( CONNECTIONLESS_MESSAGE_ID );
1923 msg.WriteString( "versionCheck" );
1924 msg.WriteLong( ASYNC_PROTOCOL_VERSION );
1925 msg.WriteShort( BUILD_OS_ID );
1926 msg.WriteString( cvarSystem->GetCVarString( "si_version" ) );
1927 msg.WriteString( cvarSystem->GetCVarString( "com_guid" ) );
1928 clientPort.SendPacket( idAsyncNetwork::GetMasterAddress(), msg.GetData(), msg.GetSize() );
1930 common->DPrintf( "sent a version check request\n" );
1932 updateState = UPDATE_SENT;
1933 updateSentTime = clientTime;
1934 showUpdateMessage = fromMenu;
1939 idAsyncClient::SendVersionDLUpdate
1941 sending those packets is not strictly necessary. just a way to tell the update server
1942 about what is going on. allows the update server to have a more precise view of the overall
1943 network load for the updates
1946 void idAsyncClient::SendVersionDLUpdate( int state ) {
1948 byte msgBuf[MAX_MESSAGE_SIZE];
1950 msg.Init( msgBuf, sizeof( msgBuf ) );
1951 msg.WriteShort( CONNECTIONLESS_MESSAGE_ID );
1952 msg.WriteString( "versionDL" );
1953 msg.WriteLong( ASYNC_PROTOCOL_VERSION );
1954 msg.WriteShort( state );
1955 clientPort.SendPacket( idAsyncNetwork::GetMasterAddress(), msg.GetData(), msg.GetSize() );
1960 idAsyncClient::HandleDownloads
1963 void idAsyncClient::HandleDownloads( void ) {
1965 if ( updateState == UPDATE_SENT && clientTime > updateSentTime + 2000 ) {
1966 // timing out on no reply
1967 updateState = UPDATE_DONE;
1968 if ( showUpdateMessage ) {
1969 session->MessageBox( MSG_OK, common->GetLanguageDict()->GetString ( "#str_04839" ), common->GetLanguageDict()->GetString ( "#str_04837" ), true );
1970 showUpdateMessage = false;
1972 common->DPrintf( "No update available\n" );
1973 } else if ( backgroundDownload.completed ) {
1974 // only enter these if the download slot is free
1975 if ( updateState == UPDATE_READY ) {
1977 if ( session->MessageBox( MSG_YESNO, updateMSG, common->GetLanguageDict()->GetString ( "#str_04330" ), true, "yes" )[0] ) {
1978 if ( !updateDirectDownload ) {
1979 sys->OpenURL( updateURL, true );
1980 updateState = UPDATE_DONE;
1983 // we're just creating the file at toplevel inside fs_savepath
1984 updateURL.ExtractFileName( updateFile );
1985 idFile_Permanent *f = static_cast< idFile_Permanent *>( fileSystem->OpenFileWrite( updateFile ) );
1989 backgroundDownload.completed = false;
1990 backgroundDownload.opcode = DLTYPE_URL;
1991 backgroundDownload.f = f;
1992 backgroundDownload.url.status = DL_WAIT;
1993 backgroundDownload.url.dlnow = 0;
1994 backgroundDownload.url.dltotal = 0;
1995 backgroundDownload.url.url = updateURL;
1996 fileSystem->BackgroundDownload( &backgroundDownload );
1998 updateState = UPDATE_DLING;
1999 SendVersionDLUpdate( 0 );
2000 session->DownloadProgressBox( &backgroundDownload, va( "Downloading %s\n", updateFile.c_str() ) );
2001 updateState = UPDATE_DONE;
2002 if ( backgroundDownload.url.status == DL_DONE ) {
2003 SendVersionDLUpdate( 1 );
2004 idStr fullPath = f->GetFullPath();
2005 fileSystem->CloseFile( f );
2006 if ( session->MessageBox( MSG_YESNO, common->GetLanguageDict()->GetString ( "#str_04331" ), common->GetLanguageDict()->GetString ( "#str_04332" ), true, "yes" )[0] ) {
2007 if ( updateMime == FILE_EXEC ) {
2008 sys->StartProcess( fullPath, true );
2010 sys->OpenURL( va( "file://%s", fullPath.c_str() ), true );
2013 session->MessageBox( MSG_OK, va( common->GetLanguageDict()->GetString ( "#str_04333" ), fullPath.c_str() ), common->GetLanguageDict()->GetString ( "#str_04334" ), true );
2016 if ( backgroundDownload.url.dlerror[ 0 ] ) {
2017 common->Warning( "update download failed. curl error: %s", backgroundDownload.url.dlerror );
2019 SendVersionDLUpdate( 2 );
2020 idStr name = f->GetName();
2021 fileSystem->CloseFile( f );
2022 fileSystem->RemoveFile( name );
2023 session->MessageBox( MSG_OK, common->GetLanguageDict()->GetString ( "#str_04335" ), common->GetLanguageDict()->GetString ( "#str_04336" ), true );
2024 if ( updateFallback.Length() ) {
2025 sys->OpenURL( updateFallback.c_str(), true );
2027 common->Printf( "no fallback URL\n" );
2032 updateState = UPDATE_DONE;
2034 } else if ( dlList.Num() ) {
2036 int numPaks = dlList.Num();
2038 int progress_start, progress_end;
2043 if ( dlList[ 0 ].url[ 0 ] == '\0' ) {
2044 // ignore empty files
2045 dlList.RemoveIndex( 0 );
2048 common->Printf( "start download for %s\n", dlList[ 0 ].url.c_str() );
2050 idFile_Permanent *f = static_cast< idFile_Permanent *>( fileSystem->MakeTemporaryFile( ) );
2052 common->Warning( "could not create temporary file" );
2057 backgroundDownload.completed = false;
2058 backgroundDownload.opcode = DLTYPE_URL;
2059 backgroundDownload.f = f;
2060 backgroundDownload.url.status = DL_WAIT;
2061 backgroundDownload.url.dlnow = 0;
2062 backgroundDownload.url.dltotal = dlList[ 0 ].size;
2063 backgroundDownload.url.url = dlList[ 0 ].url;
2064 fileSystem->BackgroundDownload( &backgroundDownload );
2067 sprintf( dltitle, common->GetLanguageDict()->GetString( "#str_07213" ), dlList[ 0 ].filename.c_str() );
2068 if ( numPaks > 1 ) {
2069 dltitle += va( " (%d/%d)", pakCount, numPaks );
2071 if ( totalDlSize ) {
2072 progress_start = (int)( (float)currentDlSize * 100.0f / (float)totalDlSize );
2073 progress_end = (int)( (float)( currentDlSize + dlList[ 0 ].size ) * 100.0f / (float)totalDlSize );
2078 session->DownloadProgressBox( &backgroundDownload, dltitle, progress_start, progress_end );
2079 if ( backgroundDownload.url.status == DL_DONE ) {
2081 const int CHUNK_SIZE = 1024 * 1024;
2088 common->Printf( "file downloaded\n" );
2089 idStr finalPath = cvarSystem->GetCVarString( "fs_savepath" );
2090 finalPath.AppendPath( dlList[ 0 ].filename );
2091 fileSystem->CreateOSPath( finalPath );
2092 // do the final copy ourselves so we do by small chunks in case the file is big
2093 saveas = fileSystem->OpenExplicitFileWrite( finalPath );
2094 buf = (byte*)Mem_Alloc( CHUNK_SIZE );
2095 f->Seek( 0, FS_SEEK_END );
2096 remainlen = f->Tell();
2097 f->Seek( 0, FS_SEEK_SET );
2098 while ( remainlen ) {
2099 readlen = Min( remainlen, CHUNK_SIZE );
2100 retlen = f->Read( buf, readlen );
2101 if ( retlen != readlen ) {
2102 common->FatalError( "short read %d of %d in idFileSystem::HandleDownload", retlen, readlen );
2104 retlen = saveas->Write( buf, readlen );
2105 if ( retlen != readlen ) {
2106 common->FatalError( "short write %d of %d in idFileSystem::HandleDownload", retlen, readlen );
2108 remainlen -= readlen;
2110 fileSystem->CloseFile( f );
2111 fileSystem->CloseFile( saveas );
2112 common->Printf( "saved as %s\n", finalPath.c_str() );
2115 // add that file to our paks list
2116 checksum = fileSystem->AddZipFile( dlList[ 0 ].filename );
2118 // verify the checksum to be what the server says
2119 if ( !checksum || checksum != dlList[ 0 ].checksum ) {
2120 // "pak is corrupted ( checksum 0x%x, expected 0x%x )"
2121 session->MessageBox( MSG_OK, va( common->GetLanguageDict()->GetString( "#str_07214" ) , checksum, dlList[0].checksum ), "Download failed", true );
2122 fileSystem->RemoveFile( dlList[ 0 ].filename );
2127 currentDlSize += dlList[ 0 ].size;
2130 common->Warning( "download failed: %s", dlList[ 0 ].url.c_str() );
2131 if ( backgroundDownload.url.dlerror[ 0 ] ) {
2132 common->Warning( "curl error: %s", backgroundDownload.url.dlerror );
2134 // "The download failed or was cancelled"
2135 // "Download failed"
2136 session->MessageBox( MSG_OK, common->GetLanguageDict()->GetString( "#str_07215" ), common->GetLanguageDict()->GetString( "#str_07216" ), true );
2142 dlList.RemoveIndex( 0 );
2143 } while ( dlList.Num() );
2145 // all downloads successful - do the dew
2146 cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "reconnect\n" );
2153 idAsyncClient::SendAuthCheck
2156 bool idAsyncClient::SendAuthCheck( const char *cdkey, const char *xpkey ) {
2158 byte msgBuf[MAX_MESSAGE_SIZE];
2160 msg.Init( msgBuf, sizeof( msgBuf ) );
2161 msg.WriteShort( CONNECTIONLESS_MESSAGE_ID );
2162 msg.WriteString( "gameAuth" );
2163 msg.WriteLong( ASYNC_PROTOCOL_VERSION );
2164 msg.WriteByte( cdkey ? 1 : 0 );
2165 msg.WriteString( cdkey ? cdkey : "" );
2166 msg.WriteByte( xpkey ? 1 : 0 );
2167 msg.WriteString( xpkey ? xpkey : "" );
2169 clientPort.SendPacket( idAsyncNetwork::GetMasterAddress(), msg.GetData(), msg.GetSize() );
2175 idAsyncClient::CheckTimeout
2178 bool idAsyncClient::CheckTimeout( void ) {
2179 if ( lastPacketTime > 0 && ( lastPacketTime + idAsyncNetwork::clientServerTimeout.GetInteger()*1000 < clientTime ) ) {
2181 session->MessageBox( MSG_OK, common->GetLanguageDict()->GetString ( "#str_04328" ), common->GetLanguageDict()->GetString ( "#str_04329" ), true );
2182 cmdSystem->BufferCommandText( CMD_EXEC_NOW, "disconnect" );
2190 idAsyncClient::ProcessDownloadInfoMessage
2193 void idAsyncClient::ProcessDownloadInfoMessage( const netadr_t from, const idBitMsg &msg ) {
2194 char buf[ MAX_STRING_CHARS ];
2195 int srvDlRequest = msg.ReadLong();
2196 int infoType = msg.ReadByte();
2201 bool gotAllFiles = true;
2203 bool gotGame = false;
2205 if ( dlRequest == -1 || srvDlRequest != dlRequest ) {
2206 common->Warning( "bad download id from server, ignored" );
2209 // mark the dlRequest as dead now whatever how we process it
2212 if ( infoType == SERVER_DL_REDIRECT ) {
2213 msg.ReadString( buf, MAX_STRING_CHARS );
2214 cmdSystem->BufferCommandText( CMD_EXEC_NOW, "disconnect" );
2215 // "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?"
2216 // "Missing required files"
2217 if ( session->MessageBox( MSG_YESNO, va( common->GetLanguageDict()->GetString( "#str_07217" ), buf ),
2218 common->GetLanguageDict()->GetString( "#str_07218" ), true, "yes" )[ 0 ] ) {
2219 sys->OpenURL( buf, true );
2221 } else if ( infoType == SERVER_DL_LIST ) {
2222 cmdSystem->BufferCommandText( CMD_EXEC_NOW, "disconnect" );
2223 if ( dlList.Num() ) {
2224 common->Warning( "tried to process a download list while already busy downloading things" );
2227 // read the URLs, check against what we requested, prompt for download
2232 pakDl = msg.ReadByte();
2233 if ( pakDl == SERVER_PAK_YES ) {
2234 if ( pakIndex == 0 ) {
2237 msg.ReadString( buf, MAX_STRING_CHARS );
2238 entry.filename = buf;
2239 msg.ReadString( buf, MAX_STRING_CHARS );
2241 entry.size = msg.ReadLong();
2242 // checksums are not transmitted, we read them from the dl request we sent
2243 entry.checksum = dlChecksums[ pakIndex ];
2244 totalDlSize += entry.size;
2245 dlList.Append( entry );
2246 common->Printf( "download %s from %s ( 0x%x )\n", entry.filename.c_str(), entry.url.c_str(), entry.checksum );
2247 } else if ( pakDl == SERVER_PAK_NO ) {
2248 msg.ReadString( buf, MAX_STRING_CHARS );
2249 entry.filename = buf;
2253 dlList.Append( entry );
2254 // first pak is game pak, only fail it if we actually requested it
2255 if ( pakIndex != 0 || dlChecksums[ 0 ] != 0 ) {
2256 common->Printf( "no download offered for %s ( 0x%x )\n", entry.filename.c_str(), dlChecksums[ pakIndex ] );
2257 gotAllFiles = false;
2260 assert( pakDl == SERVER_PAK_END );
2262 } while ( pakDl != SERVER_PAK_END );
2263 if ( dlList.Num() < dlCount ) {
2264 common->Printf( "%d files were ignored by the server\n", dlCount - dlList.Num() );
2265 gotAllFiles = false;
2267 sizeStr.BestUnit( "%.2f", totalDlSize, MEASURE_SIZE );
2268 cmdSystem->BufferCommandText( CMD_EXEC_NOW, "disconnect" );
2269 if ( totalDlSize == 0 ) {
2270 // was no downloadable stuff for us
2271 // "Can't connect to the pure server: no downloads offered"
2272 // "Missing required files"
2274 session->MessageBox( MSG_OK, common->GetLanguageDict()->GetString( "#str_07219" ), common->GetLanguageDict()->GetString( "#str_07218" ), true );
2280 // "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."
2281 // "Missing game binaries"
2282 if ( !session->MessageBox( MSG_YESNO, common->GetLanguageDict()->GetString( "#str_07220" ), common->GetLanguageDict()->GetString( "#str_07221" ), true, "yes" )[ 0 ] ) {
2287 if ( !gotAllFiles ) {
2289 // "The server only offers to download some of the files required to connect ( %s ). Download anyway?"
2290 // "Missing required files"
2291 if ( !session->MessageBox( MSG_YESNO, va( common->GetLanguageDict()->GetString( "#str_07222" ), sizeStr.c_str() ),
2292 common->GetLanguageDict()->GetString( "#str_07218" ), true, "yes" )[ 0 ] ) {
2297 if ( !asked && idAsyncNetwork::clientDownload.GetInteger() == 1 ) {
2298 // "You need to download some files to connect to this server ( %s ), proceed?"
2299 // "Missing required files"
2300 if ( !session->MessageBox( MSG_YESNO, va( common->GetLanguageDict()->GetString( "#str_07224" ), sizeStr.c_str() ),
2301 common->GetLanguageDict()->GetString( "#str_07218" ), true, "yes" )[ 0 ] ) {
2307 cmdSystem->BufferCommandText( CMD_EXEC_NOW, "disconnect" );
2308 // "You are missing some files to connect to this server, and the server doesn't provide downloads."
2309 // "Missing required files"
2310 session->MessageBox( MSG_OK, common->GetLanguageDict()->GetString( "#str_07223" ), common->GetLanguageDict()->GetString( "#str_07218" ), true );
2316 idAsyncClient::GetDownloadRequest
2319 int idAsyncClient::GetDownloadRequest( const int checksums[ MAX_PURE_PAKS ], int count, int gamePakChecksum ) {
2320 assert( !checksums[ count ] ); // 0-terminated
2321 if ( memcmp( dlChecksums + 1, checksums, sizeof( int ) * count ) || gamePakChecksum != dlChecksums[ 0 ] ) {
2324 dlChecksums[ 0 ] = gamePakChecksum;
2325 memcpy( dlChecksums + 1, checksums, sizeof( int ) * MAX_PURE_PAKS );
2327 newreq.SetSeed( Sys_Milliseconds() );
2328 dlRequest = newreq.RandomInt();
2329 dlCount = count + ( gamePakChecksum ? 1 : 0 );
2332 // this is the same dlRequest, we haven't heard from the server. keep the same id