dhewm3/neo/framework/async/AsyncServer.cpp
2020-05-28 00:18:54 +02:00

2791 lines
78 KiB
C++

/*
===========================================================================
Doom 3 GPL Source Code
Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company.
This file is part of the Doom 3 GPL Source Code ("Doom 3 Source Code").
Doom 3 Source Code is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Doom 3 Source Code is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Doom 3 Source Code. If not, see <http://www.gnu.org/licenses/>.
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.
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.
===========================================================================
*/
#include "sys/platform.h"
#include "idlib/LangDict.h"
#include "framework/Session_local.h"
#include "framework/Game.h"
#include "framework/async/AsyncNetwork.h"
const int MIN_RECONNECT_TIME = 2000;
const int EMPTY_RESEND_TIME = 500;
const int PING_RESEND_TIME = 500;
const int NOINPUT_IDLE_TIME = 30000;
const int HEARTBEAT_MSEC = 5*60*1000;
// must be kept in sync with authReplyMsg_t
const char* authReplyMsg[] = {
// "Waiting for authorization",
"#str_07204",
// "Client unknown to auth",
"#str_07205",
// "Access denied - CD Key in use",
"#str_07206",
// "Auth custom message", // placeholder - we propagate a message from the master
"#str_07207",
// "Authorize Server - Waiting for client"
"#str_07208"
};
const char* authReplyStr[] = {
"AUTH_NONE",
"AUTH_OK",
"AUTH_WAIT",
"AUTH_DENY"
};
/*
==================
idAsyncServer::idAsyncServer
==================
*/
idAsyncServer::idAsyncServer( void ) {
int i;
active = false;
realTime = 0;
serverTime = 0;
serverId = 0;
serverDataChecksum = 0;
localClientNum = -1;
gameInitId = 0;
gameFrame = 0;
gameTime = 0;
gameTimeResidual = 0;
memset( challenges, 0, sizeof( challenges ) );
memset( userCmds, 0, sizeof( userCmds ) );
for ( i = 0; i < MAX_ASYNC_CLIENTS; i++ ) {
ClearClient( i );
}
serverReloadingEngine = false;
nextHeartbeatTime = 0;
nextAsyncStatsTime = 0;
noRconOutput = true;
lastAuthTime = 0;
memset( stats_outrate, 0, sizeof( stats_outrate ) );
stats_current = 0;
stats_average_sum = 0;
stats_max = 0;
stats_max_index = 0;
}
/*
==================
idAsyncServer::InitPort
==================
*/
bool idAsyncServer::InitPort( void ) {
int lastPort;
// if this is the first time we have spawned a server, open the UDP port
if ( !serverPort.GetPort() ) {
if ( cvarSystem->GetCVarInteger( "net_port" ) != 0 ) {
if ( !serverPort.InitForPort( cvarSystem->GetCVarInteger( "net_port" ) ) ) {
common->Printf( "Unable to open server on port %d (net_port)\n", cvarSystem->GetCVarInteger( "net_port" ) );
return false;
}
} else {
// scan for multiple ports, in case other servers are running on this IP already
for ( lastPort = 0; lastPort < NUM_SERVER_PORTS; lastPort++ ) {
if ( serverPort.InitForPort( PORT_SERVER + lastPort ) ) {
break;
}
}
if ( lastPort >= NUM_SERVER_PORTS ) {
common->Printf( "Unable to open server network port.\n" );
return false;
}
}
}
return true;
}
/*
==================
idAsyncServer::ClosePort
==================
*/
void idAsyncServer::ClosePort( void ) {
int i;
serverPort.Close();
for ( i = 0; i < MAX_CHALLENGES; i++ ) {
challenges[ i ].authReplyPrint.Clear();
}
}
/*
==================
idAsyncServer::Spawn
==================
*/
void idAsyncServer::Spawn( void ) {
int i, size;
byte msgBuf[MAX_MESSAGE_SIZE];
netadr_t from;
// shutdown any current game
session->Stop();
if ( active ) {
return;
}
if ( !InitPort() ) {
return;
}
// trash any currently pending packets
while( serverPort.GetPacket( from, msgBuf, size, sizeof( msgBuf ) ) ) {
}
// reset cheats cvars
if ( !idAsyncNetwork::allowCheats.GetBool() ) {
cvarSystem->ResetFlaggedVariables( CVAR_CHEAT );
}
memset( challenges, 0, sizeof( challenges ) );
memset( userCmds, 0, sizeof( userCmds ) );
for ( i = 0; i < MAX_ASYNC_CLIENTS; i++ ) {
ClearClient( i );
}
common->Printf( "Server spawned on port %i.\n", serverPort.GetPort() );
// calculate a checksum on some of the essential data used
serverDataChecksum = declManager->GetChecksum();
// get a pseudo random server id, but don't use the id which is reserved for connectionless packets
serverId = Sys_Milliseconds() & CONNECTIONLESS_MESSAGE_ID_MASK;
active = true;
nextHeartbeatTime = 0;
nextAsyncStatsTime = 0;
ExecuteMapChange();
}
/*
==================
idAsyncServer::Kill
==================
*/
void idAsyncServer::Kill( void ) {
int i, j;
if ( !active ) {
return;
}
// drop all clients
for ( i = 0; i < MAX_ASYNC_CLIENTS; i++ ) {
DropClient( i, "#str_07135" );
}
// send some empty messages to the zombie clients to make sure they disconnect
for ( j = 0; j < 4; j++ ) {
for ( i = 0; i < MAX_ASYNC_CLIENTS; i++ ) {
if ( clients[i].clientState == SCS_ZOMBIE ) {
if ( clients[i].channel.UnsentFragmentsLeft() ) {
clients[i].channel.SendNextFragment( serverPort, serverTime );
} else {
SendEmptyToClient( i, true );
}
}
}
Sys_Sleep( 10 );
}
// reset any pureness
fileSystem->ClearPureChecksums();
active = false;
// shutdown any current game
session->Stop();
}
/*
==================
idAsyncServer::ExecuteMapChange
==================
*/
void idAsyncServer::ExecuteMapChange( void ) {
int i;
idBitMsg msg;
byte msgBuf[MAX_MESSAGE_SIZE];
idStr mapName;
findFile_t ff;
bool addonReload = false;
char bestGameType[ MAX_STRING_CHARS ];
assert( active );
// reset any pureness
fileSystem->ClearPureChecksums();
// make sure the map/gametype combo is good
game->GetBestGameType( cvarSystem->GetCVarString("si_map"), cvarSystem->GetCVarString("si_gametype"), bestGameType );
cvarSystem->SetCVarString("si_gametype", bestGameType );
// initialize map settings
cmdSystem->BufferCommandText( CMD_EXEC_NOW, "rescanSI" );
sprintf( mapName, "maps/%s", sessLocal.mapSpawnData.serverInfo.GetString( "si_map" ) );
mapName.SetFileExtension( ".map" );
ff = fileSystem->FindFile( mapName, !serverReloadingEngine );
switch( ff ) {
case FIND_NO:
common->Printf( "Can't find map %s\n", mapName.c_str() );
cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "disconnect\n" );
return;
case FIND_ADDON:
// NOTE: we have no problem with addon dependencies here because if the map is in
// an addon pack that's already on search list, then all it's deps are assumed to be on search as well
common->Printf( "map %s is in an addon pak - reloading\n", mapName.c_str() );
addonReload = true;
break;
default:
break;
}
// if we are asked to do a full reload, the strategy is completely different
if ( !serverReloadingEngine && ( addonReload || idAsyncNetwork::serverReloadEngine.GetInteger() != 0 ) ) {
if ( idAsyncNetwork::serverReloadEngine.GetInteger() != 0 ) {
common->Printf( "net_serverReloadEngine enabled - doing a full reload\n" );
}
// tell the clients to reconnect
// FIXME: shouldn't they wait for the new pure list, then reload?
// in a lot of cases this is going to trigger two reloadEngines for the clients
// one to restart, the other one to set paks right ( with addon for instance )
// can fix by reconnecting without reloading and waiting for the server to tell..
for ( i = 0; i < MAX_ASYNC_CLIENTS; i++ ) {
if ( clients[ i ].clientState >= SCS_PUREWAIT && i != localClientNum ) {
msg.Init( msgBuf, sizeof( msgBuf ) );
msg.WriteByte( SERVER_RELIABLE_MESSAGE_RELOAD );
SendReliableMessage( i, msg );
clients[ i ].clientState = SCS_ZOMBIE; // so we don't bother sending a disconnect
}
}
cmdSystem->BufferCommandText( CMD_EXEC_NOW, "reloadEngine" );
serverReloadingEngine = true; // don't get caught in endless loop
cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "spawnServer\n" );
// decrease feature
if ( idAsyncNetwork::serverReloadEngine.GetInteger() > 0 ) {
idAsyncNetwork::serverReloadEngine.SetInteger( idAsyncNetwork::serverReloadEngine.GetInteger() - 1 );
}
return;
}
serverReloadingEngine = false;
serverTime = 0;
// initialize game id and time
gameInitId ^= Sys_Milliseconds(); // NOTE: make sure the gameInitId is always a positive number because negative numbers have special meaning
gameFrame = 0;
gameTime = 0;
gameTimeResidual = 0;
memset( userCmds, 0, sizeof( userCmds ) );
if ( idAsyncNetwork::serverDedicated.GetInteger() == 0 ) {
InitLocalClient( 0 );
} else {
localClientNum = -1;
}
// re-initialize all connected clients for the new map
for ( i = 0; i < MAX_ASYNC_CLIENTS; i++ ) {
if ( clients[i].clientState >= SCS_PUREWAIT && i != localClientNum ) {
InitClient( i, clients[i].clientId, clients[i].clientRate );
SendGameInitToClient( i );
if ( sessLocal.mapSpawnData.serverInfo.GetBool( "si_pure" ) ) {
clients[ i ].clientState = SCS_PUREWAIT;
}
}
}
// load map
sessLocal.ExecuteMapChange();
if ( localClientNum >= 0 ) {
BeginLocalClient();
} else {
game->SetLocalClient( -1 );
}
if ( sessLocal.mapSpawnData.serverInfo.GetInt( "si_pure" ) ) {
// lock down the pak list
fileSystem->UpdatePureServerChecksums( );
// tell the clients so they can work out their pure lists
for ( i = 0; i < MAX_ASYNC_CLIENTS; i++ ) {
if ( clients[ i ].clientState == SCS_PUREWAIT ) {
if ( !SendReliablePureToClient( i ) ) {
clients[ i ].clientState = SCS_CONNECTED;
}
}
}
}
// serverTime gets reset, force a heartbeat so timings restart
MasterHeartbeat( true );
}
/*
==================
idAsyncServer::GetPort
==================
*/
int idAsyncServer::GetPort( void ) const {
return serverPort.GetPort();
}
/*
===============
idAsyncServer::GetBoundAdr
===============
*/
netadr_t idAsyncServer::GetBoundAdr( void ) const {
return serverPort.GetAdr();
}
/*
==================
idAsyncServer::GetOutgoingRate
==================
*/
int idAsyncServer::GetOutgoingRate( void ) const {
int i, rate;
rate = 0;
for ( i = 0; i < MAX_ASYNC_CLIENTS; i++ ) {
const serverClient_t &client = clients[i];
if ( client.clientState >= SCS_CONNECTED ) {
rate += client.channel.GetOutgoingRate();
}
}
return rate;
}
/*
==================
idAsyncServer::GetIncomingRate
==================
*/
int idAsyncServer::GetIncomingRate( void ) const {
int i, rate;
rate = 0;
for ( i = 0; i < MAX_ASYNC_CLIENTS; i++ ) {
const serverClient_t &client = clients[i];
if ( client.clientState >= SCS_CONNECTED ) {
rate += client.channel.GetIncomingRate();
}
}
return rate;
}
/*
==================
idAsyncServer::IsClientInGame
==================
*/
bool idAsyncServer::IsClientInGame( int clientNum ) const {
return ( clients[clientNum].clientState >= SCS_INGAME );
}
/*
==================
idAsyncServer::GetClientPing
==================
*/
int idAsyncServer::GetClientPing( int clientNum ) const {
const serverClient_t &client = clients[clientNum];
if ( client.clientState < SCS_CONNECTED ) {
return 99999;
} else {
return client.clientPing;
}
}
/*
==================
idAsyncServer::GetClientPrediction
==================
*/
int idAsyncServer::GetClientPrediction( int clientNum ) const {
const serverClient_t &client = clients[clientNum];
if ( client.clientState < SCS_CONNECTED ) {
return 99999;
} else {
return client.clientPrediction;
}
}
/*
==================
idAsyncServer::GetClientTimeSinceLastPacket
==================
*/
int idAsyncServer::GetClientTimeSinceLastPacket( int clientNum ) const {
const serverClient_t &client = clients[clientNum];
if ( client.clientState < SCS_CONNECTED ) {
return 99999;
} else {
return serverTime - client.lastPacketTime;
}
}
/*
==================
idAsyncServer::GetClientTimeSinceLastInput
==================
*/
int idAsyncServer::GetClientTimeSinceLastInput( int clientNum ) const {
const serverClient_t &client = clients[clientNum];
if ( client.clientState < SCS_CONNECTED ) {
return 99999;
} else {
return serverTime - client.lastInputTime;
}
}
/*
==================
idAsyncServer::GetClientOutgoingRate
==================
*/
int idAsyncServer::GetClientOutgoingRate( int clientNum ) const {
const serverClient_t &client = clients[clientNum];
if ( client.clientState < SCS_CONNECTED ) {
return -1;
} else {
return client.channel.GetOutgoingRate();
}
}
/*
==================
idAsyncServer::GetClientIncomingRate
==================
*/
int idAsyncServer::GetClientIncomingRate( int clientNum ) const {
const serverClient_t &client = clients[clientNum];
if ( client.clientState < SCS_CONNECTED ) {
return -1;
} else {
return client.channel.GetIncomingRate();
}
}
/*
==================
idAsyncServer::GetClientOutgoingCompression
==================
*/
float idAsyncServer::GetClientOutgoingCompression( int clientNum ) const {
const serverClient_t &client = clients[clientNum];
if ( client.clientState < SCS_CONNECTED ) {
return 0.0f;
} else {
return client.channel.GetOutgoingCompression();
}
}
/*
==================
idAsyncServer::GetClientIncomingCompression
==================
*/
float idAsyncServer::GetClientIncomingCompression( int clientNum ) const {
const serverClient_t &client = clients[clientNum];
if ( client.clientState < SCS_CONNECTED ) {
return 0.0f;
} else {
return client.channel.GetIncomingCompression();
}
}
/*
==================
idAsyncServer::GetClientIncomingPacketLoss
==================
*/
float idAsyncServer::GetClientIncomingPacketLoss( int clientNum ) const {
const serverClient_t &client = clients[clientNum];
if ( client.clientState < SCS_CONNECTED ) {
return 0.0f;
} else {
return client.channel.GetIncomingPacketLoss();
}
}
/*
==================
idAsyncServer::GetNumClients
==================
*/
int idAsyncServer::GetNumClients( void ) const {
int ret = 0;
for ( int i = 0; i < MAX_ASYNC_CLIENTS; i++ ) {
if ( clients[ i ].clientState >= SCS_CONNECTED ) {
ret++;
}
}
return ret;
}
/*
==================
idAsyncServer::GetNumIdleClients
==================
*/
int idAsyncServer::GetNumIdleClients( void ) const {
int ret = 0;
for ( int i = 0; i < MAX_ASYNC_CLIENTS; i++ ) {
if ( clients[ i ].clientState >= SCS_CONNECTED ) {
if ( serverTime - clients[ i ].lastInputTime > NOINPUT_IDLE_TIME ) {
ret++;
}
}
}
return ret;
}
/*
==================
idAsyncServer::DuplicateUsercmds
==================
*/
void idAsyncServer::DuplicateUsercmds( int frame, int time ) {
int i, previousIndex, currentIndex;
previousIndex = ( frame - 1 ) & ( MAX_USERCMD_BACKUP - 1 );
currentIndex = frame & ( MAX_USERCMD_BACKUP - 1 );
// duplicate previous user commands if no new commands are available for a client
for ( i = 0; i < MAX_ASYNC_CLIENTS; i++ ) {
if ( clients[i].clientState == SCS_FREE ) {
continue;
}
if ( idAsyncNetwork::DuplicateUsercmd( userCmds[previousIndex][i], userCmds[currentIndex][i], frame, time ) ) {
clients[i].numDuplicatedUsercmds++;
}
}
}
/*
==================
idAsyncServer::ClearClient
==================
*/
void idAsyncServer::ClearClient( int clientNum ) {
serverClient_t &client = clients[clientNum];
client.clientId = 0;
client.clientState = SCS_FREE;
client.clientPrediction = 0;
client.clientAheadTime = 0;
client.clientRate = 0;
client.clientPing = 0;
client.gameInitSequence = 0;
client.gameFrame = 0;
client.gameTime = 0;
client.channel.Shutdown();
client.lastConnectTime = 0;
client.lastEmptyTime = 0;
client.lastPingTime = 0;
client.lastSnapshotTime = 0;
client.lastPacketTime = 0;
client.lastInputTime = 0;
client.snapshotSequence = 0;
client.acknowledgeSnapshotSequence = 0;
client.numDuplicatedUsercmds = 0;
}
/*
==================
idAsyncServer::InitClient
==================
*/
void idAsyncServer::InitClient( int clientNum, int clientId, int clientRate ) {
int i;
// clear the user info
sessLocal.mapSpawnData.userInfo[ clientNum ].Clear(); // always start with a clean base
// clear the server client
serverClient_t &client = clients[clientNum];
client.clientId = clientId;
client.clientState = SCS_CONNECTED;
client.clientPrediction = 0;
client.clientAheadTime = 0;
client.gameInitSequence = -1;
client.gameFrame = 0;
client.gameTime = 0;
client.channel.ResetRate();
client.clientRate = clientRate ? clientRate : idAsyncNetwork::serverMaxClientRate.GetInteger();
client.channel.SetMaxOutgoingRate( Min( idAsyncNetwork::serverMaxClientRate.GetInteger(), client.clientRate ) );
client.clientPing = 0;
client.lastConnectTime = serverTime;
client.lastEmptyTime = serverTime;
client.lastPingTime = serverTime;
client.lastSnapshotTime = serverTime;
client.lastPacketTime = serverTime;
client.lastInputTime = serverTime;
client.acknowledgeSnapshotSequence = 0;
client.numDuplicatedUsercmds = 0;
// clear the user commands
for ( i = 0; i < MAX_USERCMD_BACKUP; i++ ) {
memset( &userCmds[i][clientNum], 0, sizeof( userCmds[i][clientNum] ) );
}
// let the game know a player connected
game->ServerClientConnect( clientNum, client.guid );
}
/*
==================
idAsyncServer::InitLocalClient
==================
*/
void idAsyncServer::InitLocalClient( int clientNum ) {
netadr_t badAddress;
localClientNum = clientNum;
InitClient( clientNum, 0, 0 );
memset( &badAddress, 0, sizeof( badAddress ) );
badAddress.type = NA_BAD;
clients[clientNum].channel.Init( badAddress, serverId );
clients[clientNum].clientState = SCS_INGAME;
sessLocal.mapSpawnData.userInfo[clientNum] = *cvarSystem->MoveCVarsToDict( CVAR_USERINFO );
}
/*
==================
idAsyncServer::BeginLocalClient
==================
*/
void idAsyncServer::BeginLocalClient( void ) {
game->SetLocalClient( localClientNum );
game->SetUserInfo( localClientNum, sessLocal.mapSpawnData.userInfo[localClientNum], false, false );
game->ServerClientBegin( localClientNum );
}
/*
==================
idAsyncServer::LocalClientInput
==================
*/
void idAsyncServer::LocalClientInput( void ) {
int index;
if ( localClientNum < 0 ) {
return;
}
index = gameFrame & ( MAX_USERCMD_BACKUP - 1 );
userCmds[index][localClientNum] = usercmdGen->GetDirectUsercmd();
userCmds[index][localClientNum].gameFrame = gameFrame;
userCmds[index][localClientNum].gameTime = gameTime;
if ( idAsyncNetwork::UsercmdInputChanged( userCmds[( gameFrame - 1 ) & ( MAX_USERCMD_BACKUP - 1 )][localClientNum], userCmds[index][localClientNum] ) ) {
clients[localClientNum].lastInputTime = serverTime;
}
clients[localClientNum].gameFrame = gameFrame;
clients[localClientNum].gameTime = gameTime;
clients[localClientNum].lastPacketTime = serverTime;
}
/*
==================
idAsyncServer::DropClient
==================
*/
void idAsyncServer::DropClient( int clientNum, const char *reason ) {
int i;
idBitMsg msg;
byte msgBuf[MAX_MESSAGE_SIZE];
serverClient_t &client = clients[clientNum];
if ( client.clientState <= SCS_ZOMBIE ) {
return;
}
if ( client.clientState >= SCS_PUREWAIT && clientNum != localClientNum ) {
msg.Init( msgBuf, sizeof( msgBuf ) );
msg.WriteByte( SERVER_RELIABLE_MESSAGE_DISCONNECT );
msg.WriteInt( clientNum );
msg.WriteString( reason );
for ( i = 0; i < MAX_ASYNC_CLIENTS; i++ ) {
// clientNum so SCS_PUREWAIT client gets it's own disconnect msg
if ( i == clientNum || clients[i].clientState >= SCS_CONNECTED ) {
SendReliableMessage( i, msg );
}
}
}
reason = common->GetLanguageDict()->GetString( reason );
common->Printf( "client %d %s\n", clientNum, reason );
cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "addChatLine \"%s^0 %s\"", sessLocal.mapSpawnData.userInfo[ clientNum ].GetString( "ui_name" ), reason ) );
// remove the player from the game
game->ServerClientDisconnect( clientNum );
client.clientState = SCS_ZOMBIE;
}
/*
==================
idAsyncServer::SendReliableMessage
==================
*/
void idAsyncServer::SendReliableMessage( int clientNum, const idBitMsg &msg ) {
if ( clientNum == localClientNum ) {
return;
}
if ( !clients[ clientNum ].channel.SendReliableMessage( msg ) ) {
clients[ clientNum ].channel.ClearReliableMessages();
DropClient( clientNum, "#str_07136" );
}
}
/*
==================
idAsyncServer::CheckClientTimeouts
==================
*/
void idAsyncServer::CheckClientTimeouts( void ) {
int i, zombieTimeout, clientTimeout;
zombieTimeout = serverTime - idAsyncNetwork::serverZombieTimeout.GetInteger() * 1000;
clientTimeout = serverTime - idAsyncNetwork::serverClientTimeout.GetInteger() * 1000;
for ( i = 0; i < MAX_ASYNC_CLIENTS; i++ ) {
serverClient_t &client = clients[i];
if ( i == localClientNum ) {
continue;
}
if ( client.lastPacketTime > serverTime ) {
client.lastPacketTime = serverTime;
continue;
}
if ( client.clientState == SCS_ZOMBIE && client.lastPacketTime < zombieTimeout ) {
client.channel.Shutdown();
client.clientState = SCS_FREE;
continue;
}
if ( client.clientState >= SCS_PUREWAIT && client.lastPacketTime < clientTimeout ) {
DropClient( i, "#str_07137" );
continue;
}
}
}
/*
==================
idAsyncServer::SendPrintBroadcast
==================
*/
void idAsyncServer::SendPrintBroadcast( const char *string ) {
int i;
idBitMsg msg;
byte msgBuf[MAX_MESSAGE_SIZE];
msg.Init( msgBuf, sizeof( msgBuf ) );
msg.WriteByte( SERVER_RELIABLE_MESSAGE_PRINT );
msg.WriteString( string );
for ( i = 0; i < MAX_ASYNC_CLIENTS; i++ ) {
if ( clients[i].clientState >= SCS_CONNECTED ) {
SendReliableMessage( i, msg );
}
}
}
/*
==================
idAsyncServer::SendPrintToClient
==================
*/
void idAsyncServer::SendPrintToClient( int clientNum, const char *string ) {
idBitMsg msg;
byte msgBuf[MAX_MESSAGE_SIZE];
serverClient_t &client = clients[clientNum];
if ( client.clientState < SCS_CONNECTED ) {
return;
}
msg.Init( msgBuf, sizeof( msgBuf ) );
msg.WriteByte( SERVER_RELIABLE_MESSAGE_PRINT );
msg.WriteString( string );
SendReliableMessage( clientNum, msg );
}
/*
==================
idAsyncServer::SendUserInfoBroadcast
==================
*/
void idAsyncServer::SendUserInfoBroadcast( int userInfoNum, const idDict &info, bool sendToAll ) {
idBitMsg msg;
byte msgBuf[MAX_MESSAGE_SIZE];
const idDict *gameInfo;
bool gameModifiedInfo;
gameInfo = game->SetUserInfo( userInfoNum, info, false, true );
if ( gameInfo ) {
gameModifiedInfo = true;
} else {
gameModifiedInfo = false;
gameInfo = &info;
}
if ( userInfoNum == localClientNum ) {
common->DPrintf( "local user info modified by server\n" );
cvarSystem->SetCVarsFromDict( *gameInfo );
cvarSystem->ClearModifiedFlags( CVAR_USERINFO ); // don't emit back
}
msg.Init( msgBuf, sizeof( msgBuf ) );
msg.WriteByte( SERVER_RELIABLE_MESSAGE_CLIENTINFO );
msg.WriteByte( userInfoNum );
if ( gameModifiedInfo || sendToAll ) {
msg.WriteBits( 0, 1 );
} else {
msg.WriteBits( 1, 1 );
}
#if ID_CLIENTINFO_TAGS
msg.WriteInt( sessLocal.mapSpawnData.userInfo[userInfoNum].Checksum() );
common->DPrintf( "broadcast for client %d: 0x%x\n", userInfoNum, sessLocal.mapSpawnData.userInfo[userInfoNum].Checksum() );
sessLocal.mapSpawnData.userInfo[userInfoNum].Print();
#endif
if ( gameModifiedInfo || sendToAll ) {
msg.WriteDeltaDict( *gameInfo, NULL );
} else {
msg.WriteDeltaDict( *gameInfo, &sessLocal.mapSpawnData.userInfo[userInfoNum] );
}
for ( int i = 0; i < MAX_ASYNC_CLIENTS; i++ ) {
if ( clients[i].clientState >= SCS_CONNECTED && ( sendToAll || i != userInfoNum || gameModifiedInfo ) ) {
SendReliableMessage( i, msg );
}
}
sessLocal.mapSpawnData.userInfo[userInfoNum] = *gameInfo;
}
/*
==================
idAsyncServer::UpdateUI
if the game modifies userInfo, it will call this through command system
we then need to get the info from the game, and broadcast to clients
( using DeltaDict and our current mapSpawnData as a base )
==================
*/
void idAsyncServer::UpdateUI( int clientNum ) {
const idDict *info = game->GetUserInfo( clientNum );
if ( !info ) {
common->Warning( "idAsyncServer::UpdateUI: no info from game\n" );
return;
}
SendUserInfoBroadcast( clientNum, *info, true );
}
/*
==================
idAsyncServer::SendUserInfoToClient
==================
*/
void idAsyncServer::SendUserInfoToClient( int clientNum, int userInfoNum, const idDict &info ) {
idBitMsg msg;
byte msgBuf[MAX_MESSAGE_SIZE];
if ( clients[clientNum].clientState < SCS_CONNECTED ) {
return;
}
msg.Init( msgBuf, sizeof( msgBuf ) );
msg.WriteByte( SERVER_RELIABLE_MESSAGE_CLIENTINFO );
msg.WriteByte( userInfoNum );
msg.WriteBits( 0, 1 );
#if ID_CLIENTINFO_TAGS
msg.WriteInt( 0 );
common->DPrintf( "user info %d to client %d: NULL base\n", userInfoNum, clientNum );
#endif
msg.WriteDeltaDict( info, NULL );
SendReliableMessage( clientNum, msg );
}
/*
==================
idAsyncServer::SendSyncedCvarsBroadcast
==================
*/
void idAsyncServer::SendSyncedCvarsBroadcast( const idDict &cvars ) {
idBitMsg msg;
byte msgBuf[MAX_MESSAGE_SIZE];
int i;
msg.Init( msgBuf, sizeof( msgBuf ) );
msg.WriteByte( SERVER_RELIABLE_MESSAGE_SYNCEDCVARS );
msg.WriteDeltaDict( cvars, &sessLocal.mapSpawnData.syncedCVars );
for ( i = 0; i < MAX_ASYNC_CLIENTS; i++ ) {
if ( clients[i].clientState >= SCS_CONNECTED ) {
SendReliableMessage( i, msg );
}
}
sessLocal.mapSpawnData.syncedCVars = cvars;
}
/*
==================
idAsyncServer::SendSyncedCvarsToClient
==================
*/
void idAsyncServer::SendSyncedCvarsToClient( int clientNum, const idDict &cvars ) {
idBitMsg msg;
byte msgBuf[MAX_MESSAGE_SIZE];
if ( clients[clientNum].clientState < SCS_CONNECTED ) {
return;
}
msg.Init( msgBuf, sizeof( msgBuf ) );
msg.WriteByte( SERVER_RELIABLE_MESSAGE_SYNCEDCVARS );
msg.WriteDeltaDict( cvars, NULL );
SendReliableMessage( clientNum, msg );
}
/*
==================
idAsyncServer::SendApplySnapshotToClient
==================
*/
void idAsyncServer::SendApplySnapshotToClient( int clientNum, int sequence ) {
idBitMsg msg;
byte msgBuf[MAX_MESSAGE_SIZE];
msg.Init( msgBuf, sizeof( msgBuf ) );
msg.WriteByte( SERVER_RELIABLE_MESSAGE_APPLYSNAPSHOT );
msg.WriteInt( sequence );
SendReliableMessage( clientNum, msg );
}
/*
==================
idAsyncServer::SendEmptyToClient
==================
*/
bool idAsyncServer::SendEmptyToClient( int clientNum, bool force ) {
idBitMsg msg;
byte msgBuf[MAX_MESSAGE_SIZE];
serverClient_t &client = clients[clientNum];
if ( client.lastEmptyTime > realTime ) {
client.lastEmptyTime = realTime;
}
if ( !force && ( realTime - client.lastEmptyTime < EMPTY_RESEND_TIME ) ) {
return false;
}
if ( idAsyncNetwork::verbose.GetInteger() ) {
common->Printf( "sending empty to client %d: gameInitId = %d, gameFrame = %d, gameTime = %d\n", clientNum, gameInitId, gameFrame, gameTime );
}
msg.Init( msgBuf, sizeof( msgBuf ) );
msg.WriteInt( gameInitId );
msg.WriteByte( SERVER_UNRELIABLE_MESSAGE_EMPTY );
client.channel.SendMessage( serverPort, serverTime, msg );
client.lastEmptyTime = realTime;
return true;
}
/*
==================
idAsyncServer::SendPingToClient
==================
*/
bool idAsyncServer::SendPingToClient( int clientNum ) {
idBitMsg msg;
byte msgBuf[MAX_MESSAGE_SIZE];
serverClient_t &client = clients[clientNum];
if ( client.lastPingTime > realTime ) {
client.lastPingTime = realTime;
}
if ( realTime - client.lastPingTime < PING_RESEND_TIME ) {
return false;
}
if ( idAsyncNetwork::verbose.GetInteger() == 2 ) {
common->Printf( "pinging client %d: gameInitId = %d, gameFrame = %d, gameTime = %d\n", clientNum, gameInitId, gameFrame, gameTime );
}
msg.Init( msgBuf, sizeof( msgBuf ) );
msg.WriteInt( gameInitId );
msg.WriteByte( SERVER_UNRELIABLE_MESSAGE_PING );
msg.WriteInt( realTime );
client.channel.SendMessage( serverPort, serverTime, msg );
client.lastPingTime = realTime;
return true;
}
/*
==================
idAsyncServer::SendGameInitToClient
==================
*/
void idAsyncServer::SendGameInitToClient( int clientNum ) {
idBitMsg msg;
byte msgBuf[MAX_MESSAGE_SIZE];
if ( idAsyncNetwork::verbose.GetInteger() ) {
common->Printf( "sending gameinit to client %d: gameInitId = %d, gameFrame = %d, gameTime = %d\n", clientNum, gameInitId, gameFrame, gameTime );
}
serverClient_t &client = clients[clientNum];
// clear the unsent fragments. might flood winsock but that's ok
while( client.channel.UnsentFragmentsLeft() ) {
client.channel.SendNextFragment( serverPort, serverTime );
}
msg.Init( msgBuf, sizeof( msgBuf ) );
msg.WriteInt( gameInitId );
msg.WriteByte( SERVER_UNRELIABLE_MESSAGE_GAMEINIT );
msg.WriteInt( gameFrame );
msg.WriteInt( gameTime );
msg.WriteDeltaDict( sessLocal.mapSpawnData.serverInfo, NULL );
client.gameInitSequence = client.channel.SendMessage( serverPort, serverTime, msg );
}
/*
==================
idAsyncServer::SendSnapshotToClient
==================
*/
bool idAsyncServer::SendSnapshotToClient( int clientNum ) {
int i, j, index, numUsercmds;
idBitMsg msg;
byte msgBuf[MAX_MESSAGE_SIZE];
usercmd_t * last;
byte clientInPVS[MAX_ASYNC_CLIENTS >> 3];
serverClient_t &client = clients[clientNum];
if ( serverTime - client.lastSnapshotTime < idAsyncNetwork::serverSnapshotDelay.GetInteger() ) {
return false;
}
if ( idAsyncNetwork::verbose.GetInteger() == 2 ) {
common->Printf( "sending snapshot to client %d: gameInitId = %d, gameFrame = %d, gameTime = %d\n", clientNum, gameInitId, gameFrame, gameTime );
}
// how far is the client ahead of the server minus the packet delay
client.clientAheadTime = client.gameTime - ( gameTime + gameTimeResidual );
// write the snapshot
msg.Init( msgBuf, sizeof( msgBuf ) );
msg.WriteInt( gameInitId );
msg.WriteByte( SERVER_UNRELIABLE_MESSAGE_SNAPSHOT );
msg.WriteInt( client.snapshotSequence );
msg.WriteInt( gameFrame );
msg.WriteInt( gameTime );
msg.WriteByte( idMath::ClampChar( client.numDuplicatedUsercmds ) );
msg.WriteShort( idMath::ClampShort( client.clientAheadTime ) );
// write the game snapshot
game->ServerWriteSnapshot( clientNum, client.snapshotSequence, msg, clientInPVS, MAX_ASYNC_CLIENTS );
// write the latest user commands from the other clients in the PVS to the snapshot
for ( last = NULL, i = 0; i < MAX_ASYNC_CLIENTS; i++ ) {
serverClient_t &client = clients[i];
if ( client.clientState == SCS_FREE || i == clientNum ) {
continue;
}
// if the client is not in the PVS
if ( !( clientInPVS[i >> 3] & ( 1 << ( i & 7 ) ) ) ) {
continue;
}
int maxRelay = idMath::ClampInt( 1, MAX_USERCMD_RELAY, idAsyncNetwork::serverMaxUsercmdRelay.GetInteger() );
// Max( 1, to always send at least one cmd, which we know we have because we call DuplicateUsercmds in RunFrame
numUsercmds = Max( 1, Min( client.gameFrame, gameFrame + maxRelay ) - gameFrame );
msg.WriteByte( i );
msg.WriteByte( numUsercmds );
for ( j = 0; j < numUsercmds; j++ ) {
index = ( gameFrame + j ) & ( MAX_USERCMD_BACKUP - 1 );
idAsyncNetwork::WriteUserCmdDelta( msg, userCmds[index][i], last );
last = &userCmds[index][i];
}
}
msg.WriteByte( MAX_ASYNC_CLIENTS );
client.channel.SendMessage( serverPort, serverTime, msg );
client.lastSnapshotTime = serverTime;
client.snapshotSequence++;
client.numDuplicatedUsercmds = 0;
return true;
}
/*
==================
idAsyncServer::ProcessUnreliableClientMessage
==================
*/
void idAsyncServer::ProcessUnreliableClientMessage( int clientNum, const idBitMsg &msg ) {
int i, id, acknowledgeSequence, clientGameInitId, clientGameFrame, numUsercmds, index;
usercmd_t *last;
serverClient_t &client = clients[clientNum];
if ( client.clientState == SCS_ZOMBIE ) {
return;
}
acknowledgeSequence = msg.ReadInt();
clientGameInitId = msg.ReadInt();
// while loading a map the client may send empty messages to keep the connection alive
if ( clientGameInitId == GAME_INIT_ID_MAP_LOAD ) {
if ( idAsyncNetwork::verbose.GetInteger() ) {
common->Printf( "ignore unreliable msg from client %d, gameInitId == ID_MAP_LOAD\n", clientNum );
}
return;
}
// check if the client is in the right game
if ( clientGameInitId != gameInitId ) {
if ( acknowledgeSequence > client.gameInitSequence ) {
// the client is connected but not in the right game
client.clientState = SCS_CONNECTED;
// send game init to client
SendGameInitToClient( clientNum );
if ( sessLocal.mapSpawnData.serverInfo.GetBool( "si_pure" ) ) {
client.clientState = SCS_PUREWAIT;
if ( !SendReliablePureToClient( clientNum ) ) {
client.clientState = SCS_CONNECTED;
}
}
} else if ( idAsyncNetwork::verbose.GetInteger() ) {
common->Printf( "ignore unreliable msg from client %d, wrong gameInit, old sequence\n", clientNum );
}
return;
}
client.acknowledgeSnapshotSequence = msg.ReadInt();
if ( client.clientState == SCS_CONNECTED ) {
// the client is in the right game
client.clientState = SCS_INGAME;
// send the user info of other clients
for ( i = 0; i < MAX_ASYNC_CLIENTS; i++ ) {
if ( clients[i].clientState >= SCS_CONNECTED && i != clientNum ) {
SendUserInfoToClient( clientNum, i, sessLocal.mapSpawnData.userInfo[i] );
}
}
// send synchronized cvars to client
SendSyncedCvarsToClient( clientNum, sessLocal.mapSpawnData.syncedCVars );
SendEnterGameToClient( clientNum );
// get the client running in the game
game->ServerClientBegin( clientNum );
// write any reliable messages to initialize the client game state
game->ServerWriteInitialReliableMessages( clientNum );
} else if ( client.clientState == SCS_INGAME ) {
// apply the last snapshot the client received
if ( game->ServerApplySnapshot( clientNum, client.acknowledgeSnapshotSequence ) ) {
SendApplySnapshotToClient( clientNum, client.acknowledgeSnapshotSequence );
}
}
// process the unreliable message
id = msg.ReadByte();
switch( id ) {
case CLIENT_UNRELIABLE_MESSAGE_EMPTY: {
if ( idAsyncNetwork::verbose.GetInteger() ) {
common->Printf( "received empty message for client %d\n", clientNum );
}
break;
}
case CLIENT_UNRELIABLE_MESSAGE_PINGRESPONSE: {
client.clientPing = realTime - msg.ReadInt();
break;
}
case CLIENT_UNRELIABLE_MESSAGE_USERCMD: {
client.clientPrediction = msg.ReadShort();
// read user commands
clientGameFrame = msg.ReadInt();
numUsercmds = msg.ReadByte();
for ( last = NULL, i = clientGameFrame - numUsercmds + 1; i <= clientGameFrame; i++ ) {
index = i & ( MAX_USERCMD_BACKUP - 1 );
idAsyncNetwork::ReadUserCmdDelta( msg, userCmds[index][clientNum], last );
userCmds[index][clientNum].gameFrame = i;
userCmds[index][clientNum].duplicateCount = 0;
if ( idAsyncNetwork::UsercmdInputChanged( userCmds[( i - 1 ) & ( MAX_USERCMD_BACKUP - 1 )][clientNum], userCmds[index][clientNum] ) ) {
client.lastInputTime = serverTime;
}
last = &userCmds[index][clientNum];
}
if ( last ) {
client.gameFrame = last->gameFrame;
client.gameTime = last->gameTime;
}
if ( idAsyncNetwork::verbose.GetInteger() == 2 ) {
common->Printf( "received user command for client %d, gameInitId = %d, gameFrame, %d gameTime %d\n", clientNum, clientGameInitId, client.gameFrame, client.gameTime );
}
break;
}
default: {
common->Printf( "unknown unreliable message %d from client %d\n", id, clientNum );
break;
}
}
}
/*
==================
idAsyncServer::ProcessReliableClientMessages
==================
*/
void idAsyncServer::ProcessReliableClientMessages( int clientNum ) {
idBitMsg msg;
byte msgBuf[MAX_MESSAGE_SIZE];
byte id;
serverClient_t &client = clients[clientNum];
msg.Init( msgBuf, sizeof( msgBuf ) );
while ( client.channel.GetReliableMessage( msg ) ) {
id = msg.ReadByte();
switch( id ) {
case CLIENT_RELIABLE_MESSAGE_CLIENTINFO: {
idDict info;
msg.ReadDeltaDict( info, &sessLocal.mapSpawnData.userInfo[clientNum] );
SendUserInfoBroadcast( clientNum, info );
break;
}
case CLIENT_RELIABLE_MESSAGE_PRINT: {
char string[MAX_STRING_CHARS];
msg.ReadString( string, sizeof( string ) );
common->Printf( "%s\n", string );
break;
}
case CLIENT_RELIABLE_MESSAGE_DISCONNECT: {
DropClient( clientNum, "#str_07138" );
break;
}
case CLIENT_RELIABLE_MESSAGE_PURE: {
// we get this message once the client has successfully updated it's pure list
ProcessReliablePure( clientNum, msg );
break;
}
default: {
// pass reliable message on to game code
game->ServerProcessReliableMessage( clientNum, msg );
break;
}
}
}
}
/*
==================
idAsyncServer::ProcessAuthMessage
==================
*/
void idAsyncServer::ProcessAuthMessage( const idBitMsg &msg ) {
netadr_t client_from;
char client_guid[ 12 ], string[ MAX_STRING_CHARS ];
int i, clientId;
authReply_t reply;
authReplyMsg_t replyMsg = AUTH_REPLY_WAITING;
idStr replyPrintMsg;
reply = (authReply_t)msg.ReadByte();
if ( reply <= 0 || reply >= AUTH_MAXSTATES ) {
common->DPrintf( "auth: invalid reply %d\n", reply );
return;
}
clientId = msg.ReadShort( );
msg.ReadNetadr( &client_from );
msg.ReadString( client_guid, sizeof( client_guid ) );
if ( reply != AUTH_OK ) {
replyMsg = (authReplyMsg_t)msg.ReadByte();
if ( replyMsg <= 0 || replyMsg >= AUTH_REPLY_MAXSTATES ) {
common->DPrintf( "auth: invalid reply msg %d\n", replyMsg );
return;
}
if ( replyMsg == AUTH_REPLY_PRINT ) {
msg.ReadString( string, MAX_STRING_CHARS );
replyPrintMsg = string;
}
}
lastAuthTime = serverTime;
// no message parsing below
for ( i = 0; i < MAX_CHALLENGES; i++ ) {
if ( !challenges[i].connected && challenges[ i ].clientId == clientId ) {
// return if something is wrong
// break if we have found a valid auth
if ( !strlen( challenges[ i ].guid ) ) {
common->DPrintf( "auth: client %s has no guid yet\n", Sys_NetAdrToString( challenges[ i ].address ) );
return;
}
if ( idStr::Cmp( challenges[ i ].guid, client_guid ) ) {
common->DPrintf( "auth: client %s %s not matched, auth server says guid %s\n", Sys_NetAdrToString( challenges[ i ].address ), challenges[i].guid, client_guid );
return;
}
if ( !Sys_CompareNetAdrBase( client_from, challenges[i].address ) ) {
// let auth work when server and master don't see the same IP
common->DPrintf( "auth: matched guid '%s' for != IPs %s and %s\n", client_guid, Sys_NetAdrToString( client_from ), Sys_NetAdrToString( challenges[i].address ) );
}
break;
}
}
if ( i >= MAX_CHALLENGES ) {
common->DPrintf( "auth: failed client lookup %s %s\n", Sys_NetAdrToString( client_from ), client_guid );
return;
}
if ( challenges[ i ].authState != CDK_WAIT ) {
common->DWarning( "auth: challenge 0x%x %s authState %d != CDK_WAIT", challenges[ i ].challenge, Sys_NetAdrToString( challenges[ i ].address ), challenges[ i ].authState );
return;
}
idStr::snPrintf( challenges[ i ].guid, 12, client_guid );
if ( reply == AUTH_OK ) {
challenges[ i ].authState = CDK_OK;
common->Printf( "client %s %s is authed\n", Sys_NetAdrToString( client_from ), client_guid );
} else {
const char *msg;
if ( replyMsg != AUTH_REPLY_PRINT ) {
msg = authReplyMsg[ replyMsg ];
} else {
msg = replyPrintMsg.c_str();
}
// maybe localize it
const char *l_msg = common->GetLanguageDict()->GetString( msg );
common->DPrintf( "auth: client %s %s - %s %s\n", Sys_NetAdrToString( client_from ), client_guid, authReplyStr[ reply ], l_msg );
challenges[ i ].authReply = reply;
challenges[ i ].authReplyMsg = replyMsg;
challenges[ i ].authReplyPrint = replyPrintMsg;
}
}
/*
==================
idAsyncServer::ProcessChallengeMessage
==================
*/
void idAsyncServer::ProcessChallengeMessage( const netadr_t from, const idBitMsg &msg ) {
int i, clientId, oldest, oldestTime;
idBitMsg outMsg;
byte msgBuf[MAX_MESSAGE_SIZE];
clientId = msg.ReadInt();
oldest = 0;
oldestTime = 0x7fffffff;
// see if we already have a challenge for this ip
for ( i = 0; i < MAX_CHALLENGES; i++ ) {
if ( !challenges[i].connected && Sys_CompareNetAdrBase( from, challenges[i].address ) && clientId == challenges[i].clientId ) {
break;
}
if ( challenges[i].time < oldestTime ) {
oldestTime = challenges[i].time;
oldest = i;
}
}
if ( i >= MAX_CHALLENGES ) {
// this is the first time this client has asked for a challenge
i = oldest;
challenges[i].address = from;
challenges[i].clientId = clientId;
challenges[i].challenge = ( (rand() << 16) ^ rand() ) ^ serverTime;
challenges[i].time = serverTime;
challenges[i].connected = false;
challenges[i].authState = CDK_WAIT;
challenges[i].authReply = AUTH_NONE;
challenges[i].authReplyMsg = AUTH_REPLY_WAITING;
challenges[i].authReplyPrint = "";
challenges[i].guid[0] = '\0';
}
challenges[i].pingTime = serverTime;
common->Printf( "sending challenge 0x%x to %s\n", challenges[i].challenge, Sys_NetAdrToString( from ) );
outMsg.Init( msgBuf, sizeof( msgBuf ) );
outMsg.WriteShort( CONNECTIONLESS_MESSAGE_ID );
outMsg.WriteString( "challengeResponse" );
outMsg.WriteInt( challenges[i].challenge );
outMsg.WriteShort( serverId );
outMsg.WriteString( cvarSystem->GetCVarString( "fs_game_base" ) );
outMsg.WriteString( cvarSystem->GetCVarString( "fs_game" ) );
serverPort.SendPacket( from, outMsg.GetData(), outMsg.GetSize() );
#if ID_ENFORCE_KEY_CLIENT
if ( Sys_IsLANAddress( from ) ) {
// no CD Key check for LAN clients
challenges[i].authState = CDK_OK;
} else {
if ( idAsyncNetwork::LANServer.GetBool() ) {
common->Printf( "net_LANServer is enabled. Client %s is not a LAN address, will be rejected\n", Sys_NetAdrToString( from ) );
challenges[ i ].authState = CDK_ONLYLAN;
} else {
// emit a cd key confirmation request
outMsg.BeginWriting();
outMsg.WriteShort( CONNECTIONLESS_MESSAGE_ID );
outMsg.WriteString( "srvAuth" );
outMsg.WriteInt( ASYNC_PROTOCOL_VERSION );
outMsg.WriteNetadr( from );
outMsg.WriteInt( -1 ); // this identifies "challenge" auth vs "connect" auth
// protocol 1.37 addition
outMsg.WriteByte( fileSystem->RunningD3XP() );
serverPort.SendPacket( idAsyncNetwork::GetMasterAddress(), outMsg.GetData(), outMsg.GetSize() );
}
}
#else
if (! Sys_IsLANAddress( from ) ) {
common->Printf( "Build Does not have CD Key Enforcement enabled. Client %s is not a LAN address, but will be accepted\n", Sys_NetAdrToString( from ) );
}
challenges[i].authState = CDK_OK;
#endif
}
/*
==================
idAsyncServer::SendPureServerMessage
==================
*/
bool idAsyncServer::SendPureServerMessage( const netadr_t to ) {
idBitMsg outMsg;
byte msgBuf[ MAX_MESSAGE_SIZE ];
int serverChecksums[ MAX_PURE_PAKS ];
int i;
fileSystem->GetPureServerChecksums( serverChecksums );
if ( !serverChecksums[ 0 ] ) {
// happens if you run fully expanded assets with si_pure 1
common->Warning( "pure server has no pak files referenced" );
return false;
}
common->DPrintf( "client %s: sending pure pak list\n", Sys_NetAdrToString( to ) );
// send our list of required paks
outMsg.Init( msgBuf, sizeof( msgBuf ) );
outMsg.WriteShort( CONNECTIONLESS_MESSAGE_ID );
outMsg.WriteString( "pureServer" );
i = 0;
while ( serverChecksums[ i ] ) {
outMsg.WriteInt( serverChecksums[ i++ ] );
}
outMsg.WriteInt( 0 );
serverPort.SendPacket( to, outMsg.GetData(), outMsg.GetSize() );
return true;
}
/*
==================
idAsyncServer::SendReliablePureToClient
==================
*/
bool idAsyncServer::SendReliablePureToClient( int clientNum ) {
idBitMsg msg;
byte msgBuf[ MAX_MESSAGE_SIZE ];
int serverChecksums[ MAX_PURE_PAKS ];
int i;
fileSystem->GetPureServerChecksums( serverChecksums );
if ( !serverChecksums[ 0 ] ) {
// happens if you run fully expanded assets with si_pure 1
common->Warning( "pure server has no pak files referenced" );
return false;
}
common->DPrintf( "client %d: sending pure pak list (reliable channel) @ gameInitId %d\n", clientNum, gameInitId );
msg.Init( msgBuf, sizeof( msgBuf ) );
msg.WriteByte( SERVER_RELIABLE_MESSAGE_PURE );
msg.WriteInt( gameInitId );
i = 0;
while ( serverChecksums[ i ] ) {
msg.WriteInt( serverChecksums[ i++ ] );
}
msg.WriteInt( 0 );
SendReliableMessage( clientNum, msg );
return true;
}
/*
==================
idAsyncServer::ValidateChallenge
==================
*/
int idAsyncServer::ValidateChallenge( const netadr_t from, int challenge, int clientId ) {
int i;
for ( i = 0; i < MAX_ASYNC_CLIENTS; i++ ) {
const serverClient_t &client = clients[i];
if ( client.clientState == SCS_FREE ) {
continue;
}
if ( Sys_CompareNetAdrBase( from, client.channel.GetRemoteAddress() ) &&
( clientId == client.clientId || from.port == client.channel.GetRemoteAddress().port ) ) {
if ( serverTime - client.lastConnectTime < MIN_RECONNECT_TIME ) {
common->Printf( "%s: reconnect rejected : too soon\n", Sys_NetAdrToString( from ) );
return -1;
}
break;
}
}
for ( i = 0; i < MAX_CHALLENGES; i++ ) {
if ( Sys_CompareNetAdrBase( from, challenges[i].address ) && from.port == challenges[i].address.port ) {
if ( challenge == challenges[i].challenge ) {
break;
}
}
}
if ( i == MAX_CHALLENGES ) {
PrintOOB( from, SERVER_PRINT_BADCHALLENGE, "#str_04840" );
return -1;
}
return i;
}
/*
==================
idAsyncServer::ProcessConnectMessage
==================
*/
void idAsyncServer::ProcessConnectMessage( const netadr_t from, const idBitMsg &msg ) {
int clientNum, protocol, clientDataChecksum, challenge, clientId, ping, clientRate;
idBitMsg outMsg;
byte msgBuf[ MAX_MESSAGE_SIZE ];
char guid[ 12 ];
char password[ 17 ];
int i, ichallenge, islot, numClients;
protocol = msg.ReadInt();
// check the protocol version
if ( protocol != ASYNC_PROTOCOL_VERSION ) {
// that's a msg back to a client, we don't know about it's localization, so send english
PrintOOB( from, SERVER_PRINT_BADPROTOCOL, va( "server uses protocol %d.%d\n", ASYNC_PROTOCOL_MAJOR, ASYNC_PROTOCOL_MINOR ) );
return;
}
clientDataChecksum = msg.ReadInt();
challenge = msg.ReadInt();
clientId = msg.ReadShort();
clientRate = msg.ReadInt();
// check the client data - only for non pure servers
if ( !sessLocal.mapSpawnData.serverInfo.GetInt( "si_pure" ) && clientDataChecksum != serverDataChecksum ) {
PrintOOB( from, SERVER_PRINT_MISC, "#str_04842" );
return;
}
if ( ( ichallenge = ValidateChallenge( from, challenge, clientId ) ) == -1 ) {
return;
}
msg.ReadString( guid, sizeof( guid ) );
switch ( challenges[ ichallenge ].authState ) {
case CDK_PUREWAIT:
SendPureServerMessage( from );
return;
case CDK_ONLYLAN:
common->DPrintf( "%s: not a lan client\n", Sys_NetAdrToString( from ) );
PrintOOB( from, SERVER_PRINT_MISC, "#str_04843" );
return;
case CDK_WAIT:
if ( challenges[ ichallenge ].authReply == AUTH_NONE && Min( serverTime - lastAuthTime, serverTime - challenges[ ichallenge ].time ) > AUTHORIZE_TIMEOUT ) {
common->DPrintf( "%s: Authorize server timed out\n", Sys_NetAdrToString( from ) );
break; // will continue with the connecting process
}
const char *msg, *l_msg;
if ( challenges[ ichallenge ].authReplyMsg != AUTH_REPLY_PRINT ) {
msg = authReplyMsg[ challenges[ ichallenge ].authReplyMsg ];
} else {
msg = challenges[ ichallenge ].authReplyPrint.c_str();
}
l_msg = common->GetLanguageDict()->GetString( msg );
common->DPrintf( "%s: %s\n", Sys_NetAdrToString( from ), l_msg );
if ( challenges[ ichallenge ].authReplyMsg == AUTH_REPLY_UNKNOWN || challenges[ ichallenge ].authReplyMsg == AUTH_REPLY_WAITING ) {
// the client may be trying to connect to us in LAN mode, and the server disagrees
// let the client know so it would switch to authed connection
idBitMsg outMsg;
byte msgBuf[ MAX_MESSAGE_SIZE ];
outMsg.Init( msgBuf, sizeof( msgBuf ) );
outMsg.WriteShort( CONNECTIONLESS_MESSAGE_ID );
outMsg.WriteString( "authrequired" );
serverPort.SendPacket( from, outMsg.GetData(), outMsg.GetSize() );
}
PrintOOB( from, SERVER_PRINT_MISC, msg );
// update the guid in the challenges
idStr::snPrintf( challenges[ ichallenge ].guid, sizeof( challenges[ ichallenge ].guid ), guid );
// once auth replied denied, stop sending further requests
if ( challenges[ ichallenge ].authReply != AUTH_DENY ) {
// emit a cd key confirmation request
outMsg.Init( msgBuf, sizeof( msgBuf ) );
outMsg.WriteShort( CONNECTIONLESS_MESSAGE_ID );
outMsg.WriteString( "srvAuth" );
outMsg.WriteInt( ASYNC_PROTOCOL_VERSION );
outMsg.WriteNetadr( from );
outMsg.WriteInt( clientId );
outMsg.WriteString( guid );
// protocol 1.37 addition
outMsg.WriteByte( fileSystem->RunningD3XP() );
serverPort.SendPacket( idAsyncNetwork::GetMasterAddress(), outMsg.GetData(), outMsg.GetSize() );
}
return;
default:
assert( challenges[ ichallenge ].authState == CDK_OK || challenges[ ichallenge ].authState == CDK_PUREOK );
}
numClients = 0;
for ( i = 0; i < MAX_ASYNC_CLIENTS; i++ ) {
serverClient_t &client = clients[ i ];
if ( client.clientState >= SCS_PUREWAIT ) {
numClients++;
}
}
// game may be passworded, client banned by IP or GUID
// if authState == CDK_PUREOK, the check was already performed once before entering pure checks
// but meanwhile, the max players may have been reached
msg.ReadString( password, sizeof( password ) );
char reason[MAX_STRING_CHARS];
allowReply_t reply = game->ServerAllowClient( numClients, Sys_NetAdrToString( from ), guid, password, reason );
if ( reply != ALLOW_YES ) {
common->DPrintf( "game denied connection for %s\n", Sys_NetAdrToString( from ) );
// SERVER_PRINT_GAMEDENY passes the game opcode through. Don't use PrintOOB
outMsg.Init( msgBuf, sizeof( msgBuf ) );
outMsg.WriteShort( CONNECTIONLESS_MESSAGE_ID );
outMsg.WriteString( "print" );
outMsg.WriteInt( SERVER_PRINT_GAMEDENY );
outMsg.WriteInt( reply );
outMsg.WriteString( reason );
serverPort.SendPacket( from, outMsg.GetData(), outMsg.GetSize() );
return;
}
// enter pure checks if necessary
if ( sessLocal.mapSpawnData.serverInfo.GetInt( "si_pure" ) && challenges[ ichallenge ].authState != CDK_PUREOK ) {
if ( SendPureServerMessage( from ) ) {
challenges[ ichallenge ].authState = CDK_PUREWAIT;
return;
}
}
// push back decl checksum here when running pure. just an additional safe check
if ( sessLocal.mapSpawnData.serverInfo.GetInt( "si_pure" ) && clientDataChecksum != serverDataChecksum ) {
PrintOOB( from, SERVER_PRINT_MISC, "#str_04844" );
return;
}
ping = serverTime - challenges[ ichallenge ].pingTime;
common->Printf( "challenge from %s connecting with %d ping\n", Sys_NetAdrToString( from ), ping );
challenges[ ichallenge ].connected = true;
// find a slot for the client
for ( islot = 0; islot < 3; islot++ ) {
for ( clientNum = 0; clientNum < MAX_ASYNC_CLIENTS; clientNum++ ) {
serverClient_t &client = clients[ clientNum ];
if ( islot == 0 ) {
// if this slot uses the same IP and port
if ( Sys_CompareNetAdrBase( from, client.channel.GetRemoteAddress() ) &&
( clientId == client.clientId || from.port == client.channel.GetRemoteAddress().port ) ) {
break;
}
} else if ( islot == 1 ) {
// if this client is not connected and the slot uses the same IP
if ( client.clientState >= SCS_PUREWAIT ) {
continue;
}
if ( Sys_CompareNetAdrBase( from, client.channel.GetRemoteAddress() ) ) {
break;
}
} else if ( islot == 2 ) {
// if this slot is free
if ( client.clientState == SCS_FREE ) {
break;
}
}
}
if ( clientNum < MAX_ASYNC_CLIENTS ) {
// initialize
clients[ clientNum ].channel.Init( from, serverId );
strncpy( clients[ clientNum ].guid, guid, 12 );
clients[ clientNum ].guid[11] = 0;
break;
}
}
// if no free spots available
if ( clientNum >= MAX_ASYNC_CLIENTS ) {
PrintOOB( from, SERVER_PRINT_MISC, "#str_04845" );
return;
}
common->Printf( "sending connect response to %s\n", Sys_NetAdrToString( from ) );
// send connect response message
outMsg.Init( msgBuf, sizeof( msgBuf ) );
outMsg.WriteShort( CONNECTIONLESS_MESSAGE_ID );
outMsg.WriteString( "connectResponse" );
outMsg.WriteInt( clientNum );
outMsg.WriteInt( gameInitId );
outMsg.WriteInt( gameFrame );
outMsg.WriteInt( gameTime );
outMsg.WriteDeltaDict( sessLocal.mapSpawnData.serverInfo, NULL );
serverPort.SendPacket( from, outMsg.GetData(), outMsg.GetSize() );
InitClient( clientNum, clientId, clientRate );
clients[clientNum].gameInitSequence = 1;
clients[clientNum].snapshotSequence = 1;
// clear the challenge struct so a reconnect from this client IP starts clean
memset( &challenges[ ichallenge ], 0, sizeof( challenge_t ) );
}
/*
==================
idAsyncServer::VerifyChecksumMessage
==================
*/
bool idAsyncServer::VerifyChecksumMessage( int clientNum, const netadr_t *from, const idBitMsg &msg, idStr &reply ) {
int i, numChecksums;
int checksums[ MAX_PURE_PAKS ];
int serverChecksums[ MAX_PURE_PAKS ];
// pak checksums, in a 0-terminated list
numChecksums = 0;
do {
i = msg.ReadInt( );
checksums[ numChecksums++ ] = i;
// just to make sure a broken client doesn't crash us
if ( numChecksums >= MAX_PURE_PAKS ) {
common->Warning( "MAX_PURE_PAKS ( %d ) exceeded in idAsyncServer::ProcessPureMessage\n", MAX_PURE_PAKS );
sprintf( reply, "#str_07144" );
return false;
}
} while ( i );
numChecksums--;
fileSystem->GetPureServerChecksums( serverChecksums );
assert( serverChecksums[ 0 ] );
for ( i = 0; serverChecksums[ i ] != 0; i++ ) {
if ( checksums[ i ] != serverChecksums[ i ] ) {
common->DPrintf( "client %s: pak missing ( 0x%x )\n", from ? Sys_NetAdrToString( *from ) : va( "%d", clientNum ), serverChecksums[ i ] );
sprintf( reply, "pak missing ( 0x%x )\n", serverChecksums[ i ] );
return false;
}
}
if ( checksums[ i ] != 0 ) {
common->DPrintf( "client %s: extra pak file referenced ( 0x%x )\n", from ? Sys_NetAdrToString( *from ) : va( "%d", clientNum ), checksums[ i ] );
sprintf( reply, "extra pak file referenced ( 0x%x )\n", checksums[ i ] );
return false;
}
return true;
}
/*
==================
idAsyncServer::ProcessPureMessage
==================
*/
void idAsyncServer::ProcessPureMessage( const netadr_t from, const idBitMsg &msg ) {
int iclient, challenge, clientId;
idStr reply;
challenge = msg.ReadInt();
clientId = msg.ReadShort();
if ( ( iclient = ValidateChallenge( from, challenge, clientId ) ) == -1 ) {
return;
}
if ( challenges[ iclient ].authState != CDK_PUREWAIT ) {
common->DPrintf( "client %s: got pure message, not in CDK_PUREWAIT\n", Sys_NetAdrToString( from ) );
return;
}
if ( !VerifyChecksumMessage( iclient, &from, msg, reply ) ) {
PrintOOB( from, SERVER_PRINT_MISC, reply );
return;
}
common->DPrintf( "client %s: passed pure checks\n", Sys_NetAdrToString( from ) );
challenges[ iclient ].authState = CDK_PUREOK; // next connect message will get the client through completely
}
/*
==================
idAsyncServer::ProcessReliablePure
==================
*/
void idAsyncServer::ProcessReliablePure( int clientNum, const idBitMsg &msg ) {
idStr reply;
idBitMsg outMsg;
byte msgBuf[MAX_MESSAGE_SIZE];
int clientGameInitId;
clientGameInitId = msg.ReadInt();
if ( clientGameInitId != gameInitId ) {
common->DPrintf( "client %d: ignoring reliable pure from an old gameInit (%d)\n", clientNum, clientGameInitId );
return;
}
if ( clients[ clientNum ].clientState != SCS_PUREWAIT ) {
// should not happen unless something is very wrong. still, don't let this crash us, just get rid of the client
common->DPrintf( "client %d: got reliable pure while != SCS_PUREWAIT, sending a reload\n", clientNum );
outMsg.Init( msgBuf, sizeof( msgBuf ) );
outMsg.WriteByte( SERVER_RELIABLE_MESSAGE_RELOAD );
SendReliableMessage( clientNum, msg );
// go back to SCS_CONNECTED to sleep on the client until it goes away for a reconnect
clients[ clientNum ].clientState = SCS_CONNECTED;
return;
}
if ( !VerifyChecksumMessage( clientNum, NULL, msg, reply ) ) {
DropClient( clientNum, reply );
return;
}
common->DPrintf( "client %d: passed pure checks (reliable channel)\n", clientNum );
clients[ clientNum ].clientState = SCS_CONNECTED;
}
/*
==================
idAsyncServer::RemoteConsoleOutput
==================
*/
void idAsyncServer::RemoteConsoleOutput( const char *string ) {
noRconOutput = false;
PrintOOB( rconAddress, SERVER_PRINT_RCON, string );
}
/*
==================
RConRedirect
==================
*/
void RConRedirect( const char *string ) {
idAsyncNetwork::server.RemoteConsoleOutput( string );
}
/*
==================
idAsyncServer::ProcessRemoteConsoleMessage
==================
*/
void idAsyncServer::ProcessRemoteConsoleMessage( const netadr_t from, const idBitMsg &msg ) {
idBitMsg outMsg;
byte msgBuf[952];
char string[MAX_STRING_CHARS];
if ( idAsyncNetwork::serverRemoteConsolePassword.GetString()[0] == '\0' ) {
PrintOOB( from, SERVER_PRINT_MISC, "#str_04846" );
return;
}
msg.ReadString( string, sizeof( string ) );
if ( idStr::Icmp( string, idAsyncNetwork::serverRemoteConsolePassword.GetString() ) != 0 ) {
PrintOOB( from, SERVER_PRINT_MISC, "#str_04847" );
return;
}
msg.ReadString( string, sizeof( string ) );
common->Printf( "rcon from %s: %s\n", Sys_NetAdrToString( from ), string );
rconAddress = from;
noRconOutput = true;
common->BeginRedirect( (char *)msgBuf, sizeof( msgBuf ), RConRedirect );
cmdSystem->BufferCommandText( CMD_EXEC_NOW, string );
common->EndRedirect();
if ( noRconOutput ) {
PrintOOB( rconAddress, SERVER_PRINT_RCON, "#str_04848" );
}
}
/*
==================
idAsyncServer::ProcessGetInfoMessage
==================
*/
void idAsyncServer::ProcessGetInfoMessage( const netadr_t from, const idBitMsg &msg ) {
int i, challenge;
idBitMsg outMsg;
byte msgBuf[MAX_MESSAGE_SIZE];
if ( !IsActive() ) {
return;
}
common->DPrintf( "Sending info response to %s\n", Sys_NetAdrToString( from ) );
challenge = msg.ReadInt();
outMsg.Init( msgBuf, sizeof( msgBuf ) );
outMsg.WriteShort( CONNECTIONLESS_MESSAGE_ID );
outMsg.WriteString( "infoResponse" );
outMsg.WriteInt( challenge );
outMsg.WriteInt( ASYNC_PROTOCOL_VERSION );
outMsg.WriteDeltaDict( sessLocal.mapSpawnData.serverInfo, NULL );
for ( i = 0; i < MAX_ASYNC_CLIENTS; i++ ) {
serverClient_t &client = clients[i];
if ( client.clientState < SCS_CONNECTED ) {
continue;
}
outMsg.WriteByte( i );
outMsg.WriteShort( client.clientPing );
outMsg.WriteInt( client.channel.GetMaxOutgoingRate() );
outMsg.WriteString( sessLocal.mapSpawnData.userInfo[i].GetString( "ui_name", "Player" ) );
}
outMsg.WriteByte( MAX_ASYNC_CLIENTS );
// Stradex: Originally Doom3 did outMsg.WriteLong( fileSystem->GetOSMask() ); here
// dhewm3 eliminated GetOSMask() and WriteLong() became WriteInt() as it's supposed to write an int32
// Sending -1 (instead of nothing at all) restores compatibility with id's masterserver.
outMsg.WriteInt( -1 );
serverPort.SendPacket( from, outMsg.GetData(), outMsg.GetSize() );
}
/*
===============
idAsyncServer::PrintLocalServerInfo
see (client) "getInfo" -> (server) "infoResponse" -> (client)ProcessGetInfoMessage
===============
*/
void idAsyncServer::PrintLocalServerInfo( void ) {
int i;
common->Printf( "server '%s' IP = %s\nprotocol %d.%d\n",
sessLocal.mapSpawnData.serverInfo.GetString( "si_name" ),
Sys_NetAdrToString( serverPort.GetAdr() ),
ASYNC_PROTOCOL_MAJOR,
ASYNC_PROTOCOL_MINOR );
sessLocal.mapSpawnData.serverInfo.Print();
for ( i = 0; i < MAX_ASYNC_CLIENTS; i++ ) {
serverClient_t &client = clients[i];
if ( client.clientState < SCS_CONNECTED ) {
continue;
}
common->Printf( "client %2d: %s, ping = %d, rate = %d\n", i,
sessLocal.mapSpawnData.userInfo[i].GetString( "ui_name", "Player" ),
client.clientPing, client.channel.GetMaxOutgoingRate() );
}
}
/*
==================
idAsyncServer::ConnectionlessMessage
==================
*/
bool idAsyncServer::ConnectionlessMessage( const netadr_t from, const idBitMsg &msg ) {
char string[MAX_STRING_CHARS*2]; // M. Quinn - Even Balance - PB Packets need more than 1024
msg.ReadString( string, sizeof( string ) );
// info request
if ( idStr::Icmp( string, "getInfo" ) == 0 ) {
ProcessGetInfoMessage( from, msg );
return false;
}
// remote console
if ( idStr::Icmp( string, "rcon" ) == 0 ) {
ProcessRemoteConsoleMessage( from, msg );
return true;
}
if ( !active ) {
PrintOOB( from, SERVER_PRINT_MISC, "#str_04849" );
return false;
}
// challenge from a client
if ( idStr::Icmp( string, "challenge" ) == 0 ) {
ProcessChallengeMessage( from, msg );
return false;
}
// connect from a client
if ( idStr::Icmp( string, "connect" ) == 0 ) {
ProcessConnectMessage( from, msg );
return false;
}
// pure mesasge from a client
if ( idStr::Icmp( string, "pureClient" ) == 0 ) {
ProcessPureMessage( from, msg );
return false;
}
// download request
if ( idStr::Icmp( string, "downloadRequest" ) == 0 ) {
ProcessDownloadRequestMessage( from, msg );
}
// auth server
if ( idStr::Icmp( string, "auth" ) == 0 ) {
if ( !Sys_CompareNetAdrBase( from, idAsyncNetwork::GetMasterAddress() ) ) {
common->Printf( "auth: bad source %s\n", Sys_NetAdrToString( from ) );
return false;
}
if ( idAsyncNetwork::LANServer.GetBool() ) {
common->Printf( "auth message from master. net_LANServer is enabled, ignored.\n" );
}
ProcessAuthMessage( msg );
return false;
}
return false;
}
/*
==================
idAsyncServer::ProcessMessage
==================
*/
bool idAsyncServer::ProcessMessage( const netadr_t from, idBitMsg &msg ) {
int i, id, sequence;
idBitMsg outMsg;
byte msgBuf[MAX_MESSAGE_SIZE];
id = msg.ReadShort();
// check for a connectionless message
if ( id == CONNECTIONLESS_MESSAGE_ID ) {
return ConnectionlessMessage( from, msg );
}
if ( msg.GetRemaingData() < 4 ) {
common->DPrintf( "%s: tiny packet\n", Sys_NetAdrToString( from ) );
return false;
}
// find out which client the message is from
for ( i = 0; i < MAX_ASYNC_CLIENTS; i++ ) {
serverClient_t &client = clients[i];
if ( client.clientState == SCS_FREE ) {
continue;
}
// This does not compare the UDP port, because some address translating
// routers will change that at arbitrary times.
if ( !Sys_CompareNetAdrBase( from, client.channel.GetRemoteAddress() ) || id != client.clientId ) {
continue;
}
// make sure it is a valid, in sequence packet
if ( !client.channel.Process( from, serverTime, msg, sequence ) ) {
return false; // out of order, duplicated, fragment, etc.
}
// zombie clients still need to do the channel processing to make sure they don't
// need to retransmit the final reliable message, but they don't do any other processing
if ( client.clientState == SCS_ZOMBIE ) {
return false;
}
client.lastPacketTime = serverTime;
ProcessReliableClientMessages( i );
ProcessUnreliableClientMessage( i, msg );
return false;
}
// if we received a sequenced packet from an address we don't recognize,
// send an out of band disconnect packet to it
outMsg.Init( msgBuf, sizeof( msgBuf ) );
outMsg.WriteShort( CONNECTIONLESS_MESSAGE_ID );
outMsg.WriteString( "disconnect" );
serverPort.SendPacket( from, outMsg.GetData(), outMsg.GetSize() );
return false;
}
/*
==================
idAsyncServer::SendReliableGameMessage
==================
*/
void idAsyncServer::SendReliableGameMessage( int clientNum, const idBitMsg &msg ) {
int i;
idBitMsg outMsg;
byte msgBuf[MAX_MESSAGE_SIZE];
outMsg.Init( msgBuf, sizeof( msgBuf ) );
outMsg.WriteByte( SERVER_RELIABLE_MESSAGE_GAME );
outMsg.WriteData( msg.GetData(), msg.GetSize() );
if ( clientNum >= 0 && clientNum < MAX_ASYNC_CLIENTS ) {
if ( clients[clientNum].clientState == SCS_INGAME ) {
SendReliableMessage( clientNum, outMsg );
}
return;
}
for ( i = 0; i < MAX_ASYNC_CLIENTS; i++ ) {
if ( clients[i].clientState != SCS_INGAME ) {
continue;
}
SendReliableMessage( i, outMsg );
}
}
/*
==================
idAsyncServer::LocalClientSendReliableMessageExcluding
==================
*/
void idAsyncServer::SendReliableGameMessageExcluding( int clientNum, const idBitMsg &msg ) {
int i;
idBitMsg outMsg;
byte msgBuf[MAX_MESSAGE_SIZE];
assert( clientNum >= 0 && clientNum < MAX_ASYNC_CLIENTS );
outMsg.Init( msgBuf, sizeof( msgBuf ) );
outMsg.WriteByte( SERVER_RELIABLE_MESSAGE_GAME );
outMsg.WriteData( msg.GetData(), msg.GetSize() );
for ( i = 0; i < MAX_ASYNC_CLIENTS; i++ ) {
if ( i == clientNum ) {
continue;
}
if ( clients[i].clientState != SCS_INGAME ) {
continue;
}
SendReliableMessage( i, outMsg );
}
}
/*
==================
idAsyncServer::LocalClientSendReliableMessage
==================
*/
void idAsyncServer::LocalClientSendReliableMessage( const idBitMsg &msg ) {
if ( localClientNum < 0 ) {
common->Printf( "LocalClientSendReliableMessage: no local client\n" );
return;
}
game->ServerProcessReliableMessage( localClientNum, msg );
}
/*
==================
idAsyncServer::ProcessConnectionLessMessages
==================
*/
void idAsyncServer::ProcessConnectionLessMessages( void ) {
int size, id;
idBitMsg msg;
byte msgBuf[MAX_MESSAGE_SIZE];
netadr_t from;
if ( !serverPort.GetPort() ) {
return;
}
while( serverPort.GetPacket( from, msgBuf, size, sizeof( msgBuf ) ) ) {
msg.Init( msgBuf, sizeof( msgBuf ) );
msg.SetSize( size );
msg.BeginReading();
id = msg.ReadShort();
if ( id == CONNECTIONLESS_MESSAGE_ID ) {
ConnectionlessMessage( from, msg );
}
}
}
/*
==================
idAsyncServer::UpdateTime
==================
*/
int idAsyncServer::UpdateTime( int clamp ) {
int time, msec;
time = Sys_Milliseconds();
msec = idMath::ClampInt( 0, clamp, time - realTime );
realTime = time;
serverTime += msec;
return msec;
}
/*
==================
idAsyncServer::RunFrame
==================
*/
void idAsyncServer::RunFrame( void ) {
int i, msec, size;
bool newPacket;
idBitMsg msg;
byte msgBuf[MAX_MESSAGE_SIZE];
netadr_t from;
int outgoingRate, incomingRate;
float outgoingCompression, incomingCompression;
msec = UpdateTime( 100 );
if ( !serverPort.GetPort() ) {
return;
}
if ( !active ) {
ProcessConnectionLessMessages();
return;
}
gameTimeResidual += msec;
// spin in place processing incoming packets until enough time lapsed to run a new game frame
do {
do {
// blocking read with game time residual timeout
newPacket = serverPort.GetPacketBlocking( from, msgBuf, size, sizeof( msgBuf ), USERCMD_MSEC - gameTimeResidual - 1 );
if ( newPacket ) {
msg.Init( msgBuf, sizeof( msgBuf ) );
msg.SetSize( size );
msg.BeginReading();
if ( ProcessMessage( from, msg ) ) {
return; // return because rcon was used
}
}
msec = UpdateTime( 100 );
gameTimeResidual += msec;
} while( newPacket );
} while( gameTimeResidual < USERCMD_MSEC );
// send heart beat to master servers
MasterHeartbeat();
// check for clients that timed out
CheckClientTimeouts();
if ( idAsyncNetwork::idleServer.GetBool() == ( !GetNumClients() || GetNumIdleClients() != GetNumClients() ) ) {
idAsyncNetwork::idleServer.SetBool( !idAsyncNetwork::idleServer.GetBool() );
// the need to propagate right away, only this
sessLocal.mapSpawnData.serverInfo.Set( "si_idleServer", idAsyncNetwork::idleServer.GetString() );
game->SetServerInfo( sessLocal.mapSpawnData.serverInfo );
}
// make sure the time doesn't wrap
if ( serverTime > 0x70000000 ) {
ExecuteMapChange();
return;
}
// check for synchronized cvar changes
if ( cvarSystem->GetModifiedFlags() & CVAR_NETWORKSYNC ) {
idDict newCvars;
newCvars = *cvarSystem->MoveCVarsToDict( CVAR_NETWORKSYNC );
SendSyncedCvarsBroadcast( newCvars );
cvarSystem->ClearModifiedFlags( CVAR_NETWORKSYNC );
}
// check for user info changes of the local client
if ( cvarSystem->GetModifiedFlags() & CVAR_USERINFO ) {
if ( localClientNum >= 0 ) {
idDict newInfo;
game->ThrottleUserInfo( );
newInfo = *cvarSystem->MoveCVarsToDict( CVAR_USERINFO );
SendUserInfoBroadcast( localClientNum, newInfo );
}
cvarSystem->ClearModifiedFlags( CVAR_USERINFO );
}
// advance the server game
while( gameTimeResidual >= USERCMD_MSEC ) {
// sample input for the local client
LocalClientInput();
// duplicate usercmds for clients if no new ones are available
DuplicateUsercmds( gameFrame, gameTime );
// advance game
gameReturn_t ret = game->RunFrame( userCmds[gameFrame & ( MAX_USERCMD_BACKUP - 1 ) ] );
idAsyncNetwork::ExecuteSessionCommand( ret.sessionCommand );
// update time
gameFrame++;
gameTime += USERCMD_MSEC;
gameTimeResidual -= USERCMD_MSEC;
}
// duplicate usercmds so there is always at least one available to send with snapshots
DuplicateUsercmds( gameFrame, gameTime );
// send snapshots to connected clients
for ( i = 0; i < MAX_ASYNC_CLIENTS; i++ ) {
serverClient_t &client = clients[i];
if ( client.clientState == SCS_FREE || i == localClientNum ) {
continue;
}
// modify maximum rate if necesary
if ( idAsyncNetwork::serverMaxClientRate.IsModified() ) {
client.channel.SetMaxOutgoingRate( Min( client.clientRate, idAsyncNetwork::serverMaxClientRate.GetInteger() ) );
}
// if the channel is not yet ready to send new data
if ( !client.channel.ReadyToSend( serverTime ) ) {
continue;
}
// send additional message fragments if the last message was too large to send at once
if ( client.channel.UnsentFragmentsLeft() ) {
client.channel.SendNextFragment( serverPort, serverTime );
continue;
}
if ( client.clientState == SCS_INGAME ) {
if ( !SendSnapshotToClient( i ) ) {
SendPingToClient( i );
}
} else {
SendEmptyToClient( i );
}
}
if ( com_showAsyncStats.GetBool() ) {
UpdateAsyncStatsAvg();
// dedicated will verbose to console
if ( idAsyncNetwork::serverDedicated.GetBool() && serverTime >= nextAsyncStatsTime ) {
common->Printf( "delay = %d msec, total outgoing rate = %d KB/s, total incoming rate = %d KB/s\n", GetDelay(),
GetOutgoingRate() >> 10, GetIncomingRate() >> 10 );
for ( i = 0; i < MAX_ASYNC_CLIENTS; i++ ) {
outgoingRate = GetClientOutgoingRate( i );
incomingRate = GetClientIncomingRate( i );
outgoingCompression = GetClientOutgoingCompression( i );
incomingCompression = GetClientIncomingCompression( i );
if ( outgoingRate != -1 && incomingRate != -1 ) {
common->Printf( "client %d: out rate = %d B/s (% -2.1f%%), in rate = %d B/s (% -2.1f%%)\n",
i, outgoingRate, outgoingCompression, incomingRate, incomingCompression );
}
}
idStr msg;
GetAsyncStatsAvgMsg( msg );
common->Printf( va( "%s\n", msg.c_str() ) );
nextAsyncStatsTime = serverTime + 1000;
}
}
idAsyncNetwork::serverMaxClientRate.ClearModified();
}
/*
==================
idAsyncServer::PacifierUpdate
==================
*/
void idAsyncServer::PacifierUpdate( void ) {
int i;
if ( !IsActive() ) {
return;
}
realTime = Sys_Milliseconds();
ProcessConnectionLessMessages();
for ( i = 0; i < MAX_ASYNC_CLIENTS; i++ ) {
if ( clients[i].clientState >= SCS_PUREWAIT ) {
if ( clients[i].channel.UnsentFragmentsLeft() ) {
clients[i].channel.SendNextFragment( serverPort, serverTime );
} else {
SendEmptyToClient( i );
}
}
}
}
/*
==================
idAsyncServer::PrintOOB
==================
*/
void idAsyncServer::PrintOOB( const netadr_t to, int opcode, const char *string ) {
idBitMsg outMsg;
byte msgBuf[ MAX_MESSAGE_SIZE ];
outMsg.Init( msgBuf, sizeof( msgBuf ) );
outMsg.WriteShort( CONNECTIONLESS_MESSAGE_ID );
outMsg.WriteString( "print" );
outMsg.WriteInt( opcode );
outMsg.WriteString( string );
serverPort.SendPacket( to, outMsg.GetData(), outMsg.GetSize() );
}
/*
==================
idAsyncServer::MasterHeartbeat
==================
*/
void idAsyncServer::MasterHeartbeat( bool force ) {
if ( idAsyncNetwork::LANServer.GetBool() ) {
if ( force ) {
common->Printf( "net_LANServer is enabled. Not sending heartbeats\n" );
}
return;
}
if ( force ) {
nextHeartbeatTime = 0;
}
// not yet
if ( serverTime < nextHeartbeatTime ) {
return;
}
nextHeartbeatTime = serverTime + HEARTBEAT_MSEC;
for ( int i = 0 ; i < MAX_MASTER_SERVERS ; i++ ) {
netadr_t adr;
if ( idAsyncNetwork::GetMasterAddress( i, adr ) ) {
common->Printf( "Sending heartbeat to %s\n", Sys_NetAdrToString( adr ) );
idBitMsg outMsg;
byte msgBuf[ MAX_MESSAGE_SIZE ];
outMsg.Init( msgBuf, sizeof( msgBuf ) );
outMsg.WriteShort( CONNECTIONLESS_MESSAGE_ID );
outMsg.WriteString( "heartbeat" );
serverPort.SendPacket( adr, outMsg.GetData(), outMsg.GetSize() );
}
}
}
/*
===============
idAsyncServer::SendEnterGameToClient
===============
*/
void idAsyncServer::SendEnterGameToClient( int clientNum ) {
idBitMsg msg;
byte msgBuf[ MAX_MESSAGE_SIZE ];
msg.Init( msgBuf, sizeof( msgBuf ) );
msg.WriteByte( SERVER_RELIABLE_MESSAGE_ENTERGAME );
SendReliableMessage( clientNum, msg );
}
/*
===============
idAsyncServer::UpdateAsyncStatsAvg
===============
*/
void idAsyncServer::UpdateAsyncStatsAvg( void ) {
stats_average_sum -= stats_outrate[ stats_current ];
stats_outrate[ stats_current ] = idAsyncNetwork::server.GetOutgoingRate();
if ( stats_outrate[ stats_current ] > stats_max ) {
stats_max = stats_outrate[ stats_current ];
stats_max_index = stats_current;
} else if ( stats_current == stats_max_index ) {
// find the new max
int i;
stats_max = 0;
for ( i = 0; i < stats_numsamples ; i++ ) {
if ( stats_outrate[ i ] > stats_max ) {
stats_max = stats_outrate[ i ];
stats_max_index = i;
}
}
}
stats_average_sum += stats_outrate[ stats_current ];
stats_current++; stats_current %= stats_numsamples;
}
/*
===============
idAsyncServer::GetAsyncStatsAvgMsg
===============
*/
void idAsyncServer::GetAsyncStatsAvgMsg( idStr &msg ) {
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 );
}
/*
===============
idAsyncServer::ProcessDownloadRequestMessage
===============
*/
void idAsyncServer::ProcessDownloadRequestMessage( const netadr_t from, const idBitMsg &msg ) {
int challenge, clientId, iclient, numPaks, i;
int dlPakChecksum;
int dlSize[ MAX_PURE_PAKS ]; // sizes
idStrList pakNames; // relative path
idStrList pakURLs; // game URLs
char pakbuf[ MAX_STRING_CHARS ];
idStr paklist;
byte msgBuf[ MAX_MESSAGE_SIZE ];
byte tmpBuf[ MAX_MESSAGE_SIZE ];
idBitMsg outMsg, tmpMsg;
int dlRequest;
int voidSlots = 0; // to count and verbose the right number of paks requested for downloads
challenge = msg.ReadInt();
clientId = msg.ReadShort();
dlRequest = msg.ReadInt();
if ( ( iclient = ValidateChallenge( from, challenge, clientId ) ) == -1 ) {
return;
}
if ( challenges[ iclient ].authState != CDK_PUREWAIT ) {
common->DPrintf( "client %s: got download request message, not in CDK_PUREWAIT\n", Sys_NetAdrToString( from ) );
return;
}
pakNames.Append( pakbuf );
numPaks = 1;
// read the checksums, build path names and pass that to the game code
dlPakChecksum = msg.ReadInt();
while ( dlPakChecksum ) {
if ( !( dlSize[ numPaks ] = fileSystem->ValidateDownloadPakForChecksum( dlPakChecksum, pakbuf ) ) ) {
// we pass an empty token to the game so our list doesn't get offset
common->Warning( "client requested an unknown pak 0x%x", dlPakChecksum );
pakbuf[ 0 ] = '\0';
voidSlots++;
}
pakNames.Append( pakbuf );
numPaks++;
dlPakChecksum = msg.ReadInt();
}
for ( i = 0; i < pakNames.Num(); i++ ) {
if ( i > 0 ) {
paklist += ";";
}
paklist += pakNames[ i ].c_str();
}
// read the message and pass it to the game code
common->DPrintf( "got download request for %d paks - %s\n", numPaks - voidSlots, paklist.c_str() );
outMsg.Init( msgBuf, sizeof( msgBuf ) );
outMsg.WriteShort( CONNECTIONLESS_MESSAGE_ID );
outMsg.WriteString( "downloadInfo" );
outMsg.WriteInt( dlRequest );
if ( !game->DownloadRequest( Sys_NetAdrToString( from ), challenges[ iclient ].guid, paklist.c_str(), pakbuf ) ) {
common->DPrintf( "game: no downloads\n" );
outMsg.WriteByte( SERVER_DL_NONE );
serverPort.SendPacket( from, outMsg.GetData(), outMsg.GetSize() );
return;
}
char *token, *next;
int type = 0;
token = pakbuf;
next = strchr( token, ';' );
while ( token ) {
if ( next ) {
*next = '\0';
}
if ( type == 0 ) {
type = atoi( token );
} else if ( type == SERVER_DL_REDIRECT ) {
common->DPrintf( "download request: redirect to URL %s\n", token );
outMsg.WriteByte( SERVER_DL_REDIRECT );
outMsg.WriteString( token );
serverPort.SendPacket( from, outMsg.GetData(), outMsg.GetSize() );
return;
} else if ( type == SERVER_DL_LIST ) {
pakURLs.Append( token );
} else {
common->DPrintf( "wrong op type %d\n", type );
next = token = NULL;
}
if ( next ) {
token = next + 1;
next = strchr( token, ';' );
} else {
token = NULL;
}
}
if ( type == SERVER_DL_LIST ) {
int totalDlSize = 0;
int numActualPaks = 0;
// put the answer packet together
outMsg.WriteByte( SERVER_DL_LIST );
tmpMsg.Init( tmpBuf, MAX_MESSAGE_SIZE );
for ( i = 0; i < pakURLs.Num(); i++ ) {
tmpMsg.BeginWriting();
if ( !dlSize[ i ] || !pakURLs[ i ].Length() ) {
// still send the relative path so the client knows what it missed
tmpMsg.WriteByte( SERVER_PAK_NO );
tmpMsg.WriteString( pakNames[ i ] );
} else {
totalDlSize += dlSize[ i ];
numActualPaks++;
tmpMsg.WriteByte( SERVER_PAK_YES );
tmpMsg.WriteString( pakNames[ i ] );
tmpMsg.WriteString( pakURLs[ i ] );
tmpMsg.WriteInt( dlSize[ i ] );
}
// keep last 5 bytes for an 'end of message' - SERVER_PAK_END and the totalDlSize long
if ( outMsg.GetRemainingSpace() - tmpMsg.GetSize() > 5 ) {
outMsg.WriteData( tmpMsg.GetData(), tmpMsg.GetSize() );
} else {
outMsg.WriteByte( SERVER_PAK_END );
break;
}
}
if ( i == pakURLs.Num() ) {
// put a closure even if size not exceeded
outMsg.WriteByte( SERVER_PAK_END );
}
common->DPrintf( "download request: download %d paks, %d bytes\n", numActualPaks, totalDlSize );
serverPort.SendPacket( from, outMsg.GetData(), outMsg.GetSize() );
}
}