//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: // //===========================================================================// #include "server_pch.h" #include "framesnapshot.h" #include "checksum_engine.h" #include "sv_main.h" #include "GameEventManager.h" #include "networkstringtable.h" #include "demo.h" #include "PlayerState.h" #include "tier0/vprof.h" #include "sv_packedentities.h" #include "LocalNetworkBackdoor.h" #include "testscriptmgr.h" #include "hltvserver.h" #include "pr_edict.h" #include "logofile_shared.h" #include "dt_send_eng.h" #include "sv_plugin.h" #include "download.h" #include "cmodel_engine.h" #include "tier1/CommandBuffer.h" #include "gl_cvars.h" #if defined( REPLAY_ENABLED ) #include "replayserver.h" #include "replay_internal.h" #endif #include "tier2/tier2.h" // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" extern CNetworkStringTableContainer *networkStringTableContainerServer; static ConVar sv_timeout( "sv_timeout", "65", 0, "After this many seconds without a message from a client, the client is dropped" ); static ConVar sv_maxrate( "sv_maxrate", "0", FCVAR_REPLICATED, "Max bandwidth rate allowed on server, 0 == unlimited" ); static ConVar sv_minrate( "sv_minrate", "3500", FCVAR_REPLICATED, "Min bandwidth rate allowed on server, 0 == unlimited" ); ConVar sv_maxupdaterate( "sv_maxupdaterate", "66", FCVAR_REPLICATED, "Maximum updates per second that the server will allow" ); ConVar sv_minupdaterate( "sv_minupdaterate", "10", FCVAR_REPLICATED, "Minimum updates per second that the server will allow" ); ConVar sv_stressbots("sv_stressbots", "0", FCVAR_DEVELOPMENTONLY, "If set to 1, the server calculates data and fills packets to bots. Used for perf testing."); static ConVar sv_allowdownload ("sv_allowdownload", "1", 0, "Allow clients to download files"); static ConVar sv_allowupload ("sv_allowupload", "1", 0, "Allow clients to upload customizations files"); ConVar sv_sendtables ( "sv_sendtables", "0", FCVAR_DEVELOPMENTONLY, "Force full sendtable sending path." ); extern ConVar sv_maxreplay; extern ConVar tv_snapshotrate; extern ConVar tv_transmitall; extern ConVar sv_pure_kick_clients; extern ConVar sv_pure_trace; // static ConVar sv_failuretime( "sv_failuretime", "0.5", 0, "After this long without a packet from client, don't send any more until client starts sending again" ); static const char * s_clcommands[] = { "status", "pause", "setpause", "unpause", "ping", "rpt_server_enable", "rpt_client_enable", #ifndef SWDS "rpt", "rpt_connect", "rpt_password", "rpt_screenshot", "rpt_download_log", #endif NULL, }; // Used on the server and on the client to bound its cl_rate cvar. int ClampClientRate( int nRate ) { if ( sv_maxrate.GetInt() > 0 ) { nRate = clamp( nRate, MIN_RATE, sv_maxrate.GetInt() ); } if ( sv_minrate.GetInt() > 0 ) { nRate = clamp( nRate, sv_minrate.GetInt(), MAX_RATE ); } return nRate; } CGameClient::CGameClient(int slot, CBaseServer *pServer ) { Clear(); m_nClientSlot = slot; m_nEntityIndex = slot+1; m_Server = pServer; m_pCurrentFrame = NULL; m_bIsInReplayMode = false; // NULL out data we'll never use. memset( &m_PrevPackInfo, 0, sizeof( m_PrevPackInfo ) ); m_PrevPackInfo.m_pTransmitEdict = &m_PrevTransmitEdict; } CGameClient::~CGameClient() { } bool CGameClient::ProcessClientInfo( CLC_ClientInfo *msg ) { CBaseClient::ProcessClientInfo( msg ); if ( m_bIsHLTV ) { // Likely spoofing, or misconfiguration. Don't let Disconnect pathway believe this is a replay bot, it will // asplode. m_bIsHLTV = false; Disconnect( "ProcessClientInfo: SourceTV can not connect to game directly.\n" ); return false; } #if defined( REPLAY_ENABLED ) if ( m_bIsReplay ) { // Likely spoofing, or misconfiguration. Don't let Disconnect pathway believe this is an hltv bot, it will // asplode. m_bIsReplay = false; Disconnect( "ProcessClientInfo: Replay can not connect to game directly.\n" ); return false; } #endif if ( sv_allowupload.GetBool() ) { // download all missing customizations files from this client; DownloadCustomizations(); } return true; } bool CGameClient::ProcessMove(CLC_Move *msg) { // Don't process usercmds until the client is active. If we do, there can be weird behavior // like the game trying to send reliable messages to the client and having those messages discarded. if ( !IsActive() ) return true; if ( m_LastMovementTick == sv.m_nTickCount ) { // Only one movement command per frame, someone is cheating. return true; } m_LastMovementTick = sv.m_nTickCount; int totalcmds =msg->m_nBackupCommands + msg->m_nNewCommands; // Decrement drop count by held back packet count int netdrop = m_NetChannel->GetDropNumber(); bool ignore = !sv.IsActive(); #ifdef SWDS bool paused = sv.IsPaused(); #else bool paused = sv.IsPaused() || ( !sv.IsMultiplayer() && Con_IsVisible() ); #endif // Make sure player knows of correct server time g_ServerGlobalVariables.curtime = sv.GetTime(); g_ServerGlobalVariables.frametime = host_state.interval_per_tick; // COM_Log( "sv.log", " executing %i move commands from client starting with command %i(%i)\n", // numcmds, // m_Client->m_NetChan->incoming_sequence, // m_Client->m_NetChan->incoming_sequence & SV_UPDATE_MASK ); int startbit = msg->m_DataIn.GetNumBitsRead(); serverGameClients->ProcessUsercmds ( edict, // Player edict &msg->m_DataIn, msg->m_nNewCommands, totalcmds, // Commands in packet netdrop, // Number of dropped commands ignore, // Don't actually run anything paused // Run, but don't actually do any movement ); if ( msg->m_DataIn.IsOverflowed() ) { Disconnect( "ProcessUsercmds: Overflowed reading usercmd data (check sending and receiving code for mismatches)!\n" ); return false; } int endbit = msg->m_DataIn.GetNumBitsRead(); if ( msg->m_nLength != (endbit-startbit) ) { Disconnect( "ProcessUsercmds: Incorrect reading frame (check sending and receiving code for mismatches)!\n" ); return false; } return true; } bool CGameClient::ProcessVoiceData( CLC_VoiceData *msg ) { char voiceDataBuffer[4096]; int bitsRead = msg->m_DataIn.ReadBitsClamped( voiceDataBuffer, msg->m_nLength ); SV_BroadcastVoiceData( this, Bits2Bytes(bitsRead), voiceDataBuffer, msg->m_xuid ); return true; } bool CGameClient::ProcessCmdKeyValues( CLC_CmdKeyValues *msg ) { serverGameClients->ClientCommandKeyValues( edict, msg->GetKeyValues() ); return true; } bool CGameClient::ProcessRespondCvarValue( CLC_RespondCvarValue *msg ) { if ( msg->m_iCookie > 0 ) { if ( g_pServerPluginHandler ) g_pServerPluginHandler->OnQueryCvarValueFinished( msg->m_iCookie, edict, msg->m_eStatusCode, msg->m_szCvarName, msg->m_szCvarValue ); } else { // Negative cookie means the game DLL asked for the value. if ( serverGameDLL && g_iServerGameDLLVersion >= 6 ) { #ifdef REL_TO_STAGING_MERGE_TODO serverGameDLL->OnQueryCvarValueFinished( msg->m_iCookie, edict, msg->m_eStatusCode, msg->m_szCvarName, msg->m_szCvarValue ); #endif } } return true; } #include "pure_server.h" bool CGameClient::ProcessFileCRCCheck( CLC_FileCRCCheck *msg ) { // Ignore this message if we're not in pure server mode... if ( !sv.IsInPureServerMode() ) return true; char warningStr[1024] = {0}; // The client may send us files we don't care about, so filter them here // if ( !sv.GetPureServerWhitelist()->GetForceMatchList()->IsFileInList( msg->m_szFilename ) ) // return true; // first check against all the other files users have sent FileHash_t filehash; filehash.m_md5contents = msg->m_MD5; filehash.m_crcIOSequence = msg->m_CRCIOs; filehash.m_eFileHashType = msg->m_eFileHashType; filehash.m_cbFileLen = msg->m_nFileFraction; filehash.m_nPackFileNumber = msg->m_nPackFileNumber; filehash.m_PackFileID = msg->m_PackFileID; const char *path = msg->m_szPathID; const char *fileName = msg->m_szFilename; if ( g_PureFileTracker.DoesFileMatch( path, fileName, msg->m_nFileFraction, &filehash, GetNetworkID() ) ) { // track successful file } else { V_snprintf( warningStr, sizeof( warningStr ), "Pure server: file [%s]\\%s does not match the server's file.", path, fileName ); } // still ToDo: // 1. make sure the user sends some files // 2. make sure the user doesnt skip any files // 3. make sure the user sends the right files... if ( warningStr[0] ) { if ( sv_pure_kick_clients.GetInt() ) { Disconnect( "%s", warningStr ); } else { ClientPrintf( "Warning: %s\n", warningStr ); if ( sv_pure_trace.GetInt() >= 1 ) { Msg( "[%s] %s\n", GetNetworkIDString(), warningStr ); } } } else { if ( sv_pure_trace.GetInt() >= 2 ) { Msg( "Pure server CRC check: client %s passed check for [%s]\\%s\n", GetClientName(), msg->m_szPathID, msg->m_szFilename ); } } return true; } bool CGameClient::ProcessFileMD5Check( CLC_FileMD5Check *msg ) { // Legacy message return true; } #if defined( REPLAY_ENABLED ) bool CGameClient::ProcessSaveReplay( CLC_SaveReplay *pMsg ) { // Don't allow on listen servers if ( !sv.IsDedicated() ) return false; if ( !g_pReplay ) return false; g_pReplay->SV_NotifyReplayRequested(); return true; } #endif void CGameClient::DownloadCustomizations() { for ( int i=0; iFileExists( hexname.m_Filename, "game" ) ) continue; // we already have it // we don't have it, request download from client m_nCustomFiles[i].reqID = m_NetChannel->RequestFile( hexname.m_Filename ); } } void CGameClient::Connect( const char * szName, int nUserID, INetChannel *pNetChannel, bool bFakePlayer, int clientChallenge ) { CBaseClient::Connect( szName, nUserID, pNetChannel, bFakePlayer, clientChallenge ); edict = EDICT_NUM( m_nEntityIndex ); // init PackInfo m_PackInfo.m_pClientEnt = edict; m_PackInfo.m_nPVSSize = sizeof( m_PackInfo.m_PVS ); // fire global game event - server only IGameEvent *event = g_GameEventManager.CreateEvent( "player_connect" ); { event->SetInt( "userid", m_UserID ); event->SetInt( "index", m_nClientSlot ); event->SetString( "name", m_Name ); event->SetString("networkid", GetNetworkIDString() ); event->SetString( "address", m_NetChannel?m_NetChannel->GetAddress():"none" ); event->SetInt( "bot", m_bFakePlayer?1:0 ); g_GameEventManager.FireEvent( event, true ); } // the only difference here is we don't send an // IP to prevent hackers from doing evil things event = g_GameEventManager.CreateEvent( "player_connect_client" ); if ( event ) { event->SetInt( "userid", m_UserID ); event->SetInt( "index", m_nClientSlot ); event->SetString( "name", m_Name ); event->SetString( "networkid", GetNetworkIDString() ); event->SetInt( "bot", m_bFakePlayer ? 1 : 0 ); g_GameEventManager.FireEvent( event ); } } void CGameClient::SetupPackInfo( CFrameSnapshot *pSnapshot ) { // Compute Vis for each client m_PackInfo.m_nPVSSize = (GetCollisionBSPData()->numclusters + 7) / 8; serverGameClients->ClientSetupVisibility( (edict_t *)m_pViewEntity, m_PackInfo.m_pClientEnt, m_PackInfo.m_PVS, m_PackInfo.m_nPVSSize ); // This is the frame we are creating, i.e., the next // frame after the last one that the client acknowledged m_pCurrentFrame = AllocateFrame(); m_pCurrentFrame->Init( pSnapshot ); m_PackInfo.m_pTransmitEdict = &m_pCurrentFrame->transmit_entity; // if this client is the HLTV or Replay client, add the nocheck PVS bit array // normal clients don't need that extra array #ifndef _XBOX #if defined( REPLAY_ENABLED ) if ( IsHLTV() || IsReplay() ) #else if ( IsHLTV() ) #endif { // the hltv client doesn't has a ClientFrame list m_pCurrentFrame->transmit_always = new CBitVec; m_PackInfo.m_pTransmitAlways = m_pCurrentFrame->transmit_always; } else #endif { m_PackInfo.m_pTransmitAlways = NULL; } // Add frame to ClientFrame list int nMaxFrames = MAX_CLIENT_FRAMES; if ( sv_maxreplay.GetFloat() > 0 ) { // if the server has replay features enabled, allow a way bigger frame buffer nMaxFrames = max ( (float)nMaxFrames, sv_maxreplay.GetFloat() / m_Server->GetTickInterval() ); } if ( nMaxFrames < AddClientFrame( m_pCurrentFrame ) ) { // If the client has more than 64 frames, the server will start to eat too much memory. RemoveOldestFrame(); } // Since area to area visibility is determined by each player's PVS, copy // the area network lookups into the ClientPackInfo_t m_PackInfo.m_AreasNetworked = 0; int areaCount = g_AreasNetworked.Count(); for ( int j = 0; j < areaCount; j++ ) { m_PackInfo.m_Areas[m_PackInfo.m_AreasNetworked] = g_AreasNetworked[ j ]; m_PackInfo.m_AreasNetworked++; // Msg("CGameClient::SetupPackInfo: too much areas (%i)", areaCount ); Assert( m_PackInfo.m_AreasNetworked < MAX_WORLD_AREAS ); } CM_SetupAreaFloodNums( m_PackInfo.m_AreaFloodNums, &m_PackInfo.m_nMapAreas ); } void CGameClient::SetupPrevPackInfo() { memcpy( &m_PrevTransmitEdict, m_PackInfo.m_pTransmitEdict, sizeof( m_PrevTransmitEdict ) ); // Copy the relevant fields into m_PrevPackInfo. m_PrevPackInfo.m_AreasNetworked = m_PackInfo.m_AreasNetworked; memcpy( m_PrevPackInfo.m_Areas, m_PackInfo.m_Areas, sizeof( m_PackInfo.m_Areas[0] ) * m_PackInfo.m_AreasNetworked ); m_PrevPackInfo.m_nPVSSize = m_PackInfo.m_nPVSSize; memcpy( m_PrevPackInfo.m_PVS, m_PackInfo.m_PVS, m_PackInfo.m_nPVSSize ); m_PrevPackInfo.m_nMapAreas = m_PackInfo.m_nMapAreas; memcpy( m_PrevPackInfo.m_AreaFloodNums, m_PackInfo.m_AreaFloodNums, m_PackInfo.m_nMapAreas * sizeof( m_PackInfo.m_nMapAreas ) ); } /* ================ CheckRate Make sure channel rate for active client is within server bounds ================ */ void CGameClient::SetRate(int nRate, bool bForce ) { if ( !bForce ) { nRate = ClampClientRate( nRate ); } CBaseClient::SetRate( nRate, bForce ); } void CGameClient::SetUpdateRate(int udpaterate, bool bForce) { if ( !bForce ) { if ( sv_maxupdaterate.GetInt() > 0 ) { udpaterate = clamp( udpaterate, 1, sv_maxupdaterate.GetInt() ); } if ( sv_minupdaterate.GetInt() > 0 ) { udpaterate = clamp( udpaterate, sv_minupdaterate.GetInt(), 100 ); } } CBaseClient::SetUpdateRate( udpaterate, bForce ); } void CGameClient::UpdateUserSettings() { // set voice loopback m_bVoiceLoopback = m_ConVars->GetInt( "voice_loopback", 0 ) != 0; CBaseClient::UpdateUserSettings(); // Give entity dll a chance to look at the changes. // Do this after CBaseClient::UpdateUserSettings() so name changes like prepending a (1) // take effect before the server dll sees the name. g_pServerPluginHandler->ClientSettingsChanged( edict ); } //----------------------------------------------------------------------------- // Purpose: A File has been received, if it's a logo, send it on to any other players who need it // and return true, otherwise, return false // Input : *cl - // *filename - // Output : Returns true on success, false on failure. /*----------------------------------------------------------------------------- bool CGameClient::ProcessIncomingLogo( const char *filename ) { char crcfilename[ 512 ]; char logohex[ 16 ]; Q_binarytohex( (byte *)&logo, sizeof( logo ), logohex, sizeof( logohex ) ); Q_snprintf( crcfilename, sizeof( crcfilename ), "materials/decals/downloads/%s.vtf", logohex ); // It's not a logo file? if ( Q_strcasecmp( filename, crcfilename ) ) { return false; } // First, make sure crc is valid CRC32_t check; CRC_File( &check, crcfilename ); if ( check != logo ) { ConMsg( "Incoming logo file didn't match player's logo CRC, ignoring\n" ); // Still note that it was a logo! return true; } // Okay, looks good, see if any other players need this logo file SV_SendLogo( check ); return true; } */ /* =================== SV_FullClientUpdate sends all the info about *cl to *sb =================== */ bool CGameClient::IsHearingClient( int index ) const { #if defined( REPLAY_ENABLED ) if ( IsHLTV() || IsReplay() ) #else if ( IsHLTV() ) #endif return true; if ( index == GetPlayerSlot() ) return m_bVoiceLoopback; CGameClient *pClient = sv.Client( index ); return pClient->m_VoiceStreams.Get( GetPlayerSlot() ) != 0; } bool CGameClient::IsProximityHearingClient( int index ) const { CGameClient *pClient = sv.Client( index ); return pClient->m_VoiceProximity.Get( GetPlayerSlot() ) != 0; } void CGameClient::Inactivate( void ) { if ( edict && !edict->IsFree() ) { m_Server->RemoveClientFromGame( this ); } #ifndef _XBOX if ( IsHLTV() ) { hltv->Changelevel(); } #if defined( REPLAY_ENABLED ) if ( IsReplay() ) { replay->Changelevel(); } #endif #endif CBaseClient::Inactivate(); m_Sounds.Purge(); m_VoiceStreams.ClearAll(); m_VoiceProximity.ClearAll(); DeleteClientFrames( -1 ); // delete all } bool CGameClient::UpdateAcknowledgedFramecount(int tick) { // free old client frames which won't be used anymore if ( tick != m_nDeltaTick ) { // delta tick changed, free all frames smaller than tick int removeTick = tick; if ( sv_maxreplay.GetFloat() > 0 ) removeTick -= (sv_maxreplay.GetFloat() / m_Server->GetTickInterval() ); // keep a replay buffer if ( removeTick > 0 ) { DeleteClientFrames( removeTick ); } } return CBaseClient::UpdateAcknowledgedFramecount( tick ); } void CGameClient::Clear() { #ifndef _XBOX if ( m_bIsHLTV && hltv ) { hltv->Shutdown(); } #if defined( REPLAY_ENABLED ) if ( m_bIsReplay && replay ) { replay->Shutdown(); } #endif #endif CBaseClient::Clear(); // free all frames DeleteClientFrames( -1 ); m_Sounds.Purge(); m_VoiceStreams.ClearAll(); m_VoiceProximity.ClearAll(); edict = NULL; m_pViewEntity = NULL; m_bVoiceLoopback = false; m_LastMovementTick = 0; m_nSoundSequence = 0; #if defined( REPLAY_ENABLED ) m_flLastSaveReplayTime = host_time; #endif } void CGameClient::Reconnect( void ) { // If the client was connected before, tell the game .dll to disconnect him/her. sv.RemoveClientFromGame( this ); CBaseClient::Reconnect(); } void CGameClient::Disconnect( const char *fmt, ... ) { va_list argptr; char reason[1024]; if ( m_nSignonState == SIGNONSTATE_NONE ) return; // no recursion va_start (argptr,fmt); Q_vsnprintf (reason, sizeof( reason ), fmt,argptr); va_end (argptr); // notify other clients of player leaving the game // send the username and network id so we don't depend on the CBasePlayer pointer IGameEvent *event = g_GameEventManager.CreateEvent( "player_disconnect" ); if ( event ) { event->SetInt("userid", GetUserID() ); event->SetString("reason", reason ); event->SetString("name", GetClientName() ); event->SetString("networkid", GetNetworkIDString() ); event->SetInt( "bot", m_bFakePlayer?1:0 ); g_GameEventManager.FireEvent( event ); } m_Server->RemoveClientFromGame( this ); CBaseClient::Disconnect( "%s", reason ); } bool CGameClient::SetSignonState(int state, int spawncount) { if ( state == SIGNONSTATE_CONNECTED ) { if ( !CheckConnect() ) return false; m_NetChannel->SetTimeout( SIGNON_TIME_OUT ); // allow 5 minutes to load map m_NetChannel->SetFileTransmissionMode( false ); m_NetChannel->SetMaxBufferSize( true, NET_MAX_PAYLOAD ); } else if ( state == SIGNONSTATE_NEW ) { if ( !sv.IsMultiplayer() ) { // local client as received and create string tables, // now link server tables to client tables SV_InstallClientStringTableMirrors(); } } else if ( state == SIGNONSTATE_FULL ) { if ( sv.m_bLoadgame ) { // If this game was loaded from savegame, finish restoring game now sv.FinishRestore(); } m_NetChannel->SetTimeout( sv_timeout.GetFloat() ); // use smaller timeout limit m_NetChannel->SetFileTransmissionMode( true ); #ifdef _XBOX // to save memory on the XBOX reduce reliable buffer size from 96 to 8 kB m_NetChannel->SetMaxBufferSize( true, 8*1024 ); #endif } return CBaseClient::SetSignonState( state, spawncount ); } void CGameClient::SendSound( SoundInfo_t &sound, bool isReliable ) { #if defined( REPLAY_ENABLED ) if ( IsFakeClient() && !IsHLTV() && !IsReplay() ) #else if ( IsFakeClient() && !IsHLTV() ) #endif { return; // dont send sound messages to bots } // don't send sound messages while client is replay mode if ( m_bIsInReplayMode ) { return; } // reliable sounds are send as single messages if ( isReliable ) { SVC_Sounds sndmsg; char buffer[32]; m_nSoundSequence = ( m_nSoundSequence + 1 ) & SOUND_SEQNUMBER_MASK; // increase own sound sequence counter sound.nSequenceNumber = 0; // don't transmit nSequenceNumber for reliable sounds sndmsg.m_DataOut.StartWriting(buffer, sizeof(buffer) ); sndmsg.m_nNumSounds = 1; sndmsg.m_bReliableSound = true; SoundInfo_t defaultSound; defaultSound.SetDefault(); sound.WriteDelta( &defaultSound, sndmsg.m_DataOut ); // send reliable sound as single message SendNetMsg( sndmsg, true ); return; } sound.nSequenceNumber = m_nSoundSequence; m_Sounds.AddToTail( sound ); // queue sounds until snapshot is send } void CGameClient::WriteGameSounds( bf_write &buf ) { if ( m_Sounds.Count() <= 0 ) return; char data[NET_MAX_PAYLOAD]; SVC_Sounds msg; msg.m_DataOut.StartWriting( data, sizeof(data) ); int nSoundCount = FillSoundsMessage( msg ); msg.WriteToBuffer( buf ); if ( IsTracing() ) { TraceNetworkData( buf, "Sounds [count=%d]", nSoundCount ); } } int CGameClient::FillSoundsMessage(SVC_Sounds &msg) { int i, count = m_Sounds.Count(); // send max 64 sound in multiplayer per snapshot, 255 in SP int max = m_Server->IsMultiplayer() ? 32 : 255; // Discard events if we have too many to signal with 8 bits if ( count > max ) count = max; // Nothing to send if ( !count ) return 0; SoundInfo_t defaultSound; defaultSound.SetDefault(); SoundInfo_t *pDeltaSound = &defaultSound; msg.m_nNumSounds = count; msg.m_bReliableSound = false; msg.SetReliable( false ); Assert( msg.m_DataOut.GetNumBitsLeft() > 0 ); for ( i = 0 ; i < count; i++ ) { SoundInfo_t &sound = m_Sounds[ i ]; sound.WriteDelta( pDeltaSound, msg.m_DataOut ); pDeltaSound = &m_Sounds[ i ]; } // remove added events from list int remove = m_Sounds.Count() - ( count + max ); if ( remove > 0 ) { DevMsg("Warning! Dropped %i unreliable sounds for client %s.\n" , remove, m_Name ); count+= remove; } if ( count > 0 ) { m_Sounds.RemoveMultiple( 0, count ); } Assert( m_Sounds.Count() <= max ); // keep ev_max temp ent for next update return msg.m_nNumSounds; } bool CGameClient::CheckConnect( void ) { // Allow the game dll to reject this client. char szRejectReason[128]; Q_strncpy( szRejectReason, "Connection rejected by game\n", sizeof( szRejectReason ) ); if ( !g_pServerPluginHandler->ClientConnect( edict, m_Name, m_NetChannel->GetAddress(), szRejectReason, sizeof( szRejectReason ) ) ) { // Reject the connection and drop the client. Disconnect( szRejectReason, m_Name ); return false; } return true; } void CGameClient::ActivatePlayer( void ) { CBaseClient::ActivatePlayer(); COM_TimestampedLog( "CGameClient::ActivatePlayer -start" ); // call the spawn function if ( !sv.m_bLoadgame ) { g_ServerGlobalVariables.curtime = sv.GetTime(); COM_TimestampedLog( "g_pServerPluginHandler->ClientPutInServer" ); g_pServerPluginHandler->ClientPutInServer( edict, m_Name ); } COM_TimestampedLog( "g_pServerPluginHandler->ClientActive" ); g_pServerPluginHandler->ClientActive( edict, sv.m_bLoadgame ); COM_TimestampedLog( "g_pServerPluginHandler->ClientSettingsChanged" ); g_pServerPluginHandler->ClientSettingsChanged( edict ); COM_TimestampedLog( "GetTestScriptMgr()->CheckPoint" ); GetTestScriptMgr()->CheckPoint( "client_connected" ); // don't send signonstate to client, client will switch to FULL as soon // as the first full entity update packets has been received // fire a activate event IGameEvent *event = g_GameEventManager.CreateEvent( "player_activate" ); if ( event ) { event->SetInt( "userid", GetUserID() ); g_GameEventManager.FireEvent( event ); } COM_TimestampedLog( "CGameClient::ActivatePlayer -end" ); } bool CGameClient::SendSignonData( void ) { bool bClientHasdifferentTables = false; if ( sv.m_FullSendTables.IsOverflowed() ) { Host_Error( "Send Table signon buffer overflowed %i bytes!!!\n", sv.m_FullSendTables.GetNumBytesWritten() ); return false; } if ( SendTable_GetCRC() != (CRC32_t)0 ) { bClientHasdifferentTables = m_nSendtableCRC != SendTable_GetCRC(); } #ifdef _DEBUG if ( sv_sendtables.GetInt() == 2 ) { // force sending class tables, for debugging bClientHasdifferentTables = true; } #endif // Write the send tables & class infos if needed if ( bClientHasdifferentTables ) { if ( sv_sendtables.GetBool() ) { // send client class table descriptions so it can rebuild tables ConDMsg("Client sent different SendTable CRC, sending full tables.\n" ); m_NetChannel->SendData( sv.m_FullSendTables ); } else { Disconnect( "Server uses different class tables" ); return false; } } else { // use your class infos, CRC is correct SVC_ClassInfo classmsg( true, m_Server->serverclasses ); m_NetChannel->SendNetMsg( classmsg ); } if ( !CBaseClient::SendSignonData() ) return false; m_nSoundSequence = 1; // reset sound sequence numbers after signon block return true; } void CGameClient::SpawnPlayer( void ) { // run the entrance script if ( sv.m_bLoadgame ) { // loaded games are fully inited already // if this is the last client to be connected, unpause sv.SetPaused( false ); } else { // set up the edict Assert( serverGameEnts ); serverGameEnts->FreeContainingEntity( edict ); InitializeEntityDLLFields( edict ); } // restore default client entity and turn off replay mdoe m_nEntityIndex = m_nClientSlot+1; m_bIsInReplayMode = false; // set view entity SVC_SetView setView( m_nEntityIndex ); SendNetMsg( setView ); CBaseClient::SpawnPlayer(); // notify that the player is spawning serverGameClients->ClientSpawned( edict ); } CClientFrame *CGameClient::GetDeltaFrame( int nTick ) { #ifndef _XBOX Assert ( !IsHLTV() ); // has no ClientFrames #if defined( REPLAY_ENABLED ) Assert ( !IsReplay() ); // has no ClientFrames #endif #endif if ( m_bIsInReplayMode ) { int followEntity; serverGameClients->GetReplayDelay( edict, followEntity ); Assert( followEntity > 0 ); CGameClient *pFollowEntity = sv.Client( followEntity-1 ); if ( pFollowEntity ) return pFollowEntity->GetClientFrame( nTick ); } return GetClientFrame( nTick ); } void CGameClient::WriteViewAngleUpdate() { // // send the current viewpos offset from the view entity // // a fixangle might get lost in a dropped packet. Oh well. if ( IsFakeClient() ) return; Assert( serverGameClients ); CPlayerState *pl = serverGameClients->GetPlayerState( edict ); Assert( pl ); if ( pl && pl->fixangle != FIXANGLE_NONE ) { if ( pl->fixangle == FIXANGLE_RELATIVE ) { SVC_FixAngle fixAngle( true, pl->anglechange ); m_NetChannel->SendNetMsg( fixAngle ); pl->anglechange.Init(); // clear } else { SVC_FixAngle fixAngle(false, pl->v_angle ); m_NetChannel->SendNetMsg( fixAngle ); } pl->fixangle = FIXANGLE_NONE; } } /* =================== SV_ValidateClientCommand Determine if passed in user command is valid. =================== */ bool CGameClient::IsEngineClientCommand( const CCommand &args ) const { if ( args.ArgC() == 0 ) return false; for ( int i = 0; s_clcommands[i] != NULL; ++i ) { if ( !Q_strcasecmp( args[0], s_clcommands[i] ) ) return true; } return false; } bool CGameClient::SendNetMsg(INetMessage &msg, bool bForceReliable) { #ifndef _XBOX if ( m_bIsHLTV ) { // pass this message to HLTV return hltv->SendNetMsg( msg, bForceReliable ); } #if defined( REPLAY_ENABLED ) if ( m_bIsReplay ) { // pass this message to replay return replay->SendNetMsg( msg, bForceReliable ); } #endif #endif return CBaseClient::SendNetMsg( msg, bForceReliable); } bool CGameClient::ExecuteStringCommand( const char *pCommandString ) { // first let the baseclass handle it if ( CBaseClient::ExecuteStringCommand( pCommandString ) ) return true; // Determine whether the command is appropriate CCommand args; if ( !args.Tokenize( pCommandString ) ) return false; if ( args.ArgC() == 0 ) return false; if ( IsEngineClientCommand( args ) ) { Cmd_ExecuteCommand( args, src_client, m_nClientSlot ); return true; } const ConCommandBase *pCommand = g_pCVar->FindCommandBase( args[ 0 ] ); if ( pCommand && pCommand->IsCommand() && pCommand->IsFlagSet( FCVAR_GAMEDLL ) ) { // Allow cheat commands in singleplayer, debug, or multiplayer with sv_cheats on // NOTE: Don't bother with rpt stuff; commands that matter there shouldn't have FCVAR_GAMEDLL set if ( pCommand->IsFlagSet( FCVAR_CHEAT ) ) { if ( sv.IsMultiplayer() && !CanCheat() ) return false; } if ( pCommand->IsFlagSet( FCVAR_SPONLY ) ) { if ( sv.IsMultiplayer() ) { return false; } } // Don't allow clients to execute commands marked as development only. if ( pCommand->IsFlagSet( FCVAR_DEVELOPMENTONLY ) ) { return false; } g_pServerPluginHandler->SetCommandClient( m_nClientSlot ); Cmd_Dispatch( pCommand, args ); } else { g_pServerPluginHandler->ClientCommand( edict, args ); // TODO pass client id and string } return true; } void CGameClient::SendSnapshot( CClientFrame * pFrame ) { if ( m_bIsHLTV ) { #ifndef SHARED_NET_STRING_TABLES // copy string updates from server to hltv stringtable networkStringTableContainerServer->DirectUpdate( GetMaxAckTickCount() ); #endif char *buf = (char *)_alloca( NET_MAX_PAYLOAD ); // pack sounds to one message if ( m_Sounds.Count() > 0 ) { SVC_Sounds sounds; sounds.m_DataOut.StartWriting( buf, NET_MAX_PAYLOAD ); FillSoundsMessage( sounds ); hltv->SendNetMsg( sounds ); } int maxEnts = tv_transmitall.GetBool()?255:64; hltv->WriteTempEntities( this, pFrame->GetSnapshot(), m_pLastSnapshot.GetObject(), *hltv->GetBuffer( HLTV_BUFFER_TEMPENTS ), maxEnts ); // add snapshot to HLTV server frame list hltv->AddNewFrame( pFrame ); // remember this snapshot m_pLastSnapshot = pFrame->GetSnapshot(); // fake acknowledgement, remove ClientFrame reference immediately UpdateAcknowledgedFramecount( pFrame->tick_count ); return; } #if defined( REPLAY_ENABLED ) if ( m_bIsReplay ) { #ifndef SHARED_NET_STRING_TABLES // copy string updates from server to replay stringtable networkStringTableContainerServer->DirectUpdate( GetMaxAckTickCount() ); #endif char *buf = (char *)_alloca( NET_MAX_PAYLOAD ); // pack sounds to one message if ( m_Sounds.Count() > 0 ) { SVC_Sounds sounds; sounds.m_DataOut.StartWriting( buf, NET_MAX_PAYLOAD ); FillSoundsMessage( sounds ); replay->SendNetMsg( sounds ); } int maxEnts = 255; replay->WriteTempEntities( this, pFrame->GetSnapshot(), m_pLastSnapshot.GetObject(), *replay->GetBuffer( REPLAY_BUFFER_TEMPENTS ), maxEnts ); // add snapshot to Replay server frame list if ( replay->AddNewFrame( pFrame ) ) { // remember this snapshot m_pLastSnapshot = pFrame->GetSnapshot(); // fake acknowledgement, remove ClientFrame reference immediately UpdateAcknowledgedFramecount( pFrame->tick_count ); } return; } #endif // update client viewangles update WriteViewAngleUpdate(); CBaseClient::SendSnapshot( pFrame ); } //----------------------------------------------------------------------------- // This function contains all the logic to determine if we should send a datagram // to a particular client //----------------------------------------------------------------------------- bool CGameClient::ShouldSendMessages( void ) { #ifndef _XBOX if ( m_bIsHLTV ) { // calc snapshot interval int nSnapshotInterval = 1.0f / ( m_Server->GetTickInterval() * tv_snapshotrate.GetFloat() ); // I am the HLTV client, record every nSnapshotInterval tick return ( sv.m_nTickCount >= (hltv->m_nLastTick + nSnapshotInterval) ); } #if defined( REPLAY_ENABLED ) if ( m_bIsReplay ) { const float replay_snapshotrate = 16.0f; // calc snapshot interval int nSnapshotInterval = 1.0f / ( m_Server->GetTickInterval() * replay_snapshotrate ); // I am the Replay client, record every nSnapshotInterval tick return ( sv.m_nTickCount >= (replay->m_nLastTick + nSnapshotInterval) ); } #endif #endif // If sv_stressbots is true, then treat a bot more like a regular client and do deltas and such for it. if( IsFakeClient() ) { if ( !sv_stressbots.GetBool() ) return false; } return CBaseClient::ShouldSendMessages(); } void CGameClient::FileReceived( const char *fileName, unsigned int transferID ) { //check if file is one of our requested custom files for ( int i=0; iUserInfoChanged( m_nClientSlot ); return; } } Msg( "CGameClient::FileReceived: %s not wanted.\n", fileName ); } void CGameClient::FileRequested(const char *fileName, unsigned int transferID ) { DevMsg( "File '%s' requested from client %s.\n", fileName, m_NetChannel->GetAddress() ); if ( sv_allowdownload.GetBool() ) { m_NetChannel->SendFile( fileName, transferID ); } else { m_NetChannel->DenyFile( fileName, transferID ); } } void CGameClient::FileDenied(const char *fileName, unsigned int transferID ) { ConMsg( "Downloading file '%s' from client %s failed.\n", fileName, GetClientName() ); } void CGameClient::FileSent( const char *fileName, unsigned int transferID ) { ConMsg( "Sent file '%s' to client %s.\n", fileName, GetClientName() ); } void CGameClient::PacketStart(int incoming_sequence, int outgoing_acknowledged) { // make sure m_LastMovementTick != sv.tickcount m_LastMovementTick = ( sv.m_nTickCount - 1 ); host_client = this; // During connection, only respond if client sends a packet m_bReceivedPacket = true; } void CGameClient::PacketEnd() { // Fix up clock in case prediction/etc. code reset it. g_ServerGlobalVariables.frametime = host_state.interval_per_tick; } void CGameClient::ConnectionClosing(const char *reason) { #ifndef _XBOX SV_RedirectEnd (); #endif // Check for printf format tokens in this reason string. Crash exploit. Disconnect ( (reason && !strchr( reason, '%' ) ) ? reason : "Connection closing" ); } void CGameClient::ConnectionCrashed(const char *reason) { if ( m_Name[0] && IsConnected() ) { DebuggerBreakIfDebugging_StagingOnly(); #ifndef _XBOX SV_RedirectEnd (); #endif // Check for printf format tokens in this reason string. Crash exploit. Disconnect ( (reason && !strchr( reason, '%' ) ) ? reason : "Connection lost" ); } } CClientFrame *CGameClient::GetSendFrame() { CClientFrame *pFrame = m_pCurrentFrame; // just return if replay is disabled if ( sv_maxreplay.GetFloat() <= 0 ) return pFrame; int followEntity; int delayTicks = serverGameClients->GetReplayDelay( edict, followEntity ); bool isInReplayMode = ( delayTicks > 0 ); if ( isInReplayMode != m_bIsInReplayMode ) { // force a full update when modes are switched m_nDeltaTick = -1; m_bIsInReplayMode = isInReplayMode; if ( isInReplayMode ) { m_nEntityIndex = followEntity; } else { m_nEntityIndex = m_nClientSlot+1; } } Assert( (m_nClientSlot+1 == m_nEntityIndex) || isInReplayMode ); if ( isInReplayMode ) { CGameClient *pFollowPlayer = sv.Client( followEntity-1 ); if ( !pFollowPlayer ) return NULL; pFrame = pFollowPlayer->GetClientFrame( sv.GetTick() - delayTicks, false ); if ( !pFrame ) return NULL; if ( m_pLastSnapshot == pFrame->GetSnapshot() ) return NULL; } return pFrame; } bool CGameClient::IgnoreTempEntity( CEventInfo *event ) { // in replay mode replay all temp entities if ( m_bIsInReplayMode ) return false; return CBaseClient::IgnoreTempEntity( event ); } const CCheckTransmitInfo* CGameClient::GetPrevPackInfo() { return &m_PrevPackInfo; } // This code is useful for verifying that the networking of soundinfo_t stuff isn't borked. #if 0 #include "vstdlib/random.h" class CTestSoundInfoNetworking { public: CTestSoundInfoNetworking(); void RunTest(); private: void CreateRandomSounds( int nCount ); void CreateRandomSound( SoundInfo_t &si ); void Compare( const SoundInfo_t &s1, const SoundInfo_t &s2 ); CUtlVector< SoundInfo_t > m_Sounds; CUtlVector< SoundInfo_t > m_Received; }; static CTestSoundInfoNetworking g_SoundTest; CON_COMMAND( st, "sound test" ) { int nCount = 1; if ( args.ArgC() >= 2 ) { nCount = clamp( Q_atoi( args.Arg( 1 ) ), 1, 100000 ); } for ( int i = 0 ; i < nCount; ++i ) { if ( !( i % 100 ) && i > 0 ) { Msg( "Running test %d %f %% done\n", i, 100.0f * (float)i/(float)nCount ); } g_SoundTest.RunTest(); } } CTestSoundInfoNetworking::CTestSoundInfoNetworking() { } void CTestSoundInfoNetworking::CreateRandomSound( SoundInfo_t &si ) { int entindex = RandomInt( 0, MAX_EDICTS - 1 ); int channel = RandomInt( 0, 7 ); int soundnum = RandomInt( 0, MAX_SOUNDS - 1 ); Vector org = RandomVector( -16383, 16383 ); Vector dir = RandomVector( -1.0f, 1.0f ); float flVolume = RandomFloat( 0.1f, 1.0f ); bool bLooping = RandomInt( 0, 100 ) < 5; int nPitch = RandomInt( 0, 100 ) < 5 ? RandomInt( 95, 105 ) : 100; Vector lo = RandomInt( 0, 100 ) < 5 ? RandomVector( -16383, 16383 ) : org; int speaker = RandomInt( 0, 100 ) < 2 ? RandomInt( 0, MAX_EDICTS - 1 ) : -1; soundlevel_t level = soundlevel_t(RandomInt( 70, 150 )); si.Set( entindex, channel, "foo.wav", org, dir, flVolume, level, bLooping, nPitch, lo, speaker ); si.nFlags = ( 1 << RandomInt( 0, 6 ) ); si.nSoundNum = soundnum; si.bIsSentence = RandomInt( 0, 1 ); si.bIsAmbient = RandomInt( 0, 1 ); si.fDelay = RandomInt( 0, 100 ) < 2 ? RandomFloat( -0.1, 0.1f ) : 0.0f; } void CTestSoundInfoNetworking::CreateRandomSounds( int nCount ) { m_Sounds.Purge(); m_Sounds.EnsureCount( nCount ); for ( int i = 0; i < nCount; ++i ) { SoundInfo_t &si = m_Sounds[ i ]; CreateRandomSound( si ); } } void CTestSoundInfoNetworking::RunTest() { int m_nSoundSequence = 0; CreateRandomSounds( 512 ); SoundInfo_t defaultSound; defaultSound.SetDefault(); SoundInfo_t *pDeltaSound = &defaultSound; SVC_Sounds msg; char *buf = (char *)_alloca( NET_MAX_PAYLOAD ); msg.m_DataOut.StartWriting( buf, NET_MAX_PAYLOAD ); msg.m_nNumSounds = m_Sounds.Count(); msg.m_bReliableSound = false; msg.SetReliable( false ); Assert( msg.m_DataOut.GetNumBitsLeft() > 0 ); for ( int i = 0 ; i < m_Sounds.Count(); i++ ) { SoundInfo_t &sound = m_Sounds[ i ]; sound.WriteDelta( pDeltaSound, msg.m_DataOut ); pDeltaSound = &m_Sounds[ i ]; } // Now read them out defaultSound.SetDefault(); pDeltaSound = &defaultSound; msg.m_DataIn.StartReading( buf, msg.m_DataOut.GetNumBytesWritten(), 0, msg.m_DataOut.GetNumBitsWritten() ); SoundInfo_t sound; for ( int i=0; i 32.0f ) { Msg( "origin mismatch [%f] (%f %f %f) != (%f %f %f)\n", d.Length(), s1.vOrigin.x, s1.vOrigin.y, s1.vOrigin.z, s2.vOrigin.x, s2.vOrigin.y, s2.vOrigin.z ); } // Vector vDirection; float delta = fabs( s1.fVolume - s2.fVolume ); if ( !bSndStop && delta > 1.0f ) { Msg( "vol mismatch %f %f\n", s1.fVolume, s2.fVolume ); } if ( !bSndStop && s1.Soundlevel != s2.Soundlevel ) { Msg( "sndlvl mismatch %d %d\n", s1.Soundlevel, s2.Soundlevel ); } // bLooping; if ( s1.bIsSentence != s2.bIsSentence ) { Msg( "sentence mismatch %d %d\n", s1.bIsSentence ? 1 : 0, s2.bIsSentence ? 1 : 0 ); } if ( s1.bIsAmbient != s2.bIsAmbient ) { Msg( "ambient mismatch %d %d\n", s1.bIsAmbient ? 1 : 0, s2.bIsAmbient ? 1 : 0 ); } if ( !bSndStop && s1.nPitch != s2.nPitch ) { Msg( "pitch mismatch %d %d\n", s1.nPitch, s2.nPitch ); } if ( !bSndStop && s1.nSpecialDSP != s2.nSpecialDSP ) { Msg( "special dsp mismatch %d %d\n", s1.nSpecialDSP, s2.nSpecialDSP ); } // Vector vListenerOrigin; if ( s1.nFlags != s2.nFlags ) { Msg( "flags mismatch %d %d\n", s1.nFlags, s2.nFlags ); } if ( s1.nSoundNum != s2.nSoundNum ) { Msg( "soundnum mismatch %d %d\n", s1.nSoundNum, s2.nSoundNum ); } delta = fabs( s1.fDelay - s2.fDelay ); if ( !bSndStop && delta > 0.020f ) { Msg( "delay mismatch %f %f\n", s1.fDelay, s2.fDelay ); } if ( !bSndStop && s1.nSpeakerEntity != s2.nSpeakerEntity ) { Msg( "speakerentity mismatch %d %d\n", s1.nSpeakerEntity, s2.nSpeakerEntity ); } } #endif