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 "sys/platform.h"
30 #include "idlib/LangDict.h"
31 #include "framework/async/AsyncNetwork.h"
32 #include "framework/Licensee.h"
33 #include "framework/Game.h"
34 #include "framework/Session_local.h"
35 #include "sound/sound.h"
36
37 #include "framework/async/AsyncClient.h"
38
39 const int SETUP_CONNECTION_RESEND_TIME = 1000;
40 const int EMPTY_RESEND_TIME = 500;
41 const int PREDICTION_FAST_ADJUST = 4;
42
43 /*
44 ==================
45 idAsyncClient::idAsyncClient
46 ==================
47 */
idAsyncClient(void)48 idAsyncClient::idAsyncClient( void ) {
49 guiNetMenu = NULL;
50 updateState = UPDATE_NONE;
51 Clear();
52 }
53
54 /*
55 ==================
56 idAsyncClient::Clear
57 ==================
58 */
Clear(void)59 void idAsyncClient::Clear( void ) {
60 active = false;
61 realTime = 0;
62 clientTime = 0;
63 clientId = 0;
64 clientDataChecksum = 0;
65 clientNum = 0;
66 clientState = CS_DISCONNECTED;
67 clientPrediction = 0;
68 clientPredictTime = 0;
69 serverId = 0;
70 serverChallenge = 0;
71 serverMessageSequence = 0;
72 lastConnectTime = -9999;
73 lastEmptyTime = -9999;
74 lastPacketTime = -9999;
75 lastSnapshotTime = -9999;
76 snapshotGameFrame = 0;
77 snapshotGameTime = 0;
78 snapshotSequence = 0;
79 gameInitId = GAME_INIT_ID_INVALID;
80 gameFrame = 0;
81 gameTimeResidual = 0;
82 gameTime = 0;
83 memset( userCmds, 0, sizeof( userCmds ) );
84 backgroundDownload.completed = true;
85 lastRconTime = 0;
86 showUpdateMessage = false;
87 lastFrameDelta = 0;
88
89 dlRequest = -1;
90 dlCount = -1;
91 memset( dlChecksums, 0, sizeof( int ) * MAX_PURE_PAKS );
92 currentDlSize = 0;
93 totalDlSize = 0;
94 }
95
96 /*
97 ==================
98 idAsyncClient::Shutdown
99 ==================
100 */
Shutdown(void)101 void idAsyncClient::Shutdown( void ) {
102 guiNetMenu = NULL;
103 updateMSG.Clear();
104 updateURL.Clear();
105 updateFile.Clear();
106 updateFallback.Clear();
107 backgroundDownload.url.url.Clear();
108 dlList.Clear();
109 }
110
111 /*
112 ==================
113 idAsyncClient::InitPort
114 ==================
115 */
InitPort(void)116 bool idAsyncClient::InitPort( void ) {
117 // if this is the first time we connect to a server, open the UDP port
118 if ( !clientPort.GetPort() ) {
119 if ( !clientPort.InitForPort( PORT_ANY ) ) {
120 common->Printf( "Couldn't open client network port.\n" );
121 return false;
122 }
123 }
124 // maintain it valid between connects and ui manager reloads
125 guiNetMenu = uiManager->FindGui( "guis/netmenu.gui", true, false, true );
126
127 return true;
128 }
129
130 /*
131 ==================
132 idAsyncClient::ClosePort
133 ==================
134 */
ClosePort(void)135 void idAsyncClient::ClosePort( void ) {
136 clientPort.Close();
137 }
138
139 /*
140 ==================
141 idAsyncClient::ClearPendingPackets
142 ==================
143 */
ClearPendingPackets(void)144 void idAsyncClient::ClearPendingPackets( void ) {
145 int size;
146 byte msgBuf[MAX_MESSAGE_SIZE];
147 netadr_t from;
148
149 while( clientPort.GetPacket( from, msgBuf, size, sizeof( msgBuf ) ) ) {
150 }
151 }
152
153 /*
154 ==================
155 idAsyncClient::HandleGuiCommandInternal
156 ==================
157 */
HandleGuiCommandInternal(const char * cmd)158 const char* idAsyncClient::HandleGuiCommandInternal( const char *cmd ) {
159 if ( !idStr::Cmp( cmd, "abort" ) || !idStr::Cmp( cmd, "pure_abort" ) ) {
160 common->DPrintf( "connection aborted\n" );
161 cmdSystem->BufferCommandText( CMD_EXEC_NOW, "disconnect" );
162 return "";
163 } else {
164 common->DWarning( "idAsyncClient::HandleGuiCommand: unknown cmd %s", cmd );
165 }
166 return NULL;
167 }
168
169 /*
170 ==================
171 idAsyncClient::HandleGuiCommand
172 ==================
173 */
HandleGuiCommand(const char * cmd)174 const char* idAsyncClient::HandleGuiCommand( const char *cmd ) {
175 return idAsyncNetwork::client.HandleGuiCommandInternal( cmd );
176 }
177
178 /*
179 ==================
180 idAsyncClient::ConnectToServer
181 ==================
182 */
ConnectToServer(const netadr_t adr)183 void idAsyncClient::ConnectToServer( const netadr_t adr ) {
184 // shutdown any current game. that includes network disconnect
185 session->Stop();
186
187 if ( !InitPort() ) {
188 return;
189 }
190
191 if ( cvarSystem->GetCVarBool( "net_serverDedicated" ) ) {
192 common->Printf( "Can't connect to a server as dedicated\n" );
193 return;
194 }
195
196 // trash any currently pending packets
197 ClearPendingPackets();
198
199 serverAddress = adr;
200
201 // clear the client state
202 Clear();
203
204 // get a pseudo random client id, but don't use the id which is reserved for connectionless packets
205 clientId = Sys_Milliseconds() & CONNECTIONLESS_MESSAGE_ID_MASK;
206
207 // calculate a checksum on some of the essential data used
208 clientDataChecksum = declManager->GetChecksum();
209
210 // start challenging the server
211 clientState = CS_CHALLENGING;
212
213 active = true;
214
215 guiNetMenu = uiManager->FindGui( "guis/netmenu.gui", true, false, true );
216 guiNetMenu->SetStateString( "status", va( common->GetLanguageDict()->GetString( "#str_06749" ), Sys_NetAdrToString( adr ) ) );
217 session->SetGUI( guiNetMenu, HandleGuiCommand );
218 }
219
220 /*
221 ==================
222 idAsyncClient::Reconnect
223 ==================
224 */
Reconnect(void)225 void idAsyncClient::Reconnect( void ) {
226 ConnectToServer( serverAddress );
227 }
228
229 /*
230 ==================
231 idAsyncClient::ConnectToServer
232 ==================
233 */
ConnectToServer(const char * address)234 void idAsyncClient::ConnectToServer( const char *address ) {
235 int serverNum;
236 netadr_t adr;
237
238 if ( idStr::IsNumeric( address ) ) {
239 serverNum = atoi( address );
240 if ( serverNum < 0 || serverNum >= serverList.Num() ) {
241 session->MessageBox( MSG_OK, va( common->GetLanguageDict()->GetString( "#str_06733" ), serverNum ), common->GetLanguageDict()->GetString( "#str_06735" ), true );
242 return;
243 }
244 adr = serverList[ serverNum ].adr;
245 } else {
246 if ( !Sys_StringToNetAdr( address, &adr, true ) ) {
247 session->MessageBox( MSG_OK, va( common->GetLanguageDict()->GetString( "#str_06734" ), address ), common->GetLanguageDict()->GetString( "#str_06735" ), true );
248 return;
249 }
250 }
251 if ( !adr.port ) {
252 adr.port = PORT_SERVER;
253 }
254
255 common->Printf( "\"%s\" resolved to %s\n", address, Sys_NetAdrToString( adr ) );
256
257 ConnectToServer( adr );
258 }
259
260 /*
261 ==================
262 idAsyncClient::DisconnectFromServer
263 ==================
264 */
DisconnectFromServer(void)265 void idAsyncClient::DisconnectFromServer( void ) {
266 idBitMsg msg;
267 byte msgBuf[MAX_MESSAGE_SIZE];
268
269 if ( clientState >= CS_CONNECTED ) {
270 // if we were actually connected, clear the pure list
271 fileSystem->ClearPureChecksums();
272
273 // send reliable disconnect to server
274 msg.Init( msgBuf, sizeof( msgBuf ) );
275 msg.WriteByte( CLIENT_RELIABLE_MESSAGE_DISCONNECT );
276 msg.WriteString( "disconnect" );
277
278 if ( !channel.SendReliableMessage( msg ) ) {
279 common->Error( "client->server reliable messages overflow\n" );
280 }
281
282 SendEmptyToServer( true );
283 SendEmptyToServer( true );
284 SendEmptyToServer( true );
285 }
286
287 if ( clientState != CS_PURERESTART ) {
288 channel.Shutdown();
289 clientState = CS_DISCONNECTED;
290 }
291
292 active = false;
293 }
294
295 /*
296 ==================
297 idAsyncClient::GetServerInfo
298 ==================
299 */
GetServerInfo(const netadr_t adr)300 void idAsyncClient::GetServerInfo( const netadr_t adr ) {
301 idBitMsg msg;
302 byte msgBuf[MAX_MESSAGE_SIZE];
303
304 if ( !InitPort() ) {
305 return;
306 }
307
308 msg.Init( msgBuf, sizeof( msgBuf ) );
309 msg.WriteShort( CONNECTIONLESS_MESSAGE_ID );
310 msg.WriteString( "getInfo" );
311 msg.WriteInt( serverList.GetChallenge() ); // challenge
312
313 clientPort.SendPacket( adr, msg.GetData(), msg.GetSize() );
314 }
315
316 /*
317 ==================
318 idAsyncClient::GetServerInfo
319 ==================
320 */
GetServerInfo(const char * address)321 void idAsyncClient::GetServerInfo( const char *address ) {
322 netadr_t adr;
323
324 if ( address && *address != '\0' ) {
325 if ( !Sys_StringToNetAdr( address, &adr, true ) ) {
326 common->Printf( "Couldn't get server address for \"%s\"\n", address );
327 return;
328 }
329 } else if ( active ) {
330 adr = serverAddress;
331 } else if ( idAsyncNetwork::server.IsActive() ) {
332 // used to be a Sys_StringToNetAdr( "localhost", &adr, true ); and send a packet over loopback
333 // but this breaks with net_ip ( typically, for multi-homed servers )
334 idAsyncNetwork::server.PrintLocalServerInfo();
335 return;
336 } else {
337 common->Printf( "no server found\n" );
338 return;
339 }
340
341 if ( !adr.port ) {
342 adr.port = PORT_SERVER;
343 }
344
345 GetServerInfo( adr );
346 }
347
348 /*
349 ==================
350 idAsyncClient::GetLANServers
351 ==================
352 */
GetLANServers(void)353 void idAsyncClient::GetLANServers( void ) {
354 int i;
355 idBitMsg msg;
356 byte msgBuf[MAX_MESSAGE_SIZE];
357 netadr_t broadcastAddress;
358
359 if ( !InitPort() ) {
360 return;
361 }
362
363 idAsyncNetwork::LANServer.SetBool( true );
364
365 serverList.SetupLANScan();
366
367 msg.Init( msgBuf, sizeof( msgBuf ) );
368 msg.WriteShort( CONNECTIONLESS_MESSAGE_ID );
369 msg.WriteString( "getInfo" );
370 msg.WriteInt( serverList.GetChallenge() );
371
372 broadcastAddress.type = NA_BROADCAST;
373 for ( i = 0; i < MAX_SERVER_PORTS; i++ ) {
374 broadcastAddress.port = PORT_SERVER + i;
375 clientPort.SendPacket( broadcastAddress, msg.GetData(), msg.GetSize() );
376 }
377 }
378
379 /*
380 ==================
381 idAsyncClient::GetNETServers
382 ==================
383 */
GetNETServers(void)384 void idAsyncClient::GetNETServers( void ) {
385 idBitMsg msg;
386 byte msgBuf[MAX_MESSAGE_SIZE];
387
388 idAsyncNetwork::LANServer.SetBool( false );
389
390 // NetScan only clears GUI and results, not the stored list
391 serverList.Clear( );
392 serverList.NetScan( );
393 serverList.StartServers( true );
394
395 msg.Init( msgBuf, sizeof( msgBuf ) );
396 msg.WriteShort( CONNECTIONLESS_MESSAGE_ID );
397 msg.WriteString( "getServers" );
398 msg.WriteInt( ASYNC_PROTOCOL_VERSION );
399 msg.WriteString( cvarSystem->GetCVarString( "fs_game" ) );
400 msg.WriteBits( cvarSystem->GetCVarInteger( "gui_filter_password" ), 2 );
401 msg.WriteBits( cvarSystem->GetCVarInteger( "gui_filter_players" ), 2 );
402 msg.WriteBits( cvarSystem->GetCVarInteger( "gui_filter_gameType" ), 2 );
403
404 netadr_t adr;
405 if ( idAsyncNetwork::GetMasterAddress( 0, adr ) ) {
406 clientPort.SendPacket( adr, msg.GetData(), msg.GetSize() );
407 }
408 }
409
410 /*
411 ==================
412 idAsyncClient::ListServers
413 ==================
414 */
ListServers(void)415 void idAsyncClient::ListServers( void ) {
416 int i;
417
418 for ( i = 0; i < serverList.Num(); i++ ) {
419 common->Printf( "%3d: %s %dms (%s)\n", i, serverList[i].serverInfo.GetString( "si_name" ), serverList[ i ].ping, Sys_NetAdrToString( serverList[i].adr ) );
420 }
421 }
422
423 /*
424 ==================
425 idAsyncClient::ClearServers
426 ==================
427 */
ClearServers(void)428 void idAsyncClient::ClearServers( void ) {
429 serverList.Clear();
430 }
431
432 /*
433 ==================
434 idAsyncClient::RemoteConsole
435 ==================
436 */
RemoteConsole(const char * command)437 void idAsyncClient::RemoteConsole( const char *command ) {
438 netadr_t adr;
439 idBitMsg msg;
440 byte msgBuf[MAX_MESSAGE_SIZE];
441
442 if ( !InitPort() ) {
443 return;
444 }
445
446 if ( active ) {
447 adr = serverAddress;
448 } else {
449 Sys_StringToNetAdr( idAsyncNetwork::clientRemoteConsoleAddress.GetString(), &adr, true );
450 }
451
452 if ( !adr.port ) {
453 adr.port = PORT_SERVER;
454 }
455
456 lastRconAddress = adr;
457 lastRconTime = realTime;
458
459 msg.Init( msgBuf, sizeof( msgBuf ) );
460 msg.WriteShort( CONNECTIONLESS_MESSAGE_ID );
461 msg.WriteString( "rcon" );
462 msg.WriteString( idAsyncNetwork::clientRemoteConsolePassword.GetString() );
463 msg.WriteString( command );
464
465 clientPort.SendPacket( adr, msg.GetData(), msg.GetSize() );
466 }
467
468 /*
469 ==================
470 idAsyncClient::GetPrediction
471 ==================
472 */
GetPrediction(void) const473 int idAsyncClient::GetPrediction( void ) const {
474 if ( clientState < CS_CONNECTED ) {
475 return -1;
476 } else {
477 return clientPrediction;
478 }
479 }
480
481 /*
482 ==================
483 idAsyncClient::GetTimeSinceLastPacket
484 ==================
485 */
GetTimeSinceLastPacket(void) const486 int idAsyncClient::GetTimeSinceLastPacket( void ) const {
487 if ( clientState < CS_CONNECTED ) {
488 return -1;
489 } else {
490 return clientTime - lastPacketTime;
491 }
492 }
493
494 /*
495 ==================
496 idAsyncClient::GetOutgoingRate
497 ==================
498 */
GetOutgoingRate(void) const499 int idAsyncClient::GetOutgoingRate( void ) const {
500 if ( clientState < CS_CONNECTED ) {
501 return -1;
502 } else {
503 return channel.GetOutgoingRate();
504 }
505 }
506
507 /*
508 ==================
509 idAsyncClient::GetIncomingRate
510 ==================
511 */
GetIncomingRate(void) const512 int idAsyncClient::GetIncomingRate( void ) const {
513 if ( clientState < CS_CONNECTED ) {
514 return -1;
515 } else {
516 return channel.GetIncomingRate();
517 }
518 }
519
520 /*
521 ==================
522 idAsyncClient::GetOutgoingCompression
523 ==================
524 */
GetOutgoingCompression(void) const525 float idAsyncClient::GetOutgoingCompression( void ) const {
526 if ( clientState < CS_CONNECTED ) {
527 return 0.0f;
528 } else {
529 return channel.GetOutgoingCompression();
530 }
531 }
532
533 /*
534 ==================
535 idAsyncClient::GetIncomingCompression
536 ==================
537 */
GetIncomingCompression(void) const538 float idAsyncClient::GetIncomingCompression( void ) const {
539 if ( clientState < CS_CONNECTED ) {
540 return 0.0f;
541 } else {
542 return channel.GetIncomingCompression();
543 }
544 }
545
546 /*
547 ==================
548 idAsyncClient::GetIncomingPacketLoss
549 ==================
550 */
GetIncomingPacketLoss(void) const551 float idAsyncClient::GetIncomingPacketLoss( void ) const {
552 if ( clientState < CS_CONNECTED ) {
553 return 0.0f;
554 } else {
555 return channel.GetIncomingPacketLoss();
556 }
557 }
558
559 /*
560 ==================
561 idAsyncClient::DuplicateUsercmds
562 ==================
563 */
DuplicateUsercmds(int frame,int time)564 void idAsyncClient::DuplicateUsercmds( int frame, int time ) {
565 int i, previousIndex, currentIndex;
566
567 previousIndex = ( frame - 1 ) & ( MAX_USERCMD_BACKUP - 1 );
568 currentIndex = frame & ( MAX_USERCMD_BACKUP - 1 );
569
570 // duplicate previous user commands if no new commands are available for a client
571 for ( i = 0; i < MAX_ASYNC_CLIENTS; i++ ) {
572 idAsyncNetwork::DuplicateUsercmd( userCmds[previousIndex][i], userCmds[currentIndex][i], frame, time );
573 }
574 }
575
576 /*
577 ==================
578 idAsyncClient::SendUserInfoToServer
579 ==================
580 */
SendUserInfoToServer(void)581 void idAsyncClient::SendUserInfoToServer( void ) {
582 idBitMsg msg;
583 byte msgBuf[MAX_MESSAGE_SIZE];
584 idDict info;
585
586 if ( clientState < CS_CONNECTED ) {
587 return;
588 }
589
590 info = *cvarSystem->MoveCVarsToDict( CVAR_USERINFO );
591
592 // send reliable client info to server
593 msg.Init( msgBuf, sizeof( msgBuf ) );
594 msg.WriteByte( CLIENT_RELIABLE_MESSAGE_CLIENTINFO );
595 msg.WriteDeltaDict( info, &sessLocal.mapSpawnData.userInfo[ clientNum ] );
596
597 if ( !channel.SendReliableMessage( msg ) ) {
598 common->Error( "client->server reliable messages overflow\n" );
599 }
600
601 sessLocal.mapSpawnData.userInfo[clientNum] = info;
602 }
603
604 /*
605 ==================
606 idAsyncClient::SendEmptyToServer
607 ==================
608 */
SendEmptyToServer(bool force,bool mapLoad)609 void idAsyncClient::SendEmptyToServer( bool force, bool mapLoad ) {
610 idBitMsg msg;
611 byte msgBuf[MAX_MESSAGE_SIZE];
612
613 if ( lastEmptyTime > realTime ) {
614 lastEmptyTime = realTime;
615 }
616
617 if ( !force && ( realTime - lastEmptyTime < EMPTY_RESEND_TIME ) ) {
618 return;
619 }
620
621 if ( idAsyncNetwork::verbose.GetInteger() ) {
622 common->Printf( "sending empty to server, gameInitId = %d\n", mapLoad ? GAME_INIT_ID_MAP_LOAD : gameInitId );
623 }
624
625 msg.Init( msgBuf, sizeof( msgBuf ) );
626 msg.WriteInt( serverMessageSequence );
627 msg.WriteInt( mapLoad ? GAME_INIT_ID_MAP_LOAD : gameInitId );
628 msg.WriteInt( snapshotSequence );
629 msg.WriteByte( CLIENT_UNRELIABLE_MESSAGE_EMPTY );
630
631 channel.SendMessage( clientPort, clientTime, msg );
632
633 while( channel.UnsentFragmentsLeft() ) {
634 channel.SendNextFragment( clientPort, clientTime );
635 }
636
637 lastEmptyTime = realTime;
638 }
639
640 /*
641 ==================
642 idAsyncClient::SendPingResponseToServer
643 ==================
644 */
SendPingResponseToServer(int time)645 void idAsyncClient::SendPingResponseToServer( int time ) {
646 idBitMsg msg;
647 byte msgBuf[MAX_MESSAGE_SIZE];
648
649 if ( idAsyncNetwork::verbose.GetInteger() == 2 ) {
650 common->Printf( "sending ping response to server, gameInitId = %d\n", gameInitId );
651 }
652
653 msg.Init( msgBuf, sizeof( msgBuf ) );
654 msg.WriteInt( serverMessageSequence );
655 msg.WriteInt( gameInitId );
656 msg.WriteInt( snapshotSequence );
657 msg.WriteByte( CLIENT_UNRELIABLE_MESSAGE_PINGRESPONSE );
658 msg.WriteInt( time );
659
660 channel.SendMessage( clientPort, clientTime, msg );
661 while( channel.UnsentFragmentsLeft() ) {
662 channel.SendNextFragment( clientPort, clientTime );
663 }
664 }
665
666 /*
667 ==================
668 idAsyncClient::SendUsercmdsToServer
669 ==================
670 */
SendUsercmdsToServer(void)671 void idAsyncClient::SendUsercmdsToServer( void ) {
672 int i, numUsercmds, index;
673 idBitMsg msg;
674 byte msgBuf[MAX_MESSAGE_SIZE];
675 usercmd_t * last;
676
677 if ( idAsyncNetwork::verbose.GetInteger() == 2 ) {
678 common->Printf( "sending usercmd to server: gameInitId = %d, gameFrame = %d, gameTime = %d\n", gameInitId, gameFrame, gameTime );
679 }
680
681 // generate user command for this client
682 index = gameFrame & ( MAX_USERCMD_BACKUP - 1 );
683 userCmds[index][clientNum] = usercmdGen->GetDirectUsercmd();
684 userCmds[index][clientNum].gameFrame = gameFrame;
685 userCmds[index][clientNum].gameTime = gameTime;
686
687 // send the user commands to the server
688 msg.Init( msgBuf, sizeof( msgBuf ) );
689 msg.WriteInt( serverMessageSequence );
690 msg.WriteInt( gameInitId );
691 msg.WriteInt( snapshotSequence );
692 msg.WriteByte( CLIENT_UNRELIABLE_MESSAGE_USERCMD );
693 msg.WriteShort( clientPrediction );
694
695 numUsercmds = idMath::ClampInt( 0, 10, idAsyncNetwork::clientUsercmdBackup.GetInteger() ) + 1;
696
697 // write the user commands
698 msg.WriteInt( gameFrame );
699 msg.WriteByte( numUsercmds );
700 for ( last = NULL, i = gameFrame - numUsercmds + 1; i <= gameFrame; i++ ) {
701 index = i & ( MAX_USERCMD_BACKUP - 1 );
702 idAsyncNetwork::WriteUserCmdDelta( msg, userCmds[index][clientNum], last );
703 last = &userCmds[index][clientNum];
704 }
705
706 channel.SendMessage( clientPort, clientTime, msg );
707 while( channel.UnsentFragmentsLeft() ) {
708 channel.SendNextFragment( clientPort, clientTime );
709 }
710 }
711
712 /*
713 ==================
714 idAsyncClient::InitGame
715 ==================
716 */
InitGame(int serverGameInitId,int serverGameFrame,int serverGameTime,const idDict & serverSI)717 void idAsyncClient::InitGame( int serverGameInitId, int serverGameFrame, int serverGameTime, const idDict &serverSI ) {
718 gameInitId = serverGameInitId;
719 gameFrame = snapshotGameFrame = serverGameFrame;
720 gameTime = snapshotGameTime = serverGameTime;
721 gameTimeResidual = 0;
722 memset( userCmds, 0, sizeof( userCmds ) );
723
724 for ( int i = 0; i < MAX_ASYNC_CLIENTS; i++ ) {
725 sessLocal.mapSpawnData.userInfo[ i ].Clear();
726 }
727
728 sessLocal.mapSpawnData.serverInfo = serverSI;
729 }
730
731 /*
732 ==================
733 idAsyncClient::ProcessUnreliableServerMessage
734 ==================
735 */
ProcessUnreliableServerMessage(const idBitMsg & msg)736 void idAsyncClient::ProcessUnreliableServerMessage( const idBitMsg &msg ) {
737 int i, j, index, id, numDuplicatedUsercmds, aheadOfServer, numUsercmds, delta;
738 int serverGameInitId, serverGameFrame, serverGameTime;
739 idDict serverSI;
740 usercmd_t *last;
741 bool pureWait;
742
743 serverGameInitId = msg.ReadInt();
744
745 id = msg.ReadByte();
746 switch( id ) {
747 case SERVER_UNRELIABLE_MESSAGE_EMPTY: {
748 if ( idAsyncNetwork::verbose.GetInteger() ) {
749 common->Printf( "received empty message from server\n" );
750 }
751 break;
752 }
753 case SERVER_UNRELIABLE_MESSAGE_PING: {
754 if ( idAsyncNetwork::verbose.GetInteger() == 2 ) {
755 common->Printf( "received ping message from server\n" );
756 }
757 SendPingResponseToServer( msg.ReadInt() );
758 break;
759 }
760 case SERVER_UNRELIABLE_MESSAGE_GAMEINIT: {
761 serverGameFrame = msg.ReadInt();
762 serverGameTime = msg.ReadInt();
763 msg.ReadDeltaDict( serverSI, NULL );
764 pureWait = serverSI.GetBool( "si_pure" );
765
766 InitGame( serverGameInitId, serverGameFrame, serverGameTime, serverSI );
767
768 channel.ResetRate();
769
770 if ( idAsyncNetwork::verbose.GetInteger() ) {
771 common->Printf( "received gameinit, gameInitId = %d, gameFrame = %d, gameTime = %d\n", gameInitId, gameFrame, gameTime );
772 }
773
774 // mute sound
775 soundSystem->SetMute( true );
776
777 // ensure chat icon goes away when the GUI is changed...
778 //cvarSystem->SetCVarBool( "ui_chat", false );
779
780 if ( pureWait ) {
781 guiNetMenu = uiManager->FindGui( "guis/netmenu.gui", true, false, true );
782 session->SetGUI( guiNetMenu, HandleGuiCommand );
783 session->MessageBox( MSG_ABORT, common->GetLanguageDict()->GetString ( "#str_04317" ), common->GetLanguageDict()->GetString ( "#str_04318" ), false, "pure_abort" );
784 } else {
785 // load map
786 session->SetGUI( NULL, NULL );
787 sessLocal.ExecuteMapChange();
788 }
789
790 break;
791 }
792 case SERVER_UNRELIABLE_MESSAGE_SNAPSHOT: {
793 // if the snapshot is from a different game
794 if ( serverGameInitId != gameInitId ) {
795 if ( idAsyncNetwork::verbose.GetInteger() ) {
796 common->Printf( "ignoring snapshot with != gameInitId\n" );
797 }
798 break;
799 }
800
801 snapshotSequence = msg.ReadInt();
802 snapshotGameFrame = msg.ReadInt();
803 snapshotGameTime = msg.ReadInt();
804 numDuplicatedUsercmds = msg.ReadByte();
805 aheadOfServer = msg.ReadShort();
806
807 // read the game snapshot
808 game->ClientReadSnapshot( clientNum, snapshotSequence, snapshotGameFrame, snapshotGameTime, numDuplicatedUsercmds, aheadOfServer, msg );
809
810 // read user commands of other clients from the snapshot
811 for ( last = NULL, i = msg.ReadByte(); i < MAX_ASYNC_CLIENTS; i = msg.ReadByte() ) {
812 numUsercmds = msg.ReadByte();
813 if ( numUsercmds > MAX_USERCMD_RELAY ) {
814 common->Error( "snapshot %d contains too many user commands for client %d", snapshotSequence, i );
815 break;
816 }
817 for ( j = 0; j < numUsercmds; j++ ) {
818 index = ( snapshotGameFrame + j ) & ( MAX_USERCMD_BACKUP - 1 );
819 idAsyncNetwork::ReadUserCmdDelta( msg, userCmds[index][i], last );
820 userCmds[index][i].gameFrame = snapshotGameFrame + j;
821 userCmds[index][i].duplicateCount = 0;
822 last = &userCmds[index][i];
823 }
824 // clear all user commands after the ones just read from the snapshot
825 for ( j = numUsercmds; j < MAX_USERCMD_BACKUP; j++ ) {
826 index = ( snapshotGameFrame + j ) & ( MAX_USERCMD_BACKUP - 1 );
827 userCmds[index][i].gameFrame = 0;
828 userCmds[index][i].gameTime = 0;
829 }
830 }
831
832 // if this is the first snapshot after a game init was received
833 if ( clientState == CS_CONNECTED ) {
834 gameTimeResidual = 0;
835 clientState = CS_INGAME;
836 assert( !sessLocal.GetActiveMenu( ) );
837 if ( idAsyncNetwork::verbose.GetInteger() ) {
838 common->Printf( "received first snapshot, gameInitId = %d, gameFrame %d gameTime %d\n", gameInitId, snapshotGameFrame, snapshotGameTime );
839 }
840 }
841
842 // if the snapshot is newer than the clients current game time
843 if ( gameTime < snapshotGameTime || gameTime > snapshotGameTime + idAsyncNetwork::clientMaxPrediction.GetInteger() ) {
844 gameFrame = snapshotGameFrame;
845 gameTime = snapshotGameTime;
846 gameTimeResidual = idMath::ClampInt( -idAsyncNetwork::clientMaxPrediction.GetInteger(), idAsyncNetwork::clientMaxPrediction.GetInteger(), gameTimeResidual );
847 clientPredictTime = idMath::ClampInt( -idAsyncNetwork::clientMaxPrediction.GetInteger(), idAsyncNetwork::clientMaxPrediction.GetInteger(), clientPredictTime );
848 }
849
850 // adjust the client prediction time based on the snapshot time
851 clientPrediction -= ( 1 - ( INTSIGNBITSET( aheadOfServer - idAsyncNetwork::clientPrediction.GetInteger() ) << 1 ) );
852 clientPrediction = idMath::ClampInt( idAsyncNetwork::clientPrediction.GetInteger(), idAsyncNetwork::clientMaxPrediction.GetInteger(), clientPrediction );
853 delta = gameTime - ( snapshotGameTime + clientPrediction );
854 clientPredictTime -= ( delta / PREDICTION_FAST_ADJUST ) + ( 1 - ( INTSIGNBITSET( delta ) << 1 ) );
855
856 lastSnapshotTime = clientTime;
857
858 if ( idAsyncNetwork::verbose.GetInteger() == 2 ) {
859 common->Printf( "received snapshot, gameInitId = %d, gameFrame = %d, gameTime = %d\n", gameInitId, gameFrame, gameTime );
860 }
861
862 if ( numDuplicatedUsercmds && ( idAsyncNetwork::verbose.GetInteger() == 2 ) ) {
863 common->Printf( "server duplicated %d user commands before snapshot %d\n", numDuplicatedUsercmds, snapshotGameFrame );
864 }
865 break;
866 }
867 default: {
868 common->Printf( "unknown unreliable server message %d\n", id );
869 break;
870 }
871 }
872 }
873
874 /*
875 ==================
876 idAsyncClient::ProcessReliableMessagePure
877 ==================
878 */
ProcessReliableMessagePure(const idBitMsg & msg)879 void idAsyncClient::ProcessReliableMessagePure( const idBitMsg &msg ) {
880 idBitMsg outMsg;
881 byte msgBuf[ MAX_MESSAGE_SIZE ];
882 int inChecksums[ MAX_PURE_PAKS ];
883 int i;
884 int serverGameInitId;
885
886 session->SetGUI( NULL, NULL );
887
888 serverGameInitId = msg.ReadInt();
889
890 if ( serverGameInitId != gameInitId ) {
891 common->DPrintf( "ignoring pure server checksum from an outdated gameInitId (%d)\n", serverGameInitId );
892 return;
893 }
894
895 if ( !ValidatePureServerChecksums( serverAddress, msg ) ) {
896
897 return;
898 }
899
900 if ( idAsyncNetwork::verbose.GetInteger() ) {
901 common->Printf( "received new pure server info. ExecuteMapChange and report back\n" );
902 }
903
904 // it is now ok to load the next map with updated pure checksums
905 sessLocal.ExecuteMapChange( true );
906
907 // upon receiving our pure list, the server will send us SCS_INGAME and we'll start getting snapshots
908 fileSystem->GetPureServerChecksums( inChecksums );
909 outMsg.Init( msgBuf, sizeof( msgBuf ) );
910 outMsg.WriteByte( CLIENT_RELIABLE_MESSAGE_PURE );
911
912 outMsg.WriteInt( gameInitId );
913
914 i = 0;
915 while ( inChecksums[ i ] ) {
916 outMsg.WriteInt( inChecksums[ i++ ] );
917 }
918 outMsg.WriteInt( 0 );
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 */
ReadLocalizedServerString(const idBitMsg & msg,char * out,int maxLen)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 */
ProcessReliableServerMessages(void)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.ReadInt();
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.ReadInt( );
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.ReadInt();
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 */
ProcessChallengeResponseMessage(const netadr_t from,const idBitMsg & msg)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.ReadInt();
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 */
ProcessConnectResponseMessage(const netadr_t from,const idBitMsg & msg)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.ReadInt();
1127 clientState = CS_CONNECTED;
1128 lastPacketTime = -9999;
1129
1130 serverGameInitId = msg.ReadInt();
1131 serverGameFrame = msg.ReadInt();
1132 serverGameTime = msg.ReadInt();
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 */
ProcessDisconnectMessage(const netadr_t from,const idBitMsg & msg)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 */
ProcessInfoResponseMessage(const netadr_t from,const idBitMsg & msg)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.ReadInt(); // challenge
1180 protocol = msg.ReadInt();
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.ReadInt();
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 index = serverList.InfoResponse( serverInfo );
1201
1202 common->Printf( "%d: server %s - protocol %d.%d - %s\n", index, Sys_NetAdrToString( serverInfo.adr ), protocol >> 16, protocol & 0xffff, serverInfo.serverInfo.GetString( "si_name" ) );
1203 }
1204
1205 /*
1206 ==================
1207 idAsyncClient::ProcessPrintMessage
1208 ==================
1209 */
ProcessPrintMessage(const netadr_t from,const idBitMsg & msg)1210 void idAsyncClient::ProcessPrintMessage( const netadr_t from, const idBitMsg &msg ) {
1211 char string[ MAX_STRING_CHARS ];
1212 int opcode;
1213 int game_opcode = ALLOW_YES;
1214 const char *retpass;
1215
1216 opcode = msg.ReadInt();
1217 if ( opcode == SERVER_PRINT_GAMEDENY ) {
1218 game_opcode = msg.ReadInt();
1219 }
1220 ReadLocalizedServerString( msg, string, MAX_STRING_CHARS );
1221 common->Printf( "%s\n", string );
1222 guiNetMenu->SetStateString( "status", string );
1223 if ( opcode == SERVER_PRINT_GAMEDENY ) {
1224 if ( game_opcode == ALLOW_BADPASS ) {
1225 retpass = session->MessageBox( MSG_PROMPT, common->GetLanguageDict()->GetString ( "#str_04321" ), string, true, "passprompt_ok" );
1226 ClearPendingPackets();
1227 guiNetMenu->SetStateString( "status", common->GetLanguageDict()->GetString ( "#str_04322" ));
1228 if ( retpass ) {
1229 // #790
1230 cvarSystem->SetCVarString( "password", "" );
1231 cvarSystem->SetCVarString( "password", retpass );
1232 } else {
1233 cmdSystem->BufferCommandText( CMD_EXEC_NOW, "disconnect" );
1234 }
1235 } else if ( game_opcode == ALLOW_NO ) {
1236 session->MessageBox( MSG_OK, string, common->GetLanguageDict()->GetString ( "#str_04323" ), true );
1237 ClearPendingPackets();
1238 cmdSystem->BufferCommandText( CMD_EXEC_NOW, "disconnect" );
1239 }
1240 // ALLOW_NOTYET just keeps running as usual. The GUI has an abort button
1241 } else if ( opcode == SERVER_PRINT_BADCHALLENGE && clientState >= CS_CONNECTING ) {
1242 cmdSystem->BufferCommandText( CMD_EXEC_NOW, "reconnect" );
1243 }
1244 }
1245
1246 /*
1247 ==================
1248 idAsyncClient::ProcessServersListMessage
1249 ==================
1250 */
ProcessServersListMessage(const netadr_t from,const idBitMsg & msg)1251 void idAsyncClient::ProcessServersListMessage( const netadr_t from, const idBitMsg &msg ) {
1252 if ( !Sys_CompareNetAdrBase( idAsyncNetwork::GetMasterAddress(), from ) ) {
1253 common->DPrintf( "received a server list from %s - not a valid master\n", Sys_NetAdrToString( from ) );
1254 return;
1255 }
1256 while ( msg.GetRemaingData() ) {
1257 int a,b,c,d;
1258 a = msg.ReadByte(); b = msg.ReadByte(); c = msg.ReadByte(); d = msg.ReadByte();
1259 serverList.AddServer( serverList.Num(), va( "%i.%i.%i.%i:%i", a, b, c, d, msg.ReadShort() ) );
1260 }
1261 }
1262
1263 /*
1264 ==================
1265 idAsyncClient::ProcessAuthKeyMessage
1266 ==================
1267 */
ProcessAuthKeyMessage(const netadr_t from,const idBitMsg & msg)1268 void idAsyncClient::ProcessAuthKeyMessage( const netadr_t from, const idBitMsg &msg ) {
1269 authKeyMsg_t authMsg;
1270 char read_string[ MAX_STRING_CHARS ];
1271 const char *retkey;
1272 authBadKeyStatus_t authBadStatus;
1273 int key_index;
1274 bool valid[ 2 ];
1275 idStr auth_msg;
1276
1277 if ( clientState != CS_CONNECTING && !session->WaitingForGameAuth() ) {
1278 common->Printf( "clientState != CS_CONNECTING, not waiting for game auth, authKey ignored\n" );
1279 return;
1280 }
1281
1282 authMsg = (authKeyMsg_t)msg.ReadByte();
1283 if ( authMsg == AUTHKEY_BADKEY ) {
1284 valid[ 0 ] = valid[ 1 ] = true;
1285 key_index = 0;
1286 authBadStatus = (authBadKeyStatus_t)msg.ReadByte();
1287 switch ( authBadStatus ) {
1288 case AUTHKEY_BAD_INVALID:
1289 valid[ 0 ] = ( msg.ReadByte() == 1 );
1290 valid[ 1 ] = ( msg.ReadByte() == 1 );
1291 idAsyncNetwork::BuildInvalidKeyMsg( auth_msg, valid );
1292 break;
1293 case AUTHKEY_BAD_BANNED:
1294 key_index = msg.ReadByte();
1295 auth_msg = common->GetLanguageDict()->GetString( va( "#str_0719%1d", 6 + key_index ) );
1296 auth_msg += "\n";
1297 auth_msg += common->GetLanguageDict()->GetString( "#str_04304" );
1298 valid[ key_index ] = false;
1299 break;
1300 case AUTHKEY_BAD_INUSE:
1301 key_index = msg.ReadByte();
1302 auth_msg = common->GetLanguageDict()->GetString( va( "#str_0719%1d", 8 + key_index ) );
1303 auth_msg += "\n";
1304 auth_msg += common->GetLanguageDict()->GetString( "#str_04304" );
1305 valid[ key_index ] = false;
1306 break;
1307 case AUTHKEY_BAD_MSG:
1308 // a general message explaining why this key is denied
1309 // no specific use for this atm. let's not clear the keys either
1310 msg.ReadString( read_string, MAX_STRING_CHARS );
1311 auth_msg = read_string;
1312 break;
1313 }
1314 common->DPrintf( "auth deny: %s\n", auth_msg.c_str() );
1315
1316 // keys to be cleared. applies to both net connect and game auth
1317 session->ClearCDKey( valid );
1318
1319 // get rid of the bad key - at least that's gonna annoy people who stole a fake key
1320 if ( clientState == CS_CONNECTING ) {
1321 while ( 1 ) {
1322 // here we use the auth status message
1323 retkey = session->MessageBox( MSG_CDKEY, auth_msg, common->GetLanguageDict()->GetString( "#str_04325" ), true );
1324 if ( retkey ) {
1325 if ( session->CheckKey( retkey, true, valid ) ) {
1326 cmdSystem->BufferCommandText( CMD_EXEC_NOW, "reconnect" );
1327 } else {
1328 // build a more precise message about the offline check failure
1329 idAsyncNetwork::BuildInvalidKeyMsg( auth_msg, valid );
1330 session->MessageBox( MSG_OK, auth_msg.c_str(), common->GetLanguageDict()->GetString( "#str_04327" ), true );
1331 continue;
1332 }
1333 } else {
1334 cmdSystem->BufferCommandText( CMD_EXEC_NOW, "disconnect" );
1335 }
1336 break;
1337 }
1338 } else {
1339 // forward the auth status information to the session code
1340 session->CDKeysAuthReply( false, auth_msg );
1341 }
1342 } else {
1343 msg.ReadString( read_string, MAX_STRING_CHARS );
1344 cvarSystem->SetCVarString( "com_guid", read_string );
1345 common->Printf( "guid set to %s\n", read_string );
1346 session->CDKeysAuthReply( true, NULL );
1347 }
1348 }
1349
1350 /*
1351 ==================
1352 idAsyncClient::ProcessVersionMessage
1353 ==================
1354 */
ProcessVersionMessage(const netadr_t from,const idBitMsg & msg)1355 void idAsyncClient::ProcessVersionMessage( const netadr_t from, const idBitMsg &msg ) {
1356 char string[ MAX_STRING_CHARS ];
1357
1358 if ( updateState != UPDATE_SENT ) {
1359 common->Printf( "ProcessVersionMessage: version reply, != UPDATE_SENT\n" );
1360 return;
1361 }
1362
1363 common->Printf( "A new version is available\n" );
1364 msg.ReadString( string, MAX_STRING_CHARS );
1365 updateMSG = string;
1366 updateDirectDownload = ( msg.ReadByte() != 0 );
1367 msg.ReadString( string, MAX_STRING_CHARS );
1368 updateURL = string;
1369 updateMime = (dlMime_t)msg.ReadByte();
1370 msg.ReadString( string, MAX_STRING_CHARS );
1371 updateFallback = string;
1372 updateState = UPDATE_READY;
1373 }
1374
1375 /*
1376 ==================
1377 idAsyncClient::ValidatePureServerChecksums
1378 ==================
1379 */
ValidatePureServerChecksums(const netadr_t from,const idBitMsg & msg)1380 bool idAsyncClient::ValidatePureServerChecksums( const netadr_t from, const idBitMsg &msg ) {
1381 int i, numChecksums, numMissingChecksums;
1382 int inChecksums[ MAX_PURE_PAKS ];
1383 int missingChecksums[ MAX_PURE_PAKS ];
1384 idBitMsg dlmsg;
1385 byte msgBuf[MAX_MESSAGE_SIZE];
1386
1387 // read checksums
1388 // pak checksums, in a 0-terminated list
1389 numChecksums = 0;
1390 do {
1391 i = msg.ReadInt( );
1392 inChecksums[ numChecksums++ ] = i;
1393 // just to make sure a broken message doesn't crash us
1394 if ( numChecksums >= MAX_PURE_PAKS ) {
1395 common->Warning( "MAX_PURE_PAKS ( %d ) exceeded in idAsyncClient::ProcessPureMessage\n", MAX_PURE_PAKS );
1396 return false;
1397 }
1398 } while ( i );
1399 inChecksums[ numChecksums ] = 0;
1400
1401 fsPureReply_t reply = fileSystem->SetPureServerChecksums( inChecksums, missingChecksums );
1402 switch ( reply ) {
1403 case PURE_RESTART:
1404 // need to restart the filesystem with a different pure configuration
1405 cmdSystem->BufferCommandText( CMD_EXEC_NOW, "disconnect" );
1406 // restart with the right FS configuration and get back to the server
1407 clientState = CS_PURERESTART;
1408 fileSystem->SetRestartChecksums( inChecksums );
1409 cmdSystem->BufferCommandText( CMD_EXEC_NOW, "reloadEngine" );
1410 return false;
1411 case PURE_MISSING: {
1412
1413 idStr checksums;
1414
1415 i = 0;
1416 while ( missingChecksums[ i ] ) {
1417 checksums += va( "0x%x ", missingChecksums[ i++ ] );
1418 }
1419 numMissingChecksums = i;
1420
1421 if ( idAsyncNetwork::clientDownload.GetInteger() == 0 ) {
1422 // never any downloads
1423 idStr message = va( common->GetLanguageDict()->GetString( "#str_07210" ), Sys_NetAdrToString( from ) );
1424
1425 if ( numMissingChecksums > 0 ) {
1426 message += va( common->GetLanguageDict()->GetString( "#str_06751" ), numMissingChecksums, checksums.c_str() );
1427 }
1428
1429 common->Printf( message );
1430 cmdSystem->BufferCommandText( CMD_EXEC_NOW, "disconnect" );
1431 session->MessageBox( MSG_OK, message, common->GetLanguageDict()->GetString( "#str_06735" ), true );
1432 } else {
1433 if ( clientState >= CS_CONNECTED ) {
1434 // we are already connected, reconnect to negociate the paks in connectionless mode
1435 cmdSystem->BufferCommandText( CMD_EXEC_NOW, "reconnect" );
1436 return false;
1437 }
1438 // ask the server to send back download info
1439 common->DPrintf( "missing %d paks: %s\n", numMissingChecksums, checksums.c_str() );
1440 // store the requested downloads
1441 GetDownloadRequest( missingChecksums, numMissingChecksums );
1442 // build the download request message
1443 // NOTE: in a specific function?
1444 dlmsg.Init( msgBuf, sizeof( msgBuf ) );
1445 dlmsg.WriteShort( CONNECTIONLESS_MESSAGE_ID );
1446 dlmsg.WriteString( "downloadRequest" );
1447 dlmsg.WriteInt( serverChallenge );
1448 dlmsg.WriteShort( clientId );
1449 // used to make sure the server replies to the same download request
1450 dlmsg.WriteInt( dlRequest );
1451 // special case the code pak - if we have a 0 checksum then we don't need to download it
1452 // 0-terminated list of missing paks
1453 i = 0;
1454 while ( missingChecksums[ i ] ) {
1455 dlmsg.WriteInt( missingChecksums[ i++ ] );
1456 }
1457 dlmsg.WriteInt( 0 );
1458 clientPort.SendPacket( from, dlmsg.GetData(), dlmsg.GetSize() );
1459 }
1460
1461 return false;
1462 }
1463 default:
1464 break;
1465 }
1466
1467 return true;
1468 }
1469
1470 /*
1471 ==================
1472 idAsyncClient::ProcessPureMessage
1473 ==================
1474 */
ProcessPureMessage(const netadr_t from,const idBitMsg & msg)1475 void idAsyncClient::ProcessPureMessage( const netadr_t from, const idBitMsg &msg ) {
1476 idBitMsg outMsg;
1477 byte msgBuf[ MAX_MESSAGE_SIZE ];
1478 int i;
1479 int inChecksums[ MAX_PURE_PAKS ];
1480
1481 if ( clientState != CS_CONNECTING ) {
1482 common->Printf( "clientState != CS_CONNECTING, pure msg ignored\n" );
1483 return;
1484 }
1485
1486 if ( !ValidatePureServerChecksums( from, msg ) ) {
1487 return;
1488 }
1489
1490 fileSystem->GetPureServerChecksums( inChecksums );
1491 outMsg.Init( msgBuf, sizeof( msgBuf ) );
1492 outMsg.WriteShort( CONNECTIONLESS_MESSAGE_ID );
1493 outMsg.WriteString( "pureClient" );
1494 outMsg.WriteInt( serverChallenge );
1495 outMsg.WriteShort( clientId );
1496 i = 0;
1497 while ( inChecksums[ i ] ) {
1498 outMsg.WriteInt( inChecksums[ i++ ] );
1499 }
1500 outMsg.WriteInt( 0 );
1501
1502 clientPort.SendPacket( from, outMsg.GetData(), outMsg.GetSize() );
1503 }
1504
1505 /*
1506 ==================
1507 idAsyncClient::ConnectionlessMessage
1508 ==================
1509 */
ConnectionlessMessage(const netadr_t from,const idBitMsg & msg)1510 void idAsyncClient::ConnectionlessMessage( const netadr_t from, const idBitMsg &msg ) {
1511 char string[MAX_STRING_CHARS*2]; // M. Quinn - Even Balance - PB packets can go beyond 1024
1512
1513 msg.ReadString( string, sizeof( string ) );
1514
1515 // info response from a server, are accepted from any source
1516 if ( idStr::Icmp( string, "infoResponse" ) == 0 ) {
1517 ProcessInfoResponseMessage( from, msg );
1518 return;
1519 }
1520
1521 // from master server:
1522 if ( Sys_CompareNetAdrBase( from, idAsyncNetwork::GetMasterAddress( ) ) ) {
1523 // server list
1524 if ( idStr::Icmp( string, "servers" ) == 0 ) {
1525 ProcessServersListMessage( from, msg );
1526 return;
1527 }
1528
1529 if ( idStr::Icmp( string, "authKey" ) == 0 ) {
1530 ProcessAuthKeyMessage( from, msg );
1531 return;
1532 }
1533
1534 if ( idStr::Icmp( string, "newVersion" ) == 0 ) {
1535 ProcessVersionMessage( from, msg );
1536 return;
1537 }
1538 }
1539
1540 // ignore if not from the current/last server
1541 if ( !Sys_CompareNetAdrBase( from, serverAddress ) && ( lastRconTime + 10000 < realTime || !Sys_CompareNetAdrBase( from, lastRconAddress ) ) ) {
1542 common->DPrintf( "got message '%s' from bad source: %s\n", string, Sys_NetAdrToString( from ) );
1543 return;
1544 }
1545
1546 // challenge response from the server we are connecting to
1547 if ( idStr::Icmp( string, "challengeResponse" ) == 0 ) {
1548 ProcessChallengeResponseMessage( from, msg );
1549 return;
1550 }
1551
1552 // connect response from the server we are connecting to
1553 if ( idStr::Icmp( string, "connectResponse" ) == 0 ) {
1554 ProcessConnectResponseMessage( from, msg );
1555 return;
1556 }
1557
1558 // a disconnect message from the server, which will happen if the server
1559 // dropped the connection but is still getting packets from this client
1560 if ( idStr::Icmp( string, "disconnect" ) == 0 ) {
1561 ProcessDisconnectMessage( from, msg );
1562 return;
1563 }
1564
1565 // print request from server
1566 if ( idStr::Icmp( string, "print" ) == 0 ) {
1567 ProcessPrintMessage( from, msg );
1568 return;
1569 }
1570
1571 // server pure list
1572 if ( idStr::Icmp( string, "pureServer" ) == 0 ) {
1573 ProcessPureMessage( from, msg );
1574 return;
1575 }
1576
1577 if ( idStr::Icmp( string, "downloadInfo" ) == 0 ) {
1578 ProcessDownloadInfoMessage( from, msg );
1579 }
1580
1581 if ( idStr::Icmp( string, "authrequired" ) == 0 ) {
1582 // server telling us that he's expecting an auth mode connect, just in case we're trying to connect in LAN mode
1583 if ( idAsyncNetwork::LANServer.GetBool() ) {
1584 common->Warning( "server %s requests master authorization for this client. Turning off LAN mode\n", Sys_NetAdrToString( from ) );
1585 idAsyncNetwork::LANServer.SetBool( false );
1586 }
1587 }
1588
1589 common->DPrintf( "ignored message from %s: %s\n", Sys_NetAdrToString( from ), string );
1590 }
1591
1592 /*
1593 =================
1594 idAsyncClient::ProcessMessage
1595 =================
1596 */
ProcessMessage(const netadr_t from,idBitMsg & msg)1597 void idAsyncClient::ProcessMessage( const netadr_t from, idBitMsg &msg ) {
1598 int id;
1599
1600 id = msg.ReadShort();
1601
1602 // check for a connectionless packet
1603 if ( id == CONNECTIONLESS_MESSAGE_ID ) {
1604 ConnectionlessMessage( from, msg );
1605 return;
1606 }
1607
1608 if ( clientState < CS_CONNECTED ) {
1609 return; // can't be a valid sequenced packet
1610 }
1611
1612 if ( msg.GetRemaingData() < 4 ) {
1613 common->DPrintf( "%s: tiny packet\n", Sys_NetAdrToString( from ) );
1614 return;
1615 }
1616
1617 // is this a packet from the server
1618 if ( !Sys_CompareNetAdrBase( from, channel.GetRemoteAddress() ) || id != serverId ) {
1619 common->DPrintf( "%s: sequenced server packet without connection\n", Sys_NetAdrToString( from ) );
1620 return;
1621 }
1622
1623 if ( !channel.Process( from, clientTime, msg, serverMessageSequence ) ) {
1624 return; // out of order, duplicated, fragment, etc.
1625 }
1626
1627 lastPacketTime = clientTime;
1628 ProcessReliableServerMessages();
1629 ProcessUnreliableServerMessage( msg );
1630 }
1631
1632 /*
1633 ==================
1634 idAsyncClient::SetupConnection
1635 ==================
1636 */
SetupConnection(void)1637 void idAsyncClient::SetupConnection( void ) {
1638 idBitMsg msg;
1639 byte msgBuf[MAX_MESSAGE_SIZE];
1640
1641 if ( clientTime - lastConnectTime < SETUP_CONNECTION_RESEND_TIME ) {
1642 return;
1643 }
1644
1645 if ( clientState == CS_CHALLENGING ) {
1646 common->Printf( "sending challenge to %s\n", Sys_NetAdrToString( serverAddress ) );
1647 msg.Init( msgBuf, sizeof( msgBuf ) );
1648 msg.WriteShort( CONNECTIONLESS_MESSAGE_ID );
1649 msg.WriteString( "challenge" );
1650 msg.WriteInt( clientId );
1651 clientPort.SendPacket( serverAddress, msg.GetData(), msg.GetSize() );
1652 } else if ( clientState == CS_CONNECTING ) {
1653 common->Printf( "sending connect to %s with challenge 0x%x\n", Sys_NetAdrToString( serverAddress ), serverChallenge );
1654 msg.Init( msgBuf, sizeof( msgBuf ) );
1655 msg.WriteShort( CONNECTIONLESS_MESSAGE_ID );
1656 msg.WriteString( "connect" );
1657 msg.WriteInt( ASYNC_PROTOCOL_VERSION );
1658 msg.WriteInt( clientDataChecksum );
1659 msg.WriteInt( serverChallenge );
1660 msg.WriteShort( clientId );
1661 msg.WriteInt( cvarSystem->GetCVarInteger( "net_clientMaxRate" ) );
1662 msg.WriteString( cvarSystem->GetCVarString( "com_guid" ) );
1663 msg.WriteString( cvarSystem->GetCVarString( "password" ), -1, false );
1664 // do not make the protocol depend on PB
1665 msg.WriteShort( 0 );
1666 clientPort.SendPacket( serverAddress, msg.GetData(), msg.GetSize() );
1667 #if ID_ENFORCE_KEY_CLIENT
1668 if ( idAsyncNetwork::LANServer.GetBool() ) {
1669 common->Printf( "net_LANServer is set, connecting in LAN mode\n" );
1670 } else {
1671 // emit a cd key authorization request
1672 // modified at protocol 1.37 for XP key addition
1673 msg.BeginWriting();
1674 msg.WriteShort( CONNECTIONLESS_MESSAGE_ID );
1675 msg.WriteString( "clAuth" );
1676 msg.WriteInt( ASYNC_PROTOCOL_VERSION );
1677 msg.WriteNetadr( serverAddress );
1678 // if we don't have a com_guid, this will request a direct reply from auth with it
1679 msg.WriteByte( cvarSystem->GetCVarString( "com_guid" )[0] ? 1 : 0 );
1680 // send the main key, and flag an extra byte to add XP key
1681 msg.WriteString( session->GetCDKey( false ) );
1682 const char *xpkey = session->GetCDKey( true );
1683 msg.WriteByte( xpkey ? 1 : 0 );
1684 if ( xpkey ) {
1685 msg.WriteString( xpkey );
1686 }
1687 clientPort.SendPacket( idAsyncNetwork::GetMasterAddress(), msg.GetData(), msg.GetSize() );
1688 }
1689 #else
1690 if (! Sys_IsLANAddress( serverAddress ) ) {
1691 common->Printf( "Build Does not have CD Key Enforcement enabled. The Server ( %s ) is not within the lan addresses. Attemting to connect.\n", Sys_NetAdrToString( serverAddress ) );
1692 }
1693 common->Printf( "Not Testing key.\n" );
1694 #endif
1695 } else {
1696 return;
1697 }
1698
1699 lastConnectTime = clientTime;
1700 }
1701
1702 /*
1703 ==================
1704 idAsyncClient::SendReliableGameMessage
1705 ==================
1706 */
SendReliableGameMessage(const idBitMsg & msg)1707 void idAsyncClient::SendReliableGameMessage( const idBitMsg &msg ) {
1708 idBitMsg outMsg;
1709 byte msgBuf[MAX_MESSAGE_SIZE];
1710
1711 if ( clientState < CS_INGAME ) {
1712 return;
1713 }
1714
1715 outMsg.Init( msgBuf, sizeof( msgBuf ) );
1716 outMsg.WriteByte( CLIENT_RELIABLE_MESSAGE_GAME );
1717 outMsg.WriteData( msg.GetData(), msg.GetSize() );
1718 if ( !channel.SendReliableMessage( outMsg ) ) {
1719 common->Error( "client->server reliable messages overflow\n" );
1720 }
1721 }
1722
1723 /*
1724 ==================
1725 idAsyncClient::Idle
1726 ==================
1727 */
Idle(void)1728 void idAsyncClient::Idle( void ) {
1729 // also need to read mouse for the connecting guis
1730 usercmdGen->GetDirectUsercmd();
1731
1732 SendEmptyToServer();
1733 }
1734
1735 /*
1736 ==================
1737 idAsyncClient::UpdateTime
1738 ==================
1739 */
UpdateTime(int clamp)1740 int idAsyncClient::UpdateTime( int clamp ) {
1741 int time, msec;
1742
1743 time = Sys_Milliseconds();
1744 msec = idMath::ClampInt( 0, clamp, time - realTime );
1745 realTime = time;
1746 clientTime += msec;
1747 return msec;
1748 }
1749
1750 /*
1751 ==================
1752 idAsyncClient::RunFrame
1753 ==================
1754 */
RunFrame(void)1755 void idAsyncClient::RunFrame( void ) {
1756 int msec, size;
1757 bool newPacket;
1758 idBitMsg msg;
1759 byte msgBuf[MAX_MESSAGE_SIZE];
1760 netadr_t from;
1761
1762 msec = UpdateTime( 100 );
1763
1764 if ( !clientPort.GetPort() ) {
1765 return;
1766 }
1767
1768 // handle ongoing pk4 downloads and patch downloads
1769 HandleDownloads();
1770
1771 gameTimeResidual += msec;
1772
1773 // spin in place processing incoming packets until enough time lapsed to run a new game frame
1774 do {
1775
1776 do {
1777
1778 // blocking read with game time residual timeout
1779 newPacket = clientPort.GetPacketBlocking( from, msgBuf, size, sizeof( msgBuf ), USERCMD_MSEC - ( gameTimeResidual + clientPredictTime ) - 1 );
1780 if ( newPacket ) {
1781 msg.Init( msgBuf, sizeof( msgBuf ) );
1782 msg.SetSize( size );
1783 msg.BeginReading();
1784 ProcessMessage( from, msg );
1785 }
1786
1787 msec = UpdateTime( 100 );
1788 gameTimeResidual += msec;
1789
1790 } while( newPacket );
1791
1792 } while( gameTimeResidual + clientPredictTime < USERCMD_MSEC );
1793
1794 // update server list
1795 serverList.RunFrame();
1796
1797 if ( clientState == CS_DISCONNECTED ) {
1798 usercmdGen->GetDirectUsercmd();
1799 gameTimeResidual = USERCMD_MSEC - 1;
1800 clientPredictTime = 0;
1801 return;
1802 }
1803
1804 if ( clientState == CS_PURERESTART ) {
1805 clientState = CS_DISCONNECTED;
1806 Reconnect();
1807 gameTimeResidual = USERCMD_MSEC - 1;
1808 clientPredictTime = 0;
1809 return;
1810 }
1811
1812 // if not connected setup a connection
1813 if ( clientState < CS_CONNECTED ) {
1814 // also need to read mouse for the connecting guis
1815 usercmdGen->GetDirectUsercmd();
1816 SetupConnection();
1817 gameTimeResidual = USERCMD_MSEC - 1;
1818 clientPredictTime = 0;
1819 return;
1820 }
1821
1822 if ( CheckTimeout() ) {
1823 return;
1824 }
1825
1826 // if not yet in the game send empty messages to keep data flowing through the channel
1827 if ( clientState < CS_INGAME ) {
1828 Idle();
1829 gameTimeResidual = 0;
1830 return;
1831 }
1832
1833 // check for user info changes
1834 if ( cvarSystem->GetModifiedFlags() & CVAR_USERINFO ) {
1835 game->ThrottleUserInfo( );
1836 SendUserInfoToServer( );
1837 game->SetUserInfo( clientNum, sessLocal.mapSpawnData.userInfo[ clientNum ], true, false );
1838 cvarSystem->ClearModifiedFlags( CVAR_USERINFO );
1839 }
1840
1841 if ( gameTimeResidual + clientPredictTime >= USERCMD_MSEC ) {
1842 lastFrameDelta = 0;
1843 }
1844
1845 // generate user commands for the predicted time
1846 while ( gameTimeResidual + clientPredictTime >= USERCMD_MSEC ) {
1847
1848 // send the user commands of this client to the server
1849 SendUsercmdsToServer();
1850
1851 // update time
1852 gameFrame++;
1853 gameTime += USERCMD_MSEC;
1854 gameTimeResidual -= USERCMD_MSEC;
1855
1856 // run from the snapshot up to the local game frame
1857 while ( snapshotGameFrame < gameFrame ) {
1858
1859 lastFrameDelta++;
1860
1861 // duplicate usercmds for clients if no new ones are available
1862 DuplicateUsercmds( snapshotGameFrame, snapshotGameTime );
1863
1864 // indicate the last prediction frame before a render
1865 bool lastPredictFrame = ( snapshotGameFrame + 1 >= gameFrame && gameTimeResidual + clientPredictTime < USERCMD_MSEC );
1866
1867 // run client prediction
1868 gameReturn_t ret = game->ClientPrediction( clientNum, userCmds[ snapshotGameFrame & ( MAX_USERCMD_BACKUP - 1 ) ], lastPredictFrame );
1869
1870 idAsyncNetwork::ExecuteSessionCommand( ret.sessionCommand );
1871
1872 snapshotGameFrame++;
1873 snapshotGameTime += USERCMD_MSEC;
1874 }
1875 }
1876 }
1877
1878 /*
1879 ==================
1880 idAsyncClient::PacifierUpdate
1881 ==================
1882 */
PacifierUpdate(void)1883 void idAsyncClient::PacifierUpdate( void ) {
1884 if ( !IsActive() ) {
1885 return;
1886 }
1887 realTime = Sys_Milliseconds();
1888 SendEmptyToServer( false, true );
1889 }
1890
1891 /*
1892 ==================
1893 idAsyncClient::SendVersionCheck
1894 ==================
1895 */
SendVersionCheck(bool fromMenu)1896 void idAsyncClient::SendVersionCheck( bool fromMenu ) {
1897 idBitMsg msg;
1898 byte msgBuf[MAX_MESSAGE_SIZE];
1899
1900 if ( updateState != UPDATE_NONE && !fromMenu ) {
1901 common->DPrintf( "up-to-date check was already performed\n" );
1902 return;
1903 }
1904
1905 InitPort();
1906 msg.Init( msgBuf, sizeof( msgBuf ) );
1907 msg.WriteShort( CONNECTIONLESS_MESSAGE_ID );
1908 msg.WriteString( "versionCheck" );
1909 msg.WriteInt( ASYNC_PROTOCOL_VERSION );
1910 msg.WriteString( cvarSystem->GetCVarString( "si_version" ) );
1911 msg.WriteString( cvarSystem->GetCVarString( "com_guid" ) );
1912 clientPort.SendPacket( idAsyncNetwork::GetMasterAddress(), msg.GetData(), msg.GetSize() );
1913
1914 common->DPrintf( "sent a version check request\n" );
1915
1916 updateState = UPDATE_SENT;
1917 updateSentTime = clientTime;
1918 showUpdateMessage = fromMenu;
1919 }
1920
1921 /*
1922 ==================
1923 idAsyncClient::SendVersionDLUpdate
1924
1925 sending those packets is not strictly necessary. just a way to tell the update server
1926 about what is going on. allows the update server to have a more precise view of the overall
1927 network load for the updates
1928 ==================
1929 */
SendVersionDLUpdate(int state)1930 void idAsyncClient::SendVersionDLUpdate( int state ) {
1931 idBitMsg msg;
1932 byte msgBuf[MAX_MESSAGE_SIZE];
1933
1934 msg.Init( msgBuf, sizeof( msgBuf ) );
1935 msg.WriteShort( CONNECTIONLESS_MESSAGE_ID );
1936 msg.WriteString( "versionDL" );
1937 msg.WriteInt( ASYNC_PROTOCOL_VERSION );
1938 msg.WriteShort( state );
1939 clientPort.SendPacket( idAsyncNetwork::GetMasterAddress(), msg.GetData(), msg.GetSize() );
1940 }
1941
1942 /*
1943 ==================
1944 idAsyncClient::HandleDownloads
1945 ==================
1946 */
HandleDownloads(void)1947 void idAsyncClient::HandleDownloads( void ) {
1948
1949 if ( updateState == UPDATE_SENT && clientTime > updateSentTime + 2000 ) {
1950 // timing out on no reply
1951 updateState = UPDATE_DONE;
1952 if ( showUpdateMessage ) {
1953 session->MessageBox( MSG_OK, common->GetLanguageDict()->GetString ( "#str_04839" ), common->GetLanguageDict()->GetString ( "#str_04837" ), true );
1954 showUpdateMessage = false;
1955 }
1956 common->DPrintf( "No update available\n" );
1957 } else if ( backgroundDownload.completed ) {
1958 // only enter these if the download slot is free
1959 if ( updateState == UPDATE_READY ) {
1960 //
1961 if ( session->MessageBox( MSG_YESNO, updateMSG, common->GetLanguageDict()->GetString ( "#str_04330" ), true, "yes" )[0] ) {
1962 if ( !updateDirectDownload ) {
1963 sys->OpenURL( updateURL, true );
1964 updateState = UPDATE_DONE;
1965 } else {
1966
1967 // we're just creating the file at toplevel inside fs_savepath
1968 updateURL.ExtractFileName( updateFile );
1969 idFile_Permanent *f = static_cast< idFile_Permanent *>( fileSystem->OpenFileWrite( updateFile ) );
1970 dltotal = 0;
1971 dlnow = 0;
1972
1973 backgroundDownload.completed = false;
1974 backgroundDownload.opcode = DLTYPE_URL;
1975 backgroundDownload.f = f;
1976 backgroundDownload.url.status = DL_WAIT;
1977 backgroundDownload.url.dlnow = 0;
1978 backgroundDownload.url.dltotal = 0;
1979 backgroundDownload.url.url = updateURL;
1980 fileSystem->BackgroundDownload( &backgroundDownload );
1981
1982 updateState = UPDATE_DLING;
1983 SendVersionDLUpdate( 0 );
1984 session->DownloadProgressBox( &backgroundDownload, va( "Downloading %s\n", updateFile.c_str() ) );
1985 updateState = UPDATE_DONE;
1986 if ( backgroundDownload.url.status == DL_DONE ) {
1987 SendVersionDLUpdate( 1 );
1988 idStr fullPath = f->GetFullPath();
1989 fileSystem->CloseFile( f );
1990 if ( session->MessageBox( MSG_YESNO, common->GetLanguageDict()->GetString ( "#str_04331" ), common->GetLanguageDict()->GetString ( "#str_04332" ), true, "yes" )[0] ) {
1991 if ( updateMime == DL_FILE_EXEC ) {
1992 sys->StartProcess( fullPath, true );
1993 } else {
1994 sys->OpenURL( va( "file://%s", fullPath.c_str() ), true );
1995 }
1996 } else {
1997 session->MessageBox( MSG_OK, va( common->GetLanguageDict()->GetString ( "#str_04333" ), fullPath.c_str() ), common->GetLanguageDict()->GetString ( "#str_04334" ), true );
1998 }
1999 } else {
2000 if ( backgroundDownload.url.dlerror[ 0 ] ) {
2001 common->Warning( "update download failed. curl error: %s", backgroundDownload.url.dlerror );
2002 }
2003 SendVersionDLUpdate( 2 );
2004 idStr name = f->GetName();
2005 fileSystem->CloseFile( f );
2006 fileSystem->RemoveFile( name );
2007 session->MessageBox( MSG_OK, common->GetLanguageDict()->GetString ( "#str_04335" ), common->GetLanguageDict()->GetString ( "#str_04336" ), true );
2008 if ( updateFallback.Length() ) {
2009 sys->OpenURL( updateFallback.c_str(), true );
2010 } else {
2011 common->Printf( "no fallback URL\n" );
2012 }
2013 }
2014 }
2015 } else {
2016 updateState = UPDATE_DONE;
2017 }
2018 } else if ( dlList.Num() ) {
2019
2020 int numPaks = dlList.Num();
2021 int pakCount = 1;
2022 int progress_start, progress_end;
2023 currentDlSize = 0;
2024
2025 do {
2026
2027 if ( dlList[ 0 ].url[ 0 ] == '\0' ) {
2028 // ignore empty files
2029 dlList.RemoveIndex( 0 );
2030 continue;
2031 }
2032 common->Printf( "start download for %s\n", dlList[ 0 ].url.c_str() );
2033
2034 idFile_Permanent *f = static_cast< idFile_Permanent *>( fileSystem->MakeTemporaryFile( ) );
2035 if ( !f ) {
2036 common->Warning( "could not create temporary file" );
2037 dlList.Clear();
2038 return;
2039 }
2040
2041 backgroundDownload.completed = false;
2042 backgroundDownload.opcode = DLTYPE_URL;
2043 backgroundDownload.f = f;
2044 backgroundDownload.url.status = DL_WAIT;
2045 backgroundDownload.url.dlnow = 0;
2046 backgroundDownload.url.dltotal = dlList[ 0 ].size;
2047 backgroundDownload.url.url = dlList[ 0 ].url;
2048 fileSystem->BackgroundDownload( &backgroundDownload );
2049 idStr dltitle;
2050 // "Downloading %s"
2051 sprintf( dltitle, common->GetLanguageDict()->GetString( "#str_07213" ), dlList[ 0 ].filename.c_str() );
2052 if ( numPaks > 1 ) {
2053 dltitle += va( " (%d/%d)", pakCount, numPaks );
2054 }
2055 if ( totalDlSize ) {
2056 progress_start = (int)( (float)currentDlSize * 100.0f / (float)totalDlSize );
2057 progress_end = (int)( (float)( currentDlSize + dlList[ 0 ].size ) * 100.0f / (float)totalDlSize );
2058 } else {
2059 progress_start = 0;
2060 progress_end = 100;
2061 }
2062 session->DownloadProgressBox( &backgroundDownload, dltitle, progress_start, progress_end );
2063 if ( backgroundDownload.url.status == DL_DONE ) {
2064 idFile *saveas;
2065 const int CHUNK_SIZE = 1024 * 1024;
2066 byte *buf;
2067 int remainlen;
2068 int readlen;
2069 int retlen;
2070 int checksum;
2071
2072 common->Printf( "file downloaded\n" );
2073 idStr finalPath = cvarSystem->GetCVarString( "fs_savepath" );
2074 finalPath.AppendPath( dlList[ 0 ].filename );
2075 fileSystem->CreateOSPath( finalPath );
2076 // do the final copy ourselves so we do by small chunks in case the file is big
2077 saveas = fileSystem->OpenExplicitFileWrite( finalPath );
2078 buf = (byte*)Mem_Alloc( CHUNK_SIZE );
2079 f->Seek( 0, FS_SEEK_END );
2080 remainlen = f->Tell();
2081 f->Seek( 0, FS_SEEK_SET );
2082 while ( remainlen ) {
2083 readlen = Min( remainlen, CHUNK_SIZE );
2084 retlen = f->Read( buf, readlen );
2085 if ( retlen != readlen ) {
2086 common->FatalError( "short read %d of %d in idFileSystem::HandleDownload", retlen, readlen );
2087 }
2088 retlen = saveas->Write( buf, readlen );
2089 if ( retlen != readlen ) {
2090 common->FatalError( "short write %d of %d in idFileSystem::HandleDownload", retlen, readlen );
2091 }
2092 remainlen -= readlen;
2093 }
2094 fileSystem->CloseFile( f );
2095 fileSystem->CloseFile( saveas );
2096 common->Printf( "saved as %s\n", finalPath.c_str() );
2097 Mem_Free( buf );
2098
2099 // add that file to our paks list
2100 checksum = fileSystem->AddZipFile( dlList[ 0 ].filename );
2101
2102 // verify the checksum to be what the server says
2103 if ( !checksum || checksum != dlList[ 0 ].checksum ) {
2104 // "pak is corrupted ( checksum 0x%x, expected 0x%x )"
2105 session->MessageBox( MSG_OK, va( common->GetLanguageDict()->GetString( "#str_07214" ) , checksum, dlList[0].checksum ), "Download failed", true );
2106 fileSystem->RemoveFile( dlList[ 0 ].filename );
2107 dlList.Clear();
2108 return;
2109 }
2110
2111 currentDlSize += dlList[ 0 ].size;
2112
2113 } else {
2114 common->Warning( "download failed: %s", dlList[ 0 ].url.c_str() );
2115 if ( backgroundDownload.url.dlerror[ 0 ] ) {
2116 common->Warning( "curl error: %s", backgroundDownload.url.dlerror );
2117 }
2118 // "The download failed or was cancelled"
2119 // "Download failed"
2120 session->MessageBox( MSG_OK, common->GetLanguageDict()->GetString( "#str_07215" ), common->GetLanguageDict()->GetString( "#str_07216" ), true );
2121 dlList.Clear();
2122 return;
2123 }
2124
2125 pakCount++;
2126 dlList.RemoveIndex( 0 );
2127 } while ( dlList.Num() );
2128
2129 // all downloads successful - do the dew
2130 cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "reconnect\n" );
2131 }
2132 }
2133 }
2134
2135 /*
2136 ===============
2137 idAsyncClient::SendAuthCheck
2138 ===============
2139 */
SendAuthCheck(const char * cdkey,const char * xpkey)2140 bool idAsyncClient::SendAuthCheck( const char *cdkey, const char *xpkey ) {
2141 idBitMsg msg;
2142 byte msgBuf[MAX_MESSAGE_SIZE];
2143
2144 msg.Init( msgBuf, sizeof( msgBuf ) );
2145 msg.WriteShort( CONNECTIONLESS_MESSAGE_ID );
2146 msg.WriteString( "gameAuth" );
2147 msg.WriteInt( ASYNC_PROTOCOL_VERSION );
2148 msg.WriteByte( cdkey ? 1 : 0 );
2149 msg.WriteString( cdkey ? cdkey : "" );
2150 msg.WriteByte( xpkey ? 1 : 0 );
2151 msg.WriteString( xpkey ? xpkey : "" );
2152 InitPort();
2153 clientPort.SendPacket( idAsyncNetwork::GetMasterAddress(), msg.GetData(), msg.GetSize() );
2154 return true;
2155 }
2156
2157 /*
2158 ===============
2159 idAsyncClient::CheckTimeout
2160 ===============
2161 */
CheckTimeout(void)2162 bool idAsyncClient::CheckTimeout( void ) {
2163 if ( lastPacketTime > 0 && ( lastPacketTime + idAsyncNetwork::clientServerTimeout.GetInteger()*1000 < clientTime ) ) {
2164 session->StopBox();
2165 session->MessageBox( MSG_OK, common->GetLanguageDict()->GetString ( "#str_04328" ), common->GetLanguageDict()->GetString ( "#str_04329" ), true );
2166 cmdSystem->BufferCommandText( CMD_EXEC_NOW, "disconnect" );
2167 return true;
2168 }
2169 return false;
2170 }
2171
2172 /*
2173 ===============
2174 idAsyncClient::ProcessDownloadInfoMessage
2175 ===============
2176 */
ProcessDownloadInfoMessage(const netadr_t from,const idBitMsg & msg)2177 void idAsyncClient::ProcessDownloadInfoMessage( const netadr_t from, const idBitMsg &msg ) {
2178 char buf[ MAX_STRING_CHARS ];
2179 int srvDlRequest = msg.ReadInt();
2180 int infoType = msg.ReadByte();
2181 int pakDl;
2182 int pakIndex;
2183
2184 pakDlEntry_t entry;
2185 bool gotAllFiles = true;
2186 idStr sizeStr;
2187 bool gotGame = false;
2188
2189 if ( dlRequest == -1 || srvDlRequest != dlRequest ) {
2190 common->Warning( "bad download id from server, ignored" );
2191 return;
2192 }
2193 // mark the dlRequest as dead now whatever how we process it
2194 dlRequest = -1;
2195
2196 if ( infoType == SERVER_DL_REDIRECT ) {
2197 msg.ReadString( buf, MAX_STRING_CHARS );
2198 cmdSystem->BufferCommandText( CMD_EXEC_NOW, "disconnect" );
2199 // "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?"
2200 // "Missing required files"
2201 if ( session->MessageBox( MSG_YESNO, va( common->GetLanguageDict()->GetString( "#str_07217" ), buf ),
2202 common->GetLanguageDict()->GetString( "#str_07218" ), true, "yes" )[ 0 ] ) {
2203 sys->OpenURL( buf, true );
2204 }
2205 } else if ( infoType == SERVER_DL_LIST ) {
2206 cmdSystem->BufferCommandText( CMD_EXEC_NOW, "disconnect" );
2207 if ( dlList.Num() ) {
2208 common->Warning( "tried to process a download list while already busy downloading things" );
2209 return;
2210 }
2211 // read the URLs, check against what we requested, prompt for download
2212 pakIndex = -1;
2213 totalDlSize = 0;
2214 do {
2215 pakIndex++;
2216 pakDl = msg.ReadByte();
2217 if ( pakDl == SERVER_PAK_YES ) {
2218 if ( pakIndex == 0 ) {
2219 gotGame = true;
2220 }
2221 msg.ReadString( buf, MAX_STRING_CHARS );
2222 entry.filename = buf;
2223 msg.ReadString( buf, MAX_STRING_CHARS );
2224 entry.url = buf;
2225 entry.size = msg.ReadInt();
2226 // checksums are not transmitted, we read them from the dl request we sent
2227 entry.checksum = dlChecksums[ pakIndex ];
2228 totalDlSize += entry.size;
2229 dlList.Append( entry );
2230 common->Printf( "download %s from %s ( 0x%x )\n", entry.filename.c_str(), entry.url.c_str(), entry.checksum );
2231 } else if ( pakDl == SERVER_PAK_NO ) {
2232 msg.ReadString( buf, MAX_STRING_CHARS );
2233 entry.filename = buf;
2234 entry.url = "";
2235 entry.size = 0;
2236 entry.checksum = 0;
2237 dlList.Append( entry );
2238 // first pak is game pak, only fail it if we actually requested it
2239 if ( pakIndex != 0 || dlChecksums[ 0 ] != 0 ) {
2240 common->Printf( "no download offered for %s ( 0x%x )\n", entry.filename.c_str(), dlChecksums[ pakIndex ] );
2241 gotAllFiles = false;
2242 }
2243 } else {
2244 assert( pakDl == SERVER_PAK_END );
2245 }
2246 } while ( pakDl != SERVER_PAK_END );
2247 if ( dlList.Num() < dlCount ) {
2248 common->Printf( "%d files were ignored by the server\n", dlCount - dlList.Num() );
2249 gotAllFiles = false;
2250 }
2251 sizeStr.BestUnit( "%.2f", totalDlSize, MEASURE_SIZE );
2252 cmdSystem->BufferCommandText( CMD_EXEC_NOW, "disconnect" );
2253 if ( totalDlSize == 0 ) {
2254 // was no downloadable stuff for us
2255 // "Can't connect to the pure server: no downloads offered"
2256 // "Missing required files"
2257 dlList.Clear();
2258 session->MessageBox( MSG_OK, common->GetLanguageDict()->GetString( "#str_07219" ), common->GetLanguageDict()->GetString( "#str_07218" ), true );
2259 return;
2260 }
2261 bool asked = false;
2262 if ( gotGame ) {
2263 asked = true;
2264 // "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."
2265 // "Missing game binaries"
2266 if ( !session->MessageBox( MSG_YESNO, common->GetLanguageDict()->GetString( "#str_07220" ), common->GetLanguageDict()->GetString( "#str_07221" ), true, "yes" )[ 0 ] ) {
2267 dlList.Clear();
2268 return;
2269 }
2270 }
2271 if ( !gotAllFiles ) {
2272 asked = true;
2273 // "The server only offers to download some of the files required to connect ( %s ). Download anyway?"
2274 // "Missing required files"
2275 if ( !session->MessageBox( MSG_YESNO, va( common->GetLanguageDict()->GetString( "#str_07222" ), sizeStr.c_str() ),
2276 common->GetLanguageDict()->GetString( "#str_07218" ), true, "yes" )[ 0 ] ) {
2277 dlList.Clear();
2278 return;
2279 }
2280 }
2281 if ( !asked && idAsyncNetwork::clientDownload.GetInteger() == 1 ) {
2282 // "You need to download some files to connect to this server ( %s ), proceed?"
2283 // "Missing required files"
2284 if ( !session->MessageBox( MSG_YESNO, va( common->GetLanguageDict()->GetString( "#str_07224" ), sizeStr.c_str() ),
2285 common->GetLanguageDict()->GetString( "#str_07218" ), true, "yes" )[ 0 ] ) {
2286 dlList.Clear();
2287 return;
2288 }
2289 }
2290 } else {
2291 cmdSystem->BufferCommandText( CMD_EXEC_NOW, "disconnect" );
2292 // "You are missing some files to connect to this server, and the server doesn't provide downloads."
2293 // "Missing required files"
2294 session->MessageBox( MSG_OK, common->GetLanguageDict()->GetString( "#str_07223" ), common->GetLanguageDict()->GetString( "#str_07218" ), true );
2295 }
2296 }
2297
2298 /*
2299 ===============
2300 idAsyncClient::GetDownloadRequest
2301 ===============
2302 */
GetDownloadRequest(const int checksums[MAX_PURE_PAKS],int count)2303 int idAsyncClient::GetDownloadRequest( const int checksums[ MAX_PURE_PAKS ], int count ) {
2304 assert( !checksums[ count ] ); // 0-terminated
2305 if ( memcmp( dlChecksums, checksums, sizeof( int ) * count ) ) {
2306 idRandom newreq;
2307
2308 memcpy( dlChecksums, checksums, sizeof( int ) * MAX_PURE_PAKS );
2309
2310 newreq.SetSeed( Sys_Milliseconds() );
2311 dlRequest = newreq.RandomInt();
2312 dlCount = count;
2313 return dlRequest;
2314 }
2315 // this is the same dlRequest, we haven't heard from the server. keep the same id
2316 return dlRequest;
2317 }
2318