//===== Copyright © 1996-2009, Valve Corporation, All rights reserved. ======// // // Purpose: // //===========================================================================// #include "mm_framework.h" #include "fmtstr.h" // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" ConVar mm_session_sys_delay_create( "mm_session_sys_delay_create", "0", FCVAR_DEVELOPMENTONLY ); ConVar mm_session_sys_delay_create_host( "mm_session_sys_delay_create_host", "1.2", FCVAR_DEVELOPMENTONLY ); ConVar mm_session_sys_timeout( "mm_session_sys_timeout", "3", FCVAR_DEVELOPMENTONLY ); ConVar mm_session_sys_connect_timeout( "mm_session_sys_connect_timeout", "8", FCVAR_DEVELOPMENTONLY ); ConVar mm_session_team_res_timeout( "mm_session_team_res_timeout", "30", FCVAR_DEVELOPMENTONLY ); ConVar mm_session_voice_loading( "mm_session_voice_loading", "0", FCVAR_DEVELOPMENTONLY ); ConVar mm_session_sys_ranking_timeout( "mm_session_sys_ranking_timeout", "12", FCVAR_DEVELOPMENTONLY ); ConVar mm_session_sys_pkey( "mm_session_sys_pkey", "", FCVAR_RELEASE ); ConVar mm_session_sys_kick_ban_duration( "mm_session_sys_kick_ban_duration", "180", FCVAR_RELEASE ); #define STEAM_LOBBY_CHAT_MSG_BUFFER_SIZE 65536 #ifdef _X360 static CUtlVector< CSysSessionBase * > g_arrSysSessionsPending; static CUtlVector< CSysSessionBase * > g_arrSysSessionsDelete; void SysSession360_UpdatePending() { // Delete scheduled sessions for ( int k = 0; k < g_arrSysSessionsDelete.Count(); ++ k ) { CSysSessionBase *pSession = g_arrSysSessionsDelete[k]; DevMsg( "SysSession360_UpdatePending: destroying session %p\n", pSession ); pSession->Destroy(); g_arrSysSessionsPending.FindAndFastRemove( pSession ); } g_arrSysSessionsDelete.Purge(); // Update pending sessions for ( int k = 0; k < g_arrSysSessionsPending.Count(); ++ k ) { CSysSessionBase *pSysSession = g_arrSysSessionsPending[k]; pSysSession->Update(); if ( k >= g_arrSysSessionsPending.Count() || pSysSession != g_arrSysSessionsPending[k] ) // If a pending session has removed itself, then proceed updating next frame return; } } void SysSession360_RegisterPending( CSysSessionBase *pSession ) { if ( g_arrSysSessionsPending.Find( pSession ) == g_arrSysSessionsPending.InvalidIndex() ) { DevMsg( "SysSession360_RegisterPending: registered pending session %p\n", pSession ); g_arrSysSessionsPending.AddToTail( pSession ); } else if ( g_arrSysSessionsDelete.Find( pSession ) == g_arrSysSessionsDelete.InvalidIndex() ) { DevMsg( "SysSession360_RegisterPending: registered session %p for delete\n", pSession ); g_arrSysSessionsDelete.AddToTail( pSession ); } else Error( "SysSession360_RegisterPending for deleted session!" ); } #endif static bool SysSession_AllowCreate() { #ifdef _X360 // Cannot create new sessions while another session is pending if ( g_arrSysSessionsPending.Count() ) return false; #endif static float s_fAllowCreateTime = 0.0f; float flDelay = mm_session_sys_delay_create.GetFloat(); if ( flDelay <= 0 ) { s_fAllowCreateTime = 0.0f; return true; } else { if ( s_fAllowCreateTime > 0.0f ) { if ( Plat_FloatTime() < s_fAllowCreateTime ) return false; // Delay time not elapsed yet s_fAllowCreateTime = 0.0f; return true; // Delay time has elapsed already } else { s_fAllowCreateTime = Plat_FloatTime() + flDelay; return false; // Starting the delay time } } } CSysSessionBase::CSysSessionBase( KeyValues *pSettings ) : #ifdef _X360 m_pAsyncOperation( NULL ), m_pNetworkMgr( NULL ), m_hLobbyMigrateCall( NULL ), #elif !defined( NO_STEAM ) m_CallbackOnServersConnected( this, &CSysSessionBase::Steam_OnServersConnected ), m_CallbackOnServersDisconnected( this, &CSysSessionBase::Steam_OnServersDisconnected ), m_CallbackOnP2PSessionRequest( this, &CSysSessionBase::Steam_OnP2PSessionRequest ), m_bVoiceUsingSessionP2P( false ), #endif m_Voice_flLastHeadsetStatusCheck( -1.0f ), m_pSettings( pSettings ), m_xuidMachineId( g_pPlayerManager->GetLocalPlayer( XBX_GetPrimaryUserId() )->GetXUID() ), m_result( RESULT_UNDEFINED ) { } CSysSessionBase::~CSysSessionBase() { ; } bool CSysSessionBase::Update() { #ifdef _X360 if ( m_pNetworkMgr ) { if ( m_pNetworkMgr->Update() != CX360NetworkMgr::UPDATE_SUCCESS ) return false; // the network manager destroyed or changed listener } #elif !defined( NO_STEAM ) if ( !IsServiceSession() ) { // Process P2P network uint32 uiSteamMsgSize = 0; CUtlMemory< byte > utlMemory; CSteamID idRemote; while( steamapicontext->SteamNetworking()->IsP2PPacketAvailable( &uiSteamMsgSize, INetSupport::SP2PC_LOBBY ) ) { utlMemory.EnsureCapacity( uiSteamMsgSize ); if ( steamapicontext->SteamNetworking()->ReadP2PPacket( utlMemory.Base(), uiSteamMsgSize, &uiSteamMsgSize, &idRemote, INetSupport::SP2PC_LOBBY ) ) UnpackAndReceiveMessage( utlMemory.Base(), uiSteamMsgSize, true, idRemote.ConvertToUint64() ); } } #endif Voice_UpdateLocalHeadsetsStatus(); Voice_CaptureAndTransmitLocalVoiceData(); #ifdef _X360 if ( m_pAsyncOperation ) { m_pAsyncOperation->Update(); if ( m_pAsyncOperation->IsFinished() ) { OnAsyncOperationFinished(); } } if ( UpdateMigrationCall() ) return false; #endif return true; } bool CSysSessionBase::IsServiceSession() { if ( !m_pSettings ) return true; if ( char const *szNetFlag = m_pSettings->GetString( "system/netflag", NULL ) ) { if ( !Q_stricmp( "teamlink", szNetFlag ) ) return true; } return false; } void CSysSessionBase::OnSessionEvent( KeyValues *notify ) { g_pMatchEventsSubscription->BroadcastEvent( notify ); } void CSysSessionBase::SendEventsNotification( KeyValues *notify ) { OnSessionEvent( notify ); } #ifdef _X360 void CSysSessionBase::ReleaseAsyncOperation() { if ( m_pAsyncOperation ) { m_pAsyncOperation->Release(); m_pAsyncOperation = NULL; } } INetSupport::NetworkSocket_t CSysSessionBase::GetX360NetSocket() { if ( char const *szNetFlag = m_pSettings->GetString( "system/netflag", NULL ) ) { if ( !Q_stricmp( "teamlink", szNetFlag ) ) return INetSupport::NS_SOCK_TEAMLINK; } return INetSupport::NS_SOCK_LOBBY; } bool CSysSessionBase::ShouldAllowX360HostMigration() { if ( !m_lobby.m_hHandle ) return false; // Check the session details if ( dynamic_cast< CSysSessionHost * >( this ) ) { return m_pSettings->GetInt( "members/numMachines", 0 ) > 1 && // more than 1 machine in the session !Q_stricmp( m_pSettings->GetString( "system/network" ), "LIVE" ); // migrate only on LIVE } return false; } bool CSysSessionBase::UpdateMigrationCall() { if ( !m_hLobbyMigrateCall ) return false; if ( !m_MigrateCallState.m_bFinished ) return false; // The call was outstanding and is now finished m_hLobbyMigrateCall = NULL; if ( m_MigrateCallState.m_ret != ERROR_SUCCESS ) { Assert( 0 ); KeyValues *kv = new KeyValues( "mmF->SysSessionUpdate" ); kv->SetPtr( "syssession", this ); kv->SetString( "error", "migrate" ); // Inside this event broadcast our session will be deleted SendEventsNotification( kv ); return true; } // Prepare the notification KeyValues *kvEvent = new KeyValues( "OnPlayerLeaderChanged" ); kvEvent->SetString( "migrate", "finished" ); if ( dynamic_cast< CSysSessionHost * >( this ) ) { // We need to notify all our peers that we are now the new host and we have the new // session details. KeyValues *msg = new KeyValues( "SysSession::HostMigrated" ); KeyValues::AutoDelete autodelete( msg ); msg->SetUint64( "id", m_xuidMachineId ); char chSessionInfo[ XSESSION_INFO_STRING_LENGTH ]; MMX360_SessionInfoToString( m_lobby.m_xiInfo, chSessionInfo ); msg->SetString( "sessioninfo", chSessionInfo ); // When the host migrated, we need to let the clients know which // machines are still staying in the session #ifdef _X360 if ( IsX360() && m_pNetworkMgr ) { int numMachines = m_pSettings->GetInt( "members/numMachines" ); for ( int k = 0; k < numMachines; ++ k ) { KeyValues *pMachine = m_pSettings->FindKey( CFmtStr( "members/machine%d", k ) ); if ( !pMachine ) continue; XUID idMachine = pMachine->GetUint64( "id" ); if ( idMachine && ( idMachine == m_xuidMachineId || m_pNetworkMgr->ConnectionPeerGetAddress( idMachine ) ) ) { msg->SetString( CFmtStr( "machines/%llx", idMachine ), "" ); } } } #endif DevMsg( "CSysSessionHost - host migrated - %s\n", chSessionInfo ); SendMessage( msg ); #ifdef _X360 // Drop all machines that no longer have a network connection with us // in case P2P interconnect failed earlier if ( IsX360() && m_pNetworkMgr ) { CUtlVector< XUID > arrXuidsNoP2P; int numMachines = m_pSettings->GetInt( "members/numMachines" ); for ( int k = 0; k < numMachines; ++ k ) { KeyValues *pMachine = m_pSettings->FindKey( CFmtStr( "members/machine%d", k ) ); if ( !pMachine ) continue; XUID idMachine = pMachine->GetUint64( "id" ); if ( idMachine && idMachine != m_xuidMachineId && !m_pNetworkMgr->ConnectionPeerGetAddress( idMachine ) ) arrXuidsNoP2P.AddToTail( idMachine ); } for ( int k = 0; k < arrXuidsNoP2P.Count(); ++ k ) { DevWarning( "UpdateMigrationCall - dropping machine %llx due to no P2P network connection!\n", arrXuidsNoP2P[k] ); OnPlayerLeave( arrXuidsNoP2P[k] ); } // // Update QOS reply data of the session // CUtlBuffer bufQosData; bufQosData.ActivateByteSwapping( !CByteswap::IsMachineBigEndian() ); g_pMatchFramework->GetMatchNetworkMsgController()->PackageGameDetailsForQOS( m_pSettings, bufQosData ); g_pMatchExtensions->GetIXOnline()->XNetQosListen( &m_lobby.m_xiInfo.sessionID, ( const BYTE * ) bufQosData.Base(), bufQosData.TellMaxPut(), 0, XNET_QOS_LISTEN_SET_DATA | XNET_QOS_LISTEN_ENABLE ); } #endif // Send a notification kvEvent->SetString( "state", "host" ); kvEvent->SetUint64( "xuid", m_xuidMachineId ); } else { // Send a notification DevMsg( "CSysSessionClient - client migration finished\n" ); kvEvent->SetString( "state", "client" ); kvEvent->SetUint64( "xuid", m_pSettings->GetUint64( "members/machine0/id", 0ull ) ); } SendEventsNotification( kvEvent ); return false; } #endif void CSysSessionBase::Destroy() { #ifdef _X360 // If the session is in an active gameplay state, first statistic reporting // should be initiated on the session before it can be destroyed if ( m_lobby.m_bXSessionStarted ) { DevWarning( "CSysSessionBase::Destroy called on an active gameplay session, forcing XSessionEnd!\n" ); MMX360_LobbySetActiveGameplayState( m_lobby, false, NULL ); } // If a migrate call was outstanding on the session, then disassociate the listener if ( m_hLobbyMigrateCall ) { MMX360_LobbyMigrateSetListener( m_hLobbyMigrateCall, NULL ); m_hLobbyMigrateCall = NULL; } bool bShouldAllowHostMigration = ShouldAllowX360HostMigration(); if ( KeyValues *msg = new KeyValues( "SysSession::Quit" ) ) { KeyValues::AutoDelete autodelete( msg ); msg->SetUint64( "id", m_xuidMachineId ); SendMessage( msg ); } if ( m_pNetworkMgr && !bShouldAllowHostMigration ) { m_pNetworkMgr->Destroy(); m_pNetworkMgr = NULL; } ReleaseAsyncOperation(); Voice_ProcessTalkers( NULL, false ); if ( !bShouldAllowHostMigration ) { if ( m_lobby.m_hHandle ) MMX360_LobbyDelete( m_lobby, &m_pAsyncOperation ); else SysSession360_RegisterPending( this ); // registers first as if lobby delete has completed m_pSettings = NULL; SysSession360_RegisterPending( this ); return; } else { // Waiting for migration to finish DevMsg( "CSysSessionBase::Destroy is waiting for migration to finish...\n" ); m_pSettings = NULL; SysSession360_RegisterPending( this ); return; } #elif !defined( NO_STEAM ) Voice_ProcessTalkers( NULL, false ); if ( m_lobby.m_uiLobbyID ) { if ( !IsServiceSession() ) { for ( int k = 0, kNum = steamapicontext->SteamMatchmaking()->GetNumLobbyMembers( m_lobby.m_uiLobbyID ); k < kNum; ++ k ) { CSteamID idRemote = steamapicontext->SteamMatchmaking()->GetLobbyMemberByIndex( m_lobby.m_uiLobbyID, k ); steamapicontext->SteamNetworking()->CloseP2PChannelWithUser( idRemote, INetSupport::SP2PC_LOBBY ); } } steamapicontext->SteamMatchmaking()->LeaveLobby( m_lobby.m_uiLobbyID ); } m_lobby = CSteamLobbyObject(); #endif delete this; } void CSysSessionBase::DebugPrint() { DevMsg( "CSysSessionBase\n" ); DevMsg( " machineid: %llx\n", m_xuidMachineId ); #ifdef _X360 DevMsg( " nonce: %llx\n", m_lobby.m_uiNonce ); DevMsg( " xhandle: %x\n", m_lobby.m_hHandle ); DevMsg( " xnkid: %llx\n", ( const uint64 & ) m_lobby.m_xiInfo.sessionID ); DevMsg( " xstarted: %d\n", m_lobby.m_bXSessionStarted ); XSESSION_LOCAL_DETAILS xld = {0}; DWORD dwSize = sizeof( xld ); DWORD ret = g_pMatchExtensions->GetIXOnline()->XSessionGetDetails( m_lobby.m_hHandle, &dwSize, &xld, NULL ); if ( ERROR_SUCCESS == ret ) { DevMsg( " idx host: %d\n", xld.dwUserIndexHost ); DevMsg( " game type: %d\n", xld.dwGameType ); DevMsg( " game mode: %d\n", xld.dwGameMode ); DevMsg( " flags: 0x%X\n", xld.dwFlags ); DevMsg( " pub slots: %d/%d\n", xld.dwMaxPublicSlots - xld.dwAvailablePublicSlots, xld.dwMaxPublicSlots ); DevMsg( " pri slots: %d/%d\n", xld.dwMaxPrivateSlots - xld.dwMaxPrivateSlots, xld.dwMaxPrivateSlots ); DevMsg( " members: %d (%d)\n", xld.dwActualMemberCount, xld.dwReturnedMemberCount ); DevMsg( " xsesstate: %d\n", xld.eState ); DevMsg( " xnonce: %llx\n", xld.qwNonce ); DevMsg( " xnkid: %llx\n", ( const uint64 & ) xld.sessionInfo.sessionID ); DevMsg( " xnkid arb: %llx\n", ( const uint64 & ) xld.xnkidArbitration ); } else { DevMsg( " session sys details unavailable: code %d\n", ret ); } if ( m_pNetworkMgr ) { m_pNetworkMgr->DebugPrint(); } else { DevMsg( " no network mgr\n" ); } #elif !defined( NO_STEAM ) DevMsg( " lobby id: %llx\n", m_lobby.m_uiLobbyID ); DevMsg( " lbystate: %d\n", m_lobby.m_eLobbyState ); if ( m_lobby.m_uiLobbyID ) { int numMembers = steamapicontext->SteamMatchmaking()->GetNumLobbyMembers( m_lobby.m_uiLobbyID ); DevMsg( " owner: %llx\n", steamapicontext->SteamMatchmaking()->GetLobbyOwner( m_lobby.m_uiLobbyID ).ConvertToUint64() ); DevMsg( " members: %d/%s/%d\n", numMembers, steamapicontext->SteamMatchmaking()->GetLobbyData( m_lobby.m_uiLobbyID, "members:numSlots" ), steamapicontext->SteamMatchmaking()->GetLobbyMemberLimit( m_lobby.m_uiLobbyID ) ); for ( int k = 0; k < numMembers; ++ k ) { XUID xuid = steamapicontext->SteamMatchmaking()->GetLobbyMemberByIndex( m_lobby.m_uiLobbyID, k ).ConvertToUint64(); KeyValues *kvPlayer = SessionMembersFindPlayer( m_pSettings, xuid ); if ( kvPlayer ) { DevMsg( " member%02d: %llx '%s'\n", k, xuid, kvPlayer->GetString( "name" ) ); } else { DevMsg( " member%02d: %llx \n", k, xuid ); } } DevMsg( " ldata:net: %s\n", steamapicontext->SteamMatchmaking()->GetLobbyData( m_lobby.m_uiLobbyID, "system:network" ) ); } #endif } void CSysSessionBase::Command( KeyValues *pCommand ) { KeyValues *msg = new KeyValues( "SysSession::Command" ); KeyValues::AutoDelete autodelete( msg ); msg->AddSubKey( pCommand->MakeCopy() ); SendMessage( msg ); } uint64 CSysSessionBase::GetReservationCookie() { if ( uint64 uiReservationCookieOverride = m_pSettings->GetUint64( "server/reservationid", 0ull ) ) return uiReservationCookieOverride; return GetNonceCookie(); } uint64 CSysSessionBase::GetNonceCookie() { #ifdef _X360 return m_lobby.m_uiNonce; #elif !defined( NO_STEAM ) return m_lobby.GetSessionId(); #else Assert( false ); // Not implemented for this platform return 0; #endif } uint64 CSysSessionBase::GetSessionID() { return GetNonceCookie(); } void CSysSessionBase::ReplyLanSearch( KeyValues *msg ) { // Assemble a search reply message KeyValues *reply = new KeyValues( "GameDetailsPlayer" ); KeyValues::AutoDelete autodelete( reply ); // Put information about our session #ifdef _X360 char chSessionInfo[ XSESSION_INFO_STRING_LENGTH ] = {0}; MMX360_SessionInfoToString( m_lobby.m_xiInfo, chSessionInfo ); reply->SetString( "options/sessioninfo", chSessionInfo ); #elif !defined( NO_STEAM ) reply->SetUint64( "options/sessionid", m_lobby.m_uiLobbyID ); #endif // Information about primary player if ( IPlayerLocal *pPlayer = g_pPlayerManager->GetLocalPlayer( XBX_GetPrimaryUserId() ) ) { reply->SetString( "player/name", pPlayer->GetName() ); reply->SetUint64( "player/xuid", pPlayer->GetXUID() ); #ifdef _X360 XUSER_SIGNIN_INFO xsi; if ( ERROR_SUCCESS == XUserGetSigninInfo( XBX_GetPrimaryUserId(), XUSER_GET_SIGNIN_INFO_ONLINE_XUID_ONLY, &xsi ) && xsi.xuid ) { reply->SetUint64( "player/xuidOnline", xsi.xuid ); } #endif } // Compose the binary encoding of game details CUtlBuffer bufGameDetails; bufGameDetails.ActivateByteSwapping( !CByteswap::IsMachineBigEndian() ); g_pMatchFramework->GetMatchNetworkMsgController()->PackageGameDetailsForQOS( m_pSettings, bufGameDetails ); reply->SetPtr( "binary/ptr", bufGameDetails.Base() ); reply->SetInt( "binary/size", bufGameDetails.TellMaxPut() ); // Reply to sender g_pConnectionlessLanMgr->SendPacket( reply, ( !IsX360() && msg ) ? msg->GetString( "from", NULL ) : NULL ); } void CSysSessionBase::SendMessage( KeyValues *msg ) { #ifdef _X360 if ( m_pNetworkMgr ) m_pNetworkMgr->ConnectionPeerSendMessage( msg ); // Do not receive my own quit message bool bCanReceive = !!Q_stricmp( "SysSession::Quit", msg->GetName() ); if ( bCanReceive ) ReceiveMessage( msg, true ); #elif !defined( NO_STEAM ) CUtlBuffer buf; buf.ActivateByteSwapping( !CByteswap::IsMachineBigEndian() ); buf.PutInt( g_pMatchExtensions->GetINetSupport()->GetEngineBuildNumber() ); msg->WriteAsBinary( buf ); // Special case when encoding binary data KeyValues *kvPtr = msg->FindKey( "binary/ptr" ); KeyValues *kvSize = msg->FindKey( "binary/size" ); if ( kvPtr && kvSize ) { void *pvData = kvPtr->GetPtr(); int nSize = kvSize->GetInt(); if ( pvData && nSize ) { buf.Put( pvData, nSize ); } } if ( char const *szP2P = msg->GetString( "p2p", NULL ) ) { Assert( !IsServiceSession() ); // Determine P2P type EP2PSend eSendType = k_EP2PSendUnreliableNoDelay; // "nodelay" if ( !Q_stricmp( szP2P, "reliable" ) ) eSendType = k_EP2PSendReliable; else if ( !Q_stricmp( szP2P, "buffer" ) ) eSendType = k_EP2PSendReliableWithBuffering; else if ( !Q_stricmp( szP2P, "unreliable" ) ) eSendType = k_EP2PSendUnreliable; // Transmit P2P message // for ( int k = 0, kNum = steamapicontext->SteamMatchmaking()->GetNumLobbyMembers( m_lobby.m_uiLobbyID ); k < kNum; ++ k ) // { // CSteamID idRemote = steamapicontext->SteamMatchmaking()->GetLobbyMemberByIndex( m_lobby.m_uiLobbyID, k ); // if ( idRemote.ConvertToUint64() != m_xuidMachineId ) // steamapicontext->SteamNetworking()->SendP2PPacket( idRemote, buf.Base(), buf.TellMaxPut(), eSendType, INetSupport::SP2PC_LOBBY ); // } for ( int k = 0, numMachines = m_pSettings->GetInt( "members/numMachines" ); k < numMachines; ++ k ) { for ( int ic = 0, numPlayers = m_pSettings->GetInt( CFmtStr( "members/machine%d/numPlayers", k ) ); ic < numPlayers; ++ ic ) { XUID xuidPlayer = m_pSettings->GetUint64( CFmtStr( "members/machine%d/player%d/xuid", k, ic ) ); if ( xuidPlayer && xuidPlayer != m_xuidMachineId ) { steamapicontext->SteamNetworking()->SendP2PPacket( xuidPlayer, buf.Base(), buf.TellMaxPut(), eSendType, INetSupport::SP2PC_LOBBY ); m_bVoiceUsingSessionP2P = true; } } } // Receive on our own side ReceiveMessage( msg, true, m_xuidMachineId ); } else if ( m_lobby.m_uiLobbyID ) { steamapicontext->SteamMatchmaking()->SendLobbyChatMsg( m_lobby.m_uiLobbyID, buf.Base(), buf.TellMaxPut() ); } else { ReceiveMessage( msg, true, m_xuidMachineId ); } #endif } void CSysSessionBase::ReceiveMessage( KeyValues *msg, bool bValidatedLobbyMember, XUID xuidSrc ) { char const *szMsg = msg->GetName(); if ( !Q_stricmp( "SysSession::Quit", szMsg ) ) { XUID xuidMachine = msg->GetUint64( "id" ); Assert( xuidMachine == xuidSrc ); // assuming that xuidMachine and xuid of primary user are same OnPlayerLeave( xuidSrc ); } else if ( !Q_stricmp( "SysSession::Command", szMsg ) ) { KeyValues *pCommand = msg->GetFirstTrueSubKey(); if ( !pCommand ) return; char const *szRun = pCommand->GetString( "run", "" ); bool bRun = false; if ( !Q_stricmp( szRun, "all" ) ) bRun = true; else if ( !Q_stricmp( szRun, "host" ) && dynamic_cast< CSysSessionHost * >( this ) ) bRun = true; else if ( !Q_stricmp( szRun, "clients" ) && dynamic_cast< CSysSessionClient * >( this ) ) bRun = true; else if ( !Q_stricmp( szRun, "xuid" ) && m_xuidMachineId == pCommand->GetUint64( "runxuid" ) ) bRun = true; if ( bRun ) { KeyValues *kv = new KeyValues( "mmF->SysSessionCommand" ); msg->RemoveSubKey( pCommand ); kv->AddSubKey( pCommand ); // We always set these field regardless of what was in command to indicate that it arrived from a remote machine bool bHostSrcXuid = ( xuidSrc == GetHostXuid() ); pCommand->SetBool( "_remote_host", bHostSrcXuid ); pCommand->SetUint64( "_remote_xuidsrc", xuidSrc ); kv->SetBool( "host", bHostSrcXuid ); kv->SetUint64( "xuidsrc", xuidSrc ); kv->SetPtr( "syssession", this ); OnSessionEvent( kv ); } } else if ( !Q_stricmp( "SysSession::Voice", szMsg ) ) { Voice_Playback( msg, xuidSrc ); } } #ifdef _X360 void CSysSessionBase::OnX360NetPacket( KeyValues *msg ) { ReceiveMessage( msg, true ); } void CSysSessionBase::OnX360NetDisconnected( XUID xuidRemote ) { OnPlayerLeave( xuidRemote ); } void CSysSessionBase::OnX360AllSessionMembersJoinLeave( KeyValues *kv ) { char const *szLock = kv->GetString( "system/lock", NULL ); char const *szAccess = kv->GetString( "system/access", NULL ); if ( szAccess || ( szLock && Q_stricmp( szLock, "endgame" ) && !IsX360() ) ) { if ( m_lobby.m_bXSessionStarted ) { DevWarning( "CSysSessionBase::OnX360AllSessionMembersJoinLeave cannot be called on an active gameplay session!\n" ); Assert( !m_lobby.m_bXSessionStarted ); } MMX360_LobbyLeaveMembers( m_pSettings, m_lobby ); { CX360LobbyFlags_t fl = MMX360_DescribeLobbyFlags( m_pSettings, !!dynamic_cast< CSysSessionHost * >( this ) ); g_pMatchExtensions->GetIXOnline()->XSessionModify( m_lobby.m_hHandle, fl.m_dwFlags, fl.m_numPublicSlots, fl.m_numPrivateSlots, MMX360_NewOverlappedDormant() ); } MMX360_LobbyJoinMembers( m_pSettings, m_lobby ); } } #elif !defined( NO_STEAM ) void CSysSessionBase::UnpackAndReceiveMessage( const void *pvBuffer, int numBytes, bool bValidatedLobbyMember, XUID xuidSrc ) { if ( numBytes <= 0 ) return; CUtlBuffer buf( pvBuffer, numBytes, CUtlBuffer::READ_ONLY ); buf.ActivateByteSwapping( !CByteswap::IsMachineBigEndian() ); if ( buf.GetInt() != g_pMatchExtensions->GetINetSupport()->GetEngineBuildNumber() ) return; KeyValues *msg = new KeyValues( "" ); KeyValues::AutoDelete autodelete( msg ); if ( !msg->ReadAsBinary( buf ) ) return; // Special case when decoding binary data static byte chBuffer2[ STEAM_LOBBY_CHAT_MSG_BUFFER_SIZE ]; KeyValues *kvPtr = msg->FindKey( "binary/ptr" ); KeyValues *kvSize = msg->FindKey( "binary/size" ); if ( kvPtr && kvSize ) { void *pvData = kvPtr->GetPtr(); int nSize = kvSize->GetInt(); if ( nSize < 0 || nSize >= STEAM_LOBBY_CHAT_MSG_BUFFER_SIZE ) return; if ( pvData && nSize ) { if ( !buf.Get( chBuffer2, nSize ) ) return; kvPtr->SetPtr( NULL, chBuffer2 ); } } ReceiveMessage( msg, bValidatedLobbyMember, xuidSrc ); } // We should keep this global since client DLL writes a value here volatile uint32 *g_hRankingSetupCallHandle = 0; void CSysSessionBase::SetupSteamRankingConfiguration() { KeyValues *kvNotification = new KeyValues( "SetupSteamRankingConfiguration" ); kvNotification->SetPtr( "settingsptr", m_pSettings ); kvNotification->SetPtr( "callhandleptr", ( void * ) &g_hRankingSetupCallHandle ); SendEventsNotification( kvNotification ); } bool CSysSessionBase::IsSteamRankingConfigured() const { return !g_hRankingSetupCallHandle || !*g_hRankingSetupCallHandle; } void CSysSessionBase::Steam_OnLobbyChatMsg( LobbyChatMsg_t *pLobbyChatMsg ) { if ( pLobbyChatMsg->m_ulSteamIDLobby != m_lobby.m_uiLobbyID ) return; static byte chBuffer[ STEAM_LOBBY_CHAT_MSG_BUFFER_SIZE ]; CSteamID steamIDSender; EChatEntryType ecet; int numBytes = steamapicontext->SteamMatchmaking()->GetLobbyChatEntry( m_lobby.m_uiLobbyID, pLobbyChatMsg->m_iChatID, &steamIDSender, chBuffer, sizeof( chBuffer ), &ecet ); // Is this a validated lobby member? bool bValidatedLobbyMember = ( SessionMembersFindPlayer( m_pSettings, steamIDSender.ConvertToUint64() ) != NULL ); UnpackAndReceiveMessage( chBuffer, numBytes, bValidatedLobbyMember, steamIDSender.ConvertToUint64() ); } void CSysSessionBase::Steam_OnLobbyChatUpdate( LobbyChatUpdate_t *pLobbyChatUpdate ) { if ( pLobbyChatUpdate->m_ulSteamIDLobby != m_lobby.m_uiLobbyID ) return; if ( BChatMemberStateChangeRemoved( pLobbyChatUpdate->m_rgfChatMemberStateChange ) ) { XUID xuidLocal = g_pPlayerManager->GetLocalPlayer( XBX_GetPrimaryUserId() )->GetXUID(); if ( pLobbyChatUpdate->m_ulSteamIDUserChanged == xuidLocal ) { if ( pLobbyChatUpdate->m_ulSteamIDMakingChange != xuidLocal ) { // Prepare the update notification KeyValues *kv = new KeyValues( "mmF->SysSessionUpdate" ); kv->SetPtr( "syssession", this ); kv->SetString( "error", "kicked" ); SendEventsNotification( kv ); } } else { OnPlayerLeave( pLobbyChatUpdate->m_ulSteamIDUserChanged ); } } } void CSysSessionBase::Steam_OnP2PSessionRequest( P2PSessionRequest_t *pParam ) { uint64 idRemote = pParam->m_steamIDRemote.ConvertToUint64(); if ( m_lobby.m_uiLobbyID && g_pMatchExtensions->GetIVEngineClient() && ( !g_pMatchExtensions->GetIVEngineClient()->IsConnected() || g_pMatchExtensions->GetIVEngineClient()->IsClientLocalToActiveServer() ) && SessionMembersFindPlayer( m_pSettings, idRemote ) && ( !IsPS3() || ( m_lobby.m_eLobbyState == CSteamLobbyObject::STATE_DEFAULT ) ) ) { // We are in the lobby together, accept P2P session request steamapicontext->SteamNetworking()->AcceptP2PSessionWithUser( idRemote ); m_bVoiceUsingSessionP2P = true; } } void CSysSessionBase::Steam_OnServersConnected( SteamServersConnected_t *pParam ) { } void CSysSessionBase::Steam_OnServersDisconnected( SteamServersDisconnected_t *pParam ) { // In case we are not in active gameplay we should just drop // back to main menu while Steam is disconnected if ( m_lobby.m_eLobbyState == CSteamLobbyObject::STATE_DEFAULT ) { // Prepare the update notification KeyValues *kv = new KeyValues( "mmF->SysSessionUpdate" ); kv->SetPtr( "syssession", this ); kv->SetString( "error", "SteamServersDisconnected" ); SendEventsNotification( kv ); return; } // If we already got disconnected once prevent re-entry if ( m_lobby.m_eLobbyState == CSteamLobbyObject::STATE_DISCONNECTED_FROM_STEAM ) return; m_lobby.m_eLobbyState = CSteamLobbyObject::STATE_DISCONNECTED_FROM_STEAM; // Otherwise we should manually leave the lobby steamapicontext->SteamMatchmaking()->LeaveLobby( m_lobby.m_uiLobbyID ); m_lobby.m_uiLobbyID = 0ull; // set the session lock m_pSettings->SetString( "system/lock", "SteamServersDisconnected" ); // Check if we can remove all remote players from the session if ( !V_stricmp( m_pSettings->GetString( "system/netflag" ), "noleave" ) ) { DevMsg( "CSysSessionBase::Steam_OnServersDisconnected keeping players in noleave mode.\n" ); return; } // // Remove all the remote players from the session // XUID xuidLocal = g_pPlayerManager->GetLocalPlayer( XBX_GetPrimaryUserId() )->GetXUID(); KeyValues *pMembers = m_pSettings->FindKey( "members" ); Assert( pMembers ); if ( !pMembers ) return; int numMachines = pMembers->GetInt( "numMachines", 0 ); for ( int k = 0; k < numMachines; ++ k ) { KeyValues *kvMachine = pMembers->FindKey( CFmtStr( "machine%d", k ) ); if ( !kvMachine ) continue; XUID idMachine = kvMachine->GetUint64( "id" ); if ( idMachine == xuidLocal ) { kvMachine->SetName( "machine0" ); pMembers->SetInt( "numPlayers", kvMachine->GetInt( "numPlayers", 1 ) ); pMembers->SetInt( "numSlots", kvMachine->GetInt( "numPlayers", 1 ) ); } else { pMembers->RemoveSubKey( kvMachine ); kvMachine->deleteThis(); } } pMembers->SetInt( "numMachines", 1 ); } char const * CSysSessionBase::LobbyEnterErrorAsString( LobbyEnter_t *pLobbyEnter ) { switch ( pLobbyEnter->m_EChatRoomEnterResponse ) { case k_EChatRoomEnterResponseFull: return "full"; case k_EChatRoomEnterResponseBanned: case k_EChatRoomEnterResponseNotAllowed: return "notwanted"; case k_EChatRoomEnterResponseMemberBlockedYou: return "blockedyou"; case k_EChatRoomEnterResponseYouBlockedMember: return "youblocked"; case k_EChatRoomEnterResponseDoesntExist: return "doesntexist"; case k_EChatRoomEnterResponseRatelimitExceeded: return "ratelimit"; default: return "create"; } } void CSysSessionBase::LobbySetDataFromKeyValues( char const *szPath, KeyValues *key, bool bRecurse ) { if ( !key || !szPath ) return; char chKey[ 256 ]; char chValue[ 256 ]; if ( key->GetDataType() != KeyValues::TYPE_NONE ) { PrintValue( key, chValue, ARRAYSIZE( chValue ) ); DevMsg( "LobbySetData: '%s' = '%s'\n", szPath, chValue ); steamapicontext->SteamMatchmaking()->SetLobbyData( m_lobby.m_uiLobbyID, szPath, chValue ); } else for ( KeyValues *sub = key->GetFirstSubKey(); sub; sub = sub->GetNextKey() ) { if ( !bRecurse && sub->GetDataType() == KeyValues::TYPE_NONE ) continue; Q_snprintf( chKey, ARRAYSIZE( chKey ), "%s:%s", szPath, sub->GetName() ); if ( Q_stricmp( chKey, "System:dependentlobby" ) == 0 ) { // set magic dependent lobby something steamapicontext->SteamMatchmaking()->SetLinkedLobby( m_lobby.m_uiLobbyID, sub->GetUint64() ); } else LobbySetDataFromKeyValues( chKey, sub, bRecurse ); } // Expose lobby members too because Steam doesn't do it for us (lobby owner is first) if ( !V_stricmp( szPath, "members" ) ) { CUtlVector< AccountID_t > arrAccounts; int numMachines = key->GetInt( "numMachines", 0 ); arrAccounts.EnsureCapacity( numMachines + 1 ); arrAccounts.AddToTail( CSteamID( GetHostXuid() ).GetAccountID() ); for ( int k = 0; k < numMachines; ++k ) { KeyValues *kvMachine = m_pSettings->FindKey( CFmtStr( "members/machine%d", k ) ); if ( kvMachine ) { uint64 ullMachineID = kvMachine->GetUint64( "id" ); if ( AccountID_t unAccountID = CSteamID( ullMachineID ).GetAccountID() ) { if ( unAccountID != arrAccounts.Head() ) arrAccounts.AddToTail( unAccountID ); } } } // We are going to write out these bytes as "varints" encoding, // which also ensures that they can fit in lobby metadata as no // single byte will be 0x00 const int numBytesStackAlloc = ( 1 + arrAccounts.Count() ) * 5; // 5 bytes max per varint uint8 * const packedData = ( uint8 * ) stackalloc( numBytesStackAlloc ); uint8 *pWrite = packedData; FOR_EACH_VEC( arrAccounts, k ) { uint32 data = arrAccounts[k]; while ( data > 0x7F ) { *( pWrite ++ ) = uint8( ( data & 0x7F ) | 0x80 ); data >>= 7; } Assert( data & 0x7F ); *( pWrite ++ ) = uint8( data & 0x7F ); } *( pWrite ) = 0; // null-terminator for the "string" steamapicontext->SteamMatchmaking()->SetLobbyData( m_lobby.m_uiLobbyID, "uids", ( char * ) packedData ); } } #endif void CSysSessionBase::Voice_ProcessTalkers( KeyValues *pMachine, bool bAdd ) { if ( IsServiceSession() ) return; if ( !pMachine ) // Process all members as talkers { int numMachines = m_pSettings->GetInt( "members/numMachines", 0 ); for ( int k = 0; k < numMachines; ++ k ) { KeyValues *kvMachine = m_pSettings->FindKey( CFmtStr( "members/machine%d", k ) ); if ( kvMachine ) Voice_ProcessTalkers( kvMachine, bAdd ); } if ( bAdd && m_Voice_flLastHeadsetStatusCheck < 0 ) m_Voice_flLastHeadsetStatusCheck = 0; return; } XUID xuidMachine = pMachine->GetUint64( "id", 0ull ); if ( !xuidMachine ) return; int numPlayers = pMachine->GetInt( "numPlayers", 0 ); uint64 uiMachineFlags = pMachine->GetUint64( "flags" ); for ( int k = 0; k < numPlayers; ++ k ) { XUID xuid = pMachine->GetUint64( CFmtStr( "player%d/xuid", k ), 0ull ); if ( !xuid ) continue; int iCtrlr = -1; if ( xuidMachine == m_xuidMachineId ) { iCtrlr = XBX_GetUserId( k ); xuid = 0ull; // for local users we use only controller index } if ( IEngineVoice *pIEngineVoice = g_pMatchExtensions->GetIEngineVoice() ) { if ( bAdd ) pIEngineVoice->AddPlayerToVoiceList( xuid, iCtrlr, (uiMachineFlags & MACHINE_PLATFORM_PS3) ? ENGINE_VOICE_FLAG_PS3 : 0 ); else { pIEngineVoice->RemovePlayerFromVoiceList( xuid, iCtrlr ); #if !defined( NO_STEAM ) // When removing from voice list tear down P2P session steamapicontext->SteamNetworking()->CloseP2PSessionWithUser( xuid ); #endif } } } } void CSysSessionBase::Voice_CaptureAndTransmitLocalVoiceData() { if ( IsServiceSession() ) return; IEngineVoice *v = g_pMatchExtensions->GetIEngineVoice(); if ( !v ) return; if ( g_pMatchFramework->GetMatchTitle()->GetTitleSettingsFlags() & MATCHTITLE_VOICE_INGAME ) { #ifdef _X360 if ( m_lobby.m_bXSessionStarted ) return; #elif !defined( NO_STEAM ) if ( m_lobby.m_eLobbyState != m_lobby.STATE_DEFAULT ) { if ( IsPS3() && m_bVoiceUsingSessionP2P ) { m_bVoiceUsingSessionP2P = false; // As soon as we start loading into a game we should shutdown all P2P communication for ( int k = 0, numMachines = m_pSettings->GetInt( "members/numMachines" ); k < numMachines; ++ k ) { for ( int ic = 0, numPlayers = m_pSettings->GetInt( CFmtStr( "members/machine%d/numPlayers", k ) ); ic < numPlayers; ++ ic ) { XUID xuidPlayer = m_pSettings->GetUint64( CFmtStr( "members/machine%d/player%d/xuid", k, ic ) ); if ( xuidPlayer && xuidPlayer != m_xuidMachineId ) { steamapicontext->SteamNetworking()->CloseP2PSessionWithUser( xuidPlayer ); DevMsg( "PS3 Session has shut down P2P session with %llx!\n", xuidPlayer ); } } } } return; } #endif } for ( DWORD k = 0; k < XBX_GetNumGameUsers(); ++ k ) { #ifdef _GAMECONSOLE int iCtrlr = XBX_GetUserId( k ); #else int iCtrlr = XBX_GetPrimaryUserId(); #endif if ( v->VoiceUpdateData( iCtrlr ) ) { // Capture the voice data buffers const byte *pbVoiceData = NULL; unsigned int numBytes = 0; v->GetVoiceData( iCtrlr, &pbVoiceData, &numBytes ); if ( mm_session_voice_loading.GetBool() || !( g_pMatchExtensions->GetIVEngineClient()->IsDrawingLoadingImage() || g_pMatchExtensions->GetIVEngineClient()->IsTransitioningToLoad() ) ) { // Package it up as a message KeyValues *msg = new KeyValues( "SysSession::Voice" ); KeyValues::AutoDelete autodelete_msg( msg ); msg->SetString( "p2p", "nodelay" ); // marks the message as preferring p2p transfer msg->SetUint64( "xuid", g_pPlayerManager->GetLocalPlayer( iCtrlr )->GetXUID() ); unsigned int numBytesRemaining = numBytes, numBytesOffset = 0; while ( numBytesRemaining > 0 ) { numBytes = MIN( 1024, numBytesRemaining ); msg->SetPtr( "binary/ptr", numBytesOffset + const_cast< byte * >( pbVoiceData ) ); msg->SetInt( "binary/size", numBytes ); numBytesRemaining -= numBytes; numBytesOffset += numBytes; // Deliver the message to peers SendMessage( msg ); } } // Reset voice buffers v->VoiceResetLocalData( iCtrlr ); } } } void CSysSessionBase::Voice_Playback( KeyValues *msg, XUID xuidSrc ) { XUID xuid = msg->GetUint64( "xuid", 0ull ); Assert( xuid == xuidSrc ); if ( xuid != xuidSrc ) return; const byte *pbVoiceData = ( const byte * ) msg->GetPtr( "binary/ptr" ); unsigned int numBytes = msg->GetInt( "binary/size" ); if ( !pbVoiceData || !numBytes ) // We cannot playback the data or muting the player return; if ( mm_session_voice_loading.GetBool() || !( g_pMatchExtensions->GetIVEngineClient()->IsDrawingLoadingImage() || g_pMatchExtensions->GetIVEngineClient()->IsTransitioningToLoad() ) ) { g_pMatchExtensions->GetIEngineVoice()->PlayIncomingVoiceData( xuid, pbVoiceData, numBytes ); if ( g_pMatchFramework->GetMatchSystem()->GetMatchVoice()->CanPlaybackTalker( xuid ) ) { if ( KeyValues *notify = new KeyValues( "OnPlayerActivity", "act", "voice" ) ) { notify->SetUint64( "xuid", xuid ); OnSessionEvent( notify ); } } } } void CSysSessionBase::Voice_UpdateLocalHeadsetsStatus() { if ( IsServiceSession() ) return; if ( m_Voice_flLastHeadsetStatusCheck < 0 ) return; // Not too frequently check for the headset status changes if ( Plat_FloatTime() - m_Voice_flLastHeadsetStatusCheck < 1.0f ) return; m_Voice_flLastHeadsetStatusCheck = Plat_FloatTime(); // Find the local machine KeyValues *pMachine = NULL; SessionMembersFindPlayer( m_pSettings, m_xuidMachineId, &pMachine ); if ( !pMachine ) return; // Whether current status is different from session settings for ( uint k = 0; k < XBX_GetNumGameUsers(); ++ k ) { bool bHeadset = false; #ifdef _GAMECONSOLE int iCtrlr = XBX_GetUserId( k ); if ( !XBX_GetUserIsGuest( k ) ) bHeadset = g_pMatchExtensions->GetIEngineVoice()->IsHeadsetPresent( iCtrlr ); #elif !defined( NO_STEAM ) bHeadset = g_pMatchExtensions->GetIEngineVoice()->IsHeadsetPresent( XBX_GetPrimaryUserId() ); #endif char const *szCurValue = pMachine->GetString( CFmtStr( "player%d/voice", k ), "" ); char const *szHeadsetValue = bHeadset ? "headset" : ""; if ( szCurValue[0] != szHeadsetValue[0] ) { KeyValues *msg = new KeyValues( "SysSession::VoiceStatus" ); KeyValues::AutoDelete autodelete_msg( msg ); XUID xuid = pMachine->GetUint64( CFmtStr( "player%d/xuid", k ), 0ull ); msg->SetUint64( "xuid", xuid ); msg->SetString( "voice", szHeadsetValue ); SendMessage( msg ); } } } void CSysSessionBase::Voice_UpdateMutelist() { if ( IsServiceSession() ) return; // Generate the mutelist and send it if it is different from the current settings KeyValues *msg = new KeyValues( "SysSession::VoiceMutelist" ); KeyValues::AutoDelete autodelete_msg( msg ); msg->SetUint64( "xuid", m_xuidMachineId ); if ( KeyValues *pMembers = m_pSettings ? m_pSettings->FindKey( "members" ) : NULL ) { int numMachines = pMembers->GetInt( "numMachines" ); for ( int i = 0; i < numMachines; ++ i ) { XUID xuid = pMembers->GetUint64( CFmtStr( "machine%d/id", i ) ); if ( g_pMatchVoice->IsMachineMuted( xuid ) ) msg->SetUint64( CFmtStr( "Mutelist/%d", i ), xuid ); } } // Find current mutelist KeyValues *pLocalMachine = NULL; SessionMembersFindPlayer( m_pSettings, m_xuidMachineId, &pLocalMachine ); if ( pLocalMachine ) { KeyValues *pCurMutelist = pLocalMachine->FindKey( "Mutelist" ); KeyValues *pNewMutelist = msg->FindKey( "Mutelist" ); if ( pCurMutelist && pNewMutelist ) { for ( pCurMutelist = pCurMutelist->GetFirstValue(), pNewMutelist = pNewMutelist->GetFirstValue(); pCurMutelist && pNewMutelist; pCurMutelist = pCurMutelist->GetNextValue(), pNewMutelist = pNewMutelist->GetNextValue() ) { if ( pCurMutelist->GetUint64() != pNewMutelist->GetUint64() ) break; } } if ( !pCurMutelist && !pNewMutelist ) // current and new mutelists compared equal return; } SendMessage( msg ); } void CSysSessionBase::OnPlayerLeave( XUID xuid ) { } bool CSysSessionBase::FindAndRemovePlayerFromMembers( XUID xuid ) { KeyValues *pMembers = m_pSettings->FindKey( "members" ); Assert( pMembers ); if ( !pMembers ) return false; int numMachines = pMembers->GetInt( "numMachines", 0 ); int numPlayers = pMembers->GetInt( "numPlayers", 0 ); for ( int k = 0; k < numMachines; ++ k ) { KeyValues *pMachine = pMembers->FindKey( CFmtStr( "machine%d", k ) ); if ( !pMachine ) continue; int numOtherPlayers = pMachine->GetInt( "numPlayers", 0 ); for ( int j = 0; j < numOtherPlayers; ++ j ) { XUID xuidOtherPlayer = pMachine->GetUint64( CFmtStr( "player%d/xuid", j ), 0ull ); if ( xuidOtherPlayer == xuid ) { #ifdef _X360 // On X360 we need to update lobby members server-side count MMX360_LobbyLeaveMembers( m_pSettings, m_lobby, k, k ); #endif // We also need to remove talkers Voice_ProcessTalkers( pMachine, false ); // The entire machine will be effectively disconnected KeyValues::AutoDelete autodelete( pMachine ); pMembers->RemoveSubKey( pMachine ); for ( int kk = k + 1; kk < numMachines; ++ kk ) { KeyValues *pNextMachine = pMembers->FindKey( CFmtStr( "machine%d", kk ) ); if ( !pNextMachine ) continue; pNextMachine->SetName( CFmtStr( "machine%d", kk - 1 ) ); } // Update counts numMachines = MAX( 0, numMachines - 1 ); pMembers->SetInt( "numMachines", numMachines ); numPlayers = MAX( 0, numPlayers - numOtherPlayers ); pMembers->SetInt( "numPlayers", numPlayers ); // Now fire the events for the removed players for ( j = 0; j < numOtherPlayers; ++ j ) { KeyValues *pPlayer = pMachine->FindKey( CFmtStr( "player%d", j ) ); xuidOtherPlayer = pPlayer->GetUint64( "xuid", 0ull ); KeyValues *kv = new KeyValues( "OnPlayerRemoved" ); kv->SetUint64( "xuid", xuidOtherPlayer ); if ( pPlayer ) { KeyValues *pCopyPlayer = pPlayer->MakeCopy(); pCopyPlayer->SetName( "player" ); kv->AddSubKey( pCopyPlayer ); } if ( pMachine ) { KeyValues *pCopyMachine = pMachine->MakeCopy(); pCopyMachine->SetName( "machine" ); kv->AddSubKey( pCopyMachine ); } OnSessionEvent( kv ); } #ifdef _X360 #elif !defined( NO_STEAM ) if ( dynamic_cast< CSysSessionHost * >( this ) ) { // Update members information LobbySetDataFromKeyValues( "members", m_pSettings->FindKey( "Members" ), false ); } #endif return true; } } } return false; } void CSysSessionBase::UpdateSessionProperties( KeyValues *kv, bool bHost ) { #if !defined( NO_STEAM ) SetupSteamRankingConfiguration(); #endif if ( !IsX360() && !bHost ) // On X360 every client must set their contexts, on PC it's // all Steam-server-side and only host sets the metadata return; if ( IsX360() ) return; #ifdef _X360 #elif !defined( NO_STEAM ) if ( !m_lobby.m_uiLobbyID ) return; if ( kv ) { LobbySetDataFromKeyValues( "system", kv->FindKey( "system" ) ); LobbySetDataFromKeyValues( "game", kv->FindKey( "game" ) ); LobbySetDataFromKeyValues( "options", kv->FindKey( "options" ) ); } #endif } void CSysSessionBase::SetSessionActiveGameplayState( bool bActive, char const *szSecureServerAddress ) { #ifdef _X360 MMX360_LobbySetActiveGameplayState( m_lobby, bActive, szSecureServerAddress ); #elif !defined( NO_STEAM ) switch ( m_lobby.m_eLobbyState ) { case CSteamLobbyObject::STATE_DEFAULT: if ( bActive ) { m_lobby.m_eLobbyState = CSteamLobbyObject::STATE_ACTIVE_GAME; } break; case CSteamLobbyObject::STATE_ACTIVE_GAME: if ( !bActive ) { m_lobby.m_eLobbyState = CSteamLobbyObject::STATE_DEFAULT; } break; case CSteamLobbyObject::STATE_DISCONNECTED_FROM_STEAM: if ( !bActive ) { // If active gameplay session has ended and we were disconnected from Steam // then go back to main menu m_lobby.m_eLobbyState = CSteamLobbyObject::STATE_DEFAULT; Steam_OnServersDisconnected( NULL ); } break; } #endif } void CSysSessionBase::UpdateTeamProperties( KeyValues *pTeamProperties ) { #if defined (_X360) #elif !defined(NO_STEAM) if ( !m_lobby.m_uiLobbyID ) return; LobbySetDataFromKeyValues( "members", pTeamProperties->FindKey( "members" ) ); #endif } void CSysSessionBase::UpdateServerInfo( KeyValues *pServerKey ) { #if defined (_X360) #elif !defined(NO_STEAM) LobbySetDataFromKeyValues( "server", pServerKey->FindKey( "server" ) ); #endif } void CSysSessionBase::PrintValue( KeyValues *val, char *chBuffer, int numBytesBuffer ) { switch( val->GetDataType() ) { case KeyValues::TYPE_INT: Q_snprintf( chBuffer, numBytesBuffer, "%d", val->GetInt() ); break; case KeyValues::TYPE_UINT64: Q_snprintf( chBuffer, numBytesBuffer, "%llX", val->GetUint64() ); break; case KeyValues::TYPE_FLOAT: Q_snprintf( chBuffer, numBytesBuffer, "%f", val->GetFloat() ); break; case KeyValues::TYPE_STRING: Q_snprintf( chBuffer, numBytesBuffer, "%s", val->GetString() ); break; default: Warning( "Unknown type in CSysSessionHost::PrintValue ( %s )\n", val->GetName() ); break; } } CSysSessionHost::CSysSessionHost( KeyValues *pSettings ) : CSysSessionBase( pSettings ), #ifdef _X360 #elif !defined( NO_STEAM ) m_dblDormantMembersCheckTime( Plat_FloatTime() ), m_numDormantMembersDetected( 0 ), #endif m_eState( STATE_INIT ), m_flTimeOperationStarted( 0.0f ), m_flInitializeTimestamp( 0.0f ), m_teamResKey( 0ull ), m_numRemainingTeamPlayers( 0 ), m_flTeamResStartTime( 0.0f ), m_ullCrypt( 0 ) { } CSysSessionHost::CSysSessionHost( CSysSessionClient *pClient, KeyValues *pSettings ) : CSysSessionBase( pSettings ), #ifdef _X360 #elif !defined( NO_STEAM ) m_dblDormantMembersCheckTime( Plat_FloatTime() ), m_numDormantMembersDetected( 0 ), #endif m_eState( STATE_IDLE ), m_flTimeOperationStarted( 0.0f ), m_flInitializeTimestamp( 0.0f ), m_teamResKey( 0ull ), m_numRemainingTeamPlayers( 0 ), m_flTeamResStartTime( 0.0f ), m_ullCrypt( 0 ) { m_lobby = pClient->m_lobby; m_Voice_flLastHeadsetStatusCheck = pClient->m_Voice_flLastHeadsetStatusCheck; #ifdef _X360 m_pNetworkMgr = pClient->m_pNetworkMgr; if ( m_pNetworkMgr ) m_pNetworkMgr->SetListener( this ); m_pAsyncOperation = pClient->m_pAsyncOperation; Assert( !m_pAsyncOperation ); // If client was in migrate state, then disassociate the migrate call listener if ( pClient->m_hLobbyMigrateCall ) { MMX360_LobbyMigrateSetListener( pClient->m_hLobbyMigrateCall, NULL ); pClient->m_hLobbyMigrateCall = NULL; } // Schedule the host migration call m_hLobbyMigrateCall = MMX360_LobbyMigrateHost( m_lobby, &m_MigrateCallState ); if ( !m_hLobbyMigrateCall ) { // This is a dangerous notification because we are in the tree of constructors as // follows: CMatchSessionOnlineHost -> CSysSessionHost // The guideline to avoid troubles is to make sure that no code runs in the // constructors of CMatchSessionOnlineHost and CSysSessionHost when the notification // is fired. KeyValues *kv = new KeyValues( "mmF->SysSessionUpdate" ); kv->SetPtr( "syssession", this ); kv->SetString( "error", "migrate" ); // Inside this event broadcast our session will be deleted SendEventsNotification( kv ); return; } #elif !defined( NO_STEAM ) // Install callback for messages m_CallbackOnLobbyChatMsg.Register( this, &CSysSessionBase::Steam_OnLobbyChatMsg ); m_CallbackOnLobbyChatUpdate.Register( this, &CSysSessionBase::Steam_OnLobbyChatUpdate ); // Set the migrated members information LobbySetDataFromKeyValues( "members", m_pSettings->FindKey( "members" ), false ); #endif // Send a notification KeyValues *kvEvent = new KeyValues( "OnPlayerLeaderChanged" ); kvEvent->SetString( "state", "host" ); kvEvent->SetUint64( "xuid", m_xuidMachineId ); #ifdef _X360 kvEvent->SetString( "migrate", "started" ); #endif SendEventsNotification( kvEvent ); } CSysSessionHost::~CSysSessionHost() { ; } bool CSysSessionHost::Update() { if ( !CSysSessionBase::Update() ) return false; switch( m_eState ) { case STATE_INIT: if ( !m_flInitializeTimestamp ) { m_flInitializeTimestamp = Plat_FloatTime(); #if !defined( NO_STEAM ) SetupSteamRankingConfiguration(); #endif } if ( #if !defined( NO_STEAM ) ( IsSteamRankingConfigured() || ( Plat_FloatTime() >= m_flInitializeTimestamp + mm_session_sys_ranking_timeout.GetFloat() ) ) && #endif ( Plat_FloatTime() >= m_flInitializeTimestamp + mm_session_sys_delay_create_host.GetFloat() ) && SysSession_AllowCreate() ) { UpdateStateInit(); } break; #if !defined( NO_STEAM ) case STATE_IDLE: // Track players who are in the MMS session object, but haven't made KV request to join or failed KV request and didn't drop if ( m_lobby.m_uiLobbyID ) { double dblTimeNow = Plat_FloatTime(); if ( dblTimeNow - m_dblDormantMembersCheckTime >= 15.0 ) // check every 15 seconds { m_dblDormantMembersCheckTime = dblTimeNow; int numMembers = steamapicontext->SteamMatchmaking()->GetNumLobbyMembers( m_lobby.m_uiLobbyID ); int numDormantMembers = 0; int nLimit = steamapicontext->SteamMatchmaking()->GetLobbyMemberLimit( m_lobby.m_uiLobbyID ); for ( int k = 0; nLimit && ( k < numMembers ); ++k ) { XUID xuidMember = steamapicontext->SteamMatchmaking()->GetLobbyMemberByIndex( m_lobby.m_uiLobbyID, k ).ConvertToUint64(); if ( !xuidMember ) continue; // Check if this player is in the KV session KeyValues *kvPlayer = SessionMembersFindPlayer( m_pSettings, xuidMember ); if ( kvPlayer ) continue; // member is properly authenticated, this is the common case ++ numDormantMembers; } m_numDormantMembersDetected = numDormantMembers; int nNewLimit = m_numDormantMembersDetected + m_pSettings->GetInt( "members/numSlots", 1 ); if ( nLimit && nNewLimit != nLimit ) { steamapicontext->SteamMatchmaking()->SetLobbyMemberLimit( m_lobby.m_uiLobbyID, nNewLimit ); } } } break; #endif #ifdef _X360 case STATE_ALLOWING_MIGRATE: if ( mm_session_sys_timeout.GetFloat() + m_flTimeOperationStarted < Plat_FloatTime() ) { // Assume that migration failed or we lost connection to Xbox LIVE DestroyAfterMigrationFinished(); } break; #endif } // Update reservation status if ( m_teamResKey ) { float time = Plat_FloatTime(); if ( time >= m_flTeamResStartTime + mm_session_team_res_timeout.GetFloat() ) { UnreserveTeamSession(); } } return true; } void CSysSessionHost::Destroy() { // If we are migrating, then don't let the // base class handle it because it will // post Quit messages and leave the lobby... if ( m_eState == STATE_MIGRATE ) { delete this; return; } #ifdef _X360 if ( m_eState == STATE_DELETE ) { delete this; return; } else if ( ShouldAllowX360HostMigration() ) { m_eState = STATE_ALLOWING_MIGRATE; m_flTimeOperationStarted = Plat_FloatTime(); } else { m_eState = STATE_DELETE; } #endif // Chain to base which will "delete this" CSysSessionBase::Destroy(); } void CSysSessionHost::DebugPrint() { DevMsg( "CSysSessionHost [ state=%d ]\n", m_eState ); CSysSessionBase::DebugPrint(); } XUID CSysSessionHost::GetHostXuid( XUID xuidValidResult ) { #ifdef _X360 return m_xuidMachineId; #elif !defined( NO_STEAM ) return m_lobby.m_uiLobbyID ? steamapicontext->SteamMatchmaking() ->GetLobbyOwner( m_lobby.m_uiLobbyID ).ConvertToUint64() : m_xuidMachineId; #endif return 0ull; } #ifdef _X360 void CSysSessionHost::GetHostSessionInfo( char chBuffer[ XSESSION_INFO_STRING_LENGTH ] ) { MMX360_SessionInfoToString( m_lobby.m_xiInfo, chBuffer ); } uint64 CSysSessionHost::GetHostSessionId() { return (const uint64&)(m_lobby.m_xiInfo.sessionID); } #endif void CSysSessionHost::KickPlayer( KeyValues *pCommand ) { XUID xuid = pCommand->GetUint64( "xuid", 0ull ); // Locate the machine being kicked KeyValues *pMachine = NULL; SessionMembersFindPlayer( m_pSettings, xuid, &pMachine ); if ( !pMachine ) return; // Use machine primary xuid xuid = pMachine->GetUint64( "id" ); if ( xuid == GetHostXuid() ) { DevWarning( "CSysSessionHost::Kick unsupported for host xuid!\n" ); return; } // Notify everybody that a machine is being kicked KeyValues *notify = new KeyValues( "SysSession::OnPlayerKicked" ); KeyValues::AutoDelete autodelete_notify( notify ); notify->SetUint64( "xuid", xuid ); SendMessage( notify ); // Remove player information from our records FindAndRemovePlayerFromMembers( xuid ); // Remember the player in our kicked players map m_mapKickedPlayers.InsertOrReplace( xuid, Plat_FloatTime() ); #if !defined( _X360 ) && !defined( NO_STEAM ) // Forcefully kick the player from the lobby // steamapicontext->SteamMatchmaking()->??? // On X360 we just forcefully shutdown that client's // network channel and all peers do the same, so the kicked // client is indeed kicked no matter how smartly he tries to stay. #endif } void CSysSessionHost::OnUpdateSessionSettings( KeyValues *kv ) { KeyValues *kvPropertiesUpdated = kv->FindKey( "update", false ); if ( !kvPropertiesUpdated ) kvPropertiesUpdated = kv->FindKey( "delete", false ); UpdateSessionProperties( kvPropertiesUpdated ); // Now send the message to everybody about the session update KeyValues *notify = new KeyValues( "SysSession::OnUpdate" ); KeyValues::AutoDelete autodelete_notify( notify ); if ( KeyValues *kvUpdate = kv->FindKey( "update" ) ) notify->AddSubKey( kvUpdate->MakeCopy() ); if ( KeyValues *kvDelete = kv->FindKey( "delete" ) ) notify->AddSubKey( kvDelete->MakeCopy() ); SendMessage( notify ); } void CSysSessionHost::OnPlayerUpdated( KeyValues *pPlayer ) { // Notify the framework about player updated KeyValues *kvEvent = new KeyValues( "OnPlayerUpdated" ); kvEvent->SetUint64( "xuid", pPlayer->GetUint64( "xuid" ) ); kvEvent->SetPtr( "player", pPlayer ); OnSessionEvent( kvEvent ); // Now send the message KeyValues *notify = new KeyValues( "SysSession::OnPlayerUpdated" ); KeyValues::AutoDelete autodelete( notify ); notify->MergeFrom( pPlayer, KeyValues::MERGE_KV_UPDATE ); SendMessage( notify ); } void CSysSessionHost::OnMachineUpdated( KeyValues *pMachine ) { // Send the message to peers KeyValues *notify = new KeyValues( "SysSession::OnMachineUpdated" ); KeyValues::AutoDelete autodelete( notify ); notify->MergeFrom( pMachine, KeyValues::MERGE_KV_UPDATE ); SendMessage( notify ); } void CSysSessionHost::UpdateStateInit() { #ifdef _X360 MMX360_LobbyCreate( m_pSettings, &m_pAsyncOperation ); #elif !defined( NO_STEAM ) ELobbyType eType = k_ELobbyTypeFriendsOnly; int numSlots = m_pSettings->GetInt( "members/numSlots", 1 ); SteamAPICall_t hCall = steamapicontext->SteamMatchmaking()->CreateLobby( eType, numSlots ); m_CallbackOnLobbyCreated.Set( hCall, this, &CSysSessionHost::Steam_OnLobbyCreated ); #endif m_eState = STATE_CREATING; } #ifdef _X360 void CSysSessionHost::OnAsyncOperationFinished() { if ( m_eState == STATE_CREATING ) { if ( m_pAsyncOperation->GetState() != AOS_SUCCEEDED ) { Warning( "CSysSessionHost: CreateSession failed. Error %d\n", m_pAsyncOperation->GetResult() ); ReleaseAsyncOperation(); m_eState = STATE_FAIL; KeyValues *kv = new KeyValues( "mmF->SysSessionUpdate" ); kv->SetPtr( "syssession", this ); kv->SetString( "error", "create" ); OnSessionEvent( kv ); return; } // // We have successfully created the lobby, // retrieve all information from the async creation. // m_lobby = m_pAsyncOperation->GetLobby(); ReleaseAsyncOperation(); // Expose the NONCE for all future clients of the session m_pSettings->SetUint64( "system/nonce", m_lobby.m_uiNonce ); // Setup our xnaddr char chXnaddr[ XNADDR_STRING_LENGTH ] = {0}; MMX360_XnaddrToString( m_lobby.m_xiInfo.hostAddress, chXnaddr ); m_pSettings->SetString( "members/machine0/xnaddr", chXnaddr ); InitSessionProperties(); // Create the network mgr m_pNetworkMgr = new CX360NetworkMgr( this, GetX360NetSocket() ); // Setup local Xbox 360 voice Voice_ProcessTalkers( NULL, true ); m_eState = STATE_IDLE; KeyValues *kv = new KeyValues( "mmF->SysSessionUpdate" ); kv->SetPtr( "syssession", this ); OnSessionEvent( kv ); return; } else if ( m_eState == STATE_DELETE ) { ReleaseAsyncOperation(); SysSession360_RegisterPending( this ); } else { ReleaseAsyncOperation(); } } void CSysSessionHost::OnX360NetDisconnected( XUID xuidRemote ) { if ( m_eState == STATE_DELETE || m_eState == STATE_ALLOWING_MIGRATE ) return; CSysSessionBase::OnX360NetDisconnected( xuidRemote ); } bool CSysSessionHost::OnX360NetConnectionlessPacket( netpacket_t *pkt, KeyValues *msg ) { if ( !Q_stricmp( msg->GetName(), "SysSession::TeamReservation" ) ) { XUID key = msg->GetUint64( "teamResKey" ); int teamSize = msg->GetInt( "numPlayers" ); DevMsg( "Received reservation msg: res key = %llx, numPlayers = %d\n", key, teamSize ); Process_TeamReservation( key, teamSize ); return true; } if ( m_eState == STATE_IDLE && !Q_stricmp( msg->GetName(), "SysSession::RequestJoinData" ) ) { // Check sessionid the client is trying to connect to uint64 uiSessionIdRequest = msg->GetUint64( "sessionid" ); if ( uiSessionIdRequest != ( const uint64 & ) m_lobby.m_xiInfo.sessionID ) return false; // This is a legit connectionless packet requesting to join the session XUID machineid = msg->GetUint64( "id" ); XNKID xnkidSession = m_lobby.m_xiInfo.sessionID; if ( !m_pNetworkMgr->ConnectionPeerOpenPassive( machineid, pkt, &xnkidSession ) ) { DevWarning( "CSysSessionHost discarding SysSession::RequestJoinData due to passive connection failure!\n" ); return false; } if ( !Process_RequestJoinData( machineid, msg->FindKey( "settings" ) ) ) m_pNetworkMgr->ConnectionPeerClose( machineid ); return true; } // Unknown packet, permanently block sender return false; } void CSysSessionHost::DestroyAfterMigrationFinished() { // Picking up slack from CSysSessionBase::Destroy scenario if ( m_pNetworkMgr ) { m_pNetworkMgr->Destroy(); m_pNetworkMgr = NULL; } m_eState = STATE_DELETE; MMX360_LobbyDelete( m_lobby, &m_pAsyncOperation ); } #endif void CSysSessionHost::ReceiveMessage( KeyValues *msg, bool bValidatedLobbyMember, XUID xuidSrc ) { char const *szMsg = msg->GetName(); // Parse the message depending on our state switch ( m_eState ) { case STATE_IDLE: if ( !Q_stricmp( "SysSession::RequestJoinData", szMsg ) ) { XUID machineid = msg->GetUint64( "id" ); KeyValues *pSettings = msg->FindKey( "settings" ); if ( machineid != xuidSrc ) return; Process_RequestJoinData( xuidSrc, pSettings ); } else if ( !bValidatedLobbyMember ) { return; } else if ( !Q_stricmp( "SysSession::VoiceStatus", szMsg ) ) { Process_VoiceStatus( msg, xuidSrc ); } #ifdef _X360 // (CS:GO 2017) -- these are old X360 flows that we no longer care to support else if ( !Q_stricmp( "SysSession::VoiceMutelist", szMsg ) ) { Process_VoiceMutelist( msg ); } else if ( !Q_stricmp( "SysSession::TeamReservation", szMsg ) ) { XUID key = msg->GetUint64( "teamResKey" ); int teamSize = msg->GetInt( "numPlayers" ); Process_TeamReservation( key, teamSize ); } #endif else { CSysSessionBase::ReceiveMessage( msg, bValidatedLobbyMember, xuidSrc ); } return; #ifdef _X360 case STATE_ALLOWING_MIGRATE: if ( !Q_stricmp( szMsg, "SysSession::HostMigrated" ) ) { // another peer picked up the session, bail out DestroyAfterMigrationFinished(); } return; #endif } } void CSysSessionHost::Migrate( KeyValues *pCommand ) { if ( Q_stricmp( pCommand->GetString( "migrate" ), "host>client" ) ) { Assert( 0 ); return; } #ifndef NO_STEAM Verify( steamapicontext->SteamMatchmaking()->SetLobbyOwner( m_lobby.m_uiLobbyID, pCommand->GetUint64( "xuid" ) ) ); // Prepare the update notification KeyValues *kv = new KeyValues( "mmF->SysSessionUpdate" ); kv->SetPtr( "syssession", this ); m_eState = STATE_MIGRATE; kv->SetString( "action", "client" ); // Inside this event broadcast our session will be deleted SendEventsNotification( kv ); #endif } void CSysSessionHost::OnPlayerLeave( XUID xuid ) { // We detected that the player dropped out of the lobby, // disconnect the player from the session #if !defined( NO_STEAM ) if ( !V_stricmp( m_pSettings->GetString( "system/netflag" ), "noleave" ) ) { DevMsg( "CSysSessionHost::OnPlayerLeave(%llx) ignored in noleave mode.\n", xuid ); return; } #endif if ( FindAndRemovePlayerFromMembers( xuid ) ) { // Now send the message to everybody about the session update KeyValues *reply = new KeyValues( "SysSession::OnPlayerRemoved" ); KeyValues::AutoDelete autodelete_reply( reply ); reply->SetUint64( "xuid", xuid ); SendMessage( reply ); // Fire the notification about a machine disconnected from the session if ( KeyValues *kvEvent = new KeyValues( "OnPlayerMachinesDisconnected" ) ) { kvEvent->SetInt( "numMachines", 1 ); kvEvent->SetUint64( "id", xuid ); OnSessionEvent( kvEvent ); } #ifdef _X360 // Send this down to the engine as well if ( m_lobby.m_bXSessionStarted ) { // Send a message to the server that we need to remove this player KeyValues *pDisconnectRequest = new KeyValues( "OnPlayerRemovedFromSession" ); pDisconnectRequest->SetUint64( "xuid", xuid ); g_pMatchExtensions->GetIVEngineClient()->ServerCmdKeyValues( pDisconnectRequest ); } #endif // _X360 } } #ifdef _X360 #elif !defined( NO_STEAM ) void CSysSessionHost::Steam_OnLobbyCreated( LobbyCreated_t *pLobbyCreate, bool bError ) { if ( bError || pLobbyCreate->m_eResult != k_EResultOK ) { Warning( "CSysSessionHost: CreateSession failed. Error %d\n", bError ? -1 : pLobbyCreate->m_eResult ); m_eState = STATE_FAIL; KeyValues *kv = new KeyValues( "mmF->SysSessionUpdate" ); kv->SetPtr( "syssession", this ); kv->SetString( "error", "create" ); OnSessionEvent( kv ); return; } else { m_lobby.m_uiLobbyID = pLobbyCreate->m_ulSteamIDLobby; DevMsg( "Created lobby id: 0x%llx\n", m_lobby.m_uiLobbyID ); // will get a subsequent callback at at Steam_OnLobbyEntered to indicate that we are a lobby member m_CallbackOnLobbyEntered.Register( this, &CSysSessionHost::Steam_OnLobbyEntered ); } } void CSysSessionHost::Steam_OnLobbyEntered( LobbyEnter_t *pLobbyEnter ) { // Filter out notifications not from our lobby if ( pLobbyEnter->m_ulSteamIDLobby != m_lobby.m_uiLobbyID ) return; m_CallbackOnLobbyEntered.Unregister(); if ( pLobbyEnter->m_EChatRoomEnterResponse != k_EChatRoomEnterResponseSuccess ) { Warning( "Matchmaking: lobby response %d!\n", pLobbyEnter->m_EChatRoomEnterResponse ); m_eState = STATE_FAIL; KeyValues *kv = new KeyValues( "mmF->SysSessionUpdate" ); kv->SetPtr( "syssession", this ); kv->SetString( "error", LobbyEnterErrorAsString( pLobbyEnter ) ); OnSessionEvent( kv ); return; } else { // Set all the properties of the new session InitSessionProperties(); // Install callback for messages m_CallbackOnLobbyChatMsg.Register( this, &CSysSessionBase::Steam_OnLobbyChatMsg ); m_CallbackOnLobbyChatUpdate.Register( this, &CSysSessionBase::Steam_OnLobbyChatUpdate ); // Setup voice Voice_ProcessTalkers( NULL, true ); m_eState = STATE_IDLE; KeyValues *kv = new KeyValues( "mmF->SysSessionUpdate" ); kv->SetPtr( "syssession", this ); OnSessionEvent( kv ); return; } } bool CSysSessionHost::GetLobbyType( KeyValues *kv, ELobbyType *peType, bool *pbJoinable ) { if ( !peType || !pbJoinable ) return false; char const *szLock = kv->GetString( "system/lock", NULL ); char const *szAccess = kv->GetString( "system/access", NULL ); if ( !szAccess && !szLock ) return false; if ( !szLock ) szLock = m_pSettings->GetString( "system/lock", "" ); if ( !szAccess ) szAccess = m_pSettings->GetString( "system/access", "public" ); *pbJoinable = ( szLock[0] == 0 ); // non joinable if locked if ( !Q_stricmp( "public", szAccess ) ) return *peType = k_ELobbyTypePublic, true; else if ( !Q_stricmp( "friends", szAccess ) ) return *peType = k_ELobbyTypeFriendsOnly, true; else if ( !Q_stricmp( "private", szAccess ) ) return *peType = k_ELobbyTypePrivate, true; // private else return false; } #endif void CSysSessionHost::UpdateMembersInfo() { #ifdef _X360 #elif !defined( NO_STEAM ) if ( m_lobby.m_uiLobbyID ) { steamapicontext->SteamMatchmaking()->SetLobbyMemberLimit( m_lobby.m_uiLobbyID, m_pSettings->GetInt( "members/numSlots", 1 ) + m_numDormantMembersDetected ); } // Set the initial members information LobbySetDataFromKeyValues( "members", m_pSettings->FindKey( "members" ), false ); #endif } void CSysSessionHost::InitSessionProperties() { UpdateMembersInfo(); UpdateSessionProperties( m_pSettings ); } void CSysSessionHost::UpdateSessionProperties( KeyValues *kv ) { if ( !kv ) return; CSysSessionBase::UpdateSessionProperties( kv, true ); // // Set joinability and public/private slots distribution // #ifdef _X360 OnX360AllSessionMembersJoinLeave( kv ); #elif !defined( NO_STEAM ) ELobbyType eType = k_ELobbyTypePublic; bool bJoinable = true; if ( GetLobbyType( kv, &eType, &bJoinable ) && m_lobby.m_uiLobbyID ) { steamapicontext->SteamMatchmaking()->SetLobbyType( m_lobby.m_uiLobbyID, eType ); steamapicontext->SteamMatchmaking()->SetLobbyJoinable( m_lobby.m_uiLobbyID, bJoinable ); } #endif // // Update QOS reply data of the session // #ifdef _X360 if ( IsX360() && !m_hLobbyMigrateCall ) { CUtlBuffer bufQosData; bufQosData.ActivateByteSwapping( !CByteswap::IsMachineBigEndian() ); g_pMatchFramework->GetMatchNetworkMsgController()->PackageGameDetailsForQOS( m_pSettings, bufQosData ); g_pMatchExtensions->GetIXOnline()->XNetQosListen( &m_lobby.m_xiInfo.sessionID, ( const BYTE * ) bufQosData.Base(), bufQosData.TellMaxPut(), 0, XNET_QOS_LISTEN_SET_DATA ); } #endif } bool CSysSessionHost::Process_RequestJoinData( XUID xuidClient, KeyValues *pSettings ) { // Check if the request is a duplicate because of migration and client had // to re-request join authorization from the new host, but the old host // already included the new client into the session... if ( SessionMembersFindPlayer( m_pSettings, xuidClient ) ) return true; // We should merge the client's information into our settings information int numUsersConnecting = pSettings->GetInt( "members/numPlayers", 0 ); int numMachinesConnecting = pSettings->GetInt( "members/numMachines", 0 ); int numSlotsTotal = m_pSettings->GetInt( "members/numSlots", 0 ); int numUsersCurrent = m_pSettings->GetInt( "members/numPlayers", 0 ); int numMachinesCurrent = m_pSettings->GetInt( "members/numMachines", 0 ); // CS:GO 2017 - Validate a bunch of parameters to match the authenticated XUID: if ( numMachinesConnecting != 1 ) return false; if ( numUsersConnecting != 1 ) return false; if ( pSettings->GetInt( "members/machine0/numPlayers" ) != 1 ) return false; if ( pSettings->GetUint64( "members/machine0/id" ) != xuidClient ) return false; if ( pSettings->GetUint64( "members/machine0/player0/xuid" ) != xuidClient ) return false; // Check if there are more players on the server than in session INetSupport::ClientInfo_t nsci = {0}; g_pMatchExtensions->GetINetSupport()->GetClientInfo( &nsci ); // If we represent a team lobby, then only consider players on our team if ( nsci.m_numHumanPlayers && !Q_stricmp( m_pSettings->GetString( "system/netflag", "" ), "teamlobby" ) ) { // TODO: int nMatchTeam = m_pSettings->GetInt( "server/team", 1 ); // for now just always go by Steam lobby slots nsci.m_numHumanPlayers = 0; } // Prepare the reply package KeyValues *reply = new KeyValues( "SysSession::ReplyJoinData" ); KeyValues::AutoDelete autodelete( reply ); reply->SetUint64( "id", xuidClient ); // If we have a team reservation in place then reject clients that don't // provide the reservation key if ( m_teamResKey ) { uint64 clientKey = pSettings->GetUint64( "teamResKey", 0 ); if ( m_teamResKey != clientKey) { reply->SetString( "error", "TeamResFail" ); SendMessage( reply ); return false; } else { // One more PWF team client has joined the session m_numRemainingTeamPlayers--; if ( m_numRemainingTeamPlayers == 0 ) { UnreserveTeamSession(); } } } // If any of the data is malformed, early out if ( !numUsersConnecting || !numMachinesConnecting || !numSlotsTotal || !numUsersCurrent || !numMachinesCurrent ) { reply->SetString( "error", "n/a" ); SendMessage( reply ); return false; } // If the session is using a private key (tournament lobby), then keys must match char const *szRequestLockField = pSettings->GetString( "system/lock", "" ); if ( mm_session_sys_pkey.GetString()[0] ) { if ( V_strcmp( szRequestLockField, mm_session_sys_pkey.GetString() ) ) { DevMsg( "LOBBY: blocking join request from %llu with invalid key: %s\n", xuidClient, szRequestLockField ); reply->SetString( "error", "n/a" ); SendMessage( reply ); return false; } } // If the session is locked, prevent join char const *szSessionSystemSettingsLock = m_pSettings->GetString( "system/lock", "" ); if ( szSessionSystemSettingsLock[0] ) { char const *szReplyError = "lock"; if ( !V_stricmp( "mmqueue", szSessionSystemSettingsLock ) ) { szReplyError = "LockMmQueue"; } reply->SetString( "error", szReplyError ); SendMessage( reply ); return false; } // If the request is a soft-join then check privacy and mmqueue if ( KeyValues *kvSoftChecks = pSettings->FindKey( "joincheck" ) ) { FOR_EACH_SUBKEY( kvSoftChecks, kvSubCheck ) { char const *szSoftValue = kvSubCheck->GetName(); char const *szSoftKey = kvSubCheck->GetString(); bool bSoftCheckOK = ( !V_strcmp( "#empty#", szSoftValue ) && !*m_pSettings->GetString( szSoftKey ) ) || ( ( *szSoftValue == '[' ) && V_strstr( szSoftValue, CFmtStr( "[%s]", m_pSettings->GetString( szSoftKey ) ) ) ) || !V_stricmp( m_pSettings->GetString( szSoftKey ), szSoftValue ); if ( !bSoftCheckOK ) { DevMsg( "LOBBY: blocking join request from %llu with check: %s = '%s' (actual: '%s')\n", xuidClient, szSoftKey, szSoftValue, m_pSettings->GetString( szSoftKey ) ); char const *szReplyError = "lock"; reply->SetString( "error", szReplyError ); SendMessage( reply ); return false; } } } // If the user has previously been kicked then do not let them re-join int idxKicked = m_mapKickedPlayers.Find( xuidClient ); if ( idxKicked != m_mapKickedPlayers.InvalidIndex() ) { if ( Plat_FloatTime() - m_mapKickedPlayers.Element( idxKicked ) < mm_session_sys_kick_ban_duration.GetFloat() ) { DevMsg( "LOBBY: blocking join request from %llu, still %.1f sec kick ban duration remaining, mm_session_sys_kick_ban_duration = %.1f.\n", xuidClient, m_mapKickedPlayers.Element( idxKicked ) + 1 + mm_session_sys_kick_ban_duration.GetFloat() - Plat_FloatTime(), mm_session_sys_kick_ban_duration.GetFloat() ); reply->SetString( "error", "kicked" ); SendMessage( reply ); return false; } } // If the session is full, early out if ( MAX( numUsersCurrent, nsci.m_numHumanPlayers ) + numUsersConnecting > numSlotsTotal ) { reply->SetString( "error", "full" ); SendMessage( reply ); return false; } // Get the members containers KeyValues *pMembers = m_pSettings->FindKey( "members" ); Assert( pMembers ); if ( !pMembers ) return false; KeyValues *pMembersConnecting = pSettings->FindKey( "members" ); Assert( pMembersConnecting ); if ( !pMembersConnecting ) return false; // Validate that the connecting machines have the required TU and DLC installed for ( int k = 0; k < numMachinesConnecting; ++ k ) { KeyValues *kvConnecting = pMembersConnecting->FindKey( CFmtStr( "machine%d", k ) ); Assert( kvConnecting ); if ( !kvConnecting ) continue; char const *szTuString = kvConnecting->GetString( "tuver" ); uint64 uiDlcMask = kvConnecting->GetUint64( "dlcmask" ); char const *szTuRequired = pMembers->GetString( "machine0/tuver" ); uint64 uiDlcRequired = m_pSettings->GetUint64( "game/dlcrequired" ); if ( Q_strcmp( szTuString, szTuRequired ) ) { reply->SetString( "error", "turequired" ); reply->SetString( "turequired", szTuRequired ); SendMessage( reply ); return false; } if ( ( uiDlcMask & uiDlcRequired ) != uiDlcRequired ) { reply->SetString( "error", "dlcrequired" ); reply->SetUint64( "dlcrequired", uiDlcRequired &~uiDlcMask ); reply->SetUint64( "dlcmask", uiDlcRequired ); SendMessage( reply ); return false; } } // Merge the information about the connecting members for ( int k = 0; k < numMachinesConnecting; ++ k ) { KeyValues *kvConnecting = pMembersConnecting->FindKey( CFmtStr( "machine%d", k ) ); Assert( kvConnecting ); if ( !kvConnecting ) continue; KeyValues *kvNewMachine = kvConnecting->MakeCopy(); kvNewMachine->SetName( CFmtStr( "machine%d", k + numMachinesCurrent ) ); pMembers->AddSubKey( kvNewMachine ); // Register talkers from that machine Voice_ProcessTalkers( kvNewMachine, true ); } // Update the current count of machines and members pMembers->SetInt( "numMachines", numMachinesCurrent + numMachinesConnecting ); pMembers->SetInt( "numPlayers", numUsersCurrent + numUsersConnecting ); // Join flags pMembers->SetUint64( "joinflags", pMembersConnecting->GetUint64( "joinflags" ) ); #ifdef _X360 // On X360 we need to update lobby members server-side count MMX360_LobbyJoinMembers( pSettings, m_lobby ); #endif // Fire the notifications about a bunch of machines connected to the session if ( KeyValues *kvEvent = new KeyValues( "OnPlayerMachinesConnected" ) ) { kvEvent->SetInt( "numMachines", numMachinesConnecting ); kvEvent->SetUint64( "id", xuidClient ); OnSessionEvent( kvEvent ); } // Fire the notifications about the new players for ( int k = 0; k < numMachinesConnecting; ++ k ) { KeyValues *kvConnecting = pMembersConnecting->FindKey( CFmtStr( "machine%d", k ) ); if ( !kvConnecting ) continue; int numPlayers = kvConnecting->GetInt( "numPlayers", 0 ); for ( int j = 0; j < numPlayers; ++ j ) { KeyValues *pPlayer = kvConnecting->FindKey( CFmtStr( "player%d", j ) ); XUID xuid = pPlayer->GetUint64( "xuid", 0ull ); if ( !xuid ) continue; if ( KeyValues *kvEvent = new KeyValues( "OnPlayerUpdated" ) ) { kvEvent->SetUint64( "xuid", xuid ); kvEvent->SetString( "state", "joined" ); kvEvent->SetPtr( "player", pPlayer ); OnSessionEvent( kvEvent ); } } } // Send the encryption key to the connected client too reply->SetUint64( "crypt", m_ullCrypt ); // After everything settled with the new players on this machine, // notify everybody of the new state of the session reply->AddSubKey( m_pSettings->MakeCopy() ); SendMessage( reply ); Voice_UpdateMutelist(); #ifdef _X360 #elif !defined( NO_STEAM ) // Update members information LobbySetDataFromKeyValues( "members", m_pSettings->FindKey( "members" ), false ); #endif return true; } void CSysSessionHost::Process_TeamReservation( XUID key, int teamSize ) { KeyValues *pMembers; int numPlayers; int numSlots; KeyValues *reply = new KeyValues( "SysSession::TeamReservationResult" ); KeyValues::AutoDelete autodelete( reply ); // Check we are not already reserved if ( m_teamResKey != 0 ) { reply->SetString( "result", "alreadyReserved" ); goto xit; } // Check if we have enough free slots pMembers = m_pSettings->FindKey( "members" ); numPlayers = pMembers->GetInt( "numPlayers" ); numSlots = pMembers->GetInt( "numSlots" ); if ( numSlots < numPlayers + teamSize) { reply->SetString( "result", "notEnoughSlots" ); goto xit; } reply->SetString( "result", "success" ); ReserveTeamSession( key, teamSize ); xit: SendMessage( reply ); } void CSysSessionHost::ReserveTeamSession( XUID key, int numPlayers ) { #if !defined (NO_STEAM) DevMsg( "CSysSessionHost::ReserveTeamSession\n"); m_teamResKey = key; m_numRemainingTeamPlayers = numPlayers; m_flTeamResStartTime = Plat_FloatTime(); // Set lobby data KeyValues *settings = new KeyValues( "TeamReservation" ); KeyValues::AutoDelete autodelete( settings ); settings->SetInt( "TeamRes", 1 ); LobbySetDataFromKeyValues( "TeamReservation", settings ); #endif } void CSysSessionHost::UnreserveTeamSession() { #if !defined (NO_STEAM) DevMsg( "CSysSessionHost::UnreserveTeamSession\n"); m_teamResKey = 0; m_numRemainingTeamPlayers = 0; // Set lobby data KeyValues *settings = new KeyValues( "TeamReservation" ); KeyValues::AutoDelete autodelete( settings ); settings->SetInt( "TeamRes", 0 ); LobbySetDataFromKeyValues( "TeamReservation", settings ); #endif } void CSysSessionHost::Process_VoiceStatus( KeyValues *msg, XUID xuidSrc ) { XUID xuid = msg->GetUint64( "xuid" ); Assert( xuid == xuidSrc ); if ( xuid != xuidSrc ) return; KeyValues *pPlayer = SessionMembersFindPlayer( m_pSettings, xuid ); if ( !pPlayer ) return; char const *szValue = msg->GetString( "voice" ); pPlayer->SetString( "voice", szValue ); OnPlayerUpdated( pPlayer ); } void CSysSessionHost::Process_VoiceMutelist( KeyValues *msg ) { XUID xuid = msg->GetUint64( "xuid" ); KeyValues *pMachine = NULL; SessionMembersFindPlayer( m_pSettings, xuid, &pMachine ); if ( !pMachine ) return; // Remove old mutelist if ( KeyValues *pMutelist = pMachine->FindKey( "Mutelist" ) ) { pMachine->RemoveSubKey( pMutelist ); pMutelist->deleteThis(); } // Update new mutelist if ( KeyValues *pMutelist = msg->FindKey( "Mutelist" ) ) { pMachine->FindKey( "Mutelist", true )->MergeFrom( pMutelist, KeyValues::MERGE_KV_UPDATE ); } OnMachineUpdated( pMachine ); } // // Client session implementation // CSysSessionClient::CSysSessionClient( KeyValues *pSettings ) : CSysSessionBase( pSettings ), m_eState( STATE_INIT ), m_flInitializeTimestamp( 0.0f ) { #ifdef _X360 memset( &m_xnaddrLocal, 0, sizeof( m_xnaddrLocal ) ); #endif } CSysSessionClient::CSysSessionClient( CSysSessionHost *pHost, KeyValues *pSettings ) : CSysSessionBase( pSettings ), #ifdef _X360 #elif !defined( NO_STEAM ) #endif m_eState( STATE_IDLE ), m_flInitializeTimestamp( 0.0f ) { m_lobby = pHost->m_lobby; m_Voice_flLastHeadsetStatusCheck = pHost->m_Voice_flLastHeadsetStatusCheck; #ifdef _X360 m_pNetworkMgr = pHost->m_pNetworkMgr; if ( m_pNetworkMgr ) m_pNetworkMgr->SetListener( this ); m_pAsyncOperation = pHost->m_pAsyncOperation; Assert( !m_pAsyncOperation ); // If client was in migrate state, then disassociate the migrate call listener if ( pHost->m_hLobbyMigrateCall ) { MMX360_LobbyMigrateSetListener( pHost->m_hLobbyMigrateCall, NULL ); pHost->m_hLobbyMigrateCall = NULL; } #elif !defined( NO_STEAM ) // Install callback for messages m_CallbackOnLobbyChatMsg.Register( this, &CSysSessionBase::Steam_OnLobbyChatMsg ); m_CallbackOnLobbyChatUpdate.Register( this, &CSysSessionBase::Steam_OnLobbyChatUpdate ); #endif } CSysSessionClient::~CSysSessionClient() { ; } bool CSysSessionClient::Update() { if ( !CSysSessionBase::Update() ) return false; switch( m_eState ) { case STATE_INIT: if ( !m_flInitializeTimestamp ) { m_flInitializeTimestamp = Plat_FloatTime(); #if !defined( NO_STEAM ) SetupSteamRankingConfiguration(); #endif } if ( #if !defined( NO_STEAM ) ( IsSteamRankingConfigured() || ( Plat_FloatTime() >= m_flInitializeTimestamp + mm_session_sys_ranking_timeout.GetFloat() ) ) && #endif SysSession_AllowCreate() ) UpdateStateInit(); break; case STATE_CREATING: break; case STATE_REQUESTING_JOIN_DATA: // Wait for 5 sec until data arrives if ( Plat_FloatTime() > m_RequestJoinDataInfo.m_fTimeSent + mm_session_sys_connect_timeout.GetFloat() ) { m_eState = STATE_FAIL; #ifdef _X360 if ( m_pNetworkMgr ) { m_pNetworkMgr->Destroy(); m_pNetworkMgr = NULL; } #endif Warning( "CSysSessionClient: Unable to get session information from host\n" ); KeyValues *kv = new KeyValues( "mmF->SysSessionUpdate" ); kv->SetPtr( "syssession", this ); kv->SetString( "error", "n/a" ); OnSessionEvent( kv ); } break; #if !defined( NO_STEAM ) case STATE_JOIN_LOBBY: m_CallbackOnLobbyEntered.Register( this, &CSysSessionClient::Steam_OnLobbyEntered ); steamapicontext->SteamMatchmaking()->JoinLobby( m_lobby.m_uiLobbyID ); m_eState = STATE_CREATING; break; #endif case STATE_IDLE: break; } return true; } void CSysSessionClient::Destroy() { // If we are migrating, then don't let the // base class handle it because it will // post Quit messages and leave the lobby... if ( m_eState == STATE_MIGRATE ) { delete this; return; } #ifdef _X360 if ( m_eState == STATE_DELETE ) { delete this; return; } else { m_eState = STATE_DELETE; } #endif // Chain to base which will "delete this" CSysSessionBase::Destroy(); } void CSysSessionClient::DebugPrint() { DevMsg( "CSysSessionClient [ state=%d ]\n", m_eState ); if ( m_eState == STATE_REQUESTING_JOIN_DATA ) { DevMsg( "Requested join data %.3f sec ago from xuid = %llx\n", Plat_FloatTime() - m_RequestJoinDataInfo.m_fTimeSent, m_RequestJoinDataInfo.m_xuidLeader ); } CSysSessionBase::DebugPrint(); } XUID CSysSessionClient::GetHostXuid( XUID xuidValidResult ) { #ifdef _X360 // Host is considered to be the first machine in our settings // to which we have a network connection int numMachines = m_pSettings->GetInt( "members/numMachines" ); for ( int k = 0; k < numMachines; ++ k ) { KeyValues *pMachine = m_pSettings->FindKey( CFmtStr( "members/machine%d", k ) ); if ( !pMachine ) continue; XUID idMachine = pMachine->GetUint64( "id" ); if ( idMachine == m_xuidMachineId ) return m_xuidMachineId; // Reached our own machine, we are the top machine! if ( xuidValidResult && idMachine == xuidValidResult ) return idMachine; // Maybe we don't have connection to the remote machine, but the caller thinks it could be a valid host if ( m_pNetworkMgr->ConnectionPeerGetAddress( idMachine ) ) return idMachine; } // There's nobody in the session, maybe we are our own host? return m_xuidMachineId; #elif !defined( NO_STEAM ) return m_lobby.m_uiLobbyID ? steamapicontext->SteamMatchmaking() ->GetLobbyOwner( m_lobby.m_uiLobbyID ).ConvertToUint64() : m_xuidMachineId; #endif return 0ull; } #ifdef _X360 char const * CSysSessionClient::GetHostNetworkAddress( XSESSION_INFO &xsi ) { xsi = m_lobby.m_xiInfo; char const *szNetworkAddress = m_pNetworkMgr->ConnectionPeerGetAddress( 0ull ); if ( !szNetworkAddress ) { // Maybe migration hasn't finished yet... XUID xuidHost = GetHostXuid(); DevWarning( "CSysSessionClient::GetHostNetworkAddress has no default host network address, retrying for %llx!\n", xuidHost ); szNetworkAddress = m_pNetworkMgr->ConnectionPeerGetAddress( xuidHost ); if ( !szNetworkAddress ) { DevWarning( "CSysSessionClient::GetHostNetworkAddress has no host network address for %llx!\n", xuidHost ); Assert( 0 ); // this is fatal for our session and abnormal, but the UI should just pop a message that we failed to connect } } return szNetworkAddress; } #endif void CSysSessionClient::UpdateStateInit() { #ifdef _X360 MMX360_LobbyConnect( m_pSettings, &m_pAsyncOperation ); m_eState = STATE_CREATING; #elif !defined( NO_STEAM ) m_lobby.m_uiLobbyID = m_pSettings->GetUint64( "options/sessionid", 0ull ); m_eState = STATE_JOIN_LOBBY; #endif } void CSysSessionClient::InitSessionProperties( KeyValues *pSettings ) { #ifdef _X360 // Configure our session slots and permissions to match the host CX360LobbyFlags_t fl = MMX360_DescribeLobbyFlags( pSettings, false ); g_pMatchExtensions->GetIXOnline()->XSessionModify( m_lobby.m_hHandle, fl.m_dwFlags, fl.m_numPublicSlots, fl.m_numPrivateSlots, MMX360_NewOverlappedDormant() ); // Join all the members MMX360_LobbyJoinMembers( pSettings, m_lobby ); #endif UpdateSessionProperties( pSettings ); } void CSysSessionClient::UpdateSessionProperties( KeyValues *kv ) { if ( !kv ) return; CSysSessionBase::UpdateSessionProperties( kv, false ); // // Set joinability and public/private slots distribution // #ifdef _X360 if ( !kv->FindKey( "members", false ) ) { // If this is not our initial update OnX360AllSessionMembersJoinLeave( kv ); } #endif } #ifdef _X360 void CSysSessionClient::OnAsyncOperationFinished() { if ( m_eState == STATE_CREATING ) { if ( m_pAsyncOperation->GetState() != AOS_SUCCEEDED ) { Warning( "CSysSessionClient: CreateSession failed. Error %d\n", m_pAsyncOperation->GetResult() ); ReleaseAsyncOperation(); m_eState = STATE_FAIL; KeyValues *kv = new KeyValues( "mmF->SysSessionUpdate" ); kv->SetPtr( "syssession", this ); kv->SetString( "error", "createclient" ); OnSessionEvent( kv ); return; } // // We have successfully created the client-side session, // retrieve all information from the async creation. // m_lobby = m_pAsyncOperation->GetLobby(); ReleaseAsyncOperation(); // Initialize network manager m_pNetworkMgr = new CX360NetworkMgr( this, GetX360NetSocket() ); if ( !m_pNetworkMgr->ConnectionPeerOpenActive( 0ull, m_lobby.m_xiInfo ) ) { m_eState = STATE_FAIL; m_pNetworkMgr->Destroy(); m_pNetworkMgr = NULL; KeyValues *kv = new KeyValues( "mmF->SysSessionUpdate" ); kv->SetPtr( "syssession", this ); kv->SetString( "error", "n/a" ); OnSessionEvent( kv ); return; } // Request permission to join m_eState = STATE_REQUESTING_JOIN_DATA; m_RequestJoinDataInfo.m_fTimeSent = Plat_FloatTime(); m_RequestJoinDataInfo.m_xuidLeader = 0ull; Send_RequestJoinData(); return; } else if ( m_eState == STATE_DELETE ) { ReleaseAsyncOperation(); SysSession360_RegisterPending( this ); } else { ReleaseAsyncOperation(); } } void CSysSessionClient::OnX360NetDisconnected( XUID xuidRemote ) { if ( m_eState == STATE_DELETE ) return; // Do not react to disconnections among our peer clients, // host is authoritative as far as which clients are in the session // As soon as another client will migrate to be host that client // will drop session members who haven't established the required // P2P interconnect channels if ( xuidRemote != GetHostXuid( xuidRemote ) ) // Indicate that the disconnected XUID could have been the host { DevMsg( "CSysSessionClient::OnX360NetDisconnected( %llx ) waiting for host update.\n", xuidRemote ); return; } CSysSessionBase::OnX360NetDisconnected( xuidRemote ); } bool CSysSessionClient::OnX360NetConnectionlessPacket( netpacket_t *pkt, KeyValues *msg ) { if ( m_eState == STATE_IDLE && !Q_stricmp( msg->GetName(), "SysSession::P2PConnect" ) ) { // Check nonce the client is trying to connect to uint64 uiNonce = msg->GetUint64( "nonce" ); if ( uiNonce != ( const uint64 & ) m_lobby.m_uiNonce ) return false; // This is a legit connectionless packet requesting a p2p connection XUID machineid = msg->GetUint64( "id" ); XNKID xnkidSession = m_lobby.m_xiInfo.sessionID; // Pick up the peer and add it to our spider web of connections if ( !m_pNetworkMgr->ConnectionPeerOpenPassive( machineid, pkt, &xnkidSession ) ) { DevWarning( "CSysSessionClient::P2PConnect failed to open passive connection with %llx!\n", machineid ); } return true; } // Unknown packet, permanently block sender return false; } void CSysSessionClient::XP2P_Interconnect() { // Peer-to-peer connection establish packet KeyValues *p2pMsg = new KeyValues( "SysSession::P2PConnect" ); KeyValues::AutoDelete autodelete_p2pMsg( p2pMsg ); p2pMsg->SetUint64( "nonce", m_lobby.m_uiNonce ); p2pMsg->SetUint64( "id", m_xuidMachineId ); // Open an active connection to all current peers if ( KeyValues *kvMembers = m_pSettings->FindKey( "members" ) ) { int numMachines = kvMembers->GetInt( "numMachines" ); for ( int k = 1; k < numMachines; ++ k ) // skip "0" because it's the host { KeyValues *kvMachine = kvMembers->FindKey( CFmtStr( "machine%d", k ) ); if ( !kvMachine ) continue; XSESSION_INFO xsi = m_lobby.m_xiInfo; MMX360_XnaddrFromString( xsi.hostAddress, kvMachine->GetString( "xnaddr" ) ); if ( !memcmp( &xsi.hostAddress, &m_xnaddrLocal, sizeof( m_xnaddrLocal ) ) ) continue; XUID idMachine = kvMachine->GetUint64( "id" ); if ( m_pNetworkMgr->ConnectionPeerOpenActive( idMachine, xsi ) ) { m_pNetworkMgr->ConnectionPeerSendConnectionless( idMachine, p2pMsg ); } else { DevWarning( "CSysSessionClient::XP2P_Interconnect failed to interconnect with %llx!\n", idMachine ); } } } } #endif void CSysSessionClient::ReceiveMessage( KeyValues *msg, bool bValidatedLobbyMember, XUID xuidSrc ) { char const *szMsg = msg->GetName(); // Parse the message depending on our state switch ( m_eState ) { case STATE_REQUESTING_JOIN_DATA: if ( !Q_stricmp( szMsg, "SysSession::ReplyJoinData" ) ) { if ( ( msg->GetUint64( "id" ) == m_xuidMachineId ) && ( xuidSrc == GetHostXuid() ) ) { Process_ReplyJoinData_Our( msg ); } } break; case STATE_IDLE: if ( !bValidatedLobbyMember ) { return; } else if ( !Q_stricmp( szMsg, "SysSession::ReplyJoinData" ) ) { if ( GetHostXuid() == xuidSrc ) Process_ReplyJoinData_Other( msg ); } else if ( !Q_stricmp( szMsg, "SysSession::OnPlayerRemoved" ) ) { if ( GetHostXuid() == xuidSrc ) FindAndRemovePlayerFromMembers( msg->GetUint64( "xuid" ) ); } else if ( !Q_stricmp( szMsg, "SysSession::OnPlayerKicked" ) ) { if ( GetHostXuid() == xuidSrc ) { XUID xuid = msg->GetUint64( "xuid" ); if ( xuid == m_xuidMachineId ) Process_Kicked( msg ); else FindAndRemovePlayerFromMembers( xuid ); } } else if ( !Q_stricmp( szMsg, "SysSession::OnPlayerUpdated" ) ) { if ( GetHostXuid() == xuidSrc ) Process_OnPlayerUpdated( msg ); } else if ( !Q_stricmp( szMsg, "SysSession::OnMachineUpdated" ) ) { if ( GetHostXuid() == xuidSrc ) Process_OnMachineUpdated( msg ); } #ifdef _X360 else if ( !Q_stricmp( szMsg, "SysSession::HostMigrated" ) ) { XSESSION_INFO xsi; char const *chSessionInfo = msg->GetString( "sessioninfo", "" ); MMX360_SessionInfoFromString( xsi, chSessionInfo ); // If there is an outstanding migrate call, then disable our listener on it if ( m_hLobbyMigrateCall && !m_MigrateCallState.m_bFinished ) MMX360_LobbyMigrateSetListener( m_hLobbyMigrateCall, NULL ); m_hLobbyMigrateCall = NULL; // Schedule a client migrate call m_hLobbyMigrateCall = MMX360_LobbyMigrateClient( m_lobby, xsi, &m_MigrateCallState ); if ( !m_hLobbyMigrateCall ) { KeyValues *kv = new KeyValues( "mmF->SysSessionUpdate" ); kv->SetPtr( "syssession", this ); kv->SetString( "error", "migrate" ); // Inside this event broadcast our session will be deleted SendEventsNotification( kv ); return; } else { // Update our network mgr to reflect the new host XUID id = msg->GetUint64( "id" ); m_pNetworkMgr->ConnectionPeerUpdateXuid( id, 0ull ); DevMsg( "CSysSessionClient - client migration scheduled - %s\n", chSessionInfo ); } // Now purge all network connections that the host is no longer supporting if ( int numMachines = m_pSettings->GetInt( "members/numMachines" ) ) { CUtlVector< XUID > arrCloseConnections; for ( int k = 0; k < numMachines; ++ k ) { KeyValues *pMachine = m_pSettings->FindKey( CFmtStr( "members/machine%d", k ) ); if ( !pMachine ) continue; XUID idMachine = pMachine->GetUint64( "id" ); if ( !msg->GetString( CFmtStr( "machines/%llx", idMachine ), NULL ) ) arrCloseConnections.AddToTail( idMachine ); } for ( int k = 0; k < arrCloseConnections.Count(); ++ k ) { XUID idMachine = arrCloseConnections[k]; m_pNetworkMgr->ConnectionPeerClose( idMachine ); OnPlayerLeave( idMachine ); } } // Send a notification KeyValues *kvEvent = new KeyValues( "OnPlayerLeaderChanged" ); kvEvent->SetString( "state", "client" ); kvEvent->SetUint64( "xuid", msg->GetUint64( "id" ) ); kvEvent->SetString( "migration", "started" ); SendEventsNotification( kvEvent ); } #endif else if ( !Q_stricmp( szMsg, "SysSession::OnUpdate" ) ) { if ( GetHostXuid() == xuidSrc ) { // Host is making changes to the session m_pSettings->MergeFrom( msg ); // Take care of the updated properties on the client side UpdateSessionProperties( msg->FindKey( "update", false ) ); // Broadcast the update to everybody interested MatchSession_BroadcastSessionSettingsUpdate( msg ); } } else { CSysSessionBase::ReceiveMessage( msg, bValidatedLobbyMember, xuidSrc ); } return; } } #ifdef _X360 #elif !defined( NO_STEAM ) void CSysSessionClient::Steam_OnLobbyEntered( LobbyEnter_t *pLobbyEnter ) { // Filter out notifications not from our lobby if ( pLobbyEnter->m_ulSteamIDLobby != m_lobby.m_uiLobbyID ) return; m_CallbackOnLobbyEntered.Unregister(); if ( pLobbyEnter->m_EChatRoomEnterResponse != k_EChatRoomEnterResponseSuccess ) { Warning( "CSysSessionClient: Cannot join lobby, response %d!\n", pLobbyEnter->m_EChatRoomEnterResponse ); m_eState = STATE_FAIL; KeyValues *kv = new KeyValues( "mmF->SysSessionUpdate" ); kv->SetPtr( "syssession", this ); kv->SetString( "error", LobbyEnterErrorAsString( pLobbyEnter ) ); OnSessionEvent( kv ); return; } else { XUID xuidLeader = steamapicontext->SteamMatchmaking()->GetLobbyOwner( m_lobby.m_uiLobbyID ).ConvertToUint64(); if ( m_xuidMachineId == xuidLeader ) { Warning( "CSysSessionClient: Host left lobby, unable to migrate\n" ); // We entered the lobby, but the host left KeyValues *kv = new KeyValues( "mmF->SysSessionUpdate" ); kv->SetPtr( "syssession", this ); kv->SetString( "error", "n/a" ); OnSessionEvent( kv ); return; } // DBUGG // What lobby have we just joined { DevMsg( "Joined lobby %llx\n", m_lobby.m_uiLobbyID ); int i, dataCount; dataCount = steamapicontext->SteamMatchmaking()->GetLobbyDataCount( m_lobby.m_uiLobbyID ); for ( i=0; i < dataCount; i++ ) { char key[64]; char val[64]; steamapicontext->SteamMatchmaking()->GetLobbyDataByIndex( m_lobby.m_uiLobbyID, i, key, sizeof(key), val, sizeof( val )); DevMsg( "Lobby data: %s = %s\n", key, val ); } } // In the case that the player is joining via direct connect we only want to proceed if // 1. The player has a friend in the lobby // 2. The player is not blocked by anyone in the lobby // If we have got this far (2) has already been taken care of by the frenemies code // so we just need to take care of (1) // The "options/dcFriendsReqd" flag is set in the client-server protocol // For direct connects this handshake has already happened before the lobby is joined // For matchmaking this happens after the lobby is joined so is irrelevant KeyValuesDumpAsDevMsg( m_pSettings ); bool bFriendsReqd = m_pSettings->GetBool( "options/dcFriendsRed", false ); if ( bFriendsReqd ) { // Check if the player is joining via direct connect (or invite) // if joining via invite the player will pass the test below const char *action = m_pSettings->GetString( "options/action", "" ); if ( !Q_stricmp( action, "joinsession" ) ) { // Walk lobby members int i, numLobbyMembers = steamapicontext->SteamMatchmaking()->GetNumLobbyMembers( m_lobby.m_uiLobbyID ); CSteamID playerID = steamapicontext->SteamUser()->GetSteamID(); for ( i = 0; i < numLobbyMembers; i++ ) { CSteamID memberId = steamapicontext->SteamMatchmaking()->GetLobbyMemberByIndex( m_lobby.m_uiLobbyID, i ); if ( memberId == playerID ) { continue; } EFriendRelationship reln = steamapicontext->SteamFriends()->GetFriendRelationship( memberId ); if ( reln == k_EFriendRelationshipFriend ) { break; } } if ( i >= numLobbyMembers ) { // No friends found - leave lobby steamapicontext->SteamMatchmaking()->LeaveLobby( m_lobby.m_uiLobbyID ); Warning( "CSysSessionClient: No friends in lobby - cannot join\n"); m_eState = STATE_FAIL; KeyValues *kv = new KeyValues( "mmF->SysSessionUpdate" ); kv->SetPtr( "syssession", this ); kv->SetString( "error", "Friend reqd to join lobby" ); OnSessionEvent( kv ); return; } } } // Request permission to join m_eState = STATE_REQUESTING_JOIN_DATA; m_RequestJoinDataInfo.m_fTimeSent = Plat_FloatTime(); m_RequestJoinDataInfo.m_xuidLeader = xuidLeader; Send_RequestJoinData(); return; } } #endif void CSysSessionClient::Process_ReplyJoinData_Our( KeyValues *msg ) { DevMsg("CSysSessionClient::Process_ReplyJoinData_Our\n"); KeyValuesDumpAsDevMsg( msg ); KeyValues *pSettings = msg->FindKey( "settings" ); char const *szError = msg->GetString( "error", NULL ); if ( !pSettings || szError ) { Warning( "CSysSessionClient: Received bad session data from host\n" ); m_eState = STATE_FAIL; KeyValues *kv = msg->MakeCopy(); kv->SetName( "mmF->SysSessionUpdate" ); kv->SetPtr( "syssession", this ); if ( !szError ) kv->SetString( "error", "n/a" ); OnSessionEvent( kv ); } else { m_eState = STATE_IDLE; #ifdef _X360 uint64 uiNonce = pSettings->GetUint64( "system/nonce" ); m_lobby.m_uiNonce = uiNonce; // Update host connection XUID if ( XUID xuidHost = pSettings->GetUint64( "members/machine0/id", 0ull ) ) { m_pNetworkMgr->ConnectionPeerUpdateXuid( 0ull, xuidHost ); } #endif // We have received an entirely new "settings" data, copy that to our "settings" data // after saving settings we need to restore int conteam = m_pSettings->GetInt( "conteam", -1 ); m_pSettings->Clear(); m_pSettings->SetName( pSettings->GetName() ); m_pSettings->MergeFrom( pSettings, KeyValues::MERGE_KV_UPDATE ); InitSessionProperties( m_pSettings ); if ( conteam != -1 ) { m_pSettings->SetInt("conteam", conteam ); } // Setup voice engine Voice_ProcessTalkers( NULL, true ); Voice_UpdateMutelist(); #ifdef _X360 // Peer-to-peer interconnect XP2P_Interconnect(); #endif KeyValues *kv = new KeyValues( "mmF->SysSessionUpdate" ); kv->SetPtr( "syssession", this ); if ( uint64 ullCrypt = msg->GetUint64( "crypt" ) ) kv->SetUint64( "crypt", ullCrypt ); OnSessionEvent( kv ); } } void CSysSessionClient::Process_ReplyJoinData_Other( KeyValues *msg ) { // Somebody has joined the session char const *szError = msg->GetString( "error", NULL ); KeyValues *pSettings = msg->FindKey( "settings" ); if ( !pSettings || szError ) // connection attempt was rejected return; KeyValues *pMembers = pSettings->FindKey( "members" ); if ( !pMembers ) return; // Now process all the new machines to notify the subscribers of // new players int numMachinesOld = m_pSettings->GetInt( "members/numMachines", 0 ); // Use this opportunity to fully sync-up our client-side settings int conteam = m_pSettings->GetInt( "conteam", -1 ); m_pSettings->Clear(); m_pSettings->SetName( pSettings->GetName() ); m_pSettings->MergeFrom( pSettings, KeyValues::MERGE_KV_UPDATE ); if ( conteam != -1 ) { m_pSettings->SetInt("conteam", conteam ); } #ifdef _X360 // On X360 we need to update lobby members server-side count MMX360_LobbyJoinMembers( pSettings, m_lobby, numMachinesOld ); #endif // Run over all new machines int numMachinesNew = m_pSettings->GetInt( "members/numMachines", 0 ); Assert( numMachinesNew > numMachinesOld ); for ( int k = numMachinesOld; k < numMachinesNew; ++ k ) { KeyValues *kvMachine = pMembers->FindKey( CFmtStr( "machine%d", k ) ); if ( !kvMachine ) continue; // Register talkers Voice_ProcessTalkers( kvMachine, true ); int numPlayers = kvMachine->GetInt( "numPlayers", 0 ); for ( int j = 0; j < numPlayers; ++ j ) { KeyValues *pPlayer = kvMachine->FindKey( CFmtStr( "player%d", j ) ); XUID xuid = pPlayer->GetUint64( "xuid", 0ull ); if ( !xuid ) continue; KeyValues *kvEvent = new KeyValues( "OnPlayerUpdated" ); kvEvent->SetString( "state", "joined" ); kvEvent->SetUint64( "xuid", xuid ); kvEvent->SetPtr( "player", pPlayer ); OnSessionEvent( kvEvent ); } } Voice_UpdateMutelist(); } void CSysSessionClient::Process_OnPlayerUpdated( KeyValues *msg ) { KeyValues *pPlayer = SessionMembersFindPlayer( m_pSettings, msg->GetUint64( "xuid" ) ); if ( !pPlayer ) return; char chNameBuffer[64]; Q_snprintf( chNameBuffer, ARRAYSIZE( chNameBuffer ), "%s", pPlayer->GetName() ); pPlayer->Clear(); pPlayer->SetName( chNameBuffer ); pPlayer->MergeFrom( msg, KeyValues::MERGE_KV_UPDATE ); // Notify the framework about player updated KeyValues *kvEvent = new KeyValues( "OnPlayerUpdated" ); kvEvent->SetUint64( "xuid", pPlayer->GetUint64( "xuid" ) ); kvEvent->SetPtr( "player", pPlayer ); OnSessionEvent( kvEvent ); } void CSysSessionClient::Process_OnMachineUpdated( KeyValues *msg ) { KeyValues *pMachine = NULL; SessionMembersFindPlayer( m_pSettings, msg->GetUint64( "id" ), &pMachine ); if ( !pMachine ) return; char chNameBuffer[64]; Q_snprintf( chNameBuffer, ARRAYSIZE( chNameBuffer ), "%s", pMachine->GetName() ); pMachine->Clear(); pMachine->SetName( chNameBuffer ); pMachine->MergeFrom( msg, KeyValues::MERGE_KV_UPDATE ); } void CSysSessionClient::Process_Kicked( KeyValues *msg ) { m_eState = STATE_FAIL; // Prepare the update notification KeyValues *kv = new KeyValues( "mmF->SysSessionUpdate" ); kv->SetPtr( "syssession", this ); kv->SetString( "error", "kicked" ); // Inside this event broadcast our session will be deleted SendEventsNotification( kv ); } void CSysSessionClient::Send_RequestJoinData() { KeyValues *msg = new KeyValues( "SysSession::RequestJoinData" ); KeyValues::AutoDelete autodelete( msg ); msg->SetUint64( "id", m_xuidMachineId ); // msg->AddSubKey( m_pSettings->MakeCopy() ); << don't send all settings, there's some unnecessary data msg->FindKey( "settings", true )->AddSubKey(m_pSettings->FindKey( "members" )->MakeCopy() ); KeyValues *pSystem = m_pSettings->FindKey( "options" ); uint64 teamResKey = pSystem->GetUint64( "teamResKey", 0 ); msg->FindKey( "settings" )->SetUint64( "teamResKey", teamResKey ); if ( mm_session_sys_pkey.GetString()[0] ) { msg->SetString( "settings/system/lock", mm_session_sys_pkey.GetString() ); } // If client needs to do join checks then forward them to host if ( KeyValues *kvJoinCheck = m_pSettings->FindKey( "joincheck" ) ) msg->FindKey( "settings" )->AddSubKey( kvJoinCheck->MakeCopy() ); DevMsg( "Sending join session request...\n" ); KeyValuesDumpAsDevMsg( msg, 1 ); #ifdef _X360 // Set the session id that we are connecting to msg->SetUint64( "sessionid", ( const uint64 & ) m_lobby.m_xiInfo.sessionID ); // Resolve this machine's XNADDR memset( &m_xnaddrLocal, 0, sizeof( m_xnaddrLocal ) ); while( XNET_GET_XNADDR_PENDING == g_pMatchExtensions->GetIXOnline()->XNetGetTitleXnAddr( &m_xnaddrLocal ) ) continue; char chXnaddr[ XNADDR_STRING_LENGTH ] = {0}; MMX360_XnaddrToString( m_xnaddrLocal, chXnaddr ); msg->SetString( "settings/members/machine0/xnaddr", chXnaddr ); // Now contact the remote host m_pNetworkMgr->ConnectionPeerSendConnectionless( 0ull, msg ); #elif !defined( NO_STEAM ) // Install callback for messages m_CallbackOnLobbyChatMsg.Register( this, &CSysSessionBase::Steam_OnLobbyChatMsg ); m_CallbackOnLobbyChatUpdate.Register( this, &CSysSessionBase::Steam_OnLobbyChatUpdate ); SendMessage( msg ); #endif } void CSysSessionClient::Migrate( KeyValues *pCommand ) { if ( Q_stricmp( pCommand->GetString( "migrate" ), "client>host" ) ) { Assert( 0 ); return; } #ifndef NO_STEAM uint64 uiNewLobbyOwner = steamapicontext->SteamMatchmaking()->GetLobbyOwner( m_lobby.m_uiLobbyID ).ConvertToUint64(); Assert( uiNewLobbyOwner == m_xuidMachineId ); uiNewLobbyOwner; // Prepare the update notification KeyValues *kv = new KeyValues( "mmF->SysSessionUpdate" ); kv->SetPtr( "syssession", this ); m_eState = STATE_MIGRATE; kv->SetString( "action", "host" ); // Inside this event broadcast our session will be deleted SendEventsNotification( kv ); #endif } void CSysSessionClient::OnPlayerLeave( XUID xuid ) { #if !defined( NO_STEAM ) if ( !V_stricmp( m_pSettings->GetString( "system/netflag" ), "noleave" ) ) { DevMsg( "CSysSessionClient::OnPlayerLeave(%llx) ignored in noleave mode.\n", xuid ); return; } #endif XUID xuidCurrentHost = GetHostXuid( xuid ); // Indicate the the leaving XUID could have been the host if ( m_eState == STATE_IDLE || m_eState == STATE_MIGRATE ) { FindAndRemovePlayerFromMembers( xuid ); } // We only care to handle this event further if we are becoming the new host char const *szForcedError = NULL; #ifdef _X360 XUID xuidNewHost = GetHostXuid(); if ( m_eState == STATE_REQUESTING_JOIN_DATA ) { szForcedError = "n/a"; } else if ( xuidCurrentHost != xuid ) { // Current host is not leaving, so we are fine return; } else if ( xuidNewHost != m_xuidMachineId ) { // The host is leaving, but we don't plan to host DevMsg( "CSysSessionClient::OnPlayerLeave - host left, waiting for new host (%llx)...\n", xuidNewHost ); // Send a notification KeyValues *kvEvent = new KeyValues( "OnPlayerLeaderChanged" ); kvEvent->SetString( "state", "client" ); kvEvent->SetUint64( "xuid", xuidNewHost ); kvEvent->SetString( "migration", "waiting" ); SendEventsNotification( kvEvent ); return; } else { DevMsg( "CSysSessionClient::OnPlayerLeave - becoming new host!\n" ); } #elif !defined( NO_STEAM ) XUID xuidNewHost = steamapicontext->SteamMatchmaking()->GetLobbyOwner( m_lobby.m_uiLobbyID ).ConvertToUint64(); if ( xuidNewHost != m_xuidMachineId ) { if ( m_eState == STATE_REQUESTING_JOIN_DATA && xuid == m_RequestJoinDataInfo.m_xuidLeader ) { // Resend join request // m_RequestJoinDataInfo.m_fTimeSent = Plat_FloatTime(); m_RequestJoinDataInfo.m_xuidLeader = xuidNewHost; Send_RequestJoinData(); } else if ( xuidNewHost != xuidCurrentHost ) { // Send a notification KeyValues *kvEvent = new KeyValues( "OnPlayerLeaderChanged" ); kvEvent->SetString( "state", "client" ); kvEvent->SetUint64( "xuid", xuidNewHost ); SendEventsNotification( kvEvent ); } return; } if ( m_eState != STATE_IDLE ) szForcedError = "n/a"; #endif // Prepare the update notification KeyValues *kv = new KeyValues( "mmF->SysSessionUpdate" ); kv->SetPtr( "syssession", this ); // If migration is not possible at this stage, // then a forced error will send us into FAIL state if ( szForcedError ) { m_eState = STATE_FAIL; kv->SetString( "error", szForcedError ); } else if ( !Q_stricmp( "teamlink", m_pSettings->GetString( "system/netflag", "" ) ) ) { // Team link sessions cannot migrate, we just trigger an error so that // entire client link session including network manager could destroy properly m_eState = STATE_FAIL; kv->SetString( "error", "migrate" ); } else { // We are about to migrate and become the new host // See if the title settings mgr wants to chime in if ( KeyValues *pMigrationHdlr = g_pMMF->GetMatchTitleGameSettingsMgr()->PrepareClientLobbyForMigration( m_pSettings, NULL ) ) { KeyValues::AutoDelete autodelete( pMigrationHdlr ); if ( char const *szError = pMigrationHdlr->GetString( "error", NULL ) ) { // Title forcing a migration error m_eState = STATE_FAIL; kv->SetString( "error", szError ); // Enqueue disconnect command g_pMatchExtensions->GetIVEngineClient()->ClientCmd( "disconnect" ); } } if ( m_eState != STATE_FAIL ) { m_eState = STATE_MIGRATE; kv->SetString( "action", "host" ); } } // Inside this event broadcast our session will be deleted SendEventsNotification( kv ); } CSysSessionConTeamHost::CSysSessionConTeamHost( KeyValues *pSettings ) : CSysSessionBase( pSettings ), m_eState( STATE_INIT ), m_lastRequestSendTime( 0.0f ) { #if !defined (NO_STEAM) m_CallbackOnLobbyChatMsg.Register( this, &CSysSessionBase::Steam_OnLobbyChatMsg ); #endif } CSysSessionConTeamHost::~CSysSessionConTeamHost() { #if !defined (NO_STEAM) m_CallbackOnLobbyChatMsg.Unregister(); #endif } bool CSysSessionConTeamHost::Update() { if (!CSysSessionBase::Update()) return false; switch ( m_eState ) { case STATE_INIT: #if defined (_X360) Succeeded(); break; // MMX360_LobbyConnect( m_pSettings, &m_pAsyncOperation ); #elif !defined (NO_STEAM) // Join lobby m_lobby.m_uiLobbyID = m_pSettings->GetUint64( "options/sessionid", 0ull ); m_CallbackOnLobbyEntered.Register( this, &CSysSessionConTeamHost::Steam_OnLobbyEntered ); steamapicontext->SteamMatchmaking()->JoinLobby( m_lobby.m_uiLobbyID ); #endif m_eState = STATE_WAITING_LOBBY_JOIN; break; case STATE_SEND_RESERVATION_REQUEST: SendReservationRequest(); m_eState = STATE_WAITING_RESERVATION_REQUEST; break; case STATE_WAITING_RESERVATION_REQUEST: if ( Plat_FloatTime() > m_lastRequestSendTime + mm_session_sys_connect_timeout.GetFloat() ) { DevMsg( "CSysSessionConTeamHost: Timed out waiting for reservation reply\n"); Failed(); } break; } return true; } void CSysSessionConTeamHost::Destroy() { #ifdef _X360 if ( m_eState == STATE_DELETE ) { delete this; return; } else { m_eState = STATE_DELETE; } #endif // Chain to base which will "delete this" CSysSessionBase::Destroy(); } bool CSysSessionConTeamHost::GetPlayerSidesAssignment( int *numPlayers, uint64 playerIDs[10], int side[10] ) { #if !defined (NO_STEAM) if ( GetResult() != RESULT_SUCCESS ) { return false; } const char* szNumTSlotsFree = steamapicontext->SteamMatchmaking()->GetLobbyData( m_lobby.m_uiLobbyID, "members:numTSlotsFree" ); const char* szNumCTSlotsFree = steamapicontext->SteamMatchmaking()->GetLobbyData( m_lobby.m_uiLobbyID, "members:numCTSlotsFree" ); int numTeamPlayers = m_pSettings->GetInt( "members/numPlayers" ); DevMsg( "CSysSessionConTeamHost: numTSlotsFree = %s, numCTSlotsFree = %s\n", szNumTSlotsFree, szNumCTSlotsFree); // Choose a team to join int numSlotsFree[2]; numSlotsFree[0] = atoi( szNumCTSlotsFree ); numSlotsFree[1] = atoi( szNumTSlotsFree ); // Choose a team at random int team = RandomInt(0, 1); int otherTeam = 1 - team; // If we chose a team with too few slots, but other team has enough // then swap if ( numSlotsFree[team] < numTeamPlayers ) { if ( numSlotsFree[otherTeam] >= numTeamPlayers ) { team = otherTeam; otherTeam = 1 - team; } } KeyValues *pMembers = m_pSettings->FindKey( "members" ); // Assign players to different teams for ( int i = 0; i < numTeamPlayers; i++ ) { const char *playerKey = CFmtStr( "machine%d/player0", i ); KeyValues *pPlayer = pMembers->FindKey( playerKey ); uint64 playerId = pPlayer->GetUint64( "xuid", 0 ); playerIDs[i] = playerId; // In keyvalues, team CT = 1, team T = 2 if ( numSlotsFree[team] ) { side[i] = team + 1; numSlotsFree[team]--; } else { side[i] = otherTeam + 1; } } *numPlayers = numTeamPlayers; #endif return true; } void CSysSessionConTeamHost::ReceiveMessage( KeyValues *msg, bool bValidatedLobbyMember, XUID xuidSrc ) { #if !defined (NO_STEAM) char const *szMsg = msg->GetName(); bool bProcessed = false; switch ( m_eState ) { case STATE_WAITING_RESERVATION_REQUEST: if ( !Q_stricmp( "SysSession::TeamReservationResult", szMsg ) ) { bProcessed = true; const char *res = msg->GetString( "result"); DevMsg( "CSysSessionConTeamHost: TeamReservationResult = %s\n", res ); if ( !Q_stricmp( "success", res ) ) { Succeeded(); } else { Failed(); } } break; } if ( !bProcessed ) { CSysSessionBase::ReceiveMessage( msg, bValidatedLobbyMember, xuidSrc ); } #endif } void CSysSessionConTeamHost::SendReservationRequest() { // Send reservation chat msg KeyValues *reservation = new KeyValues( "SysSession::TeamReservation" ); KeyValues::AutoDelete autodelete( reservation ); int numTeamPlayers = m_pSettings->GetInt( "members/numPlayers" ); reservation->SetInt( "numPlayers", numTeamPlayers ); uint64 teamResKey = m_pSettings->GetUint64( "members/machine0/id", 0 ); reservation->SetUint64( "teamResKey", teamResKey ); DevMsg( "Sending res request with teamResKey == %llx\n ", teamResKey ); #if defined (_X360) // Now contact the remote host m_pNetworkMgr->ConnectionPeerSendConnectionless( 0ull, reservation ); #elif !defined (NO_STEAM) CUtlBuffer buf; buf.ActivateByteSwapping( !CByteswap::IsMachineBigEndian() ); buf.PutInt( g_pMatchExtensions->GetINetSupport()->GetEngineBuildNumber() ); reservation->WriteAsBinary( buf ); steamapicontext->SteamMatchmaking()->SendLobbyChatMsg( m_lobby.m_uiLobbyID, buf.Base(), buf.TellMaxPut() ); #endif m_lastRequestSendTime = Plat_FloatTime(); } void CSysSessionConTeamHost::Succeeded() { m_eState = STATE_DONE; m_result = RESULT_SUCCESS; } void CSysSessionConTeamHost::Failed() { m_eState = STATE_DONE; m_result = RESULT_FAIL; } #ifdef _X360 void CSysSessionConTeamHost::OnAsyncOperationFinished() { if (m_eState == STATE_WAITING_LOBBY_JOIN ) { if ( m_pAsyncOperation->GetState() != AOS_SUCCEEDED ) { Warning( "CSysSessionConTeamHost: Could not join lobby\n" ); ReleaseAsyncOperation(); Failed(); return; } // // We have successfully created the client-side session, // retrieve all information from the async creation. // m_lobby = m_pAsyncOperation->GetLobby(); ReleaseAsyncOperation(); // Initialize network manager m_pNetworkMgr = new CX360NetworkMgr( this, GetX360NetSocket() ); if ( !m_pNetworkMgr->ConnectionPeerOpenActive( 0ull, m_lobby.m_xiInfo ) ) { m_pNetworkMgr->Destroy(); m_pNetworkMgr = NULL; Warning( "CSysSessionConTeamHost: ConnectionPeerOpenActive failed\n" ); Failed(); return; } m_eState = STATE_SEND_RESERVATION_REQUEST; return; } else { ReleaseAsyncOperation(); } } #elif !defined( NO_STEAM ) void CSysSessionConTeamHost::Steam_OnLobbyEntered( LobbyEnter_t *pLobbyEnter ) { // Filter out notifications not from our lobby if ( pLobbyEnter->m_ulSteamIDLobby != m_lobby.m_uiLobbyID ) return; m_CallbackOnLobbyEntered.Unregister(); if ( pLobbyEnter->m_EChatRoomEnterResponse != k_EChatRoomEnterResponseSuccess ) { Warning( "CSysSessionConTeamHost: Could not join lobby\n" ); Failed(); } else { m_eState = STATE_SEND_RESERVATION_REQUEST; } } #endif XUID CSysSessionConTeamHost::GetHostXuid( XUID xuidValidResult ) { return 0ull; } #ifdef _X360 bool CSysSessionConTeamHost::OnX360NetConnectionlessPacket( netpacket_t *pkt, KeyValues *msg ) { return true; } #endif