Counter Strike : Global Offensive Source Code
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

4034 lines
112 KiB

//===== 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/a>\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