2 ===========================================================================
5 Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company.
7 This file is part of the Doom 3 GPL Source Code (?Doom 3 Source Code?).
9 Doom 3 Source Code is free software: you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation, either version 3 of the License, or
12 (at your option) any later version.
14 Doom 3 Source Code is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 GNU General Public License for more details.
19 You should have received a copy of the GNU General Public License
20 along with Doom 3 Source Code. If not, see <http://www.gnu.org/licenses/>.
22 In addition, the Doom 3 Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 Source Code. If not, please request a copy in writing from id Software at the address below.
24 If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
26 ===========================================================================
29 #include "../../idlib/precompiled.h"
32 #include "AsyncNetwork.h"
34 #include "../Session_local.h"
36 const int MIN_RECONNECT_TIME = 2000;
37 const int EMPTY_RESEND_TIME = 500;
38 const int PING_RESEND_TIME = 500;
39 const int NOINPUT_IDLE_TIME = 30000;
41 const int HEARTBEAT_MSEC = 5*60*1000;
43 // must be kept in sync with authReplyMsg_t
44 const char* authReplyMsg[] = {
45 // "Waiting for authorization",
47 // "Client unknown to auth",
49 // "Access denied - CD Key in use",
51 // "Auth custom message", // placeholder - we propagate a message from the master
53 // "Authorize Server - Waiting for client"
57 const char* authReplyStr[] = {
66 idAsyncServer::idAsyncServer
69 idAsyncServer::idAsyncServer( void ) {
76 serverDataChecksum = 0;
82 memset( challenges, 0, sizeof( challenges ) );
83 memset( userCmds, 0, sizeof( userCmds ) );
84 for ( i = 0; i < MAX_ASYNC_CLIENTS; i++ ) {
87 serverReloadingEngine = false;
88 nextHeartbeatTime = 0;
89 nextAsyncStatsTime = 0;
93 memset( stats_outrate, 0, sizeof( stats_outrate ) );
95 stats_average_sum = 0;
102 idAsyncServer::InitPort
105 bool idAsyncServer::InitPort( void ) {
108 // if this is the first time we have spawned a server, open the UDP port
109 if ( !serverPort.GetPort() ) {
110 if ( cvarSystem->GetCVarInteger( "net_port" ) != 0 ) {
111 if ( !serverPort.InitForPort( cvarSystem->GetCVarInteger( "net_port" ) ) ) {
112 common->Printf( "Unable to open server on port %d (net_port)\n", cvarSystem->GetCVarInteger( "net_port" ) );
116 // scan for multiple ports, in case other servers are running on this IP already
117 for ( lastPort = 0; lastPort < NUM_SERVER_PORTS; lastPort++ ) {
118 if ( serverPort.InitForPort( PORT_SERVER + lastPort ) ) {
122 if ( lastPort >= NUM_SERVER_PORTS ) {
123 common->Printf( "Unable to open server network port.\n" );
134 idAsyncServer::ClosePort
137 void idAsyncServer::ClosePort( void ) {
141 for ( i = 0; i < MAX_CHALLENGES; i++ ) {
142 challenges[ i ].authReplyPrint.Clear();
151 void idAsyncServer::Spawn( void ) {
153 byte msgBuf[MAX_MESSAGE_SIZE];
156 // shutdown any current game
167 // trash any currently pending packets
168 while( serverPort.GetPacket( from, msgBuf, size, sizeof( msgBuf ) ) ) {
171 // reset cheats cvars
172 if ( !idAsyncNetwork::allowCheats.GetBool() ) {
173 cvarSystem->ResetFlaggedVariables( CVAR_CHEAT );
176 memset( challenges, 0, sizeof( challenges ) );
177 memset( userCmds, 0, sizeof( userCmds ) );
178 for ( i = 0; i < MAX_ASYNC_CLIENTS; i++ ) {
182 common->Printf( "Server spawned on port %i.\n", serverPort.GetPort() );
184 // calculate a checksum on some of the essential data used
185 serverDataChecksum = declManager->GetChecksum();
187 // get a pseudo random server id, but don't use the id which is reserved for connectionless packets
188 serverId = Sys_Milliseconds() & CONNECTIONLESS_MESSAGE_ID_MASK;
192 nextHeartbeatTime = 0;
193 nextAsyncStatsTime = 0;
203 void idAsyncServer::Kill( void ) {
211 for ( i = 0; i < MAX_ASYNC_CLIENTS; i++ ) {
212 DropClient( i, "#str_07135" );
215 // send some empty messages to the zombie clients to make sure they disconnect
216 for ( j = 0; j < 4; j++ ) {
217 for ( i = 0; i < MAX_ASYNC_CLIENTS; i++ ) {
218 if ( clients[i].clientState == SCS_ZOMBIE ) {
219 if ( clients[i].channel.UnsentFragmentsLeft() ) {
220 clients[i].channel.SendNextFragment( serverPort, serverTime );
222 SendEmptyToClient( i, true );
229 // reset any pureness
230 fileSystem->ClearPureChecksums();
234 // shutdown any current game
240 idAsyncServer::ExecuteMapChange
243 void idAsyncServer::ExecuteMapChange( void ) {
246 byte msgBuf[MAX_MESSAGE_SIZE];
249 bool addonReload = false;
250 char bestGameType[ MAX_STRING_CHARS ];
254 // reset any pureness
255 fileSystem->ClearPureChecksums();
257 // make sure the map/gametype combo is good
258 game->GetBestGameType( cvarSystem->GetCVarString("si_map"), cvarSystem->GetCVarString("si_gametype"), bestGameType );
259 cvarSystem->SetCVarString("si_gametype", bestGameType );
261 // initialize map settings
262 cmdSystem->BufferCommandText( CMD_EXEC_NOW, "rescanSI" );
264 sprintf( mapName, "maps/%s", sessLocal.mapSpawnData.serverInfo.GetString( "si_map" ) );
265 mapName.SetFileExtension( ".map" );
266 ff = fileSystem->FindFile( mapName, !serverReloadingEngine );
269 common->Printf( "Can't find map %s\n", mapName.c_str() );
270 cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "disconnect\n" );
273 // NOTE: we have no problem with addon dependencies here because if the map is in
274 // an addon pack that's already on search list, then all it's deps are assumed to be on search as well
275 common->Printf( "map %s is in an addon pak - reloading\n", mapName.c_str() );
282 // if we are asked to do a full reload, the strategy is completely different
283 if ( !serverReloadingEngine && ( addonReload || idAsyncNetwork::serverReloadEngine.GetInteger() != 0 ) ) {
284 if ( idAsyncNetwork::serverReloadEngine.GetInteger() != 0 ) {
285 common->Printf( "net_serverReloadEngine enabled - doing a full reload\n" );
287 // tell the clients to reconnect
288 // FIXME: shouldn't they wait for the new pure list, then reload?
289 // in a lot of cases this is going to trigger two reloadEngines for the clients
290 // one to restart, the other one to set paks right ( with addon for instance )
291 // can fix by reconnecting without reloading and waiting for the server to tell..
292 for ( i = 0; i < MAX_ASYNC_CLIENTS; i++ ) {
293 if ( clients[ i ].clientState >= SCS_PUREWAIT && i != localClientNum ) {
294 msg.Init( msgBuf, sizeof( msgBuf ) );
295 msg.WriteByte( SERVER_RELIABLE_MESSAGE_RELOAD );
296 SendReliableMessage( i, msg );
297 clients[ i ].clientState = SCS_ZOMBIE; // so we don't bother sending a disconnect
300 cmdSystem->BufferCommandText( CMD_EXEC_NOW, "reloadEngine" );
301 serverReloadingEngine = true; // don't get caught in endless loop
302 cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "spawnServer\n" );
304 if ( idAsyncNetwork::serverReloadEngine.GetInteger() > 0 ) {
305 idAsyncNetwork::serverReloadEngine.SetInteger( idAsyncNetwork::serverReloadEngine.GetInteger() - 1 );
309 serverReloadingEngine = false;
313 // initialize game id and time
314 gameInitId ^= Sys_Milliseconds(); // NOTE: make sure the gameInitId is always a positive number because negative numbers have special meaning
317 gameTimeResidual = 0;
318 memset( userCmds, 0, sizeof( userCmds ) );
320 if ( idAsyncNetwork::serverDedicated.GetInteger() == 0 ) {
321 InitLocalClient( 0 );
326 // re-initialize all connected clients for the new map
327 for ( i = 0; i < MAX_ASYNC_CLIENTS; i++ ) {
328 if ( clients[i].clientState >= SCS_PUREWAIT && i != localClientNum ) {
330 InitClient( i, clients[i].clientId, clients[i].clientRate );
332 SendGameInitToClient( i );
334 if ( sessLocal.mapSpawnData.serverInfo.GetBool( "si_pure" ) ) {
335 clients[ i ].clientState = SCS_PUREWAIT;
340 // setup the game pak checksums
341 // since this is not dependant on si_pure we catch anything bad before loading map
342 if ( sessLocal.mapSpawnData.serverInfo.GetInt( "si_pure" ) ) {
343 if ( !fileSystem->UpdateGamePakChecksums( ) ) {
344 session->MessageBox( MSG_OK, common->GetLanguageDict()->GetString ( "#str_04337" ), common->GetLanguageDict()->GetString ( "#str_04338" ), true );
345 cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "disconnect\n" );
351 sessLocal.ExecuteMapChange();
353 if ( localClientNum >= 0 ) {
356 game->SetLocalClient( -1 );
359 if ( sessLocal.mapSpawnData.serverInfo.GetInt( "si_pure" ) ) {
360 // lock down the pak list
361 fileSystem->UpdatePureServerChecksums( );
362 // tell the clients so they can work out their pure lists
363 for ( i = 0; i < MAX_ASYNC_CLIENTS; i++ ) {
364 if ( clients[ i ].clientState == SCS_PUREWAIT ) {
365 if ( !SendReliablePureToClient( i ) ) {
366 clients[ i ].clientState = SCS_CONNECTED;
372 // serverTime gets reset, force a heartbeat so timings restart
373 MasterHeartbeat( true );
378 idAsyncServer::GetPort
381 int idAsyncServer::GetPort( void ) const {
382 return serverPort.GetPort();
387 idAsyncServer::GetBoundAdr
390 netadr_t idAsyncServer::GetBoundAdr( void ) const {
391 return serverPort.GetAdr();
396 idAsyncServer::GetOutgoingRate
399 int idAsyncServer::GetOutgoingRate( void ) const {
403 for ( i = 0; i < MAX_ASYNC_CLIENTS; i++ ) {
404 const serverClient_t &client = clients[i];
406 if ( client.clientState >= SCS_CONNECTED ) {
407 rate += client.channel.GetOutgoingRate();
415 idAsyncServer::GetIncomingRate
418 int idAsyncServer::GetIncomingRate( void ) const {
422 for ( i = 0; i < MAX_ASYNC_CLIENTS; i++ ) {
423 const serverClient_t &client = clients[i];
425 if ( client.clientState >= SCS_CONNECTED ) {
426 rate += client.channel.GetIncomingRate();
434 idAsyncServer::IsClientInGame
437 bool idAsyncServer::IsClientInGame( int clientNum ) const {
438 return ( clients[clientNum].clientState >= SCS_INGAME );
443 idAsyncServer::GetClientPing
446 int idAsyncServer::GetClientPing( int clientNum ) const {
447 const serverClient_t &client = clients[clientNum];
449 if ( client.clientState < SCS_CONNECTED ) {
452 return client.clientPing;
458 idAsyncServer::GetClientPrediction
461 int idAsyncServer::GetClientPrediction( int clientNum ) const {
462 const serverClient_t &client = clients[clientNum];
464 if ( client.clientState < SCS_CONNECTED ) {
467 return client.clientPrediction;
473 idAsyncServer::GetClientTimeSinceLastPacket
476 int idAsyncServer::GetClientTimeSinceLastPacket( int clientNum ) const {
477 const serverClient_t &client = clients[clientNum];
479 if ( client.clientState < SCS_CONNECTED ) {
482 return serverTime - client.lastPacketTime;
488 idAsyncServer::GetClientTimeSinceLastInput
491 int idAsyncServer::GetClientTimeSinceLastInput( int clientNum ) const {
492 const serverClient_t &client = clients[clientNum];
494 if ( client.clientState < SCS_CONNECTED ) {
497 return serverTime - client.lastInputTime;
503 idAsyncServer::GetClientOutgoingRate
506 int idAsyncServer::GetClientOutgoingRate( int clientNum ) const {
507 const serverClient_t &client = clients[clientNum];
509 if ( client.clientState < SCS_CONNECTED ) {
512 return client.channel.GetOutgoingRate();
518 idAsyncServer::GetClientIncomingRate
521 int idAsyncServer::GetClientIncomingRate( int clientNum ) const {
522 const serverClient_t &client = clients[clientNum];
524 if ( client.clientState < SCS_CONNECTED ) {
527 return client.channel.GetIncomingRate();
533 idAsyncServer::GetClientOutgoingCompression
536 float idAsyncServer::GetClientOutgoingCompression( int clientNum ) const {
537 const serverClient_t &client = clients[clientNum];
539 if ( client.clientState < SCS_CONNECTED ) {
542 return client.channel.GetOutgoingCompression();
548 idAsyncServer::GetClientIncomingCompression
551 float idAsyncServer::GetClientIncomingCompression( int clientNum ) const {
552 const serverClient_t &client = clients[clientNum];
554 if ( client.clientState < SCS_CONNECTED ) {
557 return client.channel.GetIncomingCompression();
563 idAsyncServer::GetClientIncomingPacketLoss
566 float idAsyncServer::GetClientIncomingPacketLoss( int clientNum ) const {
567 const serverClient_t &client = clients[clientNum];
569 if ( client.clientState < SCS_CONNECTED ) {
572 return client.channel.GetIncomingPacketLoss();
578 idAsyncServer::GetNumClients
581 int idAsyncServer::GetNumClients( void ) const {
583 for ( int i = 0; i < MAX_ASYNC_CLIENTS; i++ ) {
584 if ( clients[ i ].clientState >= SCS_CONNECTED ) {
593 idAsyncServer::GetNumIdleClients
596 int idAsyncServer::GetNumIdleClients( void ) const {
598 for ( int i = 0; i < MAX_ASYNC_CLIENTS; i++ ) {
599 if ( clients[ i ].clientState >= SCS_CONNECTED ) {
600 if ( serverTime - clients[ i ].lastInputTime > NOINPUT_IDLE_TIME ) {
610 idAsyncServer::DuplicateUsercmds
613 void idAsyncServer::DuplicateUsercmds( int frame, int time ) {
614 int i, previousIndex, currentIndex;
616 previousIndex = ( frame - 1 ) & ( MAX_USERCMD_BACKUP - 1 );
617 currentIndex = frame & ( MAX_USERCMD_BACKUP - 1 );
619 // duplicate previous user commands if no new commands are available for a client
620 for ( i = 0; i < MAX_ASYNC_CLIENTS; i++ ) {
621 if ( clients[i].clientState == SCS_FREE ) {
625 if ( idAsyncNetwork::DuplicateUsercmd( userCmds[previousIndex][i], userCmds[currentIndex][i], frame, time ) ) {
626 clients[i].numDuplicatedUsercmds++;
633 idAsyncServer::ClearClient
636 void idAsyncServer::ClearClient( int clientNum ) {
637 serverClient_t &client = clients[clientNum];
639 client.clientState = SCS_FREE;
640 client.clientPrediction = 0;
641 client.clientAheadTime = 0;
642 client.clientRate = 0;
643 client.clientPing = 0;
644 client.gameInitSequence = 0;
645 client.gameFrame = 0;
647 client.channel.Shutdown();
648 client.lastConnectTime = 0;
649 client.lastEmptyTime = 0;
650 client.lastPingTime = 0;
651 client.lastSnapshotTime = 0;
652 client.lastPacketTime = 0;
653 client.lastInputTime = 0;
654 client.snapshotSequence = 0;
655 client.acknowledgeSnapshotSequence = 0;
656 client.numDuplicatedUsercmds = 0;
661 idAsyncServer::InitClient
664 void idAsyncServer::InitClient( int clientNum, int clientId, int clientRate ) {
667 // clear the user info
668 sessLocal.mapSpawnData.userInfo[ clientNum ].Clear(); // always start with a clean base
670 // clear the server client
671 serverClient_t &client = clients[clientNum];
672 client.clientId = clientId;
673 client.clientState = SCS_CONNECTED;
674 client.clientPrediction = 0;
675 client.clientAheadTime = 0;
676 client.gameInitSequence = -1;
677 client.gameFrame = 0;
679 client.channel.ResetRate();
680 client.clientRate = clientRate ? clientRate : idAsyncNetwork::serverMaxClientRate.GetInteger();
681 client.channel.SetMaxOutgoingRate( Min( idAsyncNetwork::serverMaxClientRate.GetInteger(), client.clientRate ) );
682 client.clientPing = 0;
683 client.lastConnectTime = serverTime;
684 client.lastEmptyTime = serverTime;
685 client.lastPingTime = serverTime;
686 client.lastSnapshotTime = serverTime;
687 client.lastPacketTime = serverTime;
688 client.lastInputTime = serverTime;
689 client.acknowledgeSnapshotSequence = 0;
690 client.numDuplicatedUsercmds = 0;
692 // clear the user commands
693 for ( i = 0; i < MAX_USERCMD_BACKUP; i++ ) {
694 memset( &userCmds[i][clientNum], 0, sizeof( userCmds[i][clientNum] ) );
697 // let the game know a player connected
698 game->ServerClientConnect( clientNum, client.guid );
703 idAsyncServer::InitLocalClient
706 void idAsyncServer::InitLocalClient( int clientNum ) {
709 localClientNum = clientNum;
710 InitClient( clientNum, 0, 0 );
711 memset( &badAddress, 0, sizeof( badAddress ) );
712 badAddress.type = NA_BAD;
713 clients[clientNum].channel.Init( badAddress, serverId );
714 clients[clientNum].clientState = SCS_INGAME;
715 sessLocal.mapSpawnData.userInfo[clientNum] = *cvarSystem->MoveCVarsToDict( CVAR_USERINFO );
720 idAsyncServer::BeginLocalClient
723 void idAsyncServer::BeginLocalClient( void ) {
724 game->SetLocalClient( localClientNum );
725 game->SetUserInfo( localClientNum, sessLocal.mapSpawnData.userInfo[localClientNum], false, false );
726 game->ServerClientBegin( localClientNum );
731 idAsyncServer::LocalClientInput
734 void idAsyncServer::LocalClientInput( void ) {
737 if ( localClientNum < 0 ) {
741 index = gameFrame & ( MAX_USERCMD_BACKUP - 1 );
742 userCmds[index][localClientNum] = usercmdGen->GetDirectUsercmd();
743 userCmds[index][localClientNum].gameFrame = gameFrame;
744 userCmds[index][localClientNum].gameTime = gameTime;
745 if ( idAsyncNetwork::UsercmdInputChanged( userCmds[( gameFrame - 1 ) & ( MAX_USERCMD_BACKUP - 1 )][localClientNum], userCmds[index][localClientNum] ) ) {
746 clients[localClientNum].lastInputTime = serverTime;
748 clients[localClientNum].gameFrame = gameFrame;
749 clients[localClientNum].gameTime = gameTime;
750 clients[localClientNum].lastPacketTime = serverTime;
755 idAsyncServer::DropClient
758 void idAsyncServer::DropClient( int clientNum, const char *reason ) {
761 byte msgBuf[MAX_MESSAGE_SIZE];
763 serverClient_t &client = clients[clientNum];
765 if ( client.clientState <= SCS_ZOMBIE ) {
769 if ( client.clientState >= SCS_PUREWAIT && clientNum != localClientNum ) {
770 msg.Init( msgBuf, sizeof( msgBuf ) );
771 msg.WriteByte( SERVER_RELIABLE_MESSAGE_DISCONNECT );
772 msg.WriteLong( clientNum );
773 msg.WriteString( reason );
774 for ( i = 0; i < MAX_ASYNC_CLIENTS; i++ ) {
775 // clientNum so SCS_PUREWAIT client gets it's own disconnect msg
776 if ( i == clientNum || clients[i].clientState >= SCS_CONNECTED ) {
777 SendReliableMessage( i, msg );
782 reason = common->GetLanguageDict()->GetString( reason );
783 common->Printf( "client %d %s\n", clientNum, reason );
784 cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "addChatLine \"%s^0 %s\"", sessLocal.mapSpawnData.userInfo[ clientNum ].GetString( "ui_name" ), reason ) );
786 // remove the player from the game
787 game->ServerClientDisconnect( clientNum );
789 client.clientState = SCS_ZOMBIE;
794 idAsyncServer::SendReliableMessage
797 void idAsyncServer::SendReliableMessage( int clientNum, const idBitMsg &msg ) {
798 if ( clientNum == localClientNum ) {
801 if ( !clients[ clientNum ].channel.SendReliableMessage( msg ) ) {
802 clients[ clientNum ].channel.ClearReliableMessages();
803 DropClient( clientNum, "#str_07136" );
809 idAsyncServer::CheckClientTimeouts
812 void idAsyncServer::CheckClientTimeouts( void ) {
813 int i, zombieTimeout, clientTimeout;
815 zombieTimeout = serverTime - idAsyncNetwork::serverZombieTimeout.GetInteger() * 1000;
816 clientTimeout = serverTime - idAsyncNetwork::serverClientTimeout.GetInteger() * 1000;
818 for ( i = 0; i < MAX_ASYNC_CLIENTS; i++ ) {
819 serverClient_t &client = clients[i];
821 if ( i == localClientNum ) {
825 if ( client.lastPacketTime > serverTime ) {
826 client.lastPacketTime = serverTime;
830 if ( client.clientState == SCS_ZOMBIE && client.lastPacketTime < zombieTimeout ) {
831 client.channel.Shutdown();
832 client.clientState = SCS_FREE;
836 if ( client.clientState >= SCS_PUREWAIT && client.lastPacketTime < clientTimeout ) {
837 DropClient( i, "#str_07137" );
845 idAsyncServer::SendPrintBroadcast
848 void idAsyncServer::SendPrintBroadcast( const char *string ) {
851 byte msgBuf[MAX_MESSAGE_SIZE];
853 msg.Init( msgBuf, sizeof( msgBuf ) );
854 msg.WriteByte( SERVER_RELIABLE_MESSAGE_PRINT );
855 msg.WriteString( string );
857 for ( i = 0; i < MAX_ASYNC_CLIENTS; i++ ) {
858 if ( clients[i].clientState >= SCS_CONNECTED ) {
859 SendReliableMessage( i, msg );
866 idAsyncServer::SendPrintToClient
869 void idAsyncServer::SendPrintToClient( int clientNum, const char *string ) {
871 byte msgBuf[MAX_MESSAGE_SIZE];
873 serverClient_t &client = clients[clientNum];
875 if ( client.clientState < SCS_CONNECTED ) {
879 msg.Init( msgBuf, sizeof( msgBuf ) );
880 msg.WriteByte( SERVER_RELIABLE_MESSAGE_PRINT );
881 msg.WriteString( string );
883 SendReliableMessage( clientNum, msg );
888 idAsyncServer::SendUserInfoBroadcast
891 void idAsyncServer::SendUserInfoBroadcast( int userInfoNum, const idDict &info, bool sendToAll ) {
893 byte msgBuf[MAX_MESSAGE_SIZE];
894 const idDict *gameInfo;
895 bool gameModifiedInfo;
897 gameInfo = game->SetUserInfo( userInfoNum, info, false, true );
899 gameModifiedInfo = true;
901 gameModifiedInfo = false;
905 if ( userInfoNum == localClientNum ) {
906 common->DPrintf( "local user info modified by server\n" );
907 cvarSystem->SetCVarsFromDict( *gameInfo );
908 cvarSystem->ClearModifiedFlags( CVAR_USERINFO ); // don't emit back
911 msg.Init( msgBuf, sizeof( msgBuf ) );
912 msg.WriteByte( SERVER_RELIABLE_MESSAGE_CLIENTINFO );
913 msg.WriteByte( userInfoNum );
914 if ( gameModifiedInfo || sendToAll ) {
915 msg.WriteBits( 0, 1 );
917 msg.WriteBits( 1, 1 );
920 #if ID_CLIENTINFO_TAGS
921 msg.WriteLong( sessLocal.mapSpawnData.userInfo[userInfoNum].Checksum() );
922 common->DPrintf( "broadcast for client %d: 0x%x\n", userInfoNum, sessLocal.mapSpawnData.userInfo[userInfoNum].Checksum() );
923 sessLocal.mapSpawnData.userInfo[userInfoNum].Print();
926 if ( gameModifiedInfo || sendToAll ) {
927 msg.WriteDeltaDict( *gameInfo, NULL );
929 msg.WriteDeltaDict( *gameInfo, &sessLocal.mapSpawnData.userInfo[userInfoNum] );
932 for ( int i = 0; i < MAX_ASYNC_CLIENTS; i++ ) {
933 if ( clients[i].clientState >= SCS_CONNECTED && ( sendToAll || i != userInfoNum || gameModifiedInfo ) ) {
934 SendReliableMessage( i, msg );
938 sessLocal.mapSpawnData.userInfo[userInfoNum] = *gameInfo;
943 idAsyncServer::UpdateUI
944 if the game modifies userInfo, it will call this through command system
945 we then need to get the info from the game, and broadcast to clients
946 ( using DeltaDict and our current mapSpawnData as a base )
949 void idAsyncServer::UpdateUI( int clientNum ) {
950 const idDict *info = game->GetUserInfo( clientNum );
953 common->Warning( "idAsyncServer::UpdateUI: no info from game\n" );
957 SendUserInfoBroadcast( clientNum, *info, true );
962 idAsyncServer::SendUserInfoToClient
965 void idAsyncServer::SendUserInfoToClient( int clientNum, int userInfoNum, const idDict &info ) {
967 byte msgBuf[MAX_MESSAGE_SIZE];
969 if ( clients[clientNum].clientState < SCS_CONNECTED ) {
973 msg.Init( msgBuf, sizeof( msgBuf ) );
974 msg.WriteByte( SERVER_RELIABLE_MESSAGE_CLIENTINFO );
975 msg.WriteByte( userInfoNum );
976 msg.WriteBits( 0, 1 );
978 #if ID_CLIENTINFO_TAGS
980 common->DPrintf( "user info %d to client %d: NULL base\n", userInfoNum, clientNum );
983 msg.WriteDeltaDict( info, NULL );
985 SendReliableMessage( clientNum, msg );
990 idAsyncServer::SendSyncedCvarsBroadcast
993 void idAsyncServer::SendSyncedCvarsBroadcast( const idDict &cvars ) {
995 byte msgBuf[MAX_MESSAGE_SIZE];
998 msg.Init( msgBuf, sizeof( msgBuf ) );
999 msg.WriteByte( SERVER_RELIABLE_MESSAGE_SYNCEDCVARS );
1000 msg.WriteDeltaDict( cvars, &sessLocal.mapSpawnData.syncedCVars );
1002 for ( i = 0; i < MAX_ASYNC_CLIENTS; i++ ) {
1003 if ( clients[i].clientState >= SCS_CONNECTED ) {
1004 SendReliableMessage( i, msg );
1008 sessLocal.mapSpawnData.syncedCVars = cvars;
1013 idAsyncServer::SendSyncedCvarsToClient
1016 void idAsyncServer::SendSyncedCvarsToClient( int clientNum, const idDict &cvars ) {
1018 byte msgBuf[MAX_MESSAGE_SIZE];
1020 if ( clients[clientNum].clientState < SCS_CONNECTED ) {
1024 msg.Init( msgBuf, sizeof( msgBuf ) );
1025 msg.WriteByte( SERVER_RELIABLE_MESSAGE_SYNCEDCVARS );
1026 msg.WriteDeltaDict( cvars, NULL );
1028 SendReliableMessage( clientNum, msg );
1033 idAsyncServer::SendApplySnapshotToClient
1036 void idAsyncServer::SendApplySnapshotToClient( int clientNum, int sequence ) {
1038 byte msgBuf[MAX_MESSAGE_SIZE];
1040 msg.Init( msgBuf, sizeof( msgBuf ) );
1041 msg.WriteByte( SERVER_RELIABLE_MESSAGE_APPLYSNAPSHOT );
1042 msg.WriteLong( sequence );
1044 SendReliableMessage( clientNum, msg );
1049 idAsyncServer::SendEmptyToClient
1052 bool idAsyncServer::SendEmptyToClient( int clientNum, bool force ) {
1054 byte msgBuf[MAX_MESSAGE_SIZE];
1056 serverClient_t &client = clients[clientNum];
1058 if ( client.lastEmptyTime > realTime ) {
1059 client.lastEmptyTime = realTime;
1062 if ( !force && ( realTime - client.lastEmptyTime < EMPTY_RESEND_TIME ) ) {
1066 if ( idAsyncNetwork::verbose.GetInteger() ) {
1067 common->Printf( "sending empty to client %d: gameInitId = %d, gameFrame = %d, gameTime = %d\n", clientNum, gameInitId, gameFrame, gameTime );
1070 msg.Init( msgBuf, sizeof( msgBuf ) );
1071 msg.WriteLong( gameInitId );
1072 msg.WriteByte( SERVER_UNRELIABLE_MESSAGE_EMPTY );
1074 client.channel.SendMessage( serverPort, serverTime, msg );
1076 client.lastEmptyTime = realTime;
1083 idAsyncServer::SendPingToClient
1086 bool idAsyncServer::SendPingToClient( int clientNum ) {
1088 byte msgBuf[MAX_MESSAGE_SIZE];
1090 serverClient_t &client = clients[clientNum];
1092 if ( client.lastPingTime > realTime ) {
1093 client.lastPingTime = realTime;
1096 if ( realTime - client.lastPingTime < PING_RESEND_TIME ) {
1100 if ( idAsyncNetwork::verbose.GetInteger() == 2 ) {
1101 common->Printf( "pinging client %d: gameInitId = %d, gameFrame = %d, gameTime = %d\n", clientNum, gameInitId, gameFrame, gameTime );
1104 msg.Init( msgBuf, sizeof( msgBuf ) );
1105 msg.WriteLong( gameInitId );
1106 msg.WriteByte( SERVER_UNRELIABLE_MESSAGE_PING );
1107 msg.WriteLong( realTime );
1109 client.channel.SendMessage( serverPort, serverTime, msg );
1111 client.lastPingTime = realTime;
1118 idAsyncServer::SendGameInitToClient
1121 void idAsyncServer::SendGameInitToClient( int clientNum ) {
1123 byte msgBuf[MAX_MESSAGE_SIZE];
1125 if ( idAsyncNetwork::verbose.GetInteger() ) {
1126 common->Printf( "sending gameinit to client %d: gameInitId = %d, gameFrame = %d, gameTime = %d\n", clientNum, gameInitId, gameFrame, gameTime );
1129 serverClient_t &client = clients[clientNum];
1131 // clear the unsent fragments. might flood winsock but that's ok
1132 while( client.channel.UnsentFragmentsLeft() ) {
1133 client.channel.SendNextFragment( serverPort, serverTime );
1136 msg.Init( msgBuf, sizeof( msgBuf ) );
1137 msg.WriteLong( gameInitId );
1138 msg.WriteByte( SERVER_UNRELIABLE_MESSAGE_GAMEINIT );
1139 msg.WriteLong( gameFrame );
1140 msg.WriteLong( gameTime );
1141 msg.WriteDeltaDict( sessLocal.mapSpawnData.serverInfo, NULL );
1142 client.gameInitSequence = client.channel.SendMessage( serverPort, serverTime, msg );
1147 idAsyncServer::SendSnapshotToClient
1150 bool idAsyncServer::SendSnapshotToClient( int clientNum ) {
1151 int i, j, index, numUsercmds;
1153 byte msgBuf[MAX_MESSAGE_SIZE];
1155 byte clientInPVS[MAX_ASYNC_CLIENTS >> 3];
1157 serverClient_t &client = clients[clientNum];
1159 if ( serverTime - client.lastSnapshotTime < idAsyncNetwork::serverSnapshotDelay.GetInteger() ) {
1163 if ( idAsyncNetwork::verbose.GetInteger() == 2 ) {
1164 common->Printf( "sending snapshot to client %d: gameInitId = %d, gameFrame = %d, gameTime = %d\n", clientNum, gameInitId, gameFrame, gameTime );
1167 // how far is the client ahead of the server minus the packet delay
1168 client.clientAheadTime = client.gameTime - ( gameTime + gameTimeResidual );
1170 // write the snapshot
1171 msg.Init( msgBuf, sizeof( msgBuf ) );
1172 msg.WriteLong( gameInitId );
1173 msg.WriteByte( SERVER_UNRELIABLE_MESSAGE_SNAPSHOT );
1174 msg.WriteLong( client.snapshotSequence );
1175 msg.WriteLong( gameFrame );
1176 msg.WriteLong( gameTime );
1177 msg.WriteByte( idMath::ClampChar( client.numDuplicatedUsercmds ) );
1178 msg.WriteShort( idMath::ClampShort( client.clientAheadTime ) );
1180 // write the game snapshot
1181 game->ServerWriteSnapshot( clientNum, client.snapshotSequence, msg, clientInPVS, MAX_ASYNC_CLIENTS );
1183 // write the latest user commands from the other clients in the PVS to the snapshot
1184 for ( last = NULL, i = 0; i < MAX_ASYNC_CLIENTS; i++ ) {
1185 serverClient_t &client = clients[i];
1187 if ( client.clientState == SCS_FREE || i == clientNum ) {
1191 // if the client is not in the PVS
1192 if ( !( clientInPVS[i >> 3] & ( 1 << ( i & 7 ) ) ) ) {
1196 int maxRelay = idMath::ClampInt( 1, MAX_USERCMD_RELAY, idAsyncNetwork::serverMaxUsercmdRelay.GetInteger() );
1198 // Max( 1, to always send at least one cmd, which we know we have because we call DuplicateUsercmds in RunFrame
1199 numUsercmds = Max( 1, Min( client.gameFrame, gameFrame + maxRelay ) - gameFrame );
1201 msg.WriteByte( numUsercmds );
1202 for ( j = 0; j < numUsercmds; j++ ) {
1203 index = ( gameFrame + j ) & ( MAX_USERCMD_BACKUP - 1 );
1204 idAsyncNetwork::WriteUserCmdDelta( msg, userCmds[index][i], last );
1205 last = &userCmds[index][i];
1208 msg.WriteByte( MAX_ASYNC_CLIENTS );
1210 client.channel.SendMessage( serverPort, serverTime, msg );
1212 client.lastSnapshotTime = serverTime;
1213 client.snapshotSequence++;
1214 client.numDuplicatedUsercmds = 0;
1221 idAsyncServer::ProcessUnreliableClientMessage
1224 void idAsyncServer::ProcessUnreliableClientMessage( int clientNum, const idBitMsg &msg ) {
1225 int i, id, acknowledgeSequence, clientGameInitId, clientGameFrame, numUsercmds, index;
1228 serverClient_t &client = clients[clientNum];
1230 if ( client.clientState == SCS_ZOMBIE ) {
1234 acknowledgeSequence = msg.ReadLong();
1235 clientGameInitId = msg.ReadLong();
1237 // while loading a map the client may send empty messages to keep the connection alive
1238 if ( clientGameInitId == GAME_INIT_ID_MAP_LOAD ) {
1239 if ( idAsyncNetwork::verbose.GetInteger() ) {
1240 common->Printf( "ignore unreliable msg from client %d, gameInitId == ID_MAP_LOAD\n", clientNum );
1245 // check if the client is in the right game
1246 if ( clientGameInitId != gameInitId ) {
1247 if ( acknowledgeSequence > client.gameInitSequence ) {
1248 // the client is connected but not in the right game
1249 client.clientState = SCS_CONNECTED;
1251 // send game init to client
1252 SendGameInitToClient( clientNum );
1254 if ( sessLocal.mapSpawnData.serverInfo.GetBool( "si_pure" ) ) {
1255 client.clientState = SCS_PUREWAIT;
1256 if ( !SendReliablePureToClient( clientNum ) ) {
1257 client.clientState = SCS_CONNECTED;
1260 } else if ( idAsyncNetwork::verbose.GetInteger() ) {
1261 common->Printf( "ignore unreliable msg from client %d, wrong gameInit, old sequence\n", clientNum );
1266 client.acknowledgeSnapshotSequence = msg.ReadLong();
1268 if ( client.clientState == SCS_CONNECTED ) {
1270 // the client is in the right game
1271 client.clientState = SCS_INGAME;
1273 // send the user info of other clients
1274 for ( i = 0; i < MAX_ASYNC_CLIENTS; i++ ) {
1275 if ( clients[i].clientState >= SCS_CONNECTED && i != clientNum ) {
1276 SendUserInfoToClient( clientNum, i, sessLocal.mapSpawnData.userInfo[i] );
1280 // send synchronized cvars to client
1281 SendSyncedCvarsToClient( clientNum, sessLocal.mapSpawnData.syncedCVars );
1283 SendEnterGameToClient( clientNum );
1285 // get the client running in the game
1286 game->ServerClientBegin( clientNum );
1288 // write any reliable messages to initialize the client game state
1289 game->ServerWriteInitialReliableMessages( clientNum );
1290 } else if ( client.clientState == SCS_INGAME ) {
1292 // apply the last snapshot the client received
1293 if ( game->ServerApplySnapshot( clientNum, client.acknowledgeSnapshotSequence ) ) {
1294 SendApplySnapshotToClient( clientNum, client.acknowledgeSnapshotSequence );
1298 // process the unreliable message
1299 id = msg.ReadByte();
1301 case CLIENT_UNRELIABLE_MESSAGE_EMPTY: {
1302 if ( idAsyncNetwork::verbose.GetInteger() ) {
1303 common->Printf( "received empty message for client %d\n", clientNum );
1307 case CLIENT_UNRELIABLE_MESSAGE_PINGRESPONSE: {
1308 client.clientPing = realTime - msg.ReadLong();
1311 case CLIENT_UNRELIABLE_MESSAGE_USERCMD: {
1313 client.clientPrediction = msg.ReadShort();
1315 // read user commands
1316 clientGameFrame = msg.ReadLong();
1317 numUsercmds = msg.ReadByte();
1318 for ( last = NULL, i = clientGameFrame - numUsercmds + 1; i <= clientGameFrame; i++ ) {
1319 index = i & ( MAX_USERCMD_BACKUP - 1 );
1320 idAsyncNetwork::ReadUserCmdDelta( msg, userCmds[index][clientNum], last );
1321 userCmds[index][clientNum].gameFrame = i;
1322 userCmds[index][clientNum].duplicateCount = 0;
1323 if ( idAsyncNetwork::UsercmdInputChanged( userCmds[( i - 1 ) & ( MAX_USERCMD_BACKUP - 1 )][clientNum], userCmds[index][clientNum] ) ) {
1324 client.lastInputTime = serverTime;
1326 last = &userCmds[index][clientNum];
1330 client.gameFrame = last->gameFrame;
1331 client.gameTime = last->gameTime;
1334 if ( idAsyncNetwork::verbose.GetInteger() == 2 ) {
1335 common->Printf( "received user command for client %d, gameInitId = %d, gameFrame, %d gameTime %d\n", clientNum, clientGameInitId, client.gameFrame, client.gameTime );
1340 common->Printf( "unknown unreliable message %d from client %d\n", id, clientNum );
1348 idAsyncServer::ProcessReliableClientMessages
1351 void idAsyncServer::ProcessReliableClientMessages( int clientNum ) {
1353 byte msgBuf[MAX_MESSAGE_SIZE];
1356 serverClient_t &client = clients[clientNum];
1358 msg.Init( msgBuf, sizeof( msgBuf ) );
1360 while ( client.channel.GetReliableMessage( msg ) ) {
1361 id = msg.ReadByte();
1363 case CLIENT_RELIABLE_MESSAGE_CLIENTINFO: {
1365 msg.ReadDeltaDict( info, &sessLocal.mapSpawnData.userInfo[clientNum] );
1366 SendUserInfoBroadcast( clientNum, info );
1369 case CLIENT_RELIABLE_MESSAGE_PRINT: {
1370 char string[MAX_STRING_CHARS];
1371 msg.ReadString( string, sizeof( string ) );
1372 common->Printf( "%s\n", string );
1375 case CLIENT_RELIABLE_MESSAGE_DISCONNECT: {
1376 DropClient( clientNum, "#str_07138" );
1379 case CLIENT_RELIABLE_MESSAGE_PURE: {
1380 // we get this message once the client has successfully updated it's pure list
1381 ProcessReliablePure( clientNum, msg );
1385 // pass reliable message on to game code
1386 game->ServerProcessReliableMessage( clientNum, msg );
1395 idAsyncServer::ProcessAuthMessage
1398 void idAsyncServer::ProcessAuthMessage( const idBitMsg &msg ) {
1399 netadr_t client_from;
1400 char client_guid[ 12 ], string[ MAX_STRING_CHARS ];
1403 authReplyMsg_t replyMsg = AUTH_REPLY_WAITING;
1404 idStr replyPrintMsg;
1406 reply = (authReply_t)msg.ReadByte();
1407 if ( reply <= 0 || reply >= AUTH_MAXSTATES ) {
1408 common->DPrintf( "auth: invalid reply %d\n", reply );
1411 clientId = msg.ReadShort( );
1412 msg.ReadNetadr( &client_from );
1413 msg.ReadString( client_guid, sizeof( client_guid ) );
1414 if ( reply != AUTH_OK ) {
1415 replyMsg = (authReplyMsg_t)msg.ReadByte();
1416 if ( replyMsg <= 0 || replyMsg >= AUTH_REPLY_MAXSTATES ) {
1417 common->DPrintf( "auth: invalid reply msg %d\n", replyMsg );
1420 if ( replyMsg == AUTH_REPLY_PRINT ) {
1421 msg.ReadString( string, MAX_STRING_CHARS );
1422 replyPrintMsg = string;
1426 lastAuthTime = serverTime;
1428 // no message parsing below
1430 for ( i = 0; i < MAX_CHALLENGES; i++ ) {
1431 if ( !challenges[i].connected && challenges[ i ].clientId == clientId ) {
1432 // return if something is wrong
1433 // break if we have found a valid auth
1434 if ( !strlen( challenges[ i ].guid ) ) {
1435 common->DPrintf( "auth: client %s has no guid yet\n", Sys_NetAdrToString( challenges[ i ].address ) );
1438 if ( idStr::Cmp( challenges[ i ].guid, client_guid ) ) {
1439 common->DPrintf( "auth: client %s %s not matched, auth server says guid %s\n", Sys_NetAdrToString( challenges[ i ].address ), challenges[i].guid, client_guid );
1442 if ( !Sys_CompareNetAdrBase( client_from, challenges[i].address ) ) {
1443 // let auth work when server and master don't see the same IP
1444 common->DPrintf( "auth: matched guid '%s' for != IPs %s and %s\n", client_guid, Sys_NetAdrToString( client_from ), Sys_NetAdrToString( challenges[i].address ) );
1449 if ( i >= MAX_CHALLENGES ) {
1450 common->DPrintf( "auth: failed client lookup %s %s\n", Sys_NetAdrToString( client_from ), client_guid );
1454 if ( challenges[ i ].authState != CDK_WAIT ) {
1455 common->DWarning( "auth: challenge 0x%x %s authState %d != CDK_WAIT", challenges[ i ].challenge, Sys_NetAdrToString( challenges[ i ].address ), challenges[ i ].authState );
1459 idStr::snPrintf( challenges[ i ].guid, 12, client_guid );
1460 if ( reply == AUTH_OK ) {
1461 challenges[ i ].authState = CDK_OK;
1462 common->Printf( "client %s %s is authed\n", Sys_NetAdrToString( client_from ), client_guid );
1465 if ( replyMsg != AUTH_REPLY_PRINT ) {
1466 msg = authReplyMsg[ replyMsg ];
1468 msg = replyPrintMsg.c_str();
1470 // maybe localize it
1471 const char *l_msg = common->GetLanguageDict()->GetString( msg );
1472 common->DPrintf( "auth: client %s %s - %s %s\n", Sys_NetAdrToString( client_from ), client_guid, authReplyStr[ reply ], l_msg );
1473 challenges[ i ].authReply = reply;
1474 challenges[ i ].authReplyMsg = replyMsg;
1475 challenges[ i ].authReplyPrint = replyPrintMsg;
1481 idAsyncServer::ProcessChallengeMessage
1484 void idAsyncServer::ProcessChallengeMessage( const netadr_t from, const idBitMsg &msg ) {
1485 int i, clientId, oldest, oldestTime;
1487 byte msgBuf[MAX_MESSAGE_SIZE];
1489 clientId = msg.ReadLong();
1492 oldestTime = 0x7fffffff;
1494 // see if we already have a challenge for this ip
1495 for ( i = 0; i < MAX_CHALLENGES; i++ ) {
1496 if ( !challenges[i].connected && Sys_CompareNetAdrBase( from, challenges[i].address ) && clientId == challenges[i].clientId ) {
1499 if ( challenges[i].time < oldestTime ) {
1500 oldestTime = challenges[i].time;
1505 if ( i >= MAX_CHALLENGES ) {
1506 // this is the first time this client has asked for a challenge
1508 challenges[i].address = from;
1509 challenges[i].clientId = clientId;
1510 challenges[i].challenge = ( (rand() << 16) ^ rand() ) ^ serverTime;
1511 challenges[i].time = serverTime;
1512 challenges[i].connected = false;
1513 challenges[i].authState = CDK_WAIT;
1514 challenges[i].authReply = AUTH_NONE;
1515 challenges[i].authReplyMsg = AUTH_REPLY_WAITING;
1516 challenges[i].authReplyPrint = "";
1517 challenges[i].guid[0] = '\0';
1519 challenges[i].pingTime = serverTime;
1521 common->Printf( "sending challenge 0x%x to %s\n", challenges[i].challenge, Sys_NetAdrToString( from ) );
1523 outMsg.Init( msgBuf, sizeof( msgBuf ) );
1524 outMsg.WriteShort( CONNECTIONLESS_MESSAGE_ID );
1525 outMsg.WriteString( "challengeResponse" );
1526 outMsg.WriteLong( challenges[i].challenge );
1527 outMsg.WriteShort( serverId );
1528 outMsg.WriteString( cvarSystem->GetCVarString( "fs_game_base" ) );
1529 outMsg.WriteString( cvarSystem->GetCVarString( "fs_game" ) );
1531 serverPort.SendPacket( from, outMsg.GetData(), outMsg.GetSize() );
1533 if ( Sys_IsLANAddress( from ) ) {
1534 // no CD Key check for LAN clients
1535 challenges[i].authState = CDK_OK;
1537 if ( idAsyncNetwork::LANServer.GetBool() ) {
1538 common->Printf( "net_LANServer is enabled. Client %s is not a LAN address, will be rejected\n", Sys_NetAdrToString( from ) );
1539 challenges[ i ].authState = CDK_ONLYLAN;
1541 // emit a cd key confirmation request
1542 outMsg.BeginWriting();
1543 outMsg.WriteShort( CONNECTIONLESS_MESSAGE_ID );
1544 outMsg.WriteString( "srvAuth" );
1545 outMsg.WriteLong( ASYNC_PROTOCOL_VERSION );
1546 outMsg.WriteNetadr( from );
1547 outMsg.WriteLong( -1 ); // this identifies "challenge" auth vs "connect" auth
1548 // protocol 1.37 addition
1549 outMsg.WriteByte( fileSystem->RunningD3XP() );
1550 serverPort.SendPacket( idAsyncNetwork::GetMasterAddress(), outMsg.GetData(), outMsg.GetSize() );
1557 idAsyncServer::SendPureServerMessage
1560 bool idAsyncServer::SendPureServerMessage( const netadr_t to, int OS ) {
1562 byte msgBuf[ MAX_MESSAGE_SIZE ];
1563 int serverChecksums[ MAX_PURE_PAKS ];
1564 int gamePakChecksum;
1567 fileSystem->GetPureServerChecksums( serverChecksums, OS, &gamePakChecksum );
1568 if ( !serverChecksums[ 0 ] ) {
1569 // happens if you run fully expanded assets with si_pure 1
1570 common->Warning( "pure server has no pak files referenced" );
1573 common->DPrintf( "client %s: sending pure pak list\n", Sys_NetAdrToString( to ) );
1575 // send our list of required paks
1576 outMsg.Init( msgBuf, sizeof( msgBuf ) );
1577 outMsg.WriteShort( CONNECTIONLESS_MESSAGE_ID );
1578 outMsg.WriteString( "pureServer" );
1581 while ( serverChecksums[ i ] ) {
1582 outMsg.WriteLong( serverChecksums[ i++ ] );
1584 outMsg.WriteLong( 0 );
1586 // write the pak checksum for game code
1587 outMsg.WriteLong( gamePakChecksum );
1589 serverPort.SendPacket( to, outMsg.GetData(), outMsg.GetSize() );
1595 idAsyncServer::SendReliablePureToClient
1598 bool idAsyncServer::SendReliablePureToClient( int clientNum ) {
1600 byte msgBuf[ MAX_MESSAGE_SIZE ];
1601 int serverChecksums[ MAX_PURE_PAKS ];
1603 int gamePakChecksum;
1605 fileSystem->GetPureServerChecksums( serverChecksums, clients[ clientNum ].OS, &gamePakChecksum );
1606 if ( !serverChecksums[ 0 ] ) {
1607 // happens if you run fully expanded assets with si_pure 1
1608 common->Warning( "pure server has no pak files referenced" );
1612 common->DPrintf( "client %d: sending pure pak list (reliable channel) @ gameInitId %d\n", clientNum, gameInitId );
1614 msg.Init( msgBuf, sizeof( msgBuf ) );
1615 msg.WriteByte( SERVER_RELIABLE_MESSAGE_PURE );
1617 msg.WriteLong( gameInitId );
1620 while ( serverChecksums[ i ] ) {
1621 msg.WriteLong( serverChecksums[ i++ ] );
1624 msg.WriteLong( gamePakChecksum );
1626 SendReliableMessage( clientNum, msg );
1633 idAsyncServer::ValidateChallenge
1636 int idAsyncServer::ValidateChallenge( const netadr_t from, int challenge, int clientId ) {
1638 for ( i = 0; i < MAX_ASYNC_CLIENTS; i++ ) {
1639 const serverClient_t &client = clients[i];
1641 if ( client.clientState == SCS_FREE ) {
1644 if ( Sys_CompareNetAdrBase( from, client.channel.GetRemoteAddress() ) &&
1645 ( clientId == client.clientId || from.port == client.channel.GetRemoteAddress().port ) ) {
1646 if ( serverTime - client.lastConnectTime < MIN_RECONNECT_TIME ) {
1647 common->Printf( "%s: reconnect rejected : too soon\n", Sys_NetAdrToString( from ) );
1654 for ( i = 0; i < MAX_CHALLENGES; i++ ) {
1655 if ( Sys_CompareNetAdrBase( from, challenges[i].address ) && from.port == challenges[i].address.port ) {
1656 if ( challenge == challenges[i].challenge ) {
1661 if ( i == MAX_CHALLENGES ) {
1662 PrintOOB( from, SERVER_PRINT_BADCHALLENGE, "#str_04840" );
1670 idAsyncServer::ProcessConnectMessage
1673 void idAsyncServer::ProcessConnectMessage( const netadr_t from, const idBitMsg &msg ) {
1674 int clientNum, protocol, clientDataChecksum, challenge, clientId, ping, clientRate;
1676 byte msgBuf[ MAX_MESSAGE_SIZE ];
1678 char password[ 17 ];
1679 int i, ichallenge, islot, OS, numClients;
1681 protocol = msg.ReadLong();
1682 OS = msg.ReadShort();
1684 // check the protocol version
1685 if ( protocol != ASYNC_PROTOCOL_VERSION ) {
1686 // that's a msg back to a client, we don't know about it's localization, so send english
1687 PrintOOB( from, SERVER_PRINT_BADPROTOCOL, va( "server uses protocol %d.%d\n", ASYNC_PROTOCOL_MAJOR, ASYNC_PROTOCOL_MINOR ) );
1691 clientDataChecksum = msg.ReadLong();
1692 challenge = msg.ReadLong();
1693 clientId = msg.ReadShort();
1694 clientRate = msg.ReadLong();
1696 // check the client data - only for non pure servers
1697 if ( !sessLocal.mapSpawnData.serverInfo.GetInt( "si_pure" ) && clientDataChecksum != serverDataChecksum ) {
1698 PrintOOB( from, SERVER_PRINT_MISC, "#str_04842" );
1702 if ( ( ichallenge = ValidateChallenge( from, challenge, clientId ) ) == -1 ) {
1705 challenges[ ichallenge ].OS = OS;
1707 msg.ReadString( guid, sizeof( guid ) );
1709 switch ( challenges[ ichallenge ].authState ) {
1711 SendPureServerMessage( from, OS );
1714 common->DPrintf( "%s: not a lan client\n", Sys_NetAdrToString( from ) );
1715 PrintOOB( from, SERVER_PRINT_MISC, "#str_04843" );
1718 if ( challenges[ ichallenge ].authReply == AUTH_NONE && Min( serverTime - lastAuthTime, serverTime - challenges[ ichallenge ].time ) > AUTHORIZE_TIMEOUT ) {
1719 common->DPrintf( "%s: Authorize server timed out\n", Sys_NetAdrToString( from ) );
1720 break; // will continue with the connecting process
1722 const char *msg, *l_msg;
1723 if ( challenges[ ichallenge ].authReplyMsg != AUTH_REPLY_PRINT ) {
1724 msg = authReplyMsg[ challenges[ ichallenge ].authReplyMsg ];
1726 msg = challenges[ ichallenge ].authReplyPrint.c_str();
1728 l_msg = common->GetLanguageDict()->GetString( msg );
1730 common->DPrintf( "%s: %s\n", Sys_NetAdrToString( from ), l_msg );
1732 if ( challenges[ ichallenge ].authReplyMsg == AUTH_REPLY_UNKNOWN || challenges[ ichallenge ].authReplyMsg == AUTH_REPLY_WAITING ) {
1733 // the client may be trying to connect to us in LAN mode, and the server disagrees
1734 // let the client know so it would switch to authed connection
1736 byte msgBuf[ MAX_MESSAGE_SIZE ];
1737 outMsg.Init( msgBuf, sizeof( msgBuf ) );
1738 outMsg.WriteShort( CONNECTIONLESS_MESSAGE_ID );
1739 outMsg.WriteString( "authrequired" );
1740 serverPort.SendPacket( from, outMsg.GetData(), outMsg.GetSize() );
1743 PrintOOB( from, SERVER_PRINT_MISC, msg );
1745 // update the guid in the challenges
1746 idStr::snPrintf( challenges[ ichallenge ].guid, sizeof( challenges[ ichallenge ].guid ), guid );
1748 // once auth replied denied, stop sending further requests
1749 if ( challenges[ ichallenge ].authReply != AUTH_DENY ) {
1750 // emit a cd key confirmation request
1751 outMsg.Init( msgBuf, sizeof( msgBuf ) );
1752 outMsg.WriteShort( CONNECTIONLESS_MESSAGE_ID );
1753 outMsg.WriteString( "srvAuth" );
1754 outMsg.WriteLong( ASYNC_PROTOCOL_VERSION );
1755 outMsg.WriteNetadr( from );
1756 outMsg.WriteLong( clientId );
1757 outMsg.WriteString( guid );
1758 // protocol 1.37 addition
1759 outMsg.WriteByte( fileSystem->RunningD3XP() );
1760 serverPort.SendPacket( idAsyncNetwork::GetMasterAddress(), outMsg.GetData(), outMsg.GetSize() );
1764 assert( challenges[ ichallenge ].authState == CDK_OK || challenges[ ichallenge ].authState == CDK_PUREOK );
1768 for ( i = 0; i < MAX_ASYNC_CLIENTS; i++ ) {
1769 serverClient_t &client = clients[ i ];
1770 if ( client.clientState >= SCS_PUREWAIT ) {
1775 // game may be passworded, client banned by IP or GUID
1776 // if authState == CDK_PUREOK, the check was already performed once before entering pure checks
1777 // but meanwhile, the max players may have been reached
1778 msg.ReadString( password, sizeof( password ) );
1779 char reason[MAX_STRING_CHARS];
1780 allowReply_t reply = game->ServerAllowClient( numClients, Sys_NetAdrToString( from ), guid, password, reason );
1781 if ( reply != ALLOW_YES ) {
1782 common->DPrintf( "game denied connection for %s\n", Sys_NetAdrToString( from ) );
1784 // SERVER_PRINT_GAMEDENY passes the game opcode through. Don't use PrintOOB
1785 outMsg.Init( msgBuf, sizeof( msgBuf ) );
1786 outMsg.WriteShort( CONNECTIONLESS_MESSAGE_ID );
1787 outMsg.WriteString( "print" );
1788 outMsg.WriteLong( SERVER_PRINT_GAMEDENY );
1789 outMsg.WriteLong( reply );
1790 outMsg.WriteString( reason );
1791 serverPort.SendPacket( from, outMsg.GetData(), outMsg.GetSize() );
1796 // enter pure checks if necessary
1797 if ( sessLocal.mapSpawnData.serverInfo.GetInt( "si_pure" ) && challenges[ ichallenge ].authState != CDK_PUREOK ) {
1798 if ( SendPureServerMessage( from, OS ) ) {
1799 challenges[ ichallenge ].authState = CDK_PUREWAIT;
1804 // push back decl checksum here when running pure. just an additional safe check
1805 if ( sessLocal.mapSpawnData.serverInfo.GetInt( "si_pure" ) && clientDataChecksum != serverDataChecksum ) {
1806 PrintOOB( from, SERVER_PRINT_MISC, "#str_04844" );
1810 ping = serverTime - challenges[ ichallenge ].pingTime;
1811 common->Printf( "challenge from %s connecting with %d ping\n", Sys_NetAdrToString( from ), ping );
1812 challenges[ ichallenge ].connected = true;
1814 // find a slot for the client
1815 for ( islot = 0; islot < 3; islot++ ) {
1816 for ( clientNum = 0; clientNum < MAX_ASYNC_CLIENTS; clientNum++ ) {
1817 serverClient_t &client = clients[ clientNum ];
1820 // if this slot uses the same IP and port
1821 if ( Sys_CompareNetAdrBase( from, client.channel.GetRemoteAddress() ) &&
1822 ( clientId == client.clientId || from.port == client.channel.GetRemoteAddress().port ) ) {
1825 } else if ( islot == 1 ) {
1826 // if this client is not connected and the slot uses the same IP
1827 if ( client.clientState >= SCS_PUREWAIT ) {
1830 if ( Sys_CompareNetAdrBase( from, client.channel.GetRemoteAddress() ) ) {
1833 } else if ( islot == 2 ) {
1834 // if this slot is free
1835 if ( client.clientState == SCS_FREE ) {
1841 if ( clientNum < MAX_ASYNC_CLIENTS ) {
1843 clients[ clientNum ].channel.Init( from, serverId );
1844 clients[ clientNum ].OS = OS;
1845 strncpy( clients[ clientNum ].guid, guid, 12 );
1846 clients[ clientNum ].guid[11] = 0;
1851 // if no free spots available
1852 if ( clientNum >= MAX_ASYNC_CLIENTS ) {
1853 PrintOOB( from, SERVER_PRINT_MISC, "#str_04845" );
1857 common->Printf( "sending connect response to %s\n", Sys_NetAdrToString( from ) );
1859 // send connect response message
1860 outMsg.Init( msgBuf, sizeof( msgBuf ) );
1861 outMsg.WriteShort( CONNECTIONLESS_MESSAGE_ID );
1862 outMsg.WriteString( "connectResponse" );
1863 outMsg.WriteLong( clientNum );
1864 outMsg.WriteLong( gameInitId );
1865 outMsg.WriteLong( gameFrame );
1866 outMsg.WriteLong( gameTime );
1867 outMsg.WriteDeltaDict( sessLocal.mapSpawnData.serverInfo, NULL );
1869 serverPort.SendPacket( from, outMsg.GetData(), outMsg.GetSize() );
1871 InitClient( clientNum, clientId, clientRate );
1873 clients[clientNum].gameInitSequence = 1;
1874 clients[clientNum].snapshotSequence = 1;
1876 // clear the challenge struct so a reconnect from this client IP starts clean
1877 memset( &challenges[ ichallenge ], 0, sizeof( challenge_t ) );
1882 idAsyncServer::VerifyChecksumMessage
1885 bool idAsyncServer::VerifyChecksumMessage( int clientNum, const netadr_t *from, const idBitMsg &msg, idStr &reply, int OS ) {
1886 int i, numChecksums;
1887 int checksums[ MAX_PURE_PAKS ];
1888 int gamePakChecksum;
1889 int serverChecksums[ MAX_PURE_PAKS ];
1890 int serverGamePakChecksum;
1892 // pak checksums, in a 0-terminated list
1895 i = msg.ReadLong( );
1896 checksums[ numChecksums++ ] = i;
1897 // just to make sure a broken client doesn't crash us
1898 if ( numChecksums >= MAX_PURE_PAKS ) {
1899 common->Warning( "MAX_PURE_PAKS ( %d ) exceeded in idAsyncServer::ProcessPureMessage\n", MAX_PURE_PAKS );
1900 sprintf( reply, "#str_07144" );
1906 // code pak checksum
1907 gamePakChecksum = msg.ReadLong( );
1909 fileSystem->GetPureServerChecksums( serverChecksums, OS, &serverGamePakChecksum );
1910 assert( serverChecksums[ 0 ] );
1912 // compare the lists
1913 if ( serverGamePakChecksum != gamePakChecksum ) {
1914 common->Printf( "client %s: invalid game code pak ( 0x%x )\n", from ? Sys_NetAdrToString( *from ) : va( "%d", clientNum ), gamePakChecksum );
1915 sprintf( reply, "#str_07145" );
1918 for ( i = 0; serverChecksums[ i ] != 0; i++ ) {
1919 if ( checksums[ i ] != serverChecksums[ i ] ) {
1920 common->DPrintf( "client %s: pak missing ( 0x%x )\n", from ? Sys_NetAdrToString( *from ) : va( "%d", clientNum ), serverChecksums[ i ] );
1921 sprintf( reply, "pak missing ( 0x%x )\n", serverChecksums[ i ] );
1925 if ( checksums[ i ] != 0 ) {
1926 common->DPrintf( "client %s: extra pak file referenced ( 0x%x )\n", from ? Sys_NetAdrToString( *from ) : va( "%d", clientNum ), checksums[ i ] );
1927 sprintf( reply, "extra pak file referenced ( 0x%x )\n", checksums[ i ] );
1935 idAsyncServer::ProcessPureMessage
1938 void idAsyncServer::ProcessPureMessage( const netadr_t from, const idBitMsg &msg ) {
1939 int iclient, challenge, clientId;
1942 challenge = msg.ReadLong();
1943 clientId = msg.ReadShort();
1945 if ( ( iclient = ValidateChallenge( from, challenge, clientId ) ) == -1 ) {
1949 if ( challenges[ iclient ].authState != CDK_PUREWAIT ) {
1950 common->DPrintf( "client %s: got pure message, not in CDK_PUREWAIT\n", Sys_NetAdrToString( from ) );
1954 if ( !VerifyChecksumMessage( iclient, &from, msg, reply, challenges[ iclient ].OS ) ) {
1955 PrintOOB( from, SERVER_PRINT_MISC, reply );
1959 common->DPrintf( "client %s: passed pure checks\n", Sys_NetAdrToString( from ) );
1960 challenges[ iclient ].authState = CDK_PUREOK; // next connect message will get the client through completely
1965 idAsyncServer::ProcessReliablePure
1968 void idAsyncServer::ProcessReliablePure( int clientNum, const idBitMsg &msg ) {
1971 byte msgBuf[MAX_MESSAGE_SIZE];
1972 int clientGameInitId;
1974 clientGameInitId = msg.ReadLong();
1975 if ( clientGameInitId != gameInitId ) {
1976 common->DPrintf( "client %d: ignoring reliable pure from an old gameInit (%d)\n", clientNum, clientGameInitId );
1980 if ( clients[ clientNum ].clientState != SCS_PUREWAIT ) {
1981 // should not happen unless something is very wrong. still, don't let this crash us, just get rid of the client
1982 common->DPrintf( "client %d: got reliable pure while != SCS_PUREWAIT, sending a reload\n", clientNum );
1983 outMsg.Init( msgBuf, sizeof( msgBuf ) );
1984 outMsg.WriteByte( SERVER_RELIABLE_MESSAGE_RELOAD );
1985 SendReliableMessage( clientNum, msg );
1986 // go back to SCS_CONNECTED to sleep on the client until it goes away for a reconnect
1987 clients[ clientNum ].clientState = SCS_CONNECTED;
1991 if ( !VerifyChecksumMessage( clientNum, NULL, msg, reply, clients[ clientNum ].OS ) ) {
1992 DropClient( clientNum, reply );
1995 common->DPrintf( "client %d: passed pure checks (reliable channel)\n", clientNum );
1996 clients[ clientNum ].clientState = SCS_CONNECTED;
2001 idAsyncServer::RemoteConsoleOutput
2004 void idAsyncServer::RemoteConsoleOutput( const char *string ) {
2005 noRconOutput = false;
2006 PrintOOB( rconAddress, SERVER_PRINT_RCON, string );
2014 void RConRedirect( const char *string ) {
2015 idAsyncNetwork::server.RemoteConsoleOutput( string );
2020 idAsyncServer::ProcessRemoteConsoleMessage
2023 void idAsyncServer::ProcessRemoteConsoleMessage( const netadr_t from, const idBitMsg &msg ) {
2026 char string[MAX_STRING_CHARS];
2028 if ( idAsyncNetwork::serverRemoteConsolePassword.GetString()[0] == '\0' ) {
2029 PrintOOB( from, SERVER_PRINT_MISC, "#str_04846" );
2033 msg.ReadString( string, sizeof( string ) );
2035 if ( idStr::Icmp( string, idAsyncNetwork::serverRemoteConsolePassword.GetString() ) != 0 ) {
2036 PrintOOB( from, SERVER_PRINT_MISC, "#str_04847" );
2040 msg.ReadString( string, sizeof( string ) );
2042 common->Printf( "rcon from %s: %s\n", Sys_NetAdrToString( from ), string );
2045 noRconOutput = true;
2046 common->BeginRedirect( (char *)msgBuf, sizeof( msgBuf ), RConRedirect );
2048 cmdSystem->BufferCommandText( CMD_EXEC_NOW, string );
2050 common->EndRedirect();
2052 if ( noRconOutput ) {
2053 PrintOOB( rconAddress, SERVER_PRINT_RCON, "#str_04848" );
2059 idAsyncServer::ProcessGetInfoMessage
2062 void idAsyncServer::ProcessGetInfoMessage( const netadr_t from, const idBitMsg &msg ) {
2065 byte msgBuf[MAX_MESSAGE_SIZE];
2067 if ( !IsActive() ) {
2071 common->DPrintf( "Sending info response to %s\n", Sys_NetAdrToString( from ) );
2073 challenge = msg.ReadLong();
2075 outMsg.Init( msgBuf, sizeof( msgBuf ) );
2076 outMsg.WriteShort( CONNECTIONLESS_MESSAGE_ID );
2077 outMsg.WriteString( "infoResponse" );
2078 outMsg.WriteLong( challenge );
2079 outMsg.WriteLong( ASYNC_PROTOCOL_VERSION );
2080 outMsg.WriteDeltaDict( sessLocal.mapSpawnData.serverInfo, NULL );
2082 for ( i = 0; i < MAX_ASYNC_CLIENTS; i++ ) {
2083 serverClient_t &client = clients[i];
2085 if ( client.clientState < SCS_CONNECTED ) {
2089 outMsg.WriteByte( i );
2090 outMsg.WriteShort( client.clientPing );
2091 outMsg.WriteLong( client.channel.GetMaxOutgoingRate() );
2092 outMsg.WriteString( sessLocal.mapSpawnData.userInfo[i].GetString( "ui_name", "Player" ) );
2094 outMsg.WriteByte( MAX_ASYNC_CLIENTS );
2095 outMsg.WriteLong( fileSystem->GetOSMask() );
2097 serverPort.SendPacket( from, outMsg.GetData(), outMsg.GetSize() );
2102 idAsyncServer::PrintLocalServerInfo
2103 see (client) "getInfo" -> (server) "infoResponse" -> (client)ProcessGetInfoMessage
2106 void idAsyncServer::PrintLocalServerInfo( void ) {
2109 common->Printf( "server '%s' IP = %s\nprotocol %d.%d OS mask 0x%x\n",
2110 sessLocal.mapSpawnData.serverInfo.GetString( "si_name" ),
2111 Sys_NetAdrToString( serverPort.GetAdr() ),
2112 ASYNC_PROTOCOL_MAJOR,
2113 ASYNC_PROTOCOL_MINOR,
2114 fileSystem->GetOSMask() );
2115 sessLocal.mapSpawnData.serverInfo.Print();
2116 for ( i = 0; i < MAX_ASYNC_CLIENTS; i++ ) {
2117 serverClient_t &client = clients[i];
2118 if ( client.clientState < SCS_CONNECTED ) {
2121 common->Printf( "client %2d: %s, ping = %d, rate = %d\n", i,
2122 sessLocal.mapSpawnData.userInfo[i].GetString( "ui_name", "Player" ),
2123 client.clientPing, client.channel.GetMaxOutgoingRate() );
2129 idAsyncServer::ConnectionlessMessage
2132 bool idAsyncServer::ConnectionlessMessage( const netadr_t from, const idBitMsg &msg ) {
2133 char string[MAX_STRING_CHARS*2]; // M. Quinn - Even Balance - PB Packets need more than 1024
2135 msg.ReadString( string, sizeof( string ) );
2138 if ( idStr::Icmp( string, "getInfo" ) == 0 ) {
2139 ProcessGetInfoMessage( from, msg );
2144 if ( idStr::Icmp( string, "rcon" ) == 0 ) {
2145 ProcessRemoteConsoleMessage( from, msg );
2150 PrintOOB( from, SERVER_PRINT_MISC, "#str_04849" );
2154 // challenge from a client
2155 if ( idStr::Icmp( string, "challenge" ) == 0 ) {
2156 ProcessChallengeMessage( from, msg );
2160 // connect from a client
2161 if ( idStr::Icmp( string, "connect" ) == 0 ) {
2162 ProcessConnectMessage( from, msg );
2166 // pure mesasge from a client
2167 if ( idStr::Icmp( string, "pureClient" ) == 0 ) {
2168 ProcessPureMessage( from, msg );
2173 if ( idStr::Icmp( string, "downloadRequest" ) == 0 ) {
2174 ProcessDownloadRequestMessage( from, msg );
2178 if ( idStr::Icmp( string, "auth" ) == 0 ) {
2179 if ( !Sys_CompareNetAdrBase( from, idAsyncNetwork::GetMasterAddress() ) ) {
2180 common->Printf( "auth: bad source %s\n", Sys_NetAdrToString( from ) );
2183 if ( idAsyncNetwork::LANServer.GetBool() ) {
2184 common->Printf( "auth message from master. net_LANServer is enabled, ignored.\n" );
2186 ProcessAuthMessage( msg );
2195 idAsyncServer::ProcessMessage
2198 bool idAsyncServer::ProcessMessage( const netadr_t from, idBitMsg &msg ) {
2199 int i, id, sequence;
2201 byte msgBuf[MAX_MESSAGE_SIZE];
2203 id = msg.ReadShort();
2205 // check for a connectionless message
2206 if ( id == CONNECTIONLESS_MESSAGE_ID ) {
2207 return ConnectionlessMessage( from, msg );
2210 if ( msg.GetRemaingData() < 4 ) {
2211 common->DPrintf( "%s: tiny packet\n", Sys_NetAdrToString( from ) );
2215 // find out which client the message is from
2216 for ( i = 0; i < MAX_ASYNC_CLIENTS; i++ ) {
2217 serverClient_t &client = clients[i];
2219 if ( client.clientState == SCS_FREE ) {
2223 // This does not compare the UDP port, because some address translating
2224 // routers will change that at arbitrary times.
2225 if ( !Sys_CompareNetAdrBase( from, client.channel.GetRemoteAddress() ) || id != client.clientId ) {
2229 // make sure it is a valid, in sequence packet
2230 if ( !client.channel.Process( from, serverTime, msg, sequence ) ) {
2231 return false; // out of order, duplicated, fragment, etc.
2234 // zombie clients still need to do the channel processing to make sure they don't
2235 // need to retransmit the final reliable message, but they don't do any other processing
2236 if ( client.clientState == SCS_ZOMBIE ) {
2240 client.lastPacketTime = serverTime;
2242 ProcessReliableClientMessages( i );
2243 ProcessUnreliableClientMessage( i, msg );
2248 // if we received a sequenced packet from an address we don't recognize,
2249 // send an out of band disconnect packet to it
2250 outMsg.Init( msgBuf, sizeof( msgBuf ) );
2251 outMsg.WriteShort( CONNECTIONLESS_MESSAGE_ID );
2252 outMsg.WriteString( "disconnect" );
2253 serverPort.SendPacket( from, outMsg.GetData(), outMsg.GetSize() );
2260 idAsyncServer::SendReliableGameMessage
2263 void idAsyncServer::SendReliableGameMessage( int clientNum, const idBitMsg &msg ) {
2266 byte msgBuf[MAX_MESSAGE_SIZE];
2268 outMsg.Init( msgBuf, sizeof( msgBuf ) );
2269 outMsg.WriteByte( SERVER_RELIABLE_MESSAGE_GAME );
2270 outMsg.WriteData( msg.GetData(), msg.GetSize() );
2272 if ( clientNum >= 0 && clientNum < MAX_ASYNC_CLIENTS ) {
2273 if ( clients[clientNum].clientState == SCS_INGAME ) {
2274 SendReliableMessage( clientNum, outMsg );
2279 for ( i = 0; i < MAX_ASYNC_CLIENTS; i++ ) {
2280 if ( clients[i].clientState != SCS_INGAME ) {
2283 SendReliableMessage( i, outMsg );
2289 idAsyncServer::LocalClientSendReliableMessageExcluding
2292 void idAsyncServer::SendReliableGameMessageExcluding( int clientNum, const idBitMsg &msg ) {
2295 byte msgBuf[MAX_MESSAGE_SIZE];
2297 assert( clientNum >= 0 && clientNum < MAX_ASYNC_CLIENTS );
2299 outMsg.Init( msgBuf, sizeof( msgBuf ) );
2300 outMsg.WriteByte( SERVER_RELIABLE_MESSAGE_GAME );
2301 outMsg.WriteData( msg.GetData(), msg.GetSize() );
2303 for ( i = 0; i < MAX_ASYNC_CLIENTS; i++ ) {
2304 if ( i == clientNum ) {
2307 if ( clients[i].clientState != SCS_INGAME ) {
2310 SendReliableMessage( i, outMsg );
2316 idAsyncServer::LocalClientSendReliableMessage
2319 void idAsyncServer::LocalClientSendReliableMessage( const idBitMsg &msg ) {
2320 if ( localClientNum < 0 ) {
2321 common->Printf( "LocalClientSendReliableMessage: no local client\n" );
2324 game->ServerProcessReliableMessage( localClientNum, msg );
2329 idAsyncServer::ProcessConnectionLessMessages
2332 void idAsyncServer::ProcessConnectionLessMessages( void ) {
2335 byte msgBuf[MAX_MESSAGE_SIZE];
2338 if ( !serverPort.GetPort() ) {
2342 while( serverPort.GetPacket( from, msgBuf, size, sizeof( msgBuf ) ) ) {
2343 msg.Init( msgBuf, sizeof( msgBuf ) );
2344 msg.SetSize( size );
2346 id = msg.ReadShort();
2347 if ( id == CONNECTIONLESS_MESSAGE_ID ) {
2348 ConnectionlessMessage( from, msg );
2355 idAsyncServer::UpdateTime
2358 int idAsyncServer::UpdateTime( int clamp ) {
2361 time = Sys_Milliseconds();
2362 msec = idMath::ClampInt( 0, clamp, time - realTime );
2370 idAsyncServer::RunFrame
2373 void idAsyncServer::RunFrame( void ) {
2377 byte msgBuf[MAX_MESSAGE_SIZE];
2379 int outgoingRate, incomingRate;
2380 float outgoingCompression, incomingCompression;
2382 msec = UpdateTime( 100 );
2384 if ( !serverPort.GetPort() ) {
2389 ProcessConnectionLessMessages();
2393 gameTimeResidual += msec;
2395 // spin in place processing incoming packets until enough time lapsed to run a new game frame
2400 // blocking read with game time residual timeout
2401 newPacket = serverPort.GetPacketBlocking( from, msgBuf, size, sizeof( msgBuf ), USERCMD_MSEC - gameTimeResidual - 1 );
2403 msg.Init( msgBuf, sizeof( msgBuf ) );
2404 msg.SetSize( size );
2406 if ( ProcessMessage( from, msg ) ) {
2407 return; // return because rcon was used
2411 msec = UpdateTime( 100 );
2412 gameTimeResidual += msec;
2414 } while( newPacket );
2416 } while( gameTimeResidual < USERCMD_MSEC );
2418 // send heart beat to master servers
2421 // check for clients that timed out
2422 CheckClientTimeouts();
2424 if ( idAsyncNetwork::idleServer.GetBool() == ( !GetNumClients() || GetNumIdleClients() != GetNumClients() ) ) {
2425 idAsyncNetwork::idleServer.SetBool( !idAsyncNetwork::idleServer.GetBool() );
2426 // the need to propagate right away, only this
2427 sessLocal.mapSpawnData.serverInfo.Set( "si_idleServer", idAsyncNetwork::idleServer.GetString() );
2428 game->SetServerInfo( sessLocal.mapSpawnData.serverInfo );
2431 // make sure the time doesn't wrap
2432 if ( serverTime > 0x70000000 ) {
2437 // check for synchronized cvar changes
2438 if ( cvarSystem->GetModifiedFlags() & CVAR_NETWORKSYNC ) {
2440 newCvars = *cvarSystem->MoveCVarsToDict( CVAR_NETWORKSYNC );
2441 SendSyncedCvarsBroadcast( newCvars );
2442 cvarSystem->ClearModifiedFlags( CVAR_NETWORKSYNC );
2445 // check for user info changes of the local client
2446 if ( cvarSystem->GetModifiedFlags() & CVAR_USERINFO ) {
2447 if ( localClientNum >= 0 ) {
2449 game->ThrottleUserInfo( );
2450 newInfo = *cvarSystem->MoveCVarsToDict( CVAR_USERINFO );
2451 SendUserInfoBroadcast( localClientNum, newInfo );
2453 cvarSystem->ClearModifiedFlags( CVAR_USERINFO );
2456 // advance the server game
2457 while( gameTimeResidual >= USERCMD_MSEC ) {
2459 // sample input for the local client
2462 // duplicate usercmds for clients if no new ones are available
2463 DuplicateUsercmds( gameFrame, gameTime );
2466 gameReturn_t ret = game->RunFrame( userCmds[gameFrame & ( MAX_USERCMD_BACKUP - 1 ) ] );
2468 idAsyncNetwork::ExecuteSessionCommand( ret.sessionCommand );
2472 gameTime += USERCMD_MSEC;
2473 gameTimeResidual -= USERCMD_MSEC;
2476 // duplicate usercmds so there is always at least one available to send with snapshots
2477 DuplicateUsercmds( gameFrame, gameTime );
2479 // send snapshots to connected clients
2480 for ( i = 0; i < MAX_ASYNC_CLIENTS; i++ ) {
2481 serverClient_t &client = clients[i];
2483 if ( client.clientState == SCS_FREE || i == localClientNum ) {
2487 // modify maximum rate if necesary
2488 if ( idAsyncNetwork::serverMaxClientRate.IsModified() ) {
2489 client.channel.SetMaxOutgoingRate( Min( client.clientRate, idAsyncNetwork::serverMaxClientRate.GetInteger() ) );
2492 // if the channel is not yet ready to send new data
2493 if ( !client.channel.ReadyToSend( serverTime ) ) {
2497 // send additional message fragments if the last message was too large to send at once
2498 if ( client.channel.UnsentFragmentsLeft() ) {
2499 client.channel.SendNextFragment( serverPort, serverTime );
2503 if ( client.clientState == SCS_INGAME ) {
2504 if ( !SendSnapshotToClient( i ) ) {
2505 SendPingToClient( i );
2508 SendEmptyToClient( i );
2512 if ( com_showAsyncStats.GetBool() ) {
2514 UpdateAsyncStatsAvg();
2516 // dedicated will verbose to console
2517 if ( idAsyncNetwork::serverDedicated.GetBool() && serverTime >= nextAsyncStatsTime ) {
2518 common->Printf( "delay = %d msec, total outgoing rate = %d KB/s, total incoming rate = %d KB/s\n", GetDelay(),
2519 GetOutgoingRate() >> 10, GetIncomingRate() >> 10 );
2521 for ( i = 0; i < MAX_ASYNC_CLIENTS; i++ ) {
2523 outgoingRate = GetClientOutgoingRate( i );
2524 incomingRate = GetClientIncomingRate( i );
2525 outgoingCompression = GetClientOutgoingCompression( i );
2526 incomingCompression = GetClientIncomingCompression( i );
2528 if ( outgoingRate != -1 && incomingRate != -1 ) {
2529 common->Printf( "client %d: out rate = %d B/s (% -2.1f%%), in rate = %d B/s (% -2.1f%%)\n",
2530 i, outgoingRate, outgoingCompression, incomingRate, incomingCompression );
2535 GetAsyncStatsAvgMsg( msg );
2536 common->Printf( va( "%s\n", msg.c_str() ) );
2538 nextAsyncStatsTime = serverTime + 1000;
2542 idAsyncNetwork::serverMaxClientRate.ClearModified();
2547 idAsyncServer::PacifierUpdate
2550 void idAsyncServer::PacifierUpdate( void ) {
2553 if ( !IsActive() ) {
2556 realTime = Sys_Milliseconds();
2557 ProcessConnectionLessMessages();
2558 for ( i = 0; i < MAX_ASYNC_CLIENTS; i++ ) {
2559 if ( clients[i].clientState >= SCS_PUREWAIT ) {
2560 if ( clients[i].channel.UnsentFragmentsLeft() ) {
2561 clients[i].channel.SendNextFragment( serverPort, serverTime );
2563 SendEmptyToClient( i );
2571 idAsyncServer::PrintOOB
2574 void idAsyncServer::PrintOOB( const netadr_t to, int opcode, const char *string ) {
2576 byte msgBuf[ MAX_MESSAGE_SIZE ];
2578 outMsg.Init( msgBuf, sizeof( msgBuf ) );
2579 outMsg.WriteShort( CONNECTIONLESS_MESSAGE_ID );
2580 outMsg.WriteString( "print" );
2581 outMsg.WriteLong( opcode );
2582 outMsg.WriteString( string );
2583 serverPort.SendPacket( to, outMsg.GetData(), outMsg.GetSize() );
2588 idAsyncServer::MasterHeartbeat
2591 void idAsyncServer::MasterHeartbeat( bool force ) {
2592 if ( idAsyncNetwork::LANServer.GetBool() ) {
2594 common->Printf( "net_LANServer is enabled. Not sending heartbeats\n" );
2599 nextHeartbeatTime = 0;
2602 if ( serverTime < nextHeartbeatTime ) {
2605 nextHeartbeatTime = serverTime + HEARTBEAT_MSEC;
2606 for ( int i = 0 ; i < MAX_MASTER_SERVERS ; i++ ) {
2608 if ( idAsyncNetwork::GetMasterAddress( i, adr ) ) {
2609 common->Printf( "Sending heartbeat to %s\n", Sys_NetAdrToString( adr ) );
2611 byte msgBuf[ MAX_MESSAGE_SIZE ];
2612 outMsg.Init( msgBuf, sizeof( msgBuf ) );
2613 outMsg.WriteShort( CONNECTIONLESS_MESSAGE_ID );
2614 outMsg.WriteString( "heartbeat" );
2615 serverPort.SendPacket( adr, outMsg.GetData(), outMsg.GetSize() );
2622 idAsyncServer::SendEnterGameToClient
2625 void idAsyncServer::SendEnterGameToClient( int clientNum ) {
2627 byte msgBuf[ MAX_MESSAGE_SIZE ];
2629 msg.Init( msgBuf, sizeof( msgBuf ) );
2630 msg.WriteByte( SERVER_RELIABLE_MESSAGE_ENTERGAME );
2631 SendReliableMessage( clientNum, msg );
2636 idAsyncServer::UpdateAsyncStatsAvg
2639 void idAsyncServer::UpdateAsyncStatsAvg( void ) {
2640 stats_average_sum -= stats_outrate[ stats_current ];
2641 stats_outrate[ stats_current ] = idAsyncNetwork::server.GetOutgoingRate();
2642 if ( stats_outrate[ stats_current ] > stats_max ) {
2643 stats_max = stats_outrate[ stats_current ];
2644 stats_max_index = stats_current;
2645 } else if ( stats_current == stats_max_index ) {
2649 for ( i = 0; i < stats_numsamples ; i++ ) {
2650 if ( stats_outrate[ i ] > stats_max ) {
2651 stats_max = stats_outrate[ i ];
2652 stats_max_index = i;
2656 stats_average_sum += stats_outrate[ stats_current ];
2657 stats_current++; stats_current %= stats_numsamples;
2662 idAsyncServer::GetAsyncStatsAvgMsg
2665 void idAsyncServer::GetAsyncStatsAvgMsg( idStr &msg ) {
2666 sprintf( msg, "avrg out: %d B/s - max %d B/s ( over %d ms )", stats_average_sum / stats_numsamples, stats_max, idAsyncNetwork::serverSnapshotDelay.GetInteger() * stats_numsamples );
2671 idAsyncServer::ProcessDownloadRequestMessage
2674 void idAsyncServer::ProcessDownloadRequestMessage( const netadr_t from, const idBitMsg &msg ) {
2675 int challenge, clientId, iclient, numPaks, i;
2678 int dlSize[ MAX_PURE_PAKS ]; // sizes
2679 idStrList pakNames; // relative path
2680 idStrList pakURLs; // game URLs
2681 char pakbuf[ MAX_STRING_CHARS ];
2683 byte msgBuf[ MAX_MESSAGE_SIZE ];
2684 byte tmpBuf[ MAX_MESSAGE_SIZE ];
2685 idBitMsg outMsg, tmpMsg;
2687 int voidSlots = 0; // to count and verbose the right number of paks requested for downloads
2689 challenge = msg.ReadLong();
2690 clientId = msg.ReadShort();
2691 dlRequest = msg.ReadLong();
2693 if ( ( iclient = ValidateChallenge( from, challenge, clientId ) ) == -1 ) {
2697 if ( challenges[ iclient ].authState != CDK_PUREWAIT ) {
2698 common->DPrintf( "client %s: got download request message, not in CDK_PUREWAIT\n", Sys_NetAdrToString( from ) );
2702 // the first token of the pak names list passed to the game will be empty if no game pak is requested
2703 dlGamePak = msg.ReadLong();
2705 if ( !( dlSize[ 0 ] = fileSystem->ValidateDownloadPakForChecksum( dlGamePak, pakbuf, true ) ) ) {
2706 common->Warning( "client requested unknown game pak 0x%x", dlGamePak );
2714 pakNames.Append( pakbuf );
2717 // read the checksums, build path names and pass that to the game code
2718 dlPakChecksum = msg.ReadLong();
2719 while ( dlPakChecksum ) {
2720 if ( !( dlSize[ numPaks ] = fileSystem->ValidateDownloadPakForChecksum( dlPakChecksum, pakbuf, false ) ) ) {
2721 // we pass an empty token to the game so our list doesn't get offset
2722 common->Warning( "client requested an unknown pak 0x%x", dlPakChecksum );
2726 pakNames.Append( pakbuf );
2728 dlPakChecksum = msg.ReadLong();
2731 for ( i = 0; i < pakNames.Num(); i++ ) {
2735 paklist += pakNames[ i ].c_str();
2738 // read the message and pass it to the game code
2739 common->DPrintf( "got download request for %d paks - %s\n", numPaks - voidSlots, paklist.c_str() );
2741 outMsg.Init( msgBuf, sizeof( msgBuf ) );
2742 outMsg.WriteShort( CONNECTIONLESS_MESSAGE_ID );
2743 outMsg.WriteString( "downloadInfo" );
2744 outMsg.WriteLong( dlRequest );
2745 if ( !game->DownloadRequest( Sys_NetAdrToString( from ), challenges[ iclient ].guid, paklist.c_str(), pakbuf ) ) {
2746 common->DPrintf( "game: no downloads\n" );
2747 outMsg.WriteByte( SERVER_DL_NONE );
2748 serverPort.SendPacket( from, outMsg.GetData(), outMsg.GetSize() );
2756 next = strchr( token, ';' );
2763 type = atoi( token );
2764 } else if ( type == SERVER_DL_REDIRECT ) {
2765 common->DPrintf( "download request: redirect to URL %s\n", token );
2766 outMsg.WriteByte( SERVER_DL_REDIRECT );
2767 outMsg.WriteString( token );
2768 serverPort.SendPacket( from, outMsg.GetData(), outMsg.GetSize() );
2770 } else if ( type == SERVER_DL_LIST ) {
2771 pakURLs.Append( token );
2773 common->DPrintf( "wrong op type %d\n", type );
2774 next = token = NULL;
2779 next = strchr( token, ';' );
2785 if ( type == SERVER_DL_LIST ) {
2786 int totalDlSize = 0;
2787 int numActualPaks = 0;
2789 // put the answer packet together
2790 outMsg.WriteByte( SERVER_DL_LIST );
2792 tmpMsg.Init( tmpBuf, MAX_MESSAGE_SIZE );
2794 for ( i = 0; i < pakURLs.Num(); i++ ) {
2795 tmpMsg.BeginWriting();
2796 if ( !dlSize[ i ] || !pakURLs[ i ].Length() ) {
2797 // still send the relative path so the client knows what it missed
2798 tmpMsg.WriteByte( SERVER_PAK_NO );
2799 tmpMsg.WriteString( pakNames[ i ] );
2801 totalDlSize += dlSize[ i ];
2803 tmpMsg.WriteByte( SERVER_PAK_YES );
2804 tmpMsg.WriteString( pakNames[ i ] );
2805 tmpMsg.WriteString( pakURLs[ i ] );
2806 tmpMsg.WriteLong( dlSize[ i ] );
2809 // keep last 5 bytes for an 'end of message' - SERVER_PAK_END and the totalDlSize long
2810 if ( outMsg.GetRemainingSpace() - tmpMsg.GetSize() > 5 ) {
2811 outMsg.WriteData( tmpMsg.GetData(), tmpMsg.GetSize() );
2813 outMsg.WriteByte( SERVER_PAK_END );
2817 if ( i == pakURLs.Num() ) {
2818 // put a closure even if size not exceeded
2819 outMsg.WriteByte( SERVER_PAK_END );
2821 common->DPrintf( "download request: download %d paks, %d bytes\n", numActualPaks, totalDlSize );
2823 serverPort.SendPacket( from, outMsg.GetData(), outMsg.GetSize() );