//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: baseclientstate.cpp: implementation of the CBaseClientState class.
//
//===============================================================================
# include "client_pch.h"
# include "baseclientstate.h"
# include "vstdlib/random.h"
# include <inetchannel.h>
# include <netmessages.h>
# include <proto_oob.h>
# include <ctype.h>
# include "cl_main.h"
# include "net.h"
# include "dt_recv_eng.h"
# include "ents_shared.h"
# include "net_synctags.h"
# include "filesystem_engine.h"
# include "host_cmd.h"
# include "GameEventManager.h"
# include "sv_rcon.h"
# include "cl_rcon.h"
# ifndef SWDS
# include "vgui_baseui_interface.h"
# include "cl_pluginhelpers.h"
# include "vgui_askconnectpanel.h"
# endif
# include "sv_steamauth.h"
# include "tier0/icommandline.h"
# include "tier0/vcrmode.h"
# include "snd_audio_source.h"
# include "cl_steamauth.h"
# include "server.h"
# include "steam/steam_api.h"
# include "matchmaking.h"
# include "sv_plugin.h"
# include "sys_dll.h"
# include "host.h"
# if defined( REPLAY_ENABLED )
# include "replay_internal.h"
# include "replayserver.h"
# endif
// memdbgon must be the last include file in a .cpp file!!!
# include "tier0/memdbgon.h"
# ifdef ENABLE_RPT
void CL_NotifyRPTOfDisconnect ( ) ;
# endif // ENABLE_RPT
# if !defined( NO_STEAM )
void UpdateNameFromSteamID ( IConVar * pConVar , CSteamID * pSteamID )
{
# ifndef SWDS
if ( ! pConVar | | ! pSteamID | | ! Steam3Client ( ) . SteamFriends ( ) )
return ;
const char * pszName = Steam3Client ( ) . SteamFriends ( ) - > GetFriendPersonaName ( * pSteamID ) ;
pConVar - > SetValue ( pszName ) ;
# endif // SWDS
}
void SetNameToSteamIDName ( IConVar * pConVar )
{
# ifndef SWDS
if ( Steam3Client ( ) . SteamUtils ( ) & & Steam3Client ( ) . SteamFriends ( ) & & Steam3Client ( ) . SteamUser ( ) )
{
CSteamID steamID = Steam3Client ( ) . SteamUser ( ) - > GetSteamID ( ) ;
UpdateNameFromSteamID ( pConVar , & steamID ) ;
}
# endif // SWDS
}
# endif // NO_STEAM
void CL_NameCvarChanged ( IConVar * pConVar , const char * pOldString , float flOldValue )
{
ConVarRef var ( pConVar ) ;
static bool bPreventRent = false ;
if ( ! bPreventRent )
{
bPreventRent = true ;
# if !defined( NO_STEAM )
SetNameToSteamIDName ( pConVar ) ;
# endif
// Remove any evil characters (ex: zero width no-break space)
char pchName [ MAX_PLAYER_NAME_LENGTH ] ;
V_strcpy_safe ( pchName , var . GetString ( ) ) ;
Q_RemoveAllEvilCharacters ( pchName ) ;
pConVar - > SetValue ( pchName ) ;
// 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 " , var . GetString ( ) ) ;
}
bPreventRent = false ;
}
}
# ifndef SWDS
void askconnect_accept_f ( )
{
char szHostName [ 256 ] ;
if ( IsAskConnectPanelActive ( szHostName , sizeof ( szHostName ) ) )
{
char szCommand [ 512 ] ;
V_snprintf ( szCommand , sizeof ( szCommand ) , " connect %s redirect " , szHostName ) ;
Cbuf_AddText ( 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 )
{
KeyValues * kv = new KeyValues ( " ClanTagChanged " ) ;
kv - > SetString ( " tag " , pTag ) ;
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 )
{
// valid clan, accept the change
CSteamID clanIDNew ( newId , Steam3Client ( ) . SteamUtils ( ) - > GetConnectedUniverse ( ) , k_EAccountTypeClan ) ;
SendClanTag ( pFriends - > GetClanTag ( clanIDNew ) ) ;
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 " , " 6 " , FCVAR_NONE , " Delay in seconds before the client will resend the 'connect' attempt " , true , CL_MIN_RESEND_TIME , true , CL_MAX_RESEND_TIME ) ;
ConVar cl_name ( " name " , " unnamed " , FCVAR_ARCHIVE | FCVAR_USERINFO | 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 " ) ;
ConVar cl_interpolate ( " cl_interpolate " , " 1.0 " , FCVAR_USERINFO | FCVAR_DEVELOPMENTONLY | FCVAR_NOT_CONNECTED , " Interpolate entities on the client. " ) ;
ConVar cl_clanid ( " cl_clanid " , " 0 " , FCVAR_ARCHIVE | FCVAR_USERINFO | FCVAR_HIDDEN , " Current clan ID for name decoration " , CL_ClanIdChanged ) ;
ConVar cl_show_connectionless_packet_warnings ( " cl_show_connectionless_packet_warnings " , " 0 " , FCVAR_NONE , " Show console messages about ignored connectionless packets on the client. " ) ;
// ---------------------------------------------------------------------------------------- //
// 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 ;
}
// Returns false if you should stop reading entities.
inline static bool CL_DetermineUpdateType ( CEntityReadInfo & u )
{
if ( ! u . m_bIsEntity | | ( u . m_nNewEntity > u . m_nOldEntity ) )
{
// If we're at the last entity, preserve whatever entities followed it in the old packet.
// If newnum > oldnum, then the server skipped sending entities that it wants to leave the state alone for.
if ( ! u . m_pFrom | | ( u . m_nOldEntity > u . m_pFrom - > last_entity ) )
{
Assert ( ! u . m_bIsEntity ) ;
u . m_UpdateType = Finished ;
return false ;
}
// Preserve entities until we reach newnum (ie: the server didn't send certain entities because
// they haven't changed).
u . m_UpdateType = PreserveEnt ;
}
else
{
if ( u . m_UpdateFlags & FHDR_ENTERPVS )
{
u . m_UpdateType = EnterPVS ;
}
else if ( u . m_UpdateFlags & FHDR_LEAVEPVS )
{
u . m_UpdateType = LeavePVS ;
}
else
{
u . m_UpdateType = DeltaEnt ;
}
}
return true ;
}
//-----------------------------------------------------------------------------
// Purpose: When a delta command is received from the server
// We need to grab the entity # out of it any the bit settings, too.
// Returns -1 if there are no more entities.
// Input : &bRemove -
// &bIsNew -
// Output : int
//-----------------------------------------------------------------------------
static inline void CL_ParseDeltaHeader ( CEntityReadInfo & u )
{
u . m_UpdateFlags = FHDR_ZERO ;
# ifdef DEBUG_NETWORKING
int startbit = u . m_pBuf - > GetNumBitsRead ( ) ;
# endif
SyncTag_Read ( u . m_pBuf , " Hdr " ) ;
u . m_nNewEntity = u . m_nHeaderBase + 1 + u . m_pBuf - > ReadUBitVar ( ) ;
u . m_nHeaderBase = u . m_nNewEntity ;
// leave pvs flag
if ( u . m_pBuf - > ReadOneBit ( ) = = 0 )
{
// enter pvs flag
if ( u . m_pBuf - > ReadOneBit ( ) ! = 0 )
{
u . m_UpdateFlags | = FHDR_ENTERPVS ;
}
}
else
{
u . m_UpdateFlags | = FHDR_LEAVEPVS ;
// Force delete flag
if ( u . m_pBuf - > ReadOneBit ( ) ! = 0 )
{
u . m_UpdateFlags | = FHDR_DELETE ;
}
}
// Output the bitstream...
# ifdef DEBUG_NETWORKING
int lastbit = u . m_pBuf - > GetNumBitsRead ( ) ;
{
void SpewBitStream ( unsigned char * pMem , int bit , int lastbit ) ;
SpewBitStream ( ( byte * ) u . m_pBuf - > m_pData , startbit , lastbit ) ;
}
# endif
}
CBaseClientState : : CBaseClientState ( )
{
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_szRetryAddress [ 0 ] = 0 ;
m_ulGameServerSteamID = 0 ;
m_retryChallenge = 0 ;
m_bRestrictServerCommands = true ;
m_bRestrictClientCommands = true ;
m_nServerCount = 0 ;
m_nCurrentSequence = 0 ;
m_nDeltaTick = 0 ;
m_bPaused = 0 ;
m_flPausedExpireTime = - 1.f ;
m_nViewEntity = 0 ;
m_nPlayerSlot = 0 ;
m_szLevelFileName [ 0 ] = 0 ;
m_szLevelBaseName [ 0 ] = 0 ;
m_nMaxClients = 0 ;
Q_memset ( m_pEntityBaselines , 0 , sizeof ( m_pEntityBaselines ) ) ;
m_nServerClasses = 0 ;
m_nServerClassBits = 0 ;
m_szEncrytionKey [ 0 ] = 0 ;
}
CBaseClientState : : ~ CBaseClientState ( )
{
}
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_szLevelFileName [ 0 ] = 0 ;
m_szLevelBaseName [ 0 ] = 0 ;
m_nMaxClients = 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 ( ) ;
RecvTable_Term ( false ) ;
if ( m_NetChannel )
m_NetChannel - > Reset ( ) ;
m_bPaused = 0 ;
m_flPausedExpireTime = - 1.f ;
m_nViewEntity = 0 ;
m_nChallengeNr = 0 ;
m_flConnectTime = 0.0f ;
}
void CBaseClientState : : FileReceived ( const char * fileName , unsigned int transferID )
{
ConMsg ( " CBaseClientState::FileReceived: %s. \n " , fileName ) ;
}
void CBaseClientState : : FileDenied ( const char * fileName , unsigned int transferID )
{
ConMsg ( " CBaseClientState::FileDenied: %s. \n " , fileName ) ;
}
void CBaseClientState : : FileRequested ( const char * fileName , unsigned int transferID )
{
ConMsg ( " File '%s' requested from %s. \n " , fileName , m_NetChannel - > GetAddress ( ) ) ;
m_NetChannel - > SendFile ( fileName , transferID ) ; // CBaseCLisntState always sends file
}
void CBaseClientState : : FileSent ( const char * fileName , unsigned int transferID )
{
ConMsg ( " File '%s' sent. \n " , fileName ) ;
}
# define REGISTER_NET_MSG( name ) \
NET_ # # name * p # # name = new NET_ # # name ( ) ; \
p # # name - > m_pMessageHandler = this ; \
chan - > RegisterMessage ( p # # name ) ; \
# define REGISTER_SVC_MSG( name ) \
SVC_ # # name * p # # name = new SVC_ # # name ( ) ; \
p # # name - > m_pMessageHandler = this ; \
chan - > RegisterMessage ( p # # name ) ; \
void CBaseClientState : : ConnectionStart ( INetChannel * chan )
{
REGISTER_NET_MSG ( Tick ) ;
REGISTER_NET_MSG ( StringCmd ) ;
REGISTER_NET_MSG ( SetConVar ) ;
REGISTER_NET_MSG ( SignonState ) ;
REGISTER_SVC_MSG ( Print ) ;
REGISTER_SVC_MSG ( ServerInfo ) ;
REGISTER_SVC_MSG ( SendTable ) ;
REGISTER_SVC_MSG ( ClassInfo ) ;
REGISTER_SVC_MSG ( SetPause ) ;
REGISTER_SVC_MSG ( CreateStringTable ) ;
REGISTER_SVC_MSG ( UpdateStringTable ) ;
REGISTER_SVC_MSG ( VoiceInit ) ;
REGISTER_SVC_MSG ( VoiceData ) ;
REGISTER_SVC_MSG ( Sounds ) ;
REGISTER_SVC_MSG ( SetView ) ;
REGISTER_SVC_MSG ( FixAngle ) ;
REGISTER_SVC_MSG ( CrosshairAngle ) ;
REGISTER_SVC_MSG ( BSPDecal ) ;
REGISTER_SVC_MSG ( GameEvent ) ;
REGISTER_SVC_MSG ( UserMessage ) ;
REGISTER_SVC_MSG ( EntityMessage ) ;
REGISTER_SVC_MSG ( PacketEntities ) ;
REGISTER_SVC_MSG ( TempEntities ) ;
REGISTER_SVC_MSG ( Prefetch ) ;
REGISTER_SVC_MSG ( Menu ) ;
REGISTER_SVC_MSG ( GameEventList ) ;
REGISTER_SVC_MSG ( GetCvarValue ) ;
REGISTER_SVC_MSG ( CmdKeyValues ) ;
REGISTER_SVC_MSG ( SetPauseTimed ) ;
}
void CBaseClientState : : ConnectionClosing ( const char * reason )
{
ConMsg ( " Disconnect: %s. \n " , reason ? reason : " unknown reason " ) ;
Disconnect ( reason ? reason : " Connection closing " , true ) ;
}
//-----------------------------------------------------------------------------
// Purpose: A svc_signonnum has been received, perform a client side setup
// Output : void CL_SignonReply
//-----------------------------------------------------------------------------
bool CBaseClientState : : SetSignonState ( int state , int count )
{
// ConDMsg ("CL_SignonReply: %i\n", cl.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 ( state = = SIGNONSTATE_FULL )
{
# if defined( REPLAY_ENABLED ) && !defined( DEDICATED )
if ( g_pClientReplayContext )
{
g_pClientReplayContext - > OnSignonStateFull ( ) ;
}
# endif
if ( IsX360 ( ) & &
g_pMatchmaking - > PreventFullServerStartup ( ) )
{
return true ;
}
}
m_nSignonState = state ;
return true ;
}
//-----------------------------------------------------------------------------
// 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 ( int challengeNr , int authProtocol , uint64 unGSSteamID , bool bGSSecure )
{
COM_TimestampedLog ( " SendConnectPacket " ) ;
netadr_t adr ;
char szServerName [ MAX_OSPATH ] ;
const char * CDKey = " NOCDKEY " ;
Q_strncpy ( szServerName , m_szRetryAddress , MAX_OSPATH ) ;
if ( ! NET_StringToAdr ( szServerName , & adr ) )
{
ConMsg ( " Bad server address (%s) \n " , szServerName ) ;
Disconnect ( " Bad server address " , true ) ;
// Host_Disconnect(); MOTODO
return ;
}
if ( adr . GetPort ( ) = = ( unsigned short ) 0 )
{
adr . SetPort ( PORT_SERVER ) ;
}
ALIGN4 char msg_buffer [ MAX_ROUTABLE_PAYLOAD ] ALIGN4_POST ;
bf_write msg ( msg_buffer , sizeof ( msg_buffer ) ) ;
msg . WriteLong ( CONNECTIONLESS_HEADER ) ;
msg . WriteByte ( C2S_CONNECT ) ;
msg . WriteLong ( PROTOCOL_VERSION ) ;
msg . WriteLong ( authProtocol ) ;
msg . WriteLong ( challengeNr ) ;
msg . WriteLong ( m_retryChallenge ) ;
msg . WriteString ( GetClientName ( ) ) ; // Name
msg . WriteString ( password . GetString ( ) ) ; // password
msg . WriteString ( GetSteamInfIDVersionInfo ( ) . szVersionString ) ; // product version
// msg.WriteByte( ( g_pServerPluginHandler->GetNumLoadedPlugins() > 0 ) ? 1 : 0 ); // have any client-side server plug-ins been loaded?
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 , adr , msg ) )
{
return ;
}
break ;
default : Host_Error ( " Unexepected authentication protocol %i! \n " , authProtocol ) ;
return ;
}
// Mark time of this attempt for retransmit requests
m_flConnectTime = net_time ;
// remember challengenr for TCP connection
m_nChallengeNr = challengeNr ;
// Remember Steam ID, if any
m_ulGameServerSteamID = unGSSteamID ;
// Send protocol and challenge value
NET_SendPacket ( NULL , m_Socket , adr , msg . GetData ( ) , msg . GetNumBytesWritten ( ) ) ;
}
//-----------------------------------------------------------------------------
// Purpose: append steam specific data to a connection response
//-----------------------------------------------------------------------------
bool CBaseClientState : : PrepareSteamConnectResponse ( uint64 unGSSteamID , bool bGSSecure , const netadr_t & adr , bf_write & msg )
{
// X360TBD: Network - Steam Dedicated Server hack
if ( IsX360 ( ) )
{
return true ;
}
#if 0 //!defined( NO_STEAM ) && !defined( SWDS )
if ( ! Steam3Client ( ) . SteamUser ( ) )
{
COM_ExplainDisconnection ( true , " #GameUI_ServerRequireSteam " ) ;
Disconnect ( " #GameUI_ServerRequireSteam " , true ) ;
return false ;
}
# endif
netadr_t checkAdr = adr ;
if ( adr . GetType ( ) = = NA_LOOPBACK | | adr . IsLocalhost ( ) )
{
checkAdr . SetIP ( net_local_adr . GetIPHostByteOrder ( ) ) ;
}
#if 0 // #ifndef SWDS
// now append the steam3 cookie
char steam3Cookie [ STEAM_KEYSIZE ] ;
uint32 steam3CookieLen = 0 ;
Steam3Client ( ) . GetAuthSessionTicket ( steam3Cookie , sizeof ( steam3Cookie ) , & steam3CookieLen , checkAdr . GetIPHostByteOrder ( ) , checkAdr . GetPort ( ) , unGSSteamID , bGSSecure ) ;
if ( steam3CookieLen = = 0 )
{
COM_ExplainDisconnection ( true , " #GameUI_ServerRequireSteam " ) ;
Disconnect ( " #GameUI_ServerRequireSteam " , true ) ;
return false ;
}
msg . WriteShort ( steam3CookieLen ) ;
if ( steam3CookieLen > 0 )
msg . WriteBytes ( steam3Cookie , steam3CookieLen ) ;
# endif
return true ;
}
// Tracks how we connected to the current server.
static ConVar cl_connectmethod ( " cl_connectmethod " , " " , FCVAR_USERINFO | FCVAR_HIDDEN , " Method by which we connected to the current server. " ) ;
/* static */ bool CBaseClientState : : ConnectMethodAllowsRedirects ( )
{
// Only HLTV should be allowed to redirect clients, but malicious servers can answer a connect
// attempt as HLTV and then redirect elsewhere. A somewhat-more complete fix for this would
// involve tracking our redirected status and refusing to interact with non-HLTV servers. For
// now, however, we just blacklist server browser / matchmaking connect methods from allowing
// redirects and allow it for other types.
const char * pConnectMethod = cl_connectmethod . GetString ( ) ;
if ( V_strcmp ( pConnectMethod , " serverbrowser_internet " ) = = 0 | |
V_strncmp ( pConnectMethod , " quickpick " , 9 ) = = 0 | |
V_strncmp ( pConnectMethod , " quickplay " , 9 ) = = 0 | |
V_strcmp ( pConnectMethod , " matchmaking " ) = = 0 | |
V_strcmp ( pConnectMethod , " coaching " ) = = 0 )
{
return false ;
}
return true ;
}
void CBaseClientState : : Connect ( const char * adr , const char * pszSourceTag )
{
# if !defined( NO_STEAM )
// 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
Q_strncpy ( m_szRetryAddress , adr , sizeof ( m_szRetryAddress ) ) ;
m_retryChallenge = ( RandomInt ( 0 , 0x0FFF ) < < 16 ) | RandomInt ( 0 , 0xFFFF ) ;
m_ulGameServerSteamID = 0 ;
m_sRetrySourceTag = pszSourceTag ;
cl_connectmethod . SetValue ( m_sRetrySourceTag . String ( ) ) ;
// For the check for resend timer to fire a connection / getchallenge request.
SetSignonState ( SIGNONSTATE_CHALLENGE , - 1 ) ;
// Force connection request to fire.
m_flConnectTime = - FLT_MAX ;
m_nRetryNumber = 0 ;
}
INetworkStringTable * CBaseClientState : : GetStringTable ( const char * name ) const
{
if ( ! m_StringTableContainer )
{
Assert ( m_StringTableContainer ) ;
return NULL ;
}
return m_StringTableContainer - > FindTable ( name ) ;
}
void CBaseClientState : : ForceFullUpdate ( void )
{
if ( m_nDeltaTick = = - 1 )
return ;
FreeEntityBaselines ( ) ;
m_nDeltaTick = - 1 ;
DevMsg ( " Requesting full game update... \n " ) ;
}
void CBaseClientState : : FullConnect ( netadr_t & adr )
{
// Initiate the network channel
COM_TimestampedLog ( " CBaseClientState::FullConnect " ) ;
m_NetChannel = NET_CreateNetChannel ( m_Socket , & adr , " CLIENT " , this ) ;
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 ;
// Mark client as connected
SetSignonState ( SIGNONSTATE_CONNECTED , - 1 ) ;
# if !defined(SWDS)
RCONClient ( ) . SetAddress ( m_NetChannel - > GetRemoteAddress ( ) ) ;
# endif
// Fire an event when we get our connection
IGameEvent * event = g_GameEventManager . CreateEvent ( " client_connected " ) ;
if ( event )
{
event - > SetString ( " address " , m_NetChannel - > GetRemoteAddress ( ) . ToString ( true ) ) ;
event - > SetInt ( " ip " , m_NetChannel - > GetRemoteAddress ( ) . GetIPNetworkByteOrder ( ) ) ; // <<< Network byte order?
event - > SetInt ( " port " , m_NetChannel - > GetRemoteAddress ( ) . GetPort ( ) ) ;
g_GameEventManager . FireEventClientSide ( event ) ;
}
}
void CBaseClientState : : ConnectionCrashed ( const char * reason )
{
DebuggerBreakIfDebugging_StagingOnly ( ) ;
ConMsg ( " Connection lost: %s. \n " , reason ? reason : " unknown reason " ) ;
Disconnect ( reason ? reason : " Connection crashed " , true ) ;
}
void CBaseClientState : : Disconnect ( const char * pszReason , bool bShowMainMenu )
{
m_flConnectTime = - FLT_MAX ;
m_nRetryNumber = 0 ;
m_ulGameServerSteamID = 0 ;
if ( m_nSignonState = = SIGNONSTATE_NONE )
return ;
# if !defined( SWDS ) && defined( ENABLE_RPT )
CL_NotifyRPTOfDisconnect ( ) ;
# endif
m_nSignonState = SIGNONSTATE_NONE ;
netadr_t adr ;
if ( m_NetChannel )
{
adr = m_NetChannel - > GetRemoteAddress ( ) ;
}
else
{
NET_StringToAdr ( m_szRetryAddress , & adr ) ;
}
# ifndef SWDS
netadr_t checkAdr = adr ;
if ( adr . GetType ( ) = = NA_LOOPBACK | | adr . IsLocalhost ( ) )
{
checkAdr . SetIP ( net_local_adr . GetIPHostByteOrder ( ) ) ;
}
Steam3Client ( ) . CancelAuthTicket ( ) ;
# endif
if ( m_NetChannel )
{
m_NetChannel - > Shutdown ( ( pszReason & & * pszReason ) ? pszReason : " Disconnect by user. " ) ;
m_NetChannel = NULL ;
}
}
void CBaseClientState : : RunFrame ( void )
{
VPROF ( " CBaseClientState::RunFrame " ) ;
tmZone ( TELEMETRY_LEVEL0 , TMZF_NONE , " %s " , __FUNCTION__ ) ;
if ( ( m_nSignonState > SIGNONSTATE_NEW ) & & m_NetChannel & & g_GameEventManager . HasClientListenersChanged ( ) )
{
// assemble a list of all events we listening to and tell the server
CLC_ListenEvents msg ;
g_GameEventManager . WriteListenEventList ( & msg ) ;
m_NetChannel - > SendNetMsg ( msg ) ;
}
if ( m_nSignonState = = SIGNONSTATE_CHALLENGE )
{
CheckForResend ( ) ;
}
}
/*
= = = = = = = = = = = = = = = = =
CL_CheckForResend
Resend a connect message if the last one has timed out
= = = = = = = = = = = = = = = = =
*/
void CBaseClientState : : CheckForResend ( void )
{
// resend if we haven't gotten a reply yet
// We only resend during the connection process.
if ( m_nSignonState ! = SIGNONSTATE_CHALLENGE )
return ;
// Wait at least the resend # of seconds.
if ( ( net_time - m_flConnectTime ) < cl_resend . GetFloat ( ) )
return ;
netadr_t adr ;
if ( ! NET_StringToAdr ( m_szRetryAddress , & adr ) )
{
ConMsg ( " Bad server address (%s) \n " , m_szRetryAddress ) ;
//Host_Disconnect();
Disconnect ( " Bad server address " , true ) ;
return ;
}
if ( adr . GetPort ( ) = = 0 )
{
adr . SetPort ( PORT_SERVER ) ;
}
// Only retry so many times before failure.
if ( m_nRetryNumber > = GetConnectionRetryNumber ( ) )
{
COM_ExplainDisconnection ( true , " Connection failed after %i retries. \n " , CL_CONNECTION_RETRIES ) ;
// Host_Disconnect();
Disconnect ( " Connection failed " , true ) ;
return ;
}
// Mark time of this attempt.
m_flConnectTime = net_time ; // for retransmit requests
// Display appropriate message
if ( Q_strncmp ( m_szRetryAddress , " localhost " , 9 ) )
{
if ( m_nRetryNumber = = 0 )
ConMsg ( " Connecting to %s... \n " , m_szRetryAddress ) ;
else
ConMsg ( " Retrying %s... \n " , m_szRetryAddress ) ;
}
// Fire an event when we attempt connection
if ( m_nRetryNumber = = 0 )
{
IGameEvent * event = g_GameEventManager . CreateEvent ( " client_beginconnect " ) ;
if ( event )
{
event - > SetString ( " address " , m_szRetryAddress ) ;
event - > SetInt ( " ip " , adr . GetIPNetworkByteOrder ( ) ) ; // <<< Network byte order?
event - > SetInt ( " port " , adr . GetPort ( ) ) ;
//event->SetInt( "retry_number", m_nRetryNumber );
event - > SetString ( " source " , m_sRetrySourceTag ) ;
g_GameEventManager . FireEventClientSide ( event ) ;
}
}
m_nRetryNumber + + ;
// Request another challenge value.
{
ALIGN4 char msg_buffer [ MAX_ROUTABLE_PAYLOAD ] ALIGN4_POST ;
bf_write msg ( msg_buffer , sizeof ( msg_buffer ) ) ;
msg . WriteLong ( CONNECTIONLESS_HEADER ) ;
msg . WriteByte ( A2S_GETCHALLENGE ) ;
msg . WriteLong ( m_retryChallenge ) ;
msg . WriteString ( " 0000000000 " ) ; // pad out
NET_SendPacket ( NULL , m_Socket , adr , msg . GetData ( ) , msg . GetNumBytesWritten ( ) ) ;
}
}
bool CBaseClientState : : ProcessConnectionlessPacket ( netpacket_t * packet )
{
VPROF ( " ProcessConnectionlessPacket " ) ;
Assert ( packet ) ;
bf_read & msg = packet - > message ; // handy shortcut
int c = msg . ReadByte ( ) ;
// ignoring a specific packet that DOTA2 broadcasts
if ( c = = C2C_MOD )
return false ;
char string [ MAX_ROUTABLE_PAYLOAD ] ;
netadr_t adrServerConnectingTo ;
NET_StringToAdr ( m_szRetryAddress , & adrServerConnectingTo ) ;
if ( ( packet - > from . GetType ( ) ! = NA_LOOPBACK ) & & ( packet - > from . GetIPNetworkByteOrder ( ) ! = adrServerConnectingTo . GetIPNetworkByteOrder ( ) ) )
{
if ( cl_show_connectionless_packet_warnings . GetBool ( ) )
{
ConDMsg ( " Discarding connectionless packet ( CL '%c' ) from %s. \n " , c , packet - > from . ToString ( ) ) ;
}
return false ;
}
switch ( c )
{
case S2C_CONNECTION : if ( m_nSignonState = = SIGNONSTATE_CHALLENGE )
{
int myChallenge = msg . ReadLong ( ) ;
if ( myChallenge ! = m_retryChallenge )
{
Msg ( " Server connection did not have the correct challenge, ignoring. \n " ) ;
return false ;
}
// server accepted our connection request
FullConnect ( packet - > from ) ;
}
break ;
case S2C_CHALLENGE : // Response from getchallenge we sent to the server we are connecting to
// Blow it off if we are not connected.
if ( m_nSignonState = = SIGNONSTATE_CHALLENGE )
{
int magicVersion = msg . ReadLong ( ) ;
if ( magicVersion ! = S2C_MAGICVERSION )
{
COM_ExplainDisconnection ( true , " #GameUI_ServerConnectOutOfDate " ) ;
Disconnect ( " #GameUI_ServerConnectOutOfDate " , true ) ;
return false ;
}
int challenge = msg . ReadLong ( ) ;
int myChallenge = msg . ReadLong ( ) ;
if ( myChallenge ! = m_retryChallenge )
{
Msg ( " Server challenge did not have the correct challenge, ignoring. \n " ) ;
return false ;
}
int authprotocol = msg . ReadLong ( ) ;
uint64 unGSSteamID = 0 ;
bool bGSSecure = false ;
#if 0
if ( authprotocol = = PROTOCOL_STEAM )
{
if ( msg . ReadShort ( ) ! = 0 )
{
Msg ( " Invalid Steam key size. \n " ) ;
Disconnect ( " Invalid Steam key size " , true ) ;
return false ;
}
if ( msg . GetNumBytesLeft ( ) > sizeof ( unGSSteamID ) )
{
if ( ! msg . ReadBytes ( & unGSSteamID , sizeof ( unGSSteamID ) ) )
{
Msg ( " Invalid GS Steam ID. \n " ) ;
Disconnect ( " Invalid GS Steam ID " , true ) ;
return false ;
}
bGSSecure = ( msg . ReadByte ( ) = = 1 ) ;
}
// The host can disable access to secure servers if you load unsigned code (mods, plugins, hacks)
if ( bGSSecure & & ! Host_IsSecureServerAllowed ( ) )
{
COM_ExplainDisconnection ( true , " #GameUI_ServerInsecure " ) ;
Disconnect ( " #GameUI_ServerInsecure " , true ) ;
return false ;
}
}
# endif
SendConnectPacket ( challenge , authprotocol , unGSSteamID , bGSSecure ) ;
}
break ;
case S2C_CONNREJECT : if ( m_nSignonState = = SIGNONSTATE_CHALLENGE ) // Spoofed?
{
int myChallenge = msg . ReadLong ( ) ;
if ( myChallenge ! = m_retryChallenge )
{
Msg ( " Received connection rejection that didn't match my challenge, ignoring. \n " ) ;
return false ;
}
msg . ReadString ( string , sizeof ( string ) ) ;
// Force failure dialog to come up now.
COM_ExplainDisconnection ( true , " %s " , string ) ;
Disconnect ( string , true ) ;
// Host_Disconnect();
}
break ;
// Unknown?
default :
// Otherwise, don't do anything.
if ( cl_show_connectionless_packet_warnings . GetBool ( ) )
{
ConDMsg ( " Bad connectionless packet ( CL '%c' ) from %s. \n " , c , packet - > from . ToString ( ) ) ;
}
return false ;
}
return true ;
}
bool CBaseClientState : : ProcessTick ( NET_Tick * msg )
{
VPROF ( " ProcessTick " ) ;
m_NetChannel - > SetRemoteFramerate ( msg - > m_flHostFrameTime , msg - > m_flHostFrameTimeStdDeviation ) ;
// 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 - > m_nTick ) ;
SetServerTickCount ( msg - > m_nTick ) ;
if ( m_StringTableContainer )
{
m_StringTableContainer - > SetTick ( GetServerTickCount ( ) ) ;
}
return ( GetServerTickCount ( ) > 0 ) ;
}
void CBaseClientState : : SendStringCmd ( const char * command )
{
if ( m_NetChannel )
{
NET_StringCmd stringCmd ( command ) ;
m_NetChannel - > SendNetMsg ( stringCmd ) ;
}
}
bool CBaseClientState : : ProcessStringCmd ( NET_StringCmd * msg )
{
VPROF ( " ProcessStringCmd " ) ;
// 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 ( msg - > m_szCommand ) ;
return true ;
}
// Check that we can add the two execution markers
if ( ! Cbuf_HasRoomForExecutionMarkers ( 2 ) )
{
AssertMsg ( false , " CBaseClientState::ProcessStringCmd called but there is no room for the execution markers. Ignoring command. " ) ;
return true ;
}
// Run the command, but make sure the command parser knows to only execute commands marked with FCVAR_SERVER_CAN_EXECUTE.
Cbuf_AddTextWithMarkers ( eCmdExecutionMarker_Enable_FCVAR_SERVER_CAN_EXECUTE , msg - > m_szCommand , eCmdExecutionMarker_Disable_FCVAR_SERVER_CAN_EXECUTE ) ;
return true ;
}
bool CBaseClientState : : ProcessSetConVar ( NET_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 - > m_ConVars . Count ( ) ; i + + )
{
const char * name = msg - > m_ConVars [ i ] . name ;
const char * value = msg - > m_ConVars [ i ] . value ;
// 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 : : ProcessSignonState ( NET_SignonState * msg )
{
VPROF ( " ProcessSignonState " ) ;
return SetSignonState ( msg - > m_nSignonState , msg - > m_nSpawnCount ) ;
}
bool CBaseClientState : : ProcessPrint ( SVC_Print * msg )
{
VPROF ( " ProcessPrint " ) ;
ConMsg ( " %s " , msg - > m_szText ) ;
return true ;
}
bool CBaseClientState : : ProcessMenu ( SVC_Menu * msg )
{
VPROF ( " ProcessMenu " ) ;
# if !defined(SWDS)
PluginHelpers_Menu ( msg ) ;
# endif
return true ;
}
bool CBaseClientState : : ProcessServerInfo ( SVC_ServerInfo * msg )
{
VPROF ( " ProcessServerInfo " ) ;
# ifndef SWDS
EngineVGui ( ) - > UpdateProgressBar ( PROGRESS_PROCESSSERVERINFO ) ;
# endif
COM_TimestampedLog ( " CBaseClient::ProcessServerInfo " ) ;
if ( msg - > m_nProtocol ! = PROTOCOL_VERSION
# if defined( DEMO_BACKWARDCOMPATABILITY ) && (! defined( SWDS ) )
& & ! ( demoplayer - > IsPlayingBack ( ) & & msg - > m_nProtocol > = PROTOCOL_VERSION_12 )
# endif
)
{
ConMsg ( " Server returned version %i, expected %i. \n " , msg - > m_nProtocol , PROTOCOL_VERSION ) ;
return false ;
}
// 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 - > m_nServerCount ;
m_nMaxClients = msg - > m_nMaxClients ;
m_nServerClasses = msg - > m_nMaxClasses ;
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 SWDS
if ( ! sv . IsActive ( ) & &
! ( m_NetChannel - > IsLoopback ( ) | | m_NetChannel - > IsNull ( ) ) )
{
// if you are joing a remote server it MUST be multipler and have maxplayer set to more than 1
// this prevents a spoofed ServerInfo packet from making the client think its in singleplayer
// and turning off a bunch of security checks
if ( m_nMaxClients < = 1 )
{
ConMsg ( " Bad maxclients (%u) from server. \n " , m_nMaxClients ) ;
return false ;
}
// reset server enforced cvars
g_pCVar - > RevertFlaggedConVars ( FCVAR_REPLICATED ) ;
// Cheats were disabled; revert all cheat cvars to their default values.
// This must be done heading into multiplayer games because people can play
// demos etc and set cheat cvars with sv_cheats 0.
g_pCVar - > RevertFlaggedConVars ( FCVAR_CHEAT ) ;
DevMsg ( " FCVAR_CHEAT cvars reverted to defaults. \n " ) ;
}
# endif
// clear all baselines still around from last game
FreeEntityBaselines ( ) ;
// force changed flag to being reset
g_GameEventManager . HasClientListenersChanged ( true ) ;
m_nPlayerSlot = msg - > m_nPlayerSlot ;
m_nViewEntity = m_nPlayerSlot + 1 ;
if ( msg - > m_fTickInterval < MINIMUM_TICK_INTERVAL | |
msg - > m_fTickInterval > MAXIMUM_TICK_INTERVAL )
{
ConMsg ( " Interval_per_tick %f out of range [%f to %f] \n " ,
msg - > m_fTickInterval , MINIMUM_TICK_INTERVAL , MAXIMUM_TICK_INTERVAL ) ;
return false ;
}
if ( ! COM_CheckGameDirectory ( msg - > m_szGameDir ) )
{
return false ;
}
Q_strncpy ( m_szLevelBaseName , msg - > m_szMapName , sizeof ( m_szLevelBaseName ) ) ;
# if !defined(SWDS)
audiosourcecache - > LevelInit ( m_szLevelBaseName ) ;
# endif
ConVarRef skyname ( " sv_skyname " ) ;
if ( skyname . IsValid ( ) )
{
skyname . SetValue ( msg - > m_szSkyName ) ;
}
m_nDeltaTick = - 1 ; // no valid snapshot for this game yet
// fire a client side event about server data
IGameEvent * event = g_GameEventManager . CreateEvent ( " server_spawn " ) ;
if ( event )
{
event - > SetString ( " hostname " , msg - > m_szHostName ) ;
event - > SetString ( " address " , m_NetChannel - > GetRemoteAddress ( ) . ToString ( true ) ) ;
event - > SetInt ( " ip " , m_NetChannel - > GetRemoteAddress ( ) . GetIPNetworkByteOrder ( ) ) ; // <<< Network byte order?
event - > SetInt ( " port " , m_NetChannel - > GetRemoteAddress ( ) . GetPort ( ) ) ;
event - > SetString ( " game " , msg - > m_szGameDir ) ;
event - > SetString ( " mapname " , msg - > m_szMapName ) ;
event - > SetInt ( " maxplayers " , msg - > m_nMaxClients ) ;
event - > SetInt ( " password " , 0 ) ; // TODO
event - > SetString ( " os " , va ( " %c " , toupper ( msg - > m_cOS ) ) ) ;
event - > SetInt ( " dedicated " , msg - > m_bIsDedicated ? 1 : 0 ) ;
if ( m_ulGameServerSteamID ! = 0 )
{
event - > SetString ( " steamid " , CSteamID ( m_ulGameServerSteamID ) . Render ( ) ) ;
}
g_GameEventManager . FireEventClientSide ( event ) ;
}
// Set default filename, but this is finalized by ClientState later, so it should not be depended on yet. See PrepareLevelResources call
Host_DefaultMapFileName ( msg - > m_szMapName , m_szLevelFileName , sizeof ( m_szLevelFileName ) ) ;
COM_TimestampedLog ( " CBaseClient::ProcessServerInfo(done) " ) ;
return true ;
}
bool CBaseClientState : : ProcessSendTable ( SVC_SendTable * msg )
{
VPROF ( " ProcessSendTable " ) ;
if ( ! RecvTable_RecvClassInfos ( & msg - > m_DataIn , msg - > m_bNeedsDecoder ) )
{
Host_EndGame ( true , " ProcessSendTable: RecvTable_RecvClassInfos failed. \n " ) ;
return false ;
}
return true ;
}
bool CBaseClientState : : ProcessClassInfo ( SVC_ClassInfo * msg )
{
VPROF ( " ProcessClassInfo " ) ;
COM_TimestampedLog ( " CBaseClient::ProcessClassInfo " ) ;
if ( msg - > m_bCreateOnClient )
{
ConMsg ( " Can't create class tables. \n " ) ;
Assert ( 0 ) ;
return false ;
}
if ( m_pServerClasses )
{
delete [ ] m_pServerClasses ;
}
m_nServerClasses = msg - > m_Classes . Count ( ) ;
m_pServerClasses = new C_ServerClassInfo [ m_nServerClasses ] ;
if ( ! m_pServerClasses )
{
Host_EndGame ( true , " ProcessClassInfo: 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 + + )
{
SVC_ClassInfo : : class_t * svclass = & msg - > m_Classes [ i ] ;
if ( svclass - > classID > = m_nServerClasses )
{
Host_EndGame ( true , " ProcessClassInfo: invalid class index (%d). \n " , svclass - > classID ) ;
return false ;
}
C_ServerClassInfo * svclassinfo = & m_pServerClasses [ svclass - > classID ] ;
int len = Q_strlen ( svclass - > classname ) + 1 ;
svclassinfo - > m_ClassName = new char [ len ] ;
Q_strncpy ( svclassinfo - > m_ClassName , svclass - > classname , len ) ;
len = Q_strlen ( svclass - > datatablename ) + 1 ;
svclassinfo - > m_DatatableName = new char [ len ] ;
Q_strncpy ( svclassinfo - > m_DatatableName , svclass - > datatablename , len ) ;
}
COM_TimestampedLog ( " CBaseClient::ProcessClassInfo(done) " ) ;
return LinkClasses ( ) ; // link server and client classes
}
bool CBaseClientState : : ProcessSetPause ( SVC_SetPause * msg )
{
VPROF ( " ProcessSetPause " ) ;
m_bPaused = msg - > m_bPaused ;
return true ;
}
bool CBaseClientState : : ProcessSetPauseTimed ( SVC_SetPauseTimed * msg )
{
VPROF ( " ProcessSetPauseTimed " ) ;
m_bPaused = msg - > m_bPaused ;
m_flPausedExpireTime = msg - > m_flExpireTime ;
return true ;
}
bool CBaseClientState : : ProcessCreateStringTable ( SVC_CreateStringTable * msg )
{
VPROF ( " ProcessCreateStringTable " ) ;
# ifndef SWDS
EngineVGui ( ) - > UpdateProgressBar ( PROGRESS_PROCESSSTRINGTABLE ) ;
# endif
COM_TimestampedLog ( " CBaseClient::ProcessCreateStringTable(%s) " , msg - > m_szTableName ) ;
m_StringTableContainer - > AllowCreation ( true ) ;
int startbit = msg - > m_DataIn . GetNumBitsRead ( ) ;
# ifndef SHARED_NET_STRING_TABLES
CNetworkStringTable * table = ( CNetworkStringTable * )
m_StringTableContainer - > CreateStringTableEx ( msg - > m_szTableName , msg - > m_nMaxEntries , msg - > m_nUserDataSize , msg - > m_nUserDataSizeBits , msg - > m_bIsFilenames ) ;
Assert ( table ) ;
table - > SetTick ( GetServerTickCount ( ) ) ; // set creation tick
HookClientStringTable ( msg - > m_szTableName ) ;
if ( msg - > m_bDataCompressed )
{
unsigned int msgUncompressedSize = msg - > m_DataIn . ReadLong ( ) ;
unsigned int msgCompressedSize = msg - > m_DataIn . ReadLong ( ) ;
unsigned int uncompressedSize = msgUncompressedSize ;
/// XXX(JohnS): 11/08/2016 - The PAD_NUMBER() call below was overflowing on UINT32_MAX-3 values. Enforcing
// resource-usage limits at this level is a lost cause without a massive overhaul, but clamp these
// to somewhat reasonable ranges to prevent overflows with less-audited code in the engine.
bool bSuccess = false ;
if ( msg - > m_DataIn . TotalBytesAvailable ( ) > 0 & &
msgCompressedSize < = ( unsigned int ) msg - > m_DataIn . TotalBytesAvailable ( ) & &
msgCompressedSize < UINT_MAX / 2 & &
msgUncompressedSize < UINT_MAX / 2 )
{
// allocate buffer for uncompressed data, align to 4 bytes boundary
char * uncompressedBuffer = new char [ PAD_NUMBER ( msgUncompressedSize , 4 ) ] ;
char * compressedBuffer = new char [ PAD_NUMBER ( msgCompressedSize , 4 ) ] ;
msg - > m_DataIn . ReadBits ( compressedBuffer , msgCompressedSize * 8 ) ;
// uncompress data
bSuccess = COM_BufferToBufferDecompress ( uncompressedBuffer , & uncompressedSize , compressedBuffer , msgCompressedSize ) ;
bSuccess & = ( uncompressedSize = = msgUncompressedSize ) ;
if ( bSuccess )
{
bf_read data ( uncompressedBuffer , uncompressedSize ) ;
table - > ParseUpdate ( data , msg - > m_nNumEntries ) ;
}
delete [ ] uncompressedBuffer ;
delete [ ] compressedBuffer ;
}
if ( ! bSuccess )
{
Assert ( false ) ;
Warning ( " Malformed message in CBaseClientState::ProcessCreateStringTable \n " ) ;
}
}
else
{
table - > ParseUpdate ( msg - > m_DataIn , msg - > m_nNumEntries ) ;
}
# endif
m_StringTableContainer - > AllowCreation ( false ) ;
int endbit = msg - > m_DataIn . GetNumBitsRead ( ) ;
COM_TimestampedLog ( " CBaseClient::ProcessCreateStringTable(%s)-done " , msg - > m_szTableName ) ;
return ( endbit - startbit ) = = msg - > m_nLength ;
}
bool CBaseClientState : : ProcessUpdateStringTable ( SVC_UpdateStringTable * msg )
{
VPROF ( " ProcessUpdateStringTable " ) ;
int startbit = msg - > m_DataIn . GetNumBitsRead ( ) ;
# 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 - > m_nTableID ) ;
table - > ParseUpdate ( msg - > m_DataIn , msg - > m_nChangedEntries ) ;
}
else
{
Warning ( " m_StringTableContainer is NULL in CBaseClientState::ProcessUpdateStringTable \n " ) ;
}
# endif
int endbit = msg - > m_DataIn . GetNumBitsRead ( ) ;
return ( endbit - startbit ) = = msg - > m_nLength ;
}
bool CBaseClientState : : ProcessSetView ( SVC_SetView * msg )
{
VPROF ( " ProcessSetView " ) ;
m_nViewEntity = msg - > m_nEntityIndex ;
return true ;
}
bool CBaseClientState : : ProcessPacketEntities ( SVC_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 - > m_bIsDelta )
{
// We are done with signon sequence.
SetSignonState ( SIGNONSTATE_FULL , m_nServerCount ) ;
}
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 - > m_bIsDelta )
{
// we received this snapshot successfully, now this is our delta reference
m_nDeltaTick = GetServerTickCount ( ) ;
}
return true ;
}
void CBaseClientState : : ReadPacketEntities ( CEntityReadInfo & u )
{
VPROF ( " ReadPacketEntities " ) ;
// Loop until there are no more entities to read
u . NextOldEntity ( ) ;
while ( u . m_UpdateType < Finished )
{
u . m_nHeaderCount - - ;
u . m_bIsEntity = ( u . m_nHeaderCount > = 0 ) ? true : false ;
if ( u . m_bIsEntity )
{
CL_ParseDeltaHeader ( u ) ;
}
u . m_UpdateType = PreserveEnt ;
while ( u . m_UpdateType = = PreserveEnt )
{
// Figure out what kind of an update this is.
if ( CL_DetermineUpdateType ( u ) )
{
switch ( u . m_UpdateType )
{
case EnterPVS : ReadEnterPVS ( u ) ;
break ;
case LeavePVS : ReadLeavePVS ( u ) ;
break ;
case DeltaEnt : ReadDeltaEnt ( u ) ;
break ;
case PreserveEnt : ReadPreserveEnt ( u ) ;
break ;
default : DevMsg ( 1 , " ReadPacketEntities: unknown updatetype %i \n " , u . m_UpdateType ) ;
break ;
}
}
}
}
// Now process explicit deletes
if ( u . m_bAsDelta & & u . m_UpdateType = = Finished )
{
ReadDeletions ( u ) ;
}
// Something didn't parse...
if ( u . m_pBuf - > IsOverflowed ( ) )
{
Host_Error ( " CL_ParsePacketEntities: buffer read overflow \n " ) ;
}
// If we get an uncompressed packet, then the server is waiting for us to ack the validsequence
// that we got the uncompressed packet on. So we stop reading packets here and force ourselves to
// send the clc_move on the next frame.
if ( ! u . m_bAsDelta )
{
m_flNextCmdTime = 0.0 ; // answer ASAP to confirm full update tick
}
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : *pHead -
// *pClassName -
// Output : static ClientClass*
//-----------------------------------------------------------------------------
ClientClass * CBaseClientState : : FindClientClass ( const char * pClassName )
{
if ( ! pClassName )
return NULL ;
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 ] ;
if ( ! pServerClass - > m_DatatableName )
continue ;
// (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 , char * packedData , int length )
{
VPROF ( " CBaseClientState::SetEntityBaseline " ) ;
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 - > AllocAndCopyPadded ( packedData , length ) ;
}
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 ) ;
Assert ( ! blfrom - > IsCompressed ( ) ) ;
blto - > m_nEntityIndex = blfrom - > m_nEntityIndex ;
blto - > m_pClientClass = blfrom - > m_pClientClass ;
blto - > m_pServerClass = blfrom - > m_pServerClass ;
blto - > AllocAndCopyPadded ( blfrom - > GetData ( ) , blfrom - > GetNumBytes ( ) ) ;
}
}
ClientClass * CBaseClientState : : GetClientClass ( int index )
{
Assert ( index < m_nServerClasses ) ;
return m_pServerClasses [ index ] . m_pClientClass ;
}
bool CBaseClientState : : GetClassBaseline ( int iClass , void const * * pData , int * pDatalen )
{
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 ) ;
if ( pInfo - > m_InstanceBaselineIndex = = INVALID_STRING_INDEX )
{
for ( int i = 0 ; i < pBaselineTable - > GetNumStrings ( ) ; + + i )
{
DevMsg ( " %i: %s \n " , i , pBaselineTable - > GetString ( i ) ) ;
}
// Gets a callstack, whereas ErrorIfNot(), does not.
Assert ( 0 ) ;
}
ErrorIfNot (
pInfo - > m_InstanceBaselineIndex ! = INVALID_STRING_INDEX ,
( " GetDynamicBaseline: FindStringIndex(%s-%s) failed. " , str , pInfo - > m_ClassName ) ;
) ;
}
* pData = pBaselineTable - > GetStringUserData ( pInfo - > m_InstanceBaselineIndex , pDatalen ) ;
return * pData ! = NULL ;
}
bool CBaseClientState : : ProcessGameEventList ( SVC_GameEventList * msg )
{
VPROF ( " ProcessGameEventList " ) ;
return g_GameEventManager . ParseEventList ( msg ) ;
}
bool CBaseClientState : : ProcessGetCvarValue ( SVC_GetCvarValue * msg )
{
VPROF ( " ProcessGetCvarValue " ) ;
// Prepare the response.
CLC_RespondCvarValue returnMsg ;
returnMsg . m_iCookie = msg - > m_iCookie ;
returnMsg . m_szCvarName = msg - > m_szCvarName ;
returnMsg . m_szCvarValue = " " ;
returnMsg . m_eStatusCode = eQueryCvarValueStatus_CvarNotFound ;
char tempValue [ 256 ] ;
// Does any ConCommand exist with this name?
const ConVar * pVar = g_pCVar - > FindVar ( msg - > m_szCvarName ) ;
if ( pVar )
{
if ( pVar - > IsFlagSet ( FCVAR_SERVER_CANNOT_QUERY ) )
{
// The server isn't allowed to query this.
returnMsg . m_eStatusCode = eQueryCvarValueStatus_CvarProtected ;
}
else
{
returnMsg . m_eStatusCode = 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 . m_szCvarValue = tempValue ;
}
else
{
// The easy case..
returnMsg . m_szCvarValue = pVar - > GetString ( ) ;
}
}
}
else
{
if ( g_pCVar - > FindCommand ( msg - > m_szCvarName ) )
returnMsg . m_eStatusCode = eQueryCvarValueStatus_NotACvar ; // It's a command, not a cvar.
else
returnMsg . m_eStatusCode = eQueryCvarValueStatus_CvarNotFound ;
}
// Send back.
m_NetChannel - > SendNetMsg ( returnMsg ) ;
return true ;
}
// Returns dem file protocol version, or, if not playing a demo, just returns PROTOCOL_VERSION
int CBaseClientState : : GetDemoProtocolVersion ( ) const
{
# ifndef SWDS // why is demo play undefined? it shuold be fine on a dedicated server
if ( demoplayer - > IsPlayingBack ( ) )
{
return demoplayer - > GetProtocolVersion ( ) ;
}
# endif
return PROTOCOL_VERSION ;
}
bool CBaseClientState : : ProcessCmdKeyValues ( SVC_CmdKeyValues * msg )
{
return true ;
}
bool CBaseClientState : : IsClientConnectionViaMatchMaking ( void )
{
return ( V_strnistr ( cl_connectmethod . GetString ( ) , " quickplay " , 9 ) | | V_strnistr ( cl_connectmethod . GetString ( ) , " matchmaking " , 11 ) ) ;
}