//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: // // $NoKeywords: $ // //=============================================================================// // hltvserver.cpp: implementation of the CHLTVServer class. // ////////////////////////////////////////////////////////////////////// #include #include #include #include #include #include #include #include #include #include #include "hltvserver.h" #include "sv_client.h" #include "hltvclient.h" #include "server.h" #include "sv_main.h" #include "framesnapshot.h" #include "networkstringtable.h" #include "cmodel_engine.h" #include "dt_recv_eng.h" #include "cdll_engine_int.h" #include "GameEventManager.h" #include "host.h" #include "proto_version.h" #include "proto_oob.h" #include "dt_common_eng.h" #include "baseautocompletefilelist.h" #include "sv_steamauth.h" #include "tier0/icommandline.h" #include "sys_dll.h" // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" #define S2A_EXTRA_DATA_HAS_GAME_PORT 0x80 // Next 2 bytes include the game port. #define S2A_EXTRA_DATA_HAS_SPECTATOR_DATA 0x40 // Next 2 bytes include the spectator port, then the spectator server name. #define S2A_EXTRA_DATA_HAS_GAMETAG_DATA 0x20 // Next bytes are the game tag string #define S2A_EXTRA_DATA_HAS_STEAMID 0x10 // Next 8 bytes are the steamID #define S2A_EXTRA_DATA_GAMEID 0x01 // Next 8 bytes are the gameID of the server #define A2S_KEY_STRING_STEAM "Source Engine Query" // required postfix to a A2S_INFO query extern CNetworkStringTableContainer *networkStringTableContainerClient; extern ConVar sv_tags; ////////////////////////////////////////////////////////////////////// // Construction/Destruction ////////////////////////////////////////////////////////////////////// CHLTVServer *hltv = NULL; static void tv_title_changed_f( IConVar *var, const char *pOldString, float flOldValue ) { if ( hltv && hltv->IsActive() ) { hltv->BroadcastLocalTitle(); } } static void tv_name_changed_f( IConVar *var, const char *pOldValue, float flOldValue ) { Steam3Server().NotifyOfServerNameChange(); } static ConVar tv_maxclients( "tv_maxclients", "128", 0, "Maximum client number on SourceTV server.", true, 0, true, 255 ); ConVar tv_autorecord( "tv_autorecord", "0", 0, "Automatically records all games as SourceTV demos." ); ConVar tv_name( "tv_name", "SourceTV", 0, "SourceTV host name", tv_name_changed_f ); static ConVar tv_password( "tv_password", "", FCVAR_NOTIFY | FCVAR_PROTECTED | FCVAR_DONTRECORD, "SourceTV password for all clients" ); static ConVar tv_overridemaster( "tv_overridemaster", "0", 0, "Overrides the SourceTV master root address." ); static ConVar tv_dispatchmode( "tv_dispatchmode", "1", 0, "Dispatch clients to relay proxies: 0=never, 1=if appropriate, 2=always" ); ConVar tv_transmitall( "tv_transmitall", "0", FCVAR_REPLICATED, "Transmit all entities (not only director view)" ); ConVar tv_debug( "tv_debug", "0", 0, "SourceTV debug info." ); ConVar tv_title( "tv_title", "SourceTV", 0, "Set title for SourceTV spectator UI", tv_title_changed_f ); static ConVar tv_deltacache( "tv_deltacache", "2", 0, "Enable delta entity bit stream cache" ); static ConVar tv_relayvoice( "tv_relayvoice", "1", 0, "Relay voice data: 0=off, 1=on" ); CDeltaEntityCache::CDeltaEntityCache() { Q_memset( m_Cache, 0, sizeof(m_Cache) ); m_nTick = 0; m_nMaxEntities = 0; m_nCacheSize = 0; } CDeltaEntityCache::~CDeltaEntityCache() { Flush(); } void CDeltaEntityCache::Flush() { if ( m_nMaxEntities != 0 ) { // at least one entity was set for ( int i=0; i= m_nMaxEntities ) return NULL; DeltaEntityEntry_s *pEntry = m_Cache[nEntityIndex]; while ( pEntry ) { if ( pEntry->nDeltaTick == nDeltaTick ) { nBits = pEntry->nBits; return (unsigned char*)(pEntry) + sizeof(DeltaEntityEntry_s); } else { // keep searching entry list pEntry = pEntry->pNext; } } return NULL; } void CDeltaEntityCache::AddDeltaBits( int nEntityIndex, int nDeltaTick, int nBits, bf_write *pBuffer ) { if ( nEntityIndex < 0 || nEntityIndex >= m_nMaxEntities || m_nCacheSize <= 0 ) return; int nBufferSize = PAD_NUMBER( Bits2Bytes(nBits), 4); DeltaEntityEntry_s *pEntry = m_Cache[nEntityIndex]; if ( pEntry == NULL ) { if ( (int)(nBufferSize+sizeof(DeltaEntityEntry_s)) > m_nCacheSize ) return; // way too big, don't even create an entry pEntry = m_Cache[nEntityIndex] = (DeltaEntityEntry_s *) malloc( m_nCacheSize ); } else { char *pEnd = (char*)(pEntry) + m_nCacheSize; // end marker while( pEntry->pNext ) { pEntry = pEntry->pNext; } int entrySize = sizeof(DeltaEntityEntry_s) + PAD_NUMBER( Bits2Bytes(pEntry->nBits), 4); DeltaEntityEntry_s *pNew = (DeltaEntityEntry_s*)((char*)(pEntry) + entrySize); if ( ((char*)(pNew) + sizeof(DeltaEntityEntry_s) + nBufferSize) > pEnd ) return; // data wouldn't fit into cache anymore, don't add new entries pEntry = pNew; pEntry->pNext = pEntry; } pEntry->pNext = NULL; // link to next pEntry->nDeltaTick = nDeltaTick; pEntry->nBits = nBits; if ( nBits > 0 ) { bf_read inBuffer; inBuffer.StartReading( pBuffer->GetData(), pBuffer->m_nDataBytes, pBuffer->GetNumBitsWritten() ); bf_write outBuffer( (char*)(pEntry) + sizeof(DeltaEntityEntry_s), nBufferSize ); outBuffer.WriteBitsFromBuffer( &inBuffer, nBits ); } } static RecvTable* FindRecvTable( const char *pName, RecvTable **pRecvTables, int nRecvTables ) { for ( int i=0; i< nRecvTables; i++ ) { if ( !Q_strcmp( pName, pRecvTables[i]->GetName() ) ) return pRecvTables[i]; } return NULL; } static RecvTable* AddRecvTableR( SendTable *sendt, RecvTable **pRecvTables, int &nRecvTables ) { RecvTable *recvt = FindRecvTable( sendt->m_pNetTableName, pRecvTables, nRecvTables ); if ( recvt ) return recvt; // already in list if ( sendt->m_nProps > 0 ) { RecvProp *receiveProps = new RecvProp[sendt->m_nProps]; for ( int i=0; i < sendt->m_nProps; i++ ) { // copy property data SendProp * sp = sendt->GetProp( i ); RecvProp * rp = &receiveProps[i]; rp->m_pVarName = sp->m_pVarName; rp->m_RecvType = sp->m_Type; if ( sp->IsExcludeProp() ) { // if prop is excluded, give different name rp->m_pVarName = "IsExcludedProp"; } if ( sp->IsInsideArray() ) { rp->SetInsideArray(); rp->m_pVarName = "InsideArrayProp"; // give different name } if ( sp->GetType() == DPT_Array ) { Assert ( sp->GetArrayProp() == sendt->GetProp( i-1 ) ); Assert( receiveProps[i-1].IsInsideArray() ); rp->SetArrayProp( &receiveProps[i-1] ); rp->InitArray( sp->m_nElements, sp->m_ElementStride ); } if ( sp->GetType() == DPT_DataTable ) { // recursive create Assert ( sp->GetDataTable() ); RecvTable *subTable = AddRecvTableR( sp->GetDataTable(), pRecvTables, nRecvTables ); rp->SetDataTable( subTable ); } } recvt = new RecvTable( receiveProps, sendt->m_nProps, sendt->m_pNetTableName ); } else { // table with no properties recvt = new RecvTable( NULL, 0, sendt->m_pNetTableName ); } pRecvTables[nRecvTables] = recvt; nRecvTables++; return recvt; } void CHLTVServer::FreeClientRecvTables() { for ( int i=0; i< m_nRecvTables; i++ ) { RecvTable *rt = m_pRecvTables[i]; // delete recv table props if ( rt->m_pProps ) { Assert( rt->m_nProps > 0 ); delete [] rt->m_pProps; } // delete the table itself delete rt; } Q_memset( m_pRecvTables, 0, sizeof( m_pRecvTables ) ); m_nRecvTables = 0; } // creates client receive tables from server send tables void CHLTVServer::InitClientRecvTables() { ServerClass* pCur = NULL; if ( ClientDLL_GetAllClasses() != NULL ) return; //already initialized // first create all SendTables for ( pCur = serverGameDLL->GetAllServerClasses(); pCur; pCur=pCur->m_pNext ) { // create receive table from send table. AddRecvTableR( pCur->m_pTable, m_pRecvTables, m_nRecvTables ); ErrorIfNot( m_nRecvTables < ARRAYSIZE( m_pRecvTables ), ("AddRecvTableR: overflowed MAX_DATATABLES") ); } // now register client classes for ( pCur = serverGameDLL->GetAllServerClasses(); pCur; pCur=pCur->m_pNext ) { ErrorIfNot( m_nRecvTables < ARRAYSIZE( m_pRecvTables ), ("ClientDLL_InitRecvTableMgr: overflowed MAX_DATATABLES") ); // find top receive table for class RecvTable * recvt = FindRecvTable( pCur->m_pTable->GetName(), m_pRecvTables, m_nRecvTables ); Assert ( recvt ); // register class, constructor addes clientClass to g_pClientClassHead list ClientClass * clientclass = new ClientClass( pCur->m_pNetworkName, NULL, NULL, recvt ); if ( !clientclass ) { Msg("HLTV_InitRecvTableMgr: failed to allocate client class %s.\n", pCur->m_pNetworkName ); return; } } RecvTable_Init( m_pRecvTables, m_nRecvTables ); } CHLTVFrame::CHLTVFrame() { } CHLTVFrame::~CHLTVFrame() { FreeBuffers(); } void CHLTVFrame::Reset( void ) { for ( int i=0; i 0 ) return true; } return false; } void CHLTVFrame::CopyHLTVData( CHLTVFrame &frame ) { // copy reliable messages int bits = frame.m_Messages[HLTV_BUFFER_RELIABLE].GetNumBitsWritten(); if ( bits > 0 ) { int bytes = PAD_NUMBER( Bits2Bytes(bits), 4 ); m_Messages[HLTV_BUFFER_RELIABLE].StartWriting( new char[ bytes ], bytes, bits ); Q_memcpy( m_Messages[HLTV_BUFFER_RELIABLE].GetBasePointer(), frame.m_Messages[HLTV_BUFFER_RELIABLE].GetBasePointer(), bytes ); } // copy unreliable messages bits = frame.m_Messages[HLTV_BUFFER_UNRELIABLE].GetNumBitsWritten(); bits += frame.m_Messages[HLTV_BUFFER_TEMPENTS].GetNumBitsWritten(); bits += frame.m_Messages[HLTV_BUFFER_SOUNDS].GetNumBitsWritten(); if ( tv_relayvoice.GetBool() ) bits += frame.m_Messages[HLTV_BUFFER_VOICE].GetNumBitsWritten(); if ( bits > 0 ) { // collapse all unreliable buffers in one int bytes = PAD_NUMBER( Bits2Bytes(bits), 4 ); m_Messages[HLTV_BUFFER_UNRELIABLE].StartWriting( new char[ bytes ], bytes ); m_Messages[HLTV_BUFFER_UNRELIABLE].WriteBits( frame.m_Messages[HLTV_BUFFER_UNRELIABLE].GetData(), frame.m_Messages[HLTV_BUFFER_UNRELIABLE].GetNumBitsWritten() ); m_Messages[HLTV_BUFFER_UNRELIABLE].WriteBits( frame.m_Messages[HLTV_BUFFER_TEMPENTS].GetData(), frame.m_Messages[HLTV_BUFFER_TEMPENTS].GetNumBitsWritten() ); m_Messages[HLTV_BUFFER_UNRELIABLE].WriteBits( frame.m_Messages[HLTV_BUFFER_SOUNDS].GetData(), frame.m_Messages[HLTV_BUFFER_SOUNDS].GetNumBitsWritten() ); if ( tv_relayvoice.GetBool() ) m_Messages[HLTV_BUFFER_UNRELIABLE].WriteBits( frame.m_Messages[HLTV_BUFFER_VOICE].GetData(), frame.m_Messages[HLTV_BUFFER_VOICE].GetNumBitsWritten() ); } } void CHLTVFrame::AllocBuffers( void ) { // allocate buffers for input frame for ( int i=0; i < HLTV_BUFFER_MAX; i++ ) { Assert( m_Messages[i].GetBasePointer() == NULL ); m_Messages[i].StartWriting( new char[NET_MAX_PAYLOAD], NET_MAX_PAYLOAD); } } void CHLTVFrame::FreeBuffers( void ) { for ( int i=0; i 0 ) { RecvTable_Term(); FreeClientRecvTables(); } // make sure everything was destroyed Assert( m_CurrentFrame == NULL ); Assert( CountClientFrames() == 0 ); } void CHLTVServer::SetMaxClients( int number ) { // allow max clients 0 in HLTV m_nMaxclients = clamp( number, 0, ABSOLUTE_PLAYER_LIMIT ); } void CHLTVServer::StartMaster(CGameClient *client) { Clear(); // clear old settings & buffers if ( !client ) { ConMsg("SourceTV client not found.\n"); return; } m_Director = serverGameDirector; if ( !m_Director ) { ConMsg("Mod doesn't support SourceTV. No director module found.\n"); return; } m_MasterClient = client; m_MasterClient->m_bIsHLTV = true; #if defined( REPLAY_ENABLED ) m_MasterClient->m_bIsReplay = false; #endif // let game.dll know that we are the HLTV client Assert( serverGameClients ); CPlayerState *player = serverGameClients->GetPlayerState( m_MasterClient->edict ); player->hltv = true; m_Server = (CGameServer*)m_MasterClient->GetServer(); // set default user settings m_MasterClient->m_ConVars->SetString( "name", tv_name.GetString() ); m_MasterClient->m_ConVars->SetString( "cl_team", "1" ); m_MasterClient->m_ConVars->SetString( "rate", "30000" ); m_MasterClient->m_ConVars->SetString( "cl_updaterate", "22" ); m_MasterClient->m_ConVars->SetString( "cl_interp_ratio", "1.0" ); m_MasterClient->m_ConVars->SetString( "cl_predict", "0" ); m_nViewEntity = m_MasterClient->GetPlayerSlot() + 1; m_nPlayerSlot = m_MasterClient->GetPlayerSlot(); // copy server settings from m_Server m_nGameServerMaxClients = m_Server->GetMaxClients(); // maxclients is different on proxy (128) serverclasses = m_Server->serverclasses; serverclassbits = m_Server->serverclassbits; V_memcpy( worldmapMD5.bits, m_Server->worldmapMD5.bits, MD5_DIGEST_LENGTH ); m_flTickInterval= m_Server->GetTickInterval(); // allocate buffers for input frame m_HLTVFrame.AllocBuffers(); InstallStringTables(); // activate director in game.dll m_Director->SetHLTVServer( this ); // register as listener for mod specific events const char **modevents = m_Director->GetModEvents(); int j = 0; while ( modevents[j] != NULL ) { const char *eventname = modevents[j]; CGameEventDescriptor *descriptor = g_GameEventManager.GetEventDescriptor( eventname ); if ( descriptor ) { g_GameEventManager.AddListener( this, descriptor, CGameEventManager::CLIENTSTUB ); } else { DevMsg("CHLTVServer::StartMaster: game event %s not found.\n", eventname ); } j++; } // copy signon buffers m_Signon.StartWriting( m_Server->m_Signon.GetBasePointer(), m_Server->m_Signon.m_nDataBytes, m_Server->m_Signon.GetNumBitsWritten() ); Q_strncpy( m_szMapname, m_Server->m_szMapname, sizeof(m_szMapname) ); Q_strncpy( m_szSkyname, m_Server->m_szSkyname, sizeof(m_szSkyname) ); NET_ListenSocket( m_Socket, true ); // activated HLTV TCP socket m_MasterClient->ExecuteStringCommand( "spectate" ); // become a spectator m_MasterClient->UpdateUserSettings(); // make sure UserInfo is correct // hack reduce signontick by one to catch changes made in the current tick m_MasterClient->m_nSignonTick--; if ( m_bMasterOnlyMode ) { // we allow only one client in master only mode tv_maxclients.SetValue( min(1,tv_maxclients.GetInt()) ); } SetMaxClients( tv_maxclients.GetInt() ); m_bSignonState = false; //master proxy is instantly connected m_nSpawnCount++; m_flStartTime = net_time; m_State = ss_active; // stop any previous recordings m_DemoRecorder.StopRecording(); // start new recording if autorecord is enabled if ( tv_autorecord.GetBool() ) { m_DemoRecorder.StartAutoRecording(); } ReconnectClients(); } void CHLTVServer::StartDemo(const char *filename) { } bool CHLTVServer::DispatchToRelay( CHLTVClient *pClient ) { if ( tv_dispatchmode.GetInt() <= DISPATCH_MODE_OFF ) return false; // don't redirect CBaseClient *pBestProxy = NULL; float fBestRatio = 1.0f; // find best relay proxy for (int i=0; i < GetClientCount(); i++ ) { CBaseClient *pProxy = m_Clients[ i ]; // check all known proxies if ( !pProxy->IsConnected() || !pProxy->IsHLTV() || (pClient == pProxy) ) continue; int slots = Q_atoi( pProxy->GetUserSetting( "hltv_slots" ) ); int clients = Q_atoi( pProxy->GetUserSetting( "hltv_clients" ) ); // skip overloaded proxies or proxies with no slots at all if ( (clients > slots) || slots <= 0 ) continue; // calc clients/slots ratio for this proxy float ratio = ((float)(clients))/((float)slots); if ( ratio < fBestRatio ) { fBestRatio = ratio; pBestProxy = pProxy; } } if ( pBestProxy == NULL ) { if ( tv_dispatchmode.GetInt() == DISPATCH_MODE_ALWAYS ) { // we are in always forward mode, drop client if we can't forward it pClient->Disconnect("No SourceTV relay available"); return true; } else { // just let client connect to this proxy return false; } } // check if client should stay on this relay server if ( (tv_dispatchmode.GetInt() == DISPATCH_MODE_AUTO) && (GetMaxClients() > 0) ) { // ratio = clients/slots. give relay proxies 25% bonus float myRatio = ((float)GetNumClients()/(float)GetMaxClients()) * 1.25f; myRatio = min( myRatio, 1.0f ); // clamp to 1 // if we have a better local ratio then other proxies, keep this client here if ( myRatio < fBestRatio ) return false; // don't redirect } const char *pszRelayAddr = pBestProxy->GetUserSetting("hltv_addr"); if ( !pszRelayAddr ) return false; ConMsg( "Redirecting spectator %s to SourceTV relay %s\n", pClient->GetNetChannel()->GetRemoteAddress().ToString(), pszRelayAddr ); // first tell that client that we are a SourceTV server, // otherwise it's might ignore the command SVC_ServerInfo serverInfo; FillServerInfo( serverInfo ); pClient->SendNetMsg( serverInfo, true ); // tell the client to connect to this new address NET_StringCmd cmdMsg( va("redirect %s\n", pszRelayAddr ) ) ; pClient->SendNetMsg( cmdMsg, true ); // increase this proxies client number in advance so this proxy isn't used again next time int clients = Q_atoi( pBestProxy->GetUserSetting( "hltv_clients" ) ); pBestProxy->SetUserCVar( "hltv_clients", va("%d", clients+1 ) ); return true; } void CHLTVServer::ConnectRelay(const char *address) { if ( m_ClientState.IsConnected() ) { // do not try to reconnect to old connection m_ClientState.m_szRetryAddress[0] = 0; // disconnect first m_ClientState.Disconnect( "HLTV server connecting to relay", true ); Changelevel(); // inactivate clients } // connect to new server m_ClientState.Connect( address, "tvrelay" ); } void CHLTVServer::StartRelay() { if ( !m_ClientState.IsConnected() && !IsPlayingBack() ) { DevMsg("StartRelay: not connected.\n"); Shutdown(); return; } Clear(); // clear old settings & buffers if ( m_nRecvTables == 0 ) { // must be done only once since Mod never changes InitClientRecvTables(); } m_HLTVFrame.AllocBuffers(); m_StringTables = &m_NetworkStringTables; SetMaxClients( tv_maxclients.GetInt() ); m_bSignonState = true; m_flStartTime = net_time; m_State = ss_loading; m_nSpawnCount++; } int CHLTVServer::GetHLTVSlot( void ) { return m_nPlayerSlot; } float CHLTVServer::GetOnlineTime( void ) { return max(0., net_time - m_flStartTime); } void CHLTVServer::GetLocalStats( int &proxies, int &slots, int &clients ) { proxies = GetNumProxies(); clients = GetNumClients(); slots = GetMaxClients(); } void CHLTVServer::GetRelayStats( int &proxies, int &slots, int &clients ) { proxies = slots = clients = 0; for (int i=0 ; i < GetClientCount() ; i++ ) { CBaseClient *client = m_Clients[ i ]; if ( !client->IsConnected() || !client->IsHLTV() ) continue; proxies += Q_atoi( client->GetUserSetting( "hltv_proxies" ) ); slots += Q_atoi( client->GetUserSetting( "hltv_slots" ) ); clients += Q_atoi( client->GetUserSetting( "hltv_clients" ) ); } } void CHLTVServer::GetGlobalStats( int &proxies, int &slots, int &clients ) { // the master proxy is the only one that really has all data to generate // global stats if ( IsMasterProxy() ) { GetRelayStats( m_nGlobalProxies, m_nGlobalSlots, m_nGlobalClients ); m_nGlobalSlots += GetMaxClients(); m_nGlobalClients += GetNumClients(); } // if this is a relay proxies, global data comes via the // wire from the master proxy proxies = m_nGlobalProxies; slots = m_nGlobalSlots; clients = m_nGlobalClients; } const netadr_t *CHLTVServer::GetRelayAddress( void ) { if ( IsMasterProxy() ) { return &net_local_adr; // TODO wrong port } else if ( m_ClientState.m_NetChannel ) { return &m_ClientState.m_NetChannel->GetRemoteAddress(); } else { return NULL; } } bool CHLTVServer::IsMasterProxy( void ) { return ( m_MasterClient != NULL ); } bool CHLTVServer::IsTVRelay() { return !IsMasterProxy(); } bool CHLTVServer::IsDemoPlayback( void ) { return false; } void CHLTVServer::BroadcastLocalTitle( CHLTVClient *client ) { IGameEvent *event = g_GameEventManager.CreateEvent( "hltv_title", true ); if ( !event ) return; event->SetString( "text", tv_title.GetString() ); char buffer_data[MAX_EVENT_BYTES]; SVC_GameEvent eventMsg; eventMsg.SetReliable( true ); eventMsg.m_DataOut.StartWriting( buffer_data, sizeof(buffer_data) ); // create bit stream from KeyValues if ( !g_GameEventManager.SerializeEvent( event, &eventMsg.m_DataOut ) ) { DevMsg("CHLTVServer: failed to serialize title '%s'.\n", event->GetName() ); g_GameEventManager.FreeEvent( event ); return; } if ( client ) { client->SendNetMsg( eventMsg ); } else { for ( int i = 0; i < m_Clients.Count(); i++ ) { client = Client(i); if ( !client->IsActive() || client->IsHLTV() ) continue; client->SendNetMsg( eventMsg ); } } g_GameEventManager.FreeEvent( event ); } void CHLTVServer::BroadcastLocalChat( const char *pszChat, const char *pszGroup ) { IGameEvent *event = g_GameEventManager.CreateEvent( "hltv_chat", true ); if ( !event ) return; event->SetString( "text", pszChat ); char buffer_data[MAX_EVENT_BYTES]; SVC_GameEvent eventMsg; eventMsg.SetReliable( false ); eventMsg.m_DataOut.StartWriting( buffer_data, sizeof(buffer_data) ); // create bit stream from KeyValues if ( !g_GameEventManager.SerializeEvent( event, &eventMsg.m_DataOut ) ) { DevMsg("CHLTVServer: failed to serialize chat '%s'.\n", event->GetName() ); g_GameEventManager.FreeEvent( event ); return; } for ( int i = 0; i < m_Clients.Count(); i++ ) { CHLTVClient *cl = Client(i); if ( !cl->IsActive() || !cl->IsSpawned() || cl->IsHLTV() ) continue; // if this is a spectator chat message and client disabled it, don't show it if ( Q_strcmp( cl->m_szChatGroup, pszGroup) || cl->m_bNoChat ) continue; cl->SendNetMsg( eventMsg ); } g_GameEventManager.FreeEvent( event ); } void CHLTVServer::BroadcastEventLocal( IGameEvent *event, bool bReliable ) { char buffer_data[MAX_EVENT_BYTES]; SVC_GameEvent eventMsg; eventMsg.SetReliable( bReliable ); eventMsg.m_DataOut.StartWriting( buffer_data, sizeof(buffer_data) ); // create bit stream from KeyValues if ( !g_GameEventManager.SerializeEvent( event, &eventMsg.m_DataOut ) ) { DevMsg("CHLTVServer: failed to serialize local event '%s'.\n", event->GetName() ); return; } for ( int i = 0; i < m_Clients.Count(); i++ ) { CHLTVClient *cl = Client(i); if ( !cl->IsActive() || !cl->IsSpawned() || cl->IsHLTV() ) continue; if ( !cl->SendNetMsg( eventMsg ) ) { if ( eventMsg.IsReliable() ) { DevMsg( "BroadcastMessage: Reliable broadcast message overflow for client %s", cl->GetClientName() ); } } } if ( tv_debug.GetBool() ) Msg("SourceTV broadcast local event: %s\n", event->GetName() ); } void CHLTVServer::BroadcastEvent(IGameEvent *event) { char buffer_data[MAX_EVENT_BYTES]; SVC_GameEvent eventMsg; eventMsg.m_DataOut.StartWriting( buffer_data, sizeof(buffer_data) ); // create bit stream from KeyValues if ( !g_GameEventManager.SerializeEvent( event, &eventMsg.m_DataOut ) ) { DevMsg("CHLTVServer: failed to serialize event '%s'.\n", event->GetName() ); return; } BroadcastMessage( eventMsg, true, true ); if ( tv_debug.GetBool() ) Msg("SourceTV broadcast event: %s\n", event->GetName() ); } void CHLTVServer::FireGameEvent(IGameEvent *event) { if ( !IsActive() ) return; char buffer_data[MAX_EVENT_BYTES]; SVC_GameEvent eventMsg; eventMsg.m_DataOut.StartWriting( buffer_data, sizeof(buffer_data) ); // create bit stream from KeyValues if ( g_GameEventManager.SerializeEvent( event, &eventMsg.m_DataOut ) ) { SendNetMsg( eventMsg ); } else { DevMsg("CHLTVServer::FireGameEvent: failed to serialize event '%s'.\n", event->GetName() ); } } bool CHLTVServer::ShouldUpdateMasterServer() { // If the main game server is active, then we let it update Steam with the server info. return !sv.IsActive(); } CBaseClient *CHLTVServer::CreateNewClient(int slot ) { return new CHLTVClient( slot, this ); } void CHLTVServer::InstallStringTables( void ) { #ifndef SHARED_NET_STRING_TABLES int numTables = m_Server->m_StringTables->GetNumTables(); m_StringTables = &m_NetworkStringTables; Assert( m_StringTables->GetNumTables() == 0); // must be empty m_StringTables->AllowCreation( true ); // master hltv needs to keep a list of changes for all table items m_StringTables->EnableRollback( true ); for ( int i =0; im_StringTables->GetTable( i ); if ( !serverTable ) continue; // get matching client table CNetworkStringTable *hltvTable = (CNetworkStringTable*)m_StringTables->CreateStringTableEx( serverTable->GetTableName(), serverTable->GetMaxStrings(), serverTable->GetUserDataSize(), serverTable->GetUserDataSizeBits(), serverTable->HasFileNameStrings() ); if ( !hltvTable ) { DevMsg("SV_InstallHLTVStringTableMirrors! Missing client table \"%s\".\n ", serverTable->GetTableName() ); continue; } // make hltv table an exact copy of server table hltvTable->CopyStringTable( serverTable ); // link hltv table to server table serverTable->SetMirrorTable( hltvTable ); } m_StringTables->AllowCreation( false ); #endif } void CHLTVServer::RestoreTick( int tick ) { #ifndef SHARED_NET_STRING_TABLES // only master proxy delays time if ( !IsMasterProxy() ) return; int numTables = m_StringTables->GetNumTables(); for ( int i =0; iGetTable( i ); pTable->RestoreTick( tick ); } #endif } void CHLTVServer::UserInfoChanged( int nClientIndex ) { // don't change UserInfo table, it keeps the infos of the original players } void CHLTVServer::LinkInstanceBaselines( void ) { // Forces to update m_pInstanceBaselineTable. AUTO_LOCK( g_svInstanceBaselineMutex ); GetInstanceBaselineTable(); Assert( m_pInstanceBaselineTable ); // update all found server classes for ( ServerClass *pClass = serverGameDLL->GetAllServerClasses(); pClass; pClass=pClass->m_pNext ) { char idString[32]; Q_snprintf( idString, sizeof( idString ), "%d", pClass->m_ClassID ); // Ok, make a new instance baseline so they can reference it. int index = m_pInstanceBaselineTable->FindStringIndex( idString ); if ( index != -1 ) { pClass->m_InstanceBaselineIndex = index; } else { pClass->m_InstanceBaselineIndex = INVALID_STRING_INDEX; } } } /* CHLTVServer::GetOriginFromPackedEntity is such a bad, bad hack. extern float DecodeFloat(SendProp const *pProp, bf_read *pIn); Vector CHLTVServer::GetOriginFromPackedEntity(PackedEntity* pe) { Vector origin; origin.Init(); SendTable *pSendTable = pe->m_pSendTable; // recursively go down until BaseEntity sendtable while ( Q_strcmp( pSendTable->GetName(), "DT_BaseEntity") ) { SendProp *pProp = pSendTable->GetProp( 0 ); // 0 = baseclass pSendTable = pProp->GetDataTable(); } for ( int i=0; i < pSendTable->GetNumProps(); i++ ) { SendProp *pProp = pSendTable->GetProp( i ); if ( Q_strcmp( pProp->GetName(), "m_vecOrigin" ) == 0 ) { Assert( pProp->GetType() == DPT_Vector ); bf_read buf( pe->LockData(), Bits2Bytes(pe->GetNumBits()), pProp->GetOffset() ); origin[0] = DecodeFloat(pProp, &buf); origin[1] = DecodeFloat(pProp, &buf); origin[2] = DecodeFloat(pProp, &buf); break; } } return origin; } */ CHLTVEntityData *FindHLTVDataInSnapshot( CFrameSnapshot * pSnapshot, int iEntIndex ) { int a = 0; int z = pSnapshot->m_nValidEntities-1; if ( iEntIndex < pSnapshot->m_pValidEntities[a] || iEntIndex > pSnapshot->m_pValidEntities[z] ) return NULL; while ( a < z ) { int m = (a+z)/2; int index = pSnapshot->m_pValidEntities[m]; if ( index == iEntIndex ) return &pSnapshot->m_pHLTVEntityData[m]; if ( iEntIndex > index ) { if ( pSnapshot->m_pValidEntities[z] == iEntIndex ) return &pSnapshot->m_pHLTVEntityData[z]; if ( a == m ) return NULL; a = m; } else { if ( pSnapshot->m_pValidEntities[a] == iEntIndex ) return &pSnapshot->m_pHLTVEntityData[a]; if ( z == m ) return NULL; z = m; } } return NULL; } void CHLTVServer::EntityPVSCheck( CClientFrame *pFrame ) { byte PVS[PAD_NUMBER( MAX_MAP_CLUSTERS,8 ) / 8]; int nPVSSize = (GetCollisionBSPData()->numclusters + 7) / 8; // setup engine PVS SV_ResetPVS( PVS, nPVSSize ); CFrameSnapshot * pSnapshot = pFrame->GetSnapshot(); Assert ( pSnapshot->m_pHLTVEntityData != NULL ); int nDirectorEntity = m_Director->GetPVSEntity(); if ( pSnapshot && nDirectorEntity > 0 ) { CHLTVEntityData *pHLTVData = FindHLTVDataInSnapshot( pSnapshot, nDirectorEntity ); if ( pHLTVData ) { m_vPVSOrigin.x = pHLTVData->origin[0]; m_vPVSOrigin.y = pHLTVData->origin[1]; m_vPVSOrigin.z = pHLTVData->origin[2]; } } else { m_vPVSOrigin = m_Director->GetPVSOrigin(); } SV_AddOriginToPVS( m_vPVSOrigin ); // know remove all entities that aren't in PVS int entindex = -1; while ( true ) { entindex = pFrame->transmit_entity.FindNextSetBit( entindex+1 ); if ( entindex < 0 ) break; // is transmit_always is set -> no PVS check if ( pFrame->transmit_always->Get(entindex) ) { pFrame->last_entity = entindex; continue; } CHLTVEntityData *pHLTVData = FindHLTVDataInSnapshot( pSnapshot, entindex ); if ( !pHLTVData ) continue; unsigned int nNodeCluster = pHLTVData->m_nNodeCluster; // check if node or cluster is in PVS if ( nNodeCluster & (1<<31) ) { // it's a node SLOW nNodeCluster &= ~(1<<31); if ( CM_HeadnodeVisible( nNodeCluster, PVS, nPVSSize ) ) { pFrame->last_entity = entindex; continue; } } else { // it's a cluster QUICK if ( PVS[nNodeCluster >> 3] & (1 << (nNodeCluster & 7)) ) { pFrame->last_entity = entindex; continue; } } // entity is not in PVS, remove from transmit_entity list pFrame->transmit_entity.Clear( entindex ); } } void CHLTVServer::SignonComplete() { Assert ( !IsMasterProxy() ); m_bSignonState = false; LinkInstanceBaselines(); if ( tv_debug.GetBool() ) Msg("SourceTV signon complete.\n" ); } CClientFrame *CHLTVServer::AddNewFrame( CClientFrame *clientFrame ) { VPROF_BUDGET( "CHLTVServer::AddNewFrame", "HLTV" ); Assert ( clientFrame ); Assert( clientFrame->tick_count > m_nLastTick ); m_nLastTick = clientFrame->tick_count; m_HLTVFrame.SetSnapshot( clientFrame->GetSnapshot() ); m_HLTVFrame.tick_count = clientFrame->tick_count; m_HLTVFrame.last_entity = clientFrame->last_entity; m_HLTVFrame.transmit_entity = clientFrame->transmit_entity; // remember tick of first valid frame if ( m_nFirstTick < 0 ) { m_nFirstTick = clientFrame->tick_count; m_nTickCount = m_nFirstTick; if ( !IsMasterProxy() ) { Assert ( m_State == ss_loading ); m_State = ss_active; // we are now ready to go ReconnectClients(); ConMsg("SourceTV relay active.\n" ); Steam3Server().Activate( CSteam3Server::eServerTypeTVRelay ); Steam3Server().SendUpdatedServerDetails(); } else { ConMsg("SourceTV broadcast active.\n" ); } } CHLTVFrame *hltvFrame = new CHLTVFrame; // copy tickcount & entities from client frame hltvFrame->CopyFrame( *clientFrame ); //copy rest (messages, tempents) from current HLTV frame hltvFrame->CopyHLTVData( m_HLTVFrame ); // add frame to HLTV server AddClientFrame( hltvFrame ); if ( IsMasterProxy() && m_DemoRecorder.IsRecording() ) { m_DemoRecorder.WriteFrame( &m_HLTVFrame ); } // reset HLTV frame for recording next messages etc. m_HLTVFrame.Reset(); m_HLTVFrame.SetSnapshot( NULL ); return hltvFrame; } void CHLTVServer::SendClientMessages ( bool bSendSnapshots ) { // build individual updates for ( int i=0; i< m_Clients.Count(); i++ ) { CHLTVClient* client = Client(i); // Update Host client send state... if ( !client->ShouldSendMessages() ) { continue; } // Append the unreliable data (player updates and packet entities) if ( m_CurrentFrame && client->IsActive() ) { // don't send same snapshot twice client->SendSnapshot( m_CurrentFrame ); } else { // Connected, but inactive, just send reliable, sequenced info. client->m_NetChannel->Transmit(); } client->UpdateSendState(); client->m_fLastSendTime = net_time; } } void CHLTVServer::UpdateStats( void ) { if ( m_fNextSendUpdateTime > net_time ) return; m_fNextSendUpdateTime = net_time + 8.0f; // fire game event for everyone IGameEvent *event = NULL; if ( !IsMasterProxy() && !m_ClientState.IsConnected() ) { // we are disconnected from SourceTV server event = g_GameEventManager.CreateEvent( "hltv_message", true ); if ( !event ) return; event->SetString( "text", "SourceTV reconnecting ..." ); } else { int proxies, slots, clients; GetGlobalStats( proxies, slots, clients ); event = g_GameEventManager.CreateEvent( "hltv_status", true ); if ( !event ) return; char address[32]; if ( IsMasterProxy() || tv_overridemaster.GetBool() ) { // broadcast own address Q_snprintf( address, sizeof(address), "%s:%u", net_local_adr.ToString(true), GetUDPPort() ); } else { // forward address Q_snprintf( address, sizeof(address), "%s", m_RootServer.ToString() ); } event->SetString( "master", address ); event->SetInt( "clients", clients ); event->SetInt( "slots", slots); event->SetInt( "proxies", proxies ); } if ( IsMasterProxy() ) { // as a master fire event for every one g_GameEventManager.FireEvent( event ); } else { // as a relay proxy just broadcast event BroadcastEvent( event ); } } bool CHLTVServer::SendNetMsg( INetMessage &msg, bool bForceReliable ) { if ( m_bSignonState ) { return msg.WriteToBuffer( m_Signon ); } int buffer = HLTV_BUFFER_UNRELIABLE; // default destination if ( msg.IsReliable() ) { buffer = HLTV_BUFFER_RELIABLE; } else if ( msg.GetType() == svc_Sounds ) { buffer = HLTV_BUFFER_SOUNDS; } else if ( msg.GetType() == svc_VoiceData ) { buffer = HLTV_BUFFER_VOICE; } else if ( msg.GetType() == svc_TempEntities ) { buffer = HLTV_BUFFER_TEMPENTS; } // anything else goes to the unreliable bin return msg.WriteToBuffer( m_HLTVFrame.m_Messages[buffer] ); } bf_write *CHLTVServer::GetBuffer( int nBuffer ) { if ( nBuffer < 0 || nBuffer >= HLTV_BUFFER_MAX ) return NULL; return &m_HLTVFrame.m_Messages[nBuffer]; } IServer *CHLTVServer::GetBaseServer() { return (IServer*)this; } IHLTVDirector *CHLTVServer::GetDirector() { return m_Director; } CClientFrame *CHLTVServer::GetDeltaFrame( int nTick ) { if ( !tv_deltacache.GetBool() ) return GetClientFrame( nTick ); //expensive // TODO make that a utlmap FOR_EACH_VEC( m_FrameCache, iFrame ) { if ( m_FrameCache[iFrame].nTick == nTick ) return m_FrameCache[iFrame].pFrame; } int i = m_FrameCache.AddToTail(); CFrameCacheEntry_s &entry = m_FrameCache[i]; entry.nTick = nTick; entry.pFrame = GetClientFrame( nTick ); //expensive return entry.pFrame; } void CHLTVServer::RunFrame() { VPROF_BUDGET( "CHLTVServer::RunFrame", "HLTV" ); // update network time etc NET_RunFrame( Plat_FloatTime() ); if ( m_ClientState.m_nSignonState > SIGNONSTATE_NONE ) { // process data from net socket NET_ProcessSocket( m_ClientState.m_Socket, &m_ClientState ); m_ClientState.RunFrame(); m_ClientState.SendPacket(); } // check if HLTV server if active if ( !IsActive() ) return; if ( host_frametime > 0 ) { m_flFPS = m_flFPS * 0.99f + 0.01f/host_frametime; } if ( IsPlayingBack() ) return; // get current tick time for director module and restore // world (stringtables, framebuffers) as they were at this time UpdateTick(); // Run any commands from client and play client Think functions if it is time. CBaseServer::RunFrame(); UpdateStats(); SendClientMessages( true ); // Update the Steam server if we're running a relay. if ( !sv.IsActive() ) Steam3Server().RunFrame(); UpdateMasterServer(); } void CHLTVServer::UpdateTick( void ) { VPROF_BUDGET( "CHLTVServer::UpdateTick", "HLTV" ); if ( m_nFirstTick < 0 ) { m_nTickCount = 0; m_CurrentFrame = NULL; return; } // set tick time to last frame added int nNewTick = m_nLastTick; if ( IsMasterProxy() ) { // get tick from director, he decides delay etc nNewTick = max( m_nFirstTick, m_Director->GetDirectorTick() ); } // the the closest available frame CHLTVFrame *newFrame = (CHLTVFrame*) GetClientFrame( nNewTick, false ); if ( newFrame == NULL ) return; // we dont have a new frame if ( m_CurrentFrame == newFrame ) return; // current frame didn't change m_CurrentFrame = newFrame; m_nTickCount = m_CurrentFrame->tick_count; if ( IsMasterProxy() ) { // now do master proxy stuff // restore string tables for this time RestoreTick( m_nTickCount ); // remove entities out of current PVS if ( tv_transmitall.GetBool() == false ) { EntityPVSCheck( m_CurrentFrame ); } } else { // delta entity cache works only for relay proxies m_DeltaCache.SetTick( m_CurrentFrame->tick_count, m_CurrentFrame->last_entity+1 ); } int removeTick = m_nTickCount - 16.0f/m_flTickInterval; // keep 16 seconds buffer if ( removeTick > 0 ) { DeleteClientFrames( removeTick ); } m_FrameCache.RemoveAll(); } const char *CHLTVServer::GetName( void ) const { return tv_name.GetString(); } void CHLTVServer::FillServerInfo(SVC_ServerInfo &serverinfo) { CBaseServer::FillServerInfo( serverinfo ); serverinfo.m_nPlayerSlot = m_nPlayerSlot; // all spectators think they're the HLTV client serverinfo.m_nMaxClients = m_nGameServerMaxClients; } void CHLTVServer::Clear( void ) { CBaseServer::Clear(); m_Director = NULL; m_MasterClient = NULL; m_ClientState.Clear(); m_Server = NULL; m_nFirstTick = -1; m_nLastTick = 0; m_nTickCount = 0; m_CurrentFrame = NULL; m_nPlayerSlot = 0; m_flStartTime = 0.0f; m_nViewEntity = 1; m_nGameServerMaxClients = 0; m_fNextSendUpdateTime = 0.0f; m_HLTVFrame.FreeBuffers(); m_vPVSOrigin.Init(); DeleteClientFrames( -1 ); m_DeltaCache.Flush(); m_FrameCache.RemoveAll(); } bool CHLTVServer::ProcessConnectionlessPacket( netpacket_t * packet ) { bf_read msg = packet->message; // We're copying the message, so we don't need to seek back when passing packet to the base class. // int bits = msg.GetNumBitsRead(); char c = msg.ReadChar(); if ( c == 0 ) { return false; } switch ( c ) { #ifndef NO_STEAM case A2S_INFO: char rgchInfoPostfix[64]; msg.ReadString( rgchInfoPostfix, sizeof( rgchInfoPostfix ) ); if ( !Q_stricmp( rgchInfoPostfix, A2S_KEY_STRING_STEAM ) ) { ReplyInfo( packet->from ); return true; } break; //case A2S_PLAYER: // return true; #endif // #ifndef NO_STEAM } return CBaseServer::ProcessConnectionlessPacket( packet ); } void CHLTVServer::Init(bool bIsDedicated) { CBaseServer::Init( bIsDedicated ); m_Socket = NS_HLTV; // check if only master proxy is allowed, no broadcasting if ( CommandLine()->FindParm("-tvmasteronly") ) { m_bMasterOnlyMode = true; } } void CHLTVServer::Changelevel() { m_DemoRecorder.StopRecording(); InactivateClients(); DeleteClientFrames(-1); m_CurrentFrame = NULL; } void CHLTVServer::GetNetStats( float &avgIn, float &avgOut ) { CBaseServer::GetNetStats( avgIn, avgOut ); if ( m_ClientState.IsActive() ) { avgIn += m_ClientState.m_NetChannel->GetAvgData(FLOW_INCOMING); avgOut += m_ClientState.m_NetChannel->GetAvgData(FLOW_OUTGOING); } } void CHLTVServer::Shutdown( void ) { m_DemoRecorder.StopRecording(); // if recording, stop now if ( IsMasterProxy() ) { if ( m_MasterClient ) m_MasterClient->Disconnect( "SourceTV stop." ); if ( m_Director ) m_Director->SetHLTVServer( NULL ); } else { // do not try to reconnect to old connection m_ClientState.m_szRetryAddress[0] = 0; m_ClientState.Disconnect( "HLTV server shutting down", true ); } g_GameEventManager.RemoveListener( this ); CBaseServer::Shutdown(); } CDemoFile *CHLTVServer::GetDemoFile() { return &m_DemoFile; } //----------------------------------------------------------------------------- // Purpose: // Output : Returns true on success, false on failure. //----------------------------------------------------------------------------- bool CHLTVServer::IsPlayingBack( void ) { return m_bPlayingBack; } bool CHLTVServer::IsPlaybackPaused() { return m_bPlaybackPaused; } float CHLTVServer::GetPlaybackTimeScale() { return m_flPlaybackRateModifier; } void CHLTVServer::SetPlaybackTimeScale(float timescale) { m_flPlaybackRateModifier = timescale; } void CHLTVServer::ResyncDemoClock() { m_nStartTick = host_tickcount; } int CHLTVServer::GetPlaybackStartTick( void ) { return m_nStartTick; } int CHLTVServer::GetPlaybackTick( void ) { return host_tickcount - m_nStartTick; } int CHLTVServer::GetTotalTicks(void) { return m_DemoFile.m_DemoHeader.playback_ticks; } bool CHLTVServer::StartPlayback( const char *filename, bool bAsTimeDemo ) { Clear(); if ( !m_DemoFile.Open( filename, true ) ) { return false; } // Read in the m_DemoHeader demoheader_t *dh = m_DemoFile.ReadDemoHeader(); if ( !dh ) { ConMsg( "Failed to read demo header.\n" ); m_DemoFile.Close(); return false; } // create a fake channel with a NULL address m_ClientState.m_NetChannel = NET_CreateNetChannel( NS_CLIENT, NULL, "DEMO", &m_ClientState ); if ( !m_ClientState.m_NetChannel ) { ConMsg( "CDemo::Play: failed to create demo net channel\n" ); m_DemoFile.Close(); return false; } m_ClientState.m_NetChannel->SetTimeout( -1.0f ); // never timeout // Now read in the directory structure. m_bPlayingBack = true; ConMsg( "Reading complete demo file at once...\n"); double start = Plat_FloatTime(); ReadCompleteDemoFile(); double diff = Plat_FloatTime() - start; ConMsg( "Reading time :%.4f\n", diff ); NET_RemoveNetChannel( m_ClientState.m_NetChannel, true ); m_ClientState.m_NetChannel = NULL; return true; } void CHLTVServer::ReadCompleteDemoFile() { int tick = 0; byte cmd = dem_signon; char buffer[NET_MAX_PAYLOAD]; netpacket_t demoPacket; // setup demo packet data buffer Q_memset( &demoPacket, 0, sizeof(demoPacket) ); demoPacket.from.SetType( NA_LOOPBACK); while ( true ) { m_DemoFile.ReadCmdHeader( cmd, tick ); // COMMAND HANDLERS switch ( cmd ) { case dem_synctick: ResyncDemoClock(); break; case dem_stop: // MOTODO we finished reading the file return ; case dem_consolecmd: { NET_StringCmd cmdmsg( m_DemoFile.ReadConsoleCommand() ); m_ClientState.ProcessStringCmd( &cmdmsg ); } break; case dem_datatables: { ALIGN4 char data[64*1024] ALIGN4_POST; bf_read buf( "dem_datatables", data, sizeof(data) ); m_DemoFile.ReadNetworkDataTables( &buf ); buf.Seek( 0 ); // support for older engine demos if ( !DataTable_LoadDataTablesFromBuffer( &buf, m_DemoFile.m_DemoHeader.demoprotocol ) ) { Host_Error( "Error parsing network data tables during demo playback." ); } } break; case dem_stringtables: { void *data = NULL; int dataLen = 512 * 1024; while ( dataLen <= DEMO_FILE_MAX_STRINGTABLE_SIZE ) { data = realloc( data, dataLen ); bf_read buf( "dem_stringtables", data, dataLen ); // did we successfully read if ( m_DemoFile.ReadStringTables( &buf ) > 0 ) { buf.Seek( 0 ); if ( !networkStringTableContainerClient->ReadStringTables( buf ) ) { Host_Error( "Error parsing string tables during demo playback." ); } break; } // Didn't fit. Try doubling the size of the buffer dataLen *= 2; } if ( dataLen > DEMO_FILE_MAX_STRINGTABLE_SIZE ) { Warning( "ReadCompleteDemoFile failed to read string tables. Trying to read string tables that's bigger than max string table size\n" ); } free( data ); } break; case dem_usercmd: { char bufferIn[256]; int length = sizeof( bufferIn ); m_DemoFile.ReadUserCmd( bufferIn, length ); // MOTODO HLTV must store user commands too } break; case dem_signon: case dem_packet: { int inseq, outseqack = 0; m_DemoFile.ReadCmdInfo( m_LastCmdInfo ); // MOTODO must be stored somewhere m_DemoFile.ReadSequenceInfo( inseq, outseqack ); int length = m_DemoFile.ReadRawData( buffer, sizeof(buffer) ); if ( length > 0 ) { // succsessfully read new demopacket demoPacket.received = realtime; demoPacket.size = length; demoPacket.message.StartReading( buffer, length ); m_ClientState.m_NetChannel->ProcessPacket( &demoPacket, false ); } } break; } } } int CHLTVServer::GetChallengeType ( netadr_t &adr ) { return PROTOCOL_HASHEDCDKEY; // HLTV doesn't need Steam authentication } const char *CHLTVServer::GetPassword() const { const char *password = tv_password.GetString(); // if password is empty or "none", return NULL if ( !password[0] || !Q_stricmp(password, "none" ) ) { return NULL; } return password; } IClient *CHLTVServer::ConnectClient ( netadr_t &adr, int protocol, int challenge, int clientChallenge, int authProtocol, const char *name, const char *password, const char *hashedCDkey, int cdKeyLen ) { IClient *client = (CHLTVClient*)CBaseServer::ConnectClient( adr, protocol, challenge, clientChallenge, authProtocol, name, password, hashedCDkey, cdKeyLen ); if ( client ) { // remember password CHLTVClient *pHltvClient = (CHLTVClient*)client; Q_strncpy( pHltvClient->m_szPassword, password, sizeof(pHltvClient->m_szPassword) ); } return client; } int CHLTVServer::GetProtocolVersion() { if ( GetDemoFile() ) return GetDemoFile()->GetProtocolVersion(); return PROTOCOL_VERSION; } #ifndef NO_STEAM void CHLTVServer::ReplyInfo( const netadr_t &adr ) { static char gamedir[MAX_OSPATH]; Q_FileBase( com_gamedir, gamedir, sizeof( gamedir ) ); CUtlBuffer buf; buf.EnsureCapacity( 2048 ); buf.PutUnsignedInt( LittleDWord( CONNECTIONLESS_HEADER ) ); buf.PutUnsignedChar( S2A_INFO_SRC ); buf.PutUnsignedChar( GetProtocolVersion() ); // Hardcoded protocol version number buf.PutString( GetName() ); buf.PutString( GetMapName() ); buf.PutString( gamedir ); buf.PutString( serverGameDLL->GetGameDescription() ); // The next field is a 16-bit version of the AppID. If our AppID < 65536, // then let's go ahead and put in in there, to maximize compatibility // with old clients who might be only using this field but not the new one. // However, if our AppID won't fit, there's no way we can be compatible, // anyway, so just put in a zero, which is better than a bogus AppID. uint16 usAppIdShort = (uint16)GetSteamAppID(); if ( (AppId_t)usAppIdShort != GetSteamAppID() ) { usAppIdShort = 0; } buf.PutShort( LittleWord( usAppIdShort ) ); // player info buf.PutUnsignedChar( GetNumClients() ); buf.PutUnsignedChar( GetMaxClients() ); buf.PutUnsignedChar( 0 ); // NOTE: This key's meaning is changed in the new version. Since we send gameport and specport, // it knows whether we're running SourceTV or not. Then it only needs to know if we're a dedicated or listen server. if ( IsDedicated() ) buf.PutUnsignedChar( 'd' ); // d = dedicated server else buf.PutUnsignedChar( 'l' ); // l = listen server #if defined(_WIN32) buf.PutUnsignedChar( 'w' ); #elif defined(OSX) buf.PutUnsignedChar( 'm' ); #else // LINUX? buf.PutUnsignedChar( 'l' ); #endif // Password? buf.PutUnsignedChar( GetPassword() != NULL ? 1 : 0 ); buf.PutUnsignedChar( Steam3Server().BSecure() ? 1 : 0 ); buf.PutString( GetSteamInfIDVersionInfo().szVersionString ); // // NEW DATA. // // Write a byte with some flags that describe what is to follow. const char *pchTags = sv_tags.GetString(); byte nNewFlags = 0; //if ( GetGamePort() != 0 ) // nNewFlags |= S2A_EXTRA_DATA_HAS_GAME_PORT; if ( Steam3Server().GetGSSteamID().IsValid() ) nNewFlags |= S2A_EXTRA_DATA_HAS_STEAMID; if ( GetUDPPort() != 0 ) nNewFlags |= S2A_EXTRA_DATA_HAS_SPECTATOR_DATA; if ( pchTags && pchTags[0] != '\0' ) nNewFlags |= S2A_EXTRA_DATA_HAS_GAMETAG_DATA; nNewFlags |= S2A_EXTRA_DATA_GAMEID; buf.PutUnsignedChar( nNewFlags ); // Write the rest of the data. //if ( nNewFlags & S2A_EXTRA_DATA_HAS_GAME_PORT ) //{ // buf.PutShort( LittleWord( GetGamePort() ) ); //} if ( nNewFlags & S2A_EXTRA_DATA_HAS_STEAMID ) { buf.PutUint64( LittleQWord( Steam3Server().GetGSSteamID().ConvertToUint64() ) ); } if ( nNewFlags & S2A_EXTRA_DATA_HAS_SPECTATOR_DATA ) { buf.PutShort( LittleWord( GetUDPPort() ) ); buf.PutString( GetName() ); } if ( nNewFlags & S2A_EXTRA_DATA_HAS_GAMETAG_DATA ) { buf.PutString( pchTags ); } if ( nNewFlags & S2A_EXTRA_DATA_GAMEID ) { // !FIXME! Is there a reason we aren't using the other half // of this field? Shouldn't we put the game mod ID in there, too? // We have the game dir. buf.PutUint64( LittleQWord( CGameID( GetSteamAppID() ).ToUint64() ) ); } NET_SendPacket( NULL, m_Socket, adr, (unsigned char *)buf.Base(), buf.TellPut() ); } #endif // #ifndef NO_STEAM CON_COMMAND( tv_status, "Show SourceTV server status." ) { int slots, proxies, clients; float in, out; char gd[MAX_OSPATH]; Q_FileBase( com_gamedir, gd, sizeof( gd ) ); if ( !hltv || !hltv->IsActive() ) { ConMsg("SourceTV not active.\n" ); return; } hltv->GetNetStats( in, out ); in /= 1024; // as KB out /= 1024; ConMsg("--- SourceTV Status ---\n"); ConMsg("Online %s, FPS %.1f, Version %i (%s)\n", COM_FormatSeconds( hltv->GetOnlineTime() ), hltv->m_flFPS, build_number(), #ifdef _WIN32 "Win32" ); #else "Linux" ); #endif if ( hltv->IsDemoPlayback() ) { ConMsg("Playing Demo File \"%s\"\n", "TODO demo file name" ); } else if ( hltv->IsMasterProxy() ) { ConMsg("Master \"%s\", delay %.0f\n", hltv->GetName(), hltv->GetDirector()->GetDelay() ); } else // if ( m_Server->IsRelayProxy() ) { if ( hltv->GetRelayAddress() ) { ConMsg("Relay \"%s\", connect to %s\n", hltv->GetName(), hltv->GetRelayAddress()->ToString() ); } else { ConMsg("Relay \"%s\", not connect.\n", hltv->GetName() ); } } ConMsg("Game Time %s, Mod \"%s\", Map \"%s\", Players %i\n", COM_FormatSeconds( hltv->GetTime() ), gd, hltv->GetMapName(), hltv->GetNumPlayers() ); ConMsg("Local IP %s:%i, KB/sec In %.1f, Out %.1f\n", net_local_adr.ToString( true ), hltv->GetUDPPort(), in ,out ); hltv->GetLocalStats( proxies, slots, clients ); ConMsg("Local Slots %i, Spectators %i, Proxies %i\n", slots, clients-proxies, proxies ); hltv->GetGlobalStats( proxies, slots, clients); ConMsg("Total Slots %i, Spectators %i, Proxies %i\n", slots, clients-proxies, proxies); if ( hltv->m_DemoRecorder.IsRecording() ) { ConMsg("Recording to \"%s\", length %s.\n", hltv->m_DemoRecorder.GetDemoFile()->m_szFileName, COM_FormatSeconds( host_state.interval_per_tick * hltv->m_DemoRecorder.GetRecordingTick() ) ); } } CON_COMMAND( tv_relay, "Connect to SourceTV server and relay broadcast." ) { if ( args.ArgC() < 2 ) { ConMsg( "Usage: tv_relay \n" ); return; } const char *address = args.ArgS(); // If it's not a single player connection to "localhost", initialize networking & stop listenserver if ( !Q_strncmp( address, "localhost", 9 ) ) { ConMsg( "SourceTV can't connect to localhost.\n" ); return; } if ( !hltv ) { hltv = new CHLTVServer; hltv->Init( NET_IsDedicated() ); } if ( hltv->m_bMasterOnlyMode ) { ConMsg("SourceTV in Master-Only mode.\n" ); return; } // shutdown anything else Host_Disconnect( false ); // start networking NET_SetMutiplayer( true ); hltv->ConnectRelay( address ); } CON_COMMAND( tv_stop, "Stops the SourceTV broadcast." ) { if ( !hltv || !hltv->IsActive() ) { ConMsg("SourceTV not active.\n" ); return; } int nClients = hltv->GetNumClients(); hltv->Shutdown(); ConMsg("SourceTV stopped, %i clients disconnected.\n", nClients ); } CON_COMMAND( tv_retry, "Reconnects the SourceTV relay proxy." ) { if ( !hltv ) { ConMsg("SourceTV not active.\n" ); return; } if ( hltv->m_bMasterOnlyMode ) { ConMsg("SourceTV in Master-Only mode.\n" ); return; } if ( !hltv->m_ClientState.m_szRetryAddress[ 0 ] ) { ConMsg( "Can't retry, no previous SourceTV connection\n" ); return; } ConMsg( "Commencing SourceTV connection retry to %s\n", hltv->m_ClientState.m_szRetryAddress ); Cbuf_AddText( va( "tv_relay %s\n", hltv->m_ClientState.m_szRetryAddress ) ); } CON_COMMAND( tv_record, "Starts SourceTV demo recording." ) { if ( args.ArgC() < 2 ) { ConMsg( "Usage: tv_record \n" ); return; } if ( !hltv || !hltv->IsActive() ) { ConMsg("SourceTV not active.\n" ); return; } if ( !hltv->IsMasterProxy() ) { ConMsg("Only SourceTV Master can record demos instantly.\n" ); return; } if ( hltv->m_DemoRecorder.IsRecording() ) { ConMsg("SourceTV already recording to %s.\n", hltv->m_DemoRecorder.GetDemoFile()->m_szFileName ); return; } // check path first if ( !COM_IsValidPath( args[1] ) ) { ConMsg( "record %s: invalid path.\n", args[1] ); return; } char name[ MAX_OSPATH ]; Q_strncpy( name, args[1], sizeof( name ) ); // add .dem if not already set by user Q_DefaultExtension( name, ".dem", sizeof( name ) ); hltv->m_DemoRecorder.StartRecording( name, false ); } CON_COMMAND( tv_stoprecord, "Stops SourceTV demo recording." ) { if ( !hltv || !hltv->IsActive() ) { ConMsg("SourceTV not active.\n" ); return; } hltv->m_DemoRecorder.StopRecording(); } CON_COMMAND( tv_clients, "Shows list of connected SourceTV clients." ) { if ( !hltv || !hltv->IsActive() ) { ConMsg("SourceTV not active.\n" ); return; } int nCount = 0; for ( int i=0; iGetClientCount(); i++) { CHLTVClient *client = hltv->Client( i ); INetChannel *netchan = client->GetNetChannel(); if ( !netchan ) continue; ConMsg("ID: %i, \"%s\" %s, Time %s, %s, In %.1f, Out %.1f.\n", client->GetUserID(), client->GetClientName(), client->IsHLTV() ? "(Relay)" : "", COM_FormatSeconds( netchan->GetTimeConnected() ), netchan->GetAddress(), netchan->GetAvgData( FLOW_INCOMING ) / 1024, netchan->GetAvgData( FLOW_OUTGOING ) / 1024 ); nCount++; } ConMsg("--- Total %i connected clients ---\n", nCount ); } CON_COMMAND( tv_msg, "Send a screen message to all clients." ) { if ( !hltv || !hltv->IsActive() ) { ConMsg("SourceTV not active.\n" ); return; } IGameEvent *msg = g_GameEventManager.CreateEvent( "hltv_message", true ); if ( msg ) { msg->SetString( "text", args.ArgS() ); hltv->BroadcastEventLocal( msg, false ); g_GameEventManager.FreeEvent( msg ); } } #ifndef SWDS //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void EditDemo_f( const CCommand &args ) { if ( cmd_source != src_command ) return; if ( args.ArgC() < 2 ) { Msg ("editdemo : edits a demo\n"); return; } // set current demo player to client demo player demoplayer = hltv; // // open the demo file // char name[ MAX_OSPATH ]; Q_strncpy( name, args[1], sizeof( name ) ); Q_DefaultExtension( name, ".dem", sizeof( name ) ); hltv->m_ClientState.m_bSaveMemory = true; demoplayer->StartPlayback( name, false ); } CON_COMMAND_AUTOCOMPLETEFILE( editdemo, EditDemo_f, "Edit a recorded demo file (.dem ).", NULL, dem ); #endif