mirror of
https://github.com/ptitSeb/Serious-Engine
synced 2024-11-29 21:25:54 +01:00
1609 lines
49 KiB
C++
1609 lines
49 KiB
C++
/* Copyright (c) 2002-2012 Croteam Ltd.
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of version 2 of the GNU General Public License as published by
|
|
the Free Software Foundation
|
|
|
|
|
|
This program 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 this program; if not, write to the Free Software Foundation, Inc.,
|
|
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */
|
|
|
|
#include "stdh.h"
|
|
|
|
#include <Engine/Build.h>
|
|
#include <Engine/Network/Network.h>
|
|
#include <Engine/Network/Server.h>
|
|
#include <Engine/Network/SessionState.h>
|
|
#include <Engine/Network/PlayerSource.h>
|
|
#include <Engine/Network/PlayerBuffer.h>
|
|
#include <Engine/Network/PlayerTarget.h>
|
|
#include <Engine/Network/NetworkProfile.h>
|
|
#include <Engine/Network/ClientInterface.h>
|
|
#include <Engine/Network/CommunicationInterface.h>
|
|
#include <Engine/Network/Compression.h>
|
|
#include <Engine/Network/Diff.h>
|
|
#include <Engine/Network/CPacket.h>
|
|
#include <Engine/World/WorldSettings.h>
|
|
#include <Engine/Base/Console.h>
|
|
#include <Engine/Base/Shell.h>
|
|
#include <Engine/Math/Functions.h>
|
|
#include <Engine/Entities/InternalClasses.h>
|
|
#include <Engine/Base/CRC.h>
|
|
#include <Engine/Base/ErrorTable.h>
|
|
#include <Engine/GameAgent/GameAgent.h>
|
|
|
|
#include <Engine/Templates/StaticArray.cpp>
|
|
|
|
extern INDEX ser_iSyncCheckBuffer;
|
|
extern FLOAT net_tmDisconnectTimeout;
|
|
extern BOOL con_bCapture;
|
|
extern CTString con_strCapture;
|
|
extern CTString ser_strIPMask;
|
|
extern CTString ser_strNameMask;
|
|
extern INDEX ser_bInverseBanning;
|
|
extern BOOL MatchesBanMask(const CTString &strString, const CTString &strMask);
|
|
extern CClientInterface cm_aciClients[SERVER_CLIENTS];
|
|
|
|
CSessionSocket::CSessionSocket(void)
|
|
{
|
|
sso_bActive = FALSE;
|
|
sso_bVIP = FALSE;
|
|
sso_bSendStream = FALSE;
|
|
sso_iDisconnectedState = 0;
|
|
sso_iLastSentSequence = -1;
|
|
sso_ctBadSyncs = 0;
|
|
sso_tvLastMessageSent.Clear();
|
|
sso_tvLastPingSent.Clear();
|
|
}
|
|
CSessionSocket::~CSessionSocket(void)
|
|
{
|
|
sso_bActive = FALSE;
|
|
sso_bVIP = FALSE;
|
|
sso_bSendStream = FALSE;
|
|
sso_iLastSentSequence = -1;
|
|
sso_ctBadSyncs = 0;
|
|
sso_tvLastMessageSent.Clear();
|
|
sso_tvLastPingSent.Clear();
|
|
}
|
|
void CSessionSocket::Clear(void)
|
|
{
|
|
sso_bActive = FALSE;
|
|
sso_bVIP = FALSE;
|
|
sso_bSendStream = FALSE;
|
|
sso_tvMessageReceived.Clear();
|
|
sso_tmLastSyncReceived = -1.0f;
|
|
sso_iLastSentSequence = -1;
|
|
sso_tvLastMessageSent.Clear();
|
|
sso_tvLastPingSent.Clear();
|
|
sso_nsBuffer.Clear();
|
|
sso_iDisconnectedState = 0;
|
|
sso_ctBadSyncs = 0;
|
|
sso_sspParams.Clear();
|
|
}
|
|
|
|
void CSessionSocket::Activate(void)
|
|
{
|
|
#if DEBUG_SYNCSTREAMDUMPING
|
|
ClearDumpStream();
|
|
#endif
|
|
|
|
ASSERT(!sso_bActive);
|
|
sso_bActive = TRUE;
|
|
sso_bVIP = FALSE;
|
|
sso_bSendStream = FALSE;
|
|
sso_tvMessageReceived.Clear();
|
|
sso_tmLastSyncReceived = -1.0f;
|
|
sso_iLastSentSequence = -1;
|
|
sso_tvLastMessageSent.Clear();
|
|
sso_tvLastPingSent.Clear();
|
|
sso_iDisconnectedState = 0;
|
|
sso_ctBadSyncs = 0;
|
|
sso_sspParams.Clear();
|
|
// sso_nsBuffer.Clear();
|
|
}
|
|
|
|
void CSessionSocket::Deactivate(void)
|
|
{
|
|
sso_iDisconnectedState = 0;
|
|
sso_iLastSentSequence = -1;
|
|
sso_tvLastMessageSent.Clear();
|
|
sso_tvLastPingSent.Clear();
|
|
sso_ctBadSyncs = 0;
|
|
sso_bActive = FALSE;
|
|
sso_nsBuffer.Clear();
|
|
sso_sspParams.Clear();
|
|
}
|
|
BOOL CSessionSocket::IsActive(void)
|
|
{
|
|
return sso_bActive;
|
|
}
|
|
|
|
extern INDEX cli_iBufferActions;
|
|
extern INDEX cli_iMaxBPS;
|
|
extern INDEX cli_iMinBPS;
|
|
|
|
CSessionSocketParams::CSessionSocketParams(void)
|
|
{
|
|
Clear();
|
|
}
|
|
|
|
void CSessionSocketParams::Clear(void)
|
|
{
|
|
ssp_iBufferActions = 2;
|
|
ssp_iMaxBPS = 4000;
|
|
ssp_iMinBPS = 1000;
|
|
}
|
|
|
|
static void ClampParams(void)
|
|
{
|
|
cli_iBufferActions = Clamp(cli_iBufferActions, INDEX(1), INDEX(20));
|
|
cli_iMaxBPS = Clamp(cli_iMaxBPS, INDEX(100), INDEX(1000000));
|
|
cli_iMinBPS = Clamp(cli_iMinBPS, INDEX(100), INDEX(1000000));
|
|
}
|
|
|
|
// check if up to date with current params
|
|
BOOL CSessionSocketParams::IsUpToDate(void)
|
|
{
|
|
ClampParams();
|
|
return
|
|
ssp_iBufferActions == cli_iBufferActions &&
|
|
ssp_iMaxBPS == cli_iMaxBPS &&
|
|
ssp_iMinBPS == cli_iMinBPS;
|
|
}
|
|
|
|
// update
|
|
void CSessionSocketParams::Update(void)
|
|
{
|
|
ClampParams();
|
|
ssp_iBufferActions = cli_iBufferActions;
|
|
ssp_iMaxBPS = cli_iMaxBPS;
|
|
ssp_iMinBPS = cli_iMinBPS;
|
|
}
|
|
|
|
// message operations
|
|
CNetworkMessage &operator<<(CNetworkMessage &nm, CSessionSocketParams &ssp)
|
|
{
|
|
nm<<ssp.ssp_iBufferActions<<ssp.ssp_iMaxBPS<<ssp.ssp_iMinBPS;
|
|
return nm;
|
|
}
|
|
CNetworkMessage &operator>>(CNetworkMessage &nm, CSessionSocketParams &ssp)
|
|
{
|
|
nm>>ssp.ssp_iBufferActions>>ssp.ssp_iMaxBPS>>ssp.ssp_iMinBPS;
|
|
return nm;
|
|
}
|
|
|
|
/*
|
|
* Constructor.
|
|
*/
|
|
CServer::CServer(void)
|
|
{
|
|
srv_bActive = FALSE;
|
|
|
|
srv_assoSessions.New(NET_MAXGAMECOMPUTERS);
|
|
srv_aplbPlayers.New(NET_MAXGAMEPLAYERS);
|
|
// initialize player indices
|
|
INDEX iPlayer = 0;
|
|
FOREACHINSTATICARRAY(srv_aplbPlayers, CPlayerBuffer, itplb) {
|
|
itplb->plb_Index = iPlayer;
|
|
iPlayer++;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Destructor.
|
|
*/
|
|
CServer::~CServer()
|
|
{
|
|
srv_bActive = FALSE;
|
|
}
|
|
|
|
/*
|
|
* Clear the object.
|
|
*/
|
|
void CServer::Stop(void)
|
|
{
|
|
// stop gameagent
|
|
GameAgent_ServerEnd();
|
|
|
|
// tell all clients to disconnect
|
|
INDEX ctClients = srv_assoSessions.sa_Count;
|
|
INDEX iClient;
|
|
for (iClient=0;iClient<ctClients;iClient++) {
|
|
if (srv_assoSessions[iClient].sso_bActive == TRUE) {
|
|
SendDisconnectMessage(iClient,"Server stopped.");
|
|
}
|
|
}
|
|
|
|
// run a few updates, so the message gets sent
|
|
for (int ctUpdates=0;ctUpdates<10;ctUpdates++) {
|
|
BOOL bFound = FALSE;
|
|
for (iClient=0;iClient<ctClients;iClient++) {
|
|
if (srv_assoSessions[iClient].sso_bActive == TRUE) {
|
|
bFound = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
if (bFound == FALSE) {
|
|
break;
|
|
} else {
|
|
_cmiComm.Server_Update();
|
|
Sleep(100);
|
|
}
|
|
}
|
|
|
|
|
|
// stop network driver server
|
|
_cmiComm.Server_Close();
|
|
|
|
// clear all session
|
|
srv_assoSessions.Clear();
|
|
srv_assoSessions.New(NET_MAXGAMECOMPUTERS);
|
|
// clear list of players
|
|
srv_aplbPlayers.Clear();
|
|
srv_aplbPlayers.New(NET_MAXGAMEPLAYERS);
|
|
// initialize player indices
|
|
INDEX iPlayer = 0;
|
|
{FOREACHINSTATICARRAY(srv_aplbPlayers, CPlayerBuffer, itplb) {
|
|
itplb->plb_Index = iPlayer;
|
|
iPlayer++;
|
|
}}
|
|
|
|
// init buffer for sync checks
|
|
srv_ascChecks.Clear();
|
|
|
|
srv_bActive = FALSE;
|
|
};
|
|
|
|
/*
|
|
* Initialize server and start message handlers.
|
|
*/
|
|
void CServer::Start_t(void)
|
|
{
|
|
// init buffer for sync checks
|
|
srv_ascChecks.Clear();
|
|
|
|
// set up structures
|
|
srv_tmLastProcessedTick = 0.0f;
|
|
srv_iLastProcessedSequence = -1; // -1 so that next one will be 0
|
|
srv_bActive = TRUE;
|
|
srv_bPause = FALSE;
|
|
srv_bGameFinished = FALSE;
|
|
srv_fServerStep = 0.0f;
|
|
|
|
// init network driver server
|
|
_cmiComm.Server_Init_t();
|
|
|
|
// init gameagent
|
|
if (_cmiComm.IsNetworkEnabled()) {
|
|
GameAgent_ServerInit();
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Send disconnect message to some node.
|
|
*/
|
|
void CServer::SendDisconnectMessage(INDEX iClient, const char *strExplanation, BOOL bStream)
|
|
{
|
|
CSessionSocket &sso = srv_assoSessions[iClient];
|
|
|
|
if (!bStream) {
|
|
CNetworkMessage nmDisconnect(MSG_INF_DISCONNECTED);
|
|
// compose message
|
|
nmDisconnect<<CTString(strExplanation);
|
|
// send it
|
|
_pNetwork->SendToClientReliable(iClient, nmDisconnect);
|
|
} else {
|
|
CTMemoryStream strmDisconnect;
|
|
strmDisconnect<<INDEX(MSG_INF_DISCONNECTED);
|
|
strmDisconnect<<CTString(strExplanation);
|
|
|
|
// send the stream to the remote session state
|
|
_pNetwork->SendToClientReliable(iClient, strmDisconnect);
|
|
}
|
|
// report that it has gone away
|
|
CPrintF(TRANS("Client '%s' ordered to disconnect: %s\n"),
|
|
_cmiComm.Server_GetClientName(iClient), strExplanation);
|
|
// if not disconnected before
|
|
if (sso.sso_iDisconnectedState==0) {
|
|
// mark the disconnection
|
|
sso.sso_iDisconnectedState = 1;
|
|
// if the client was already kicked before, but is still hanging here
|
|
} else {
|
|
// force the disconnection
|
|
CPrintF(TRANS("Forcing client '%s' to disconnect\n"),
|
|
_cmiComm.Server_GetClientName(iClient));
|
|
sso.sso_iDisconnectedState = 2;
|
|
}
|
|
}
|
|
|
|
// add a new sync check to buffer
|
|
void CServer::AddSyncCheck(const CSyncCheck &sc)
|
|
{
|
|
ser_iSyncCheckBuffer = ClampDn(ser_iSyncCheckBuffer, INDEX(1));
|
|
if (srv_ascChecks.Count()!=ser_iSyncCheckBuffer) {
|
|
srv_ascChecks.Clear();
|
|
srv_ascChecks.New(ser_iSyncCheckBuffer);
|
|
}
|
|
// find the oldest one
|
|
INDEX iOldest = 0;
|
|
for (INDEX i=1; i<srv_ascChecks.Count(); i++) {
|
|
if (srv_ascChecks[i].sc_tmTick<srv_ascChecks[iOldest].sc_tmTick) {
|
|
iOldest=i;
|
|
}
|
|
}
|
|
|
|
// overwrite it
|
|
srv_ascChecks[iOldest] = sc;
|
|
}
|
|
|
|
// try to find a sync check for given time in the buffer (-1==too old, 0==found, 1==toonew)
|
|
INDEX CServer::FindSyncCheck(TIME tmTick, CSyncCheck &sc)
|
|
{
|
|
BOOL bHasEarlier = FALSE;
|
|
BOOL bHasLater = FALSE;
|
|
for (INDEX i=0; i<srv_ascChecks.Count(); i++) {
|
|
TIME tmInTable = srv_ascChecks[i].sc_tmTick;
|
|
if (tmInTable==tmTick) {
|
|
sc = srv_ascChecks[i];
|
|
return 0;
|
|
} else if (tmInTable<tmTick) {
|
|
bHasEarlier = TRUE;
|
|
} else if (tmInTable>tmTick) {
|
|
bHasLater = TRUE;
|
|
}
|
|
}
|
|
|
|
if (!bHasEarlier) {
|
|
ASSERT(bHasLater);
|
|
return -1;
|
|
} else if (!bHasLater) {
|
|
ASSERT(bHasEarlier);
|
|
return +1;
|
|
} else {
|
|
ASSERT(FALSE); // cannot have both earlier and later and not be found
|
|
return +1;
|
|
}
|
|
}
|
|
|
|
/* Send one regular batch of sequences to a client. */
|
|
void CServer::SendGameStreamBlocks(INDEX iClient)
|
|
{
|
|
// get corresponding session socket
|
|
CSessionSocket &sso = srv_assoSessions[iClient];
|
|
|
|
// gather needed data to decide what to send
|
|
INDEX iLastSent = sso.sso_iLastSentSequence;
|
|
INDEX ctMinBytes = sso.sso_sspParams.ssp_iMinBPS/20;
|
|
INDEX ctMaxBytes = sso.sso_sspParams.ssp_iMaxBPS/20;
|
|
// make sure outgoing message doesn't overflow UDP size
|
|
ctMinBytes = Clamp(ctMinBytes, 0L, 1000L);
|
|
ctMaxBytes = Clamp(ctMaxBytes, 0L, 1000L);
|
|
// limit the clients BPS by server's local settings
|
|
extern INDEX ser_iMaxAllowedBPS;
|
|
ctMinBytes = ClampUp(ctMinBytes, (INDEX) ( ser_iMaxAllowedBPS/20L - MAX_HEADER_SIZE));
|
|
ctMaxBytes = ClampUp(ctMaxBytes, (INDEX) (ser_iMaxAllowedBPS/20L - MAX_HEADER_SIZE));
|
|
|
|
// prevent server/singleplayer from flooding itself
|
|
extern INDEX cli_bPredictIfServer;
|
|
if (iClient==0 && !cli_bPredictIfServer) {
|
|
ctMinBytes = 0;
|
|
ctMaxBytes = 1E6;
|
|
}
|
|
|
|
// CPrintF("Send%d(%d, %d, %d): ", iClient, iLastSent, ctMinBytes, ctMaxBytes);
|
|
|
|
// start after last sequence that was sent and go upwards
|
|
INDEX iSequence = iLastSent+1;
|
|
INDEX iStep = +1;
|
|
// CPrintF("last=%d -- ", iLastSent);
|
|
|
|
// initialize the message that is to be sent
|
|
CNetworkMessage nmGameStreamBlocks(MSG_GAMESTREAMBLOCKS);
|
|
// get one message for last compressed message of valid size
|
|
CNetworkMessage nmPackedBlocks(MSG_GAMESTREAMBLOCKS);
|
|
CNetworkMessage nmPackedBlocksNew(MSG_GAMESTREAMBLOCKS);
|
|
|
|
// repeat for max 100 sequences
|
|
INDEX iBlocksOk = 0;
|
|
INDEX iMaxSent = -1;
|
|
for(INDEX i=0; i<100; i++) {
|
|
if (iStep<0 && iBlocksOk>=3) {
|
|
// break;
|
|
}
|
|
// get the stream block with current sequence
|
|
// CPrintF("%d: ", iSequence);
|
|
CNetworkStreamBlock *pnsbBlock;
|
|
CNetworkStream::Result res = sso.sso_nsBuffer.GetBlockBySequence(iSequence, pnsbBlock);
|
|
// if it is not found
|
|
if (res!=CNetworkStream::R_OK) {
|
|
// if going upward
|
|
if (iStep>0 ) {
|
|
// // if this block is missing
|
|
// && res==CNetworkStream::R_BLOCKMISSING
|
|
// if none sent so far
|
|
if (iBlocksOk<=0) {
|
|
// give up
|
|
// CPrintF("giving up\n");
|
|
break;
|
|
}
|
|
// CPrintF("rewind ", iSequence);
|
|
// rewind and continue downward
|
|
if (iSequence == iLastSent+1) {
|
|
iSequence = iLastSent-1;
|
|
} else {
|
|
iSequence = iLastSent;
|
|
}
|
|
iStep = -1;
|
|
// retry
|
|
continue;
|
|
// otherwise
|
|
} else {
|
|
// stop adding more blocks
|
|
break;
|
|
}
|
|
}
|
|
// if uncompressed message would overflow
|
|
if (nmGameStreamBlocks.nm_slSize+pnsbBlock->nm_slSize+32>MAX_NETWORKMESSAGE_SIZE) {
|
|
// CPrintF("overflow ");
|
|
break;
|
|
}
|
|
|
|
// add this block to the message and pack it
|
|
pnsbBlock->WriteToMessage(nmGameStreamBlocks);
|
|
nmPackedBlocksNew.Reinit();
|
|
nmGameStreamBlocks.PackDefault(nmPackedBlocksNew);
|
|
// if some blocks written already and the batch is too large
|
|
if (iBlocksOk>0) {
|
|
if (iStep>0 && nmPackedBlocksNew.nm_slSize>=ctMaxBytes ||
|
|
iStep<0 && nmPackedBlocksNew.nm_slSize>=ctMinBytes ) {
|
|
// stop
|
|
// CPrintF("toomuch ");
|
|
break;
|
|
}
|
|
}
|
|
// use new pack
|
|
// CPrintF("added ");
|
|
nmPackedBlocks = nmPackedBlocksNew;
|
|
iMaxSent = Max(iMaxSent, iSequence);
|
|
iSequence+= iStep;
|
|
iBlocksOk++;
|
|
}
|
|
|
|
// if no blocks to write
|
|
if (iBlocksOk<=0) {
|
|
// if not sent anything for some time
|
|
CTimerValue tvNow = _pTimer->GetHighPrecisionTimer();
|
|
extern FLOAT ser_tmKeepAlive;
|
|
if ((tvNow-sso.sso_tvLastMessageSent).GetSeconds()>ser_tmKeepAlive) {
|
|
// send keepalive
|
|
CNetworkMessage nmKeepalive(MSG_KEEPALIVE);
|
|
_pNetwork->SendToClient(iClient, nmKeepalive);
|
|
sso.sso_tvLastMessageSent = tvNow;
|
|
}
|
|
// CPrintF("nothing\n");
|
|
return;
|
|
}
|
|
|
|
// send the message to the client
|
|
// CPrintF("sent: %d=%dB\n", iBlocksOk, nmPackedBlocks.nm_slSize);
|
|
_pNetwork->SendToClient(iClient, nmPackedBlocks);
|
|
sso.sso_iLastSentSequence = Max(sso.sso_iLastSentSequence, iMaxSent);
|
|
sso.sso_tvLastMessageSent = _pTimer->GetHighPrecisionTimer();
|
|
|
|
// remove the block(s) that fall out of the buffer
|
|
extern INDEX ser_iRememberBehind;
|
|
sso.sso_nsBuffer.RemoveOlderBlocksBySequence(srv_iLastProcessedSequence-ser_iRememberBehind);
|
|
|
|
|
|
// if haven't sent pings for some time
|
|
CTimerValue tvNow = _pTimer->GetHighPrecisionTimer();
|
|
extern FLOAT ser_tmPingUpdate;
|
|
if ((tvNow-sso.sso_tvLastPingSent).GetSeconds()>ser_tmPingUpdate) {
|
|
// send ping info
|
|
CNetworkMessage nmPings(MSG_INF_PINGS);
|
|
for(INDEX i=0; i<NET_MAXGAMEPLAYERS; i++) {
|
|
CPlayerBuffer &plb = srv_aplbPlayers[i];
|
|
if (plb.IsActive()) {
|
|
BOOL b = 1;
|
|
nmPings.WriteBits(&b, 1);
|
|
nmPings.WriteBits(&plb.plb_iPing, 10);
|
|
} else {
|
|
BOOL b = 0;
|
|
nmPings.WriteBits(&b, 1);
|
|
}
|
|
}
|
|
_pNetwork->SendToClient(iClient, nmPings);
|
|
sso.sso_tvLastPingSent = tvNow;
|
|
}
|
|
}
|
|
|
|
/* Resend a batch of game stream blocks to a client. */
|
|
void CServer::ResendGameStreamBlocks(INDEX iClient, INDEX iSequence0, INDEX ctSequences)
|
|
{
|
|
extern INDEX net_bReportMiscErrors;
|
|
if (net_bReportMiscErrors) {
|
|
CPrintF(TRANS("Server: Resending sequences %d-%d(%d) to '%s'..."),
|
|
iSequence0, iSequence0+ctSequences-1, ctSequences, _cmiComm.Server_GetClientName(iClient));
|
|
}
|
|
|
|
// get corresponding session socket
|
|
CSessionSocket &sso = srv_assoSessions[iClient];
|
|
|
|
// create a package message
|
|
CNetworkMessage nmGameStreamBlocks(MSG_GAMESTREAMBLOCKS);
|
|
CNetworkMessage nmPackedBlocks(MSG_GAMESTREAMBLOCKS);
|
|
|
|
// for each sequence
|
|
INDEX iSequence = iSequence0;
|
|
for(; iSequence<iSequence0+ctSequences; iSequence++) {
|
|
// get the stream block with that sequence
|
|
CNetworkStreamBlock *pnsbBlock;
|
|
CNetworkStream::Result res = sso.sso_nsBuffer.GetBlockBySequence(iSequence, pnsbBlock);
|
|
// if it is not found
|
|
if (res!=CNetworkStream::R_OK) {
|
|
// tell the requesting session state to disconnect
|
|
SendDisconnectMessage(iClient, TRANS("Gamestream synchronization lost"));
|
|
return;
|
|
}
|
|
|
|
CNetworkMessage nmPackedBlocksNew(MSG_GAMESTREAMBLOCKS);
|
|
// pack it in the batch
|
|
pnsbBlock->WriteToMessage(nmGameStreamBlocks);
|
|
nmGameStreamBlocks.PackDefault(nmPackedBlocksNew);
|
|
// if the batch is too large
|
|
if (nmPackedBlocksNew.nm_slSize>512) {
|
|
// stop
|
|
break;
|
|
}
|
|
// use new pack
|
|
nmPackedBlocks = nmPackedBlocksNew;
|
|
}
|
|
|
|
// send the last batch of valid size
|
|
_pfNetworkProfile.IncrementCounter(CNetworkProfile::PCI_GAMESTREAMRESENDS);
|
|
_pNetwork->SendToClient(iClient, nmPackedBlocks);
|
|
extern INDEX net_bReportMiscErrors;
|
|
if (net_bReportMiscErrors) {
|
|
CPrintF(TRANS(" sent %d-%d(%d - %db)\n"),
|
|
iSequence0, iSequence, iSequence-iSequence0-1, nmPackedBlocks.nm_slSize);
|
|
}
|
|
}
|
|
|
|
/* Get number of active players. */
|
|
INDEX CServer::GetPlayersCount(void)
|
|
{
|
|
INDEX ctPlayers = 0;
|
|
FOREACHINSTATICARRAY(srv_aplbPlayers, CPlayerBuffer, itplb) {
|
|
if (itplb->IsActive()) {
|
|
ctPlayers++;
|
|
}
|
|
}
|
|
return ctPlayers;
|
|
}
|
|
/* Get number of active vip players. */
|
|
INDEX CServer::GetVIPPlayersCount(void)
|
|
{
|
|
INDEX ctPlayers = 0;
|
|
FOREACHINSTATICARRAY(srv_aplbPlayers, CPlayerBuffer, itplb) {
|
|
if (itplb->IsActive() && srv_assoSessions[itplb->plb_iClient].sso_bVIP) {
|
|
ctPlayers++;
|
|
}
|
|
}
|
|
return ctPlayers;
|
|
}
|
|
/* Get total number of active clients. */
|
|
INDEX CServer::GetClientsCount(void)
|
|
{
|
|
INDEX ctClients = 0;
|
|
// for each active session
|
|
for(INDEX iSession=0; iSession<srv_assoSessions.Count(); iSession++) {
|
|
CSessionSocket &sso = srv_assoSessions[iSession];
|
|
if (iSession>0 && !sso.IsActive()) {
|
|
continue;
|
|
}
|
|
ctClients++;
|
|
}
|
|
|
|
return ctClients;
|
|
|
|
}
|
|
/* Get number of active vip clients. */
|
|
INDEX CServer::GetVIPClientsCount(void)
|
|
{
|
|
INDEX ctClients = 0;
|
|
// for each active session
|
|
for(INDEX iSession=0; iSession<srv_assoSessions.Count(); iSession++) {
|
|
CSessionSocket &sso = srv_assoSessions[iSession];
|
|
if (iSession>0 && !sso.IsActive()) {
|
|
continue;
|
|
}
|
|
if (sso.sso_bVIP) {
|
|
ctClients++;
|
|
}
|
|
}
|
|
|
|
return ctClients;
|
|
}
|
|
/* Get number of active observers. */
|
|
INDEX CServer::GetObserversCount(void)
|
|
{
|
|
INDEX ctClients = 0;
|
|
// for each active session
|
|
for(INDEX iSession=0; iSession<srv_assoSessions.Count(); iSession++) {
|
|
CSessionSocket &sso = srv_assoSessions[iSession];
|
|
if (iSession>0 && !sso.IsActive()) {
|
|
continue;
|
|
}
|
|
if (sso.sso_ctLocalPlayers == 0) {
|
|
ctClients++;
|
|
}
|
|
}
|
|
|
|
return ctClients;
|
|
}
|
|
|
|
/* Get number of active players on one client. */
|
|
INDEX CServer::GetPlayersCountForClient(INDEX iClient)
|
|
{
|
|
INDEX ctPlayers = 0;
|
|
FOREACHINSTATICARRAY(srv_aplbPlayers, CPlayerBuffer, itplb) {
|
|
if (itplb->IsActive() && itplb->plb_iClient==iClient) {
|
|
ctPlayers++;
|
|
}
|
|
}
|
|
return ctPlayers;
|
|
}
|
|
|
|
/*
|
|
* Find first inactive client.
|
|
*/
|
|
CPlayerBuffer *CServer::FirstInactivePlayer(void)
|
|
{
|
|
// for all players in game
|
|
FOREACHINSTATICARRAY(srv_aplbPlayers, CPlayerBuffer, itplb) {
|
|
// if player is not active
|
|
if (!itplb->IsActive()) {
|
|
// return it
|
|
return &itplb.Current();
|
|
}
|
|
}
|
|
// if no inactive players found, return error
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* Check if some character already exists in this session.
|
|
*/
|
|
BOOL CServer::CharacterNameIsUsed(CPlayerCharacter &pcCharacter)
|
|
{
|
|
// for all players in game
|
|
FOREACHINSTATICARRAY(srv_aplbPlayers, CPlayerBuffer, itplb) {
|
|
// if it is active and has same character as this one
|
|
if (itplb->IsActive() && itplb->plb_pcCharacter == pcCharacter) {
|
|
// it exists
|
|
return TRUE;
|
|
}
|
|
}
|
|
// otherwise, it doesn't exist
|
|
return FALSE;
|
|
}
|
|
|
|
// find a mask of all players on a certain client
|
|
ULONG CServer::MaskOfPlayersOnClient(INDEX iClient)
|
|
{
|
|
ULONG ulClientPlayers = 0;
|
|
for(INDEX ipl=0; ipl<srv_aplbPlayers.Count(); ipl++) {
|
|
CPlayerBuffer &plb = srv_aplbPlayers[ipl];
|
|
if (plb.IsActive() && plb.plb_iClient==iClient) {
|
|
ulClientPlayers |= 1UL<<ipl;
|
|
}
|
|
}
|
|
return ulClientPlayers;
|
|
}
|
|
|
|
/*
|
|
* Handle network messages.
|
|
*/
|
|
void CServer::ServerLoop(void)
|
|
{
|
|
// if not started
|
|
if (!srv_bActive) {
|
|
ASSERTALWAYS("Running server loop for before starting server!");
|
|
// do not gather/send actions
|
|
return;
|
|
}
|
|
|
|
_pfNetworkProfile.StartTimer(CNetworkProfile::PTI_SERVER_LOOP);
|
|
|
|
// try {
|
|
// _cmiComm.Server_Accept_t();
|
|
// } catch (char *strError) {
|
|
// CPrintF(TRANS("Accepting failed, no more clients can connect: %s\n"), strError);
|
|
// }
|
|
// handle all incoming messages
|
|
HandleAll();
|
|
|
|
INDEX iSpeed = 1;
|
|
extern INDEX ser_bWaitFirstPlayer;
|
|
// if the local session is keeping up with time and not paused
|
|
BOOL bPaused = srv_bPause || _pNetwork->ga_bLocalPause || _pNetwork->IsWaitingForPlayers() ||
|
|
srv_bGameFinished || ser_bWaitFirstPlayer;
|
|
if (_pNetwork->ga_sesSessionState.ses_bKeepingUpWithTime
|
|
&&(_pTimer->GetRealTimeTick()-_pNetwork->ga_sesSessionState.ses_tmLastUpdated<=_pTimer->TickQuantum*2.01f)
|
|
&& !bPaused ) {
|
|
|
|
// advance time
|
|
srv_fServerStep += _pNetwork->ga_fGameRealTimeFactor*_pNetwork->ga_sesSessionState.ses_fRealTimeFactor;
|
|
// if stepped to next tick
|
|
if (srv_fServerStep>=1.0f) {
|
|
|
|
// find how many ticks were stepped
|
|
INDEX iSpeed = ClampDn(INDEX(srv_fServerStep), 1L);
|
|
srv_fServerStep -= iSpeed;
|
|
|
|
// for each tick
|
|
for( INDEX i=0; i<iSpeed; i++) {
|
|
// make allaction messages for one tick
|
|
MakeAllActions();
|
|
}
|
|
}
|
|
}
|
|
|
|
// for each active session
|
|
for(INDEX iSession=0; iSession<srv_assoSessions.Count(); iSession++) {
|
|
CSessionSocket &sso = srv_assoSessions[iSession];
|
|
if (iSession>0 && (!sso.IsActive() || !sso.sso_bSendStream)) {
|
|
continue;
|
|
}
|
|
// send one regular batch of sequences to the client
|
|
SendGameStreamBlocks(iSession);
|
|
}
|
|
|
|
_pfNetworkProfile.StopTimer(CNetworkProfile::PTI_SERVER_LOOP);
|
|
}
|
|
|
|
// make allaction messages for one tick
|
|
void CServer::MakeAllActions(void)
|
|
{
|
|
// increment tick counter and processed sequence
|
|
srv_tmLastProcessedTick += _pTimer->TickQuantum;
|
|
srv_iLastProcessedSequence++;
|
|
|
|
// for each active session
|
|
for(INDEX iSession=0; iSession<srv_assoSessions.Count(); iSession++) {
|
|
CSessionSocket &sso = srv_assoSessions[iSession];
|
|
if (iSession>0 && !sso.IsActive()) {
|
|
continue;
|
|
}
|
|
|
|
// create all-actions message
|
|
CNetworkStreamBlock nsbAllActions(MSG_SEQ_ALLACTIONS, srv_iLastProcessedSequence);
|
|
// write time there
|
|
nsbAllActions<<srv_tmLastProcessedTick;
|
|
|
|
// for all players in game
|
|
INDEX iPlayer = 0;
|
|
FOREACHINSTATICARRAY(srv_aplbPlayers, CPlayerBuffer, itplb) {
|
|
// if player is active
|
|
if (itplb->IsActive()) {
|
|
// player indices transmission is unneccessary unless if debugging
|
|
// // write its index
|
|
// nsbAllActions<<iPlayer;
|
|
// write its action
|
|
itplb->CreateActionPacket(&nsbAllActions, iSession);
|
|
}
|
|
iPlayer++;
|
|
}
|
|
|
|
// add the all-actions block to the buffer
|
|
sso.sso_nsBuffer.AddBlock(nsbAllActions);
|
|
}
|
|
|
|
// for all players in game
|
|
{FOREACHINSTATICARRAY(srv_aplbPlayers, CPlayerBuffer, itplb) {
|
|
// if player is active
|
|
if (itplb->IsActive()) {
|
|
// flush oldest action
|
|
itplb->AdvanceActionBuffer();
|
|
}
|
|
}}
|
|
}
|
|
|
|
// add a block to streams for all sessions
|
|
void CServer::AddBlockToAllSessions(CNetworkStreamBlock &nsb)
|
|
{
|
|
// for each active session
|
|
for(INDEX iSession=0; iSession<srv_assoSessions.Count(); iSession++) {
|
|
CSessionSocket &sso = srv_assoSessions[iSession];
|
|
if (iSession>0 && !sso.IsActive()) {
|
|
continue;
|
|
}
|
|
|
|
// add the block to the buffer
|
|
sso.sso_nsBuffer.AddBlock(nsb);
|
|
}
|
|
}
|
|
|
|
/* Send initialization info to local client. */
|
|
void CServer::ConnectLocalSessionState(INDEX iClient, CNetworkMessage &nm)
|
|
{
|
|
// find session of this client
|
|
CSessionSocket &sso = srv_assoSessions[iClient];
|
|
// activate it
|
|
sso.Activate();
|
|
// prepare his initialization message
|
|
CNetworkMessage nmInitMainServer(MSG_REP_CONNECTLOCALSESSIONSTATE);
|
|
nmInitMainServer<<srv_tmLastProcessedTick;
|
|
nmInitMainServer<<srv_iLastProcessedSequence;
|
|
sso.sso_ctLocalPlayers = -1;
|
|
nm>>sso.sso_sspParams;
|
|
|
|
// send him server session state initialization message
|
|
_pNetwork->SendToClientReliable(iClient, nmInitMainServer);
|
|
}
|
|
|
|
/* Send initialization info to remote client. */
|
|
void CServer::ConnectRemoteSessionState(INDEX iClient, CNetworkMessage &nm)
|
|
{
|
|
ASSERT(iClient>0);
|
|
// find session of this client
|
|
CSessionSocket &sso = srv_assoSessions[iClient];
|
|
|
|
// if the IP is banned
|
|
if (!MatchesBanMask(_cmiComm.Server_GetClientName(iClient), ser_strIPMask) != !ser_bInverseBanning) {
|
|
// disconnect the client
|
|
SendDisconnectMessage(iClient, TRANS("You are banned from this server"), /*bStream=*/TRUE);
|
|
return;
|
|
}
|
|
|
|
// read version info
|
|
INDEX iTag, iMajor, iMinor;
|
|
nm>>iTag;
|
|
if (iTag=='VTAG') {
|
|
nm>>iMajor>>iMinor;
|
|
} else {
|
|
iMajor = 109;
|
|
iMinor = 1;
|
|
}
|
|
// if wrong
|
|
if (iMajor!=_SE_BUILD_MAJOR || iMinor!=_SE_BUILD_MINOR) {
|
|
// disconnect the client
|
|
CTString strExplanation;
|
|
strExplanation.PrintF(TRANS(
|
|
"This server runs version %d.%d, your version is %d.%d.\n"
|
|
"Please visit http://www.croteam.com for information on version updating."),
|
|
_SE_BUILD_MAJOR, _SE_BUILD_MINOR, iMajor, iMinor);
|
|
SendDisconnectMessage(iClient, strExplanation, /*bStream=*/TRUE);
|
|
return;
|
|
}
|
|
extern CTString net_strConnectPassword;
|
|
extern CTString net_strVIPPassword;
|
|
extern CTString net_strObserverPassword;
|
|
extern INDEX net_iVIPReserve;
|
|
extern INDEX net_iMaxObservers;
|
|
extern INDEX net_iMaxClients;
|
|
CTString strGivenMod;
|
|
CTString strGivenPassword;
|
|
nm>>strGivenMod>>strGivenPassword;
|
|
INDEX ctWantedLocalPlayers;
|
|
nm>>ctWantedLocalPlayers;
|
|
// if wrong mod
|
|
if (_strModName!=strGivenMod) {
|
|
// disconnect the client
|
|
// NOTE: DO NOT TRANSLATE THIS STRING!
|
|
CTString strMod(0, "MOD:%s\\%s", _strModName, _strModURL);
|
|
SendDisconnectMessage(iClient, strMod, /*bStream=*/TRUE);
|
|
return;
|
|
}
|
|
|
|
// get counts of allowed players, clients, vips and check for connection allowance
|
|
INDEX ctMaxAllowedPlayers = _pNetwork->ga_sesSessionState.ses_ctMaxPlayers;
|
|
INDEX ctMaxAllowedClients = ctMaxAllowedPlayers;
|
|
if (net_iMaxClients>0) {
|
|
ctMaxAllowedClients = ClampUp(net_iMaxClients, (INDEX)NET_MAXGAMECOMPUTERS);
|
|
}
|
|
INDEX ctMaxAllowedVIPPlayers = 0;
|
|
INDEX ctMaxAllowedVIPClients = 0;
|
|
if (net_iVIPReserve>0 && net_strVIPPassword!="") {
|
|
ctMaxAllowedVIPPlayers = ClampDn(net_iVIPReserve-GetVIPPlayersCount(), 0L);
|
|
ctMaxAllowedVIPClients = ClampDn(net_iVIPReserve-GetVIPClientsCount(), 0L);
|
|
}
|
|
INDEX ctMaxAllowedObservers = net_iMaxObservers;
|
|
|
|
// get current counts
|
|
INDEX ctCurrentPlayers = GetPlayersCount();
|
|
INDEX ctCurrentClients = GetClientsCount();
|
|
INDEX ctCurrentObservers = GetObserversCount();
|
|
|
|
// check which passwords this client can satisfy
|
|
BOOL bAutorizedAsVIP = FALSE;
|
|
BOOL bAutorizedAsObserver = FALSE;
|
|
BOOL bAutorizedAsPlayer = FALSE;
|
|
if (net_strVIPPassword!="" && net_strVIPPassword==strGivenPassword) {
|
|
bAutorizedAsVIP = TRUE;
|
|
bAutorizedAsPlayer = TRUE;
|
|
bAutorizedAsObserver = TRUE;
|
|
}
|
|
if (net_strConnectPassword=="" || net_strConnectPassword==strGivenPassword) {
|
|
bAutorizedAsPlayer = TRUE;
|
|
}
|
|
if ((net_strObserverPassword==""&&bAutorizedAsPlayer) || net_strObserverPassword==strGivenPassword) {
|
|
bAutorizedAsObserver = TRUE;
|
|
}
|
|
|
|
// if the user is not authorised as a VIP
|
|
if (!bAutorizedAsVIP) {
|
|
// artificially decrease allowed number of players and clients
|
|
ctMaxAllowedPlayers = ClampDn(ctMaxAllowedPlayers-ctMaxAllowedVIPPlayers, 0L);
|
|
ctMaxAllowedClients = ClampDn(ctMaxAllowedClients-ctMaxAllowedVIPClients, 0L);
|
|
}
|
|
|
|
// if too many clients or players
|
|
if (ctCurrentPlayers+ctWantedLocalPlayers>ctMaxAllowedPlayers
|
|
||ctCurrentClients+1>ctMaxAllowedClients) {
|
|
// disconnect the client
|
|
SendDisconnectMessage(iClient, TRANS("Server full!"), /*bStream=*/TRUE);
|
|
return;
|
|
}
|
|
|
|
// if the user is trying to connect as observer
|
|
if (ctWantedLocalPlayers==0) {
|
|
// if too many observers already
|
|
if (ctCurrentObservers>=ctMaxAllowedObservers && !bAutorizedAsVIP) {
|
|
// disconnect the client
|
|
SendDisconnectMessage(iClient, TRANS("Too many observers!"), /*bStream=*/TRUE);
|
|
return;
|
|
}
|
|
// if observer password is wrong
|
|
if (!bAutorizedAsObserver) {
|
|
// disconnect the client
|
|
if (strGivenPassword=="") {
|
|
SendDisconnectMessage(iClient, TRANS("This server requires password for observers!"), /*bStream=*/TRUE);
|
|
} else {
|
|
SendDisconnectMessage(iClient, TRANS("Wrong observer password!"), /*bStream=*/TRUE);
|
|
}
|
|
}
|
|
// if the user is trying to connect as player
|
|
} else {
|
|
// if player password is wrong
|
|
if (!bAutorizedAsPlayer) {
|
|
// disconnect the client
|
|
if (strGivenPassword=="") {
|
|
SendDisconnectMessage(iClient, TRANS("This server requires password to connect!"), /*bStream=*/TRUE);
|
|
} else {
|
|
SendDisconnectMessage(iClient, TRANS("Wrong password!"), /*bStream=*/TRUE);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
// activate it
|
|
sso.Activate();
|
|
// load parameters for it
|
|
sso.sso_ctLocalPlayers = ctWantedLocalPlayers;
|
|
sso.sso_bVIP = bAutorizedAsVIP;
|
|
nm>>sso.sso_sspParams;
|
|
|
|
// try to
|
|
try {
|
|
// create base info to be sent
|
|
extern CTString ser_strMOTD;
|
|
CTMemoryStream strmInfo;
|
|
strmInfo<<INDEX(MSG_REP_CONNECTREMOTESESSIONSTATE);
|
|
strmInfo<<ser_strMOTD;
|
|
strmInfo<<_pNetwork->ga_World.wo_fnmFileName;
|
|
strmInfo<<_pNetwork->ga_sesSessionState.ses_ulSpawnFlags;
|
|
strmInfo.Write_t(_pNetwork->ga_aubDefaultProperties, NET_MAXSESSIONPROPERTIES);
|
|
SLONG slSize = strmInfo.GetStreamSize();
|
|
|
|
// send the stream to the remote session state
|
|
_pNetwork->SendToClientReliable(iClient, strmInfo);
|
|
|
|
CPrintF(TRANS("Server: Sent initialization info to '%s' (%dk)\n"),
|
|
(const char*)_cmiComm.Server_GetClientName(iClient), slSize/1024);
|
|
// if failed
|
|
} catch (char *strError) {
|
|
// deactivate it
|
|
sso.Deactivate();
|
|
|
|
// report error
|
|
CPrintF(TRANS("Server: Cannot prepare connection data: %s\n"), strError);
|
|
}
|
|
}
|
|
|
|
/* Send session state data to remote client. */
|
|
void CServer::SendSessionStateData(INDEX iClient)
|
|
{
|
|
ASSERT(iClient>0);
|
|
// find session of this client
|
|
CSessionSocket &sso = srv_assoSessions[iClient];
|
|
// copy its buffer from local session state
|
|
sso.sso_nsBuffer.Copy(srv_assoSessions[0].sso_nsBuffer);
|
|
|
|
// try to
|
|
try {
|
|
// prepare files or memory streams for connection info
|
|
CTFileStream strmStateFile; CTMemoryStream strmStateMem;
|
|
CTFileStream strmDeltaFile; CTMemoryStream strmDeltaMem;
|
|
CTStream *pstrmState; CTMemoryStream *pstrmDelta;
|
|
extern INDEX net_bDumpConnectionInfo;
|
|
|
|
UBYTE* pubSrc = NULL;
|
|
/*if (net_bDumpConnectionInfo) {
|
|
strmStateFile.Create_t(CTString("Temp\\State.bin"));
|
|
strmDeltaFile.Create_t(CTString("Temp\\Delta.bin"));
|
|
pstrmState = &strmStateFile;
|
|
pstrmDelta = &strmDeltaFile;
|
|
} else {*/
|
|
pstrmState = &strmStateMem;
|
|
pstrmDelta = &strmDeltaMem;
|
|
|
|
pubSrc = strmDeltaMem.mstrm_pubBuffer + strmDeltaMem.mstrm_slLocation;
|
|
//}
|
|
|
|
ASSERT(pubSrc != NULL);
|
|
|
|
// write main session state
|
|
_pNetwork->ga_sesSessionState.Write_t(pstrmState);
|
|
pstrmState->SetPos_t(0);
|
|
SLONG slFullSize = pstrmState->GetStreamSize();
|
|
|
|
CTMemoryStream strmInfo;
|
|
strmInfo<<INDEX(MSG_REP_STATEDELTA);
|
|
|
|
// compress it to another one, using delta from original
|
|
CTMemoryStream strmDefaultState;
|
|
strmDefaultState.Write_t
|
|
(_pNetwork->ga_pubDefaultState, _pNetwork->ga_slDefaultStateSize);
|
|
strmDefaultState.SetPos_t(0);
|
|
DIFF_Diff_t(&strmDefaultState, pstrmState, pstrmDelta);
|
|
pstrmDelta->SetPos_t(0);
|
|
SLONG slDeltaSize = pstrmDelta->GetStreamSize();
|
|
CzlibCompressor comp;
|
|
comp.PackStream_t(*pstrmDelta, strmInfo);
|
|
|
|
SLONG slSize = strmInfo.GetStreamSize();
|
|
|
|
// send the stream to the remote session state
|
|
_pNetwork->SendToClientReliable(iClient, strmInfo);
|
|
|
|
CPrintF(TRANS("Server: Sent connection data to '%s' (%dk->%dk->%dk)\n"),
|
|
(const char*)_cmiComm.Server_GetClientName(iClient),
|
|
slFullSize/1024, slDeltaSize/1024, slSize/1024);
|
|
if (net_bDumpConnectionInfo) {
|
|
CPrintF(TRANS("Server: Connection data dumped.\n"));
|
|
}
|
|
|
|
// if failed
|
|
} catch (char *strError) {
|
|
// deactivate it
|
|
sso.Deactivate();
|
|
|
|
// report error
|
|
CPrintF(TRANS("Server: Cannot prepare connection data: %s\n"), strError);
|
|
}
|
|
}
|
|
|
|
/* Handle incoming network messages. */
|
|
void CServer::HandleAll()
|
|
{
|
|
// clear last accepted client info
|
|
INDEX iClient = -1;
|
|
/* if (_cmiComm.GetLastAccepted(iClient)) {
|
|
CPrintF(TRANS("Server: Accepted session connection by '%s'\n"),
|
|
_cmiComm.Server_GetClientName(iClient));
|
|
}
|
|
*/
|
|
|
|
// for each active client
|
|
{for( INDEX iClient=0; iClient<NET_MAXGAMECOMPUTERS; iClient++) {
|
|
// handle all of its messages
|
|
HandleAllForAClient(iClient);
|
|
}}
|
|
}
|
|
|
|
|
|
|
|
|
|
void CServer::HandleAllForAClient(INDEX iClient)
|
|
{
|
|
// if the client is not connected
|
|
if (!_cmiComm.Server_IsClientUsed(iClient)) {
|
|
// skip it
|
|
return;
|
|
}
|
|
|
|
// update the client's max BPS limit from the session socket parameters
|
|
cm_aciClients[iClient].ci_pbOutputBuffer.pb_pbsLimits.pbs_fBandwidthLimit = srv_assoSessions[iClient].sso_sspParams.ssp_iMaxBPS*8;
|
|
|
|
// find session of this client
|
|
CSessionSocket &sso = srv_assoSessions[iClient];
|
|
// if hasn't received sync check for too long
|
|
extern INDEX ser_bKickOnSyncLate;
|
|
if (ser_bKickOnSyncLate &&sso.sso_bActive &&
|
|
sso.sso_tmLastSyncReceived>0 &&
|
|
sso.sso_tmLastSyncReceived<_pNetwork->ga_sesSessionState.ses_tmLastSyncCheck -
|
|
(2*ser_iSyncCheckBuffer*_pNetwork->ga_sesSessionState.ses_tmSyncCheckFrequency)) {
|
|
SendDisconnectMessage(iClient, TRANS("No valid SYNCCHECK received for too long!"));
|
|
}
|
|
|
|
if (iClient>0 && sso.sso_bActive && sso.sso_bSendStream && sso.sso_tvMessageReceived.tv_llValue>0 &&
|
|
(_pTimer->GetHighPrecisionTimer()-sso.sso_tvMessageReceived).GetSeconds()>net_tmDisconnectTimeout) {
|
|
SendDisconnectMessage(iClient, TRANS("Connection timeout"));
|
|
}
|
|
|
|
// if the client is disconnected
|
|
if (!_cmiComm.Server_IsClientUsed(iClient) || sso.sso_iDisconnectedState>1) {
|
|
CPrintF(TRANS("Server: Client '%s' disconnected.\n"), _cmiComm.Server_GetClientName(iClient));
|
|
// clear it
|
|
_cmiComm.Server_ClearClient(iClient);
|
|
// free all that data that was allocated for the client
|
|
HandleClientDisconected(iClient);
|
|
}
|
|
|
|
CNetworkMessage nmReceived;
|
|
// repeat
|
|
FOREVER {
|
|
// if there is some reliable message
|
|
if (_pNetwork->ReceiveFromClientReliable(iClient, nmReceived)) {
|
|
// process it
|
|
Handle(iClient, nmReceived);
|
|
|
|
// if there are no more messages
|
|
} else {
|
|
// skip to receiving unreliable
|
|
break;
|
|
}
|
|
}
|
|
|
|
// if the client has confirmed disconnect in this loop
|
|
if (!_cmiComm.Server_IsClientUsed(iClient) || sso.sso_iDisconnectedState>1) {
|
|
CPrintF(TRANS("Server: Client '%s' disconnected.\n"), _cmiComm.Server_GetClientName(iClient));
|
|
// clear it
|
|
_cmiComm.Server_ClearClient(iClient);
|
|
// free all that data that was allocated for the client
|
|
HandleClientDisconected(iClient);
|
|
}
|
|
|
|
// repeat
|
|
FOREVER {
|
|
// if there is some unreliable message
|
|
if (_pNetwork->ReceiveFromClient(iClient, nmReceived)) {
|
|
// process it
|
|
Handle(iClient, nmReceived);
|
|
// if there are no more messages
|
|
} else {
|
|
// finish with this client
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void CServer::HandleClientDisconected(INDEX iClient)
|
|
{
|
|
// find session of this client
|
|
CSessionSocket &sso = srv_assoSessions[iClient];
|
|
// deactivate it
|
|
sso.Deactivate();
|
|
|
|
INDEX iPlayer = 0;
|
|
FOREACHINSTATICARRAY(srv_aplbPlayers, CPlayerBuffer, itplb) {
|
|
// if player is on that client
|
|
if (itplb->plb_iClient==iClient) {
|
|
// create message for removing player from all session
|
|
CNetworkStreamBlock nsbRemPlayerData(MSG_SEQ_REMPLAYER, ++srv_iLastProcessedSequence);
|
|
nsbRemPlayerData<<iPlayer; // player index
|
|
// put the message in buffer to be sent to all sessions
|
|
AddBlockToAllSessions(nsbRemPlayerData);
|
|
// deactivate it
|
|
itplb->Deactivate();
|
|
}
|
|
iPlayer++;
|
|
}
|
|
}
|
|
|
|
// split the rcon response string into lines and send one by one to the client
|
|
static void SendAdminResponse(INDEX iClient, const CTString &strResponse)
|
|
{
|
|
CTString str = strResponse;
|
|
|
|
while (str!="") {
|
|
CTString strLine = str;
|
|
strLine.OnlyFirstLine();
|
|
str.RemovePrefix(strLine);
|
|
str.DeleteChar(0);
|
|
if (strLine.Length()>0) {
|
|
CNetworkMessage nm(MSG_ADMIN_RESPONSE);
|
|
nm<<strLine;
|
|
_pNetwork->SendToClientReliable(iClient, nm);
|
|
}
|
|
}
|
|
}
|
|
|
|
void CServer::Handle(INDEX iClient, CNetworkMessage &nmMessage)
|
|
{
|
|
CSessionSocket &sso = srv_assoSessions[iClient];
|
|
sso.sso_tvMessageReceived = _pTimer->GetHighPrecisionTimer();
|
|
|
|
switch (nmMessage.GetType()) {
|
|
|
|
// if if it is just keepalive, ignore it
|
|
case MSG_KEEPALIVE: break;
|
|
case MSG_REP_DISCONNECTED: {
|
|
CSessionSocket &sso = srv_assoSessions[iClient];
|
|
sso.sso_iDisconnectedState=2;
|
|
} break;
|
|
|
|
// if local session state asks for registration
|
|
case MSG_REQ_CONNECTLOCALSESSIONSTATE: {
|
|
ConnectLocalSessionState(iClient, nmMessage);
|
|
} break;
|
|
// if remote server asks for registration
|
|
case MSG_REQ_CONNECTREMOTESESSIONSTATE: {
|
|
ConnectRemoteSessionState(iClient, nmMessage);
|
|
} break;
|
|
// if remote server asks for data
|
|
case MSG_REQ_STATEDELTA: {
|
|
CPrintF(TRANS("Sending statedelta response\n"));
|
|
SendSessionStateData(iClient);
|
|
} break;
|
|
// if player asks for registration
|
|
case MSG_REQ_CONNECTPLAYER: {
|
|
|
|
// check that someone doesn't add too many players
|
|
if (iClient>0 && GetPlayersCountForClient(iClient)>=sso.sso_ctLocalPlayers) {
|
|
CTString strMessage;
|
|
strMessage.PrintF(TRANS("Protocol violation"));
|
|
SendDisconnectMessage(iClient, strMessage);
|
|
}
|
|
|
|
// read character data from the message
|
|
CPlayerCharacter pcCharacter;
|
|
nmMessage>>pcCharacter;
|
|
|
|
// if the name is banned
|
|
if (!MatchesBanMask(pcCharacter.GetName(), ser_strNameMask) != !ser_bInverseBanning) {
|
|
// disconnect the client
|
|
SendDisconnectMessage(iClient, TRANS("You are banned from this server"), /*bStream=*/TRUE);
|
|
return;
|
|
}
|
|
|
|
CPlayerBuffer *pplbNewClient;
|
|
|
|
// find some inactive player
|
|
pplbNewClient = FirstInactivePlayer();
|
|
// if there is some active player with same character name
|
|
if (CharacterNameIsUsed(pcCharacter)) {
|
|
// send refusal message
|
|
CTString strMessage;
|
|
strMessage.PrintF(TRANS("Player character '%s' already exists in this session."),
|
|
pcCharacter.GetName());
|
|
SendDisconnectMessage(iClient, strMessage);
|
|
|
|
// if the max. number of clients is not reached
|
|
} else if (pplbNewClient!=NULL) {
|
|
// activate it
|
|
pplbNewClient->Activate(iClient);
|
|
INDEX iNewPlayer = pplbNewClient->plb_Index;
|
|
|
|
// remember its character
|
|
pplbNewClient->plb_pcCharacter = pcCharacter;
|
|
|
|
// create message for adding player data to sessions
|
|
CNetworkStreamBlock nsbAddClientData(MSG_SEQ_ADDPLAYER, ++srv_iLastProcessedSequence);
|
|
nsbAddClientData<<iNewPlayer; // client index
|
|
nsbAddClientData<<pcCharacter; // client character data
|
|
extern INDEX ser_bWaitFirstPlayer;
|
|
ser_bWaitFirstPlayer = 0; // player is here don't wait any more
|
|
|
|
// put the message in buffer to be sent to all servers
|
|
AddBlockToAllSessions(nsbAddClientData);
|
|
|
|
// send him client initialization message
|
|
CNetworkMessage nmPlayerRegistered(MSG_REP_CONNECTPLAYER);
|
|
nmPlayerRegistered<<iNewPlayer; // player index
|
|
_pNetwork->SendToClientReliable(iClient, nmPlayerRegistered);
|
|
|
|
// notify gameagent
|
|
GameAgent_ServerStateChanged();
|
|
// if refused
|
|
} else {
|
|
// send him refusal message
|
|
SendDisconnectMessage(iClient, TRANS("Too many players in session."));
|
|
}
|
|
} break;
|
|
|
|
// if client source wants to change character
|
|
case MSG_REQ_CHARACTERCHANGE: {
|
|
// read character data from the message
|
|
INDEX iPlayer;
|
|
CPlayerCharacter pcCharacter;
|
|
nmMessage>>iPlayer>>pcCharacter;
|
|
// first check if the request is valid
|
|
if (iPlayer<0 || iPlayer>srv_aplbPlayers.Count() ) {
|
|
break;
|
|
}
|
|
CPlayerBuffer &plb = srv_aplbPlayers[iPlayer];
|
|
if (plb.plb_iClient!=iClient || !(plb.plb_pcCharacter==pcCharacter) ) {
|
|
break;
|
|
}
|
|
|
|
// if all was right, add that as change a sequence
|
|
CNetworkStreamBlock nsbChangeChar(MSG_SEQ_CHARACTERCHANGE, ++srv_iLastProcessedSequence);
|
|
nsbChangeChar<<iPlayer;
|
|
nsbChangeChar<<pcCharacter;
|
|
|
|
plb.plb_pcCharacter = pcCharacter;
|
|
|
|
// put the message in buffer to be sent to all clients
|
|
AddBlockToAllSessions(nsbChangeChar);
|
|
|
|
} break;
|
|
|
|
// if client source sends action packet
|
|
case MSG_ACTION: {
|
|
CSessionSocket &sso = srv_assoSessions[iClient];
|
|
// for each possible player on that client
|
|
for(INDEX ipls=0; ipls<NET_MAXLOCALPLAYERS; ipls++) {
|
|
// see if saved in the message
|
|
BOOL bSaved = 0;
|
|
nmMessage.ReadBits(&bSaved, 1);
|
|
// if saved
|
|
if (bSaved) {
|
|
// read client index
|
|
INDEX iPlayer = 0;
|
|
nmMessage.ReadBits(&iPlayer, 4);
|
|
CPlayerBuffer &plb = srv_aplbPlayers[iPlayer];
|
|
// if the player is not on that client
|
|
if (plb.plb_iClient!=iClient) {
|
|
// consider the entire message invalid
|
|
CPrintF("Wrong Client!\n");
|
|
break;
|
|
}
|
|
// read ping
|
|
plb.plb_iPing = 0;
|
|
nmMessage.ReadBits(&plb.plb_iPing, 10);
|
|
// let the corresponding client buffer receive the message
|
|
INDEX iMaxBuffer = sso.sso_sspParams.ssp_iBufferActions;
|
|
extern INDEX cli_bPredictIfServer;
|
|
if (iClient==0 && !cli_bPredictIfServer) {
|
|
iMaxBuffer = 1;
|
|
}
|
|
plb.ReceiveActionPacket(&nmMessage, iMaxBuffer);
|
|
}
|
|
}
|
|
} break;
|
|
// if client sent a synchronization check
|
|
case MSG_SYNCCHECK: {
|
|
extern INDEX ser_bReportSyncOK;
|
|
extern INDEX ser_bReportSyncBad;
|
|
extern INDEX ser_bReportSyncLate;
|
|
extern INDEX ser_bReportSyncEarly;
|
|
extern INDEX ser_bPauseOnSyncBad;
|
|
extern INDEX ser_iKickOnSyncBad;
|
|
|
|
// read sync check from the packet
|
|
CSyncCheck scRemote;
|
|
nmMessage.Read(&scRemote, sizeof(scRemote));
|
|
|
|
// try to find it in buffer
|
|
CSyncCheck scLocal;
|
|
INDEX iFound = FindSyncCheck(scRemote.sc_tmTick, scLocal);
|
|
// if found
|
|
if (iFound==0) {
|
|
// flush the clients stream buffer up to that sequence
|
|
// (the sync is used as piggy-backed acknowledge of packet receival)
|
|
CSessionSocket &sso = srv_assoSessions[iClient];
|
|
sso.sso_nsBuffer.RemoveOlderBlocksBySequence(scRemote.sc_iSequence);
|
|
|
|
// if level was changed
|
|
if (scLocal.sc_iLevel!=scRemote.sc_iLevel) {
|
|
// disconnect the client
|
|
SendDisconnectMessage(iClient, TRANS("Level change in progress. Please retry."));
|
|
// if it is wrong crc
|
|
} else if (scLocal.sc_ulCRC!=scRemote.sc_ulCRC) {
|
|
sso.sso_ctBadSyncs++;
|
|
if( ser_bReportSyncBad) {
|
|
CPrintF( TRANS("SYNCBAD: Client '%s', Sequence %d Tick %.2f - bad %d\n"),
|
|
_cmiComm.Server_GetClientName(iClient), scRemote.sc_iSequence , scRemote.sc_tmTick, sso.sso_ctBadSyncs);
|
|
}
|
|
if (ser_iKickOnSyncBad>0) {
|
|
if (sso.sso_ctBadSyncs>=ser_iKickOnSyncBad) {
|
|
SendDisconnectMessage(iClient, TRANS("Too many bad syncs"));
|
|
}
|
|
} else if( ser_bPauseOnSyncBad) {
|
|
_pNetwork->ga_sesSessionState.ses_bWantPause = TRUE;
|
|
}
|
|
} else {
|
|
sso.sso_ctBadSyncs = 0;
|
|
if (ser_bReportSyncOK) {
|
|
CPrintF( TRANS("SYNCOK: Client '%s', Tick %.2f\n"),
|
|
_cmiComm.Server_GetClientName(iClient), scRemote.sc_tmTick);
|
|
}
|
|
}
|
|
|
|
// remember that this client has sent sync for that tick
|
|
if (srv_assoSessions[iClient].sso_tmLastSyncReceived<scRemote.sc_tmTick) {
|
|
srv_assoSessions[iClient].sso_tmLastSyncReceived = scRemote.sc_tmTick;
|
|
}
|
|
|
|
// if too old
|
|
} else if (iFound<0) {
|
|
// report only if syncs are ok now (so that we don't report a bunch of late syncs on level change
|
|
if( ser_bReportSyncLate && srv_assoSessions[iClient].sso_tmLastSyncReceived>0) {
|
|
CPrintF( TRANS("SYNCLATE: Client '%s', Tick %.2f\n"),
|
|
_cmiComm.Server_GetClientName(iClient), scRemote.sc_tmTick);
|
|
}
|
|
// if too new
|
|
} else {
|
|
if( ser_bReportSyncEarly) {
|
|
CPrintF( TRANS("SYNCEARLY: Client '%s', Tick %.2f\n"),
|
|
_cmiComm.Server_GetClientName(iClient), scRemote.sc_tmTick);
|
|
}
|
|
// remember that this client has sent sync for that tick
|
|
// (even though we cannot really check that it is valid)
|
|
if (srv_assoSessions[iClient].sso_tmLastSyncReceived<scRemote.sc_tmTick) {
|
|
srv_assoSessions[iClient].sso_tmLastSyncReceived = scRemote.sc_tmTick;
|
|
}
|
|
}
|
|
|
|
} break;
|
|
// if a server requests resend of some game stream packets
|
|
case MSG_REQUESTGAMESTREAMRESEND: {
|
|
// get the sequences from the block
|
|
INDEX iSequence0, ctSequences;
|
|
nmMessage>>iSequence0;
|
|
nmMessage>>ctSequences;
|
|
// resend the game stream blocks to the server
|
|
ResendGameStreamBlocks(iClient, iSequence0, ctSequences);
|
|
} break;
|
|
// if a client wants to toggle pause
|
|
case MSG_REQ_PAUSE: {
|
|
// read the pause state from the message
|
|
BOOL bWantPause;
|
|
nmMessage>>(INDEX&)bWantPause;
|
|
// if state is new
|
|
if (!srv_bPause != !bWantPause) {
|
|
|
|
// if the client may pause
|
|
extern INDEX ser_bClientsMayPause;
|
|
if (_cmiComm.Server_IsClientLocal(iClient) || ser_bClientsMayPause) {
|
|
// change it
|
|
srv_bPause = bWantPause;
|
|
// add the pause state block to the buffer to be sent to all clients
|
|
CNetworkStreamBlock nsbPause(MSG_SEQ_PAUSE, ++srv_iLastProcessedSequence);
|
|
nsbPause<<(INDEX&)srv_bPause;
|
|
nsbPause<<_cmiComm.Server_GetClientName(iClient);
|
|
AddBlockToAllSessions(nsbPause);
|
|
}
|
|
}
|
|
} break;
|
|
// if a player wants to change its buffer settings
|
|
case MSG_SET_CLIENTSETTINGS: {
|
|
// read data
|
|
CSessionSocket &sso = srv_assoSessions[iClient];
|
|
nmMessage>>sso.sso_sspParams;
|
|
} break;
|
|
// if a chat message was sent
|
|
case MSG_CHAT_IN: {
|
|
// get it
|
|
ULONG ulFrom, ulTo;
|
|
CTString strMessage;
|
|
nmMessage>>ulFrom>>ulTo>>strMessage;
|
|
|
|
// filter the from address by the client's players
|
|
ulFrom &= MaskOfPlayersOnClient(iClient);
|
|
// if the source has no players
|
|
if (ulFrom==0) {
|
|
// make it public message
|
|
ulTo = -1;
|
|
}
|
|
|
|
// make the outgoing message
|
|
CNetworkMessage nmOut(MSG_CHAT_OUT);
|
|
nmOut<<ulFrom;
|
|
if (ulFrom==0) {
|
|
CTString strFrom;
|
|
if (iClient==0) {
|
|
strFrom = TRANS("Server");
|
|
} else {
|
|
strFrom.PrintF(TRANS("Client %d"), iClient);
|
|
}
|
|
nmOut<<strFrom;
|
|
}
|
|
nmOut<<strMessage;
|
|
|
|
// for each active client
|
|
for(INDEX iSession=0; iSession<srv_assoSessions.Count(); iSession++) {
|
|
CSessionSocket &sso = srv_assoSessions[iSession];
|
|
if (iSession>0 && !sso.IsActive()) {
|
|
continue;
|
|
}
|
|
// if message is public or the client has some of destination players
|
|
if (ulTo==-1 || ulTo&MaskOfPlayersOnClient(iSession)) {
|
|
// send the message to that computer
|
|
_pNetwork->SendToClient(iSession, nmOut);
|
|
}
|
|
}
|
|
|
|
} break;
|
|
// if a crc response is received
|
|
case MSG_REQ_CRCLIST: {
|
|
CPrintF(TRANS("Sending CRC response\n"));
|
|
// create CRC challenge
|
|
CTMemoryStream strmCRC;
|
|
strmCRC<<INDEX(MSG_REQ_CRCCHECK);
|
|
strmCRC.Write_t(_pNetwork->ga_pubCRCList, _pNetwork->ga_slCRCList);
|
|
SLONG slSize = strmCRC.GetStreamSize();
|
|
|
|
// send the stream to the remote session state
|
|
_pNetwork->SendToClientReliable(iClient, strmCRC);
|
|
CPrintF(TRANS("Server: Sent CRC challenge to '%s' (%dk)\n"),
|
|
(const char*)_cmiComm.Server_GetClientName(iClient), slSize/1024);
|
|
|
|
} break;
|
|
// if a crc response is received
|
|
case MSG_REP_CRCCHECK: {
|
|
// get it
|
|
ULONG ulCRC;
|
|
INDEX iLastSequence;
|
|
nmMessage>>ulCRC>>iLastSequence;
|
|
// if not same
|
|
if (_pNetwork->ga_ulCRC!=ulCRC) {
|
|
// disconnect the client
|
|
SendDisconnectMessage(iClient, TRANS("Wrong CRC check."));
|
|
// if same
|
|
} else {
|
|
CPrintF(TRANS("Server: Client '%s', CRC check OK\n"),
|
|
(const char*)_cmiComm.Server_GetClientName(iClient));
|
|
// use the piggybacked sequence number to initiate sending stream to it
|
|
CSessionSocket &sso = srv_assoSessions[iClient];
|
|
sso.sso_bSendStream = TRUE;
|
|
sso.sso_nsBuffer.RemoveOlderBlocksBySequence(iLastSequence);
|
|
sso.sso_iLastSentSequence = iLastSequence;
|
|
}
|
|
|
|
} break;
|
|
// if a rcon request is received
|
|
case MSG_ADMIN_COMMAND: {
|
|
extern CTString net_strAdminPassword;
|
|
// get it
|
|
CTString strPassword, strCommand;
|
|
nmMessage>>strPassword>>strCommand;
|
|
if (net_strAdminPassword=="") {
|
|
CNetworkMessage nmRes(MSG_ADMIN_RESPONSE);
|
|
nmRes<<CTString(TRANS("Remote administration not allowed on this server.\n"));
|
|
CPrintF(TRANS("Server: Client '%s', Tried to use remote administration.\n"),
|
|
(const char*)_cmiComm.Server_GetClientName(iClient));
|
|
_pNetwork->SendToClientReliable(iClient, nmRes);
|
|
} else if (net_strAdminPassword!=strPassword) {
|
|
CPrintF(TRANS("Server: Client '%s', Wrong password for remote administration.\n"),
|
|
(const char*)_cmiComm.Server_GetClientName(iClient));
|
|
SendDisconnectMessage(iClient, TRANS("Wrong admin password. The attempt was logged."));
|
|
break;
|
|
} else {
|
|
|
|
CPrintF(TRANS("Server: Client '%s', Admin cmd: %s\n"),
|
|
(const char*)_cmiComm.Server_GetClientName(iClient), strCommand);
|
|
|
|
con_bCapture = TRUE;
|
|
con_strCapture = "";
|
|
_pShell->Execute(strCommand+";");
|
|
|
|
CTString strResponse = CTString(">")+strCommand+"\n"+con_strCapture;
|
|
SendAdminResponse(iClient, strResponse);
|
|
con_bCapture = FALSE;
|
|
con_strCapture = "";
|
|
}
|
|
} break;
|
|
// otherwise
|
|
default:
|
|
ASSERT(FALSE);
|
|
}
|
|
}
|