]> icculus.org git repositories - icculus/iodoom3.git/blob - neo/framework/async/AsyncClient.cpp
hello world
[icculus/iodoom3.git] / neo / framework / async / AsyncClient.cpp
1 /*
2 ===========================================================================
3
4 Doom 3 GPL Source Code
5 Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company. 
6
7 This file is part of the Doom 3 GPL Source Code (?Doom 3 Source Code?).  
8
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.
13
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.
18
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/>.
21
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.
23
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.
25
26 ===========================================================================
27 */
28
29 #include "../../idlib/precompiled.h"
30 #pragma hdrstop
31
32 #include "AsyncNetwork.h"
33
34 #include "../Session_local.h"
35
36 const int SETUP_CONNECTION_RESEND_TIME  = 1000;
37 const int EMPTY_RESEND_TIME                             = 500;
38 const int PREDICTION_FAST_ADJUST                = 4;
39
40
41 /*
42 ==================
43 idAsyncClient::idAsyncClient
44 ==================
45 */
46 idAsyncClient::idAsyncClient( void ) {
47         guiNetMenu = NULL;
48         updateState = UPDATE_NONE;
49         Clear();
50 }
51
52 /*
53 ==================
54 idAsyncClient::Clear
55 ==================
56 */
57 void idAsyncClient::Clear( void ) {
58         active = false;
59         realTime = 0;
60         clientTime = 0;
61         clientId = 0;
62         clientDataChecksum = 0;
63         clientNum = 0;
64         clientState = CS_DISCONNECTED;
65         clientPrediction = 0;
66         clientPredictTime = 0;
67         serverId = 0;
68         serverChallenge = 0;
69         serverMessageSequence = 0;
70         lastConnectTime = -9999;
71         lastEmptyTime = -9999;
72         lastPacketTime = -9999;
73         lastSnapshotTime = -9999;
74         snapshotGameFrame = 0;
75         snapshotGameTime = 0;
76         snapshotSequence = 0;
77         gameInitId = GAME_INIT_ID_INVALID;
78         gameFrame = 0;
79         gameTimeResidual = 0;
80         gameTime = 0;
81         memset( userCmds, 0, sizeof( userCmds ) );
82         backgroundDownload.completed = true;
83         lastRconTime = 0;
84         showUpdateMessage = false;
85         lastFrameDelta = 0;
86
87         dlRequest = -1;
88         dlCount = -1;
89         memset( dlChecksums, 0, sizeof( int ) * MAX_PURE_PAKS );
90         currentDlSize = 0;
91         totalDlSize = 0;
92 }
93
94 /*
95 ==================
96 idAsyncClient::Shutdown
97 ==================
98 */
99 void idAsyncClient::Shutdown( void ) {
100         guiNetMenu = NULL;
101         updateMSG.Clear();
102         updateURL.Clear();
103         updateFile.Clear();
104         updateFallback.Clear();
105         backgroundDownload.url.url.Clear();
106         dlList.Clear();
107 }
108
109 /*
110 ==================
111 idAsyncClient::InitPort
112 ==================
113 */
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" );
119                         return false;
120                 }
121         }
122         // maintain it valid between connects and ui manager reloads
123         guiNetMenu = uiManager->FindGui( "guis/netmenu.gui", true, false, true );
124
125         return true;
126 }
127
128 /*
129 ==================
130 idAsyncClient::ClosePort
131 ==================
132 */
133 void idAsyncClient::ClosePort( void ) {
134         clientPort.Close();
135 }
136
137 /*
138 ==================
139 idAsyncClient::ClearPendingPackets
140 ==================
141 */
142 void idAsyncClient::ClearPendingPackets( void ) {
143         int                     size;
144         byte            msgBuf[MAX_MESSAGE_SIZE];
145         netadr_t        from;
146
147         while( clientPort.GetPacket( from, msgBuf, size, sizeof( msgBuf ) ) ) {
148         }
149 }
150
151 /*
152 ==================
153 idAsyncClient::HandleGuiCommandInternal
154 ==================
155 */
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" );
160                 return "";
161         } else {
162                 common->DWarning( "idAsyncClient::HandleGuiCommand: unknown cmd %s", cmd );
163         }
164         return NULL;
165 }
166
167 /*
168 ==================
169 idAsyncClient::HandleGuiCommand
170 ==================
171 */
172 const char* idAsyncClient::HandleGuiCommand( const char *cmd ) {
173         return idAsyncNetwork::client.HandleGuiCommandInternal( cmd );
174 }
175
176 /*
177 ==================
178 idAsyncClient::ConnectToServer
179 ==================
180 */
181 void idAsyncClient::ConnectToServer( const netadr_t adr ) {
182         // shutdown any current game. that includes network disconnect
183         session->Stop();
184
185         if ( !InitPort() ) {
186                 return;
187         }
188
189         if ( cvarSystem->GetCVarBool( "net_serverDedicated" ) ) {
190                 common->Printf( "Can't connect to a server as dedicated\n" );
191                 return;
192         }
193
194         // trash any currently pending packets
195         ClearPendingPackets();
196         
197         serverAddress = adr;
198
199         // clear the client state
200         Clear();
201
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;
204
205         // calculate a checksum on some of the essential data used
206         clientDataChecksum = declManager->GetChecksum();
207
208         // start challenging the server
209         clientState = CS_CHALLENGING;
210
211         active = true;
212
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 );
216 }
217
218 /*
219 ==================
220 idAsyncClient::Reconnect
221 ==================
222 */
223 void idAsyncClient::Reconnect( void ) {
224         ConnectToServer( serverAddress );
225 }
226
227 /*
228 ==================
229 idAsyncClient::ConnectToServer
230 ==================
231 */
232 void idAsyncClient::ConnectToServer( const char *address ) {
233         int serverNum;
234         netadr_t adr;
235
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 );
240                         return;
241                 }
242                 adr = serverList[ serverNum ].adr;
243         } else {
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 );
246                         return;
247                 }
248         }
249         if ( !adr.port ) {
250                 adr.port = PORT_SERVER;
251         }
252
253         common->Printf( "\"%s\" resolved to %s\n", address, Sys_NetAdrToString( adr ) );
254
255         ConnectToServer( adr );
256 }
257
258 /*
259 ==================
260 idAsyncClient::DisconnectFromServer
261 ==================
262 */
263 void idAsyncClient::DisconnectFromServer( void ) {
264         idBitMsg        msg;
265         byte            msgBuf[MAX_MESSAGE_SIZE];
266
267         if ( clientState >= CS_CONNECTED ) {
268                 // if we were actually connected, clear the pure list
269                 fileSystem->ClearPureChecksums();
270
271                 // send reliable disconnect to server
272                 msg.Init( msgBuf, sizeof( msgBuf ) );
273                 msg.WriteByte( CLIENT_RELIABLE_MESSAGE_DISCONNECT );
274                 msg.WriteString( "disconnect" );
275
276                 if ( !channel.SendReliableMessage( msg ) ) {
277                         common->Error( "client->server reliable messages overflow\n" );
278                 }
279
280                 SendEmptyToServer( true );
281                 SendEmptyToServer( true );
282                 SendEmptyToServer( true );
283         }
284
285         if ( clientState != CS_PURERESTART ) {
286                 channel.Shutdown();
287                 clientState = CS_DISCONNECTED;
288         }
289
290         active = false;
291 }
292
293 /*
294 ==================
295 idAsyncClient::GetServerInfo
296 ==================
297 */
298 void idAsyncClient::GetServerInfo( const netadr_t adr ) {
299         idBitMsg        msg;
300         byte            msgBuf[MAX_MESSAGE_SIZE];
301         
302         if ( !InitPort() ) {
303                 return;
304         }
305
306         msg.Init( msgBuf, sizeof( msgBuf ) );
307         msg.WriteShort( CONNECTIONLESS_MESSAGE_ID );
308         msg.WriteString( "getInfo" );
309         msg.WriteLong( serverList.GetChallenge() );     // challenge
310
311         clientPort.SendPacket( adr, msg.GetData(), msg.GetSize() );     
312 }
313
314 /*
315 ==================
316 idAsyncClient::GetServerInfo
317 ==================
318 */
319 void idAsyncClient::GetServerInfo( const char *address ) {
320         netadr_t        adr;
321
322         if ( address && *address != '\0' ) {
323                 if ( !Sys_StringToNetAdr( address, &adr, true ) ) {
324                         common->Printf( "Couldn't get server address for \"%s\"\n", address );
325                         return;
326                 }
327         } else if ( active ) {
328                 adr = serverAddress;
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();
333                 return;
334         } else {
335                 common->Printf( "no server found\n" );
336                 return;
337         }
338
339         if ( !adr.port ) {
340                 adr.port = PORT_SERVER;
341         }
342
343         GetServerInfo( adr );
344 }
345
346 /*
347 ==================
348 idAsyncClient::GetLANServers
349 ==================
350 */
351 void idAsyncClient::GetLANServers( void ) {
352         int                     i;
353         idBitMsg        msg;
354         byte            msgBuf[MAX_MESSAGE_SIZE];
355         netadr_t        broadcastAddress;
356
357         if ( !InitPort() ) {
358                 return;
359         }
360
361         idAsyncNetwork::LANServer.SetBool( true );
362
363         serverList.SetupLANScan();
364
365         msg.Init( msgBuf, sizeof( msgBuf ) );
366         msg.WriteShort( CONNECTIONLESS_MESSAGE_ID );
367         msg.WriteString( "getInfo" );
368         msg.WriteLong( serverList.GetChallenge() );
369
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() );
374         }
375 }
376
377 /*
378 ==================
379 idAsyncClient::GetNETServers
380 ==================
381 */
382 void idAsyncClient::GetNETServers( void ) {
383         idBitMsg        msg;
384         byte            msgBuf[MAX_MESSAGE_SIZE];
385
386         idAsyncNetwork::LANServer.SetBool( false );
387
388         // NetScan only clears GUI and results, not the stored list
389         serverList.Clear( );
390         serverList.NetScan( );
391         serverList.StartServers( true );
392
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 );
401
402         netadr_t adr;
403         if ( idAsyncNetwork::GetMasterAddress( 0, adr ) ) {
404                 clientPort.SendPacket( adr, msg.GetData(), msg.GetSize() );
405         }
406 }
407
408 /*
409 ==================
410 idAsyncClient::ListServers
411 ==================
412 */
413 void idAsyncClient::ListServers( void ) {
414         int i;
415
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 ) );
418         }
419 }
420
421 /*
422 ==================
423 idAsyncClient::ClearServers
424 ==================
425 */
426 void idAsyncClient::ClearServers( void ) {
427         serverList.Clear();
428 }
429
430 /*
431 ==================
432 idAsyncClient::RemoteConsole
433 ==================
434 */
435 void idAsyncClient::RemoteConsole( const char *command ) {
436         netadr_t        adr;
437         idBitMsg        msg;
438         byte            msgBuf[MAX_MESSAGE_SIZE];
439
440         if ( !InitPort() ) {
441                 return;
442         }
443
444         if ( active ) {
445                 adr = serverAddress;
446         } else {
447                 Sys_StringToNetAdr( idAsyncNetwork::clientRemoteConsoleAddress.GetString(), &adr, true );
448         }
449         
450         if ( !adr.port ) {
451                 adr.port = PORT_SERVER;
452         }
453
454         lastRconAddress = adr;
455         lastRconTime = realTime;
456
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 );
462
463         clientPort.SendPacket( adr, msg.GetData(), msg.GetSize() );     
464 }
465
466 /*
467 ==================
468 idAsyncClient::GetPrediction
469 ==================
470 */
471 int idAsyncClient::GetPrediction( void ) const {
472         if ( clientState < CS_CONNECTED ) {
473                 return -1;
474         } else {
475                 return clientPrediction;
476         }
477 }
478
479 /*
480 ==================
481 idAsyncClient::GetTimeSinceLastPacket
482 ==================
483 */
484 int idAsyncClient::GetTimeSinceLastPacket( void ) const {
485         if ( clientState < CS_CONNECTED ) {
486                 return -1;
487         } else {
488                 return clientTime - lastPacketTime;
489         }
490 }
491
492 /*
493 ==================
494 idAsyncClient::GetOutgoingRate
495 ==================
496 */
497 int idAsyncClient::GetOutgoingRate( void ) const {
498         if ( clientState < CS_CONNECTED ) {
499                 return -1;
500         } else {
501                 return channel.GetOutgoingRate();
502         }
503 }
504
505 /*
506 ==================
507 idAsyncClient::GetIncomingRate
508 ==================
509 */
510 int idAsyncClient::GetIncomingRate( void ) const {
511         if ( clientState < CS_CONNECTED ) {
512                 return -1;
513         } else {
514                 return channel.GetIncomingRate();
515         }
516 }
517
518 /*
519 ==================
520 idAsyncClient::GetOutgoingCompression
521 ==================
522 */
523 float idAsyncClient::GetOutgoingCompression( void ) const {
524         if ( clientState < CS_CONNECTED ) {
525                 return 0.0f;
526         } else {
527                 return channel.GetOutgoingCompression();
528         }
529 }
530
531 /*
532 ==================
533 idAsyncClient::GetIncomingCompression
534 ==================
535 */
536 float idAsyncClient::GetIncomingCompression( void ) const {
537         if ( clientState < CS_CONNECTED ) {
538                 return 0.0f;
539         } else {
540                 return channel.GetIncomingCompression();
541         }
542 }
543
544 /*
545 ==================
546 idAsyncClient::GetIncomingPacketLoss
547 ==================
548 */
549 float idAsyncClient::GetIncomingPacketLoss( void ) const {
550         if ( clientState < CS_CONNECTED ) {
551                 return 0.0f;
552         } else {
553                 return channel.GetIncomingPacketLoss();
554         }
555 }
556
557 /*
558 ==================
559 idAsyncClient::DuplicateUsercmds
560 ==================
561 */
562 void idAsyncClient::DuplicateUsercmds( int frame, int time ) {
563         int i, previousIndex, currentIndex;
564
565         previousIndex = ( frame - 1 ) & ( MAX_USERCMD_BACKUP - 1 );
566         currentIndex = frame & ( MAX_USERCMD_BACKUP - 1 );
567
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 );
571         }
572 }
573
574 /*
575 ==================
576 idAsyncClient::SendUserInfoToServer
577 ==================
578 */
579 void idAsyncClient::SendUserInfoToServer( void ) {
580         idBitMsg        msg;
581         byte            msgBuf[MAX_MESSAGE_SIZE];
582         idDict          info;
583
584         if ( clientState < CS_CONNECTED ) {
585                 return;
586         }
587
588         info = *cvarSystem->MoveCVarsToDict( CVAR_USERINFO );
589         
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 ] );
594
595         if ( !channel.SendReliableMessage( msg ) ) {
596                 common->Error( "client->server reliable messages overflow\n" );
597         }
598
599         sessLocal.mapSpawnData.userInfo[clientNum] = info;
600 }
601
602 /*
603 ==================
604 idAsyncClient::SendEmptyToServer
605 ==================
606 */
607 void idAsyncClient::SendEmptyToServer( bool force, bool mapLoad ) {
608         idBitMsg        msg;
609         byte            msgBuf[MAX_MESSAGE_SIZE];
610
611         if ( lastEmptyTime > realTime ) {
612                 lastEmptyTime = realTime;
613         }
614
615         if ( !force && ( realTime - lastEmptyTime < EMPTY_RESEND_TIME ) ) {
616                 return;
617         }
618
619         if ( idAsyncNetwork::verbose.GetInteger() ) {
620                 common->Printf( "sending empty to server, gameInitId = %d\n", mapLoad ? GAME_INIT_ID_MAP_LOAD : gameInitId );
621         }
622
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 );
628
629         channel.SendMessage( clientPort, clientTime, msg );
630
631         while( channel.UnsentFragmentsLeft() ) {
632                 channel.SendNextFragment( clientPort, clientTime );
633         }
634
635         lastEmptyTime = realTime;
636 }
637
638 /*
639 ==================
640 idAsyncClient::SendPingResponseToServer
641 ==================
642 */
643 void idAsyncClient::SendPingResponseToServer( int time ) {
644         idBitMsg        msg;
645         byte            msgBuf[MAX_MESSAGE_SIZE];
646
647         if ( idAsyncNetwork::verbose.GetInteger() == 2 ) {
648                 common->Printf( "sending ping response to server, gameInitId = %d\n", gameInitId );
649         }
650
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 );
657
658         channel.SendMessage( clientPort, clientTime, msg );
659         while( channel.UnsentFragmentsLeft() ) {
660                 channel.SendNextFragment( clientPort, clientTime );
661         }
662 }
663
664 /*
665 ==================
666 idAsyncClient::SendUsercmdsToServer
667 ==================
668 */
669 void idAsyncClient::SendUsercmdsToServer( void ) {
670         int                     i, numUsercmds, index;
671         idBitMsg        msg;
672         byte            msgBuf[MAX_MESSAGE_SIZE];
673         usercmd_t *     last;
674
675         if ( idAsyncNetwork::verbose.GetInteger() == 2 ) {
676                 common->Printf( "sending usercmd to server: gameInitId = %d, gameFrame = %d, gameTime = %d\n", gameInitId, gameFrame, gameTime );
677         }
678
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;
684
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 );
692
693         numUsercmds = idMath::ClampInt( 0, 10, idAsyncNetwork::clientUsercmdBackup.GetInteger() ) + 1;
694
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];
702         }
703
704         channel.SendMessage( clientPort, clientTime, msg );
705         while( channel.UnsentFragmentsLeft() ) {
706                 channel.SendNextFragment( clientPort, clientTime );
707         }
708 }
709
710 /*
711 ==================
712 idAsyncClient::InitGame
713 ==================
714 */
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 ) );
721
722         for ( int i = 0; i < MAX_ASYNC_CLIENTS; i++ ) {
723                 sessLocal.mapSpawnData.userInfo[ i ].Clear();
724         }
725
726         sessLocal.mapSpawnData.serverInfo = serverSI;
727 }
728
729 /*
730 ==================
731 idAsyncClient::ProcessUnreliableServerMessage
732 ==================
733 */
734 void idAsyncClient::ProcessUnreliableServerMessage( const idBitMsg &msg ) {
735         int i, j, index, id, numDuplicatedUsercmds, aheadOfServer, numUsercmds, delta;
736         int serverGameInitId, serverGameFrame, serverGameTime;
737         idDict serverSI;
738         usercmd_t *last;
739         bool pureWait;
740
741         serverGameInitId = msg.ReadLong();
742
743         id = msg.ReadByte();
744         switch( id ) {
745                 case SERVER_UNRELIABLE_MESSAGE_EMPTY: {
746                         if ( idAsyncNetwork::verbose.GetInteger() ) {
747                                 common->Printf( "received empty message from server\n" );
748                         }
749                         break;
750                 }
751                 case SERVER_UNRELIABLE_MESSAGE_PING: {
752                         if ( idAsyncNetwork::verbose.GetInteger() == 2 ) {
753                                 common->Printf( "received ping message from server\n" );
754                         }
755                         SendPingResponseToServer( msg.ReadLong() );
756                         break;
757                 }
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" );
763
764                         InitGame( serverGameInitId, serverGameFrame, serverGameTime, serverSI );
765
766                         channel.ResetRate();
767
768                         if ( idAsyncNetwork::verbose.GetInteger() ) {
769                                 common->Printf( "received gameinit, gameInitId = %d, gameFrame = %d, gameTime = %d\n", gameInitId, gameFrame, gameTime );
770                         }
771
772                         // mute sound
773                         soundSystem->SetMute( true );
774
775                         // ensure chat icon goes away when the GUI is changed...
776                         //cvarSystem->SetCVarBool( "ui_chat", false );
777
778                         if ( pureWait ) {
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" );
782                         } else {
783                                 // load map
784                                 session->SetGUI( NULL, NULL );
785                                 sessLocal.ExecuteMapChange();
786                         }
787
788                         break;
789                 }
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" );
795                                 }
796                                 break;
797                         }
798
799                         snapshotSequence = msg.ReadLong();
800                         snapshotGameFrame = msg.ReadLong();
801                         snapshotGameTime = msg.ReadLong();
802                         numDuplicatedUsercmds = msg.ReadByte();
803                         aheadOfServer = msg.ReadShort();
804
805                         // read the game snapshot
806                         game->ClientReadSnapshot( clientNum, snapshotSequence, snapshotGameFrame, snapshotGameTime, numDuplicatedUsercmds, aheadOfServer, msg );
807
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 );
813                                         break;
814                                 }
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];
821                                 }
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;
827                                 }
828                         }
829
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 );
837                                 }
838                         }
839
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 );
846                         }
847
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 ) );
853
854                         lastSnapshotTime = clientTime;
855
856                         if ( idAsyncNetwork::verbose.GetInteger() == 2 ) {
857                                 common->Printf( "received snapshot, gameInitId = %d, gameFrame = %d, gameTime = %d\n", gameInitId, gameFrame, gameTime );
858                         }
859
860                         if ( numDuplicatedUsercmds && ( idAsyncNetwork::verbose.GetInteger() == 2 ) ) {
861                                 common->Printf( "server duplicated %d user commands before snapshot %d\n", numDuplicatedUsercmds, snapshotGameFrame );
862                         }
863                         break;
864                 }
865                 default: {
866                         common->Printf( "unknown unreliable server message %d\n", id );
867                         break;
868                 }
869         }
870 }
871
872 /*
873 ==================
874 idAsyncClient::ProcessReliableMessagePure
875 ==================
876 */
877 void idAsyncClient::ProcessReliableMessagePure( const idBitMsg &msg ) {
878         idBitMsg        outMsg;
879         byte            msgBuf[ MAX_MESSAGE_SIZE ];
880         int                     inChecksums[ MAX_PURE_PAKS ];
881         int                     i;
882         int                     gamePakChecksum;
883         int                     serverGameInitId;
884
885         session->SetGUI( NULL, NULL );
886
887         serverGameInitId = msg.ReadLong();
888
889         if ( serverGameInitId != gameInitId ) {
890                 common->DPrintf( "ignoring pure server checksum from an outdated gameInitId (%d)\n", serverGameInitId );
891                 return;
892         }
893
894         if ( !ValidatePureServerChecksums( serverAddress, msg ) ) {
895                 
896                 return;
897         }
898
899         if ( idAsyncNetwork::verbose.GetInteger() ) {
900                 common->Printf( "received new pure server info. ExecuteMapChange and report back\n" );
901         }
902
903         // it is now ok to load the next map with updated pure checksums
904         sessLocal.ExecuteMapChange( true );
905
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 );
910
911         outMsg.WriteLong( gameInitId );
912
913         i = 0;
914         while ( inChecksums[ i ] ) {
915                 outMsg.WriteLong( inChecksums[ i++ ] );
916         }
917         outMsg.WriteLong( 0 );
918         outMsg.WriteLong( gamePakChecksum );
919
920         if ( !channel.SendReliableMessage( outMsg ) ) {
921                 common->Error( "client->server reliable messages overflow\n" );
922         }
923 }
924
925 /*
926 ===============
927 idAsyncClient::ReadLocalizedServerString
928 ===============
929 */
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 ) );
934 }
935
936 /*
937 ==================
938 idAsyncClient::ProcessReliableServerMessages
939 ==================
940 */
941 void idAsyncClient::ProcessReliableServerMessages( void ) {
942         idBitMsg        msg;
943         byte            msgBuf[MAX_MESSAGE_SIZE];
944         byte            id;
945
946         msg.Init( msgBuf, sizeof( msgBuf ) );
947
948         while ( channel.GetReliableMessage( msg ) ) {
949                 id = msg.ReadByte();
950                 switch( id ) {
951                         case SERVER_RELIABLE_MESSAGE_CLIENTINFO: {
952                                 int clientNum;
953                                 clientNum = msg.ReadByte();
954
955                                 idDict &info = sessLocal.mapSpawnData.userInfo[ clientNum ];
956                                 bool haveBase = ( msg.ReadBits( 1 ) != 0 );
957
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 );
963                                         info.Print();
964                                 } else {
965                                         common->DPrintf( "SERVER_RELIABLE_MESSAGE_CLIENTINFO %d (haveBase: %s): checksums ok 0x%x\n", clientNum, haveBase ? "true" : "false", checksum );
966                                 }
967 #endif
968
969                                 if ( haveBase ) {
970                                         msg.ReadDeltaDict( info, &info );
971                                 } else {
972                                         msg.ReadDeltaDict( info, NULL );
973                                 }
974
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
980                                 }
981                                 game->SetUserInfo( clientNum, info, true, false );
982                                 break;
983                         }
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 );
990                                 }
991                                 break;
992                         }
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 );
997                                 break;
998                         }
999                         case SERVER_RELIABLE_MESSAGE_DISCONNECT: {
1000                                 int clientNum;
1001                                 char string[MAX_STRING_CHARS];
1002                                 clientNum = msg.ReadLong( );
1003                                 ReadLocalizedServerString( msg, string, MAX_STRING_CHARS );
1004                                 if ( clientNum == idAsyncClient::clientNum ) {
1005                                         session->Stop();
1006                                         session->MessageBox( MSG_OK, string, common->GetLanguageDict()->GetString ( "#str_04319" ), true );
1007                                         session->StartMenu();
1008                                 } else {
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();
1012                                 }
1013                                 break;
1014                         }
1015                         case SERVER_RELIABLE_MESSAGE_APPLYSNAPSHOT: {
1016                                 int sequence;
1017                                 sequence = msg.ReadLong();
1018                                 if ( !game->ClientApplySnapshot( clientNum, sequence ) ) {
1019                                         session->Stop();
1020                                         common->Error( "couldn't apply snapshot %d", sequence );
1021                                 }
1022                                 break;
1023                         }
1024                         case SERVER_RELIABLE_MESSAGE_PURE: {
1025                                 ProcessReliableMessagePure( msg );
1026                                 break;
1027                         }
1028                         case SERVER_RELIABLE_MESSAGE_RELOAD: {
1029                                 if ( idAsyncNetwork::verbose.GetBool() ) {
1030                                         common->Printf( "got MESSAGE_RELOAD from server\n" );
1031                                 }
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" );
1034                                 break;
1035                         }
1036                         case SERVER_RELIABLE_MESSAGE_ENTERGAME: {
1037                                 SendUserInfoToServer();
1038                                 game->SetUserInfo( clientNum, sessLocal.mapSpawnData.userInfo[ clientNum ], true, false );
1039                                 cvarSystem->ClearModifiedFlags( CVAR_USERINFO );
1040                                 break;
1041                         }
1042                         default: {
1043                                 // pass reliable message on to game code
1044                                 game->ClientProcessReliableMessage( clientNum, msg );
1045                                 break;
1046                         }
1047                 }
1048         }
1049 }
1050
1051 /*
1052 ==================
1053 idAsyncClient::ProcessChallengeResponseMessage
1054 ==================
1055 */
1056 void idAsyncClient::ProcessChallengeResponseMessage( const netadr_t from, const idBitMsg &msg ) {       
1057         char serverGame[ MAX_STRING_CHARS ], serverGameBase[ MAX_STRING_CHARS ];
1058
1059         if ( clientState != CS_CHALLENGING ) {
1060                 common->Printf( "Unwanted challenge response received.\n" );
1061                 return;
1062         }
1063
1064         serverChallenge = msg.ReadLong();
1065         serverId = msg.ReadShort();
1066         msg.ReadString( serverGameBase, MAX_STRING_CHARS );
1067         msg.ReadString( serverGame, MAX_STRING_CHARS );
1068
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" );
1079                         return;
1080                 }
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" );
1086                 return;
1087         }
1088
1089         common->Printf( "received challenge response 0x%x from %s\n", serverChallenge, Sys_NetAdrToString( from ) );
1090
1091         // start sending connect packets instead of challenge request packets
1092         clientState = CS_CONNECTING;
1093         lastConnectTime = -9999;
1094
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;
1098 }
1099
1100 /*
1101 ==================
1102 idAsyncClient::ProcessConnectResponseMessage
1103 ==================
1104 */
1105 void idAsyncClient::ProcessConnectResponseMessage( const netadr_t from, const idBitMsg &msg ) {
1106         int serverGameInitId, serverGameFrame, serverGameTime;
1107         idDict serverSI;
1108
1109         if ( clientState >= CS_CONNECTED ) {
1110                 common->Printf( "Duplicate connect received.\n" );
1111                 return;
1112         }
1113         if ( clientState != CS_CONNECTING ) {
1114                 common->Printf( "Connect response packet while not connecting.\n" );
1115                 return;
1116         }
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 ) );
1120                 return;
1121         }
1122
1123         common->Printf( "received connect response from %s\n", Sys_NetAdrToString( from ) );
1124
1125         channel.Init( from, clientId );
1126         clientNum = msg.ReadLong();
1127         clientState = CS_CONNECTED;
1128         lastPacketTime = -9999;
1129
1130         serverGameInitId = msg.ReadLong();
1131         serverGameFrame = msg.ReadLong();
1132         serverGameTime = msg.ReadLong();
1133         msg.ReadDeltaDict( serverSI, NULL );
1134
1135         InitGame( serverGameInitId, serverGameFrame, serverGameTime, serverSI );
1136
1137         // load map
1138         session->SetGUI( NULL, NULL );
1139         sessLocal.ExecuteMapChange();
1140
1141         clientPredictTime = clientPrediction = idMath::ClampInt( 0, idAsyncNetwork::clientMaxPrediction.GetInteger(), clientTime - lastConnectTime );
1142 }
1143
1144 /*
1145 ==================
1146 idAsyncClient::ProcessDisconnectMessage
1147 ==================
1148 */
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" );
1152                 return;
1153         }
1154         if ( !Sys_CompareNetAdrBase( from, serverAddress ) ) {
1155                 common->Printf( "Disconnect packet from unknown server.\n" );
1156                 return;
1157         }
1158         session->Stop();
1159         session->MessageBox( MSG_OK, common->GetLanguageDict()->GetString ( "#str_04320" ), NULL, true );
1160         session->StartMenu();
1161 }
1162
1163 /*
1164 ==================
1165 idAsyncClient::ProcessInfoResponseMessage
1166 ==================
1167 */
1168 void idAsyncClient::ProcessInfoResponseMessage( const netadr_t from, const idBitMsg &msg ) {
1169         int i, protocol, index;
1170         networkServer_t serverInfo;
1171         bool verbose = false;
1172
1173         if ( from.type == NA_LOOPBACK || cvarSystem->GetCVarBool( "developer" ) ) {
1174                 verbose = true;
1175         }
1176
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 );
1183                 return;
1184         }
1185         msg.ReadDeltaDict( serverInfo.serverInfo, NULL );
1186
1187         if ( verbose ) {
1188                 common->Printf( "server IP = %s\n", Sys_NetAdrToString( serverInfo.adr ) );
1189                 serverInfo.serverInfo.Print();
1190         }
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 );
1195                 if ( verbose ) {
1196                         common->Printf( "client %2d: %s, ping = %d, rate = %d\n", i, serverInfo.nickname[ serverInfo.clients ], serverInfo.pings[ serverInfo.clients ], serverInfo.rate[ serverInfo.clients ] );
1197                 }
1198                 serverInfo.clients++;
1199         }
1200         serverInfo.OSMask = msg.ReadLong();
1201         index = serverList.InfoResponse( serverInfo );
1202
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" ) );
1204 }
1205
1206 /*
1207 ==================
1208 idAsyncClient::ProcessPrintMessage
1209 ==================
1210 */
1211 void idAsyncClient::ProcessPrintMessage( const netadr_t from, const idBitMsg &msg ) {
1212         char            string[ MAX_STRING_CHARS ];
1213         int                     opcode;
1214         int                     game_opcode = ALLOW_YES;
1215         const char      *retpass;
1216
1217         opcode = msg.ReadLong();
1218         if ( opcode == SERVER_PRINT_GAMEDENY ) {
1219                 game_opcode = msg.ReadLong();
1220         }
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" ));
1229                         if ( retpass ) {
1230                                 // #790
1231                                 cvarSystem->SetCVarString( "password", "" );
1232                                 cvarSystem->SetCVarString( "password", retpass );                       
1233                         } else {
1234                                 cmdSystem->BufferCommandText( CMD_EXEC_NOW, "disconnect" );
1235                         }
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" );
1240                 }
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" );
1244         }
1245 }
1246
1247 /*
1248 ==================
1249 idAsyncClient::ProcessServersListMessage
1250 ==================
1251 */
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 ) );
1255                 return;
1256         }
1257         while ( msg.GetRemaingData() ) {
1258                 int a,b,c,d;
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() ) );
1261         }
1262 }
1263
1264 /*
1265 ==================
1266 idAsyncClient::ProcessAuthKeyMessage
1267 ==================
1268 */
1269 void idAsyncClient::ProcessAuthKeyMessage( const netadr_t from, const idBitMsg &msg ) {
1270         authKeyMsg_t            authMsg;
1271         char                            read_string[ MAX_STRING_CHARS ];
1272         const char                      *retkey;
1273         authBadKeyStatus_t      authBadStatus;
1274         int                                     key_index;
1275         bool                            valid[ 2 ];
1276         idStr                           auth_msg;
1277
1278         if ( clientState != CS_CONNECTING && !session->WaitingForGameAuth() ) {
1279                 common->Printf( "clientState != CS_CONNECTING, not waiting for game auth, authKey ignored\n" );
1280                 return;
1281         }
1282
1283         authMsg = (authKeyMsg_t)msg.ReadByte();
1284         if ( authMsg == AUTHKEY_BADKEY ) {
1285                 valid[ 0 ] = valid[ 1 ] = true;
1286                 key_index = 0;
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 );
1293                         break;
1294                 case AUTHKEY_BAD_BANNED:
1295                         key_index = msg.ReadByte();
1296                         auth_msg = common->GetLanguageDict()->GetString( va( "#str_0719%1d", 6 + key_index ) );
1297                         auth_msg += "\n";
1298                         auth_msg += common->GetLanguageDict()->GetString( "#str_04304" );
1299                         valid[ key_index ] = false;
1300                         break;
1301                 case AUTHKEY_BAD_INUSE:
1302                         key_index = msg.ReadByte();
1303                         auth_msg = common->GetLanguageDict()->GetString( va( "#str_0719%1d", 8 + key_index ) );
1304                         auth_msg += "\n";
1305                         auth_msg += common->GetLanguageDict()->GetString( "#str_04304" );
1306                         valid[ key_index ] = false;
1307                         break;
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;
1313                         break;
1314                 }
1315                 common->DPrintf( "auth deny: %s\n", auth_msg.c_str() );
1316                 
1317                 // keys to be cleared. applies to both net connect and game auth
1318                 session->ClearCDKey( valid );
1319
1320                 // get rid of the bad key - at least that's gonna annoy people who stole a fake key
1321                 if ( clientState == CS_CONNECTING ) {
1322                         while ( 1 ) {
1323                                 // here we use the auth status message
1324                                 retkey = session->MessageBox( MSG_CDKEY, auth_msg, common->GetLanguageDict()->GetString( "#str_04325" ), true );
1325                                 if ( retkey ) {
1326                                         if ( session->CheckKey( retkey, true, valid ) ) {
1327                                                 cmdSystem->BufferCommandText( CMD_EXEC_NOW, "reconnect" );
1328                                         } else {
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 );
1332                                                 continue;
1333                                         }
1334                                 } else {
1335                                         cmdSystem->BufferCommandText( CMD_EXEC_NOW, "disconnect" );
1336                                 }
1337                                 break;
1338                         }
1339                 } else {
1340                         // forward the auth status information to the session code
1341                         session->CDKeysAuthReply( false, auth_msg );
1342                 }
1343         } else {
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 );
1348         }
1349 }
1350
1351 /*
1352 ==================
1353 idAsyncClient::ProcessVersionMessage
1354 ==================
1355 */
1356 void idAsyncClient::ProcessVersionMessage( const netadr_t from, const idBitMsg &msg ) {
1357         char string[ MAX_STRING_CHARS ];
1358
1359         if ( updateState != UPDATE_SENT ) {
1360                 common->Printf( "ProcessVersionMessage: version reply, != UPDATE_SENT\n" );
1361                 return;
1362         }
1363
1364         common->Printf( "A new version is available\n" );
1365         msg.ReadString( string, MAX_STRING_CHARS );
1366         updateMSG = string;
1367         updateDirectDownload = ( msg.ReadByte() != 0 );
1368         msg.ReadString( string, MAX_STRING_CHARS );
1369         updateURL = string;
1370         updateMime = (dlMime_t)msg.ReadByte();
1371         msg.ReadString( string, MAX_STRING_CHARS );
1372         updateFallback = string;
1373         updateState = UPDATE_READY;
1374 }
1375
1376 /*
1377 ==================
1378 idAsyncClient::ValidatePureServerChecksums
1379 ==================
1380 */
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;
1387         idBitMsg        dlmsg;
1388         byte            msgBuf[MAX_MESSAGE_SIZE];
1389
1390         // read checksums
1391         // pak checksums, in a 0-terminated list
1392         numChecksums = 0;
1393         do {
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 );
1399                         return false;
1400                 }
1401         } while ( i );
1402         inChecksums[ numChecksums ] = 0;
1403         inGamePakChecksum = msg.ReadLong();
1404
1405         fsPureReply_t reply = fileSystem->SetPureServerChecksums( inChecksums, inGamePakChecksum, missingChecksums, &missingGamePakChecksum );
1406         switch ( reply ) {
1407                 case PURE_RESTART:
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" );
1414                         return false;
1415                 case PURE_MISSING: {
1416
1417                         idStr checksums;
1418
1419                         i = 0;
1420                         while ( missingChecksums[ i ] ) {
1421                                 checksums += va( "0x%x ", missingChecksums[ i++ ] );
1422                         }
1423                         numMissingChecksums = i;
1424
1425                         if ( idAsyncNetwork::clientDownload.GetInteger() == 0 ) {
1426                                 // never any downloads
1427                                 idStr message = va( common->GetLanguageDict()->GetString( "#str_07210" ), Sys_NetAdrToString( from ) );
1428
1429                                 if ( numMissingChecksums > 0 ) {
1430                                         message += va( common->GetLanguageDict()->GetString( "#str_06751" ), numMissingChecksums, checksums.c_str() );
1431                                 }
1432                                 if ( missingGamePakChecksum ) {
1433                                         message += va( common->GetLanguageDict()->GetString( "#str_06750" ), missingGamePakChecksum );
1434                                 }
1435
1436                                 common->Printf( message );
1437                                 cmdSystem->BufferCommandText( CMD_EXEC_NOW, "disconnect" );
1438                                 session->MessageBox( MSG_OK, message, common->GetLanguageDict()->GetString( "#str_06735" ), true );
1439                         } else {
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" );
1443                                         return false;
1444                                 }
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 );
1449                                 }
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
1464                                 i = 0;
1465                                 while ( missingChecksums[ i ] ) {
1466                                         dlmsg.WriteLong( missingChecksums[ i++ ] );
1467                                 }
1468                                 dlmsg.WriteLong( 0 );
1469                                 clientPort.SendPacket( from, dlmsg.GetData(), dlmsg.GetSize() );
1470                         }
1471
1472                         return false;
1473                 }
1474                 case PURE_NODLL:
1475                         common->Printf( common->GetLanguageDict()->GetString( "#str_07211" ), Sys_NetAdrToString( from ) );
1476                         cmdSystem->BufferCommandText( CMD_EXEC_NOW, "disconnect" );
1477                         return false;
1478                 default:
1479                         return true;
1480         }
1481         return true;
1482 }
1483
1484 /*
1485 ==================
1486 idAsyncClient::ProcessPureMessage
1487 ==================
1488 */
1489 void idAsyncClient::ProcessPureMessage( const netadr_t from, const idBitMsg &msg ) {
1490         idBitMsg        outMsg;
1491         byte            msgBuf[ MAX_MESSAGE_SIZE ];
1492         int                     i;
1493         int                     inChecksums[ MAX_PURE_PAKS ];
1494         int                     gamePakChecksum;
1495
1496         if ( clientState != CS_CONNECTING ) {
1497                 common->Printf( "clientState != CS_CONNECTING, pure msg ignored\n" );
1498                 return;
1499         }
1500
1501         if ( !ValidatePureServerChecksums( from, msg ) ) {
1502                 return;
1503         }
1504
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 );
1511         i = 0;
1512         while ( inChecksums[ i ] ) {
1513                 outMsg.WriteLong( inChecksums[ i++ ] );
1514         }
1515         outMsg.WriteLong( 0 );
1516         outMsg.WriteLong( gamePakChecksum );
1517         clientPort.SendPacket( from, outMsg.GetData(), outMsg.GetSize() );
1518 }
1519
1520 /*
1521 ==================
1522 idAsyncClient::ConnectionlessMessage
1523 ==================
1524 */
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
1527
1528         msg.ReadString( string, sizeof( string ) );
1529
1530         // info response from a server, are accepted from any source
1531         if ( idStr::Icmp( string, "infoResponse" ) == 0 ) {
1532                 ProcessInfoResponseMessage( from, msg );
1533                 return;
1534         }
1535
1536         // from master server:
1537         if ( Sys_CompareNetAdrBase( from, idAsyncNetwork::GetMasterAddress( ) ) ) {
1538                 // server list
1539                 if ( idStr::Icmp( string, "servers" ) == 0 ) {
1540                         ProcessServersListMessage( from, msg );
1541                         return;
1542                 }
1543         
1544                 if ( idStr::Icmp( string, "authKey" ) == 0 ) {
1545                         ProcessAuthKeyMessage( from, msg );
1546                         return;
1547                 }
1548
1549                 if ( idStr::Icmp( string, "newVersion" ) == 0 ) {
1550                         ProcessVersionMessage( from, msg );
1551                         return;
1552                 }
1553         }
1554
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 ) );
1558                 return;
1559         }
1560
1561         // challenge response from the server we are connecting to
1562         if ( idStr::Icmp( string, "challengeResponse" ) == 0 ) {
1563                 ProcessChallengeResponseMessage( from, msg );
1564                 return;
1565         }
1566
1567         // connect response from the server we are connecting to
1568         if ( idStr::Icmp( string, "connectResponse" ) == 0 ) {
1569                 ProcessConnectResponseMessage( from, msg );
1570                 return;
1571         }
1572
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 );
1577                 return;
1578         }
1579
1580         // print request from server
1581         if ( idStr::Icmp( string, "print" ) == 0 ) {
1582                 ProcessPrintMessage( from, msg );
1583                 return;
1584         }
1585
1586         // server pure list
1587         if ( idStr::Icmp( string, "pureServer" ) == 0 ) {
1588                 ProcessPureMessage( from, msg );
1589                 return;
1590         }
1591
1592         if ( idStr::Icmp( string, "downloadInfo" ) == 0 ) {
1593                 ProcessDownloadInfoMessage( from, msg );
1594         }
1595
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 );
1601                 }
1602         }
1603
1604         common->DPrintf( "ignored message from %s: %s\n", Sys_NetAdrToString( from ), string );
1605 }
1606
1607 /*
1608 =================
1609 idAsyncClient::ProcessMessage
1610 =================
1611 */
1612 void idAsyncClient::ProcessMessage( const netadr_t from, idBitMsg &msg ) {
1613         int id;
1614
1615         id = msg.ReadShort();
1616
1617         // check for a connectionless packet
1618         if ( id == CONNECTIONLESS_MESSAGE_ID ) {
1619                 ConnectionlessMessage( from, msg );
1620                 return;
1621         }
1622
1623         if ( clientState < CS_CONNECTED ) {
1624                 return;         // can't be a valid sequenced packet
1625         }
1626
1627         if ( msg.GetRemaingData() < 4 ) {
1628                 common->DPrintf( "%s: tiny packet\n", Sys_NetAdrToString( from ) );
1629                 return;
1630         }
1631
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 ) );
1635                 return;
1636         }
1637
1638         if ( !channel.Process( from, clientTime, msg, serverMessageSequence ) ) {
1639                 return;         // out of order, duplicated, fragment, etc.
1640         }
1641
1642         lastPacketTime = clientTime;
1643         ProcessReliableServerMessages();
1644         ProcessUnreliableServerMessage( msg );
1645 }
1646
1647 /*
1648 ==================
1649 idAsyncClient::SetupConnection
1650 ==================
1651 */
1652 void idAsyncClient::SetupConnection( void ) {
1653         idBitMsg        msg;
1654         byte            msgBuf[MAX_MESSAGE_SIZE];
1655
1656         if ( clientTime - lastConnectTime < SETUP_CONNECTION_RESEND_TIME ) {
1657                 return;
1658         }
1659
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 );
1673 #if ID_FAKE_PURE
1674                 // fake win32 OS - might need to adapt depending on the case
1675                 msg.WriteShort( 0 );
1676 #else
1677                 msg.WriteShort( BUILD_OS_ID );
1678 #endif
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() );
1688                 
1689                 if ( idAsyncNetwork::LANServer.GetBool() ) {
1690                         common->Printf( "net_LANServer is set, connecting in LAN mode\n" );
1691                 } else {
1692                         // emit a cd key authorization request
1693                         // modified at protocol 1.37 for XP key addition
1694                         msg.BeginWriting();
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 );
1705                         if ( xpkey ) {
1706                                 msg.WriteString( xpkey );
1707                         }
1708                         clientPort.SendPacket( idAsyncNetwork::GetMasterAddress(), msg.GetData(), msg.GetSize() );
1709                 }
1710         } else {
1711                 return;
1712         }
1713
1714         lastConnectTime = clientTime;
1715 }
1716
1717 /*
1718 ==================
1719 idAsyncClient::SendReliableGameMessage
1720 ==================
1721 */
1722 void idAsyncClient::SendReliableGameMessage( const idBitMsg &msg ) {
1723         idBitMsg        outMsg;
1724         byte            msgBuf[MAX_MESSAGE_SIZE];
1725
1726         if ( clientState < CS_INGAME ) {
1727                 return;
1728         }
1729
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" );
1735         }
1736 }
1737
1738 /*
1739 ==================
1740 idAsyncClient::Idle
1741 ==================
1742 */
1743 void idAsyncClient::Idle( void ) {
1744         // also need to read mouse for the connecting guis
1745         usercmdGen->GetDirectUsercmd();
1746
1747         SendEmptyToServer();
1748 }
1749
1750 /*
1751 ==================
1752 idAsyncClient::UpdateTime
1753 ==================
1754 */
1755 int idAsyncClient::UpdateTime( int clamp ) {
1756         int time, msec;
1757
1758         time = Sys_Milliseconds();
1759         msec = idMath::ClampInt( 0, clamp, time - realTime );
1760         realTime = time;
1761         clientTime += msec;
1762         return msec;
1763 }
1764
1765 /*
1766 ==================
1767 idAsyncClient::RunFrame
1768 ==================
1769 */
1770 void idAsyncClient::RunFrame( void ) {
1771         int                     msec, size;
1772         bool            newPacket;
1773         idBitMsg        msg;
1774         byte            msgBuf[MAX_MESSAGE_SIZE];
1775         netadr_t        from;
1776
1777         msec = UpdateTime( 100 );
1778
1779         if ( !clientPort.GetPort() ) {
1780                 return;
1781         }
1782
1783         // handle ongoing pk4 downloads and patch downloads
1784         HandleDownloads();
1785
1786         gameTimeResidual += msec;
1787
1788         // spin in place processing incoming packets until enough time lapsed to run a new game frame
1789         do {
1790
1791                 do {
1792
1793                         // blocking read with game time residual timeout
1794                         newPacket = clientPort.GetPacketBlocking( from, msgBuf, size, sizeof( msgBuf ), USERCMD_MSEC - ( gameTimeResidual + clientPredictTime ) - 1 );
1795                         if ( newPacket ) {
1796                                 msg.Init( msgBuf, sizeof( msgBuf ) );
1797                                 msg.SetSize( size );
1798                                 msg.BeginReading();
1799                                 ProcessMessage( from, msg );
1800                         }
1801
1802                         msec = UpdateTime( 100 );
1803                         gameTimeResidual += msec;
1804
1805                 } while( newPacket );
1806
1807         } while( gameTimeResidual + clientPredictTime < USERCMD_MSEC );
1808
1809         // update server list
1810         serverList.RunFrame();
1811
1812         if ( clientState == CS_DISCONNECTED ) {
1813                 usercmdGen->GetDirectUsercmd();
1814                 gameTimeResidual = USERCMD_MSEC - 1;
1815                 clientPredictTime = 0;
1816                 return;
1817         }
1818
1819         if ( clientState == CS_PURERESTART ) {
1820                 clientState = CS_DISCONNECTED;
1821                 Reconnect();
1822                 gameTimeResidual = USERCMD_MSEC - 1;
1823                 clientPredictTime = 0;
1824                 return;
1825         }
1826
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();
1831                 SetupConnection();
1832                 gameTimeResidual = USERCMD_MSEC - 1;
1833                 clientPredictTime = 0;
1834                 return;
1835         }
1836
1837         if ( CheckTimeout() ) {
1838                 return;
1839         }
1840
1841         // if not yet in the game send empty messages to keep data flowing through the channel
1842         if ( clientState < CS_INGAME ) {
1843                 Idle();
1844                 gameTimeResidual = 0;
1845                 return;
1846         }
1847
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 );
1854         }
1855
1856         if ( gameTimeResidual + clientPredictTime >= USERCMD_MSEC ) {
1857                 lastFrameDelta = 0;
1858         }
1859
1860         // generate user commands for the predicted time
1861         while ( gameTimeResidual + clientPredictTime >= USERCMD_MSEC ) {
1862
1863                 // send the user commands of this client to the server
1864                 SendUsercmdsToServer();
1865
1866                 // update time
1867                 gameFrame++;
1868                 gameTime += USERCMD_MSEC;
1869                 gameTimeResidual -= USERCMD_MSEC;
1870
1871                 // run from the snapshot up to the local game frame
1872                 while ( snapshotGameFrame < gameFrame ) {
1873
1874                         lastFrameDelta++;
1875
1876                         // duplicate usercmds for clients if no new ones are available
1877                         DuplicateUsercmds( snapshotGameFrame, snapshotGameTime );
1878
1879                         // indicate the last prediction frame before a render
1880                         bool lastPredictFrame = ( snapshotGameFrame + 1 >= gameFrame && gameTimeResidual + clientPredictTime < USERCMD_MSEC );
1881
1882                         // run client prediction
1883                         gameReturn_t ret = game->ClientPrediction( clientNum, userCmds[ snapshotGameFrame & ( MAX_USERCMD_BACKUP - 1 ) ], lastPredictFrame );
1884
1885                         idAsyncNetwork::ExecuteSessionCommand( ret.sessionCommand );
1886
1887                         snapshotGameFrame++;
1888                         snapshotGameTime += USERCMD_MSEC;
1889                 }
1890         }
1891 }
1892
1893 /*
1894 ==================
1895 idAsyncClient::PacifierUpdate
1896 ==================
1897 */
1898 void idAsyncClient::PacifierUpdate( void ) {
1899         if ( !IsActive() ) {
1900                 return;
1901         }
1902         realTime = Sys_Milliseconds();
1903         SendEmptyToServer( false, true );
1904 }
1905
1906 /*
1907 ==================
1908 idAsyncClient::SendVersionCheck
1909 ==================
1910 */
1911 void idAsyncClient::SendVersionCheck( bool fromMenu ) {
1912         idBitMsg        msg;
1913         byte            msgBuf[MAX_MESSAGE_SIZE];
1914
1915         if ( updateState != UPDATE_NONE && !fromMenu ) {
1916                 common->DPrintf( "up-to-date check was already performed\n" );
1917                 return;
1918         }
1919
1920         InitPort();
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() );
1929
1930         common->DPrintf( "sent a version check request\n" );
1931
1932         updateState = UPDATE_SENT;
1933         updateSentTime = clientTime;
1934         showUpdateMessage = fromMenu;
1935 }
1936
1937 /*
1938 ==================
1939 idAsyncClient::SendVersionDLUpdate
1940
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
1944 ==================
1945 */
1946 void idAsyncClient::SendVersionDLUpdate( int state ) {
1947         idBitMsg        msg;
1948         byte            msgBuf[MAX_MESSAGE_SIZE];
1949
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() );
1956 }
1957
1958 /*
1959 ==================
1960 idAsyncClient::HandleDownloads
1961 ==================
1962 */
1963 void idAsyncClient::HandleDownloads( void ) {
1964
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;
1971                 }
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 ) {
1976                         // 
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;
1981                                 } else {
1982
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 ) );
1986                                         dltotal = 0;
1987                                         dlnow = 0;
1988
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 );
1997
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 );
2009                                                         } else {
2010                                                                 sys->OpenURL( va( "file://%s", fullPath.c_str() ), true );
2011                                                         }
2012                                                 } else {
2013                                                         session->MessageBox( MSG_OK, va( common->GetLanguageDict()->GetString ( "#str_04333" ), fullPath.c_str() ), common->GetLanguageDict()->GetString ( "#str_04334" ), true );
2014                                                 }
2015                                         } else {
2016                                                 if ( backgroundDownload.url.dlerror[ 0 ] ) {
2017                                                         common->Warning( "update download failed. curl error: %s", backgroundDownload.url.dlerror );
2018                                                 }
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 );
2026                                                 } else {
2027                                                         common->Printf( "no fallback URL\n" );
2028                                                 }
2029                                         }
2030                                 }
2031                         } else {
2032                                 updateState = UPDATE_DONE;
2033                         }
2034                 } else if ( dlList.Num() ) {
2035
2036                         int numPaks = dlList.Num();
2037                         int pakCount = 1;
2038                         int progress_start, progress_end;
2039                         currentDlSize = 0;
2040
2041                         do {
2042
2043                                 if ( dlList[ 0 ].url[ 0 ] == '\0' ) {
2044                                         // ignore empty files
2045                                         dlList.RemoveIndex( 0 );
2046                                         continue;
2047                                 }
2048                                 common->Printf( "start download for %s\n", dlList[ 0 ].url.c_str() );
2049
2050                                 idFile_Permanent *f = static_cast< idFile_Permanent *>( fileSystem->MakeTemporaryFile( ) );
2051                                 if ( !f ) {
2052                                         common->Warning( "could not create temporary file" );
2053                                         dlList.Clear();
2054                                         return;
2055                                 }
2056
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 );
2065                                 idStr dltitle;
2066                                 // "Downloading %s"
2067                                 sprintf( dltitle, common->GetLanguageDict()->GetString( "#str_07213" ), dlList[ 0 ].filename.c_str() );
2068                                 if ( numPaks > 1 ) {
2069                                         dltitle += va( " (%d/%d)", pakCount, numPaks );
2070                                 }
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 );
2074                                 } else {
2075                                         progress_start = 0;
2076                                         progress_end = 100;
2077                                 }
2078                                 session->DownloadProgressBox( &backgroundDownload, dltitle, progress_start, progress_end );
2079                                 if ( backgroundDownload.url.status == DL_DONE ) {                               
2080                                         idFile          *saveas;
2081                                         const int       CHUNK_SIZE = 1024 * 1024;
2082                                         byte            *buf;
2083                                         int                     remainlen;
2084                                         int                     readlen;
2085                                         int                     retlen;
2086                                         int                     checksum;
2087
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 );
2103                                                 }
2104                                                 retlen = saveas->Write( buf, readlen );
2105                                                 if ( retlen != readlen ) {
2106                                                         common->FatalError( "short write %d of %d in idFileSystem::HandleDownload", retlen, readlen );
2107                                                 }
2108                                                 remainlen -= readlen;
2109                                         }
2110                                         fileSystem->CloseFile( f );
2111                                         fileSystem->CloseFile( saveas );
2112                                         common->Printf( "saved as %s\n", finalPath.c_str() );
2113                                         Mem_Free( buf );
2114                                         
2115                                         // add that file to our paks list
2116                                         checksum = fileSystem->AddZipFile( dlList[ 0 ].filename );
2117
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 );
2123                                                 dlList.Clear();
2124                                                 return;
2125                                         }
2126
2127                                         currentDlSize += dlList[ 0 ].size;
2128                                         
2129                                 } else {
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 );
2133                                         }
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 );
2137                                         dlList.Clear();
2138                                         return;
2139                                 }
2140
2141                                 pakCount++;
2142                                 dlList.RemoveIndex( 0 );                        
2143                         } while ( dlList.Num() );
2144                         
2145                         // all downloads successful - do the dew
2146                         cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "reconnect\n" );
2147                 }
2148         }
2149 }
2150
2151 /*
2152 ===============
2153 idAsyncClient::SendAuthCheck
2154 ===============
2155 */
2156 bool idAsyncClient::SendAuthCheck( const char *cdkey, const char *xpkey ) {
2157         idBitMsg        msg;
2158         byte            msgBuf[MAX_MESSAGE_SIZE];
2159
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 : "" );
2168         InitPort();
2169         clientPort.SendPacket( idAsyncNetwork::GetMasterAddress(), msg.GetData(), msg.GetSize() );
2170         return true;
2171 }
2172
2173 /*
2174 ===============
2175 idAsyncClient::CheckTimeout
2176 ===============
2177 */
2178 bool idAsyncClient::CheckTimeout( void ) {
2179         if ( lastPacketTime > 0 && ( lastPacketTime + idAsyncNetwork::clientServerTimeout.GetInteger()*1000 < clientTime ) ) {
2180                 session->StopBox();
2181                 session->MessageBox( MSG_OK, common->GetLanguageDict()->GetString ( "#str_04328" ), common->GetLanguageDict()->GetString ( "#str_04329" ), true );
2182                 cmdSystem->BufferCommandText( CMD_EXEC_NOW, "disconnect" );
2183                 return true;
2184         }
2185         return false;
2186 }
2187
2188 /*
2189 ===============
2190 idAsyncClient::ProcessDownloadInfoMessage
2191 ===============
2192 */
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();
2197         int                             pakDl;
2198         int                             pakIndex;
2199         
2200         pakDlEntry_t    entry;
2201         bool                    gotAllFiles = true;
2202         idStr                   sizeStr;
2203         bool                    gotGame = false;
2204
2205         if ( dlRequest == -1 || srvDlRequest != dlRequest ) {
2206                 common->Warning( "bad download id from server, ignored" );
2207                 return;
2208         }
2209         // mark the dlRequest as dead now whatever how we process it
2210         dlRequest = -1;
2211
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 );
2220                 }
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" );
2225                         return;
2226                 }
2227                 // read the URLs, check against what we requested, prompt for download
2228                 pakIndex = -1;
2229                 totalDlSize = 0;
2230                 do {
2231                         pakIndex++;
2232                         pakDl = msg.ReadByte();
2233                         if ( pakDl == SERVER_PAK_YES ) {
2234                                 if ( pakIndex == 0 ) {
2235                                         gotGame = true;
2236                                 }
2237                                 msg.ReadString( buf, MAX_STRING_CHARS );
2238                                 entry.filename = buf;
2239                                 msg.ReadString( buf, MAX_STRING_CHARS );
2240                                 entry.url = buf;
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;
2250                                 entry.url = "";
2251                                 entry.size = 0;
2252                                 entry.checksum = 0;
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;
2258                                 }
2259                         } else {
2260                                 assert( pakDl == SERVER_PAK_END );
2261                         }                       
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;
2266                 }
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"
2273                         dlList.Clear();
2274                         session->MessageBox( MSG_OK, common->GetLanguageDict()->GetString( "#str_07219" ), common->GetLanguageDict()->GetString( "#str_07218" ), true );
2275                         return;
2276                 }
2277                 bool asked = false;
2278                 if ( gotGame ) {
2279                         asked = 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 ] ) {
2283                                 dlList.Clear();
2284                                 return;
2285                         }
2286                 }
2287                 if ( !gotAllFiles ) {
2288                         asked = true;
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 ] ) {
2293                                 dlList.Clear();
2294                                 return;
2295                         }
2296                 }
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 ] ) {
2302                                 dlList.Clear();
2303                                 return;
2304                         }
2305                 }
2306         } else {
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 );
2311         }
2312 }
2313
2314 /*
2315 ===============
2316 idAsyncClient::GetDownloadRequest
2317 ===============
2318 */
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 ] ) {
2322                 idRandom newreq;
2323
2324                 dlChecksums[ 0 ] = gamePakChecksum;
2325                 memcpy( dlChecksums + 1, checksums, sizeof( int ) * MAX_PURE_PAKS );
2326
2327                 newreq.SetSeed( Sys_Milliseconds() );
2328                 dlRequest = newreq.RandomInt();
2329                 dlCount = count + ( gamePakChecksum ? 1 : 0 );
2330                 return dlRequest;
2331         }
2332         // this is the same dlRequest, we haven't heard from the server. keep the same id
2333         return dlRequest;
2334 }