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.

4033 lines
112 KiB

  1. //===== Copyright � 1996-2009, Valve Corporation, All rights reserved. ======//
  2. //
  3. // Purpose:
  4. //
  5. //===========================================================================//
  6. #include "mm_framework.h"
  7. #include "fmtstr.h"
  8. // memdbgon must be the last include file in a .cpp file!!!
  9. #include "tier0/memdbgon.h"
  10. ConVar mm_session_sys_delay_create( "mm_session_sys_delay_create", "0", FCVAR_DEVELOPMENTONLY );
  11. ConVar mm_session_sys_delay_create_host( "mm_session_sys_delay_create_host", "1.2", FCVAR_DEVELOPMENTONLY );
  12. ConVar mm_session_sys_timeout( "mm_session_sys_timeout", "3", FCVAR_DEVELOPMENTONLY );
  13. ConVar mm_session_sys_connect_timeout( "mm_session_sys_connect_timeout", "8", FCVAR_DEVELOPMENTONLY );
  14. ConVar mm_session_team_res_timeout( "mm_session_team_res_timeout", "30", FCVAR_DEVELOPMENTONLY );
  15. ConVar mm_session_voice_loading( "mm_session_voice_loading", "0", FCVAR_DEVELOPMENTONLY );
  16. ConVar mm_session_sys_ranking_timeout( "mm_session_sys_ranking_timeout", "12", FCVAR_DEVELOPMENTONLY );
  17. ConVar mm_session_sys_pkey( "mm_session_sys_pkey", "", FCVAR_RELEASE );
  18. ConVar mm_session_sys_kick_ban_duration( "mm_session_sys_kick_ban_duration", "180", FCVAR_RELEASE );
  19. #define STEAM_LOBBY_CHAT_MSG_BUFFER_SIZE 65536
  20. #ifdef _X360
  21. static CUtlVector< CSysSessionBase * > g_arrSysSessionsPending;
  22. static CUtlVector< CSysSessionBase * > g_arrSysSessionsDelete;
  23. void SysSession360_UpdatePending()
  24. {
  25. // Delete scheduled sessions
  26. for ( int k = 0; k < g_arrSysSessionsDelete.Count(); ++ k )
  27. {
  28. CSysSessionBase *pSession = g_arrSysSessionsDelete[k];
  29. DevMsg( "SysSession360_UpdatePending: destroying session %p\n", pSession );
  30. pSession->Destroy();
  31. g_arrSysSessionsPending.FindAndFastRemove( pSession );
  32. }
  33. g_arrSysSessionsDelete.Purge();
  34. // Update pending sessions
  35. for ( int k = 0; k < g_arrSysSessionsPending.Count(); ++ k )
  36. {
  37. CSysSessionBase *pSysSession = g_arrSysSessionsPending[k];
  38. pSysSession->Update();
  39. if ( k >= g_arrSysSessionsPending.Count() || pSysSession != g_arrSysSessionsPending[k] )
  40. // If a pending session has removed itself, then proceed updating next frame
  41. return;
  42. }
  43. }
  44. void SysSession360_RegisterPending( CSysSessionBase *pSession )
  45. {
  46. if ( g_arrSysSessionsPending.Find( pSession ) == g_arrSysSessionsPending.InvalidIndex() )
  47. {
  48. DevMsg( "SysSession360_RegisterPending: registered pending session %p\n", pSession );
  49. g_arrSysSessionsPending.AddToTail( pSession );
  50. }
  51. else if ( g_arrSysSessionsDelete.Find( pSession ) == g_arrSysSessionsDelete.InvalidIndex() )
  52. {
  53. DevMsg( "SysSession360_RegisterPending: registered session %p for delete\n", pSession );
  54. g_arrSysSessionsDelete.AddToTail( pSession );
  55. }
  56. else
  57. Error( "SysSession360_RegisterPending for deleted session!" );
  58. }
  59. #endif
  60. static bool SysSession_AllowCreate()
  61. {
  62. #ifdef _X360
  63. // Cannot create new sessions while another session is pending
  64. if ( g_arrSysSessionsPending.Count() )
  65. return false;
  66. #endif
  67. static float s_fAllowCreateTime = 0.0f;
  68. float flDelay = mm_session_sys_delay_create.GetFloat();
  69. if ( flDelay <= 0 )
  70. {
  71. s_fAllowCreateTime = 0.0f;
  72. return true;
  73. }
  74. else
  75. {
  76. if ( s_fAllowCreateTime > 0.0f )
  77. {
  78. if ( Plat_FloatTime() < s_fAllowCreateTime )
  79. return false; // Delay time not elapsed yet
  80. s_fAllowCreateTime = 0.0f;
  81. return true; // Delay time has elapsed already
  82. }
  83. else
  84. {
  85. s_fAllowCreateTime = Plat_FloatTime() + flDelay;
  86. return false; // Starting the delay time
  87. }
  88. }
  89. }
  90. CSysSessionBase::CSysSessionBase( KeyValues *pSettings ) :
  91. #ifdef _X360
  92. m_pAsyncOperation( NULL ),
  93. m_pNetworkMgr( NULL ),
  94. m_hLobbyMigrateCall( NULL ),
  95. #elif !defined( NO_STEAM )
  96. m_CallbackOnServersConnected( this, &CSysSessionBase::Steam_OnServersConnected ),
  97. m_CallbackOnServersDisconnected( this, &CSysSessionBase::Steam_OnServersDisconnected ),
  98. m_CallbackOnP2PSessionRequest( this, &CSysSessionBase::Steam_OnP2PSessionRequest ),
  99. m_bVoiceUsingSessionP2P( false ),
  100. #endif
  101. m_Voice_flLastHeadsetStatusCheck( -1.0f ),
  102. m_pSettings( pSettings ),
  103. m_xuidMachineId( g_pPlayerManager->GetLocalPlayer( XBX_GetPrimaryUserId() )->GetXUID() ),
  104. m_result( RESULT_UNDEFINED )
  105. {
  106. }
  107. CSysSessionBase::~CSysSessionBase()
  108. {
  109. ;
  110. }
  111. bool CSysSessionBase::Update()
  112. {
  113. #ifdef _X360
  114. if ( m_pNetworkMgr )
  115. {
  116. if ( m_pNetworkMgr->Update() != CX360NetworkMgr::UPDATE_SUCCESS )
  117. return false; // the network manager destroyed or changed listener
  118. }
  119. #elif !defined( NO_STEAM )
  120. if ( !IsServiceSession() )
  121. {
  122. // Process P2P network
  123. uint32 uiSteamMsgSize = 0;
  124. CUtlMemory< byte > utlMemory;
  125. CSteamID idRemote;
  126. while( steamapicontext->SteamNetworking()->IsP2PPacketAvailable( &uiSteamMsgSize, INetSupport::SP2PC_LOBBY ) )
  127. {
  128. utlMemory.EnsureCapacity( uiSteamMsgSize );
  129. if ( steamapicontext->SteamNetworking()->ReadP2PPacket( utlMemory.Base(), uiSteamMsgSize, &uiSteamMsgSize, &idRemote, INetSupport::SP2PC_LOBBY ) )
  130. UnpackAndReceiveMessage( utlMemory.Base(), uiSteamMsgSize, true, idRemote.ConvertToUint64() );
  131. }
  132. }
  133. #endif
  134. Voice_UpdateLocalHeadsetsStatus();
  135. Voice_CaptureAndTransmitLocalVoiceData();
  136. #ifdef _X360
  137. if ( m_pAsyncOperation )
  138. {
  139. m_pAsyncOperation->Update();
  140. if ( m_pAsyncOperation->IsFinished() )
  141. {
  142. OnAsyncOperationFinished();
  143. }
  144. }
  145. if ( UpdateMigrationCall() )
  146. return false;
  147. #endif
  148. return true;
  149. }
  150. bool CSysSessionBase::IsServiceSession()
  151. {
  152. if ( !m_pSettings )
  153. return true;
  154. if ( char const *szNetFlag = m_pSettings->GetString( "system/netflag", NULL ) )
  155. {
  156. if ( !Q_stricmp( "teamlink", szNetFlag ) )
  157. return true;
  158. }
  159. return false;
  160. }
  161. void CSysSessionBase::OnSessionEvent( KeyValues *notify )
  162. {
  163. g_pMatchEventsSubscription->BroadcastEvent( notify );
  164. }
  165. void CSysSessionBase::SendEventsNotification( KeyValues *notify )
  166. {
  167. OnSessionEvent( notify );
  168. }
  169. #ifdef _X360
  170. void CSysSessionBase::ReleaseAsyncOperation()
  171. {
  172. if ( m_pAsyncOperation )
  173. {
  174. m_pAsyncOperation->Release();
  175. m_pAsyncOperation = NULL;
  176. }
  177. }
  178. INetSupport::NetworkSocket_t CSysSessionBase::GetX360NetSocket()
  179. {
  180. if ( char const *szNetFlag = m_pSettings->GetString( "system/netflag", NULL ) )
  181. {
  182. if ( !Q_stricmp( "teamlink", szNetFlag ) )
  183. return INetSupport::NS_SOCK_TEAMLINK;
  184. }
  185. return INetSupport::NS_SOCK_LOBBY;
  186. }
  187. bool CSysSessionBase::ShouldAllowX360HostMigration()
  188. {
  189. if ( !m_lobby.m_hHandle )
  190. return false;
  191. // Check the session details
  192. if ( dynamic_cast< CSysSessionHost * >( this ) )
  193. {
  194. return
  195. m_pSettings->GetInt( "members/numMachines", 0 ) > 1 && // more than 1 machine in the session
  196. !Q_stricmp( m_pSettings->GetString( "system/network" ), "LIVE" ); // migrate only on LIVE
  197. }
  198. return false;
  199. }
  200. bool CSysSessionBase::UpdateMigrationCall()
  201. {
  202. if ( !m_hLobbyMigrateCall )
  203. return false;
  204. if ( !m_MigrateCallState.m_bFinished )
  205. return false;
  206. // The call was outstanding and is now finished
  207. m_hLobbyMigrateCall = NULL;
  208. if ( m_MigrateCallState.m_ret != ERROR_SUCCESS )
  209. {
  210. Assert( 0 );
  211. KeyValues *kv = new KeyValues( "mmF->SysSessionUpdate" );
  212. kv->SetPtr( "syssession", this );
  213. kv->SetString( "error", "migrate" );
  214. // Inside this event broadcast our session will be deleted
  215. SendEventsNotification( kv );
  216. return true;
  217. }
  218. // Prepare the notification
  219. KeyValues *kvEvent = new KeyValues( "OnPlayerLeaderChanged" );
  220. kvEvent->SetString( "migrate", "finished" );
  221. if ( dynamic_cast< CSysSessionHost * >( this ) )
  222. {
  223. // We need to notify all our peers that we are now the new host and we have the new
  224. // session details.
  225. KeyValues *msg = new KeyValues( "SysSession::HostMigrated" );
  226. KeyValues::AutoDelete autodelete( msg );
  227. msg->SetUint64( "id", m_xuidMachineId );
  228. char chSessionInfo[ XSESSION_INFO_STRING_LENGTH ];
  229. MMX360_SessionInfoToString( m_lobby.m_xiInfo, chSessionInfo );
  230. msg->SetString( "sessioninfo", chSessionInfo );
  231. // When the host migrated, we need to let the clients know which
  232. // machines are still staying in the session
  233. #ifdef _X360
  234. if ( IsX360() && m_pNetworkMgr )
  235. {
  236. int numMachines = m_pSettings->GetInt( "members/numMachines" );
  237. for ( int k = 0; k < numMachines; ++ k )
  238. {
  239. KeyValues *pMachine = m_pSettings->FindKey( CFmtStr( "members/machine%d", k ) );
  240. if ( !pMachine )
  241. continue;
  242. XUID idMachine = pMachine->GetUint64( "id" );
  243. if ( idMachine &&
  244. ( idMachine == m_xuidMachineId ||
  245. m_pNetworkMgr->ConnectionPeerGetAddress( idMachine ) ) )
  246. {
  247. msg->SetString( CFmtStr( "machines/%llx", idMachine ), "" );
  248. }
  249. }
  250. }
  251. #endif
  252. DevMsg( "CSysSessionHost - host migrated - %s\n", chSessionInfo );
  253. SendMessage( msg );
  254. #ifdef _X360
  255. // Drop all machines that no longer have a network connection with us
  256. // in case P2P interconnect failed earlier
  257. if ( IsX360() && m_pNetworkMgr )
  258. {
  259. CUtlVector< XUID > arrXuidsNoP2P;
  260. int numMachines = m_pSettings->GetInt( "members/numMachines" );
  261. for ( int k = 0; k < numMachines; ++ k )
  262. {
  263. KeyValues *pMachine = m_pSettings->FindKey( CFmtStr( "members/machine%d", k ) );
  264. if ( !pMachine )
  265. continue;
  266. XUID idMachine = pMachine->GetUint64( "id" );
  267. if ( idMachine &&
  268. idMachine != m_xuidMachineId &&
  269. !m_pNetworkMgr->ConnectionPeerGetAddress( idMachine ) )
  270. arrXuidsNoP2P.AddToTail( idMachine );
  271. }
  272. for ( int k = 0; k < arrXuidsNoP2P.Count(); ++ k )
  273. {
  274. DevWarning( "UpdateMigrationCall - dropping machine %llx due to no P2P network connection!\n", arrXuidsNoP2P[k] );
  275. OnPlayerLeave( arrXuidsNoP2P[k] );
  276. }
  277. //
  278. // Update QOS reply data of the session
  279. //
  280. CUtlBuffer bufQosData;
  281. bufQosData.ActivateByteSwapping( !CByteswap::IsMachineBigEndian() );
  282. g_pMatchFramework->GetMatchNetworkMsgController()->PackageGameDetailsForQOS( m_pSettings, bufQosData );
  283. g_pMatchExtensions->GetIXOnline()->XNetQosListen( &m_lobby.m_xiInfo.sessionID,
  284. ( const BYTE * ) bufQosData.Base(), bufQosData.TellMaxPut(),
  285. 0, XNET_QOS_LISTEN_SET_DATA | XNET_QOS_LISTEN_ENABLE );
  286. }
  287. #endif
  288. // Send a notification
  289. kvEvent->SetString( "state", "host" );
  290. kvEvent->SetUint64( "xuid", m_xuidMachineId );
  291. }
  292. else
  293. {
  294. // Send a notification
  295. DevMsg( "CSysSessionClient - client migration finished\n" );
  296. kvEvent->SetString( "state", "client" );
  297. kvEvent->SetUint64( "xuid", m_pSettings->GetUint64( "members/machine0/id", 0ull ) );
  298. }
  299. SendEventsNotification( kvEvent );
  300. return false;
  301. }
  302. #endif
  303. void CSysSessionBase::Destroy()
  304. {
  305. #ifdef _X360
  306. // If the session is in an active gameplay state, first statistic reporting
  307. // should be initiated on the session before it can be destroyed
  308. if ( m_lobby.m_bXSessionStarted )
  309. {
  310. DevWarning( "CSysSessionBase::Destroy called on an active gameplay session, forcing XSessionEnd!\n" );
  311. MMX360_LobbySetActiveGameplayState( m_lobby, false, NULL );
  312. }
  313. // If a migrate call was outstanding on the session, then disassociate the listener
  314. if ( m_hLobbyMigrateCall )
  315. {
  316. MMX360_LobbyMigrateSetListener( m_hLobbyMigrateCall, NULL );
  317. m_hLobbyMigrateCall = NULL;
  318. }
  319. bool bShouldAllowHostMigration = ShouldAllowX360HostMigration();
  320. if ( KeyValues *msg = new KeyValues( "SysSession::Quit" ) )
  321. {
  322. KeyValues::AutoDelete autodelete( msg );
  323. msg->SetUint64( "id", m_xuidMachineId );
  324. SendMessage( msg );
  325. }
  326. if ( m_pNetworkMgr && !bShouldAllowHostMigration )
  327. {
  328. m_pNetworkMgr->Destroy();
  329. m_pNetworkMgr = NULL;
  330. }
  331. ReleaseAsyncOperation();
  332. Voice_ProcessTalkers( NULL, false );
  333. if ( !bShouldAllowHostMigration )
  334. {
  335. if ( m_lobby.m_hHandle )
  336. MMX360_LobbyDelete( m_lobby, &m_pAsyncOperation );
  337. else
  338. SysSession360_RegisterPending( this ); // registers first as if lobby delete has completed
  339. m_pSettings = NULL;
  340. SysSession360_RegisterPending( this );
  341. return;
  342. }
  343. else
  344. {
  345. // Waiting for migration to finish
  346. DevMsg( "CSysSessionBase::Destroy is waiting for migration to finish...\n" );
  347. m_pSettings = NULL;
  348. SysSession360_RegisterPending( this );
  349. return;
  350. }
  351. #elif !defined( NO_STEAM )
  352. Voice_ProcessTalkers( NULL, false );
  353. if ( m_lobby.m_uiLobbyID )
  354. {
  355. if ( !IsServiceSession() )
  356. {
  357. for ( int k = 0, kNum = steamapicontext->SteamMatchmaking()->GetNumLobbyMembers( m_lobby.m_uiLobbyID ); k < kNum; ++ k )
  358. {
  359. CSteamID idRemote = steamapicontext->SteamMatchmaking()->GetLobbyMemberByIndex( m_lobby.m_uiLobbyID, k );
  360. steamapicontext->SteamNetworking()->CloseP2PChannelWithUser( idRemote, INetSupport::SP2PC_LOBBY );
  361. }
  362. }
  363. steamapicontext->SteamMatchmaking()->LeaveLobby( m_lobby.m_uiLobbyID );
  364. }
  365. m_lobby = CSteamLobbyObject();
  366. #endif
  367. delete this;
  368. }
  369. void CSysSessionBase::DebugPrint()
  370. {
  371. DevMsg( "CSysSessionBase\n" );
  372. DevMsg( " machineid: %llx\n", m_xuidMachineId );
  373. #ifdef _X360
  374. DevMsg( " nonce: %llx\n", m_lobby.m_uiNonce );
  375. DevMsg( " xhandle: %x\n", m_lobby.m_hHandle );
  376. DevMsg( " xnkid: %llx\n", ( const uint64 & ) m_lobby.m_xiInfo.sessionID );
  377. DevMsg( " xstarted: %d\n", m_lobby.m_bXSessionStarted );
  378. XSESSION_LOCAL_DETAILS xld = {0};
  379. DWORD dwSize = sizeof( xld );
  380. DWORD ret = g_pMatchExtensions->GetIXOnline()->XSessionGetDetails( m_lobby.m_hHandle, &dwSize, &xld, NULL );
  381. if ( ERROR_SUCCESS == ret )
  382. {
  383. DevMsg( " idx host: %d\n", xld.dwUserIndexHost );
  384. DevMsg( " game type: %d\n", xld.dwGameType );
  385. DevMsg( " game mode: %d\n", xld.dwGameMode );
  386. DevMsg( " flags: 0x%X\n", xld.dwFlags );
  387. DevMsg( " pub slots: %d/%d\n", xld.dwMaxPublicSlots - xld.dwAvailablePublicSlots, xld.dwMaxPublicSlots );
  388. DevMsg( " pri slots: %d/%d\n", xld.dwMaxPrivateSlots - xld.dwMaxPrivateSlots, xld.dwMaxPrivateSlots );
  389. DevMsg( " members: %d (%d)\n", xld.dwActualMemberCount, xld.dwReturnedMemberCount );
  390. DevMsg( " xsesstate: %d\n", xld.eState );
  391. DevMsg( " xnonce: %llx\n", xld.qwNonce );
  392. DevMsg( " xnkid: %llx\n", ( const uint64 & ) xld.sessionInfo.sessionID );
  393. DevMsg( " xnkid arb: %llx\n", ( const uint64 & ) xld.xnkidArbitration );
  394. }
  395. else
  396. {
  397. DevMsg( " session sys details unavailable: code %d\n", ret );
  398. }
  399. if ( m_pNetworkMgr )
  400. {
  401. m_pNetworkMgr->DebugPrint();
  402. }
  403. else
  404. {
  405. DevMsg( " no network mgr\n" );
  406. }
  407. #elif !defined( NO_STEAM )
  408. DevMsg( " lobby id: %llx\n", m_lobby.m_uiLobbyID );
  409. DevMsg( " lbystate: %d\n", m_lobby.m_eLobbyState );
  410. if ( m_lobby.m_uiLobbyID )
  411. {
  412. int numMembers = steamapicontext->SteamMatchmaking()->GetNumLobbyMembers( m_lobby.m_uiLobbyID );
  413. DevMsg( " owner: %llx\n", steamapicontext->SteamMatchmaking()->GetLobbyOwner( m_lobby.m_uiLobbyID ).ConvertToUint64() );
  414. DevMsg( " members: %d/%s/%d\n", numMembers,
  415. steamapicontext->SteamMatchmaking()->GetLobbyData( m_lobby.m_uiLobbyID, "members:numSlots" ),
  416. steamapicontext->SteamMatchmaking()->GetLobbyMemberLimit( m_lobby.m_uiLobbyID ) );
  417. for ( int k = 0; k < numMembers; ++ k )
  418. {
  419. XUID xuid = steamapicontext->SteamMatchmaking()->GetLobbyMemberByIndex( m_lobby.m_uiLobbyID, k ).ConvertToUint64();
  420. KeyValues *kvPlayer = SessionMembersFindPlayer( m_pSettings, xuid );
  421. if ( kvPlayer )
  422. {
  423. DevMsg( " member%02d: %llx '%s'\n", k, xuid, kvPlayer->GetString( "name" ) );
  424. }
  425. else
  426. {
  427. DevMsg( " member%02d: %llx <n/a>\n", k, xuid );
  428. }
  429. }
  430. DevMsg( " ldata:net: %s\n", steamapicontext->SteamMatchmaking()->GetLobbyData( m_lobby.m_uiLobbyID, "system:network" ) );
  431. }
  432. #endif
  433. }
  434. void CSysSessionBase::Command( KeyValues *pCommand )
  435. {
  436. KeyValues *msg = new KeyValues( "SysSession::Command" );
  437. KeyValues::AutoDelete autodelete( msg );
  438. msg->AddSubKey( pCommand->MakeCopy() );
  439. SendMessage( msg );
  440. }
  441. uint64 CSysSessionBase::GetReservationCookie()
  442. {
  443. if ( uint64 uiReservationCookieOverride = m_pSettings->GetUint64( "server/reservationid", 0ull ) )
  444. return uiReservationCookieOverride;
  445. return GetNonceCookie();
  446. }
  447. uint64 CSysSessionBase::GetNonceCookie()
  448. {
  449. #ifdef _X360
  450. return m_lobby.m_uiNonce;
  451. #elif !defined( NO_STEAM )
  452. return m_lobby.GetSessionId();
  453. #else
  454. Assert( false ); // Not implemented for this platform
  455. return 0;
  456. #endif
  457. }
  458. uint64 CSysSessionBase::GetSessionID()
  459. {
  460. return GetNonceCookie();
  461. }
  462. void CSysSessionBase::ReplyLanSearch( KeyValues *msg )
  463. {
  464. // Assemble a search reply message
  465. KeyValues *reply = new KeyValues( "GameDetailsPlayer" );
  466. KeyValues::AutoDelete autodelete( reply );
  467. // Put information about our session
  468. #ifdef _X360
  469. char chSessionInfo[ XSESSION_INFO_STRING_LENGTH ] = {0};
  470. MMX360_SessionInfoToString( m_lobby.m_xiInfo, chSessionInfo );
  471. reply->SetString( "options/sessioninfo", chSessionInfo );
  472. #elif !defined( NO_STEAM )
  473. reply->SetUint64( "options/sessionid", m_lobby.m_uiLobbyID );
  474. #endif
  475. // Information about primary player
  476. if ( IPlayerLocal *pPlayer = g_pPlayerManager->GetLocalPlayer( XBX_GetPrimaryUserId() ) )
  477. {
  478. reply->SetString( "player/name", pPlayer->GetName() );
  479. reply->SetUint64( "player/xuid", pPlayer->GetXUID() );
  480. #ifdef _X360
  481. XUSER_SIGNIN_INFO xsi;
  482. if ( ERROR_SUCCESS == XUserGetSigninInfo( XBX_GetPrimaryUserId(), XUSER_GET_SIGNIN_INFO_ONLINE_XUID_ONLY, &xsi )
  483. && xsi.xuid )
  484. {
  485. reply->SetUint64( "player/xuidOnline", xsi.xuid );
  486. }
  487. #endif
  488. }
  489. // Compose the binary encoding of game details
  490. CUtlBuffer bufGameDetails;
  491. bufGameDetails.ActivateByteSwapping( !CByteswap::IsMachineBigEndian() );
  492. g_pMatchFramework->GetMatchNetworkMsgController()->PackageGameDetailsForQOS( m_pSettings, bufGameDetails );
  493. reply->SetPtr( "binary/ptr", bufGameDetails.Base() );
  494. reply->SetInt( "binary/size", bufGameDetails.TellMaxPut() );
  495. // Reply to sender
  496. g_pConnectionlessLanMgr->SendPacket( reply,
  497. ( !IsX360() && msg ) ? msg->GetString( "from", NULL ) : NULL );
  498. }
  499. void CSysSessionBase::SendMessage( KeyValues *msg )
  500. {
  501. #ifdef _X360
  502. if ( m_pNetworkMgr )
  503. m_pNetworkMgr->ConnectionPeerSendMessage( msg );
  504. // Do not receive my own quit message
  505. bool bCanReceive = !!Q_stricmp( "SysSession::Quit", msg->GetName() );
  506. if ( bCanReceive )
  507. ReceiveMessage( msg, true );
  508. #elif !defined( NO_STEAM )
  509. CUtlBuffer buf;
  510. buf.ActivateByteSwapping( !CByteswap::IsMachineBigEndian() );
  511. buf.PutInt( g_pMatchExtensions->GetINetSupport()->GetEngineBuildNumber() );
  512. msg->WriteAsBinary( buf );
  513. // Special case when encoding binary data
  514. KeyValues *kvPtr = msg->FindKey( "binary/ptr" );
  515. KeyValues *kvSize = msg->FindKey( "binary/size" );
  516. if ( kvPtr && kvSize )
  517. {
  518. void *pvData = kvPtr->GetPtr();
  519. int nSize = kvSize->GetInt();
  520. if ( pvData && nSize )
  521. {
  522. buf.Put( pvData, nSize );
  523. }
  524. }
  525. if ( char const *szP2P = msg->GetString( "p2p", NULL ) )
  526. {
  527. Assert( !IsServiceSession() );
  528. // Determine P2P type
  529. EP2PSend eSendType = k_EP2PSendUnreliableNoDelay; // "nodelay"
  530. if ( !Q_stricmp( szP2P, "reliable" ) )
  531. eSendType = k_EP2PSendReliable;
  532. else if ( !Q_stricmp( szP2P, "buffer" ) )
  533. eSendType = k_EP2PSendReliableWithBuffering;
  534. else if ( !Q_stricmp( szP2P, "unreliable" ) )
  535. eSendType = k_EP2PSendUnreliable;
  536. // Transmit P2P message
  537. // for ( int k = 0, kNum = steamapicontext->SteamMatchmaking()->GetNumLobbyMembers( m_lobby.m_uiLobbyID ); k < kNum; ++ k )
  538. // {
  539. // CSteamID idRemote = steamapicontext->SteamMatchmaking()->GetLobbyMemberByIndex( m_lobby.m_uiLobbyID, k );
  540. // if ( idRemote.ConvertToUint64() != m_xuidMachineId )
  541. // steamapicontext->SteamNetworking()->SendP2PPacket( idRemote, buf.Base(), buf.TellMaxPut(), eSendType, INetSupport::SP2PC_LOBBY );
  542. // }
  543. for ( int k = 0, numMachines = m_pSettings->GetInt( "members/numMachines" ); k < numMachines; ++ k )
  544. {
  545. for ( int ic = 0, numPlayers = m_pSettings->GetInt( CFmtStr( "members/machine%d/numPlayers", k ) ); ic < numPlayers; ++ ic )
  546. {
  547. XUID xuidPlayer = m_pSettings->GetUint64( CFmtStr( "members/machine%d/player%d/xuid", k, ic ) );
  548. if ( xuidPlayer && xuidPlayer != m_xuidMachineId )
  549. {
  550. steamapicontext->SteamNetworking()->SendP2PPacket( xuidPlayer, buf.Base(), buf.TellMaxPut(), eSendType, INetSupport::SP2PC_LOBBY );
  551. m_bVoiceUsingSessionP2P = true;
  552. }
  553. }
  554. }
  555. // Receive on our own side
  556. ReceiveMessage( msg, true, m_xuidMachineId );
  557. }
  558. else if ( m_lobby.m_uiLobbyID )
  559. {
  560. steamapicontext->SteamMatchmaking()->SendLobbyChatMsg( m_lobby.m_uiLobbyID, buf.Base(), buf.TellMaxPut() );
  561. }
  562. else
  563. {
  564. ReceiveMessage( msg, true, m_xuidMachineId );
  565. }
  566. #endif
  567. }
  568. void CSysSessionBase::ReceiveMessage( KeyValues *msg, bool bValidatedLobbyMember, XUID xuidSrc )
  569. {
  570. char const *szMsg = msg->GetName();
  571. if ( !Q_stricmp( "SysSession::Quit", szMsg ) )
  572. {
  573. XUID xuidMachine = msg->GetUint64( "id" );
  574. Assert( xuidMachine == xuidSrc );
  575. // assuming that xuidMachine and xuid of primary user are same
  576. OnPlayerLeave( xuidSrc );
  577. }
  578. else if ( !Q_stricmp( "SysSession::Command", szMsg ) )
  579. {
  580. KeyValues *pCommand = msg->GetFirstTrueSubKey();
  581. if ( !pCommand )
  582. return;
  583. char const *szRun = pCommand->GetString( "run", "" );
  584. bool bRun = false;
  585. if ( !Q_stricmp( szRun, "all" ) )
  586. bRun = true;
  587. else if ( !Q_stricmp( szRun, "host" ) && dynamic_cast< CSysSessionHost * >( this ) )
  588. bRun = true;
  589. else if ( !Q_stricmp( szRun, "clients" ) && dynamic_cast< CSysSessionClient * >( this ) )
  590. bRun = true;
  591. else if ( !Q_stricmp( szRun, "xuid" ) && m_xuidMachineId == pCommand->GetUint64( "runxuid" ) )
  592. bRun = true;
  593. if ( bRun )
  594. {
  595. KeyValues *kv = new KeyValues( "mmF->SysSessionCommand" );
  596. msg->RemoveSubKey( pCommand );
  597. kv->AddSubKey( pCommand );
  598. // We always set these field regardless of what was in command to indicate that it arrived from a remote machine
  599. bool bHostSrcXuid = ( xuidSrc == GetHostXuid() );
  600. pCommand->SetBool( "_remote_host", bHostSrcXuid );
  601. pCommand->SetUint64( "_remote_xuidsrc", xuidSrc );
  602. kv->SetBool( "host", bHostSrcXuid );
  603. kv->SetUint64( "xuidsrc", xuidSrc );
  604. kv->SetPtr( "syssession", this );
  605. OnSessionEvent( kv );
  606. }
  607. }
  608. else if ( !Q_stricmp( "SysSession::Voice", szMsg ) )
  609. {
  610. Voice_Playback( msg, xuidSrc );
  611. }
  612. }
  613. #ifdef _X360
  614. void CSysSessionBase::OnX360NetPacket( KeyValues *msg )
  615. {
  616. ReceiveMessage( msg, true );
  617. }
  618. void CSysSessionBase::OnX360NetDisconnected( XUID xuidRemote )
  619. {
  620. OnPlayerLeave( xuidRemote );
  621. }
  622. void CSysSessionBase::OnX360AllSessionMembersJoinLeave( KeyValues *kv )
  623. {
  624. char const *szLock = kv->GetString( "system/lock", NULL );
  625. char const *szAccess = kv->GetString( "system/access", NULL );
  626. if ( szAccess || ( szLock && Q_stricmp( szLock, "endgame" ) && !IsX360() ) )
  627. {
  628. if ( m_lobby.m_bXSessionStarted )
  629. {
  630. DevWarning( "CSysSessionBase::OnX360AllSessionMembersJoinLeave cannot be called on an active gameplay session!\n" );
  631. Assert( !m_lobby.m_bXSessionStarted );
  632. }
  633. MMX360_LobbyLeaveMembers( m_pSettings, m_lobby );
  634. {
  635. CX360LobbyFlags_t fl = MMX360_DescribeLobbyFlags( m_pSettings, !!dynamic_cast< CSysSessionHost * >( this ) );
  636. g_pMatchExtensions->GetIXOnline()->XSessionModify( m_lobby.m_hHandle, fl.m_dwFlags, fl.m_numPublicSlots, fl.m_numPrivateSlots, MMX360_NewOverlappedDormant() );
  637. }
  638. MMX360_LobbyJoinMembers( m_pSettings, m_lobby );
  639. }
  640. }
  641. #elif !defined( NO_STEAM )
  642. void CSysSessionBase::UnpackAndReceiveMessage( const void *pvBuffer, int numBytes, bool bValidatedLobbyMember, XUID xuidSrc )
  643. {
  644. if ( numBytes <= 0 )
  645. return;
  646. CUtlBuffer buf( pvBuffer, numBytes, CUtlBuffer::READ_ONLY );
  647. buf.ActivateByteSwapping( !CByteswap::IsMachineBigEndian() );
  648. if ( buf.GetInt() != g_pMatchExtensions->GetINetSupport()->GetEngineBuildNumber() )
  649. return;
  650. KeyValues *msg = new KeyValues( "" );
  651. KeyValues::AutoDelete autodelete( msg );
  652. if ( !msg->ReadAsBinary( buf ) )
  653. return;
  654. // Special case when decoding binary data
  655. static byte chBuffer2[ STEAM_LOBBY_CHAT_MSG_BUFFER_SIZE ];
  656. KeyValues *kvPtr = msg->FindKey( "binary/ptr" );
  657. KeyValues *kvSize = msg->FindKey( "binary/size" );
  658. if ( kvPtr && kvSize )
  659. {
  660. void *pvData = kvPtr->GetPtr();
  661. int nSize = kvSize->GetInt();
  662. if ( nSize < 0 || nSize >= STEAM_LOBBY_CHAT_MSG_BUFFER_SIZE )
  663. return;
  664. if ( pvData && nSize )
  665. {
  666. if ( !buf.Get( chBuffer2, nSize ) )
  667. return;
  668. kvPtr->SetPtr( NULL, chBuffer2 );
  669. }
  670. }
  671. ReceiveMessage( msg, bValidatedLobbyMember, xuidSrc );
  672. }
  673. // We should keep this global since client DLL writes a value here
  674. volatile uint32 *g_hRankingSetupCallHandle = 0;
  675. void CSysSessionBase::SetupSteamRankingConfiguration()
  676. {
  677. KeyValues *kvNotification = new KeyValues( "SetupSteamRankingConfiguration" );
  678. kvNotification->SetPtr( "settingsptr", m_pSettings );
  679. kvNotification->SetPtr( "callhandleptr", ( void * ) &g_hRankingSetupCallHandle );
  680. SendEventsNotification( kvNotification );
  681. }
  682. bool CSysSessionBase::IsSteamRankingConfigured() const
  683. {
  684. return !g_hRankingSetupCallHandle || !*g_hRankingSetupCallHandle;
  685. }
  686. void CSysSessionBase::Steam_OnLobbyChatMsg( LobbyChatMsg_t *pLobbyChatMsg )
  687. {
  688. if ( pLobbyChatMsg->m_ulSteamIDLobby != m_lobby.m_uiLobbyID )
  689. return;
  690. static byte chBuffer[ STEAM_LOBBY_CHAT_MSG_BUFFER_SIZE ];
  691. CSteamID steamIDSender;
  692. EChatEntryType ecet;
  693. int numBytes = steamapicontext->SteamMatchmaking()->GetLobbyChatEntry(
  694. m_lobby.m_uiLobbyID, pLobbyChatMsg->m_iChatID,
  695. &steamIDSender,
  696. chBuffer, sizeof( chBuffer ),
  697. &ecet );
  698. // Is this a validated lobby member?
  699. bool bValidatedLobbyMember = ( SessionMembersFindPlayer( m_pSettings, steamIDSender.ConvertToUint64() ) != NULL );
  700. UnpackAndReceiveMessage( chBuffer, numBytes, bValidatedLobbyMember, steamIDSender.ConvertToUint64() );
  701. }
  702. void CSysSessionBase::Steam_OnLobbyChatUpdate( LobbyChatUpdate_t *pLobbyChatUpdate )
  703. {
  704. if ( pLobbyChatUpdate->m_ulSteamIDLobby != m_lobby.m_uiLobbyID )
  705. return;
  706. if ( BChatMemberStateChangeRemoved( pLobbyChatUpdate->m_rgfChatMemberStateChange ) )
  707. {
  708. XUID xuidLocal = g_pPlayerManager->GetLocalPlayer( XBX_GetPrimaryUserId() )->GetXUID();
  709. if ( pLobbyChatUpdate->m_ulSteamIDUserChanged == xuidLocal )
  710. {
  711. if ( pLobbyChatUpdate->m_ulSteamIDMakingChange != xuidLocal )
  712. {
  713. // Prepare the update notification
  714. KeyValues *kv = new KeyValues( "mmF->SysSessionUpdate" );
  715. kv->SetPtr( "syssession", this );
  716. kv->SetString( "error", "kicked" );
  717. SendEventsNotification( kv );
  718. }
  719. }
  720. else
  721. {
  722. OnPlayerLeave( pLobbyChatUpdate->m_ulSteamIDUserChanged );
  723. }
  724. }
  725. }
  726. void CSysSessionBase::Steam_OnP2PSessionRequest( P2PSessionRequest_t *pParam )
  727. {
  728. uint64 idRemote = pParam->m_steamIDRemote.ConvertToUint64();
  729. if ( m_lobby.m_uiLobbyID && g_pMatchExtensions->GetIVEngineClient() &&
  730. ( !g_pMatchExtensions->GetIVEngineClient()->IsConnected() || g_pMatchExtensions->GetIVEngineClient()->IsClientLocalToActiveServer() ) &&
  731. SessionMembersFindPlayer( m_pSettings, idRemote ) &&
  732. ( !IsPS3() || ( m_lobby.m_eLobbyState == CSteamLobbyObject::STATE_DEFAULT ) ) )
  733. {
  734. // We are in the lobby together, accept P2P session request
  735. steamapicontext->SteamNetworking()->AcceptP2PSessionWithUser( idRemote );
  736. m_bVoiceUsingSessionP2P = true;
  737. }
  738. }
  739. void CSysSessionBase::Steam_OnServersConnected( SteamServersConnected_t *pParam )
  740. {
  741. }
  742. void CSysSessionBase::Steam_OnServersDisconnected( SteamServersDisconnected_t *pParam )
  743. {
  744. // In case we are not in active gameplay we should just drop
  745. // back to main menu while Steam is disconnected
  746. if ( m_lobby.m_eLobbyState == CSteamLobbyObject::STATE_DEFAULT )
  747. {
  748. // Prepare the update notification
  749. KeyValues *kv = new KeyValues( "mmF->SysSessionUpdate" );
  750. kv->SetPtr( "syssession", this );
  751. kv->SetString( "error", "SteamServersDisconnected" );
  752. SendEventsNotification( kv );
  753. return;
  754. }
  755. // If we already got disconnected once prevent re-entry
  756. if ( m_lobby.m_eLobbyState == CSteamLobbyObject::STATE_DISCONNECTED_FROM_STEAM )
  757. return;
  758. m_lobby.m_eLobbyState = CSteamLobbyObject::STATE_DISCONNECTED_FROM_STEAM;
  759. // Otherwise we should manually leave the lobby
  760. steamapicontext->SteamMatchmaking()->LeaveLobby( m_lobby.m_uiLobbyID );
  761. m_lobby.m_uiLobbyID = 0ull;
  762. // set the session lock
  763. m_pSettings->SetString( "system/lock", "SteamServersDisconnected" );
  764. // Check if we can remove all remote players from the session
  765. if ( !V_stricmp( m_pSettings->GetString( "system/netflag" ), "noleave" ) )
  766. {
  767. DevMsg( "CSysSessionBase::Steam_OnServersDisconnected keeping players in noleave mode.\n" );
  768. return;
  769. }
  770. //
  771. // Remove all the remote players from the session
  772. //
  773. XUID xuidLocal = g_pPlayerManager->GetLocalPlayer( XBX_GetPrimaryUserId() )->GetXUID();
  774. KeyValues *pMembers = m_pSettings->FindKey( "members" );
  775. Assert( pMembers );
  776. if ( !pMembers )
  777. return;
  778. int numMachines = pMembers->GetInt( "numMachines", 0 );
  779. for ( int k = 0; k < numMachines; ++ k )
  780. {
  781. KeyValues *kvMachine = pMembers->FindKey( CFmtStr( "machine%d", k ) );
  782. if ( !kvMachine )
  783. continue;
  784. XUID idMachine = kvMachine->GetUint64( "id" );
  785. if ( idMachine == xuidLocal )
  786. {
  787. kvMachine->SetName( "machine0" );
  788. pMembers->SetInt( "numPlayers", kvMachine->GetInt( "numPlayers", 1 ) );
  789. pMembers->SetInt( "numSlots", kvMachine->GetInt( "numPlayers", 1 ) );
  790. }
  791. else
  792. {
  793. pMembers->RemoveSubKey( kvMachine );
  794. kvMachine->deleteThis();
  795. }
  796. }
  797. pMembers->SetInt( "numMachines", 1 );
  798. }
  799. char const * CSysSessionBase::LobbyEnterErrorAsString( LobbyEnter_t *pLobbyEnter )
  800. {
  801. switch ( pLobbyEnter->m_EChatRoomEnterResponse )
  802. {
  803. case k_EChatRoomEnterResponseFull:
  804. return "full";
  805. case k_EChatRoomEnterResponseBanned:
  806. case k_EChatRoomEnterResponseNotAllowed:
  807. return "notwanted";
  808. case k_EChatRoomEnterResponseMemberBlockedYou:
  809. return "blockedyou";
  810. case k_EChatRoomEnterResponseYouBlockedMember:
  811. return "youblocked";
  812. case k_EChatRoomEnterResponseDoesntExist:
  813. return "doesntexist";
  814. case k_EChatRoomEnterResponseRatelimitExceeded:
  815. return "ratelimit";
  816. default:
  817. return "create";
  818. }
  819. }
  820. void CSysSessionBase::LobbySetDataFromKeyValues( char const *szPath, KeyValues *key, bool bRecurse )
  821. {
  822. if ( !key || !szPath )
  823. return;
  824. char chKey[ 256 ];
  825. char chValue[ 256 ];
  826. if ( key->GetDataType() != KeyValues::TYPE_NONE )
  827. {
  828. PrintValue( key, chValue, ARRAYSIZE( chValue ) );
  829. DevMsg( "LobbySetData: '%s' = '%s'\n", szPath, chValue );
  830. steamapicontext->SteamMatchmaking()->SetLobbyData( m_lobby.m_uiLobbyID, szPath, chValue );
  831. }
  832. else for ( KeyValues *sub = key->GetFirstSubKey(); sub; sub = sub->GetNextKey() )
  833. {
  834. if ( !bRecurse && sub->GetDataType() == KeyValues::TYPE_NONE )
  835. continue;
  836. Q_snprintf( chKey, ARRAYSIZE( chKey ), "%s:%s", szPath, sub->GetName() );
  837. if ( Q_stricmp( chKey, "System:dependentlobby" ) == 0 )
  838. {
  839. // set magic dependent lobby something
  840. steamapicontext->SteamMatchmaking()->SetLinkedLobby( m_lobby.m_uiLobbyID, sub->GetUint64() );
  841. }
  842. else
  843. LobbySetDataFromKeyValues( chKey, sub, bRecurse );
  844. }
  845. // Expose lobby members too because Steam doesn't do it for us (lobby owner is first)
  846. if ( !V_stricmp( szPath, "members" ) )
  847. {
  848. CUtlVector< AccountID_t > arrAccounts;
  849. int numMachines = key->GetInt( "numMachines", 0 );
  850. arrAccounts.EnsureCapacity( numMachines + 1 );
  851. arrAccounts.AddToTail( CSteamID( GetHostXuid() ).GetAccountID() );
  852. for ( int k = 0; k < numMachines; ++k )
  853. {
  854. KeyValues *kvMachine = m_pSettings->FindKey( CFmtStr( "members/machine%d", k ) );
  855. if ( kvMachine )
  856. {
  857. uint64 ullMachineID = kvMachine->GetUint64( "id" );
  858. if ( AccountID_t unAccountID = CSteamID( ullMachineID ).GetAccountID() )
  859. {
  860. if ( unAccountID != arrAccounts.Head() )
  861. arrAccounts.AddToTail( unAccountID );
  862. }
  863. }
  864. }
  865. // We are going to write out these bytes as "varints" encoding,
  866. // which also ensures that they can fit in lobby metadata as no
  867. // single byte will be 0x00
  868. const int numBytesStackAlloc = ( 1 + arrAccounts.Count() ) * 5; // 5 bytes max per varint
  869. uint8 * const packedData = ( uint8 * ) stackalloc( numBytesStackAlloc );
  870. uint8 *pWrite = packedData;
  871. FOR_EACH_VEC( arrAccounts, k )
  872. {
  873. uint32 data = arrAccounts[k];
  874. while ( data > 0x7F )
  875. {
  876. *( pWrite ++ ) = uint8( ( data & 0x7F ) | 0x80 );
  877. data >>= 7;
  878. }
  879. Assert( data & 0x7F );
  880. *( pWrite ++ ) = uint8( data & 0x7F );
  881. }
  882. *( pWrite ) = 0; // null-terminator for the "string"
  883. steamapicontext->SteamMatchmaking()->SetLobbyData( m_lobby.m_uiLobbyID, "uids", ( char * ) packedData );
  884. }
  885. }
  886. #endif
  887. void CSysSessionBase::Voice_ProcessTalkers( KeyValues *pMachine, bool bAdd )
  888. {
  889. if ( IsServiceSession() )
  890. return;
  891. if ( !pMachine ) // Process all members as talkers
  892. {
  893. int numMachines = m_pSettings->GetInt( "members/numMachines", 0 );
  894. for ( int k = 0; k < numMachines; ++ k )
  895. {
  896. KeyValues *kvMachine = m_pSettings->FindKey( CFmtStr( "members/machine%d", k ) );
  897. if ( kvMachine )
  898. Voice_ProcessTalkers( kvMachine, bAdd );
  899. }
  900. if ( bAdd && m_Voice_flLastHeadsetStatusCheck < 0 )
  901. m_Voice_flLastHeadsetStatusCheck = 0;
  902. return;
  903. }
  904. XUID xuidMachine = pMachine->GetUint64( "id", 0ull );
  905. if ( !xuidMachine )
  906. return;
  907. int numPlayers = pMachine->GetInt( "numPlayers", 0 );
  908. uint64 uiMachineFlags = pMachine->GetUint64( "flags" );
  909. for ( int k = 0; k < numPlayers; ++ k )
  910. {
  911. XUID xuid = pMachine->GetUint64( CFmtStr( "player%d/xuid", k ), 0ull );
  912. if ( !xuid )
  913. continue;
  914. int iCtrlr = -1;
  915. if ( xuidMachine == m_xuidMachineId )
  916. {
  917. iCtrlr = XBX_GetUserId( k );
  918. xuid = 0ull; // for local users we use only controller index
  919. }
  920. if ( IEngineVoice *pIEngineVoice = g_pMatchExtensions->GetIEngineVoice() )
  921. {
  922. if ( bAdd )
  923. pIEngineVoice->AddPlayerToVoiceList( xuid, iCtrlr, (uiMachineFlags & MACHINE_PLATFORM_PS3) ? ENGINE_VOICE_FLAG_PS3 : 0 );
  924. else
  925. {
  926. pIEngineVoice->RemovePlayerFromVoiceList( xuid, iCtrlr );
  927. #if !defined( NO_STEAM )
  928. // When removing from voice list tear down P2P session
  929. steamapicontext->SteamNetworking()->CloseP2PSessionWithUser( xuid );
  930. #endif
  931. }
  932. }
  933. }
  934. }
  935. void CSysSessionBase::Voice_CaptureAndTransmitLocalVoiceData()
  936. {
  937. if ( IsServiceSession() )
  938. return;
  939. IEngineVoice *v = g_pMatchExtensions->GetIEngineVoice();
  940. if ( !v )
  941. return;
  942. if ( g_pMatchFramework->GetMatchTitle()->GetTitleSettingsFlags() & MATCHTITLE_VOICE_INGAME )
  943. {
  944. #ifdef _X360
  945. if ( m_lobby.m_bXSessionStarted )
  946. return;
  947. #elif !defined( NO_STEAM )
  948. if ( m_lobby.m_eLobbyState != m_lobby.STATE_DEFAULT )
  949. {
  950. if ( IsPS3() && m_bVoiceUsingSessionP2P )
  951. {
  952. m_bVoiceUsingSessionP2P = false;
  953. // As soon as we start loading into a game we should shutdown all P2P communication
  954. for ( int k = 0, numMachines = m_pSettings->GetInt( "members/numMachines" ); k < numMachines; ++ k )
  955. {
  956. for ( int ic = 0, numPlayers = m_pSettings->GetInt( CFmtStr( "members/machine%d/numPlayers", k ) ); ic < numPlayers; ++ ic )
  957. {
  958. XUID xuidPlayer = m_pSettings->GetUint64( CFmtStr( "members/machine%d/player%d/xuid", k, ic ) );
  959. if ( xuidPlayer && xuidPlayer != m_xuidMachineId )
  960. {
  961. steamapicontext->SteamNetworking()->CloseP2PSessionWithUser( xuidPlayer );
  962. DevMsg( "PS3 Session has shut down P2P session with %llx!\n", xuidPlayer );
  963. }
  964. }
  965. }
  966. }
  967. return;
  968. }
  969. #endif
  970. }
  971. for ( DWORD k = 0; k < XBX_GetNumGameUsers(); ++ k )
  972. {
  973. #ifdef _GAMECONSOLE
  974. int iCtrlr = XBX_GetUserId( k );
  975. #else
  976. int iCtrlr = XBX_GetPrimaryUserId();
  977. #endif
  978. if ( v->VoiceUpdateData( iCtrlr ) )
  979. {
  980. // Capture the voice data buffers
  981. const byte *pbVoiceData = NULL;
  982. unsigned int numBytes = 0;
  983. v->GetVoiceData( iCtrlr, &pbVoiceData, &numBytes );
  984. if ( mm_session_voice_loading.GetBool() || !(
  985. g_pMatchExtensions->GetIVEngineClient()->IsDrawingLoadingImage() ||
  986. g_pMatchExtensions->GetIVEngineClient()->IsTransitioningToLoad() ) )
  987. {
  988. // Package it up as a message
  989. KeyValues *msg = new KeyValues( "SysSession::Voice" );
  990. KeyValues::AutoDelete autodelete_msg( msg );
  991. msg->SetString( "p2p", "nodelay" ); // marks the message as preferring p2p transfer
  992. msg->SetUint64( "xuid", g_pPlayerManager->GetLocalPlayer( iCtrlr )->GetXUID() );
  993. unsigned int numBytesRemaining = numBytes, numBytesOffset = 0;
  994. while ( numBytesRemaining > 0 )
  995. {
  996. numBytes = MIN( 1024, numBytesRemaining );
  997. msg->SetPtr( "binary/ptr", numBytesOffset + const_cast< byte * >( pbVoiceData ) );
  998. msg->SetInt( "binary/size", numBytes );
  999. numBytesRemaining -= numBytes;
  1000. numBytesOffset += numBytes;
  1001. // Deliver the message to peers
  1002. SendMessage( msg );
  1003. }
  1004. }
  1005. // Reset voice buffers
  1006. v->VoiceResetLocalData( iCtrlr );
  1007. }
  1008. }
  1009. }
  1010. void CSysSessionBase::Voice_Playback( KeyValues *msg, XUID xuidSrc )
  1011. {
  1012. XUID xuid = msg->GetUint64( "xuid", 0ull );
  1013. Assert( xuid == xuidSrc );
  1014. if ( xuid != xuidSrc )
  1015. return;
  1016. const byte *pbVoiceData = ( const byte * ) msg->GetPtr( "binary/ptr" );
  1017. unsigned int numBytes = msg->GetInt( "binary/size" );
  1018. if ( !pbVoiceData || !numBytes )
  1019. // We cannot playback the data or muting the player
  1020. return;
  1021. if ( mm_session_voice_loading.GetBool() || !(
  1022. g_pMatchExtensions->GetIVEngineClient()->IsDrawingLoadingImage() ||
  1023. g_pMatchExtensions->GetIVEngineClient()->IsTransitioningToLoad() ) )
  1024. {
  1025. g_pMatchExtensions->GetIEngineVoice()->PlayIncomingVoiceData( xuid, pbVoiceData, numBytes );
  1026. if ( g_pMatchFramework->GetMatchSystem()->GetMatchVoice()->CanPlaybackTalker( xuid ) )
  1027. {
  1028. if ( KeyValues *notify = new KeyValues( "OnPlayerActivity", "act", "voice" ) )
  1029. {
  1030. notify->SetUint64( "xuid", xuid );
  1031. OnSessionEvent( notify );
  1032. }
  1033. }
  1034. }
  1035. }
  1036. void CSysSessionBase::Voice_UpdateLocalHeadsetsStatus()
  1037. {
  1038. if ( IsServiceSession() )
  1039. return;
  1040. if ( m_Voice_flLastHeadsetStatusCheck < 0 )
  1041. return;
  1042. // Not too frequently check for the headset status changes
  1043. if ( Plat_FloatTime() - m_Voice_flLastHeadsetStatusCheck < 1.0f )
  1044. return;
  1045. m_Voice_flLastHeadsetStatusCheck = Plat_FloatTime();
  1046. // Find the local machine
  1047. KeyValues *pMachine = NULL;
  1048. SessionMembersFindPlayer( m_pSettings, m_xuidMachineId, &pMachine );
  1049. if ( !pMachine )
  1050. return;
  1051. // Whether current status is different from session settings
  1052. for ( uint k = 0; k < XBX_GetNumGameUsers(); ++ k )
  1053. {
  1054. bool bHeadset = false;
  1055. #ifdef _GAMECONSOLE
  1056. int iCtrlr = XBX_GetUserId( k );
  1057. if ( !XBX_GetUserIsGuest( k ) )
  1058. bHeadset = g_pMatchExtensions->GetIEngineVoice()->IsHeadsetPresent( iCtrlr );
  1059. #elif !defined( NO_STEAM )
  1060. bHeadset = g_pMatchExtensions->GetIEngineVoice()->IsHeadsetPresent( XBX_GetPrimaryUserId() );
  1061. #endif
  1062. char const *szCurValue = pMachine->GetString( CFmtStr( "player%d/voice", k ), "" );
  1063. char const *szHeadsetValue = bHeadset ? "headset" : "";
  1064. if ( szCurValue[0] != szHeadsetValue[0] )
  1065. {
  1066. KeyValues *msg = new KeyValues( "SysSession::VoiceStatus" );
  1067. KeyValues::AutoDelete autodelete_msg( msg );
  1068. XUID xuid = pMachine->GetUint64( CFmtStr( "player%d/xuid", k ), 0ull );
  1069. msg->SetUint64( "xuid", xuid );
  1070. msg->SetString( "voice", szHeadsetValue );
  1071. SendMessage( msg );
  1072. }
  1073. }
  1074. }
  1075. void CSysSessionBase::Voice_UpdateMutelist()
  1076. {
  1077. if ( IsServiceSession() )
  1078. return;
  1079. // Generate the mutelist and send it if it is different from the current settings
  1080. KeyValues *msg = new KeyValues( "SysSession::VoiceMutelist" );
  1081. KeyValues::AutoDelete autodelete_msg( msg );
  1082. msg->SetUint64( "xuid", m_xuidMachineId );
  1083. if ( KeyValues *pMembers = m_pSettings ? m_pSettings->FindKey( "members" ) : NULL )
  1084. {
  1085. int numMachines = pMembers->GetInt( "numMachines" );
  1086. for ( int i = 0; i < numMachines; ++ i )
  1087. {
  1088. XUID xuid = pMembers->GetUint64( CFmtStr( "machine%d/id", i ) );
  1089. if ( g_pMatchVoice->IsMachineMuted( xuid ) )
  1090. msg->SetUint64( CFmtStr( "Mutelist/%d", i ), xuid );
  1091. }
  1092. }
  1093. // Find current mutelist
  1094. KeyValues *pLocalMachine = NULL;
  1095. SessionMembersFindPlayer( m_pSettings, m_xuidMachineId, &pLocalMachine );
  1096. if ( pLocalMachine )
  1097. {
  1098. KeyValues *pCurMutelist = pLocalMachine->FindKey( "Mutelist" );
  1099. KeyValues *pNewMutelist = msg->FindKey( "Mutelist" );
  1100. if ( pCurMutelist && pNewMutelist )
  1101. {
  1102. for ( pCurMutelist = pCurMutelist->GetFirstValue(),
  1103. pNewMutelist = pNewMutelist->GetFirstValue();
  1104. pCurMutelist && pNewMutelist;
  1105. pCurMutelist = pCurMutelist->GetNextValue(),
  1106. pNewMutelist = pNewMutelist->GetNextValue() )
  1107. {
  1108. if ( pCurMutelist->GetUint64() != pNewMutelist->GetUint64() )
  1109. break;
  1110. }
  1111. }
  1112. if ( !pCurMutelist && !pNewMutelist )
  1113. // current and new mutelists compared equal
  1114. return;
  1115. }
  1116. SendMessage( msg );
  1117. }
  1118. void CSysSessionBase::OnPlayerLeave( XUID xuid )
  1119. {
  1120. }
  1121. bool CSysSessionBase::FindAndRemovePlayerFromMembers( XUID xuid )
  1122. {
  1123. KeyValues *pMembers = m_pSettings->FindKey( "members" );
  1124. Assert( pMembers );
  1125. if ( !pMembers )
  1126. return false;
  1127. int numMachines = pMembers->GetInt( "numMachines", 0 );
  1128. int numPlayers = pMembers->GetInt( "numPlayers", 0 );
  1129. for ( int k = 0; k < numMachines; ++ k )
  1130. {
  1131. KeyValues *pMachine = pMembers->FindKey( CFmtStr( "machine%d", k ) );
  1132. if ( !pMachine )
  1133. continue;
  1134. int numOtherPlayers = pMachine->GetInt( "numPlayers", 0 );
  1135. for ( int j = 0; j < numOtherPlayers; ++ j )
  1136. {
  1137. XUID xuidOtherPlayer = pMachine->GetUint64( CFmtStr( "player%d/xuid", j ), 0ull );
  1138. if ( xuidOtherPlayer == xuid )
  1139. {
  1140. #ifdef _X360
  1141. // On X360 we need to update lobby members server-side count
  1142. MMX360_LobbyLeaveMembers( m_pSettings, m_lobby, k, k );
  1143. #endif
  1144. // We also need to remove talkers
  1145. Voice_ProcessTalkers( pMachine, false );
  1146. // The entire machine will be effectively disconnected
  1147. KeyValues::AutoDelete autodelete( pMachine );
  1148. pMembers->RemoveSubKey( pMachine );
  1149. for ( int kk = k + 1; kk < numMachines; ++ kk )
  1150. {
  1151. KeyValues *pNextMachine = pMembers->FindKey( CFmtStr( "machine%d", kk ) );
  1152. if ( !pNextMachine )
  1153. continue;
  1154. pNextMachine->SetName( CFmtStr( "machine%d", kk - 1 ) );
  1155. }
  1156. // Update counts
  1157. numMachines = MAX( 0, numMachines - 1 );
  1158. pMembers->SetInt( "numMachines", numMachines );
  1159. numPlayers = MAX( 0, numPlayers - numOtherPlayers );
  1160. pMembers->SetInt( "numPlayers", numPlayers );
  1161. // Now fire the events for the removed players
  1162. for ( j = 0; j < numOtherPlayers; ++ j )
  1163. {
  1164. KeyValues *pPlayer = pMachine->FindKey( CFmtStr( "player%d", j ) );
  1165. xuidOtherPlayer = pPlayer->GetUint64( "xuid", 0ull );
  1166. KeyValues *kv = new KeyValues( "OnPlayerRemoved" );
  1167. kv->SetUint64( "xuid", xuidOtherPlayer );
  1168. if ( pPlayer )
  1169. {
  1170. KeyValues *pCopyPlayer = pPlayer->MakeCopy();
  1171. pCopyPlayer->SetName( "player" );
  1172. kv->AddSubKey( pCopyPlayer );
  1173. }
  1174. if ( pMachine )
  1175. {
  1176. KeyValues *pCopyMachine = pMachine->MakeCopy();
  1177. pCopyMachine->SetName( "machine" );
  1178. kv->AddSubKey( pCopyMachine );
  1179. }
  1180. OnSessionEvent( kv );
  1181. }
  1182. #ifdef _X360
  1183. #elif !defined( NO_STEAM )
  1184. if ( dynamic_cast< CSysSessionHost * >( this ) )
  1185. {
  1186. // Update members information
  1187. LobbySetDataFromKeyValues( "members", m_pSettings->FindKey( "Members" ), false );
  1188. }
  1189. #endif
  1190. return true;
  1191. }
  1192. }
  1193. }
  1194. return false;
  1195. }
  1196. void CSysSessionBase::UpdateSessionProperties( KeyValues *kv, bool bHost )
  1197. {
  1198. #if !defined( NO_STEAM )
  1199. SetupSteamRankingConfiguration();
  1200. #endif
  1201. if ( !IsX360() && !bHost )
  1202. // On X360 every client must set their contexts, on PC it's
  1203. // all Steam-server-side and only host sets the metadata
  1204. return;
  1205. if ( IsX360() )
  1206. return;
  1207. #ifdef _X360
  1208. #elif !defined( NO_STEAM )
  1209. if ( !m_lobby.m_uiLobbyID )
  1210. return;
  1211. if ( kv )
  1212. {
  1213. LobbySetDataFromKeyValues( "system", kv->FindKey( "system" ) );
  1214. LobbySetDataFromKeyValues( "game", kv->FindKey( "game" ) );
  1215. LobbySetDataFromKeyValues( "options", kv->FindKey( "options" ) );
  1216. }
  1217. #endif
  1218. }
  1219. void CSysSessionBase::SetSessionActiveGameplayState( bool bActive, char const *szSecureServerAddress )
  1220. {
  1221. #ifdef _X360
  1222. MMX360_LobbySetActiveGameplayState( m_lobby, bActive, szSecureServerAddress );
  1223. #elif !defined( NO_STEAM )
  1224. switch ( m_lobby.m_eLobbyState )
  1225. {
  1226. case CSteamLobbyObject::STATE_DEFAULT:
  1227. if ( bActive )
  1228. {
  1229. m_lobby.m_eLobbyState = CSteamLobbyObject::STATE_ACTIVE_GAME;
  1230. }
  1231. break;
  1232. case CSteamLobbyObject::STATE_ACTIVE_GAME:
  1233. if ( !bActive )
  1234. {
  1235. m_lobby.m_eLobbyState = CSteamLobbyObject::STATE_DEFAULT;
  1236. }
  1237. break;
  1238. case CSteamLobbyObject::STATE_DISCONNECTED_FROM_STEAM:
  1239. if ( !bActive )
  1240. {
  1241. // If active gameplay session has ended and we were disconnected from Steam
  1242. // then go back to main menu
  1243. m_lobby.m_eLobbyState = CSteamLobbyObject::STATE_DEFAULT;
  1244. Steam_OnServersDisconnected( NULL );
  1245. }
  1246. break;
  1247. }
  1248. #endif
  1249. }
  1250. void CSysSessionBase::UpdateTeamProperties( KeyValues *pTeamProperties )
  1251. {
  1252. #if defined (_X360)
  1253. #elif !defined(NO_STEAM)
  1254. if ( !m_lobby.m_uiLobbyID )
  1255. return;
  1256. LobbySetDataFromKeyValues( "members", pTeamProperties->FindKey( "members" ) );
  1257. #endif
  1258. }
  1259. void CSysSessionBase::UpdateServerInfo( KeyValues *pServerKey )
  1260. {
  1261. #if defined (_X360)
  1262. #elif !defined(NO_STEAM)
  1263. LobbySetDataFromKeyValues( "server", pServerKey->FindKey( "server" ) );
  1264. #endif
  1265. }
  1266. void CSysSessionBase::PrintValue( KeyValues *val, char *chBuffer, int numBytesBuffer )
  1267. {
  1268. switch( val->GetDataType() )
  1269. {
  1270. case KeyValues::TYPE_INT:
  1271. Q_snprintf( chBuffer, numBytesBuffer, "%d", val->GetInt() );
  1272. break;
  1273. case KeyValues::TYPE_UINT64:
  1274. Q_snprintf( chBuffer, numBytesBuffer, "%llX", val->GetUint64() );
  1275. break;
  1276. case KeyValues::TYPE_FLOAT:
  1277. Q_snprintf( chBuffer, numBytesBuffer, "%f", val->GetFloat() );
  1278. break;
  1279. case KeyValues::TYPE_STRING:
  1280. Q_snprintf( chBuffer, numBytesBuffer, "%s", val->GetString() );
  1281. break;
  1282. default:
  1283. Warning( "Unknown type in CSysSessionHost::PrintValue ( %s )\n", val->GetName() );
  1284. break;
  1285. }
  1286. }
  1287. CSysSessionHost::CSysSessionHost( KeyValues *pSettings ) :
  1288. CSysSessionBase( pSettings ),
  1289. #ifdef _X360
  1290. #elif !defined( NO_STEAM )
  1291. m_dblDormantMembersCheckTime( Plat_FloatTime() ),
  1292. m_numDormantMembersDetected( 0 ),
  1293. #endif
  1294. m_eState( STATE_INIT ),
  1295. m_flTimeOperationStarted( 0.0f ),
  1296. m_flInitializeTimestamp( 0.0f ),
  1297. m_teamResKey( 0ull ),
  1298. m_numRemainingTeamPlayers( 0 ),
  1299. m_flTeamResStartTime( 0.0f ),
  1300. m_ullCrypt( 0 )
  1301. {
  1302. }
  1303. CSysSessionHost::CSysSessionHost( CSysSessionClient *pClient, KeyValues *pSettings ) :
  1304. CSysSessionBase( pSettings ),
  1305. #ifdef _X360
  1306. #elif !defined( NO_STEAM )
  1307. m_dblDormantMembersCheckTime( Plat_FloatTime() ),
  1308. m_numDormantMembersDetected( 0 ),
  1309. #endif
  1310. m_eState( STATE_IDLE ),
  1311. m_flTimeOperationStarted( 0.0f ),
  1312. m_flInitializeTimestamp( 0.0f ),
  1313. m_teamResKey( 0ull ),
  1314. m_numRemainingTeamPlayers( 0 ),
  1315. m_flTeamResStartTime( 0.0f ),
  1316. m_ullCrypt( 0 )
  1317. {
  1318. m_lobby = pClient->m_lobby;
  1319. m_Voice_flLastHeadsetStatusCheck = pClient->m_Voice_flLastHeadsetStatusCheck;
  1320. #ifdef _X360
  1321. m_pNetworkMgr = pClient->m_pNetworkMgr;
  1322. if ( m_pNetworkMgr )
  1323. m_pNetworkMgr->SetListener( this );
  1324. m_pAsyncOperation = pClient->m_pAsyncOperation;
  1325. Assert( !m_pAsyncOperation );
  1326. // If client was in migrate state, then disassociate the migrate call listener
  1327. if ( pClient->m_hLobbyMigrateCall )
  1328. {
  1329. MMX360_LobbyMigrateSetListener( pClient->m_hLobbyMigrateCall, NULL );
  1330. pClient->m_hLobbyMigrateCall = NULL;
  1331. }
  1332. // Schedule the host migration call
  1333. m_hLobbyMigrateCall = MMX360_LobbyMigrateHost( m_lobby, &m_MigrateCallState );
  1334. if ( !m_hLobbyMigrateCall )
  1335. {
  1336. // This is a dangerous notification because we are in the tree of constructors as
  1337. // follows: CMatchSessionOnlineHost -> CSysSessionHost
  1338. // The guideline to avoid troubles is to make sure that no code runs in the
  1339. // constructors of CMatchSessionOnlineHost and CSysSessionHost when the notification
  1340. // is fired.
  1341. KeyValues *kv = new KeyValues( "mmF->SysSessionUpdate" );
  1342. kv->SetPtr( "syssession", this );
  1343. kv->SetString( "error", "migrate" );
  1344. // Inside this event broadcast our session will be deleted
  1345. SendEventsNotification( kv );
  1346. return;
  1347. }
  1348. #elif !defined( NO_STEAM )
  1349. // Install callback for messages
  1350. m_CallbackOnLobbyChatMsg.Register( this, &CSysSessionBase::Steam_OnLobbyChatMsg );
  1351. m_CallbackOnLobbyChatUpdate.Register( this, &CSysSessionBase::Steam_OnLobbyChatUpdate );
  1352. // Set the migrated members information
  1353. LobbySetDataFromKeyValues( "members", m_pSettings->FindKey( "members" ), false );
  1354. #endif
  1355. // Send a notification
  1356. KeyValues *kvEvent = new KeyValues( "OnPlayerLeaderChanged" );
  1357. kvEvent->SetString( "state", "host" );
  1358. kvEvent->SetUint64( "xuid", m_xuidMachineId );
  1359. #ifdef _X360
  1360. kvEvent->SetString( "migrate", "started" );
  1361. #endif
  1362. SendEventsNotification( kvEvent );
  1363. }
  1364. CSysSessionHost::~CSysSessionHost()
  1365. {
  1366. ;
  1367. }
  1368. bool CSysSessionHost::Update()
  1369. {
  1370. if ( !CSysSessionBase::Update() )
  1371. return false;
  1372. switch( m_eState )
  1373. {
  1374. case STATE_INIT:
  1375. if ( !m_flInitializeTimestamp )
  1376. {
  1377. m_flInitializeTimestamp = Plat_FloatTime();
  1378. #if !defined( NO_STEAM )
  1379. SetupSteamRankingConfiguration();
  1380. #endif
  1381. }
  1382. if (
  1383. #if !defined( NO_STEAM )
  1384. ( IsSteamRankingConfigured() || ( Plat_FloatTime() >= m_flInitializeTimestamp + mm_session_sys_ranking_timeout.GetFloat() ) ) &&
  1385. #endif
  1386. ( Plat_FloatTime() >= m_flInitializeTimestamp + mm_session_sys_delay_create_host.GetFloat() ) &&
  1387. SysSession_AllowCreate()
  1388. )
  1389. {
  1390. UpdateStateInit();
  1391. }
  1392. break;
  1393. #if !defined( NO_STEAM )
  1394. case STATE_IDLE:
  1395. // 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
  1396. if ( m_lobby.m_uiLobbyID )
  1397. {
  1398. double dblTimeNow = Plat_FloatTime();
  1399. if ( dblTimeNow - m_dblDormantMembersCheckTime >= 15.0 ) // check every 15 seconds
  1400. {
  1401. m_dblDormantMembersCheckTime = dblTimeNow;
  1402. int numMembers = steamapicontext->SteamMatchmaking()->GetNumLobbyMembers( m_lobby.m_uiLobbyID );
  1403. int numDormantMembers = 0;
  1404. int nLimit = steamapicontext->SteamMatchmaking()->GetLobbyMemberLimit( m_lobby.m_uiLobbyID );
  1405. for ( int k = 0; nLimit && ( k < numMembers ); ++k )
  1406. {
  1407. XUID xuidMember = steamapicontext->SteamMatchmaking()->GetLobbyMemberByIndex( m_lobby.m_uiLobbyID, k ).ConvertToUint64();
  1408. if ( !xuidMember )
  1409. continue;
  1410. // Check if this player is in the KV session
  1411. KeyValues *kvPlayer = SessionMembersFindPlayer( m_pSettings, xuidMember );
  1412. if ( kvPlayer )
  1413. continue; // member is properly authenticated, this is the common case
  1414. ++ numDormantMembers;
  1415. }
  1416. m_numDormantMembersDetected = numDormantMembers;
  1417. int nNewLimit = m_numDormantMembersDetected + m_pSettings->GetInt( "members/numSlots", 1 );
  1418. if ( nLimit && nNewLimit != nLimit )
  1419. {
  1420. steamapicontext->SteamMatchmaking()->SetLobbyMemberLimit( m_lobby.m_uiLobbyID, nNewLimit );
  1421. }
  1422. }
  1423. }
  1424. break;
  1425. #endif
  1426. #ifdef _X360
  1427. case STATE_ALLOWING_MIGRATE:
  1428. if ( mm_session_sys_timeout.GetFloat() + m_flTimeOperationStarted < Plat_FloatTime() )
  1429. {
  1430. // Assume that migration failed or we lost connection to Xbox LIVE
  1431. DestroyAfterMigrationFinished();
  1432. }
  1433. break;
  1434. #endif
  1435. }
  1436. // Update reservation status
  1437. if ( m_teamResKey )
  1438. {
  1439. float time = Plat_FloatTime();
  1440. if ( time >= m_flTeamResStartTime + mm_session_team_res_timeout.GetFloat() )
  1441. {
  1442. UnreserveTeamSession();
  1443. }
  1444. }
  1445. return true;
  1446. }
  1447. void CSysSessionHost::Destroy()
  1448. {
  1449. // If we are migrating, then don't let the
  1450. // base class handle it because it will
  1451. // post Quit messages and leave the lobby...
  1452. if ( m_eState == STATE_MIGRATE )
  1453. {
  1454. delete this;
  1455. return;
  1456. }
  1457. #ifdef _X360
  1458. if ( m_eState == STATE_DELETE )
  1459. {
  1460. delete this;
  1461. return;
  1462. }
  1463. else if ( ShouldAllowX360HostMigration() )
  1464. {
  1465. m_eState = STATE_ALLOWING_MIGRATE;
  1466. m_flTimeOperationStarted = Plat_FloatTime();
  1467. }
  1468. else
  1469. {
  1470. m_eState = STATE_DELETE;
  1471. }
  1472. #endif
  1473. // Chain to base which will "delete this"
  1474. CSysSessionBase::Destroy();
  1475. }
  1476. void CSysSessionHost::DebugPrint()
  1477. {
  1478. DevMsg( "CSysSessionHost [ state=%d ]\n", m_eState );
  1479. CSysSessionBase::DebugPrint();
  1480. }
  1481. XUID CSysSessionHost::GetHostXuid( XUID xuidValidResult )
  1482. {
  1483. #ifdef _X360
  1484. return m_xuidMachineId;
  1485. #elif !defined( NO_STEAM )
  1486. return m_lobby.m_uiLobbyID ? steamapicontext->SteamMatchmaking()
  1487. ->GetLobbyOwner( m_lobby.m_uiLobbyID ).ConvertToUint64() : m_xuidMachineId;
  1488. #endif
  1489. return 0ull;
  1490. }
  1491. #ifdef _X360
  1492. void CSysSessionHost::GetHostSessionInfo( char chBuffer[ XSESSION_INFO_STRING_LENGTH ] )
  1493. {
  1494. MMX360_SessionInfoToString( m_lobby.m_xiInfo, chBuffer );
  1495. }
  1496. uint64 CSysSessionHost::GetHostSessionId()
  1497. {
  1498. return (const uint64&)(m_lobby.m_xiInfo.sessionID);
  1499. }
  1500. #endif
  1501. void CSysSessionHost::KickPlayer( KeyValues *pCommand )
  1502. {
  1503. XUID xuid = pCommand->GetUint64( "xuid", 0ull );
  1504. // Locate the machine being kicked
  1505. KeyValues *pMachine = NULL;
  1506. SessionMembersFindPlayer( m_pSettings, xuid, &pMachine );
  1507. if ( !pMachine )
  1508. return;
  1509. // Use machine primary xuid
  1510. xuid = pMachine->GetUint64( "id" );
  1511. if ( xuid == GetHostXuid() )
  1512. {
  1513. DevWarning( "CSysSessionHost::Kick unsupported for host xuid!\n" );
  1514. return;
  1515. }
  1516. // Notify everybody that a machine is being kicked
  1517. KeyValues *notify = new KeyValues( "SysSession::OnPlayerKicked" );
  1518. KeyValues::AutoDelete autodelete_notify( notify );
  1519. notify->SetUint64( "xuid", xuid );
  1520. SendMessage( notify );
  1521. // Remove player information from our records
  1522. FindAndRemovePlayerFromMembers( xuid );
  1523. // Remember the player in our kicked players map
  1524. m_mapKickedPlayers.InsertOrReplace( xuid, Plat_FloatTime() );
  1525. #if !defined( _X360 ) && !defined( NO_STEAM )
  1526. // Forcefully kick the player from the lobby
  1527. // steamapicontext->SteamMatchmaking()->???
  1528. // On X360 we just forcefully shutdown that client's
  1529. // network channel and all peers do the same, so the kicked
  1530. // client is indeed kicked no matter how smartly he tries to stay.
  1531. #endif
  1532. }
  1533. void CSysSessionHost::OnUpdateSessionSettings( KeyValues *kv )
  1534. {
  1535. KeyValues *kvPropertiesUpdated = kv->FindKey( "update", false );
  1536. if ( !kvPropertiesUpdated )
  1537. kvPropertiesUpdated = kv->FindKey( "delete", false );
  1538. UpdateSessionProperties( kvPropertiesUpdated );
  1539. // Now send the message to everybody about the session update
  1540. KeyValues *notify = new KeyValues( "SysSession::OnUpdate" );
  1541. KeyValues::AutoDelete autodelete_notify( notify );
  1542. if ( KeyValues *kvUpdate = kv->FindKey( "update" ) )
  1543. notify->AddSubKey( kvUpdate->MakeCopy() );
  1544. if ( KeyValues *kvDelete = kv->FindKey( "delete" ) )
  1545. notify->AddSubKey( kvDelete->MakeCopy() );
  1546. SendMessage( notify );
  1547. }
  1548. void CSysSessionHost::OnPlayerUpdated( KeyValues *pPlayer )
  1549. {
  1550. // Notify the framework about player updated
  1551. KeyValues *kvEvent = new KeyValues( "OnPlayerUpdated" );
  1552. kvEvent->SetUint64( "xuid", pPlayer->GetUint64( "xuid" ) );
  1553. kvEvent->SetPtr( "player", pPlayer );
  1554. OnSessionEvent( kvEvent );
  1555. // Now send the message
  1556. KeyValues *notify = new KeyValues( "SysSession::OnPlayerUpdated" );
  1557. KeyValues::AutoDelete autodelete( notify );
  1558. notify->MergeFrom( pPlayer, KeyValues::MERGE_KV_UPDATE );
  1559. SendMessage( notify );
  1560. }
  1561. void CSysSessionHost::OnMachineUpdated( KeyValues *pMachine )
  1562. {
  1563. // Send the message to peers
  1564. KeyValues *notify = new KeyValues( "SysSession::OnMachineUpdated" );
  1565. KeyValues::AutoDelete autodelete( notify );
  1566. notify->MergeFrom( pMachine, KeyValues::MERGE_KV_UPDATE );
  1567. SendMessage( notify );
  1568. }
  1569. void CSysSessionHost::UpdateStateInit()
  1570. {
  1571. #ifdef _X360
  1572. MMX360_LobbyCreate( m_pSettings, &m_pAsyncOperation );
  1573. #elif !defined( NO_STEAM )
  1574. ELobbyType eType = k_ELobbyTypeFriendsOnly;
  1575. int numSlots = m_pSettings->GetInt( "members/numSlots", 1 );
  1576. SteamAPICall_t hCall = steamapicontext->SteamMatchmaking()->CreateLobby( eType, numSlots );
  1577. m_CallbackOnLobbyCreated.Set( hCall, this, &CSysSessionHost::Steam_OnLobbyCreated );
  1578. #endif
  1579. m_eState = STATE_CREATING;
  1580. }
  1581. #ifdef _X360
  1582. void CSysSessionHost::OnAsyncOperationFinished()
  1583. {
  1584. if ( m_eState == STATE_CREATING )
  1585. {
  1586. if ( m_pAsyncOperation->GetState() != AOS_SUCCEEDED )
  1587. {
  1588. Warning( "CSysSessionHost: CreateSession failed. Error %d\n", m_pAsyncOperation->GetResult() );
  1589. ReleaseAsyncOperation();
  1590. m_eState = STATE_FAIL;
  1591. KeyValues *kv = new KeyValues( "mmF->SysSessionUpdate" );
  1592. kv->SetPtr( "syssession", this );
  1593. kv->SetString( "error", "create" );
  1594. OnSessionEvent( kv );
  1595. return;
  1596. }
  1597. //
  1598. // We have successfully created the lobby,
  1599. // retrieve all information from the async creation.
  1600. //
  1601. m_lobby = m_pAsyncOperation->GetLobby();
  1602. ReleaseAsyncOperation();
  1603. // Expose the NONCE for all future clients of the session
  1604. m_pSettings->SetUint64( "system/nonce", m_lobby.m_uiNonce );
  1605. // Setup our xnaddr
  1606. char chXnaddr[ XNADDR_STRING_LENGTH ] = {0};
  1607. MMX360_XnaddrToString( m_lobby.m_xiInfo.hostAddress, chXnaddr );
  1608. m_pSettings->SetString( "members/machine0/xnaddr", chXnaddr );
  1609. InitSessionProperties();
  1610. // Create the network mgr
  1611. m_pNetworkMgr = new CX360NetworkMgr( this, GetX360NetSocket() );
  1612. // Setup local Xbox 360 voice
  1613. Voice_ProcessTalkers( NULL, true );
  1614. m_eState = STATE_IDLE;
  1615. KeyValues *kv = new KeyValues( "mmF->SysSessionUpdate" );
  1616. kv->SetPtr( "syssession", this );
  1617. OnSessionEvent( kv );
  1618. return;
  1619. }
  1620. else if ( m_eState == STATE_DELETE )
  1621. {
  1622. ReleaseAsyncOperation();
  1623. SysSession360_RegisterPending( this );
  1624. }
  1625. else
  1626. {
  1627. ReleaseAsyncOperation();
  1628. }
  1629. }
  1630. void CSysSessionHost::OnX360NetDisconnected( XUID xuidRemote )
  1631. {
  1632. if ( m_eState == STATE_DELETE || m_eState == STATE_ALLOWING_MIGRATE )
  1633. return;
  1634. CSysSessionBase::OnX360NetDisconnected( xuidRemote );
  1635. }
  1636. bool CSysSessionHost::OnX360NetConnectionlessPacket( netpacket_t *pkt, KeyValues *msg )
  1637. {
  1638. if ( !Q_stricmp( msg->GetName(), "SysSession::TeamReservation" ) )
  1639. {
  1640. XUID key = msg->GetUint64( "teamResKey" );
  1641. int teamSize = msg->GetInt( "numPlayers" );
  1642. DevMsg( "Received reservation msg: res key = %llx, numPlayers = %d\n", key, teamSize );
  1643. Process_TeamReservation( key, teamSize );
  1644. return true;
  1645. }
  1646. if ( m_eState == STATE_IDLE && !Q_stricmp( msg->GetName(), "SysSession::RequestJoinData" ) )
  1647. {
  1648. // Check sessionid the client is trying to connect to
  1649. uint64 uiSessionIdRequest = msg->GetUint64( "sessionid" );
  1650. if ( uiSessionIdRequest != ( const uint64 & ) m_lobby.m_xiInfo.sessionID )
  1651. return false;
  1652. // This is a legit connectionless packet requesting to join the session
  1653. XUID machineid = msg->GetUint64( "id" );
  1654. XNKID xnkidSession = m_lobby.m_xiInfo.sessionID;
  1655. if ( !m_pNetworkMgr->ConnectionPeerOpenPassive( machineid, pkt, &xnkidSession ) )
  1656. {
  1657. DevWarning( "CSysSessionHost discarding SysSession::RequestJoinData due to passive connection failure!\n" );
  1658. return false;
  1659. }
  1660. if ( !Process_RequestJoinData( machineid, msg->FindKey( "settings" ) ) )
  1661. m_pNetworkMgr->ConnectionPeerClose( machineid );
  1662. return true;
  1663. }
  1664. // Unknown packet, permanently block sender
  1665. return false;
  1666. }
  1667. void CSysSessionHost::DestroyAfterMigrationFinished()
  1668. {
  1669. // Picking up slack from CSysSessionBase::Destroy scenario
  1670. if ( m_pNetworkMgr )
  1671. {
  1672. m_pNetworkMgr->Destroy();
  1673. m_pNetworkMgr = NULL;
  1674. }
  1675. m_eState = STATE_DELETE;
  1676. MMX360_LobbyDelete( m_lobby, &m_pAsyncOperation );
  1677. }
  1678. #endif
  1679. void CSysSessionHost::ReceiveMessage( KeyValues *msg, bool bValidatedLobbyMember, XUID xuidSrc )
  1680. {
  1681. char const *szMsg = msg->GetName();
  1682. // Parse the message depending on our state
  1683. switch ( m_eState )
  1684. {
  1685. case STATE_IDLE:
  1686. if ( !Q_stricmp( "SysSession::RequestJoinData", szMsg ) )
  1687. {
  1688. XUID machineid = msg->GetUint64( "id" );
  1689. KeyValues *pSettings = msg->FindKey( "settings" );
  1690. if ( machineid != xuidSrc )
  1691. return;
  1692. Process_RequestJoinData( xuidSrc, pSettings );
  1693. }
  1694. else if ( !bValidatedLobbyMember )
  1695. {
  1696. return;
  1697. }
  1698. else if ( !Q_stricmp( "SysSession::VoiceStatus", szMsg ) )
  1699. {
  1700. Process_VoiceStatus( msg, xuidSrc );
  1701. }
  1702. #ifdef _X360 // (CS:GO 2017) -- these are old X360 flows that we no longer care to support
  1703. else if ( !Q_stricmp( "SysSession::VoiceMutelist", szMsg ) )
  1704. {
  1705. Process_VoiceMutelist( msg );
  1706. }
  1707. else if ( !Q_stricmp( "SysSession::TeamReservation", szMsg ) )
  1708. {
  1709. XUID key = msg->GetUint64( "teamResKey" );
  1710. int teamSize = msg->GetInt( "numPlayers" );
  1711. Process_TeamReservation( key, teamSize );
  1712. }
  1713. #endif
  1714. else
  1715. {
  1716. CSysSessionBase::ReceiveMessage( msg, bValidatedLobbyMember, xuidSrc );
  1717. }
  1718. return;
  1719. #ifdef _X360
  1720. case STATE_ALLOWING_MIGRATE:
  1721. if ( !Q_stricmp( szMsg, "SysSession::HostMigrated" ) )
  1722. {
  1723. // another peer picked up the session, bail out
  1724. DestroyAfterMigrationFinished();
  1725. }
  1726. return;
  1727. #endif
  1728. }
  1729. }
  1730. void CSysSessionHost::Migrate( KeyValues *pCommand )
  1731. {
  1732. if ( Q_stricmp( pCommand->GetString( "migrate" ), "host>client" ) )
  1733. {
  1734. Assert( 0 );
  1735. return;
  1736. }
  1737. #ifndef NO_STEAM
  1738. Verify( steamapicontext->SteamMatchmaking()->SetLobbyOwner( m_lobby.m_uiLobbyID, pCommand->GetUint64( "xuid" ) ) );
  1739. // Prepare the update notification
  1740. KeyValues *kv = new KeyValues( "mmF->SysSessionUpdate" );
  1741. kv->SetPtr( "syssession", this );
  1742. m_eState = STATE_MIGRATE;
  1743. kv->SetString( "action", "client" );
  1744. // Inside this event broadcast our session will be deleted
  1745. SendEventsNotification( kv );
  1746. #endif
  1747. }
  1748. void CSysSessionHost::OnPlayerLeave( XUID xuid )
  1749. {
  1750. // We detected that the player dropped out of the lobby,
  1751. // disconnect the player from the session
  1752. #if !defined( NO_STEAM )
  1753. if ( !V_stricmp( m_pSettings->GetString( "system/netflag" ), "noleave" ) )
  1754. {
  1755. DevMsg( "CSysSessionHost::OnPlayerLeave(%llx) ignored in noleave mode.\n", xuid );
  1756. return;
  1757. }
  1758. #endif
  1759. if ( FindAndRemovePlayerFromMembers( xuid ) )
  1760. {
  1761. // Now send the message to everybody about the session update
  1762. KeyValues *reply = new KeyValues( "SysSession::OnPlayerRemoved" );
  1763. KeyValues::AutoDelete autodelete_reply( reply );
  1764. reply->SetUint64( "xuid", xuid );
  1765. SendMessage( reply );
  1766. // Fire the notification about a machine disconnected from the session
  1767. if ( KeyValues *kvEvent = new KeyValues( "OnPlayerMachinesDisconnected" ) )
  1768. {
  1769. kvEvent->SetInt( "numMachines", 1 );
  1770. kvEvent->SetUint64( "id", xuid );
  1771. OnSessionEvent( kvEvent );
  1772. }
  1773. #ifdef _X360
  1774. // Send this down to the engine as well
  1775. if ( m_lobby.m_bXSessionStarted )
  1776. {
  1777. // Send a message to the server that we need to remove this player
  1778. KeyValues *pDisconnectRequest = new KeyValues( "OnPlayerRemovedFromSession" );
  1779. pDisconnectRequest->SetUint64( "xuid", xuid );
  1780. g_pMatchExtensions->GetIVEngineClient()->ServerCmdKeyValues( pDisconnectRequest );
  1781. }
  1782. #endif // _X360
  1783. }
  1784. }
  1785. #ifdef _X360
  1786. #elif !defined( NO_STEAM )
  1787. void CSysSessionHost::Steam_OnLobbyCreated( LobbyCreated_t *pLobbyCreate, bool bError )
  1788. {
  1789. if ( bError || pLobbyCreate->m_eResult != k_EResultOK )
  1790. {
  1791. Warning( "CSysSessionHost: CreateSession failed. Error %d\n", bError ? -1 : pLobbyCreate->m_eResult );
  1792. m_eState = STATE_FAIL;
  1793. KeyValues *kv = new KeyValues( "mmF->SysSessionUpdate" );
  1794. kv->SetPtr( "syssession", this );
  1795. kv->SetString( "error", "create" );
  1796. OnSessionEvent( kv );
  1797. return;
  1798. }
  1799. else
  1800. {
  1801. m_lobby.m_uiLobbyID = pLobbyCreate->m_ulSteamIDLobby;
  1802. DevMsg( "Created lobby id: 0x%llx\n", m_lobby.m_uiLobbyID );
  1803. // will get a subsequent callback at at Steam_OnLobbyEntered to indicate that we are a lobby member
  1804. m_CallbackOnLobbyEntered.Register( this, &CSysSessionHost::Steam_OnLobbyEntered );
  1805. }
  1806. }
  1807. void CSysSessionHost::Steam_OnLobbyEntered( LobbyEnter_t *pLobbyEnter )
  1808. {
  1809. // Filter out notifications not from our lobby
  1810. if ( pLobbyEnter->m_ulSteamIDLobby != m_lobby.m_uiLobbyID )
  1811. return;
  1812. m_CallbackOnLobbyEntered.Unregister();
  1813. if ( pLobbyEnter->m_EChatRoomEnterResponse != k_EChatRoomEnterResponseSuccess )
  1814. {
  1815. Warning( "Matchmaking: lobby response %d!\n", pLobbyEnter->m_EChatRoomEnterResponse );
  1816. m_eState = STATE_FAIL;
  1817. KeyValues *kv = new KeyValues( "mmF->SysSessionUpdate" );
  1818. kv->SetPtr( "syssession", this );
  1819. kv->SetString( "error", LobbyEnterErrorAsString( pLobbyEnter ) );
  1820. OnSessionEvent( kv );
  1821. return;
  1822. }
  1823. else
  1824. {
  1825. // Set all the properties of the new session
  1826. InitSessionProperties();
  1827. // Install callback for messages
  1828. m_CallbackOnLobbyChatMsg.Register( this, &CSysSessionBase::Steam_OnLobbyChatMsg );
  1829. m_CallbackOnLobbyChatUpdate.Register( this, &CSysSessionBase::Steam_OnLobbyChatUpdate );
  1830. // Setup voice
  1831. Voice_ProcessTalkers( NULL, true );
  1832. m_eState = STATE_IDLE;
  1833. KeyValues *kv = new KeyValues( "mmF->SysSessionUpdate" );
  1834. kv->SetPtr( "syssession", this );
  1835. OnSessionEvent( kv );
  1836. return;
  1837. }
  1838. }
  1839. bool CSysSessionHost::GetLobbyType( KeyValues *kv, ELobbyType *peType, bool *pbJoinable )
  1840. {
  1841. if ( !peType || !pbJoinable )
  1842. return false;
  1843. char const *szLock = kv->GetString( "system/lock", NULL );
  1844. char const *szAccess = kv->GetString( "system/access", NULL );
  1845. if ( !szAccess && !szLock )
  1846. return false;
  1847. if ( !szLock )
  1848. szLock = m_pSettings->GetString( "system/lock", "" );
  1849. if ( !szAccess )
  1850. szAccess = m_pSettings->GetString( "system/access", "public" );
  1851. *pbJoinable = ( szLock[0] == 0 ); // non joinable if locked
  1852. if ( !Q_stricmp( "public", szAccess ) )
  1853. return *peType = k_ELobbyTypePublic, true;
  1854. else if ( !Q_stricmp( "friends", szAccess ) )
  1855. return *peType = k_ELobbyTypeFriendsOnly, true;
  1856. else if ( !Q_stricmp( "private", szAccess ) )
  1857. return *peType = k_ELobbyTypePrivate, true; // private
  1858. else
  1859. return false;
  1860. }
  1861. #endif
  1862. void CSysSessionHost::UpdateMembersInfo()
  1863. {
  1864. #ifdef _X360
  1865. #elif !defined( NO_STEAM )
  1866. if ( m_lobby.m_uiLobbyID )
  1867. {
  1868. steamapicontext->SteamMatchmaking()->SetLobbyMemberLimit( m_lobby.m_uiLobbyID,
  1869. m_pSettings->GetInt( "members/numSlots", 1 ) + m_numDormantMembersDetected );
  1870. }
  1871. // Set the initial members information
  1872. LobbySetDataFromKeyValues( "members", m_pSettings->FindKey( "members" ), false );
  1873. #endif
  1874. }
  1875. void CSysSessionHost::InitSessionProperties()
  1876. {
  1877. UpdateMembersInfo();
  1878. UpdateSessionProperties( m_pSettings );
  1879. }
  1880. void CSysSessionHost::UpdateSessionProperties( KeyValues *kv )
  1881. {
  1882. if ( !kv )
  1883. return;
  1884. CSysSessionBase::UpdateSessionProperties( kv, true );
  1885. //
  1886. // Set joinability and public/private slots distribution
  1887. //
  1888. #ifdef _X360
  1889. OnX360AllSessionMembersJoinLeave( kv );
  1890. #elif !defined( NO_STEAM )
  1891. ELobbyType eType = k_ELobbyTypePublic;
  1892. bool bJoinable = true;
  1893. if ( GetLobbyType( kv, &eType, &bJoinable ) && m_lobby.m_uiLobbyID )
  1894. {
  1895. steamapicontext->SteamMatchmaking()->SetLobbyType( m_lobby.m_uiLobbyID, eType );
  1896. steamapicontext->SteamMatchmaking()->SetLobbyJoinable( m_lobby.m_uiLobbyID, bJoinable );
  1897. }
  1898. #endif
  1899. //
  1900. // Update QOS reply data of the session
  1901. //
  1902. #ifdef _X360
  1903. if ( IsX360() && !m_hLobbyMigrateCall )
  1904. {
  1905. CUtlBuffer bufQosData;
  1906. bufQosData.ActivateByteSwapping( !CByteswap::IsMachineBigEndian() );
  1907. g_pMatchFramework->GetMatchNetworkMsgController()->PackageGameDetailsForQOS( m_pSettings, bufQosData );
  1908. g_pMatchExtensions->GetIXOnline()->XNetQosListen( &m_lobby.m_xiInfo.sessionID,
  1909. ( const BYTE * ) bufQosData.Base(), bufQosData.TellMaxPut(),
  1910. 0, XNET_QOS_LISTEN_SET_DATA );
  1911. }
  1912. #endif
  1913. }
  1914. bool CSysSessionHost::Process_RequestJoinData( XUID xuidClient, KeyValues *pSettings )
  1915. {
  1916. // Check if the request is a duplicate because of migration and client had
  1917. // to re-request join authorization from the new host, but the old host
  1918. // already included the new client into the session...
  1919. if ( SessionMembersFindPlayer( m_pSettings, xuidClient ) )
  1920. return true;
  1921. // We should merge the client's information into our settings information
  1922. int numUsersConnecting = pSettings->GetInt( "members/numPlayers", 0 );
  1923. int numMachinesConnecting = pSettings->GetInt( "members/numMachines", 0 );
  1924. int numSlotsTotal = m_pSettings->GetInt( "members/numSlots", 0 );
  1925. int numUsersCurrent = m_pSettings->GetInt( "members/numPlayers", 0 );
  1926. int numMachinesCurrent = m_pSettings->GetInt( "members/numMachines", 0 );
  1927. // CS:GO 2017 - Validate a bunch of parameters to match the authenticated XUID:
  1928. if ( numMachinesConnecting != 1 ) return false;
  1929. if ( numUsersConnecting != 1 ) return false;
  1930. if ( pSettings->GetInt( "members/machine0/numPlayers" ) != 1 ) return false;
  1931. if ( pSettings->GetUint64( "members/machine0/id" ) != xuidClient ) return false;
  1932. if ( pSettings->GetUint64( "members/machine0/player0/xuid" ) != xuidClient ) return false;
  1933. // Check if there are more players on the server than in session
  1934. INetSupport::ClientInfo_t nsci = {0};
  1935. g_pMatchExtensions->GetINetSupport()->GetClientInfo( &nsci );
  1936. // If we represent a team lobby, then only consider players on our team
  1937. if ( nsci.m_numHumanPlayers &&
  1938. !Q_stricmp( m_pSettings->GetString( "system/netflag", "" ), "teamlobby" ) )
  1939. {
  1940. // TODO: int nMatchTeam = m_pSettings->GetInt( "server/team", 1 );
  1941. // for now just always go by Steam lobby slots
  1942. nsci.m_numHumanPlayers = 0;
  1943. }
  1944. // Prepare the reply package
  1945. KeyValues *reply = new KeyValues( "SysSession::ReplyJoinData" );
  1946. KeyValues::AutoDelete autodelete( reply );
  1947. reply->SetUint64( "id", xuidClient );
  1948. // If we have a team reservation in place then reject clients that don't
  1949. // provide the reservation key
  1950. if ( m_teamResKey )
  1951. {
  1952. uint64 clientKey = pSettings->GetUint64( "teamResKey", 0 );
  1953. if ( m_teamResKey != clientKey)
  1954. {
  1955. reply->SetString( "error", "TeamResFail" );
  1956. SendMessage( reply );
  1957. return false;
  1958. }
  1959. else
  1960. {
  1961. // One more PWF team client has joined the session
  1962. m_numRemainingTeamPlayers--;
  1963. if ( m_numRemainingTeamPlayers == 0 )
  1964. {
  1965. UnreserveTeamSession();
  1966. }
  1967. }
  1968. }
  1969. // If any of the data is malformed, early out
  1970. if ( !numUsersConnecting || !numMachinesConnecting ||
  1971. !numSlotsTotal || !numUsersCurrent || !numMachinesCurrent )
  1972. {
  1973. reply->SetString( "error", "n/a" );
  1974. SendMessage( reply );
  1975. return false;
  1976. }
  1977. // If the session is using a private key (tournament lobby), then keys must match
  1978. char const *szRequestLockField = pSettings->GetString( "system/lock", "" );
  1979. if ( mm_session_sys_pkey.GetString()[0] )
  1980. {
  1981. if ( V_strcmp( szRequestLockField, mm_session_sys_pkey.GetString() ) )
  1982. {
  1983. DevMsg( "LOBBY: blocking join request from %llu with invalid key: %s\n", xuidClient, szRequestLockField );
  1984. reply->SetString( "error", "n/a" );
  1985. SendMessage( reply );
  1986. return false;
  1987. }
  1988. }
  1989. // If the session is locked, prevent join
  1990. char const *szSessionSystemSettingsLock = m_pSettings->GetString( "system/lock", "" );
  1991. if ( szSessionSystemSettingsLock[0] )
  1992. {
  1993. char const *szReplyError = "lock";
  1994. if ( !V_stricmp( "mmqueue", szSessionSystemSettingsLock ) )
  1995. {
  1996. szReplyError = "LockMmQueue";
  1997. }
  1998. reply->SetString( "error", szReplyError );
  1999. SendMessage( reply );
  2000. return false;
  2001. }
  2002. // If the request is a soft-join then check privacy and mmqueue
  2003. if ( KeyValues *kvSoftChecks = pSettings->FindKey( "joincheck" ) )
  2004. {
  2005. FOR_EACH_SUBKEY( kvSoftChecks, kvSubCheck )
  2006. {
  2007. char const *szSoftValue = kvSubCheck->GetName();
  2008. char const *szSoftKey = kvSubCheck->GetString();
  2009. bool bSoftCheckOK = ( !V_strcmp( "#empty#", szSoftValue ) && !*m_pSettings->GetString( szSoftKey ) ) ||
  2010. ( ( *szSoftValue == '[' ) && V_strstr( szSoftValue, CFmtStr( "[%s]", m_pSettings->GetString( szSoftKey ) ) ) ) ||
  2011. !V_stricmp( m_pSettings->GetString( szSoftKey ), szSoftValue );
  2012. if ( !bSoftCheckOK )
  2013. {
  2014. DevMsg( "LOBBY: blocking join request from %llu with check: %s = '%s' (actual: '%s')\n", xuidClient,
  2015. szSoftKey, szSoftValue, m_pSettings->GetString( szSoftKey ) );
  2016. char const *szReplyError = "lock";
  2017. reply->SetString( "error", szReplyError );
  2018. SendMessage( reply );
  2019. return false;
  2020. }
  2021. }
  2022. }
  2023. // If the user has previously been kicked then do not let them re-join
  2024. int idxKicked = m_mapKickedPlayers.Find( xuidClient );
  2025. if ( idxKicked != m_mapKickedPlayers.InvalidIndex() )
  2026. {
  2027. if ( Plat_FloatTime() - m_mapKickedPlayers.Element( idxKicked ) < mm_session_sys_kick_ban_duration.GetFloat() )
  2028. {
  2029. DevMsg( "LOBBY: blocking join request from %llu, still %.1f sec kick ban duration remaining, mm_session_sys_kick_ban_duration = %.1f.\n", xuidClient,
  2030. m_mapKickedPlayers.Element( idxKicked ) + 1 + mm_session_sys_kick_ban_duration.GetFloat() - Plat_FloatTime(), mm_session_sys_kick_ban_duration.GetFloat() );
  2031. reply->SetString( "error", "kicked" );
  2032. SendMessage( reply );
  2033. return false;
  2034. }
  2035. }
  2036. // If the session is full, early out
  2037. if ( MAX( numUsersCurrent, nsci.m_numHumanPlayers )
  2038. + numUsersConnecting > numSlotsTotal )
  2039. {
  2040. reply->SetString( "error", "full" );
  2041. SendMessage( reply );
  2042. return false;
  2043. }
  2044. // Get the members containers
  2045. KeyValues *pMembers = m_pSettings->FindKey( "members" );
  2046. Assert( pMembers );
  2047. if ( !pMembers )
  2048. return false;
  2049. KeyValues *pMembersConnecting = pSettings->FindKey( "members" );
  2050. Assert( pMembersConnecting );
  2051. if ( !pMembersConnecting )
  2052. return false;
  2053. // Validate that the connecting machines have the required TU and DLC installed
  2054. for ( int k = 0; k < numMachinesConnecting; ++ k )
  2055. {
  2056. KeyValues *kvConnecting = pMembersConnecting->FindKey( CFmtStr( "machine%d", k ) );
  2057. Assert( kvConnecting );
  2058. if ( !kvConnecting )
  2059. continue;
  2060. char const *szTuString = kvConnecting->GetString( "tuver" );
  2061. uint64 uiDlcMask = kvConnecting->GetUint64( "dlcmask" );
  2062. char const *szTuRequired = pMembers->GetString( "machine0/tuver" );
  2063. uint64 uiDlcRequired = m_pSettings->GetUint64( "game/dlcrequired" );
  2064. if ( Q_strcmp( szTuString, szTuRequired ) )
  2065. {
  2066. reply->SetString( "error", "turequired" );
  2067. reply->SetString( "turequired", szTuRequired );
  2068. SendMessage( reply );
  2069. return false;
  2070. }
  2071. if ( ( uiDlcMask & uiDlcRequired ) != uiDlcRequired )
  2072. {
  2073. reply->SetString( "error", "dlcrequired" );
  2074. reply->SetUint64( "dlcrequired", uiDlcRequired &~uiDlcMask );
  2075. reply->SetUint64( "dlcmask", uiDlcRequired );
  2076. SendMessage( reply );
  2077. return false;
  2078. }
  2079. }
  2080. // Merge the information about the connecting members
  2081. for ( int k = 0; k < numMachinesConnecting; ++ k )
  2082. {
  2083. KeyValues *kvConnecting = pMembersConnecting->FindKey( CFmtStr( "machine%d", k ) );
  2084. Assert( kvConnecting );
  2085. if ( !kvConnecting )
  2086. continue;
  2087. KeyValues *kvNewMachine = kvConnecting->MakeCopy();
  2088. kvNewMachine->SetName( CFmtStr( "machine%d", k + numMachinesCurrent ) );
  2089. pMembers->AddSubKey( kvNewMachine );
  2090. // Register talkers from that machine
  2091. Voice_ProcessTalkers( kvNewMachine, true );
  2092. }
  2093. // Update the current count of machines and members
  2094. pMembers->SetInt( "numMachines", numMachinesCurrent + numMachinesConnecting );
  2095. pMembers->SetInt( "numPlayers", numUsersCurrent + numUsersConnecting );
  2096. // Join flags
  2097. pMembers->SetUint64( "joinflags", pMembersConnecting->GetUint64( "joinflags" ) );
  2098. #ifdef _X360
  2099. // On X360 we need to update lobby members server-side count
  2100. MMX360_LobbyJoinMembers( pSettings, m_lobby );
  2101. #endif
  2102. // Fire the notifications about a bunch of machines connected to the session
  2103. if ( KeyValues *kvEvent = new KeyValues( "OnPlayerMachinesConnected" ) )
  2104. {
  2105. kvEvent->SetInt( "numMachines", numMachinesConnecting );
  2106. kvEvent->SetUint64( "id", xuidClient );
  2107. OnSessionEvent( kvEvent );
  2108. }
  2109. // Fire the notifications about the new players
  2110. for ( int k = 0; k < numMachinesConnecting; ++ k )
  2111. {
  2112. KeyValues *kvConnecting = pMembersConnecting->FindKey( CFmtStr( "machine%d", k ) );
  2113. if ( !kvConnecting )
  2114. continue;
  2115. int numPlayers = kvConnecting->GetInt( "numPlayers", 0 );
  2116. for ( int j = 0; j < numPlayers; ++ j )
  2117. {
  2118. KeyValues *pPlayer = kvConnecting->FindKey( CFmtStr( "player%d", j ) );
  2119. XUID xuid = pPlayer->GetUint64( "xuid", 0ull );
  2120. if ( !xuid )
  2121. continue;
  2122. if ( KeyValues *kvEvent = new KeyValues( "OnPlayerUpdated" ) )
  2123. {
  2124. kvEvent->SetUint64( "xuid", xuid );
  2125. kvEvent->SetString( "state", "joined" );
  2126. kvEvent->SetPtr( "player", pPlayer );
  2127. OnSessionEvent( kvEvent );
  2128. }
  2129. }
  2130. }
  2131. // Send the encryption key to the connected client too
  2132. reply->SetUint64( "crypt", m_ullCrypt );
  2133. // After everything settled with the new players on this machine,
  2134. // notify everybody of the new state of the session
  2135. reply->AddSubKey( m_pSettings->MakeCopy() );
  2136. SendMessage( reply );
  2137. Voice_UpdateMutelist();
  2138. #ifdef _X360
  2139. #elif !defined( NO_STEAM )
  2140. // Update members information
  2141. LobbySetDataFromKeyValues( "members", m_pSettings->FindKey( "members" ), false );
  2142. #endif
  2143. return true;
  2144. }
  2145. void CSysSessionHost::Process_TeamReservation( XUID key, int teamSize )
  2146. {
  2147. KeyValues *pMembers;
  2148. int numPlayers;
  2149. int numSlots;
  2150. KeyValues *reply = new KeyValues( "SysSession::TeamReservationResult" );
  2151. KeyValues::AutoDelete autodelete( reply );
  2152. // Check we are not already reserved
  2153. if ( m_teamResKey != 0 )
  2154. {
  2155. reply->SetString( "result", "alreadyReserved" );
  2156. goto xit;
  2157. }
  2158. // Check if we have enough free slots
  2159. pMembers = m_pSettings->FindKey( "members" );
  2160. numPlayers = pMembers->GetInt( "numPlayers" );
  2161. numSlots = pMembers->GetInt( "numSlots" );
  2162. if ( numSlots < numPlayers + teamSize)
  2163. {
  2164. reply->SetString( "result", "notEnoughSlots" );
  2165. goto xit;
  2166. }
  2167. reply->SetString( "result", "success" );
  2168. ReserveTeamSession( key, teamSize );
  2169. xit:
  2170. SendMessage( reply );
  2171. }
  2172. void CSysSessionHost::ReserveTeamSession( XUID key, int numPlayers )
  2173. {
  2174. #if !defined (NO_STEAM)
  2175. DevMsg( "CSysSessionHost::ReserveTeamSession\n");
  2176. m_teamResKey = key;
  2177. m_numRemainingTeamPlayers = numPlayers;
  2178. m_flTeamResStartTime = Plat_FloatTime();
  2179. // Set lobby data
  2180. KeyValues *settings = new KeyValues( "TeamReservation" );
  2181. KeyValues::AutoDelete autodelete( settings );
  2182. settings->SetInt( "TeamRes", 1 );
  2183. LobbySetDataFromKeyValues( "TeamReservation", settings );
  2184. #endif
  2185. }
  2186. void CSysSessionHost::UnreserveTeamSession()
  2187. {
  2188. #if !defined (NO_STEAM)
  2189. DevMsg( "CSysSessionHost::UnreserveTeamSession\n");
  2190. m_teamResKey = 0;
  2191. m_numRemainingTeamPlayers = 0;
  2192. // Set lobby data
  2193. KeyValues *settings = new KeyValues( "TeamReservation" );
  2194. KeyValues::AutoDelete autodelete( settings );
  2195. settings->SetInt( "TeamRes", 0 );
  2196. LobbySetDataFromKeyValues( "TeamReservation", settings );
  2197. #endif
  2198. }
  2199. void CSysSessionHost::Process_VoiceStatus( KeyValues *msg, XUID xuidSrc )
  2200. {
  2201. XUID xuid = msg->GetUint64( "xuid" );
  2202. Assert( xuid == xuidSrc );
  2203. if ( xuid != xuidSrc )
  2204. return;
  2205. KeyValues *pPlayer = SessionMembersFindPlayer( m_pSettings, xuid );
  2206. if ( !pPlayer )
  2207. return;
  2208. char const *szValue = msg->GetString( "voice" );
  2209. pPlayer->SetString( "voice", szValue );
  2210. OnPlayerUpdated( pPlayer );
  2211. }
  2212. void CSysSessionHost::Process_VoiceMutelist( KeyValues *msg )
  2213. {
  2214. XUID xuid = msg->GetUint64( "xuid" );
  2215. KeyValues *pMachine = NULL;
  2216. SessionMembersFindPlayer( m_pSettings, xuid, &pMachine );
  2217. if ( !pMachine )
  2218. return;
  2219. // Remove old mutelist
  2220. if ( KeyValues *pMutelist = pMachine->FindKey( "Mutelist" ) )
  2221. {
  2222. pMachine->RemoveSubKey( pMutelist );
  2223. pMutelist->deleteThis();
  2224. }
  2225. // Update new mutelist
  2226. if ( KeyValues *pMutelist = msg->FindKey( "Mutelist" ) )
  2227. {
  2228. pMachine->FindKey( "Mutelist", true )->MergeFrom( pMutelist, KeyValues::MERGE_KV_UPDATE );
  2229. }
  2230. OnMachineUpdated( pMachine );
  2231. }
  2232. //
  2233. // Client session implementation
  2234. //
  2235. CSysSessionClient::CSysSessionClient( KeyValues *pSettings ) :
  2236. CSysSessionBase( pSettings ),
  2237. m_eState( STATE_INIT ),
  2238. m_flInitializeTimestamp( 0.0f )
  2239. {
  2240. #ifdef _X360
  2241. memset( &m_xnaddrLocal, 0, sizeof( m_xnaddrLocal ) );
  2242. #endif
  2243. }
  2244. CSysSessionClient::CSysSessionClient( CSysSessionHost *pHost, KeyValues *pSettings ) :
  2245. CSysSessionBase( pSettings ),
  2246. #ifdef _X360
  2247. #elif !defined( NO_STEAM )
  2248. #endif
  2249. m_eState( STATE_IDLE ),
  2250. m_flInitializeTimestamp( 0.0f )
  2251. {
  2252. m_lobby = pHost->m_lobby;
  2253. m_Voice_flLastHeadsetStatusCheck = pHost->m_Voice_flLastHeadsetStatusCheck;
  2254. #ifdef _X360
  2255. m_pNetworkMgr = pHost->m_pNetworkMgr;
  2256. if ( m_pNetworkMgr )
  2257. m_pNetworkMgr->SetListener( this );
  2258. m_pAsyncOperation = pHost->m_pAsyncOperation;
  2259. Assert( !m_pAsyncOperation );
  2260. // If client was in migrate state, then disassociate the migrate call listener
  2261. if ( pHost->m_hLobbyMigrateCall )
  2262. {
  2263. MMX360_LobbyMigrateSetListener( pHost->m_hLobbyMigrateCall, NULL );
  2264. pHost->m_hLobbyMigrateCall = NULL;
  2265. }
  2266. #elif !defined( NO_STEAM )
  2267. // Install callback for messages
  2268. m_CallbackOnLobbyChatMsg.Register( this, &CSysSessionBase::Steam_OnLobbyChatMsg );
  2269. m_CallbackOnLobbyChatUpdate.Register( this, &CSysSessionBase::Steam_OnLobbyChatUpdate );
  2270. #endif
  2271. }
  2272. CSysSessionClient::~CSysSessionClient()
  2273. {
  2274. ;
  2275. }
  2276. bool CSysSessionClient::Update()
  2277. {
  2278. if ( !CSysSessionBase::Update() )
  2279. return false;
  2280. switch( m_eState )
  2281. {
  2282. case STATE_INIT:
  2283. if ( !m_flInitializeTimestamp )
  2284. {
  2285. m_flInitializeTimestamp = Plat_FloatTime();
  2286. #if !defined( NO_STEAM )
  2287. SetupSteamRankingConfiguration();
  2288. #endif
  2289. }
  2290. if (
  2291. #if !defined( NO_STEAM )
  2292. ( IsSteamRankingConfigured() || ( Plat_FloatTime() >= m_flInitializeTimestamp + mm_session_sys_ranking_timeout.GetFloat() ) ) &&
  2293. #endif
  2294. SysSession_AllowCreate()
  2295. )
  2296. UpdateStateInit();
  2297. break;
  2298. case STATE_CREATING:
  2299. break;
  2300. case STATE_REQUESTING_JOIN_DATA:
  2301. // Wait for 5 sec until data arrives
  2302. if ( Plat_FloatTime() > m_RequestJoinDataInfo.m_fTimeSent + mm_session_sys_connect_timeout.GetFloat() )
  2303. {
  2304. m_eState = STATE_FAIL;
  2305. #ifdef _X360
  2306. if ( m_pNetworkMgr )
  2307. {
  2308. m_pNetworkMgr->Destroy();
  2309. m_pNetworkMgr = NULL;
  2310. }
  2311. #endif
  2312. Warning( "CSysSessionClient: Unable to get session information from host\n" );
  2313. KeyValues *kv = new KeyValues( "mmF->SysSessionUpdate" );
  2314. kv->SetPtr( "syssession", this );
  2315. kv->SetString( "error", "n/a" );
  2316. OnSessionEvent( kv );
  2317. }
  2318. break;
  2319. #if !defined( NO_STEAM )
  2320. case STATE_JOIN_LOBBY:
  2321. m_CallbackOnLobbyEntered.Register( this, &CSysSessionClient::Steam_OnLobbyEntered );
  2322. steamapicontext->SteamMatchmaking()->JoinLobby( m_lobby.m_uiLobbyID );
  2323. m_eState = STATE_CREATING;
  2324. break;
  2325. #endif
  2326. case STATE_IDLE:
  2327. break;
  2328. }
  2329. return true;
  2330. }
  2331. void CSysSessionClient::Destroy()
  2332. {
  2333. // If we are migrating, then don't let the
  2334. // base class handle it because it will
  2335. // post Quit messages and leave the lobby...
  2336. if ( m_eState == STATE_MIGRATE )
  2337. {
  2338. delete this;
  2339. return;
  2340. }
  2341. #ifdef _X360
  2342. if ( m_eState == STATE_DELETE )
  2343. {
  2344. delete this;
  2345. return;
  2346. }
  2347. else
  2348. {
  2349. m_eState = STATE_DELETE;
  2350. }
  2351. #endif
  2352. // Chain to base which will "delete this"
  2353. CSysSessionBase::Destroy();
  2354. }
  2355. void CSysSessionClient::DebugPrint()
  2356. {
  2357. DevMsg( "CSysSessionClient [ state=%d ]\n", m_eState );
  2358. if ( m_eState == STATE_REQUESTING_JOIN_DATA )
  2359. {
  2360. DevMsg( "Requested join data %.3f sec ago from xuid = %llx\n",
  2361. Plat_FloatTime() - m_RequestJoinDataInfo.m_fTimeSent, m_RequestJoinDataInfo.m_xuidLeader );
  2362. }
  2363. CSysSessionBase::DebugPrint();
  2364. }
  2365. XUID CSysSessionClient::GetHostXuid( XUID xuidValidResult )
  2366. {
  2367. #ifdef _X360
  2368. // Host is considered to be the first machine in our settings
  2369. // to which we have a network connection
  2370. int numMachines = m_pSettings->GetInt( "members/numMachines" );
  2371. for ( int k = 0; k < numMachines; ++ k )
  2372. {
  2373. KeyValues *pMachine = m_pSettings->FindKey( CFmtStr( "members/machine%d", k ) );
  2374. if ( !pMachine )
  2375. continue;
  2376. XUID idMachine = pMachine->GetUint64( "id" );
  2377. if ( idMachine == m_xuidMachineId )
  2378. return m_xuidMachineId; // Reached our own machine, we are the top machine!
  2379. if ( xuidValidResult && idMachine == xuidValidResult )
  2380. return idMachine; // Maybe we don't have connection to the remote machine, but the caller thinks it could be a valid host
  2381. if ( m_pNetworkMgr->ConnectionPeerGetAddress( idMachine ) )
  2382. return idMachine;
  2383. }
  2384. // There's nobody in the session, maybe we are our own host?
  2385. return m_xuidMachineId;
  2386. #elif !defined( NO_STEAM )
  2387. return m_lobby.m_uiLobbyID ? steamapicontext->SteamMatchmaking()
  2388. ->GetLobbyOwner( m_lobby.m_uiLobbyID ).ConvertToUint64() : m_xuidMachineId;
  2389. #endif
  2390. return 0ull;
  2391. }
  2392. #ifdef _X360
  2393. char const * CSysSessionClient::GetHostNetworkAddress( XSESSION_INFO &xsi )
  2394. {
  2395. xsi = m_lobby.m_xiInfo;
  2396. char const *szNetworkAddress = m_pNetworkMgr->ConnectionPeerGetAddress( 0ull );
  2397. if ( !szNetworkAddress )
  2398. {
  2399. // Maybe migration hasn't finished yet...
  2400. XUID xuidHost = GetHostXuid();
  2401. DevWarning( "CSysSessionClient::GetHostNetworkAddress has no default host network address, retrying for %llx!\n", xuidHost );
  2402. szNetworkAddress = m_pNetworkMgr->ConnectionPeerGetAddress( xuidHost );
  2403. if ( !szNetworkAddress )
  2404. {
  2405. DevWarning( "CSysSessionClient::GetHostNetworkAddress has no host network address for %llx!\n", xuidHost );
  2406. Assert( 0 ); // this is fatal for our session and abnormal, but the UI should just pop a message that we failed to connect
  2407. }
  2408. }
  2409. return szNetworkAddress;
  2410. }
  2411. #endif
  2412. void CSysSessionClient::UpdateStateInit()
  2413. {
  2414. #ifdef _X360
  2415. MMX360_LobbyConnect( m_pSettings, &m_pAsyncOperation );
  2416. m_eState = STATE_CREATING;
  2417. #elif !defined( NO_STEAM )
  2418. m_lobby.m_uiLobbyID = m_pSettings->GetUint64( "options/sessionid", 0ull );
  2419. m_eState = STATE_JOIN_LOBBY;
  2420. #endif
  2421. }
  2422. void CSysSessionClient::InitSessionProperties( KeyValues *pSettings )
  2423. {
  2424. #ifdef _X360
  2425. // Configure our session slots and permissions to match the host
  2426. CX360LobbyFlags_t fl = MMX360_DescribeLobbyFlags( pSettings, false );
  2427. g_pMatchExtensions->GetIXOnline()->XSessionModify( m_lobby.m_hHandle, fl.m_dwFlags, fl.m_numPublicSlots, fl.m_numPrivateSlots, MMX360_NewOverlappedDormant() );
  2428. // Join all the members
  2429. MMX360_LobbyJoinMembers( pSettings, m_lobby );
  2430. #endif
  2431. UpdateSessionProperties( pSettings );
  2432. }
  2433. void CSysSessionClient::UpdateSessionProperties( KeyValues *kv )
  2434. {
  2435. if ( !kv )
  2436. return;
  2437. CSysSessionBase::UpdateSessionProperties( kv, false );
  2438. //
  2439. // Set joinability and public/private slots distribution
  2440. //
  2441. #ifdef _X360
  2442. if ( !kv->FindKey( "members", false ) )
  2443. {
  2444. // If this is not our initial update
  2445. OnX360AllSessionMembersJoinLeave( kv );
  2446. }
  2447. #endif
  2448. }
  2449. #ifdef _X360
  2450. void CSysSessionClient::OnAsyncOperationFinished()
  2451. {
  2452. if ( m_eState == STATE_CREATING )
  2453. {
  2454. if ( m_pAsyncOperation->GetState() != AOS_SUCCEEDED )
  2455. {
  2456. Warning( "CSysSessionClient: CreateSession failed. Error %d\n", m_pAsyncOperation->GetResult() );
  2457. ReleaseAsyncOperation();
  2458. m_eState = STATE_FAIL;
  2459. KeyValues *kv = new KeyValues( "mmF->SysSessionUpdate" );
  2460. kv->SetPtr( "syssession", this );
  2461. kv->SetString( "error", "createclient" );
  2462. OnSessionEvent( kv );
  2463. return;
  2464. }
  2465. //
  2466. // We have successfully created the client-side session,
  2467. // retrieve all information from the async creation.
  2468. //
  2469. m_lobby = m_pAsyncOperation->GetLobby();
  2470. ReleaseAsyncOperation();
  2471. // Initialize network manager
  2472. m_pNetworkMgr = new CX360NetworkMgr( this, GetX360NetSocket() );
  2473. if ( !m_pNetworkMgr->ConnectionPeerOpenActive( 0ull, m_lobby.m_xiInfo ) )
  2474. {
  2475. m_eState = STATE_FAIL;
  2476. m_pNetworkMgr->Destroy();
  2477. m_pNetworkMgr = NULL;
  2478. KeyValues *kv = new KeyValues( "mmF->SysSessionUpdate" );
  2479. kv->SetPtr( "syssession", this );
  2480. kv->SetString( "error", "n/a" );
  2481. OnSessionEvent( kv );
  2482. return;
  2483. }
  2484. // Request permission to join
  2485. m_eState = STATE_REQUESTING_JOIN_DATA;
  2486. m_RequestJoinDataInfo.m_fTimeSent = Plat_FloatTime();
  2487. m_RequestJoinDataInfo.m_xuidLeader = 0ull;
  2488. Send_RequestJoinData();
  2489. return;
  2490. }
  2491. else if ( m_eState == STATE_DELETE )
  2492. {
  2493. ReleaseAsyncOperation();
  2494. SysSession360_RegisterPending( this );
  2495. }
  2496. else
  2497. {
  2498. ReleaseAsyncOperation();
  2499. }
  2500. }
  2501. void CSysSessionClient::OnX360NetDisconnected( XUID xuidRemote )
  2502. {
  2503. if ( m_eState == STATE_DELETE )
  2504. return;
  2505. // Do not react to disconnections among our peer clients,
  2506. // host is authoritative as far as which clients are in the session
  2507. // As soon as another client will migrate to be host that client
  2508. // will drop session members who haven't established the required
  2509. // P2P interconnect channels
  2510. if ( xuidRemote != GetHostXuid( xuidRemote ) ) // Indicate that the disconnected XUID could have been the host
  2511. {
  2512. DevMsg( "CSysSessionClient::OnX360NetDisconnected( %llx ) waiting for host update.\n", xuidRemote );
  2513. return;
  2514. }
  2515. CSysSessionBase::OnX360NetDisconnected( xuidRemote );
  2516. }
  2517. bool CSysSessionClient::OnX360NetConnectionlessPacket( netpacket_t *pkt, KeyValues *msg )
  2518. {
  2519. if ( m_eState == STATE_IDLE && !Q_stricmp( msg->GetName(), "SysSession::P2PConnect" ) )
  2520. {
  2521. // Check nonce the client is trying to connect to
  2522. uint64 uiNonce = msg->GetUint64( "nonce" );
  2523. if ( uiNonce != ( const uint64 & ) m_lobby.m_uiNonce )
  2524. return false;
  2525. // This is a legit connectionless packet requesting a p2p connection
  2526. XUID machineid = msg->GetUint64( "id" );
  2527. XNKID xnkidSession = m_lobby.m_xiInfo.sessionID;
  2528. // Pick up the peer and add it to our spider web of connections
  2529. if ( !m_pNetworkMgr->ConnectionPeerOpenPassive( machineid, pkt, &xnkidSession ) )
  2530. {
  2531. DevWarning( "CSysSessionClient::P2PConnect failed to open passive connection with %llx!\n", machineid );
  2532. }
  2533. return true;
  2534. }
  2535. // Unknown packet, permanently block sender
  2536. return false;
  2537. }
  2538. void CSysSessionClient::XP2P_Interconnect()
  2539. {
  2540. // Peer-to-peer connection establish packet
  2541. KeyValues *p2pMsg = new KeyValues( "SysSession::P2PConnect" );
  2542. KeyValues::AutoDelete autodelete_p2pMsg( p2pMsg );
  2543. p2pMsg->SetUint64( "nonce", m_lobby.m_uiNonce );
  2544. p2pMsg->SetUint64( "id", m_xuidMachineId );
  2545. // Open an active connection to all current peers
  2546. if ( KeyValues *kvMembers = m_pSettings->FindKey( "members" ) )
  2547. {
  2548. int numMachines = kvMembers->GetInt( "numMachines" );
  2549. for ( int k = 1; k < numMachines; ++ k ) // skip "0" because it's the host
  2550. {
  2551. KeyValues *kvMachine = kvMembers->FindKey( CFmtStr( "machine%d", k ) );
  2552. if ( !kvMachine )
  2553. continue;
  2554. XSESSION_INFO xsi = m_lobby.m_xiInfo;
  2555. MMX360_XnaddrFromString( xsi.hostAddress, kvMachine->GetString( "xnaddr" ) );
  2556. if ( !memcmp( &xsi.hostAddress, &m_xnaddrLocal, sizeof( m_xnaddrLocal ) ) )
  2557. continue;
  2558. XUID idMachine = kvMachine->GetUint64( "id" );
  2559. if ( m_pNetworkMgr->ConnectionPeerOpenActive( idMachine, xsi ) )
  2560. {
  2561. m_pNetworkMgr->ConnectionPeerSendConnectionless( idMachine, p2pMsg );
  2562. }
  2563. else
  2564. {
  2565. DevWarning( "CSysSessionClient::XP2P_Interconnect failed to interconnect with %llx!\n", idMachine );
  2566. }
  2567. }
  2568. }
  2569. }
  2570. #endif
  2571. void CSysSessionClient::ReceiveMessage( KeyValues *msg, bool bValidatedLobbyMember, XUID xuidSrc )
  2572. {
  2573. char const *szMsg = msg->GetName();
  2574. // Parse the message depending on our state
  2575. switch ( m_eState )
  2576. {
  2577. case STATE_REQUESTING_JOIN_DATA:
  2578. if ( !Q_stricmp( szMsg, "SysSession::ReplyJoinData" ) )
  2579. {
  2580. if ( ( msg->GetUint64( "id" ) == m_xuidMachineId )
  2581. && ( xuidSrc == GetHostXuid() ) )
  2582. {
  2583. Process_ReplyJoinData_Our( msg );
  2584. }
  2585. }
  2586. break;
  2587. case STATE_IDLE:
  2588. if ( !bValidatedLobbyMember )
  2589. {
  2590. return;
  2591. }
  2592. else if ( !Q_stricmp( szMsg, "SysSession::ReplyJoinData" ) )
  2593. {
  2594. if ( GetHostXuid() == xuidSrc )
  2595. Process_ReplyJoinData_Other( msg );
  2596. }
  2597. else if ( !Q_stricmp( szMsg, "SysSession::OnPlayerRemoved" ) )
  2598. {
  2599. if ( GetHostXuid() == xuidSrc )
  2600. FindAndRemovePlayerFromMembers( msg->GetUint64( "xuid" ) );
  2601. }
  2602. else if ( !Q_stricmp( szMsg, "SysSession::OnPlayerKicked" ) )
  2603. {
  2604. if ( GetHostXuid() == xuidSrc )
  2605. {
  2606. XUID xuid = msg->GetUint64( "xuid" );
  2607. if ( xuid == m_xuidMachineId )
  2608. Process_Kicked( msg );
  2609. else
  2610. FindAndRemovePlayerFromMembers( xuid );
  2611. }
  2612. }
  2613. else if ( !Q_stricmp( szMsg, "SysSession::OnPlayerUpdated" ) )
  2614. {
  2615. if ( GetHostXuid() == xuidSrc )
  2616. Process_OnPlayerUpdated( msg );
  2617. }
  2618. else if ( !Q_stricmp( szMsg, "SysSession::OnMachineUpdated" ) )
  2619. {
  2620. if ( GetHostXuid() == xuidSrc )
  2621. Process_OnMachineUpdated( msg );
  2622. }
  2623. #ifdef _X360
  2624. else if ( !Q_stricmp( szMsg, "SysSession::HostMigrated" ) )
  2625. {
  2626. XSESSION_INFO xsi;
  2627. char const *chSessionInfo = msg->GetString( "sessioninfo", "" );
  2628. MMX360_SessionInfoFromString( xsi, chSessionInfo );
  2629. // If there is an outstanding migrate call, then disable our listener on it
  2630. if ( m_hLobbyMigrateCall && !m_MigrateCallState.m_bFinished )
  2631. MMX360_LobbyMigrateSetListener( m_hLobbyMigrateCall, NULL );
  2632. m_hLobbyMigrateCall = NULL;
  2633. // Schedule a client migrate call
  2634. m_hLobbyMigrateCall = MMX360_LobbyMigrateClient( m_lobby, xsi, &m_MigrateCallState );
  2635. if ( !m_hLobbyMigrateCall )
  2636. {
  2637. KeyValues *kv = new KeyValues( "mmF->SysSessionUpdate" );
  2638. kv->SetPtr( "syssession", this );
  2639. kv->SetString( "error", "migrate" );
  2640. // Inside this event broadcast our session will be deleted
  2641. SendEventsNotification( kv );
  2642. return;
  2643. }
  2644. else
  2645. {
  2646. // Update our network mgr to reflect the new host
  2647. XUID id = msg->GetUint64( "id" );
  2648. m_pNetworkMgr->ConnectionPeerUpdateXuid( id, 0ull );
  2649. DevMsg( "CSysSessionClient - client migration scheduled - %s\n", chSessionInfo );
  2650. }
  2651. // Now purge all network connections that the host is no longer supporting
  2652. if ( int numMachines = m_pSettings->GetInt( "members/numMachines" ) )
  2653. {
  2654. CUtlVector< XUID > arrCloseConnections;
  2655. for ( int k = 0; k < numMachines; ++ k )
  2656. {
  2657. KeyValues *pMachine = m_pSettings->FindKey( CFmtStr( "members/machine%d", k ) );
  2658. if ( !pMachine )
  2659. continue;
  2660. XUID idMachine = pMachine->GetUint64( "id" );
  2661. if ( !msg->GetString( CFmtStr( "machines/%llx", idMachine ), NULL ) )
  2662. arrCloseConnections.AddToTail( idMachine );
  2663. }
  2664. for ( int k = 0; k < arrCloseConnections.Count(); ++ k )
  2665. {
  2666. XUID idMachine = arrCloseConnections[k];
  2667. m_pNetworkMgr->ConnectionPeerClose( idMachine );
  2668. OnPlayerLeave( idMachine );
  2669. }
  2670. }
  2671. // Send a notification
  2672. KeyValues *kvEvent = new KeyValues( "OnPlayerLeaderChanged" );
  2673. kvEvent->SetString( "state", "client" );
  2674. kvEvent->SetUint64( "xuid", msg->GetUint64( "id" ) );
  2675. kvEvent->SetString( "migration", "started" );
  2676. SendEventsNotification( kvEvent );
  2677. }
  2678. #endif
  2679. else if ( !Q_stricmp( szMsg, "SysSession::OnUpdate" ) )
  2680. {
  2681. if ( GetHostXuid() == xuidSrc )
  2682. {
  2683. // Host is making changes to the session
  2684. m_pSettings->MergeFrom( msg );
  2685. // Take care of the updated properties on the client side
  2686. UpdateSessionProperties( msg->FindKey( "update", false ) );
  2687. // Broadcast the update to everybody interested
  2688. MatchSession_BroadcastSessionSettingsUpdate( msg );
  2689. }
  2690. }
  2691. else
  2692. {
  2693. CSysSessionBase::ReceiveMessage( msg, bValidatedLobbyMember, xuidSrc );
  2694. }
  2695. return;
  2696. }
  2697. }
  2698. #ifdef _X360
  2699. #elif !defined( NO_STEAM )
  2700. void CSysSessionClient::Steam_OnLobbyEntered( LobbyEnter_t *pLobbyEnter )
  2701. {
  2702. // Filter out notifications not from our lobby
  2703. if ( pLobbyEnter->m_ulSteamIDLobby != m_lobby.m_uiLobbyID )
  2704. return;
  2705. m_CallbackOnLobbyEntered.Unregister();
  2706. if ( pLobbyEnter->m_EChatRoomEnterResponse != k_EChatRoomEnterResponseSuccess )
  2707. {
  2708. Warning( "CSysSessionClient: Cannot join lobby, response %d!\n", pLobbyEnter->m_EChatRoomEnterResponse );
  2709. m_eState = STATE_FAIL;
  2710. KeyValues *kv = new KeyValues( "mmF->SysSessionUpdate" );
  2711. kv->SetPtr( "syssession", this );
  2712. kv->SetString( "error", LobbyEnterErrorAsString( pLobbyEnter ) );
  2713. OnSessionEvent( kv );
  2714. return;
  2715. }
  2716. else
  2717. {
  2718. XUID xuidLeader = steamapicontext->SteamMatchmaking()->GetLobbyOwner( m_lobby.m_uiLobbyID ).ConvertToUint64();
  2719. if ( m_xuidMachineId == xuidLeader )
  2720. {
  2721. Warning( "CSysSessionClient: Host left lobby, unable to migrate\n" );
  2722. // We entered the lobby, but the host left
  2723. KeyValues *kv = new KeyValues( "mmF->SysSessionUpdate" );
  2724. kv->SetPtr( "syssession", this );
  2725. kv->SetString( "error", "n/a" );
  2726. OnSessionEvent( kv );
  2727. return;
  2728. }
  2729. // DBUGG
  2730. // What lobby have we just joined
  2731. {
  2732. DevMsg( "Joined lobby %llx\n", m_lobby.m_uiLobbyID );
  2733. int i, dataCount;
  2734. dataCount = steamapicontext->SteamMatchmaking()->GetLobbyDataCount( m_lobby.m_uiLobbyID );
  2735. for ( i=0; i < dataCount; i++ )
  2736. {
  2737. char key[64];
  2738. char val[64];
  2739. steamapicontext->SteamMatchmaking()->GetLobbyDataByIndex( m_lobby.m_uiLobbyID,
  2740. i, key, sizeof(key), val, sizeof( val ));
  2741. DevMsg( "Lobby data: %s = %s\n", key, val );
  2742. }
  2743. }
  2744. // In the case that the player is joining via direct connect we only want to proceed if
  2745. // 1. The player has a friend in the lobby
  2746. // 2. The player is not blocked by anyone in the lobby
  2747. // If we have got this far (2) has already been taken care of by the frenemies code
  2748. // so we just need to take care of (1)
  2749. // The "options/dcFriendsReqd" flag is set in the client-server protocol
  2750. // For direct connects this handshake has already happened before the lobby is joined
  2751. // For matchmaking this happens after the lobby is joined so is irrelevant
  2752. KeyValuesDumpAsDevMsg( m_pSettings );
  2753. bool bFriendsReqd = m_pSettings->GetBool( "options/dcFriendsRed", false );
  2754. if ( bFriendsReqd )
  2755. {
  2756. // Check if the player is joining via direct connect (or invite)
  2757. // if joining via invite the player will pass the test below
  2758. const char *action = m_pSettings->GetString( "options/action", "" );
  2759. if ( !Q_stricmp( action, "joinsession" ) )
  2760. {
  2761. // Walk lobby members
  2762. int i, numLobbyMembers = steamapicontext->SteamMatchmaking()->GetNumLobbyMembers( m_lobby.m_uiLobbyID );
  2763. CSteamID playerID = steamapicontext->SteamUser()->GetSteamID();
  2764. for ( i = 0; i < numLobbyMembers; i++ )
  2765. {
  2766. CSteamID memberId = steamapicontext->SteamMatchmaking()->GetLobbyMemberByIndex(
  2767. m_lobby.m_uiLobbyID, i );
  2768. if ( memberId == playerID )
  2769. {
  2770. continue;
  2771. }
  2772. EFriendRelationship reln = steamapicontext->SteamFriends()->GetFriendRelationship( memberId );
  2773. if ( reln == k_EFriendRelationshipFriend )
  2774. {
  2775. break;
  2776. }
  2777. }
  2778. if ( i >= numLobbyMembers )
  2779. {
  2780. // No friends found - leave lobby
  2781. steamapicontext->SteamMatchmaking()->LeaveLobby( m_lobby.m_uiLobbyID );
  2782. Warning( "CSysSessionClient: No friends in lobby - cannot join\n");
  2783. m_eState = STATE_FAIL;
  2784. KeyValues *kv = new KeyValues( "mmF->SysSessionUpdate" );
  2785. kv->SetPtr( "syssession", this );
  2786. kv->SetString( "error", "Friend reqd to join lobby" );
  2787. OnSessionEvent( kv );
  2788. return;
  2789. }
  2790. }
  2791. }
  2792. // Request permission to join
  2793. m_eState = STATE_REQUESTING_JOIN_DATA;
  2794. m_RequestJoinDataInfo.m_fTimeSent = Plat_FloatTime();
  2795. m_RequestJoinDataInfo.m_xuidLeader = xuidLeader;
  2796. Send_RequestJoinData();
  2797. return;
  2798. }
  2799. }
  2800. #endif
  2801. void CSysSessionClient::Process_ReplyJoinData_Our( KeyValues *msg )
  2802. {
  2803. DevMsg("CSysSessionClient::Process_ReplyJoinData_Our\n");
  2804. KeyValuesDumpAsDevMsg( msg );
  2805. KeyValues *pSettings = msg->FindKey( "settings" );
  2806. char const *szError = msg->GetString( "error", NULL );
  2807. if ( !pSettings || szError )
  2808. {
  2809. Warning( "CSysSessionClient: Received bad session data from host\n" );
  2810. m_eState = STATE_FAIL;
  2811. KeyValues *kv = msg->MakeCopy();
  2812. kv->SetName( "mmF->SysSessionUpdate" );
  2813. kv->SetPtr( "syssession", this );
  2814. if ( !szError )
  2815. kv->SetString( "error", "n/a" );
  2816. OnSessionEvent( kv );
  2817. }
  2818. else
  2819. {
  2820. m_eState = STATE_IDLE;
  2821. #ifdef _X360
  2822. uint64 uiNonce = pSettings->GetUint64( "system/nonce" );
  2823. m_lobby.m_uiNonce = uiNonce;
  2824. // Update host connection XUID
  2825. if ( XUID xuidHost = pSettings->GetUint64( "members/machine0/id", 0ull ) )
  2826. {
  2827. m_pNetworkMgr->ConnectionPeerUpdateXuid( 0ull, xuidHost );
  2828. }
  2829. #endif
  2830. // We have received an entirely new "settings" data, copy that to our "settings" data
  2831. // after saving settings we need to restore
  2832. int conteam = m_pSettings->GetInt( "conteam", -1 );
  2833. m_pSettings->Clear();
  2834. m_pSettings->SetName( pSettings->GetName() );
  2835. m_pSettings->MergeFrom( pSettings, KeyValues::MERGE_KV_UPDATE );
  2836. InitSessionProperties( m_pSettings );
  2837. if ( conteam != -1 )
  2838. {
  2839. m_pSettings->SetInt("conteam", conteam );
  2840. }
  2841. // Setup voice engine
  2842. Voice_ProcessTalkers( NULL, true );
  2843. Voice_UpdateMutelist();
  2844. #ifdef _X360
  2845. // Peer-to-peer interconnect
  2846. XP2P_Interconnect();
  2847. #endif
  2848. KeyValues *kv = new KeyValues( "mmF->SysSessionUpdate" );
  2849. kv->SetPtr( "syssession", this );
  2850. if ( uint64 ullCrypt = msg->GetUint64( "crypt" ) )
  2851. kv->SetUint64( "crypt", ullCrypt );
  2852. OnSessionEvent( kv );
  2853. }
  2854. }
  2855. void CSysSessionClient::Process_ReplyJoinData_Other( KeyValues *msg )
  2856. {
  2857. // Somebody has joined the session
  2858. char const *szError = msg->GetString( "error", NULL );
  2859. KeyValues *pSettings = msg->FindKey( "settings" );
  2860. if ( !pSettings || szError )
  2861. // connection attempt was rejected
  2862. return;
  2863. KeyValues *pMembers = pSettings->FindKey( "members" );
  2864. if ( !pMembers )
  2865. return;
  2866. // Now process all the new machines to notify the subscribers of
  2867. // new players
  2868. int numMachinesOld = m_pSettings->GetInt( "members/numMachines", 0 );
  2869. // Use this opportunity to fully sync-up our client-side settings
  2870. int conteam = m_pSettings->GetInt( "conteam", -1 );
  2871. m_pSettings->Clear();
  2872. m_pSettings->SetName( pSettings->GetName() );
  2873. m_pSettings->MergeFrom( pSettings, KeyValues::MERGE_KV_UPDATE );
  2874. if ( conteam != -1 )
  2875. {
  2876. m_pSettings->SetInt("conteam", conteam );
  2877. }
  2878. #ifdef _X360
  2879. // On X360 we need to update lobby members server-side count
  2880. MMX360_LobbyJoinMembers( pSettings, m_lobby, numMachinesOld );
  2881. #endif
  2882. // Run over all new machines
  2883. int numMachinesNew = m_pSettings->GetInt( "members/numMachines", 0 );
  2884. Assert( numMachinesNew > numMachinesOld );
  2885. for ( int k = numMachinesOld; k < numMachinesNew; ++ k )
  2886. {
  2887. KeyValues *kvMachine = pMembers->FindKey( CFmtStr( "machine%d", k ) );
  2888. if ( !kvMachine )
  2889. continue;
  2890. // Register talkers
  2891. Voice_ProcessTalkers( kvMachine, true );
  2892. int numPlayers = kvMachine->GetInt( "numPlayers", 0 );
  2893. for ( int j = 0; j < numPlayers; ++ j )
  2894. {
  2895. KeyValues *pPlayer = kvMachine->FindKey( CFmtStr( "player%d", j ) );
  2896. XUID xuid = pPlayer->GetUint64( "xuid", 0ull );
  2897. if ( !xuid )
  2898. continue;
  2899. KeyValues *kvEvent = new KeyValues( "OnPlayerUpdated" );
  2900. kvEvent->SetString( "state", "joined" );
  2901. kvEvent->SetUint64( "xuid", xuid );
  2902. kvEvent->SetPtr( "player", pPlayer );
  2903. OnSessionEvent( kvEvent );
  2904. }
  2905. }
  2906. Voice_UpdateMutelist();
  2907. }
  2908. void CSysSessionClient::Process_OnPlayerUpdated( KeyValues *msg )
  2909. {
  2910. KeyValues *pPlayer = SessionMembersFindPlayer( m_pSettings, msg->GetUint64( "xuid" ) );
  2911. if ( !pPlayer )
  2912. return;
  2913. char chNameBuffer[64];
  2914. Q_snprintf( chNameBuffer, ARRAYSIZE( chNameBuffer ), "%s", pPlayer->GetName() );
  2915. pPlayer->Clear();
  2916. pPlayer->SetName( chNameBuffer );
  2917. pPlayer->MergeFrom( msg, KeyValues::MERGE_KV_UPDATE );
  2918. // Notify the framework about player updated
  2919. KeyValues *kvEvent = new KeyValues( "OnPlayerUpdated" );
  2920. kvEvent->SetUint64( "xuid", pPlayer->GetUint64( "xuid" ) );
  2921. kvEvent->SetPtr( "player", pPlayer );
  2922. OnSessionEvent( kvEvent );
  2923. }
  2924. void CSysSessionClient::Process_OnMachineUpdated( KeyValues *msg )
  2925. {
  2926. KeyValues *pMachine = NULL;
  2927. SessionMembersFindPlayer( m_pSettings, msg->GetUint64( "id" ), &pMachine );
  2928. if ( !pMachine )
  2929. return;
  2930. char chNameBuffer[64];
  2931. Q_snprintf( chNameBuffer, ARRAYSIZE( chNameBuffer ), "%s", pMachine->GetName() );
  2932. pMachine->Clear();
  2933. pMachine->SetName( chNameBuffer );
  2934. pMachine->MergeFrom( msg, KeyValues::MERGE_KV_UPDATE );
  2935. }
  2936. void CSysSessionClient::Process_Kicked( KeyValues *msg )
  2937. {
  2938. m_eState = STATE_FAIL;
  2939. // Prepare the update notification
  2940. KeyValues *kv = new KeyValues( "mmF->SysSessionUpdate" );
  2941. kv->SetPtr( "syssession", this );
  2942. kv->SetString( "error", "kicked" );
  2943. // Inside this event broadcast our session will be deleted
  2944. SendEventsNotification( kv );
  2945. }
  2946. void CSysSessionClient::Send_RequestJoinData()
  2947. {
  2948. KeyValues *msg = new KeyValues( "SysSession::RequestJoinData" );
  2949. KeyValues::AutoDelete autodelete( msg );
  2950. msg->SetUint64( "id", m_xuidMachineId );
  2951. // msg->AddSubKey( m_pSettings->MakeCopy() ); << don't send all settings, there's some unnecessary data
  2952. msg->FindKey( "settings", true )->AddSubKey(m_pSettings->FindKey( "members" )->MakeCopy() );
  2953. KeyValues *pSystem = m_pSettings->FindKey( "options" );
  2954. uint64 teamResKey = pSystem->GetUint64( "teamResKey", 0 );
  2955. msg->FindKey( "settings" )->SetUint64( "teamResKey", teamResKey );
  2956. if ( mm_session_sys_pkey.GetString()[0] )
  2957. {
  2958. msg->SetString( "settings/system/lock", mm_session_sys_pkey.GetString() );
  2959. }
  2960. // If client needs to do join checks then forward them to host
  2961. if ( KeyValues *kvJoinCheck = m_pSettings->FindKey( "joincheck" ) )
  2962. msg->FindKey( "settings" )->AddSubKey( kvJoinCheck->MakeCopy() );
  2963. DevMsg( "Sending join session request...\n" );
  2964. KeyValuesDumpAsDevMsg( msg, 1 );
  2965. #ifdef _X360
  2966. // Set the session id that we are connecting to
  2967. msg->SetUint64( "sessionid", ( const uint64 & ) m_lobby.m_xiInfo.sessionID );
  2968. // Resolve this machine's XNADDR
  2969. memset( &m_xnaddrLocal, 0, sizeof( m_xnaddrLocal ) );
  2970. while( XNET_GET_XNADDR_PENDING == g_pMatchExtensions->GetIXOnline()->XNetGetTitleXnAddr( &m_xnaddrLocal ) )
  2971. continue;
  2972. char chXnaddr[ XNADDR_STRING_LENGTH ] = {0};
  2973. MMX360_XnaddrToString( m_xnaddrLocal, chXnaddr );
  2974. msg->SetString( "settings/members/machine0/xnaddr", chXnaddr );
  2975. // Now contact the remote host
  2976. m_pNetworkMgr->ConnectionPeerSendConnectionless( 0ull, msg );
  2977. #elif !defined( NO_STEAM )
  2978. // Install callback for messages
  2979. m_CallbackOnLobbyChatMsg.Register( this, &CSysSessionBase::Steam_OnLobbyChatMsg );
  2980. m_CallbackOnLobbyChatUpdate.Register( this, &CSysSessionBase::Steam_OnLobbyChatUpdate );
  2981. SendMessage( msg );
  2982. #endif
  2983. }
  2984. void CSysSessionClient::Migrate( KeyValues *pCommand )
  2985. {
  2986. if ( Q_stricmp( pCommand->GetString( "migrate" ), "client>host" ) )
  2987. {
  2988. Assert( 0 );
  2989. return;
  2990. }
  2991. #ifndef NO_STEAM
  2992. uint64 uiNewLobbyOwner = steamapicontext->SteamMatchmaking()->GetLobbyOwner( m_lobby.m_uiLobbyID ).ConvertToUint64();
  2993. Assert( uiNewLobbyOwner == m_xuidMachineId );
  2994. uiNewLobbyOwner;
  2995. // Prepare the update notification
  2996. KeyValues *kv = new KeyValues( "mmF->SysSessionUpdate" );
  2997. kv->SetPtr( "syssession", this );
  2998. m_eState = STATE_MIGRATE;
  2999. kv->SetString( "action", "host" );
  3000. // Inside this event broadcast our session will be deleted
  3001. SendEventsNotification( kv );
  3002. #endif
  3003. }
  3004. void CSysSessionClient::OnPlayerLeave( XUID xuid )
  3005. {
  3006. #if !defined( NO_STEAM )
  3007. if ( !V_stricmp( m_pSettings->GetString( "system/netflag" ), "noleave" ) )
  3008. {
  3009. DevMsg( "CSysSessionClient::OnPlayerLeave(%llx) ignored in noleave mode.\n", xuid );
  3010. return;
  3011. }
  3012. #endif
  3013. XUID xuidCurrentHost = GetHostXuid( xuid ); // Indicate the the leaving XUID could have been the host
  3014. if ( m_eState == STATE_IDLE || m_eState == STATE_MIGRATE )
  3015. {
  3016. FindAndRemovePlayerFromMembers( xuid );
  3017. }
  3018. // We only care to handle this event further if we are becoming the new host
  3019. char const *szForcedError = NULL;
  3020. #ifdef _X360
  3021. XUID xuidNewHost = GetHostXuid();
  3022. if ( m_eState == STATE_REQUESTING_JOIN_DATA )
  3023. {
  3024. szForcedError = "n/a";
  3025. }
  3026. else if ( xuidCurrentHost != xuid )
  3027. {
  3028. // Current host is not leaving, so we are fine
  3029. return;
  3030. }
  3031. else if ( xuidNewHost != m_xuidMachineId )
  3032. {
  3033. // The host is leaving, but we don't plan to host
  3034. DevMsg( "CSysSessionClient::OnPlayerLeave - host left, waiting for new host (%llx)...\n", xuidNewHost );
  3035. // Send a notification
  3036. KeyValues *kvEvent = new KeyValues( "OnPlayerLeaderChanged" );
  3037. kvEvent->SetString( "state", "client" );
  3038. kvEvent->SetUint64( "xuid", xuidNewHost );
  3039. kvEvent->SetString( "migration", "waiting" );
  3040. SendEventsNotification( kvEvent );
  3041. return;
  3042. }
  3043. else
  3044. {
  3045. DevMsg( "CSysSessionClient::OnPlayerLeave - becoming new host!\n" );
  3046. }
  3047. #elif !defined( NO_STEAM )
  3048. XUID xuidNewHost = steamapicontext->SteamMatchmaking()->GetLobbyOwner( m_lobby.m_uiLobbyID ).ConvertToUint64();
  3049. if ( xuidNewHost != m_xuidMachineId )
  3050. {
  3051. if ( m_eState == STATE_REQUESTING_JOIN_DATA &&
  3052. xuid == m_RequestJoinDataInfo.m_xuidLeader )
  3053. {
  3054. // Resend join request
  3055. // m_RequestJoinDataInfo.m_fTimeSent = Plat_FloatTime();
  3056. m_RequestJoinDataInfo.m_xuidLeader = xuidNewHost;
  3057. Send_RequestJoinData();
  3058. }
  3059. else if ( xuidNewHost != xuidCurrentHost )
  3060. {
  3061. // Send a notification
  3062. KeyValues *kvEvent = new KeyValues( "OnPlayerLeaderChanged" );
  3063. kvEvent->SetString( "state", "client" );
  3064. kvEvent->SetUint64( "xuid", xuidNewHost );
  3065. SendEventsNotification( kvEvent );
  3066. }
  3067. return;
  3068. }
  3069. if ( m_eState != STATE_IDLE )
  3070. szForcedError = "n/a";
  3071. #endif
  3072. // Prepare the update notification
  3073. KeyValues *kv = new KeyValues( "mmF->SysSessionUpdate" );
  3074. kv->SetPtr( "syssession", this );
  3075. // If migration is not possible at this stage,
  3076. // then a forced error will send us into FAIL state
  3077. if ( szForcedError )
  3078. {
  3079. m_eState = STATE_FAIL;
  3080. kv->SetString( "error", szForcedError );
  3081. }
  3082. else if ( !Q_stricmp( "teamlink", m_pSettings->GetString( "system/netflag", "" ) ) )
  3083. {
  3084. // Team link sessions cannot migrate, we just trigger an error so that
  3085. // entire client link session including network manager could destroy properly
  3086. m_eState = STATE_FAIL;
  3087. kv->SetString( "error", "migrate" );
  3088. }
  3089. else
  3090. {
  3091. // We are about to migrate and become the new host
  3092. // See if the title settings mgr wants to chime in
  3093. if ( KeyValues *pMigrationHdlr = g_pMMF->GetMatchTitleGameSettingsMgr()->PrepareClientLobbyForMigration( m_pSettings, NULL ) )
  3094. {
  3095. KeyValues::AutoDelete autodelete( pMigrationHdlr );
  3096. if ( char const *szError = pMigrationHdlr->GetString( "error", NULL ) )
  3097. {
  3098. // Title forcing a migration error
  3099. m_eState = STATE_FAIL;
  3100. kv->SetString( "error", szError );
  3101. // Enqueue disconnect command
  3102. g_pMatchExtensions->GetIVEngineClient()->ClientCmd( "disconnect" );
  3103. }
  3104. }
  3105. if ( m_eState != STATE_FAIL )
  3106. {
  3107. m_eState = STATE_MIGRATE;
  3108. kv->SetString( "action", "host" );
  3109. }
  3110. }
  3111. // Inside this event broadcast our session will be deleted
  3112. SendEventsNotification( kv );
  3113. }
  3114. CSysSessionConTeamHost::CSysSessionConTeamHost( KeyValues *pSettings ) :
  3115. CSysSessionBase( pSettings ),
  3116. m_eState( STATE_INIT ),
  3117. m_lastRequestSendTime( 0.0f )
  3118. {
  3119. #if !defined (NO_STEAM)
  3120. m_CallbackOnLobbyChatMsg.Register( this, &CSysSessionBase::Steam_OnLobbyChatMsg );
  3121. #endif
  3122. }
  3123. CSysSessionConTeamHost::~CSysSessionConTeamHost()
  3124. {
  3125. #if !defined (NO_STEAM)
  3126. m_CallbackOnLobbyChatMsg.Unregister();
  3127. #endif
  3128. }
  3129. bool CSysSessionConTeamHost::Update()
  3130. {
  3131. if (!CSysSessionBase::Update())
  3132. return false;
  3133. switch ( m_eState )
  3134. {
  3135. case STATE_INIT:
  3136. #if defined (_X360)
  3137. Succeeded();
  3138. break;
  3139. // MMX360_LobbyConnect( m_pSettings, &m_pAsyncOperation );
  3140. #elif !defined (NO_STEAM)
  3141. // Join lobby
  3142. m_lobby.m_uiLobbyID = m_pSettings->GetUint64( "options/sessionid", 0ull );
  3143. m_CallbackOnLobbyEntered.Register( this, &CSysSessionConTeamHost::Steam_OnLobbyEntered );
  3144. steamapicontext->SteamMatchmaking()->JoinLobby( m_lobby.m_uiLobbyID );
  3145. #endif
  3146. m_eState = STATE_WAITING_LOBBY_JOIN;
  3147. break;
  3148. case STATE_SEND_RESERVATION_REQUEST:
  3149. SendReservationRequest();
  3150. m_eState = STATE_WAITING_RESERVATION_REQUEST;
  3151. break;
  3152. case STATE_WAITING_RESERVATION_REQUEST:
  3153. if ( Plat_FloatTime() > m_lastRequestSendTime + mm_session_sys_connect_timeout.GetFloat() )
  3154. {
  3155. DevMsg( "CSysSessionConTeamHost: Timed out waiting for reservation reply\n");
  3156. Failed();
  3157. }
  3158. break;
  3159. }
  3160. return true;
  3161. }
  3162. void CSysSessionConTeamHost::Destroy()
  3163. {
  3164. #ifdef _X360
  3165. if ( m_eState == STATE_DELETE )
  3166. {
  3167. delete this;
  3168. return;
  3169. }
  3170. else
  3171. {
  3172. m_eState = STATE_DELETE;
  3173. }
  3174. #endif
  3175. // Chain to base which will "delete this"
  3176. CSysSessionBase::Destroy();
  3177. }
  3178. bool CSysSessionConTeamHost::GetPlayerSidesAssignment( int *numPlayers, uint64 playerIDs[10], int side[10] )
  3179. {
  3180. #if !defined (NO_STEAM)
  3181. if ( GetResult() != RESULT_SUCCESS )
  3182. {
  3183. return false;
  3184. }
  3185. const char* szNumTSlotsFree = steamapicontext->SteamMatchmaking()->GetLobbyData( m_lobby.m_uiLobbyID, "members:numTSlotsFree" );
  3186. const char* szNumCTSlotsFree = steamapicontext->SteamMatchmaking()->GetLobbyData( m_lobby.m_uiLobbyID, "members:numCTSlotsFree" );
  3187. int numTeamPlayers = m_pSettings->GetInt( "members/numPlayers" );
  3188. DevMsg( "CSysSessionConTeamHost: numTSlotsFree = %s, numCTSlotsFree = %s\n", szNumTSlotsFree, szNumCTSlotsFree);
  3189. // Choose a team to join
  3190. int numSlotsFree[2];
  3191. numSlotsFree[0] = atoi( szNumCTSlotsFree );
  3192. numSlotsFree[1] = atoi( szNumTSlotsFree );
  3193. // Choose a team at random
  3194. int team = RandomInt(0, 1);
  3195. int otherTeam = 1 - team;
  3196. // If we chose a team with too few slots, but other team has enough
  3197. // then swap
  3198. if ( numSlotsFree[team] < numTeamPlayers )
  3199. {
  3200. if ( numSlotsFree[otherTeam] >= numTeamPlayers )
  3201. {
  3202. team = otherTeam;
  3203. otherTeam = 1 - team;
  3204. }
  3205. }
  3206. KeyValues *pMembers = m_pSettings->FindKey( "members" );
  3207. // Assign players to different teams
  3208. for ( int i = 0; i < numTeamPlayers; i++ )
  3209. {
  3210. const char *playerKey = CFmtStr( "machine%d/player0", i );
  3211. KeyValues *pPlayer = pMembers->FindKey( playerKey );
  3212. uint64 playerId = pPlayer->GetUint64( "xuid", 0 );
  3213. playerIDs[i] = playerId;
  3214. // In keyvalues, team CT = 1, team T = 2
  3215. if ( numSlotsFree[team] )
  3216. {
  3217. side[i] = team + 1;
  3218. numSlotsFree[team]--;
  3219. }
  3220. else
  3221. {
  3222. side[i] = otherTeam + 1;
  3223. }
  3224. }
  3225. *numPlayers = numTeamPlayers;
  3226. #endif
  3227. return true;
  3228. }
  3229. void CSysSessionConTeamHost::ReceiveMessage( KeyValues *msg, bool bValidatedLobbyMember, XUID xuidSrc )
  3230. {
  3231. #if !defined (NO_STEAM)
  3232. char const *szMsg = msg->GetName();
  3233. bool bProcessed = false;
  3234. switch ( m_eState )
  3235. {
  3236. case STATE_WAITING_RESERVATION_REQUEST:
  3237. if ( !Q_stricmp( "SysSession::TeamReservationResult", szMsg ) )
  3238. {
  3239. bProcessed = true;
  3240. const char *res = msg->GetString( "result");
  3241. DevMsg( "CSysSessionConTeamHost: TeamReservationResult = %s\n", res );
  3242. if ( !Q_stricmp( "success", res ) )
  3243. {
  3244. Succeeded();
  3245. }
  3246. else
  3247. {
  3248. Failed();
  3249. }
  3250. }
  3251. break;
  3252. }
  3253. if ( !bProcessed )
  3254. {
  3255. CSysSessionBase::ReceiveMessage( msg, bValidatedLobbyMember, xuidSrc );
  3256. }
  3257. #endif
  3258. }
  3259. void CSysSessionConTeamHost::SendReservationRequest()
  3260. {
  3261. // Send reservation chat msg
  3262. KeyValues *reservation = new KeyValues( "SysSession::TeamReservation" );
  3263. KeyValues::AutoDelete autodelete( reservation );
  3264. int numTeamPlayers = m_pSettings->GetInt( "members/numPlayers" );
  3265. reservation->SetInt( "numPlayers", numTeamPlayers );
  3266. uint64 teamResKey = m_pSettings->GetUint64( "members/machine0/id", 0 );
  3267. reservation->SetUint64( "teamResKey", teamResKey );
  3268. DevMsg( "Sending res request with teamResKey == %llx\n ", teamResKey );
  3269. #if defined (_X360)
  3270. // Now contact the remote host
  3271. m_pNetworkMgr->ConnectionPeerSendConnectionless( 0ull, reservation );
  3272. #elif !defined (NO_STEAM)
  3273. CUtlBuffer buf;
  3274. buf.ActivateByteSwapping( !CByteswap::IsMachineBigEndian() );
  3275. buf.PutInt( g_pMatchExtensions->GetINetSupport()->GetEngineBuildNumber() );
  3276. reservation->WriteAsBinary( buf );
  3277. steamapicontext->SteamMatchmaking()->SendLobbyChatMsg( m_lobby.m_uiLobbyID, buf.Base(), buf.TellMaxPut() );
  3278. #endif
  3279. m_lastRequestSendTime = Plat_FloatTime();
  3280. }
  3281. void CSysSessionConTeamHost::Succeeded()
  3282. {
  3283. m_eState = STATE_DONE;
  3284. m_result = RESULT_SUCCESS;
  3285. }
  3286. void CSysSessionConTeamHost::Failed()
  3287. {
  3288. m_eState = STATE_DONE;
  3289. m_result = RESULT_FAIL;
  3290. }
  3291. #ifdef _X360
  3292. void CSysSessionConTeamHost::OnAsyncOperationFinished()
  3293. {
  3294. if (m_eState == STATE_WAITING_LOBBY_JOIN )
  3295. {
  3296. if ( m_pAsyncOperation->GetState() != AOS_SUCCEEDED )
  3297. {
  3298. Warning( "CSysSessionConTeamHost: Could not join lobby\n" );
  3299. ReleaseAsyncOperation();
  3300. Failed();
  3301. return;
  3302. }
  3303. //
  3304. // We have successfully created the client-side session,
  3305. // retrieve all information from the async creation.
  3306. //
  3307. m_lobby = m_pAsyncOperation->GetLobby();
  3308. ReleaseAsyncOperation();
  3309. // Initialize network manager
  3310. m_pNetworkMgr = new CX360NetworkMgr( this, GetX360NetSocket() );
  3311. if ( !m_pNetworkMgr->ConnectionPeerOpenActive( 0ull, m_lobby.m_xiInfo ) )
  3312. {
  3313. m_pNetworkMgr->Destroy();
  3314. m_pNetworkMgr = NULL;
  3315. Warning( "CSysSessionConTeamHost: ConnectionPeerOpenActive failed\n" );
  3316. Failed();
  3317. return;
  3318. }
  3319. m_eState = STATE_SEND_RESERVATION_REQUEST;
  3320. return;
  3321. }
  3322. else
  3323. {
  3324. ReleaseAsyncOperation();
  3325. }
  3326. }
  3327. #elif !defined( NO_STEAM )
  3328. void CSysSessionConTeamHost::Steam_OnLobbyEntered( LobbyEnter_t *pLobbyEnter )
  3329. {
  3330. // Filter out notifications not from our lobby
  3331. if ( pLobbyEnter->m_ulSteamIDLobby != m_lobby.m_uiLobbyID )
  3332. return;
  3333. m_CallbackOnLobbyEntered.Unregister();
  3334. if ( pLobbyEnter->m_EChatRoomEnterResponse != k_EChatRoomEnterResponseSuccess )
  3335. {
  3336. Warning( "CSysSessionConTeamHost: Could not join lobby\n" );
  3337. Failed();
  3338. }
  3339. else
  3340. {
  3341. m_eState = STATE_SEND_RESERVATION_REQUEST;
  3342. }
  3343. }
  3344. #endif
  3345. XUID CSysSessionConTeamHost::GetHostXuid( XUID xuidValidResult )
  3346. {
  3347. return 0ull;
  3348. }
  3349. #ifdef _X360
  3350. bool CSysSessionConTeamHost::OnX360NetConnectionlessPacket( netpacket_t *pkt, KeyValues *msg )
  3351. {
  3352. return true;
  3353. }
  3354. #endif