csgo-2018-source/engine/baseclientstate.cpp
2021-07-24 21:11:47 -07:00

3795 lines
117 KiB
C++
Raw Permalink Blame History

//===== Copyright <20> 1996-2005, Valve Corporation, All rights reserved. ======//
//
// Purpose: baseclientstate.cpp: implementation of the CBaseClientState class.
//
//===========================================================================//
#include "client_pch.h"
#include "baseclientstate.h"
#include "inetchannel.h"
#include "netmessages.h"
#include "proto_oob.h"
#include "dt_recv_eng.h"
#include "host_cmd.h"
#include "GameEventManager.h"
#include "cl_rcon.h"
#ifndef DEDICATED
#include "cl_pluginhelpers.h"
#include "vgui_askconnectpanel.h"
#include "cdll_engine_int.h"
#endif
#include "sv_steamauth.h"
#include "snd_audio_source.h"
#include "server.h"
#include "cl_steamauth.h"
#if defined( REPLAY_ENABLED )
#include "replayserver.h"
#include "replayhistorymanager.h"
#endif
#include "filesystem/IQueuedLoader.h"
#include "serializedentity.h"
#include "checksum_engine.h"
#include "matchmaking/imatchframework.h"
#include "mathlib/IceKey.H"
#include "hltvserver.h"
#include "UtlStringMap.h"
#if defined( INCLUDE_SCALEFORM )
#include "scaleformui/scaleformui.h"
#endif
#include "vgui/ILocalize.h"
#include "eiface.h"
#include "cl_broadcast.h"
#include "csgo_limits.h"
#include "csgo_limits.inl"
#if defined( _PS3 )
#include <sysutil/sysutil_userinfo.h>
#endif
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
ConVar cl_teammate_color_1( "cl_teammate_color_1", "240 243 32" );
ConVar cl_teammate_color_2( "cl_teammate_color_2", "150 34 223" );
ConVar cl_teammate_color_3( "cl_teammate_color_3", "0 165 90" );
ConVar cl_teammate_color_4( "cl_teammate_color_4", "92 168 255" );
ConVar cl_teammate_color_5( "cl_teammate_color_5", "255 155 37" );
#if defined( INCLUDE_SCALEFORM )
const char* g_szDefaultScaleformClientMovieName = "resource/flash/GameUIRootMovie.swf";
#endif
#ifdef ENABLE_RPT
void CL_NotifyRPTOfDisconnect( );
#endif // ENABLE_RPT
#if ( !defined( NO_STEAM ) && (!defined( DEDICATED ) ) )
void UpdateNameFromSteamID( IConVar *pConVar, CSteamID *pSteamID )
{
#if !defined( DEDICATED )
if ( !pConVar || !pSteamID || !Steam3Client().SteamFriends() )
return;
#if defined( _PS3 )
CSteamID sPsnId = Steam3Client().SteamUser()->GetConsoleSteamID();
if ( sPsnId.IsValid() )
{
const char *pszName = Steam3Client().SteamFriends()->GetFriendPersonaName( sPsnId );
pConVar->SetValue( pszName );
}
#else
Assert( pSteamID->GetAccountID() != 0 || CommandLine()->FindParm( "-ignoreSteamAsserts" ) );
const char *pszName = Steam3Client().SteamFriends()->GetFriendPersonaName( *pSteamID );
pConVar->SetValue( pszName );
#endif // _PS3
#endif
}
void SetNameToSteamIDName( IConVar *pConVar )
{
#if !defined( DEDICATED )
if ( Steam3Client().SteamUtils() && Steam3Client().SteamFriends() && Steam3Client().SteamUser() )
{
CSteamID steamID = Steam3Client().SteamUser()->GetSteamID();
UpdateNameFromSteamID( pConVar, &steamID );
}
#endif
}
#endif
void CL_NameCvarChanged( IConVar *pConVar, const char *pOldString, float flOldValue )
{
CSplitScreenAddedConVar *pCheck = dynamic_cast< CSplitScreenAddedConVar * >( pConVar );
if ( pCheck )
return;
#ifndef DEDICATED
#if !defined( NO_STEAM )
static bool bPreventRent = false;
if ( !bPreventRent )
{
bPreventRent = true;
SetNameToSteamIDName( pConVar );
bPreventRent = false;
}
#endif
#endif
ConVarRef var( pConVar );
// store off the last known name, that isn't default, in the registry
// this is a transition step so it can be used to display in friends
if ( 0 != Q_stricmp( var.GetString(), var.GetDefault() )
&& 0 != Q_stricmp( var.GetString(), "player" ) )
{
Sys_SetRegKeyValue( "Software\\Valve\\Steam", "LastGameNameUsed", (char *)var.GetString() );
}
}
#ifndef DEDICATED
void askconnect_accept_f()
{
char szHostName[256];
if ( IsAskConnectPanelActive( szHostName, sizeof( szHostName ) ) )
{
char szCommand[512];
V_snprintf( szCommand, sizeof( szCommand ), "connect %s", szHostName );
Cbuf_AddText( Cbuf_GetCurrentPlayer(), szCommand );
HideAskConnectPanel();
}
}
ConCommand askconnect_accept( "askconnect_accept", askconnect_accept_f, "Accept a redirect request by the server.", FCVAR_DONTRECORD );
#endif
#ifndef SWDS
extern IVEngineClient *engineClient;
// ---------------------------------------------------------------------------------------- //
static void SendClanTag( const char *pTag, const char *pName )
{
KeyValues *kv = new KeyValues( "ClanTagChanged" );
kv->SetString( "tag", pTag );
kv->SetString( "name", pName );
engineClient->ServerCmdKeyValues( kv );
}
#endif
// ---------------------------------------------------------------------------------------- //
void CL_ClanIdChanged( IConVar *pConVar, const char *pOldString, float flOldValue )
{
#ifndef SWDS
// Get the clan ID we're trying to select
ConVarRef var( pConVar );
uint32 newId = var.GetInt();
if ( newId == 0 )
{
// Default value, equates to no tag
SendClanTag( "", "" );
return;
}
#if !defined( NO_STEAM )
// Make sure this player is actually part of the desired clan
ISteamFriends *pFriends = Steam3Client().SteamFriends();
if ( pFriends )
{
int iGroupCount = pFriends->GetClanCount();
for ( int k = 0; k < iGroupCount; ++ k )
{
CSteamID clanID = pFriends->GetClanByIndex( k );
if ( clanID.GetAccountID() == newId )
{
CSteamID clanID( newId, Steam3Client().SteamUtils()->GetConnectedUniverse(), k_EAccountTypeClan );
// valid clan, accept the change
const char *szClanTag = pFriends->GetClanTag( clanID );
char chLimitedTag[ MAX_CLAN_TAG_LENGTH ];
CopyStringTruncatingMalformedUTF8Tail( chLimitedTag, szClanTag, MAX_CLAN_TAG_LENGTH );
const char *szClanName = pFriends->GetClanName( clanID );
SendClanTag( chLimitedTag, szClanName );
return;
}
}
}
#endif // NO_STEAM
// Couldn't validate the ID, so clear to the default (no tag)
var.SetValue( 0 );
#endif // !SWDS
}
ConVar cl_resend ( "cl_resend", "2", FCVAR_RELEASE, "Delay in seconds before the client will resend the 'connect' attempt", true, CL_MIN_RESEND_TIME, true, CL_MAX_RESEND_TIME );
ConVar cl_resend_timeout ( "cl_resend_timeout", "60", FCVAR_RELEASE, "Total time allowed for the client to resend the 'connect' attempt", true, CL_MIN_RESEND_TIME, true, 1000 * CL_MAX_RESEND_TIME );
ConVar cl_name ( "name","unnamed", FCVAR_ARCHIVE | FCVAR_USERINFO | FCVAR_SS | FCVAR_PRINTABLEONLY | FCVAR_SERVER_CAN_EXECUTE, "Current user name", CL_NameCvarChanged );
ConVar password ( "password", "", FCVAR_ARCHIVE | FCVAR_SERVER_CANNOT_QUERY | FCVAR_DONTRECORD, "Current server access password" );
static ConVar cl_interpolate( "cl_interpolate", "1", FCVAR_RELEASE, "Enables or disables interpolation on listen servers or during demo playback" );
ConVar cl_clanid( "cl_clanid", "0", FCVAR_ARCHIVE | FCVAR_USERINFO | FCVAR_HIDDEN, "Current clan ID for name decoration", CL_ClanIdChanged );
ConVar cl_color( "cl_color", "0", FCVAR_ARCHIVE | FCVAR_USERINFO, "Preferred teammate color", true, 0, true, 4 );
ConVar cl_decryptdata_key( "cl_decryptdata_key", "", FCVAR_RELEASE, "Key to decrypt encrypted GOTV messages" );
ConVar cl_decryptdata_key_pub( "cl_decryptdata_key_pub", "", FCVAR_RELEASE, "Key to decrypt public encrypted GOTV messages" );
ConVar cl_hideserverip( "cl_hideserverip", "0", FCVAR_RELEASE, "If set to 1, server IPs will be hidden in the console (except when you type 'status')" );
#ifdef _X360
ConVar cl_networkid_force ( "networkid_force", "", FCVAR_USERINFO | FCVAR_SS | FCVAR_PRINTABLEONLY | FCVAR_SERVER_CAN_EXECUTE | FCVAR_DEVELOPMENTONLY, "Forceful value for network id (e.g. XUID)" );
#endif
static ConVar cl_failremoteconnections( "cl_failremoteconnections", "0", FCVAR_DEVELOPMENTONLY, "Force connection attempts to time out" );
static uint32 GetPrivateIPDelayMsecs()
{
// Lesser of 1/2 cl_resend interval or 1000 msecs
float flSeconds = clamp( cl_resend.GetFloat() * 0.5f, 0.0f, 1.0f );
return (uint32)( flSeconds * 1000.0f );
}
// ---------------------------------------------------------------------------------------- //
// C_ServerClassInfo implementation.
// ---------------------------------------------------------------------------------------- //
C_ServerClassInfo::C_ServerClassInfo()
{
m_ClassName = NULL;
m_DatatableName = NULL;
m_InstanceBaselineIndex = INVALID_STRING_INDEX;
}
C_ServerClassInfo::~C_ServerClassInfo()
{
delete [] m_ClassName;
delete [] m_DatatableName;
}
// ---------------------------------------------------------------------------------------- //
// Server messaging helpers
// ---------------------------------------------------------------------------------------- //
CServerMsg::CServerMsg( CBaseClientState *pParent, IMatchAsyncOperationCallback *pCallback,
const ns_address& serverAdr, int socket, uint32 maxAttempts, double timeout ):
m_pParent( pParent )
{
m_eState = AOS_RUNNING;
m_pCallback = pCallback;
m_serverAdr = serverAdr;
m_socket = socket;
m_lastMsgSendTime = 0.0;
m_timeOut = timeout;
m_maxAttempts = maxAttempts;
m_numAttempts = 0;
m_result = 0;
}
void CServerMsg::Update()
{
if ( m_eState != AOS_RUNNING )
{
return;
}
double dt = net_time - m_lastMsgSendTime;
if ( dt < m_timeOut )
{
return;
}
if ( m_numAttempts >= m_maxAttempts )
{
// Failed to receive a reply
m_eState = AOS_FAILED;
m_pCallback->OnOperationFinished( this );
}
else
{
m_lastToken = RandomInt( INT_MIN, INT_MAX );
SendMsg( m_serverAdr, m_socket, m_lastToken );
m_lastMsgSendTime = net_time;
m_numAttempts++;
}
}
bool CServerMsg::IsValidResponse( const ns_address& from, uint32 token )
{
if ( GetState() != AOS_RUNNING )
{
// We are not expecting any responses
return false;
}
if ( !from.CompareAdr(GetServerAddr()) )
{
// Not expecting a response from this address
return false;
}
if ( token != GetLastToken() )
{
// This response is not for the last message sent
return false;
}
return true;
}
void CServerMsg::ResponseReceived( uint64 result )
{
if ( GetState() == AOS_RUNNING )
{
m_eState = AOS_SUCCEEDED;
m_result = result;
m_pCallback->OnOperationFinished( this );
}
}
extern ConVar sv_mmqueue_reservation_timeout;
extern ConVar sv_mmqueue_reservation_extended_timeout;
CServerMsg_CheckReservation::CServerMsg_CheckReservation( CBaseClientState *pParent, IMatchAsyncOperationCallback *pCallback,
const ns_address &serverAdr, int socket, uint64 reservationCookie, uint32 uiReservationStage ) :
CServerMsg( pParent, pCallback, serverAdr, socket, (uiReservationStage > 1) ? sv_mmqueue_reservation_extended_timeout.GetInt() : sv_mmqueue_reservation_timeout.GetInt(), 1.0 ) // Try as many times as server reservation seconds
{
m_reservationCookie = reservationCookie;
m_uiReservationStage = uiReservationStage;
}
void CServerMsg_CheckReservation::Release()
{
if ( m_pParent )
m_pParent->m_arrSvReservationCheck.FindAndFastRemove( this );
delete this;
}
void CServerMsg_CheckReservation::SendMsg( const ns_address &serverAdr, int socket, uint32 token )
{
// send the reservation message
char buffer[64];
bf_write msg(buffer,sizeof(buffer));
msg.WriteLong( CONNECTIONLESS_HEADER );
msg.WriteByte( A2S_RESERVE_CHECK );
msg.WriteLong( GetHostVersion() );
msg.WriteLong( token );
msg.WriteLong( m_uiReservationStage );
msg.WriteLongLong( m_reservationCookie );
#ifndef SWDS
msg.WriteLongLong( Steam3Client().SteamUser()->GetSteamID().ConvertToUint64() );
#else
msg.WriteLongLong( 0 );
#endif
#ifndef DEDICATED
if ( serverAdr.GetAddressType() == NSAT_PROXIED_GAMESERVER )
NET_InitSteamDatagramProxiedGameserverConnection( serverAdr );
#endif
NET_SendPacket( NULL, socket, serverAdr, msg.GetData(), msg.GetNumBytesWritten() );
}
void CServerMsg_CheckReservation::ResponseReceived( const ns_address &from, bf_read &msg, int32 hostVersion, uint32 token )
{
if ( hostVersion != GetHostVersion() )
return;
if ( !IsValidResponse( from, token ) )
return;
uint32 uiReservationStage = msg.ReadLong();
int numPlayersAwaiting = msg.ReadByte();
if ( numPlayersAwaiting == 0 )
{
DevMsg( "Server confirmed all players reservation%u\n", uiReservationStage );
CServerMsg::ResponseReceived( numPlayersAwaiting );
}
else
{
DevMsg( "Server reservation%u is awaiting %d\n", uiReservationStage, numPlayersAwaiting );
if ( 0x7F == numPlayersAwaiting )
{
// Failed to receive a reply
m_eState = AOS_FAILED;
m_pCallback->OnOperationFinished( this );
}
else
{
m_result = numPlayersAwaiting;
m_pCallback->OnOperationFinished( this ); // we remain in AOS_RUNNING, just notify the callback
}
}
}
CServerMsg_Ping::CServerMsg_Ping( CBaseClientState *pParent, IMatchAsyncOperationCallback *pCallback, const ns_address &serverAdr, int socket ) :
CServerMsg( pParent, pCallback, serverAdr, socket, 3, 5.0 )
{
m_timeLastMsgSent = 0.0;
}
void CServerMsg_Ping::Release()
{
if ( m_pParent )
m_pParent->m_arrSvPing.FindAndFastRemove( this );
delete this;
}
void CServerMsg_Ping::SendMsg( const ns_address &serverAdr, int socket, uint32 token )
{
m_timeLastMsgSent = net_time;
char buffer[64];
bf_write msg(buffer,sizeof(buffer));
msg.WriteLong( CONNECTIONLESS_HEADER );
msg.WriteByte( A2S_PING );
msg.WriteLong( GetHostVersion() );
msg.WriteLong( token );
#ifndef DEDICATED
if ( serverAdr.GetAddressType() == NSAT_PROXIED_GAMESERVER )
NET_InitSteamDatagramProxiedGameserverConnection( serverAdr );
#endif
DevMsg( "Pinging %s\n", ns_address_render( serverAdr ).String() );
NET_SendPacket( NULL, socket, serverAdr, msg.GetData(), msg.GetNumBytesWritten() );
}
void CServerMsg_Ping::ResponseReceived( const ns_address& from, bf_read &msg, int32 hostVersion, uint32 token )
{
if ( hostVersion != GetHostVersion() )
return;
if ( !IsValidResponse( from, token ) )
return;
double dt = net_time - m_timeLastMsgSent;
uint64 result = (uint64)( dt * 1000 );
CServerMsg::ResponseReceived( result );
}
// ---------------------------------------------------------------------------------------- //
// C_ServerClassInfo implementation.
// ---------------------------------------------------------------------------------------- //
CBaseClientState::CBaseClientState() :
m_BaselineHandles( DefLessFunc( int ) )
{
m_bSplitScreenUser = false;
m_Socket = NS_CLIENT;
m_pServerClasses = NULL;
m_StringTableContainer = NULL;
m_NetChannel = NULL;
m_nSignonState = SIGNONSTATE_NONE;
m_nChallengeNr = 0;
m_flConnectTime = 0;
m_nRetryNumber = 0;
m_nRetryMax = CL_CONNECTION_RETRIES;
m_nServerCount = 0;
m_nCurrentSequence = 0;
m_nDeltaTick = 0;
m_bPaused = 0;
m_nViewEntity = 0;
m_nPlayerSlot = 0;
m_nSplitScreenSlot = 0;
m_nMaxClients = 0;
m_nNumPlayersToConnect = 1;
Q_memset( m_pEntityBaselines, 0, sizeof( m_pEntityBaselines ) );
m_nServerClasses = 0;
m_nServerClassBits = 0;
m_ListenServerSteamID = 0ull;
m_flNextCmdTime = -1.0f;
Q_memset( m_szLevelName, 0, sizeof( m_szLevelName ) );
Q_memset( m_szLevelNameShort, 0, sizeof( m_szLevelNameShort ) );
Q_memset( m_szLastLevelNameShort, 0, sizeof( m_szLevelNameShort ) );
m_iEncryptionKeySize = 0;
Q_memset( m_szEncryptionKey, 0, sizeof( m_szEncryptionKey ) );
m_bRestrictServerCommands = true;
m_bRestrictClientCommands = true;
m_bServerConnectionRedirect = false;
m_bServerInfoProcessed = false;
m_nServerProtocolVersion = 0;
m_nServerInfoMsgProtocol = 0;
m_pServerReservationOperation = NULL;
m_pServerReservationCallback = NULL;
m_flReservationMsgSendTime = 0;
m_nReservationMsgRetryNumber = 0;
m_bEnteredPassword = false;
m_bWaitingForPassword = false;
#if ENGINE_CONNECT_VIA_MMS
m_bWaitingForServerGameDetails = false;
#endif
m_nServerReservationCookie = 0;
m_pKVGameSettings = NULL;
m_unUGCMapFileID = 0;
m_ulGameServerSteamID = 0;
}
CBaseClientState::~CBaseClientState()
{
if ( m_pKVGameSettings )
{
m_pKVGameSettings->deleteThis();
m_pKVGameSettings = NULL;
}
FOR_EACH_MAP( m_BaselineHandles, i )
{
g_pSerializedEntities->ReleaseSerializedEntity( m_BaselineHandles[ i ] );
}
m_BaselineHandles.RemoveAll();
}
void CBaseClientState::Clear( void )
{
m_nServerCount = -1;
m_nDeltaTick = -1;
m_ClockDriftMgr.Clear();
m_nCurrentSequence = 0;
m_nServerClasses = 0;
m_nServerClassBits = 0;
m_nPlayerSlot = 0;
m_nSplitScreenSlot = 0;
m_szLevelName[0] = 0;
m_nMaxClients = 0;
m_unUGCMapFileID = 0;
// m_nNumPlayersToConnect = 1; <-- when clearing state we need to preserve num players to properly "reconnect" to the server
// Need to cache off the name of the current map before clearing it so we can use when doing a memory flush.
if ( m_szLevelNameShort[0] )
{
V_strncpy( m_szLastLevelNameShort, m_szLevelNameShort, sizeof( m_szLastLevelNameShort ) );
}
m_szLevelNameShort[ 0 ] = 0;
if ( m_pServerClasses )
{
delete[] m_pServerClasses;
m_pServerClasses = NULL;
}
if ( m_StringTableContainer )
{
#ifndef SHARED_NET_STRING_TABLES
m_StringTableContainer->RemoveAllTables();
#endif
m_StringTableContainer = NULL;
}
FreeEntityBaselines();
if ( !m_bSplitScreenUser )
{
RecvTable_Term( false );
}
if ( m_NetChannel )
m_NetChannel->Reset();
m_bPaused = 0;
m_nViewEntity = 0;
m_nChallengeNr = 0;
m_flConnectTime = 0.0f;
m_bServerInfoProcessed = false;
m_nServerProtocolVersion = 0;
m_nServerInfoMsgProtocol = 0;
// Free all avatar data
m_mapPlayerAvatarData.PurgeAndDeleteElements();
}
void CBaseClientState::FileReceived( const char * fileName, unsigned int transferID, bool bIsReplayDemoFile )
{
ConMsg( "CBaseClientState::FileReceived: %s.\n", fileName );
#if defined( REPLAY_ENABLED )
if ( isReplayDemoFile )
{
CClientReplayHistoryEntryData *pEntry = static_cast< CClientReplayHistoryEntryData *>( g_pClientReplayHistoryManager->FindEntry( fileName ) ); Assert( pEntry );
if ( pEntry )
{
pEntry->m_bTransferComplete = true;
g_pClientReplayHistoryManager->FlushEntriesToDisk();
}
}
#endif
}
void CBaseClientState::FileDenied(const char *fileName, unsigned int transferID, bool bIsReplayDemoFile )
{
ConMsg( "CBaseClientState::FileDenied: %s.\n", fileName );
}
void CBaseClientState::FileRequested(const char *fileName, unsigned int transferID, bool bIsReplayDemoFile )
{
ConMsg( "File '%s' requested from %s.\n", fileName, m_NetChannel->GetAddress() );
m_NetChannel->SendFile( fileName, transferID, bIsReplayDemoFile ); // CBaseClientState always sends file
}
void CBaseClientState::FileSent(const char *fileName, unsigned int transferID, bool bIsReplayDemoFile )
{
ConMsg( "File '%s' sent.\n", fileName );
}
void CBaseClientState::ConnectionStart(INetChannel *chan)
{
m_NETMsgTick.Bind< CNETMsg_Tick_t >( chan, UtlMakeDelegate( this, &CBaseClientState::NETMsg_Tick ) );
m_NETMsgStringCmd.Bind< CNETMsg_StringCmd_t >( chan, UtlMakeDelegate( this, &CBaseClientState::NETMsg_StringCmd ) );
m_NETMsgSignonState.Bind< CNETMsg_SignonState_t >( chan, UtlMakeDelegate( this, &CBaseClientState::NETMsg_SignonState ) );
m_NETMsgSetConVar.Bind< CNETMsg_SetConVar_t >( chan, UtlMakeDelegate( this, &CBaseClientState::NETMsg_SetConVar ) );
m_NETMsgPlayerAvatarData.Bind< CNETMsg_PlayerAvatarData_t >( chan, UtlMakeDelegate( this, &CBaseClientState::NETMsg_PlayerAvatarData ) );
m_SVCMsgServerInfo.Bind< CSVCMsg_ServerInfo_t >( chan, UtlMakeDelegate( this, &CBaseClientState::SVCMsg_ServerInfo ) );
m_SVCMsgClassInfo.Bind< CSVCMsg_ClassInfo_t >( chan, UtlMakeDelegate( this, &CBaseClientState::SVCMsg_ClassInfo ) );
m_SVCMsgSendTable.Bind< CSVCMsg_SendTable_t >( chan, UtlMakeDelegate( this, &CBaseClientState::SVCMsg_SendTable ) );
m_SVCMsgCmdKeyValues.Bind< CSVCMsg_CmdKeyValues_t>( chan, UtlMakeDelegate( this, &CBaseClientState::SVCMsg_CmdKeyValues ) );
m_SVCMsg_EncryptedData.Bind< CSVCMsg_EncryptedData_t>( chan, UtlMakeDelegate( this, &CBaseClientState::SVCMsg_EncryptedData ) );
m_SVCMsgPrint.Bind< CSVCMsg_Print_t >( chan, UtlMakeDelegate( this, &CBaseClientState::SVCMsg_Print ) );
m_SVCMsgSetPause.Bind< CSVCMsg_SetPause_t >( chan, UtlMakeDelegate( this, &CBaseClientState::SVCMsg_SetPause ) );
m_SVCMsgSetView.Bind< CSVCMsg_SetView_t >( chan, UtlMakeDelegate( this, &CBaseClientState::SVCMsg_SetView ) );
m_SVCMsgCreateStringTable.Bind< CSVCMsg_CreateStringTable_t >( chan, UtlMakeDelegate( this, &CBaseClientState::SVCMsg_CreateStringTable ) );
m_SVCMsgUpdateStringTable.Bind< CSVCMsg_UpdateStringTable_t >( chan, UtlMakeDelegate( this, &CBaseClientState::SVCMsg_UpdateStringTable ) );
m_SVCMsgVoiceInit.Bind< CSVCMsg_VoiceInit_t >( chan, UtlMakeDelegate( this, &CBaseClientState::SVCMsg_VoiceInit ) );
m_SVCMsgVoiceData.Bind< CSVCMsg_VoiceData_t >( chan, UtlMakeDelegate( this, &CBaseClientState::SVCMsg_VoiceData ) );
m_SVCMsgFixAngle.Bind< CSVCMsg_FixAngle_t >( chan, UtlMakeDelegate( this, &CBaseClientState::SVCMsg_FixAngle ) );
m_SVCMsgPrefetch.Bind< CSVCMsg_Prefetch_t >( chan, UtlMakeDelegate( this, &CBaseClientState::SVCMsg_Prefetch ) );
m_SVCMsgCrosshairAngle.Bind< CSVCMsg_CrosshairAngle_t >( chan, UtlMakeDelegate( this, &CBaseClientState::SVCMsg_CrosshairAngle ) );
m_SVCMsgBSPDecal.Bind< CSVCMsg_BSPDecal_t >( chan, UtlMakeDelegate( this, &CBaseClientState::SVCMsg_BSPDecal ) );
m_SVCMsgSplitScreen.Bind< CSVCMsg_SplitScreen_t >( chan, UtlMakeDelegate( this, &CBaseClientState::SVCMsg_SplitScreen ) );
m_SVCMsgGetCvarValue.Bind< CSVCMsg_GetCvarValue_t >( chan, UtlMakeDelegate( this, &CBaseClientState::SVCMsg_GetCvarValue ) );
m_SVCMsgMenu.Bind< CSVCMsg_Menu_t >( chan, UtlMakeDelegate( this, &CBaseClientState::SVCMsg_Menu ) );
m_SVCMsgUserMessage.Bind< CSVCMsg_UserMessage_t >( chan, UtlMakeDelegate( this, &CBaseClientState::SVCMsg_UserMessage ) );
m_SVCMsgPaintmapData.Bind< CSVCMsg_PaintmapData_t >( chan, UtlMakeDelegate(this, &CBaseClientState::SVCMsg_PaintmapData ) );
m_SVCMsgGameEvent.Bind< CSVCMsg_GameEvent_t >( chan, UtlMakeDelegate( this, &CBaseClientState::SVCMsg_GameEvent ) );
m_SVCMsgGameEventList.Bind< CSVCMsg_GameEventList_t >( chan, UtlMakeDelegate( this, &CBaseClientState::SVCMsg_GameEventList ) );
m_SVCMsgTempEntities.Bind< CSVCMsg_TempEntities_t >( chan, UtlMakeDelegate( this, &CBaseClientState::SVCMsg_TempEntities ) );
m_SVCMsgPacketEntities.Bind< CSVCMsg_PacketEntities_t >( chan, UtlMakeDelegate( this, &CBaseClientState::SVCMsg_PacketEntities ) );
m_SVCMsgSounds.Bind< CSVCMsg_Sounds_t >( chan, UtlMakeDelegate( this, &CBaseClientState::SVCMsg_Sounds ) );
m_SVCMsgEntityMsg.Bind< CSVCMsg_EntityMsg_t >( chan, UtlMakeDelegate( this, &CBaseClientState::SVCMsg_EntityMsg ) );
}
void CBaseClientState::ConnectionStop( )
{
m_NETMsgTick.Unbind();
m_NETMsgStringCmd.Unbind();
m_NETMsgSignonState.Unbind();
m_NETMsgSetConVar.Unbind();
m_NETMsgPlayerAvatarData.Unbind();
m_SVCMsgServerInfo.Unbind();
m_SVCMsgClassInfo.Unbind();
m_SVCMsgSendTable.Unbind();
m_SVCMsgCmdKeyValues.Unbind();
m_SVCMsg_EncryptedData.Unbind();
m_SVCMsgPrint.Unbind();
m_SVCMsgSetPause.Unbind();
m_SVCMsgSetView.Unbind();
m_SVCMsgCreateStringTable.Unbind();
m_SVCMsgUpdateStringTable.Unbind();
m_SVCMsgVoiceInit.Unbind();
m_SVCMsgVoiceData.Unbind();
m_SVCMsgFixAngle.Unbind();
m_SVCMsgPrefetch.Unbind();
m_SVCMsgCrosshairAngle.Unbind();
m_SVCMsgBSPDecal.Unbind();
m_SVCMsgSplitScreen.Unbind();
m_SVCMsgGetCvarValue.Unbind();
m_SVCMsgMenu.Unbind();
m_SVCMsgUserMessage.Unbind();
m_SVCMsgPaintmapData.Unbind();
m_SVCMsgGameEvent.Unbind();
m_SVCMsgGameEventList.Unbind();
m_SVCMsgTempEntities.Unbind();
m_SVCMsgPacketEntities.Unbind();
m_SVCMsgSounds.Unbind();
m_SVCMsgEntityMsg.Unbind();
}
void CBaseClientState::ConnectionClosing( const char *reason )
{
ConMsg( "Disconnect: %s.\n", reason?reason:"unknown reason" );
Disconnect();
}
//-----------------------------------------------------------------------------
// Purpose: A svc_signonnum has been received, perform a client side setup
// Output : void CL_SignonReply
//-----------------------------------------------------------------------------
bool CBaseClientState::SetSignonState ( int state, int count, const CNETMsg_SignonState *msg )
{
// ConDMsg ("CL_SignonReply: %i\n", GetBaseLocalClient().signon);
if ( state < SIGNONSTATE_NONE || state > SIGNONSTATE_CHANGELEVEL )
{
ConMsg ("Received signon %i when at %i\n", state, m_nSignonState );
Assert( 0 );
return false;
}
if ( (state > SIGNONSTATE_CONNECTED) && (state <= m_nSignonState) && !m_NetChannel->IsPlayback() )
{
ConMsg ("Received signon %i when at %i\n", state, m_nSignonState);
Assert( 0 );
return false;
}
if ( (count != m_nServerCount) && (count != -1) && (m_nServerCount != -1) && !m_NetChannel->IsPlayback() )
{
ConMsg ("Received wrong spawn count %i when at %i\n", count, m_nServerCount );
Assert( 0 );
return false;
}
if ( m_nSignonState < SIGNONSTATE_CONNECTED && state >= SIGNONSTATE_CONNECTED )
{
// Reset direct connect lobby once client is in game
m_DirectConnectLobby = DirectConnectLobby_t();
// Reset all client-generated keys that are too old
if ( m_mapGeneratedEncryptionKeys.Count() > 300 )
{
int numPurge = 300 - m_mapGeneratedEncryptionKeys.Count();
numPurge = MIN( numPurge, 3000 );
while ( numPurge -- > 0 )
{
int32 idxOldestKey = m_mapGeneratedEncryptionKeys.FirstInorder();
delete [] m_mapGeneratedEncryptionKeys.Element( idxOldestKey );
m_mapGeneratedEncryptionKeys.RemoveAt( idxOldestKey );
}
}
}
m_nSignonState = state;
return true;
}
//////////////////////////////////////////////////////////////////////////
//
// 3rd party plugins managed encryption keys map
//
static CUtlStringMap< CUtlBuffer * > g_mapServersToCertificates;
void RegisterServerCertificate( char const *szServerAddress, int numBytesPayload, void const *pvPayload )
{
// Allocate new storage
CUtlBuffer *pNew = new CUtlBuffer;
pNew->EnsureCapacity( numBytesPayload );
pNew->SeekPut( CUtlBuffer::SEEK_HEAD, numBytesPayload );
V_memcpy( pNew->Base(), pvPayload, numBytesPayload );
UtlSymId_t symid = g_mapServersToCertificates.Find( szServerAddress );
if ( symid != UTL_INVAL_SYMBOL )
{
delete g_mapServersToCertificates[ symid ];
g_mapServersToCertificates[ symid ] = pNew;
}
else
{
g_mapServersToCertificates.Insert( szServerAddress, pNew );
}
}
//-----------------------------------------------------------------------------
// Purpose: called by CL_Connect and CL_CheckResend
// If we are in ca_connecting state and we have gotten a challenge
// response before the timeout, send another "connect" request.
// Output : void CL_SendConnectPacket
//-----------------------------------------------------------------------------
void CBaseClientState::SendConnectPacket ( const ns_address &netAdrRemote, int challengeNr, int authProtocol, uint64 unGSSteamID, bool bGSSecure )
{
COM_TimestampedLog( "SendConnectPacket" );
if ( !netAdrRemote.IsLoopback() )
{
bool bFound = m_Remote.IsAddressInList( netAdrRemote );
if ( !bFound )
{
Warning( "Sending connect packet to unexpected address %s\n", ns_address_render( netAdrRemote ).String() );
}
}
const char *CDKey = "NOCDKEY";
char msg_buffer[MAX_ROUTABLE_PAYLOAD * 2];
bf_write msg( msg_buffer, sizeof(msg_buffer) );
msg.WriteLong( CONNECTIONLESS_HEADER );
msg.WriteByte( C2S_CONNECT );
msg.WriteLong( m_nServerProtocolVersion ? m_nServerProtocolVersion : GetHostVersion() ); // fake to the server as the client with matching version, validate later
msg.WriteLong( authProtocol );
msg.WriteLong( challengeNr );
// msg.WriteString( GetClientName() ); // Name
msg.WriteString( "" ); // Server can find the name in FCVAR_USERINFO block, save on connectionless packet size
msg.WriteString( password.GetString() ); // password
//Send player info for main player and split screen players
msg.WriteByte( m_nNumPlayersToConnect );
int numBytesPacketHeader = msg.GetNumBytesWritten();
for( int playerCount = 0; playerCount < m_nNumPlayersToConnect; ++playerCount )
{
CCLCMsg_SplitPlayerConnect_t splitMsg;
Host_BuildUserInfoUpdateMessage( playerCount, splitMsg.mutable_convars(), false );
if ( CHLTVClientState *pHLTVClientState = dynamic_cast< CHLTVClientState * >( this ) )
{
pHLTVClientState->SetLocalInfoConvarsForUpstreamConnection( *splitMsg.mutable_convars(), true );
}
#ifdef _DEBUG
for ( int ii = 0; ii < splitMsg.convars().cvars_size(); ++ ii )
{
CMsg_CVars::CVar cvinfo( splitMsg.convars().cvars( ii ) );
NetMsgExpandCVarUsingDictionary( &cvinfo );
DevMsg( "[NET] connect user info: '%s' = '%s'\n", cvinfo.name().c_str(), cvinfo.value().c_str() );
}
#endif
splitMsg.WriteToBuffer( msg );
}
int numBytesFcvarUserInfo = msg.GetNumBytesWritten() - numBytesPacketHeader;
// Track cookie and certificate
int numBytesCookie = msg.GetNumBytesWritten();
// add the low violence setting
msg.WriteOneBit( g_bLowViolence );
// add the server reservation cookie, if we have one
msg.WriteLongLong( m_nServerReservationCookie );
msg.WriteByte( (uint8)CROSSPLAYPLATFORM_THISPLATFORM );
//
// write the client encryption key to be used
//
DeferredConnection_t &dc = m_DeferredConnection;
if ( dc.m_nEncryptionKey )
{
msg.WriteLong( dc.m_nEncryptionKey );
byte *pbEncryptionKey = NULL;
int32 idx = m_mapGeneratedEncryptionKeys.Find( dc.m_nEncryptionKey );
if ( idx != m_mapGeneratedEncryptionKeys.InvalidIndex() )
{
pbEncryptionKey = m_mapGeneratedEncryptionKeys.Element( idx );
}
else
{
dc.m_nEncryptedSize = 0;
}
msg.WriteLong( dc.m_nEncryptedSize );
if ( dc.m_nEncryptedSize )
msg.WriteBytes( pbEncryptionKey + NET_CRYPT_KEY_LENGTH, dc.m_nEncryptedSize );
}
else
{
// Try to see if there's a client-plugin override for the encryption key for this server?
bool bWriteZeroEncryptionKey = true;
ns_address_render renderRemoteAsString( netAdrRemote );
UtlSymId_t utlKey = g_mapServersToCertificates.Find( renderRemoteAsString.String() );
if ( utlKey != UTL_INVAL_SYMBOL )
{
CUtlBuffer &buf = *g_mapServersToCertificates[ utlKey ];
const int32 numMetadataBytes = sizeof( int32 ) + NET_CRYPT_KEY_LENGTH;
if ( buf.TellPut() > numMetadataBytes )
{
bWriteZeroEncryptionKey = false;
msg.WriteLong( *reinterpret_cast< int32 * >( buf.Base() ) );
int32 numEncryptedSize = buf.TellPut() - numMetadataBytes;
msg.WriteLong( numEncryptedSize );
msg.WriteBytes( ( const char * )( buf.Base() ) + numMetadataBytes, numEncryptedSize );
}
}
if ( bWriteZeroEncryptionKey )
{
msg.WriteLong( 0 );
}
}
numBytesCookie = msg.GetNumBytesWritten() - numBytesCookie;
int numBytesSteamAuth = msg.GetNumBytesWritten();
switch ( authProtocol )
{
// Fall through, bogus protocol type, use CD key hash.
case PROTOCOL_HASHEDCDKEY: CDKey = GetCDKeyHash();
msg.WriteString( CDKey ); // cdkey
break;
case PROTOCOL_STEAM: if ( !PrepareSteamConnectResponse( unGSSteamID, bGSSecure, netAdrRemote, msg ) )
{
return;
}
break;
default: Host_Error( "Unexepected authentication protocol %i!\n", authProtocol );
return;
}
numBytesSteamAuth = msg.GetNumBytesWritten() - numBytesSteamAuth;
// Mark time of this attempt for retransmit requests
m_flConnectTime = net_time;
// remember challengenr for TCP connection
m_nChallengeNr = challengeNr;
// Send protocol and challenge value
if ( msg.GetNumBytesWritten() > 896 )
{
Warning( "[NET] Client connect packet too large for %s, total size %u bytes ( %u header, %u info, %u cookie, %u auth )\n", ns_address_render( netAdrRemote ).String(), msg.GetNumBytesWritten(),
numBytesPacketHeader, numBytesFcvarUserInfo, numBytesCookie, numBytesSteamAuth );
Assert( 0 );
}
else
{
DevMsg( "[NET] Sending client connect packet to %s, total size %u bytes ( %u header, %u info, %u cookie, %u auth )\n", ns_address_render( netAdrRemote ).String(), msg.GetNumBytesWritten(),
numBytesPacketHeader, numBytesFcvarUserInfo, numBytesCookie, numBytesSteamAuth );
}
NET_SendPacket( NULL, m_Socket, netAdrRemote, msg.GetData(), msg.GetNumBytesWritten() );
// Remember Steam ID, if any
m_ulGameServerSteamID = unGSSteamID;
}
//-----------------------------------------------------------------------------
// Purpose: append steam specific data to a connection response
//-----------------------------------------------------------------------------
bool CBaseClientState::PrepareSteamConnectResponse( uint64 unGSSteamID, bool bGSSecure, const ns_address &adr, bf_write &msg )
{
// X360TBD: Network - Steam Dedicated Server hack
if ( IsX360() )
{
return true;
}
#if !defined( NO_STEAM ) && !defined( DEDICATED )
if ( !Steam3Client().SteamUser() )
{
COM_ExplainDisconnection( true, "The server requires that you be running Steam.\n" );
Disconnect();
return false;
}
#endif
#ifndef DEDICATED
// now append the steam3 cookie
char steam3Cookie[ STEAM_KEYSIZE ];
uint32 steam3CookieLen;
Steam3Client().GetAuthSessionTicket( steam3Cookie, sizeof(steam3Cookie), &steam3CookieLen, unGSSteamID, bGSSecure );
msg.WriteShort( steam3CookieLen );
if ( steam3CookieLen > 0 )
msg.WriteBytes( steam3Cookie, steam3CookieLen );
#endif
return true;
}
bool Remote_t::Resolve()
{
if ( !m_adrRemote.SetFromString( m_szRetryAddress ) )
{
return false;
}
if ( m_adrRemote.IsType<netadr_t>() && m_adrRemote.AsType<netadr_t>().GetPort() == 0 )
{
m_adrRemote.AsType<netadr_t>().SetPort( PORT_SERVER );
}
return true;
}
bool CAddressList::IsRemoteInList( char const *pchAdrCheck ) const
{
for ( int i = 0; i < m_List.Count(); ++i )
{
if ( !Q_stricmp( pchAdrCheck, Get( i ).m_szRetryAddress.String() ) )
return true;
}
return false;
}
bool CAddressList::IsAddressInList( const ns_address &adr ) const
{
for ( int i = 0; i < m_List.Count(); ++i )
{
if ( adr.CompareAdr( Get( i ).m_adrRemote ) )
return true;
}
return false;
}
void CAddressList::RemoveAll()
{
m_List.RemoveAll();
}
void CAddressList::Describe( CUtlString &str )
{
for ( int i = 0; i < m_List.Count(); ++i )
{
str += va( "%s(%s) ", Get( i ).m_szAlias.String(), ns_address_render( Get( i ).m_adrRemote ).String() );
}
}
int CAddressList::Count() const
{
return m_List.Count();
}
Remote_t &CAddressList::Get( int index )
{
Assert( index >= 0 && index < m_List.Count() );
return m_List[ index ];
}
const Remote_t &CAddressList::Get( int index ) const
{
Assert( index >= 0 && index < m_List.Count() );
return m_List[ index ];
}
void CAddressList::AddRemote( char const *pchAddress, char const *pchAlias )
{
if ( IsRemoteInList( pchAddress ) )
return;
Remote_t remote;
remote.m_szRetryAddress = pchAddress;
remote.m_szAlias = pchAlias;
remote.Resolve();
m_List.AddToTail( remote );
}
void CBaseClientState::ConnectInternal( const char *pchPublicAddress, char const *pchPrivateAddress, int numPlayers, const char* szJoinType )
{
#ifndef DEDICATED
#if !defined( NO_STEAM )
if ( !IsX360() ) // X360 matchmaking sets the forced user info values
{
// Get our name from steam. Needs to be done before connecting
// because we won't have triggered a check by changing our name.
IConVar *pVar = g_pCVar->FindVar( "name" );
if ( pVar )
{
SetNameToSteamIDName( pVar );
}
}
#endif
#endif
m_Remote.RemoveAll();
m_Remote.AddRemote( pchPublicAddress, "public" );
m_Remote.AddRemote( pchPrivateAddress, "private" );
if ( ShouldUseDirectConnectAddress( m_Remote ) )
{
ConColorMsg( Color( 0, 255, 0, 255 ), "Adding direct connect address to connection %s\n", ns_address_render( m_DirectConnectLobby.m_adrRemote ).String() );
m_Remote.AddRemote( ns_address_render( m_DirectConnectLobby.m_adrRemote ).String(), "direct" );
}
//standard connect always connects one players.
m_nNumPlayersToConnect = numPlayers;
// For the check for resend timer to fire a connection / getchallenge request.
SetSignonState( SIGNONSTATE_CHALLENGE, -1, NULL );
// Force connection request to fire.
m_flConnectTime = -FLT_MAX;
m_nRetryNumber = 0;
// Retry for up to timeout seconds
m_nRetryMax = cl_resend_timeout.GetFloat() / cl_resend.GetFloat();
m_ulGameServerSteamID = 0;
#if !defined ( DEDICATED )
if ( szJoinType && g_ClientDLL )
g_ClientDLL->RecordUIEvent( szJoinType );
#endif
}
void CBaseClientState::Connect( const char *pchPublicAddress, char const *pchPrivateAddress, const char* szJoinType )
{
ConnectInternal( pchPublicAddress, pchPrivateAddress, 1, szJoinType );
}
void CBaseClientState::ConnectSplitScreen( const char *pchPublicAddress, char const *pchPrivateAddress, int numPlayers, const char* szJoinType )
{
ConnectInternal( pchPublicAddress, pchPrivateAddress, numPlayers, szJoinType );
}
INetworkStringTable *CBaseClientState::GetStringTable( const char * name ) const
{
if ( !m_StringTableContainer )
{
Assert( m_StringTableContainer );
return NULL;
}
return m_StringTableContainer->FindTable( name );
}
void CBaseClientState::ForceFullUpdate( char const *pchReason )
{
if ( m_nDeltaTick == -1 )
return;
FreeEntityBaselines();
m_nDeltaTick = -1;
DevMsg( "Requesting full game update (%s)...\n", pchReason );
}
void CBaseClientState::FullConnect( const ns_address &adr, int nEncryptionKey )
{
// Initiate the network channel
byte *pbEncryptionKey = NULL;
if ( nEncryptionKey )
{
int32 idxEncryptedKey = m_mapGeneratedEncryptionKeys.Find( nEncryptionKey );
if ( idxEncryptedKey != m_mapGeneratedEncryptionKeys.InvalidIndex() )
{
pbEncryptionKey = m_mapGeneratedEncryptionKeys.Element( idxEncryptedKey );
}
else
{
ns_address_render renderRemoteAsString( adr );
UtlSymId_t utlKey = g_mapServersToCertificates.Find( renderRemoteAsString.String() );
if ( utlKey != UTL_INVAL_SYMBOL )
{
CUtlBuffer &buf = *g_mapServersToCertificates[ utlKey ];
const int32 numMetadataBytes = sizeof( int32 ) + NET_CRYPT_KEY_LENGTH;
if ( buf.Size() > numMetadataBytes )
{
pbEncryptionKey = ( ( byte * ) ( buf.Base() ) + sizeof( int32 ) );
}
}
}
}
COM_TimestampedLog( "CBaseClientState::FullConnect" );
m_NetChannel = NET_CreateNetChannel( m_Socket, &adr, "CLIENT", this, pbEncryptionKey, false );
Assert( m_NetChannel );
m_NetChannel->StartStreaming( m_nChallengeNr ); // open TCP stream
// Bump connection time to now so we don't resend a connection
// Request
m_flConnectTime = net_time;
// We'll request a full delta from the baseline
m_nDeltaTick = -1;
// We can send a cmd right away
m_flNextCmdTime = net_time;
// If we used a server reservation cookie to connect, clear it
m_nServerReservationCookie = 0;
// Mark client as connected
SetSignonState( SIGNONSTATE_CONNECTED, -1, NULL );
#if !defined(DEDICATED)
ns_address rconAdr = m_NetChannel->GetRemoteAddress();
if ( rconAdr.IsType<netadr_t>() )
{
RCONClient().SetAddress( rconAdr.AsType<netadr_t>() );
}
#endif
}
void CBaseClientState::ConnectionCrashed(const char *reason)
{
ConMsg( "Connection lost: %s.\n", reason?reason:"unknown reason" );
Disconnect();
}
void CBaseClientState::Disconnect( bool bShowMainMenu )
{
m_DeferredConnection.m_bActive = false;
m_bWaitingForPassword = false;
#if ENGINE_CONNECT_VIA_MMS
m_bWaitingForServerGameDetails = false;
#endif
m_bEnteredPassword = false;
m_flConnectTime = -FLT_MAX;
m_nRetryNumber = 0;
m_ulGameServerSteamID = 0;
if ( m_nSignonState == SIGNONSTATE_NONE )
return;
#if !defined( DEDICATED ) && defined( ENABLE_RPT )
CL_NotifyRPTOfDisconnect( );
#endif
SetSignonState( SIGNONSTATE_NONE, -1, NULL );
// Don't clear cookie here as this can get called as part of connection process if changing to new server, etc.
// m_nServerReservationCookie = 0;
ns_address adr;
if ( m_NetChannel )
{
adr = m_NetChannel->GetRemoteAddress();
}
else if ( m_Remote.Count() > 0 )
{
const char *pszAddr = m_Remote.Get( 0 ).m_szRetryAddress;
if ( !adr.SetFromString( m_Remote.Get( 0 ).m_szRetryAddress ) )
{
Warning( "Unable to parse retry address '%s'\n", pszAddr );
}
}
#ifndef DEDICATED
ns_address checkAdr = adr;
if ( adr.IsLoopback() || adr.IsLocalhost() )
{
checkAdr.AsType<netadr_t>().SetIP( net_local_adr.GetIPHostByteOrder() );
}
if ( m_ListenServerSteamID != 0ull && m_Remote.Count() > 0 )
{
Assert( g_pSteamSocketMgr->GetSteamIDForRemote( m_Remote.Get( 0 ).m_adrRemote ) == m_ListenServerSteamID );
NET_TerminateSteamConnection( m_Socket, m_ListenServerSteamID );
m_ListenServerSteamID = 0ull;
}
Steam3Client().CancelAuthTicket();
#endif
if ( m_NetChannel )
{
m_NetChannel->Shutdown( "Disconnect" );
m_NetChannel = NULL;
}
#ifndef DEDICATED
// Get rid of any whitelist in our filesystem and reload any files that the previous whitelist forced
// to come from Steam.
// MD: This causes an annoying pause when you disconnect from a server, so just leave the last whitelist active
// until you connect to a new server.
//CL_HandlePureServerWhitelist( NULL );
#endif
#ifndef DEDICATED
if ( m_bSplitScreenUser &&
splitscreen->IsValidSplitScreenSlot( m_nSplitScreenSlot ) )
{
splitscreen->RemoveSplitScreenUser( m_nSplitScreenSlot, m_nPlayerSlot + 1 );
}
#if defined( INCLUDE_SCALEFORM )
if ( g_pScaleformUI )
{
g_pScaleformUI->ShutdownIME();
g_pScaleformUI->SlotRelease( SF_SS_SLOT( m_nSplitScreenSlot ) );
}
#endif
#endif // DEDICATED
}
void CBaseClientState::RunFrame (void)
{
VPROF("CBaseClientState::RunFrame");
if ( (m_nSignonState > SIGNONSTATE_NEW) && m_NetChannel && g_GameEventManager.HasClientListenersChanged() )
{
// assemble a list of all events we listening to and tell the server
CCLCMsg_ListenEvents_t msg;
g_GameEventManager.WriteListenEventList( &msg );
m_NetChannel->SendNetMsg( msg );
}
if ( m_nSignonState == SIGNONSTATE_CHALLENGE )
{
CheckForResend();
}
CheckForReservationResend();
for ( int i = 0; i < m_arrSvReservationCheck.Count(); ++ i )
{
CServerMsg_CheckReservation *pSv = m_arrSvReservationCheck[ i ];
Assert( pSv );
if ( pSv )
{
pSv->Update();
// Calling "Update" will potentially call handlers' interface async operation
// finished callback which can release "pSv" object and modify
// contents of our clientstate m_arrSvPing array. Always must check
// it again for being in array before attempting to dereference it.
// Also, it is ok to skip update on some ping objects when array changes
// since update is only responsible for re-sends of the pings
}
}
for ( int i = 0; i < m_arrSvReservationCheck.Count(); ++ i )
{
CServerMsg_CheckReservation *pSv = m_arrSvReservationCheck[ i ];
Assert( pSv );
if ( pSv && pSv->IsFinished() )
{
// delete pSvPing; -- client will release
m_arrSvReservationCheck.FastRemove( i -- );
}
}
for ( int iSvPing = 0; iSvPing < m_arrSvPing.Count(); ++ iSvPing )
{
CServerMsg_Ping *pSvPing = m_arrSvPing[ iSvPing ];
Assert( pSvPing );
if ( pSvPing )
{
pSvPing->Update();
// Calling "Update" will potentially call handlers' interface async operation
// finished callback which can release "pSvPing" object and modify
// contents of our clientstate m_arrSvPing array. Always must check
// it again for being in array before attempting to dereference it.
// Also, it is ok to skip update on some ping objects when array changes
// since update is only responsible for re-sends of the pings
}
}
for ( int iSvPing = 0; iSvPing < m_arrSvPing.Count(); ++ iSvPing )
{
CServerMsg_Ping *pSvPing = m_arrSvPing[ iSvPing ];
Assert( pSvPing );
if ( pSvPing && pSvPing->IsFinished() )
{
// delete pSvPing; -- client will release
m_arrSvPing.FastRemove( iSvPing -- );
}
}
}
/*
=================
ResetConnectionRetries
Reset the resend state so that the next call to CheckForResend()
will try. Call this after a listen server connects to Steam.
=================
*/
void CBaseClientState::ResetConnectionRetries()
{
m_flConnectTime = -FLT_MAX;
m_nRetryNumber = 0;
}
/*
=================
CL_CheckForResend
Resend a connect message if the last one has timed out
=================
*/
void CBaseClientState::CheckForResend ( bool bForceResendNow /* = false */ )
{
// resend if we haven't gotten a reply yet
// We only resend during the connection process.
if ( m_nSignonState != SIGNONSTATE_CHALLENGE )
return;
if ( m_bWaitingForPassword )
return;
// Wait at least the resend # of seconds.
if ( !bForceResendNow && ( ( net_time - m_flConnectTime ) < cl_resend.GetFloat() ) )
return;
// No addresses in list!
if ( m_Remote.Count() <= 0 )
{
Assert( 0 );
return;
}
for ( int i = 0; i < m_Remote.Count(); ++i )
{
if ( !m_Remote.Get( i ).Resolve() )
{
ConMsg( "Bad server address %s(%s)\n", m_Remote.Get( i ).m_szAlias.String(), m_Remote.Get( i ).m_szRetryAddress.String() );
Disconnect();
return;
}
}
// Only retry so many times before failure.
if ( m_nRetryNumber >= GetConnectionRetryNumber() )
{
COM_ExplainDisconnection( true, "Connection failed after %i retries.\n", GetConnectionRetryNumber() );
// Host_Disconnect();
Disconnect();
return;
}
// Mark time of this attempt.
m_flConnectTime = net_time; // for retransmit requests
// Display appropriate message
if ( !StringHasPrefix( m_Remote.Get( 0 ).m_szRetryAddress, "localhost" ) )
{
CUtlString desc;
m_Remote.Describe( desc );
ConMsg ("%s %s...\n", m_nRetryNumber == 0 ? "Connecting to" : "Retrying", desc.String() );
}
#ifndef DEDICATED
#if ENGINE_CONNECT_VIA_MMS
if ( m_bWaitingForServerGameDetails )
// This is a special handler for when we need to fetch server game details
// before we can connect to the server
{
for ( int i = 0; i < m_Remote.Count(); ++i )
{
Remote_t &remote = m_Remote.Get( i );
ResendGameDetailsRequest( remote.m_adrRemote );
}
++m_nRetryNumber;
return;
}
#endif
#endif
char payload[ 128 ];
Q_snprintf( payload, sizeof( payload ), "%cconnect0x%08X", A2S_GETCHALLENGE, m_DeferredConnection.m_nChallenge );
// Request another challenge value.
ISteamSocketMgr::ESteamCnxType cnxType = g_pSteamSocketMgr->GetCnxType();
bool bShouldSendSteamRetry =
( m_ListenServerSteamID != 0ull ) &&
( m_nRetryNumber > 0 ) &&
!( m_nRetryNumber & 0x1 );
if ( m_ListenServerSteamID != 0ull &&
cnxType == ISteamSocketMgr::ESCT_ALWAYS )
{
bShouldSendSteamRetry = true;
}
if ( IsX360() )
bShouldSendSteamRetry = false;
else if ( m_ListenServerSteamID ) // PORTAL2-specific: force Steam cnx for P2P (todo: need latest Steam P2P APIs)
bShouldSendSteamRetry = true;
if ( !bShouldSendSteamRetry )
{
for ( int i = 0; i < m_Remote.Count(); ++i )
{
const Remote_t &remote = m_Remote.Get( i );
const char *pszProtocol = "[unknown protocol]";
char szAddress[128];
V_strcpy_safe( szAddress, ns_address_render( remote.m_adrRemote ).String() );
switch ( remote.m_adrRemote.GetAddressType() )
{
case NSAT_PROXIED_CLIENT: // we're connecting to a server, not a client
default:
Assert( false );
break;
case NSAT_NETADR:
pszProtocol = "UDP";
if ( cl_hideserverip.GetInt()>0 )
V_sprintf_safe( szAddress, "<hidden>" );
break;
case NSAT_P2P:
pszProtocol = "SteamP2P";
break;
case NSAT_PROXIED_GAMESERVER:
#ifdef DEDICATED
Assert( false );
#else
// Make sure we have a ticket, and are setup to talk to this guy
if ( !NET_InitSteamDatagramProxiedGameserverConnection( remote.m_adrRemote ) )
continue;
pszProtocol = "SteamDatagram";
#endif
break;
}
if ( developer.GetInt() != 0 )
{
ConColorMsg( Color( 0, 255, 0, 255 ), "%.3f: Sending connect to %s address %s via %s\n", net_time, remote.m_szAlias.String(), szAddress, pszProtocol );
}
NET_OutOfBandDelayedPrintf( m_Socket, remote.m_adrRemote, GetPrivateIPDelayMsecs() * i, payload, Q_strlen( payload ) + 1 );
}
}
else if ( m_ListenServerSteamID )
{
Msg( "%.3f: Sending Steam connect to %s %llx\n", net_time, m_Remote.Get( 0 ).m_szRetryAddress.String(), m_ListenServerSteamID );
m_Remote.Get( 0 ).m_adrRemote = NET_InitiateSteamConnection( m_Socket, m_ListenServerSteamID, payload, Q_strlen( payload ) + 1 );
}
else
{
Warning( "%.3f: Steam connection to unknown SteamId (%s) failed!\n", net_time, m_Remote.Get( 0 ).m_szRetryAddress.String() );
}
++m_nRetryNumber;
}
void CBaseClientState::ResendGameDetailsRequest( const ns_address &adr )
{
#ifndef DEDICATED
g_pMatchFramework->GetEventsSubscription()->BroadcastEvent( new KeyValues(
"Client::ResendGameDetailsRequest", "to", ns_address_render( adr ).String() ) );
#endif
}
static void Read_S2A_INFO_SRC( const ns_address &from, bf_read *msg )
{
char str[ 1024 ];
Msg( "Responder : %s\n", ns_address_render( from ).String() );
// read protocol version
Msg( "Protocol : %d\n", (int)msg->ReadByte() );
msg->ReadString( str, sizeof( str ) );
Msg( "Hostname : %s\n", str );
msg->ReadString( str, sizeof( str ) );
Msg( "Map : %s\n", str );
msg->ReadString( str, sizeof( str ) );
Msg( "Game : %s\n", str );
msg->ReadString( str, sizeof( str ) );
Msg( "Description : %s\n", str );
Msg( "AppID : %u\n", (unsigned int)msg->ReadShort() );
Msg( "Players : %u\n", (unsigned int)msg->ReadByte() );
Msg( "MaxPlayers : %u\n", (unsigned int)msg->ReadByte() );
Msg( "Bots : %u\n", (unsigned int)msg->ReadByte() );
char const *sType = "???";
switch ( msg->ReadByte() )
{
default:
break;
case 'd':
sType = "dedicated";
break;
case 'p':
sType = "proxy";
break;
case 'l':
sType = "listen";
break;
}
Msg( "Server Type : %s\n", sType );
char const *osType = "???";
switch ( msg->ReadByte() )
{
default:
break;
case 'l':
osType = "Linux";
break;
case 'w':
osType = "Windows";
break;
}
Msg( "OS Type : %s\n", osType );
Msg( "Password : %s\n", msg->ReadByte() > 0 ? "yes" : "no" );
Msg( "Secure : %s\n", msg->ReadByte() > 0 ? "yes" : "no" );
msg->ReadString( str, sizeof( str ) );
Msg( "Version : %s\n", str );
if ( msg->GetNumBytesLeft() <= 0 )
return;
unsigned char infoByte = msg->ReadByte();
if ( infoByte & S2A_EXTRA_DATA_HAS_GAME_PORT )
{
Msg( "Game Port : %u\n", (unsigned short)msg->ReadShort() );
}
if ( infoByte & S2A_EXTRA_DATA_HAS_SPECTATOR_DATA )
{
Msg( "Spectator Port: %u\n", (unsigned short)msg->ReadShort() );
msg->ReadString( str, sizeof( str ) );
Msg( "SpectatorName : %s\n", str );
}
if ( infoByte & S2A_EXTRA_DATA_HAS_GAMETAG_DATA )
{
msg->ReadString( str, sizeof( str ) );
Msg( "Public Tags : %s\n", str );
}
}
bool CBaseClientState::ProcessConnectionlessPacket( netpacket_t *packet )
{
VPROF( "ProcessConnectionlessPacket" );
Assert( packet );
// NOTE: msg is a reference to packet->message, so reading
// from "msg" will also advance read-pointer in "packet->message"!!!
// ... and vice-versa, hence passing "packet" to a nested function
// will make that function receive a modified message with advanced
// read pointer.
// [ this differs from server-side connectionless packet processing ]
bf_read &msg = packet->message; // handy shortcut
bf_read msgOriginal = packet->message;
int c = msg.ReadByte();
char string[MAX_ROUTABLE_PAYLOAD];
// FIXME: For some of these, we should confirm that the sender of
// the message is what we think the server is...
switch ( c )
{
case S2C_CONNECTION:
if ( ( m_nSignonState == SIGNONSTATE_CHALLENGE ) &&
( packet->from.CompareAdr(m_DeferredConnection.m_adrServerAddress, true ) ) &&
( msg.ReadByte() == '.' ) )
{
char chEncryptionKeyIndex[9] = {};
for ( int j = 0; j < 8; ++ j )
chEncryptionKeyIndex[j] = msg.ReadByte();
bool bConnectionExpectingEncryptionKey = ( m_DeferredConnection.m_nEncryptionKey != 0 ) ||
( g_mapServersToCertificates.Find( ns_address_render( packet->from ).String() ) != UTL_INVAL_SYMBOL );
int nEncryptionKeyIndex = 0;
if ( ( 1 == sscanf( chEncryptionKeyIndex, "%08X", &nEncryptionKeyIndex ) ) &&
( ( nEncryptionKeyIndex != 0 ) == bConnectionExpectingEncryptionKey ) )
{
// server accepted our connection request
FullConnect( packet->from, nEncryptionKeyIndex );
}
}
break;
case S2C_CHALLENGE:
// Response from getchallenge we sent to the server we are connecting to
if ( packet->from.IsLocalhost() || packet->from.IsLoopback() || !cl_failremoteconnections.GetBool() )
{
DeferredConnection_t &dc = m_DeferredConnection;
dc.m_bActive = false;
dc.m_adrServerAddress = packet->from;
dc.m_nChallenge = msg.ReadLong();
dc.m_nAuthprotocol = msg.ReadLong();
dc.m_unGSSteamID = 0;
dc.m_bGSSecure = false;
if ( dc.m_nAuthprotocol == PROTOCOL_STEAM )
{
if ( msg.ReadShort() != 0 )
{
Msg("Invalid Steam key size.\n");
Disconnect();
return false;
}
dc.m_unGSSteamID = msg.ReadLongLong();
dc.m_bGSSecure = msg.ReadByte() ? true : false;
}
else
{
msg.ReadShort();
dc.m_unGSSteamID = msg.ReadLongLong(); // still read out game server SteamID for token validation
msg.ReadByte();
}
if ( msg.IsOverflowed() )
{
Msg( "Invalid challenge packet.\n" );
Disconnect();
return false;
}
// The host can disable access to secure servers if you load unsigned code (mods, plugins, hacks)
if ( dc.m_bGSSecure && !Host_IsSecureServerAllowed() )
{
m_netadrReserveServer.RemoveAll();
m_nServerReservationCookie = 0;
m_pServerReservationCallback = NULL;
#if !defined(DEDICATED)
g_pMatchFramework->CloseSession();
g_pMatchFramework->GetEventsSubscription()->BroadcastEvent( new KeyValues( "OnClientInsecureBlocked", "reason", "connect" ) );
#endif
Disconnect();
return false;
}
char context[ 256 ] = { 0 };
msg.ReadString( context, sizeof( context ) );
if ( StringHasPrefix( context, "reserve" ) )
{
HandleReserveServerChallengeResponse( m_DeferredConnection.m_nChallenge );
}
else if ( StringHasPrefix( context, "connect" ) )
{
// Blow it off if we are not connected.
if ( m_nSignonState != SIGNONSTATE_CHALLENGE )
{
return false;
}
dc.m_bActive = true;
int nProto = msg.ReadLong();
m_nServerProtocolVersion = nProto;
if ( nProto > GetHostVersion() ) // server is running newer version
{
Msg( "Server is running a newer version, client version %d, server version %d\n", GetHostVersion(), nProto );
Disconnect();
return false;
}
if ( nProto < GetHostVersion() ) // server is running older version
{
Msg( "Server is running an older version, client version %d, server version %d\n", GetHostVersion(), nProto );
Disconnect();
return false;
}
msg.ReadString( dc.m_chLobbyType, ARRAYSIZE( dc.m_chLobbyType ) - 1 );
dc.m_bRequiresPassword = ( msg.ReadByte() != 0 );
dc.m_unLobbyID = msg.ReadLongLong();
dc.m_bDCFriendsReqd = (msg.ReadByte() != 0);
dc.m_bOfficialValveServer = ( msg.ReadByte() != 0 );
// Generate an encryption key for this challenge
bool bEncryptedChannel = ( msg.ReadByte() != 0 );
if ( bEncryptedChannel )
{
byte chKeyPub[1024] = {};
byte chKeySgn[1024] = {};
int cbKeyPub = msg.ReadLong();
msg.ReadBytes( chKeyPub, cbKeyPub );
int cbKeySgn = msg.ReadLong();
msg.ReadBytes( chKeySgn, cbKeySgn );
if ( msg.IsOverflowed() )
{
Msg( "Invalid challenge packet.\n" );
Disconnect();
return false;
}
// Verify server certificate signature
byte *pbAllocatedKey = NULL;
int nAllocatedCryptoBlockSize = 0;
if ( !NET_CryptVerifyServerCertificateAndAllocateSessionKey( dc.m_bOfficialValveServer, dc.m_adrServerAddress,
chKeyPub, cbKeyPub, chKeySgn, cbKeySgn,
&pbAllocatedKey, &nAllocatedCryptoBlockSize ) || !pbAllocatedKey || !nAllocatedCryptoBlockSize )
{
delete [] pbAllocatedKey;
Msg( "Bad challenge signature.\n" );
Disconnect();
return false;
}
static int s_nGeneratedEncryptionKey = 0;
++s_nGeneratedEncryptionKey;
if ( !s_nGeneratedEncryptionKey )
++ s_nGeneratedEncryptionKey;
m_mapGeneratedEncryptionKeys.InsertOrReplace( s_nGeneratedEncryptionKey, pbAllocatedKey );
dc.m_nEncryptionKey = s_nGeneratedEncryptionKey;
dc.m_nEncryptedSize = nAllocatedCryptoBlockSize;
}
else
{
dc.m_nEncryptionKey = 0;
dc.m_nEncryptedSize = 0;
}
Msg( "Server using '%s' lobbies, requiring pw %s, lobby id %llx\n",
dc.m_chLobbyType[0] ? dc.m_chLobbyType : "<none>",
dc.m_bRequiresPassword ? "yes" : "no",
dc.m_unLobbyID );
#if ENGINE_CONNECT_VIA_MMS
// Check if server is denying dc. If it sent us a -1 then we have to use our own reservation id.
// This will work if we joined the right lobby, otherwise it will fail
if ( dc.m_unLobbyID == (uint64)(-1) )
{
// GSidhu - Detect the case when we are trying to direct connect - ensure the reservation
// cookie we are holding is reset to 0 between session
//if ( m_nServerReservationCookie == 0 )
//{
// COM_ExplainDisconnection( true, "Connecting to a Competitive mode game on a Valve CS:GO server is not allowed, use matchmaking instead.\n" );
// Disconnect();
// break;
//}
// else
{
dc.m_unLobbyID = m_nServerReservationCookie;
}
}
RememberIPAddressForLobby( dc.m_unLobbyID, dc.m_adrServerAddress );
#endif
if ( !dc.m_chLobbyType[0] && !dc.m_unLobbyID && dc.m_bRequiresPassword &&
( !password.GetString()[ 0 ] ) )
{
// Stop resending challenges while PW dialog is up
m_bWaitingForPassword = true;
// Show PW UI with current string
#ifndef DEDICATED
SCR_EndLoadingPlaque();
EngineVGui()->ShowPasswordUI( password.GetString() );
#endif
}
else if ( dc.m_chLobbyType[0] && !sv.IsActive() && !dc.m_unLobbyID &&
m_nServerReservationCookie )
{
// Server protocol violation - client has reserved this server, but server
// replies that it requires lobbies and doesn't yet have a lobby ID
Warning( "Server error - failed to handle reservation request.\n" );
Disconnect();
return false;
}
else if ( StringHasPrefix( context, "connect-retry" ) &&
dc.m_chLobbyType[0] && !sv.IsActive() && !dc.m_unLobbyID )
// Server tells us that we need to issue another "connect" with a valid
// challenge, then it will reserve itself for a brief period to
// let us create the required lobby
{
Msg( "Grace request retry for unreserved server...\n" );
CheckForResend( true ); // force a resend with the correct challenge nr
}
else if ( StringHasPrefix( context, "connect-matchmaking-only" ) )
// This response is sent by Valve CS:GO servers - we cannot
// direct-connect and need to go via matchmaking instead
{
COM_ExplainDisconnection( true, "You must use matchmaking to connect to this CS:GO server.\n" );
Disconnect();
break;
}
else if ( StringHasPrefix( context, "connect-lan-only" ) )
// This response is sent by anonymous community servers - we cannot
// direct-connect unless we are on the same LAN network
{
COM_ExplainDisconnection( true, "You cannot connect to this CS:GO server because it is restricted to LAN connections only.\n" );
Disconnect();
break;
}
else if ( !StringHasPrefix( context, "connect-granted" ) &&
dc.m_chLobbyType[0] && !sv.IsActive() && !dc.m_unLobbyID )
// Server requires lobbies, but is unreserved at the moment, so
// we should keep waiting for "connect-granted" response before we
// proceed and create a lobby
{
Msg( "Server did not approve grace request, retrying...\n" );
CheckForResend( true ); // force a resend with the correct challenge nr
}
else
{
if ( dc.m_chLobbyType[0] && !sv.IsActive() && !dc.m_unLobbyID )
{
Msg( "Server approved grace request...\n" );
}
HandleDeferredConnection();
}
}
}
break;
case A2A_PRINT:
if ( msg.ReadString( string, sizeof(string) ) )
{
ConMsg ( "%s\n", string );
}
break;
case S2C_CONNREJECT:
if ( m_nSignonState == SIGNONSTATE_CHALLENGE )
{
msg.ReadString( string, sizeof(string) );
// Check if the connection is rejected with a redirect address
if ( char const *szRedirectAddress = StringAfterPrefix( string, "ConnectRedirectAddress:" ) )
{
m_Remote.RemoveAll();
m_Remote.AddRemote( szRedirectAddress, "public" );
// For the check for resend timer to fire a connection / getchallenge request.
SetSignonState( SIGNONSTATE_CHALLENGE, -1, NULL );
// Force connection request to fire.
m_flConnectTime = -FLT_MAX;
m_nRetryNumber = 0;
// Retry for up to timeout seconds
m_nRetryMax = cl_resend_timeout.GetFloat() / cl_resend.GetFloat();
break;
}
// Force failure dialog to come up now.
COM_ExplainDisconnection( true, "%s", string );
Disconnect();
// Host_Disconnect();
}
break;
case A2A_PING:
NET_OutOfBandPrintf( m_Socket, packet->from, "%c00000000000000", A2A_ACK );
break;
case A2A_ACK:
ConMsg ("A2A_ACK from %s\n", ns_address_render( packet->from ).String() );
#if defined( _GAMECONSOLE )
// skip \r\n
msg.ReadByte();
msg.ReadByte();
const void *pvData = msg.GetBasePointer() + ( msg.GetNumBitsRead() >> 3 );
int numBytes = msg.GetNumBytesLeft();
KeyValues *notify = new KeyValues( "A2A_ACK" );
notify->SetPtr( "ptr", const_cast< void * >( pvData ) );
notify->SetInt( "size", numBytes );
g_pMatchFramework->GetEventsSubscription()->BroadcastEvent( notify );
#endif
break;
case A2A_CUSTOM:
break; // TODO fire local game event
case S2A_RESERVE_RESPONSE:
if ( msg.ReadLong() == GetHostVersion() )
{
ReservationResponseReply_t reply;
reply.m_adrFrom = packet->from;
reply.m_uiResponse = msg.ReadByte();
reply.m_bValveDS = msg.ReadOneBit() ? true : false;
reply.m_numGameSlots = msg.ReadLong();
HandleReservationResponse( reply );
}
break;
case S2A_RESERVE_CHECK_RESPONSE:
{
int32 hostVersion = msg.ReadLong();
uint32 token = msg.ReadLong();
for ( int i = 0; i < m_arrSvReservationCheck.Count(); ++ i )
{
CServerMsg_CheckReservation *pSv = m_arrSvReservationCheck[ i ];
if ( pSv && pSv->m_serverAdr.CompareAdr( packet->from ) )
{
pSv->ResponseReceived( packet->from, msg, hostVersion, token );
}
}
}
break;
case S2A_PING_RESPONSE:
{
int32 hostVersion = msg.ReadLong();
uint32 token = msg.ReadLong();
for ( int iSvPing = 0; iSvPing < m_arrSvPing.Count(); ++ iSvPing )
{
CServerMsg_Ping *pSvPing = m_arrSvPing[ iSvPing ];
if ( pSvPing && pSvPing->m_serverAdr.CompareAdr( packet->from ) )
{
pSvPing->ResponseReceived( packet->from, msg, hostVersion, token );
}
}
}
break;
#ifndef DEDICATED
case 0:
{
// Feed into matchmaking
packet->message = msgOriginal;
KeyValues *notify = new KeyValues( "OnNetLanConnectionlessPacket" );
notify->SetPtr( "rawpkt", packet );
g_pMatchFramework->GetEventsSubscription()->BroadcastEvent( notify );
}
return true;
#endif
#if defined( _GAMECONSOLE )
case M2A_SERVER_BATCH:
{
// skip \n
msg.ReadByte();
const void *pvData = msg.GetBasePointer() + ( msg.GetNumBitsRead() >> 3 );
int numBytes = msg.GetNumBytesLeft();
KeyValues *notify = new KeyValues( "M2A_SERVER_BATCH" );
notify->SetPtr( "ptr", const_cast< void * >( pvData ) );
notify->SetInt( "size", numBytes );
g_pMatchFramework->GetEventsSubscription()->BroadcastEvent( notify );
}
break;
case A2A_KV_CMD:
{
int nVersion = msg.ReadByte();
if ( nVersion == A2A_KV_VERSION )
{
int nHeader = msg.ReadLong();
int nReplyId = msg.ReadLong();
int nChallenge = msg.ReadLong();
int nExtra = msg.ReadLong();
int numBytes = msg.ReadLong();
KeyValues *kvData = NULL;
if ( numBytes > 0 && numBytes <= MAX_ROUTABLE_PAYLOAD )
{
void *pvBytes = stackalloc( numBytes );
if ( msg.ReadBytes( pvBytes, numBytes ) )
{
kvData = new KeyValues( "" );
CUtlBuffer buf( pvBytes, numBytes, CUtlBuffer::READ_ONLY );
buf.ActivateByteSwapping( !CByteswap::IsMachineBigEndian() );
if ( !kvData->ReadAsBinary( buf ) )
{
kvData->deleteThis();
kvData = NULL;
}
}
}
KeyValues *notify = new KeyValues( "A2A_KV_CMD" );
if ( kvData )
notify->AddSubKey( kvData );
notify->SetInt( "version", nVersion );
notify->SetInt( "header", nHeader );
notify->SetInt( "replyid", nReplyId );
notify->SetInt( "challenge", nChallenge );
notify->SetInt( "extra", nExtra );
notify->SetInt( "size", numBytes );
g_pMatchFramework->GetEventsSubscription()->BroadcastEvent( notify );
}
}
break;
#endif
case S2A_INFO_SRC:
{
// Handle pingserver response
Read_S2A_INFO_SRC( packet->from, &msg );
}
return true;
// Unknown?
default:
// Otherwise, don't do anything.
ConDMsg( "Bad connectionless packet ( CL '%c') from %s.\n", c, ns_address_render( packet->from ).String() );
return false;
}
return true;
}
void CBaseClientState::OnEvent( KeyValues *pEvent )
{
#if ENGINE_CONNECT_VIA_MMS
char const *szEvent = pEvent->GetName();
if ( !Q_stricmp( "OnNetLanConnectionlessPacket", szEvent ) )
{
if ( KeyValues *pGameDetailsServer = pEvent->FindKey( "GameDetailsServer" ) )
{
char const *szDetailsAdr = pGameDetailsServer->GetString( "ConnectServerDetailsRequest/server" );
if ( !m_bWaitingForServerGameDetails ||
!szDetailsAdr || !*szDetailsAdr )
return;
int idxRemoteReconnect = -1;
for ( int i = 0; i < m_Remote.Count(); ++i )
{
const netadr_t &adr = m_Remote.Get( i ).m_adrRemote;
if ( !Q_stricmp( adr.ToString(), szDetailsAdr ) )
{
idxRemoteReconnect = i;
break;
}
}
if ( idxRemoteReconnect >= 0 )
{
// This is our direct connect probe response
m_bWaitingForServerGameDetails = false;
Msg( "Received game details information from %s...\n",
m_Remote.Get( idxRemoteReconnect ).m_szRetryAddress.String() );
Disconnect( false ); // disconnect the current attempt, will retry with reservation
//
// Prepare the settings
//
pGameDetailsServer->SetName( "settings" );
if ( KeyValues *kvLanSearch = pGameDetailsServer->FindKey( "ConnectServerDetailsRequest" ) )
{
// LanSearch is not needed for the sessions
pGameDetailsServer->RemoveSubKey( kvLanSearch );
kvLanSearch->deleteThis();
}
// Add the bypass lobby flag
KeyValues *optionsKey = pGameDetailsServer->FindKey( "options", true);
optionsKey->SetInt( "bypasslobby", 1 );
Disconnect( false );
g_pMatchFramework->GetEventsSubscription()->BroadcastEvent( new KeyValues(
"OnEngineLevelLoadingSession", "reason", "CreateSession" ) );
g_pMatchFramework->CreateSession( pGameDetailsServer );
}
}
}
#endif
}
void CBaseClientState::SetConnectionPassword( char const *pchCurrentPW )
{
if ( !pchCurrentPW || !*pchCurrentPW )
{
m_bWaitingForPassword = false;
m_bEnteredPassword = false;
Msg( "Connection to %s failed, server requires a password\n", ns_address_render( m_DeferredConnection.m_adrServerAddress ).String() );
Disconnect();
return;
}
#ifndef DEDICATED
SCR_BeginLoadingPlaque();
#endif
m_bWaitingForPassword = false;
m_bEnteredPassword = true;
password.SetValue( pchCurrentPW );
HandleDeferredConnection();
}
void CBaseClientState::RememberIPAddressForLobby( uint64 unLobbyID, const ns_address &adrRemote )
{
ConColorMsg( Color( 0, 255, 0, 255 ), "RememberIPAddressForLobby: lobby %llx from address %s\n", unLobbyID, ( cl_hideserverip.GetInt()>0 && adrRemote.IsType<netadr_t>() ) ? "<ip hidden>" : ns_address_render( adrRemote ).String() );
m_DirectConnectLobby.m_unLobbyID = unLobbyID;
m_DirectConnectLobby.m_adrRemote = adrRemote;
// Keep valid for 1 minute
m_DirectConnectLobby.m_flEndTime = realtime + 60.0f;
}
void CBaseClientState::HandleDeferredConnection()
{
DeferredConnection_t &dc = m_DeferredConnection;
if ( !dc.m_bActive )
return;
dc.m_bActive = false;
#ifndef DEDICATED
// If we started a listen server then we should connect no matter what
if ( ( dc.m_chLobbyType[0] || dc.m_unLobbyID ) && !sv.IsActive() )
{
#if ENGINE_CONNECT_VIA_MMS
if ( dc.m_unLobbyID )
{
IMatchSession *pMatchSession = g_pMatchFramework->GetMatchSession();
KeyValues *pSessionSysData = pMatchSession ? pMatchSession->GetSessionSystemData() : NULL;
uint64 xuidSessionReservation = pSessionSysData ? pSessionSysData->GetUint64( "xuidReserve" ) : 0ull;
if ( dc.m_bOfficialValveServer || ( xuidSessionReservation == dc.m_unLobbyID ) ) // Force the connection to official server, they aren't direct-connectable otherwise
{
SendConnectPacket ( dc.m_adrServerAddress, dc.m_nChallenge, dc.m_nAuthprotocol, dc.m_unGSSteamID, dc.m_bGSSecure );
}
else
{
KeyValues *pSettings = new KeyValues( "settings" );
KeyValues::AutoDelete autodelete( pSettings );
pSettings->SetString( "system/network", "LIVE" );
pSettings->SetString( "options/action", "joinsession" );
pSettings->SetUint64( "options/sessionid", dc.m_unLobbyID );
pSettings->SetBool( "options/dcFriendsRed", dc.m_bDCFriendsReqd );
if ( !dc.m_bOfficialValveServer )
pSettings->SetInt( "game/hosted", 1 );
pSettings->SetString( "options/server", dc.m_bOfficialValveServer ? "official" : "dedicated" );
Disconnect( true );
g_pMatchFramework->GetEventsSubscription()->BroadcastEvent( new KeyValues(
"OnEngineLevelLoadingSession", "reason", "MatchSession" ) );
g_pMatchFramework->MatchSession( pSettings );
}
}
else
{
// Server has no lobby but needs one, we need to enter through the UI.
Msg( "Retrying connection to %s, server requires lobby reservation but is unreserved.\n", dc.m_adrServerAddress.ToString() );
// Prevent resending the challenges to the server
m_bWaitingForServerGameDetails = true;
ResendGameDetailsRequest( dc.m_adrServerAddress );
}
#else
bool bCanSendConnectPacketRightNow = false;
// Always allow to connect if we have a reservation in m_nServerReservationCookie
if ( m_nServerReservationCookie )
bCanSendConnectPacketRightNow = true;
// Always allow to connect to listen server peer SteamID
if ( m_ListenServerSteamID )
bCanSendConnectPacketRightNow = true;
// Always allow to connect from dedicated or ValveDS or GOTV relay
if ( IsClientStateTv() || NET_IsDedicated() || sv.IsDedicated() || ( serverGameDLL && serverGameDLL->IsValveDS() ) )
bCanSendConnectPacketRightNow = true;
if ( !bCanSendConnectPacketRightNow && (
!dc.m_unGSSteamID || // Connecting to GOTV relay
( CSteamID( dc.m_unGSSteamID ).GetEAccountType() == k_EAccountTypeInvalid ) // Connecting to sv_lan 1 server
) )
{
static const bool s_bAllowLanWhitelist = !CommandLine()->FindParm( "-ignorelanwhitelist" );
bCanSendConnectPacketRightNow = // only allow for LAN server setup to bypass GC auth
dc.m_adrServerAddress.IsLocalhost() || dc.m_adrServerAddress.IsLoopback() // localhost/loopback
|| ( s_bAllowLanWhitelist && dc.m_adrServerAddress.IsReservedAdr() ) // LAN RFC 1918
;
}
// If we determined that client is good to go then just follow up with a real connect packet
if ( bCanSendConnectPacketRightNow )
{
SendConnectPacket( dc.m_adrServerAddress, dc.m_nChallenge, dc.m_nAuthprotocol, dc.m_unGSSteamID, dc.m_bGSSecure );
return;
}
// Check that if we are falling through we have a good game server Steam ID to ask GC about
if ( !dc.m_unGSSteamID || !CSteamID( dc.m_unGSSteamID ).BGameServerAccount() )
{
Disconnect( true ); // cannot retry this attempt - the GS SteamID is not good
COM_ExplainDisconnection( true, "You cannot connect to this CS:GO server because it is restricted to LAN connections only.\n" );
return;
}
//
// Otherwise we require that client obtained a GS cookie from GC
// which allows GC to deny connections to blacklisted game servers
//
uint64 uiReservationCookie = 0ull;
{
KeyValues *kvCreateSession = new KeyValues( "OnEngineLevelLoadingSession" );
kvCreateSession->SetString( "reason", "CreateSession" );
kvCreateSession->SetString( "adr", ns_address_render( dc.m_adrServerAddress ).String() );
kvCreateSession->SetUint64( "gsid", dc.m_unGSSteamID );
kvCreateSession->SetPtr( "ptr", &uiReservationCookie );
g_pMatchFramework->GetEventsSubscription()->BroadcastEvent( kvCreateSession );
}
if ( !uiReservationCookie )
{
Disconnect( true ); // disconnect the current attempt, will retry with GC reservation
{
KeyValues *kvCreateSession = new KeyValues( "OnEngineLevelLoadingSession" );
kvCreateSession->SetString( "reason", "CreateSession" );
kvCreateSession->SetString( "adr", ns_address_render( dc.m_adrServerAddress ).String() );
kvCreateSession->SetUint64( "gsid", dc.m_unGSSteamID );
// NO PTR HERE, FORCE COOKIE: kvCreateSession->SetPtr( "ptr", &uiReservationCookie );
g_pMatchFramework->GetEventsSubscription()->BroadcastEvent( kvCreateSession );
}
}
else
SendConnectPacket( dc.m_adrServerAddress, dc.m_nChallenge, dc.m_nAuthprotocol, dc.m_unGSSteamID, dc.m_bGSSecure );
#endif
}
else
#endif
{
SendConnectPacket ( dc.m_adrServerAddress, dc.m_nChallenge, dc.m_nAuthprotocol, dc.m_unGSSteamID, dc.m_bGSSecure );
}
}
bool CBaseClientState::NETMsg_Tick( const CNETMsg_Tick& msg )
{
VPROF( "ProcessTick" );
m_NetChannel->SetRemoteFramerate(
CNETMsg_Tick_t::FrametimeToFloat( msg.host_computationtime() ),
CNETMsg_Tick_t::FrametimeToFloat( msg.host_computationtime_std_deviation() ),
CNETMsg_Tick_t::FrametimeToFloat( msg.host_framestarttime_std_deviation() ) );
// Note: CClientState separates the client and server clock states and drifts
// the client's clock to match the server's, but right here, we keep the two clocks in sync.
SetClientTickCount( msg.tick() );
SetServerTickCount( msg.tick() );
if ( m_StringTableContainer )
{
m_StringTableContainer->SetTick( GetServerTickCount() );
}
return true;// (GetServerTickCount()>0);
}
void CBaseClientState::SendStringCmd(const char * command)
{
if ( m_NetChannel)
{
CNETMsg_StringCmd_t stringCmd( command );
m_NetChannel->SendNetMsg( stringCmd );
if ( strstr( command, "disconnect" ) )
{
// When client requests a disconnect this is last moment to
// push any data into secure network, forcefully transmit netchannel
m_NetChannel->Transmit();
}
}
}
bool CBaseClientState::NETMsg_StringCmd( const CNETMsg_StringCmd& msg )
{
VPROF( "ProcessStringCmd" );
return InternalProcessStringCmd( msg );
}
bool CBaseClientState::InternalProcessStringCmd( const CNETMsg_StringCmd& msg )
{
const char *command = msg.command().c_str();
// Don't restrict commands from the server in single player or if cl_restrict_stuffed_commands is 0.
if ( !m_bRestrictServerCommands || sv.IsActive() )
{
Cbuf_AddText ( Cbuf_GetCurrentPlayer(), command, kCommandSrcCode ); // FIXME: Should this be kCommandSrcNetServer and we push the m_bRestrictServerCommands into the server check?
return true;
}
CCommand args;
args.Tokenize( command );
if ( args.ArgC() <= 0 )
return true;
// Run the command, but make sure the command parser knows to only execute commands marked with FCVAR_SERVER_CAN_EXECUTE.
Cbuf_AddText( Cbuf_GetCurrentPlayer(), command, kCommandSrcNetServer );
return true;
}
#ifndef DEDICATED
class CScaleformAvatarImageProviderImpl : public IScaleformAvatarImageProvider
{
public:
// Scaleform low-level image needs rgba bits of the inventory image (if it's ready)
virtual bool GetImageInfo( uint64 xuid, ImageInfo_t *pImageInfo ) OVERRIDE
{
CSteamID steamID( xuid );
if ( !steamID.IsValid() || !steamID.BIndividualAccount() || !steamID.GetAccountID() )
return false;
if ( !GetBaseLocalClient().IsConnected() )
return false;
// Find the player with the given account ID
CBaseClientState::PlayerAvatarDataMap_t const &data = GetBaseLocalClient().m_mapPlayerAvatarData;
CBaseClientState::PlayerAvatarDataMap_t::IndexType_t const idxData = data.Find( steamID.GetAccountID() );
if ( idxData == data.InvalidIndex() )
return false;
const CNETMsg_PlayerAvatarData& msg = *data.Element( idxData );
pImageInfo->m_cbImageData = msg.rgb().size();
pImageInfo->m_pvImageData = msg.rgb().data();
return true;
}
} g_CScaleformAvatarImageProviderImpl;
#endif
bool CBaseClientState::NETMsg_PlayerAvatarData( const CNETMsg_PlayerAvatarData& msg )
{
PlayerAvatarDataMap_t::IndexType_t idxData = m_mapPlayerAvatarData.Find( msg.accountid() );
if ( idxData != m_mapPlayerAvatarData.InvalidIndex() )
{
delete m_mapPlayerAvatarData.Element( idxData );
m_mapPlayerAvatarData.RemoveAt( idxData );
}
CNETMsg_PlayerAvatarData_t *pClientDataCopy = new CNETMsg_PlayerAvatarData_t;
pClientDataCopy->CopyFrom( msg );
m_mapPlayerAvatarData.Insert( pClientDataCopy->accountid(), pClientDataCopy );
#ifndef DEDICATED
if ( g_pScaleformUI )
g_pScaleformUI->AvatarImageReload( uint64( pClientDataCopy->accountid() ), &g_CScaleformAvatarImageProviderImpl );
#endif
return true;
}
CNETMsg_PlayerAvatarData_t * CBaseClientState::AllocOwnPlayerAvatarData() const
{
#ifndef DEDICATED
// If the game server is not GOTV then upload our own avatar data
extern ConVar sv_reliableavatardata;
if ( ( this == &GetBaseLocalClient() )
&& !GetBaseLocalClient().ishltv
&& m_NetChannel && IsConnected()
&& sv_reliableavatardata.GetBool() )
{
CSteamID steamID = Steam3Client().SteamUser()->GetSteamID();
int iAvatar = Steam3Client().SteamFriends()->GetMediumFriendAvatar( steamID );
if ( ( iAvatar != -1 ) && ( iAvatar != 0 ) )
{
uint32 wide = 0, tall = 0;
if ( Steam3Client().SteamUtils()->GetImageSize( iAvatar, &wide, &tall ) && ( wide == 64 ) && ( tall == 64 ) )
{
CUtlVector< byte > memAvatarData;
memAvatarData.SetCount( 64 * 64 * 4 );
memset( memAvatarData.Base(), 0xFF, memAvatarData.Count() );
Steam3Client().SteamUtils()->GetImageRGBA( iAvatar, memAvatarData.Base(), memAvatarData.Count() );
// trim alpha for size
for ( int y = 0; y < 64; ++y ) for ( int x = 0; x < 64; ++x )
{
V_memmove( memAvatarData.Base() + y * 64 * 3 + x * 3, memAvatarData.Base() + y * 64 * 4 + x * 4, 3 );
}
return new CNETMsg_PlayerAvatarData_t( steamID.GetAccountID(), memAvatarData.Base(), 64 * 64 * 3 );
}
}
// If we got here then we failed to obtain our own medium avatar, ping Steam rack and hope for an avatar changed callback
if ( !Steam3Client().SteamFriends()->RequestUserInformation( steamID, false ) )
{
static bool s_bReentrantGuard = false;
if ( !s_bReentrantGuard )
{ // Allow one retry here
s_bReentrantGuard = true;
return AllocOwnPlayerAvatarData();
s_bReentrantGuard = false;
}
}
}
#endif
return NULL;
}
bool CBaseClientState::NETMsg_SetConVar( const CNETMsg_SetConVar& msg )
{
VPROF( "ProcessSetConVar" );
// Never process on local client, since the ConVar is directly linked here
if ( m_NetChannel->IsLoopback() )
return true;
for ( int i=0; i<msg.convars().cvars_size(); i++ )
{
const char *name = NetMsgGetCVarUsingDictionary( msg.convars().cvars(i) );
const char *value = msg.convars().cvars(i).value().c_str();
// De-constify
ConVarRef var( name );
if ( !var.IsValid() )
{
ConMsg( "SetConVar: No such cvar ( %s set to %s), skipping\n",
name, value );
continue;
}
// Make sure server is only setting replicated game ConVars
if ( !var.IsFlagSet( FCVAR_REPLICATED ) )
{
ConMsg( "SetConVar: Can't set server cvar %s to %s, not marked as FCVAR_REPLICATED on client\n",
name, value );
continue;
}
// Set value directly ( don't call through cv->DirectSet!!! )
if ( !sv.IsActive() )
{
var.SetValue( value );
DevMsg( "SetConVar: %s = \"%s\"\n", name, value );
}
}
return true;
}
bool CBaseClientState::NETMsg_SignonState( const CNETMsg_SignonState& msg )
{
VPROF( "ProcessSignonState" );
return SetSignonState( msg.signon_state(), msg.spawn_count(), &msg ) ;
}
bool CBaseClientState::SVCMsg_Print( const CSVCMsg_Print& msg )
{
VPROF( "SVCMsg_Print" );
ConMsg( "%s", msg.text().c_str() );
return true;
}
bool CBaseClientState::SVCMsg_Menu( const CSVCMsg_Menu& msg )
{
VPROF( "SVCMsg_Menu" );
#if !defined(DEDICATED)
PluginHelpers_Menu( msg );
#endif
return true;
}
bool CBaseClientState::SVCMsg_ServerInfo( const CSVCMsg_ServerInfo& msg )
{
VPROF( "SVCMsg_ServerInfo" );
#ifndef DEDICATED
EngineVGui()->UpdateProgressBar(PROGRESS_PROCESSSERVERINFO);
#endif
COM_TimestampedLog( " CBaseClient::SVCMsg_ServerInfo" );
if ( msg.protocol() != GetHostVersion() )
{
#if !defined( DEDICATED )
if ( demoplayer && demoplayer->IsPlayingBack() )
{
ConMsg ( "WARNING: Server demo version %i, client version %i.\n", msg.protocol(), GetHostVersion() );
}
else
#endif
{
ConMsg ( "WARNING: Server version %i, client version %i.\n", msg.protocol(), GetHostVersion() );
}
}
// Parse servercount (i.e., # of servers spawned since server .exe started)
// So that we can detect new server startup during download, etc.
m_nServerCount = msg.server_count();
m_nMaxClients = msg.max_clients();
m_nServerClasses = msg.max_classes();
m_nServerClassBits = Q_log2( m_nServerClasses ) + 1;
if ( m_nMaxClients < 1 || m_nMaxClients > ABSOLUTE_PLAYER_LIMIT )
{
ConMsg ("Bad maxclients (%u) from server.\n", m_nMaxClients);
return false;
}
if ( m_nServerClasses < 1 || m_nServerClasses > MAX_SERVER_CLASSES )
{
ConMsg ("Bad maxclasses (%u) from server.\n", m_nServerClasses);
return false;
}
#ifndef DEDICATED
if ( !sv.IsActive() &&
( s_ClientBroadcastPlayer.IsPlayingBack() || // /*( demoplayer && demoplayer->IsPlayingBack() )*/
!( m_NetChannel->IsLoopback() || m_NetChannel->IsNull() || m_NetChannel->GetRemoteAddress().IsLocalhost() )
)
)
{
// reset server enforced cvars
g_pCVar->RevertFlaggedConVars( FCVAR_REPLICATED );
extern void RevertAllModifiedLocalState();
RevertAllModifiedLocalState();
}
#endif
// clear all baselines still around from last game
FreeEntityBaselines();
// force changed flag to being reset
g_GameEventManager.HasClientListenersChanged( true );
#ifndef DEDICATED
splitscreen->AddBaseUser( 0, msg.player_slot() + 1 );
#if defined( INCLUDE_SCALEFORM )
if ( g_pScaleformUI )
{
extern IScaleformSlotInitController *g_pIScaleformSlotInitControllerEngineImpl;
g_pScaleformUI->InitSlot( SF_SS_SLOT( 0 ), g_szDefaultScaleformClientMovieName, g_pIScaleformSlotInitControllerEngineImpl );
}
#endif
#endif
m_nPlayerSlot = msg.player_slot();
m_nViewEntity = msg.player_slot() + 1;
if ( msg.tick_interval() < MINIMUM_TICK_INTERVAL ||
msg.tick_interval() > MAXIMUM_TICK_INTERVAL )
{
ConMsg ("Interval_per_tick %f out of range [%f to %f]\n",
msg.tick_interval(), MINIMUM_TICK_INTERVAL, MAXIMUM_TICK_INTERVAL );
return false;
}
if ( !COM_CheckGameDirectory( msg.game_dir().c_str() ) )
{
return false;
}
Q_snprintf( m_szLevelName, sizeof( m_szLevelName ), "maps/%s%s.bsp", msg.map_name().c_str(), GetPlatformExt() );
Q_FixSlashes( m_szLevelName );
Q_strncpy( m_szLevelNameShort, msg.map_name().c_str(), sizeof( m_szLevelNameShort ) );
Q_strncpy( m_szMapGroupName, msg.map_group_name().c_str(), sizeof( m_szMapGroupName ) );
m_unUGCMapFileID = msg.ugc_map_id();
#if !defined(DEDICATED)
EngineVGui()->SetProgressLevelName( m_szLevelNameShort );
audiosourcecache->LevelInit( m_szLevelNameShort );
#endif
ConVarRef skyname( "sv_skyname" );
if ( skyname.IsValid() )
{
skyname.SetValue( msg.sky_name().c_str() );
}
m_nDeltaTick = -1; // no valid snapshot for this game yet
// fire a client side event about server data
IGameEvent *pEvent = g_GameEventManager.CreateEvent( "server_spawn" );
if ( pEvent )
{
pEvent->SetString( "hostname", msg.host_name().c_str() );
ns_address adr = m_NetChannel->GetRemoteAddress();
if ( adr.IsType<netadr_t>() )
{
pEvent->SetString( "address", CUtlNetAdrRender( adr.AsType<netadr_t>(), true ).String() );
pEvent->SetInt( "port", adr.GetPort() );
}
else
{
pEvent->SetString( "address", ns_address_render( adr ).String() );
}
pEvent->SetString( "game", msg.game_dir().c_str() );
pEvent->SetString( "mapname", msg.map_name().c_str() );
pEvent->SetInt( "maxplayers", msg.max_clients() );
pEvent->SetInt( "password", 0 ); // TODO
pEvent->SetString( "os", Q_strupr( va("%c", msg.c_os() ) ) );
pEvent->SetBool( "dedicated", msg.is_dedicated() );
pEvent->SetBool( "official", msg.is_official_valve_server() );
if ( m_ulGameServerSteamID != 0 )
{
pEvent->SetString( "steamid", CSteamID( m_ulGameServerSteamID ).Render() );
}
g_GameEventManager.FireEventClientSide( pEvent );
}
// Verify that the client doesn't play on the server with mismatching version
if ( m_nServerProtocolVersion && ( GetHostVersion() > m_nServerProtocolVersion ) )
{
if ( !msg.is_hltv() && !msg.is_redirecting_to_proxy_relay() )
{
// Newer client attempts to play on an older server which is not GOTV, bail here
Warning( "Failed to connect to a gameserver, client version %d, server version %d\n", GetHostVersion(), m_nServerProtocolVersion );
Disconnect();
return false;
}
}
// must be set BEFORE loading bsp which indicates above dependent code has completed
m_bServerInfoProcessed = true;
m_nServerInfoMsgProtocol = msg.protocol();
if ( !sv.IsActive() && ( !msg.is_hltv() || !msg.is_redirecting_to_proxy_relay() ) )
{
// For a non-local connection, the bsp needs to be loaded first, BEFORE any server string table
// callbacks occur. These occur after this function and before the SIGNONSTATE_NEW,
// causing unexpected out of order issues. The server material string table of precached
// materials has bsp dependencies (i.e. due to cubemap patching) so bsp load must be first.
// This also lets (fixes) the queued loader batch fast load the resources instead of the callbacks
// loading them via the slower synchronous method.
if ( IsGameConsole() && g_pQueuedLoader->IsMapLoading() )
{
Msg( "New CSVCMsg_ServerInfo message - loading map %s. Forcing current map load to end.\n", msg.map_name().c_str() );
g_pQueuedLoader->EndMapLoading( true );
}
SV_CheckForFlushMemory( m_szLastLevelNameShort, m_szLevelNameShort );
SV_FlushMemoryIfMarked();
// A map is about to be loaded into memory
HostState_Pre_LoadMapIntoMemory();
// CSGO custom map detection
bool bClientHasMap = true;
bool bIsRelay = false;
for ( CHltvServerIterator hltv; hltv; hltv.Next() )
{
if ( hltv->IsTVRelay() )
{
bIsRelay = true;
break;
}
}
if ( !bIsRelay ) // not a single one of the hltv servers is relay
{
char bspModelName[ MAX_PATH ];
Q_snprintf( bspModelName, sizeof( bspModelName ), "maps/%s.bsp", msg.map_name().c_str() );
#if !defined( _GAMECONSOLE ) && !defined( DEDICATED )
bool bCrcClientMapValid = false;
CRC32_t crcClientMap;
CRC32_Init( &crcClientMap );
// Compute CRC of client map on disk
{
FileHandle_t mapfile = g_pFileSystem->OpenEx( bspModelName, "rb", IsGameConsole() ? FSOPEN_NEVERINPACK : 0, "GAME" );
if ( mapfile != FILESYSTEM_INVALID_HANDLE )
{
g_pFileSystem->Close( mapfile );
bCrcClientMapValid = CRC_MapFile( &crcClientMap, bspModelName );
}
}
if ( demoplayer && demoplayer->IsPlayingBack() )
{
// We are playing a demo
// Can we use the map on disk directly?
if ( bCrcClientMapValid && msg.map_crc() && ( crcClientMap == msg.map_crc() ) )
{
// Seems like everything looks good on disk and we can proceed with map on disk
}
else
{
// We are trying to playback a demo, but CRC of the client map doesn't match
// the CRC of the server map.
// There are some known official maps that we can redirect into proper version
// on Steam Workshop.
char chMapName[ MAX_PATH ] = { 0 };
V_sprintf_safe( chMapName, "%s", msg.map_name().c_str() );
// Check if it's Valve official Workshop symbolic link
if ( char const *szPastWorkshop = StringAfterPrefix( chMapName, "workshop/" ) )
{
//uint64 uiWkshpId = Q_atoui64( szPastWorkshop );
bool bValveOfficialMap = false;
/** Removed for partner depot **/
if ( bValveOfficialMap )
{
if ( char const *szMapNameTrail = strchr( szPastWorkshop, '/' ) )
Q_memmove( chMapName, szMapNameTrail + 1, chMapName + Q_ARRAYSIZE( chMapName ) - szMapNameTrail - 1 );
}
}
// Now that workshop symlink has been resolved, verify whether
// it is one of Valve official maps and redirect to Workshop based on known CRC
uint64 uiKnownVersionWkshpId = 0;
uint32 uiRepackedWkshpCrc = 0;
/** Removed for partner depot **/
extern ConVar debug_map_crc;
if ( debug_map_crc.GetBool() && !uiKnownVersionWkshpId )
{ // Force a debug error when debugging map CRC's
Warning( "debug_map_crc: Map version mismatch for %s CRC=%u, no fallback version specified in csgo_official_map_versions!\n",
chMapName, msg.map_crc() );
}
if ( uiKnownVersionWkshpId )
{ // We have a fallback version specified for the CRC mismatch
Msg( "Map version fallback for %s CRC=%u: %llu (CRC=%u)\n", chMapName, msg.map_crc(), uiKnownVersionWkshpId, uiRepackedWkshpCrc );
Q_snprintf( m_szLevelName, sizeof( m_szLevelName ), "maps/workshop/%llu/%s.bsp", uiKnownVersionWkshpId, chMapName );
Q_FixSlashes( m_szLevelName );
Q_snprintf( m_szLevelNameShort, sizeof( m_szLevelNameShort ), "workshop/%llu/%s", uiKnownVersionWkshpId, chMapName );
Q_snprintf( bspModelName, sizeof( bspModelName ), "maps/workshop/%llu/%s.bsp", uiKnownVersionWkshpId, chMapName );
m_unUGCMapFileID = uiKnownVersionWkshpId;
// Dirty method: patch in a different map crc for derived class processing to pick up repacked crc value
const_cast< CSVCMsg_ServerInfo & >( msg ).set_map_crc( uiRepackedWkshpCrc );
// Check if we already have the workshop file for the fallback version downloaded
FileHandle_t mapfile = g_pFileSystem->OpenEx( bspModelName, "rb", IsGameConsole() ? FSOPEN_NEVERINPACK : 0, "GAME" );
if ( mapfile != FILESYSTEM_INVALID_HANDLE )
{
g_pFileSystem->Close( mapfile );
bCrcClientMapValid = true; // prevent CRC from resetting bClientHasMap further down in the code, compat versions never change so chances are the map is good right off the bat
}
else
bClientHasMap = false;
}
}
}
else
{
// We are not playing a demo
if ( bCrcClientMapValid && msg.map_crc() && ( crcClientMap != msg.map_crc() ) && ( m_unUGCMapFileID != 0 ) )
bClientHasMap = false; // If the crc doesn't match servers delay map load until after downloading new version from the workshop
}
if ( !bCrcClientMapValid )
bClientHasMap = false;
#endif
if ( bClientHasMap )
modelloader->GetModelForName( bspModelName, IModelLoader::FMODELLOADER_CLIENT );
} // not a relay
// If we connect to a dedicated server, we need to load up the dictionary file
CRC32_t crc = CRC32_ConvertFromUnsignedLong( msg.string_table_crc() );
if ( !g_pStringTableDictionary->OnLevelLoadStart( bClientHasMap ? m_szLevelNameShort : NULL, &crc ) )
{
// Allow us to continue with a mismatch string table
// this can occur with slighty different versisons
Warning( "***String table CRC mismatch, may need to rebuild bsp if model oddities occur!\n" );
}
// Client needs an opportunity to write all profile information
g_pMatchFramework->GetEventsSubscription()->BroadcastEvent( new KeyValues(
"OnProfilesWriteOpportunity", "reason", "checkpoint"
) );
}
COM_TimestampedLog( "CBaseClient::ProcessServerInfo(done)" );
return true;
}
bool CBaseClientState::SVCMsg_SendTable( const CSVCMsg_SendTable& msg )
{
VPROF( "SVCMsg_SendTable" );
if ( !RecvTable_RecvClassInfos( msg ) )
{
Host_EndGame(true, "ProcessSendTable: RecvTable_RecvClassInfos failed.\n" );
return false;
}
return true;
}
bool CBaseClientState::SVCMsg_ClassInfo( const CSVCMsg_ClassInfo& msg )
{
VPROF( "SVCMsg_ClassInfo" );
COM_TimestampedLog( " CBaseClient::SVCMsg_ClassInfo" );
if ( msg.create_on_client() )
{
ConMsg ( "Can't create class tables.\n");
Assert( 0 );
return false;
}
if( m_pServerClasses )
{
delete [] m_pServerClasses;
}
m_nServerClasses = msg.classes_size();
m_pServerClasses = new C_ServerClassInfo[ m_nServerClasses ];
if ( !m_pServerClasses )
{
Host_EndGame(true, "SVCMsg_ClassInfo: can't allocate %d C_ServerClassInfos.\n", m_nServerClasses);
return false;
}
// copy class names and class IDs from message to CClientState
for (int i=0; i<m_nServerClasses; i++)
{
const CSVCMsg_ClassInfo::class_t& svclass = msg.classes( i );
if( svclass.class_id() >= m_nServerClasses )
{
Host_EndGame(true, "SVCMsg_ClassInfo: invalid class index (%d).\n", svclass.class_id());
return false;
}
C_ServerClassInfo * svclassinfo = &m_pServerClasses[svclass.class_id()];
int len = Q_strlen(svclass.class_name().c_str()) + 1;
svclassinfo->m_ClassName = new char[ len ];
Q_strncpy( svclassinfo->m_ClassName, svclass.class_name().c_str(), len );
len = Q_strlen(svclass.data_table_name().c_str()) + 1;
svclassinfo->m_DatatableName = new char[ len ];
Q_strncpy( svclassinfo->m_DatatableName,svclass.data_table_name().c_str(), len );
}
COM_TimestampedLog( " CBaseClient::SVCMsg_ClassInfo(done)" );
return LinkClasses(); // link server and client classes
}
bool CBaseClientState::SVCMsg_SetPause( const CSVCMsg_SetPause& msg )
{
VPROF( "SVCMsg_SetPause" );
m_bPaused = msg.paused();
return true;
}
bool CBaseClientState::SVCMsg_CreateStringTable( const CSVCMsg_CreateStringTable &msg )
{
VPROF( "SVCMsg_CreateStringTable" );
#ifndef DEDICATED
EngineVGui()->UpdateProgressBar(PROGRESS_PROCESSSTRINGTABLE);
#endif
COM_TimestampedLog( " CBaseClient::ProcessCreateStringTable(%s)", msg.name().c_str() );
m_StringTableContainer->AllowCreation( true );
#ifndef SHARED_NET_STRING_TABLES
CNetworkStringTable *table = (CNetworkStringTable*)
m_StringTableContainer->CreateStringTable( msg.name().c_str(), msg.max_entries(), msg.user_data_size(), msg.user_data_size_bits(), msg.flags() );
Assert ( table );
table->SetTick( GetServerTickCount() ); // set creation tick
HookClientStringTable( msg.name().c_str() );
bf_read data( &msg.string_data()[0], msg.string_data().size() );
table->ParseUpdate( data, msg.num_entries() );
#endif
m_StringTableContainer->AllowCreation( false );
COM_TimestampedLog( " CBaseClient::ProcessCreateStringTable(%s)-done", msg.name().c_str() );
return true;
}
bool CBaseClientState::SVCMsg_UpdateStringTable( const CSVCMsg_UpdateStringTable &msg )
{
VPROF( "ProcessUpdateStringTable" );
#ifndef SHARED_NET_STRING_TABLES
//m_StringTableContainer is NULL on level transitions, Seems to be caused by a UpdateStringTable packet comming in before the ServerInfo packet
// I'm not sure this is safe, but at least we won't crash. The realy odd thing is this can happen on the server as well.//tmauer
if(m_StringTableContainer != NULL)
{
CNetworkStringTable *table = (CNetworkStringTable*)
m_StringTableContainer->GetTable( msg.table_id() );
bf_read data( &msg.string_data()[0], msg.string_data().size() );
table->ParseUpdate( data, msg.num_changed_entries() );
}
else
{
Warning("m_StringTableContainer is NULL in CBaseClientState::ProcessUpdateStringTable\n");
}
#endif
return true;
}
bool CBaseClientState::SVCMsg_SetView( const CSVCMsg_SetView& msg )
{
#if !defined( LINUX )
// dkorus: may need this for online split screen
ASSERT_LOCAL_PLAYER_RESOLVABLE();
#endif
m_nViewEntity = msg.entity_index();
return true;
}
bool CBaseClientState::SVCMsg_PacketEntities( const CSVCMsg_PacketEntities &msg )
{
VPROF( "ProcessPacketEntities" );
// First update is the final signon stage where we actually receive an entity (i.e., the world at least)
if ( m_nSignonState < SIGNONSTATE_SPAWN )
{
ConMsg("Received packet entities while connecting!\n");
return false;
}
if ( m_nSignonState == SIGNONSTATE_SPAWN )
{
if ( !msg.is_delta() )
{
// We are done with signon sequence.
SetSignonState( SIGNONSTATE_FULL, m_nServerCount, NULL );
}
else
{
ConMsg("Received delta packet entities while spawing!\n");
return false;
}
}
// overwrite a -1 delta_tick only if packet was uncompressed
if ( (m_nDeltaTick >= 0) || !msg.is_delta() )
{
// we received this snapshot successfully, now this is our delta reference
m_nDeltaTick = GetServerTickCount();
}
return true;
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : *pHead -
// *pClassName -
// Output : static ClientClass*
//-----------------------------------------------------------------------------
ClientClass* CBaseClientState::FindClientClass(const char *pClassName)
{
for(ClientClass *pCur=ClientDLL_GetAllClasses(); pCur; pCur=pCur->m_pNext)
{
if( Q_stricmp(pCur->m_pNetworkName, pClassName) == 0)
return pCur;
}
return NULL;
}
bool CBaseClientState::LinkClasses()
{
// Verify that we have received info about all classes.
for ( int i=0; i < m_nServerClasses; i++ )
{
if ( !m_pServerClasses[i].m_DatatableName )
{
Host_EndGame(true, "CL_ParseClassInfo_EndClasses: class %d not initialized.\n", i);
return false;
}
}
// Match the server classes to the client classes.
for ( int i=0; i < m_nServerClasses; i++ )
{
C_ServerClassInfo *pServerClass = &m_pServerClasses[i];
// (this can be null in which case we just use default behavior).
pServerClass->m_pClientClass = FindClientClass(pServerClass->m_ClassName);
if ( pServerClass->m_pClientClass )
{
// If the class names match, then their datatables must match too.
// It's ok if the client is missing a class that the server has. In that case,
// if the server actually tries to use it, the client will bomb out.
const char *pServerName = pServerClass->m_DatatableName;
const char *pClientName = pServerClass->m_pClientClass->m_pRecvTable->GetName();
if ( Q_stricmp( pServerName, pClientName ) != 0 )
{
Host_EndGame( true, "CL_ParseClassInfo_EndClasses: server and client classes for '%s' use different datatables (server: %s, client: %s)",
pServerClass->m_ClassName, pServerName, pClientName );
return false;
}
// copy class ID
pServerClass->m_pClientClass->m_ClassID = i;
}
else
{
Msg( "Client missing DT class %s\n", pServerClass->m_ClassName );
}
}
return true;
}
PackedEntity *CBaseClientState::GetEntityBaseline(int iBaseline, int nEntityIndex)
{
Assert( (iBaseline == 0) || (iBaseline == 1) );
return m_pEntityBaselines[iBaseline][nEntityIndex];
}
void CBaseClientState::FreeEntityBaselines()
{
for ( int i=0; i<2; i++ )
{
for ( int j=0; j<MAX_EDICTS; j++ )
if ( m_pEntityBaselines[i][j] )
{
delete m_pEntityBaselines[i][j];
m_pEntityBaselines[i][j] = NULL;
}
}
}
void CBaseClientState::SetEntityBaseline(int iBaseline, ClientClass *pClientClass, int index, SerializedEntityHandle_t handle)
{
Assert( index >= 0 && index < MAX_EDICTS );
Assert( pClientClass );
Assert( (iBaseline == 0) || (iBaseline == 1) );
PackedEntity *entitybl = m_pEntityBaselines[iBaseline][index];
if ( !entitybl )
{
entitybl = m_pEntityBaselines[iBaseline][index] = new PackedEntity();
}
entitybl->m_pClientClass = pClientClass;
entitybl->m_nEntityIndex = index;
entitybl->m_pServerClass = NULL;
// Copy out the data we just decoded.
entitybl->SetPackedData( handle );
}
void CBaseClientState::CopyEntityBaseline( int iFrom, int iTo )
{
Assert ( iFrom != iTo );
for ( int i=0; i<MAX_EDICTS; i++ )
{
PackedEntity *blfrom = m_pEntityBaselines[iFrom][i];
PackedEntity *blto = m_pEntityBaselines[iTo][i];
if( !blfrom )
{
// make sure blto doesn't exists
if ( blto )
{
// ups, we already had this entity but our ack got lost
// we have to remove it again to stay in sync
delete m_pEntityBaselines[iTo][i];
m_pEntityBaselines[iTo][i] = NULL;
}
continue;
}
if ( !blto )
{
// create new to baseline if none existed before
blto = m_pEntityBaselines[iTo][i] = new PackedEntity();
blto->m_pClientClass = NULL;
blto->m_pServerClass = NULL;
blto->m_ReferenceCount = 0;
}
Assert( blfrom->m_nEntityIndex == i );
blto->m_nEntityIndex = blfrom->m_nEntityIndex;
blto->m_pClientClass = blfrom->m_pClientClass;
blto->m_pServerClass = blfrom->m_pServerClass;
blto->CopyPackedData( blfrom->GetPackedData() );
}
}
ClientClass *CBaseClientState::GetClientClass( int index )
{
Assert( index < m_nServerClasses );
return m_pServerClasses[index].m_pClientClass;
}
void CBaseClientState::UpdateInstanceBaseline( int nStringNumber )
{
int nSlot = m_BaselineHandles.Find( nStringNumber );
if ( nSlot != m_BaselineHandles.InvalidIndex() )
{
// Release old
g_pSerializedEntities->ReleaseSerializedEntity( m_BaselineHandles[ nSlot ] );
m_BaselineHandles[ nSlot ] = SERIALIZED_ENTITY_HANDLE_INVALID;
}
else
{
m_BaselineHandles.Insert( nStringNumber, SERIALIZED_ENTITY_HANDLE_INVALID );
}
}
bool CBaseClientState::GetClassBaseline( int iClass, SerializedEntityHandle_t *pHandle )
{
ErrorIfNot(
iClass >= 0 && iClass < m_nServerClasses,
("GetDynamicBaseline: invalid class index '%d'", iClass) );
// We lazily update these because if you connect to a server that's already got some dynamic baselines,
// you'll get the baselines BEFORE you get the class descriptions.
C_ServerClassInfo *pInfo = &m_pServerClasses[iClass];
INetworkStringTable *pBaselineTable = GetStringTable( INSTANCE_BASELINE_TABLENAME );
ErrorIfNot( pBaselineTable != NULL, ("GetDynamicBaseline: NULL baseline table" ) );
if ( pInfo->m_InstanceBaselineIndex == INVALID_STRING_INDEX )
{
// The key is the class index string.
char str[64];
Q_snprintf( str, sizeof( str ), "%d", iClass );
pInfo->m_InstanceBaselineIndex = pBaselineTable->FindStringIndex( str );
ErrorIfNot(
pInfo->m_InstanceBaselineIndex != INVALID_STRING_INDEX,
("GetDynamicBaseline: FindStringIndex(%s-%s) failed.", str, pInfo->m_ClassName );
);
}
int slot = m_BaselineHandles.Find( pInfo->m_InstanceBaselineIndex );
Assert( slot != m_BaselineHandles.InvalidIndex() );
*pHandle = m_BaselineHandles[ slot ];
if ( *pHandle == SERIALIZED_ENTITY_HANDLE_INVALID )
{
SerializedEntityHandle_t handle = g_pSerializedEntities->AllocateSerializedEntity( __FILE__, __LINE__ );
int nLength = 0;
const void *pData = pBaselineTable->GetStringUserData( pInfo->m_InstanceBaselineIndex, &nLength );
bf_read readbuf( "UpdateInstanceBaseline", pData, nLength );
RecvTable_ReadFieldList( pInfo->m_pClientClass->m_pRecvTable, readbuf, handle, -1, false );
*pHandle = m_BaselineHandles[ slot ] = handle;
}
return *pHandle != SERIALIZED_ENTITY_HANDLE_INVALID;
}
bool CBaseClientState::SVCMsg_GameEventList( const CSVCMsg_GameEventList& msg )
{
VPROF( "SVCMsg_GameEventList" );
return g_GameEventManager.ParseEventList( msg );
}
bool CBaseClientState::SVCMsg_GetCvarValue( const CSVCMsg_GetCvarValue& msg )
{
VPROF( "SVCMsg_GetCvarValue" );
// Prepare the response.
CCLCMsg_RespondCvarValue_t returnMsg;
returnMsg.set_cookie( msg.cookie() );
returnMsg.set_name( msg.cvar_name().c_str() );
returnMsg.set_value( "" );
returnMsg.set_status_code( eQueryCvarValueStatus_CvarNotFound );
char tempValue[256];
// Does any ConCommand exist with this name?
const ConVar *pVar = g_pCVar->FindVar( msg.cvar_name().c_str() );
if ( pVar )
{
if ( pVar->IsFlagSet( FCVAR_SERVER_CANNOT_QUERY ) )
{
// The server isn't allowed to query this.
returnMsg.set_status_code( eQueryCvarValueStatus_CvarProtected );
}
else
{
returnMsg.set_status_code( eQueryCvarValueStatus_ValueIntact );
if ( pVar->IsFlagSet( FCVAR_NEVER_AS_STRING ) )
{
// The cvar won't store a string, so we have to come up with a string for it ourselves.
if ( fabs( pVar->GetFloat() - pVar->GetInt() ) < 0.001f )
{
Q_snprintf( tempValue, sizeof( tempValue ), "%d", pVar->GetInt() );
}
else
{
Q_snprintf( tempValue, sizeof( tempValue ), "%f", pVar->GetFloat() );
}
returnMsg.set_value( tempValue );
}
else
{
// The easy case..
returnMsg.set_value( pVar->GetString() );
}
}
}
else
{
if ( g_pCVar->FindCommand( msg.cvar_name().c_str() ) )
returnMsg.set_status_code( eQueryCvarValueStatus_NotACvar ); // It's a command, not a cvar.
else
returnMsg.set_status_code( eQueryCvarValueStatus_CvarNotFound );
}
// Send back.
m_NetChannel->SendNetMsg( returnMsg );
return true;
}
bool CBaseClientState::SVCMsg_SplitScreen( const CSVCMsg_SplitScreen& msg )
{
#ifndef DEDICATED
switch ( msg.type() )
{
default:
Assert( 0 );
break;
case MSG_SPLITSCREEN_ADDUSER:
{
splitscreen->AddSplitScreenUser( msg.slot(), msg.player_index() );
#if defined( INCLUDE_SCALEFORM )
extern IScaleformSlotInitController *g_pIScaleformSlotInitControllerEngineImpl;
g_pScaleformUI->InitSlot( SF_SS_SLOT( msg.slot() ), g_szDefaultScaleformClientMovieName, g_pIScaleformSlotInitControllerEngineImpl );
#endif
}
break;
case MSG_SPLITSCREEN_REMOVEUSER:
{
splitscreen->RemoveSplitScreenUser( msg.slot(), msg.player_index() );
#if defined( INCLUDE_SCALEFORM )
g_pScaleformUI->SlotRelease( SF_SS_SLOT( msg.slot() ) );
#endif
}
break;
}
#endif
return true;
}
bool CBaseClientState::ChangeSplitscreenUser( int nSplitScreenUserSlot )
{
#ifndef DEDICATED
Assert( splitscreen->IsValidSplitScreenSlot( nSplitScreenUserSlot ) );
if ( !splitscreen->IsValidSplitScreenSlot( nSplitScreenUserSlot ) )
return true;
// Msg( "Networking changing slot to %d\n", msg->m_nSlot );
splitscreen->SetActiveSplitScreenPlayerSlot( nSplitScreenUserSlot );
#endif
return true;
}
bool CBaseClientState::SVCMsg_CmdKeyValues( const CSVCMsg_CmdKeyValues& msg )
{
#ifndef DEDICATED
KeyValues *pMsgKeyValues = CmdKeyValuesHelper::SVCMsg_GetKeyValues( msg );
KeyValues::AutoDelete autodelete_pMsgKeyValues( pMsgKeyValues );
char const *szName = pMsgKeyValues->GetName();
if ( !V_strcmp( szName, "dsp_player" ) )
{
extern void dsp_player_set( int val );
dsp_player_set( pMsgKeyValues->GetInt() );
return true;
}
KeyValues *pEvent = new KeyValues( "Client::CmdKeyValues" );
pEvent->AddSubKey( autodelete_pMsgKeyValues.Detach() );
pEvent->SetInt( "slot", m_nSplitScreenSlot );
g_pMatchFramework->GetEventsSubscription()->BroadcastEvent( pEvent );
#endif
return true;
}
bool CBaseClientState::SVCMsg_EncryptedData( const CSVCMsg_EncryptedData& msg )
{
#ifndef DEDICATED
// Decrypt the message and process embedded data
char const *szKey = "";
switch ( msg.key_type() )
{
case kEncryptedMessageKeyType_Private:
szKey = cl_decryptdata_key.GetString();
break;
case kEncryptedMessageKeyType_Public:
szKey = cl_decryptdata_key_pub.GetString();
break;
}
return CmdEncryptedDataMessageCodec::SVCMsg_EncryptedData_Process( msg, m_NetChannel, szKey );
#else
return true;
#endif
}
int CBaseClientState::GetViewEntity()
{
return m_nViewEntity;
}
bool CBaseClientState::ShouldUseDirectConnectAddress( const CAddressList &list ) const
{
// Expired?
if ( realtime > m_DirectConnectLobby.m_flEndTime )
return false;
// No server IP?
if ( m_DirectConnectLobby.m_adrRemote.IsType<netadr_t>() && !m_DirectConnectLobby.m_adrRemote.AsType<netadr_t>().GetIPHostByteOrder() )
return false;
// Either joining unreserved server or same lobby ID
if ( m_DirectConnectLobby.m_unLobbyID != 0ull && m_DirectConnectLobby.m_unLobbyID != m_nServerReservationCookie )
return false;
// Already in list
if ( list.IsAddressInList( m_DirectConnectLobby.m_adrRemote ) )
return false;
return true;
}
uint64 CBaseClientState::CAsyncOperation_ReserveServer::GetResult() {
if ( m_eState != AOS_SUCCEEDED )
return 0;
static char buf[64];
V_strcpy_safe( buf, ns_address_render( m_adr ).String() );
return reinterpret_cast<uintp>( buf );
}
//-----------------------------------------------------------------------------
// Purpose: Sends a message to game server to reserve it for members of our
// lobby for a short time to ensure everyone in lobby will be able to join.
// Server will send response accepting or denying reservation. It will only
// accept if it is empty and unreserved.
//-----------------------------------------------------------------------------
void CBaseClientState::ReserveServer( const ns_address &netAdrPublic, const ns_address &netAdrPrivate, uint64 nServerReservationCookie,
KeyValues *pKVGameSettings, IMatchAsyncOperationCallback *pCallback, IMatchAsyncOperation **ppAsyncOperation )
{
NET_SetMultiplayer( true );
// Should not already have a reservation in progress -- should only attempt one reservation at a time
Assert( !m_pServerReservationCallback );
if ( m_pServerReservationCallback )
{
ReservationResponseReply_t reply;
reply.m_adrFrom = m_netadrReserveServer.Get( 0 ).m_adrRemote;
HandleReservationResponse( reply );
}
if ( ppAsyncOperation )
{
m_pServerReservationOperation = new CAsyncOperation_ReserveServer( this );
*ppAsyncOperation = m_pServerReservationOperation;
}
m_pServerReservationCallback = pCallback;
m_nServerReservationCookie = nServerReservationCookie;
m_pKVGameSettings = pKVGameSettings->MakeCopy();
m_flReservationMsgSendTime = FLT_MIN;
m_nReservationMsgRetryNumber = 0;
m_bEnteredPassword = false;
m_netadrReserveServer.RemoveAll();
m_netadrReserveServer.AddRemote( ns_address_render( netAdrPublic ).String(), "public" );
if ( !netAdrPrivate.IsNull() ) // if we have a valid private address specified
m_netadrReserveServer.AddRemote( ns_address_render( netAdrPrivate ).String(), "private" );
if ( ShouldUseDirectConnectAddress( m_netadrReserveServer ) )
{
ConColorMsg( Color( 0, 255, 0, 255 ), "Adding direct connect address to reservation %s\n", ns_address_render( m_DirectConnectLobby.m_adrRemote ).String() );
m_netadrReserveServer.AddRemote( ns_address_render( m_DirectConnectLobby.m_adrRemote ).String(), "direct" );
}
// send the reservation message
SendReserveServerMsg();
}
bool CBaseClientState::CheckServerReservation( const ns_address &netAdrPublic, uint64 nServerReservationCookie, uint32 uiReservationStage,
IMatchAsyncOperationCallback *pCallback, IMatchAsyncOperation **ppAsyncOperation )
{
Assert( ppAsyncOperation );
if ( !ppAsyncOperation )
return false;
NET_SetMultiplayer( true );
CServerMsg_CheckReservation *pSvReservationCheck = new CServerMsg_CheckReservation( this, pCallback, netAdrPublic,
m_Socket, nServerReservationCookie, uiReservationStage );
*ppAsyncOperation = pSvReservationCheck;
m_arrSvReservationCheck.AddToTail( pSvReservationCheck );
return true;
}
bool CBaseClientState::ServerPing( const ns_address &netAdrPublic,
IMatchAsyncOperationCallback *pCallback, IMatchAsyncOperation **ppAsyncOperation )
{
Assert( ppAsyncOperation );
if ( !ppAsyncOperation )
return false;
NET_SetMultiplayer( true );
CServerMsg_Ping *pSvPing = new CServerMsg_Ping( this, pCallback, netAdrPublic, m_Socket );
*ppAsyncOperation = pSvPing;
m_arrSvPing.AddToTail( pSvPing );
return true;
}
//-----------------------------------------------------------------------------
// Purpose: Handles response from game server to reservation request
//-----------------------------------------------------------------------------
void CBaseClientState::HandleReservationResponse( const ReservationResponseReply_t &reply )
{
// Is this the address we expect? (It might not if this is a very delayed response
// from an earlier game server we tried to reserve on and subsequently gave up on.)
bool bAddressMatches = ( reply.m_adrFrom.IsLoopback() || m_netadrReserveServer.IsAddressInList( reply.m_adrFrom ) );
if ( !bAddressMatches )
return;
IMatchAsyncOperationCallback *pCallback = m_pServerReservationCallback;
if ( reply.m_uiResponse != 2 )
{
m_pServerReservationCallback = NULL;
if ( m_pKVGameSettings )
{
m_pKVGameSettings->deleteThis();
m_pKVGameSettings = NULL;
}
}
if ( !pCallback )
return;
// If we are expecting a response (have a callback to call) and the address matches what
// we expect, call the callback then clear it.
if ( m_pServerReservationOperation )
{
if ( reply.m_uiResponse == 2 )
{
// Reservation pending response, reset retry counter
m_nReservationMsgRetryNumber = 0;
DevMsg( "[MM] Server %s reservation pending response waiting...\n", ns_address_render( reply.m_adrFrom ).String() );
return;
}
m_pServerReservationOperation->m_eState = reply.m_uiResponse ? AOS_SUCCEEDED : AOS_FAILED;
m_pServerReservationOperation->m_adr = reply.m_adrFrom;
m_pServerReservationOperation->m_numGameSlotsForReservation = reply.m_numGameSlots;
}
pCallback->OnOperationFinished( m_pServerReservationOperation );
}
//-----------------------------------------------------------------------------
// Purpose: Resend a server reservation request if necessary
//-----------------------------------------------------------------------------
void CBaseClientState::CheckForReservationResend()
{
const float RESERVATION_RESEND_INTERVAL=3.0f;
const float MAX_RESERVATION_RETRIES=2;
if ( m_bWaitingForPassword )
return;
// do we have a reservation in progress?
if ( !m_pServerReservationCallback )
return;
// is it time to resend?
if ( ( net_time - m_flReservationMsgSendTime ) < RESERVATION_RESEND_INTERVAL )
return;
// fail if too many resends
if ( m_nReservationMsgRetryNumber >= MAX_RESERVATION_RETRIES )
{
CUtlString desc;
m_netadrReserveServer.Describe( desc );
Msg( "[MM] Attempt to reserve server %s failed; timed out after %d attempts\n", desc.String(), m_nReservationMsgRetryNumber + 1 );
ReservationResponseReply_t reply;
reply.m_adrFrom = m_netadrReserveServer.Get( 0 ).m_adrRemote;
HandleReservationResponse( reply );
return;
}
m_nReservationMsgRetryNumber++;
SendReserveServerMsg();
}
void CBaseClientState::SendReserveServerChallenge()
{
// Send to master asking for a challenge #
for ( int i = 0; i < m_netadrReserveServer.Count(); ++i )
{
Msg( "[MM] Sending reservation request to %s\n", ns_address_render( m_netadrReserveServer.Get( i ).m_adrRemote ).String() );
NET_OutOfBandDelayedPrintf( m_Socket, m_netadrReserveServer.Get( i ).m_adrRemote, GetPrivateIPDelayMsecs() * i, "%creserve0000000", A2S_GETCHALLENGE );
}
// Mark time of this attempt.
m_flReservationMsgSendTime = net_time; // for retransmit requests
}
void CBaseClientState::HandleReserveServerChallengeResponse( int nChallengeNr )
{
if ( !m_pServerReservationCallback )
return;
char buffer[MAX_OOB_KEYVALUES+128];
bf_write msg(buffer,sizeof(buffer));
msg.WriteLong( CONNECTIONLESS_HEADER );
msg.WriteByte( A2S_RESERVE );
msg.WriteLong( GetHostVersion() );
BuildReserveServerPayload( msg, nChallengeNr );
for ( int i = 0; i < m_netadrReserveServer.Count(); ++i )
{
NET_SendPacket( NULL, m_Socket, m_netadrReserveServer.Get( i ).m_adrRemote, msg.GetData(), msg.GetNumBytesWritten() );
}
}
//-----------------------------------------------------------------------------
// Purpose: encrypts an 8-byte sequence
//-----------------------------------------------------------------------------
inline void Encrypt8ByteSequence( IceKey& cipher, const unsigned char *plainText, unsigned char *cipherText)
{
cipher.encrypt(plainText, cipherText);
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void EncryptBuffer( IceKey& cipher, unsigned char *bufData, uint bufferSize)
{
unsigned char *cipherText = bufData;
unsigned char *plainText = bufData;
uint bytesEncrypted = 0;
while (bytesEncrypted < bufferSize)
{
// encrypt 8 byte section
Encrypt8ByteSequence( cipher, plainText, cipherText);
bytesEncrypted += 8;
cipherText += 8;
plainText += 8;
}
}
void CBaseClientState::BuildReserveServerPayload( bf_write &msg, int nChallengeNr )
{
char buffer[MAX_OOB_KEYVALUES+128];
bf_write payload(buffer,sizeof(buffer));
if ( !IsX360() )
{
// Magic # to ensure icey decrypt worked
payload.WriteLong( 0xfeedbeef );
}
// send the cookie that everyone in the joining party will provide to let them into the reserved server
payload.WriteLongLong( m_nServerReservationCookie );
int nSettingsLength = 0;
CUtlBuffer buf;
//this buffer needs to be endian compliant sot he X360 can talk correctly to the PC Dedicated server.
if( buf.IsBigEndian() )
{
buf.SetBigEndian( false );
}
if ( m_pKVGameSettings )
{
// if we have KeyValues with game settings, convert to binary blob
m_pKVGameSettings->WriteAsBinary( buf );
nSettingsLength = buf.TellPut();
// make sure it's not going to overflow one UDP packet
Assert( nSettingsLength <= MAX_OOB_KEYVALUES );
if ( nSettingsLength > MAX_OOB_KEYVALUES )
{
ReservationResponseReply_t reply;
reply.m_adrFrom = m_netadrReserveServer.Get( 0 ).m_adrRemote;
HandleReservationResponse( reply );
return;
}
}
// write # of bytes in game settings keyvalues
payload.WriteLong( nSettingsLength );
if ( nSettingsLength > 0 )
{
// write game setting keyvalues
payload.WriteBytes( buf.Base(), nSettingsLength );
}
if ( !IsX360() )
{
// Pad it to multiple of 8 bytes
while ( payload.GetNumBytesWritten() % 8 )
{
payload.WriteByte( 0 );
}
IceKey cipher(1); /* medium encryption level */
unsigned char ucEncryptionKey[8] = { 0 };
*( int * )&ucEncryptionKey[ 0 ] = LittleDWord( nChallengeNr ^ 0x5ef8ce12 );
*( int * )&ucEncryptionKey[ 4 ] = LittleDWord( nChallengeNr ^ 0xaa98e42c );
cipher.set( ucEncryptionKey );
EncryptBuffer( cipher, (byte *)payload.GetBasePointer(), payload.GetNumBytesWritten() );
msg.WriteLong( payload.GetNumBytesWritten() );
}
msg.WriteBytes( payload.GetBasePointer(), payload.GetNumBytesWritten() );
}
//-----------------------------------------------------------------------------
// Purpose: Sends a server reservation request
//-----------------------------------------------------------------------------
void CBaseClientState::SendReserveServerMsg()
{
if ( !IsX360() )
{
// The PC uses a more complicated challenge response system to prevent DDoS style attacks
SendReserveServerChallenge();
return;
}
Assert( m_pServerReservationCallback );
char buffer[MAX_OOB_KEYVALUES+128];
bf_write msg(buffer,sizeof(buffer));
msg.WriteLong( CONNECTIONLESS_HEADER );
msg.WriteByte( A2S_RESERVE );
msg.WriteLong( GetHostVersion() );
BuildReserveServerPayload( msg, 0 );
for ( int i = 0; i < m_netadrReserveServer.Count(); ++i )
{
NET_SendPacket( NULL, m_Socket, m_netadrReserveServer.Get( i ).m_adrRemote, msg.GetData(), msg.GetNumBytesWritten() );
}
// Mark time of this attempt.
m_flReservationMsgSendTime = net_time; // for retransmit requests
}
#ifndef DEDICATED
CSetActiveSplitScreenPlayerGuard::CSetActiveSplitScreenPlayerGuard( char const *pchContext, int nLine, int slot )
{
m_pchContext = pchContext;
m_nLine = nLine;
m_nSaveSlot = splitscreen->SetActiveSplitScreenPlayerSlot( slot );
m_bResolvable = splitscreen->SetLocalPlayerIsResolvable( pchContext, nLine, true );
}
CSetActiveSplitScreenPlayerGuard::~CSetActiveSplitScreenPlayerGuard()
{
splitscreen->SetActiveSplitScreenPlayerSlot( m_nSaveSlot );
splitscreen->SetLocalPlayerIsResolvable( m_pchContext, m_nLine, m_bResolvable );
}
#endif