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