You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1855 lines
51 KiB
1855 lines
51 KiB
//========= 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 !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() ); |
|
} |
|
|
|
#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 ( 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; |
|
} |
|
} |
|
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 ) ); |
|
} |
|
|
|
|
|
|