//===== Copyright © 1996-2009, Valve Corporation, All rights reserved. ======// // // Purpose: // //===========================================================================// #include "mm_framework.h" #include "fmtstr.h" // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" #ifdef _X360 // // Dormant operations // struct OverlappedDormant_t { XOVERLAPPED *m_pOverlapped; void ( *m_pfnCompletion )( XOVERLAPPED *, void * ); void *m_pvParam; }; static CUtlVector< OverlappedDormant_t > s_arrOverlappedDormants; struct ScheduledCall_t { float m_flTimeToCall; void ( *m_pfnCall )( ScheduledCall_t *pThis ); }; static CUtlVector< ScheduledCall_t * > s_arrScheduledCalls; static CUtlVector< IDormantOperation * > s_arrDormantInterfaces; XOVERLAPPED * MMX360_NewOverlappedDormant( void (*pfnCompletion)( XOVERLAPPED *, void * ), void *pvParam ) { XOVERLAPPED *pOverlapped = new XOVERLAPPED; memset( pOverlapped, 0, sizeof( XOVERLAPPED ) ); OverlappedDormant_t od = { pOverlapped, pfnCompletion, pvParam }; s_arrOverlappedDormants.AddToTail( od ); return pOverlapped; } void MMX360_RegisterDormant( IDormantOperation *pDormant ) { if ( !pDormant ) return; if ( s_arrDormantInterfaces.Find( pDormant ) != s_arrDormantInterfaces.InvalidIndex() ) return; s_arrDormantInterfaces.AddToTail( pDormant ); } void MMX360_UpdateDormantOperations() { int nQueuedOverlapped = s_arrOverlappedDormants.Count(); for ( int k = 0; k < s_arrOverlappedDormants.Count(); ++ k ) { OverlappedDormant_t od = s_arrOverlappedDormants[k]; if ( XHasOverlappedIoCompleted( od.m_pOverlapped ) ) { s_arrOverlappedDormants.FastRemove( k -- ); if ( od.m_pfnCompletion ) { (od.m_pfnCompletion)( od.m_pOverlapped, od.m_pvParam ); } delete od.m_pOverlapped; } } if ( nQueuedOverlapped && !s_arrOverlappedDormants.Count() ) { DevMsg( 2, "MMX360_UpdateDormantOperations finished all overlapped calls.\n" ); } int nQueuedScheduledCalls = s_arrScheduledCalls.Count(); for ( int k = 0; k < s_arrScheduledCalls.Count(); ++ k ) { ScheduledCall_t *pCall = s_arrScheduledCalls[k]; if ( Plat_FloatTime() > pCall->m_flTimeToCall ) { s_arrScheduledCalls.FastRemove( k -- ); ( pCall->m_pfnCall )( pCall ); } } if ( nQueuedScheduledCalls && !s_arrScheduledCalls.Count() ) { DevMsg( 2, "MMX360_UpdateDormantOperations finished all scheduled calls.\n" ); } int nDormantInterfaces = s_arrDormantInterfaces.Count(); for ( int k = 0; k < s_arrDormantInterfaces.Count(); ++ k ) { IDormantOperation *pDormant = s_arrDormantInterfaces[k]; bool bKeepRunning = pDormant->UpdateDormantOperation(); if ( !bKeepRunning ) s_arrDormantInterfaces.Remove( k -- ); } if ( nDormantInterfaces && !s_arrDormantInterfaces.Count() ) { DevMsg( 2, "MMX360_UpdateDormantOperations finished all dormant interfaces.\n" ); } } int MMX360_GetUserCtrlrIndex( XUID xuid ) { if ( !xuid ) return -1; for ( int k = 0; k < ( int ) XBX_GetNumGameUsers(); ++ k ) { int iCtrlr = XBX_GetUserId( k ); XUID xuidLocal = 0; if ( ERROR_SUCCESS == XUserGetXUID( iCtrlr, &xuidLocal ) ) { if ( xuidLocal == xuid ) return iCtrlr; } XUSER_SIGNIN_INFO xsi; if ( ERROR_SUCCESS == XUserGetSigninInfo( iCtrlr, XUSER_GET_SIGNIN_INFO_OFFLINE_XUID_ONLY, &xsi ) ) { if ( xsi.xuid == xuid ) return iCtrlr; } if ( ERROR_SUCCESS == XUserGetSigninInfo( iCtrlr, XUSER_GET_SIGNIN_INFO_ONLINE_XUID_ONLY, &xsi ) ) { if ( xsi.xuid == xuid ) return iCtrlr; } } return -1; } void MMX360_Helper_DataToString( void const *pvData, int numBytes, char *pchBuffer ) { for ( const unsigned char *pb = ( const unsigned char * ) pvData, *pbEnd = pb + numBytes; pb < pbEnd; ++ pb ) { *( pchBuffer ++ ) = '0' + ( unsigned char )( pb[0] & 0x0Fu ); *( pchBuffer ++ ) = '0' + ( unsigned char )( ( pb[0] >> 4 ) & 0x0Fu ); } *pchBuffer = 0; } void MMX360_Helper_DataFromString( void *pvData, int numBytes, char const *pchBuffer ) { if ( !pchBuffer ) goto parse_error; for ( unsigned char *pb = ( unsigned char * ) pvData, *pbEnd = pb + numBytes; pb < pbEnd; ++ pb ) { if ( !pchBuffer[0] || !pchBuffer[1] ) goto parse_error; pb[0] = ( unsigned char )( ( pchBuffer[0] - '0' ) & 0x0Fu ) | ( unsigned char )( ( ( pchBuffer[1] - '0' ) & 0x0Fu ) << 4 ); pchBuffer += 2; } return; parse_error: memset( pvData, 0, numBytes ); } void MMX360_SessionInfoToString( XSESSION_INFO const &xsi, char *pchBuffer ) { MMX360_Helper_DataToString( &xsi, sizeof( xsi ), pchBuffer ); } void MMX360_SessionInfoFromString( XSESSION_INFO &xsi, char const *pchBuffer ) { MMX360_Helper_DataFromString( &xsi, sizeof( xsi ), pchBuffer ); } void MMX360_XnaddrToString( XNADDR const &xsi, char *pchBuffer ) { MMX360_Helper_DataToString( &xsi, sizeof( xsi ), pchBuffer ); } void MMX360_XnaddrFromString( XNADDR &xsi, char const *pchBuffer ) { MMX360_Helper_DataFromString( &xsi, sizeof( xsi ), pchBuffer ); } // // Active gameplay sessions stack // // Only one session can be considered active gameplay session // and we will consider the last session that received XSessionStart // struct CX360ActiveGameplaySession_t { CX360ActiveGameplaySession_t( HANDLE hHandle ) { m_hHandle = hHandle; m_flLastFlushTime = Plat_FloatTime(); m_bNeedsFlush = false; } HANDLE m_hHandle; float m_flLastFlushTime; bool m_bNeedsFlush; }; static CUtlVector< CX360ActiveGameplaySession_t > s_arrActiveGameplaySessions; CX360ActiveGameplaySession_t * MMX360_GetActiveGameplaySession() { return s_arrActiveGameplaySessions.Count() ? &s_arrActiveGameplaySessions.Tail() : NULL; } // // Session stats flush scheduled call // struct ScheduledCall_FlushSessionStats : public ScheduledCall_t { ScheduledCall_FlushSessionStats() { m_flTimeToCall = Plat_FloatTime() + 0.1f; m_pfnCall = Callback; } protected: void Run() { // Walk all the scheduled sessions and XSessionFlushStats them if needed for ( int k = 0; k < s_arrActiveGameplaySessions.Count(); ++ k ) { CX360ActiveGameplaySession_t &ags = s_arrActiveGameplaySessions[k]; if ( !ags.m_bNeedsFlush ) continue; if ( Plat_FloatTime() < ags.m_flLastFlushTime + 6*60 ) // TCR: cannot flush within 5 minutes of last flush (use 6 minutes just in case) continue; // Schedule an overlapped flush ags.m_flLastFlushTime = Plat_FloatTime(); ags.m_bNeedsFlush = false; DevMsg( "ScheduledCall_FlushSessionStats for session %p (@%.2f)\n", ags.m_hHandle, ags.m_flLastFlushTime ); g_pMatchExtensions->GetIXOnline()->XSessionFlushStats( ags.m_hHandle, MMX360_NewOverlappedDormant() ); } delete this; } static void Callback( ScheduledCall_t *pThis ) { ( ( ScheduledCall_FlushSessionStats * )( pThis ) )->Run(); } }; // // Leaderboard async requests implementation // class CLeaderboardX360_Writer : public IX360LeaderboardBatchWriter { public: explicit CLeaderboardX360_Writer( XUID xuidGamer ); public: virtual void AddProperty( int dwViewId, XUSER_PROPERTY const &xp ); virtual void WriteBatchAndDestroy(); protected: bool WriteBatch(); void Destroy(); static void OnXSessionWriteStatsCompleted( XOVERLAPPED *, void *pvThis ) { ( ( CLeaderboardX360_Writer * )pvThis )->Destroy(); } public: CUtlVector< XSESSION_VIEW_PROPERTIES > m_arrLbViews; CUtlVectorFixed< XUSER_PROPERTY, 64 > m_arrLbProperties; HANDLE m_hSession; XUID m_xuidGamer; }; IX360LeaderboardBatchWriter * MMX360_CreateLeaderboardBatchWriter( XUID xuidGamer ) { if ( !MMX360_GetActiveGameplaySession() ) { Warning( "MMX360_CreateLeaderboardBatchWriter called with no active gameplay session!\n" ); Assert( 0 ); return NULL; } return new CLeaderboardX360_Writer( xuidGamer ); } CLeaderboardX360_Writer::CLeaderboardX360_Writer( XUID xuidGamer ) { CX360ActiveGameplaySession_t *ags = MMX360_GetActiveGameplaySession(); m_hSession = ags ? ags->m_hHandle : NULL; m_xuidGamer = xuidGamer; } void CLeaderboardX360_Writer::AddProperty( int dwViewId, XUSER_PROPERTY const &xp ) { if ( m_arrLbProperties.Count() >= 63 ) { Warning( "CLeaderboardX360_Writer: exceeded number of properties for update!\n" ); return; } m_arrLbProperties.AddMultipleToTail( 1, &xp ); XUSER_PROPERTY *xProp = &m_arrLbProperties.Tail(); if ( !m_arrLbViews.Count() || m_arrLbViews.Tail().dwViewId != ( DWORD ) dwViewId ) { m_arrLbViews.AddMultipleToTail( 1 ); XSESSION_VIEW_PROPERTIES *xView = &m_arrLbViews.Tail(); xView->dwViewId = dwViewId; xView->dwNumProperties = 0; xView->pProperties = xProp; } ++ m_arrLbViews.Tail().dwNumProperties; } bool CLeaderboardX360_Writer::WriteBatch() { if ( !m_arrLbViews.Count() ) return false; CX360ActiveGameplaySession_t &ags = *MMX360_GetActiveGameplaySession(); if ( !&ags || !ags.m_hHandle ) { Warning( "CLeaderboardX360_Writer::WriteBatch called without active gameplay session!\n" ); Assert( 0 ); return false; } if ( ags.m_hHandle != m_hSession ) { Warning( "CLeaderboardX360_Writer::WriteBatch called when active gameplay session changed!\n" ); Assert( 0 ); return false; } DWORD ret = g_pMatchExtensions->GetIXOnline()->XSessionWriteStats( ags.m_hHandle, m_xuidGamer, m_arrLbViews.Count(), m_arrLbViews.Base(), MMX360_NewOverlappedDormant( OnXSessionWriteStatsCompleted, this ) ); if ( ret != ERROR_SUCCESS && ret != ERROR_IO_PENDING ) { Warning( "XSessionWriteStats failed, code %d, xuid=%llx, numViews=%d, numProps=%d\n", ret, m_xuidGamer, m_arrLbViews.Count(), m_arrLbProperties.Count() ); return false; } // Mark the session as needing a flush when the TCR allows us to flush session stats ags.m_bNeedsFlush = true; DevMsg( "XSessionWriteStats for session %p (%.2f, last flush @%.2f)\n", ags.m_hHandle, Plat_FloatTime(), ags.m_flLastFlushTime ); return true; } void CLeaderboardX360_Writer::Destroy() { m_hSession = NULL; delete this; // Submit a scheduled call to flush session stats for all previously written stats that might need a flush DevMsg( "ScheduledCall_FlushSessionStats @%.2f\n", Plat_FloatTime() ); s_arrScheduledCalls.AddToTail( new ScheduledCall_FlushSessionStats ); } void CLeaderboardX360_Writer::WriteBatchAndDestroy() { if ( !WriteBatch() ) Destroy(); } // // Basic async operation on a lobby // class CX360LobbyAsyncOperation : public IX360LobbyAsyncOperation, public IDormantOperation { public: CX360LobbyAsyncOperation() : m_eState( AOS_RUNNING ), m_result( 0 ), m_pCancelOverlappedJob( NULL ) { memset( &m_xOverlapped, 0, sizeof( m_xOverlapped ) ); } virtual ~CX360LobbyAsyncOperation() { ; } // // IMatchAsyncOperation // public: // Poll if operation has completed virtual bool IsFinished() { return m_eState >= AOS_ABORTED; } // Operation state virtual AsyncOperationState_t GetState() { return m_eState; } // Retrieve a generic completion result for simple operations // that return simple results upon success, // results are operation-specific, may result in undefined behavior // if operation is still in progress. virtual uint64 GetResult() { return m_result; } // Request operation to be aborted virtual void Abort(); // Release the operation interface and all resources // associated with the operation. Operation callbacks // will not be called after Release. Operation object // cannot be accessed after Release. virtual void Release(); // // Overrides // public: virtual CX360LobbyObject const & GetLobby() { return m_lobby; } virtual void Update(); virtual void OnFinished( DWORD dwRetCode, DWORD dwResult ); virtual bool UpdateDormantOperation(); public: AsyncOperationState_t m_eState; XOVERLAPPED m_xOverlapped; uint64 m_result; CX360LobbyObject m_lobby; CJob *m_pCancelOverlappedJob; }; void CX360LobbyAsyncOperation::Abort() { Update(); if ( m_eState != AOS_RUNNING ) return; m_eState = AOS_ABORTING; m_pCancelOverlappedJob = ThreadExecute( MMX360_CancelOverlapped, &m_xOverlapped ); m_eState = AOS_ABORTED; } void CX360LobbyAsyncOperation::Release() { if ( !IsFinished() ) { Abort(); } if ( m_pCancelOverlappedJob ) MMX360_RegisterDormant( this ); else delete this; } void CX360LobbyAsyncOperation::Update() { if ( IsFinished() ) return; // Check if the operation has completed if ( !XHasOverlappedIoCompleted( &m_xOverlapped ) ) return; DWORD dwResult; DWORD dwRetCode = XGetOverlappedResult( &m_xOverlapped, &dwResult, FALSE ); m_eState = ( dwRetCode == ERROR_SUCCESS ) ? AOS_SUCCEEDED : AOS_FAILED; OnFinished( dwRetCode, dwResult ); } bool CX360LobbyAsyncOperation::UpdateDormantOperation() { if ( !m_pCancelOverlappedJob->IsFinished() ) return true; // keep running dormant m_pCancelOverlappedJob->Release(); m_pCancelOverlappedJob = NULL; delete this; return false; // destroyed object, remove from dormant list } void CX360LobbyAsyncOperation::OnFinished( DWORD dwRetCode, DWORD dwResult ) { m_result = dwResult; } class CX360LobbyQosOperation : public CX360LobbyAsyncOperation { public: CX360LobbyQosOperation() : m_pXnQos( NULL ) {} public: virtual void Abort(); virtual void Update(); public: XNQOS *m_pXnQos; }; void CX360LobbyQosOperation::Abort() { Update(); if ( m_eState != AOS_RUNNING ) return; g_pMatchExtensions->GetIXOnline()->XNetQosRelease( m_pXnQos ); m_pXnQos = NULL; m_eState = AOS_ABORTED; } void CX360LobbyQosOperation::Update() { if ( IsFinished() ) return; // Check if the QOS is not running if ( !m_pXnQos ) { m_eState = AOS_FAILED; OnFinished( ERROR_SUCCESS, ERROR_SUCCESS ); return; } // Check if still pending if ( m_pXnQos->cxnqosPending > 0 ) return; bool bSuccess = true; BYTE uNeedFlags = XNET_XNQOSINFO_TARGET_CONTACTED | XNET_XNQOSINFO_DATA_RECEIVED; for ( uint k = 0; k < m_pXnQos->cxnqos; ++ k ) { XNQOSINFO &xqi = m_pXnQos->axnqosinfo[k]; if ( ( ( xqi.bFlags & uNeedFlags ) != uNeedFlags) || ( xqi.bFlags & XNET_XNQOSINFO_TARGET_DISABLED ) || !xqi.cbData || !xqi.pbData ) { bSuccess = false; break; } } m_eState = ( bSuccess ) ? AOS_SUCCEEDED : AOS_FAILED; DWORD dwCode = ( bSuccess ) ? ERROR_SUCCESS : ERROR_FILE_NOT_FOUND; OnFinished( dwCode, dwCode ); // Release the QOS g_pMatchExtensions->GetIXOnline()->XNetQosRelease( m_pXnQos ); m_pXnQos = NULL; } template < typename TBase = CX360LobbyAsyncOperation > class CX360AsyncOperationDelegating : public TBase { public: typedef CX360AsyncOperationDelegating TX360AsyncOperationDelegating; typedef TBase TX360AsyncOperationDelegatingBase; public: CX360AsyncOperationDelegating() : m_pDelegate( NULL ) { } // // IMatchAsyncOperation // public: // Poll if operation has completed virtual bool IsFinished() { return m_pDelegate ? m_pDelegate->IsFinished() : TBase::IsFinished(); } // Operation state virtual AsyncOperationState_t GetState() { return m_pDelegate ? m_pDelegate->GetState() : TBase::GetState(); } // Retrieve a generic completion result for simple operations // that return simple results upon success, // results are operation-specific, may result in undefined behavior // if operation is still in progress. virtual uint64 GetResult() { return m_pDelegate ? m_pDelegate->GetResult() : TBase::GetResult(); } // Request operation to be aborted virtual void Abort() { if ( m_pDelegate ) m_pDelegate->Abort(); else TBase::Abort(); } // Release the operation interface and all resources // associated with the operation. Operation callbacks // will not be called after Release. Operation object // cannot be accessed after Release. virtual void Release() { if ( m_pDelegate ) m_pDelegate->Release(); m_pDelegate = NULL; TBase::Release(); } // // Overrides // public: virtual CX360LobbyObject const & GetLobby() { if ( m_pDelegate ) return m_pDelegate->GetLobby(); else return TBase::GetLobby(); } virtual void Update() { if ( m_pDelegate ) m_pDelegate->Update(); else TBase::Update(); } public: IX360LobbyAsyncOperation *m_pDelegate; }; // // Deleting a lobby // class CX360LobbyAsyncOperation_LobbyDelete : public CX360LobbyAsyncOperation { public: explicit CX360LobbyAsyncOperation_LobbyDelete( CX360LobbyObject &lobby ) { m_lobby = lobby; // Submit XSessionDelete DWORD ret = g_pMatchExtensions->GetIXOnline()->XSessionDelete( m_lobby.m_hHandle, &m_xOverlapped ); if ( ret != ERROR_IO_PENDING ) { DevWarning( "XSessionDelete failed (code 0x%08X)\n", ret ); Assert( 0 ); m_eState = AOS_FAILED; m_result = ret; } } virtual void Abort() { DevWarning( "XSessionDelete cannot be aborted!\n" ); Assert( 0 ); } virtual void OnFinished( DWORD dwRetCode, DWORD dwResult ) { CX360LobbyAsyncOperation::OnFinished( dwRetCode, dwResult ); CloseHandle( m_lobby.m_hHandle ); } }; void MMX360_LobbyDelete( CX360LobbyObject &lobby, IX360LobbyAsyncOperation **ppOperation ) { Assert( lobby.m_hHandle ); Assert( ppOperation ); if ( ppOperation ) *ppOperation = NULL; else return; // Release QOS listener g_pMatchExtensions->GetIXOnline()->XNetQosListen( &lobby.m_xiInfo.sessionID, 0, 0, 0, XNET_QOS_LISTEN_RELEASE ); if ( lobby.m_bXSessionStarted ) { DevWarning( "LobbyDelete called on an active gameplay session, forcing XSessionEnd!\n" ); // TODO: investigate whether this is not resulting in any side-effects in arbitrated ranked games // especially when the host leaves the game. MMX360_LobbySetActiveGameplayState( lobby, false, NULL ); } *ppOperation = new CX360LobbyAsyncOperation_LobbyDelete( lobby ); lobby = CX360LobbyObject(); } // // Describing lobby flags // CX360LobbyFlags_t MMX360_DescribeLobbyFlags( KeyValues *pSettings, bool bHost, bool bWantLocked ) { CX360LobbyFlags_t fl = {0}; char const *szLock = pSettings->GetString( "system/lock", NULL ); char const *szNetwork = pSettings->GetString( "system/network", "LIVE" ); char const *szAccess = pSettings->GetString( "system/access", "public" ); char const *szNetFlag = pSettings->GetString( "system/netflag", NULL ); int numSlots = pSettings->GetInt( "members/numSlots", XBX_GetNumGameUsers() ); // Gametype fl.m_dwGameType = X_CONTEXT_GAME_TYPE_STANDARD; // Flags if ( !Q_stricmp( szNetwork, "LIVE" ) ) { fl.m_dwFlags = XSESSION_CREATE_LIVE_MULTIPLAYER_STANDARD; if ( szNetFlag ) { if ( !Q_stricmp( szNetFlag, "teamlobby" ) ) { fl.m_dwFlags = XSESSION_CREATE_USES_PRESENCE | XSESSION_CREATE_USES_PEER_NETWORK; } else if ( !Q_stricmp( szNetFlag, "teamlink" ) ) { fl.m_dwFlags = /*XSESSION_CREATE_USES_STATS |*/ XSESSION_CREATE_USES_MATCHMAKING | XSESSION_CREATE_USES_PEER_NETWORK; } } } else if ( !Q_stricmp( szNetwork, "lan" ) ) { fl.m_dwFlags = XSESSION_CREATE_SYSTEMLINK; } else { fl.m_dwFlags = XSESSION_CREATE_SINGLEPLAYER_WITH_STATS; } // Whether session can be disabled in XUI fl.m_bCanLockJoins = ( ( fl.m_dwFlags & XSESSION_CREATE_USES_PRESENCE ) == XSESSION_CREATE_USES_PRESENCE ); // Hosting if ( bHost ) { fl.m_dwFlags |= XSESSION_CREATE_HOST; } bool bPublic = true; if ( fl.m_bCanLockJoins ) { if ( bWantLocked || ( szLock && *szLock && !IsX360() ) ) { bPublic = false; fl.m_dwFlags |= ( XSESSION_CREATE_INVITES_DISABLED | XSESSION_CREATE_JOIN_VIA_PRESENCE_DISABLED | XSESSION_CREATE_JOIN_IN_PROGRESS_DISABLED ); } else if ( !Q_stricmp( "private", szAccess ) ) { bPublic = false; fl.m_dwFlags |= XSESSION_CREATE_JOIN_VIA_PRESENCE_DISABLED; } else if ( !Q_stricmp( "friends", szAccess ) ) { fl.m_dwFlags |= XSESSION_CREATE_JOIN_VIA_PRESENCE_FRIENDS_ONLY; } } fl.m_numPublicSlots = bPublic ? numSlots : 0; fl.m_numPrivateSlots = bPublic ? 0 : numSlots; return fl; } // // Lobby creation // class CX360LobbyAsyncOperation_LobbyCreate : public CX360LobbyAsyncOperation { public: explicit CX360LobbyAsyncOperation_LobbyCreate( CX360LobbyFlags_t const &fl ) { // Set the required contexts for ( int k = 0; k < ( int ) XBX_GetNumGameUsers(); ++ k ) { int iCtrlr = XBX_GetUserId( k ); XUserSetContext( iCtrlr, X_CONTEXT_GAME_TYPE, fl.m_dwGameType ); } // Submit XSessionCreate DWORD ret = g_pMatchExtensions->GetIXOnline()->XSessionCreate( fl.m_dwFlags, XBX_GetPrimaryUserId(), fl.m_numPublicSlots, fl.m_numPrivateSlots, &m_lobby.m_uiNonce, &m_lobby.m_xiInfo, &m_xOverlapped, &m_lobby.m_hHandle ); if ( ret != ERROR_IO_PENDING ) { DevWarning( "XSessionCreate failed (code 0x%08X)\n", ret ); Assert( 0 ); m_eState = AOS_FAILED; m_result = ret; } } virtual void OnFinished( DWORD dwRetCode, DWORD dwResult ) { CX360LobbyAsyncOperation::OnFinished( dwRetCode, dwResult ); if ( GetState() == AOS_SUCCEEDED ) { // Enable QOS listener when session creation succeeds g_pMatchExtensions->GetIXOnline()->XNetQosListen( &m_lobby.m_xiInfo.sessionID, 0, 0, 0, XNET_QOS_LISTEN_ENABLE ); } } }; void MMX360_LobbyCreate( KeyValues *pSettings, IX360LobbyAsyncOperation **ppOperation ) { Assert( pSettings ); Assert( ppOperation ); if ( ppOperation ) *ppOperation = NULL; else return; if ( !pSettings ) return; // Determine parameters CX360LobbyFlags_t fl = MMX360_DescribeLobbyFlags( pSettings, true, true ); if ( KeyValues *pKv = g_pMMF->GetMatchTitleGameSettingsMgr()->PrepareForSessionCreate( pSettings ) ) { pKv->deleteThis(); } // Allocate our lobby object *ppOperation = new CX360LobbyAsyncOperation_LobbyCreate( fl ); } // // Lobby connect operation // class CX360LobbyAsyncOperation_LobbyConnect : public CX360LobbyAsyncOperation { public: explicit CX360LobbyAsyncOperation_LobbyConnect( KeyValues *pHostSettings, XSESSION_INFO const &xsi ) { m_lobby.m_xiInfo = xsi; // Prepare for lobby create CX360LobbyFlags_t fl = MMX360_DescribeLobbyFlags( pHostSettings, false, true ); if ( KeyValues *pKv = g_pMMF->GetMatchTitleGameSettingsMgr()->PrepareForSessionCreate( pHostSettings ) ) { pKv->deleteThis(); } // Set the required contexts for ( int k = 0; k < ( int ) XBX_GetNumGameUsers(); ++ k ) { int iCtrlr = XBX_GetUserId( k ); XUserSetContext( iCtrlr, X_CONTEXT_GAME_TYPE, fl.m_dwGameType ); } // Submit XSessionCreate DWORD ret = g_pMatchExtensions->GetIXOnline()->XSessionCreate( fl.m_dwFlags, XBX_GetPrimaryUserId(), fl.m_numPublicSlots, fl.m_numPrivateSlots, &m_lobby.m_uiNonce, &m_lobby.m_xiInfo, &m_xOverlapped, &m_lobby.m_hHandle ); if ( ret != ERROR_IO_PENDING ) { DevWarning( "XSessionCreate failed (code 0x%08X)\n", ret ); Assert( 0 ); m_eState = AOS_FAILED; m_result = ret; } } }; class CX360LobbyAsyncOperation_LobbyQosAndConnect : public CX360AsyncOperationDelegating< CX360LobbyQosOperation > { public: explicit CX360LobbyAsyncOperation_LobbyQosAndConnect( XSESSION_INFO const &xsi ) : m_xsiQosInfo( xsi ) { // Perform the QOS lookup m_pXnaddr = &m_xsiQosInfo.hostAddress; m_pXnkid = &m_xsiQosInfo.sessionID; m_pXnkey = &m_xsiQosInfo.keyExchangeKey; INT ret = g_pMatchExtensions->GetIXOnline()->XNetQosLookup( 1, &m_pXnaddr, &m_pXnkid, &m_pXnkey, 0, NULL, NULL, 2, 0, 0, NULL, &m_pXnQos ); if ( 0 != ret ) { DevWarning( "XNetQosLookup failed (code 0x%08X)\n", ret ); Assert( 0 ); m_eState = AOS_FAILED; m_result = ERROR_NO_SUCH_PRIVILEGE; return; } } public: virtual void OnFinished( DWORD dwRetCode, DWORD dwResult ) { TX360AsyncOperationDelegating::OnFinished( dwRetCode, dwResult ); if ( m_eState == AOS_SUCCEEDED && !m_pDelegate ) { // We successfully contacted the QOS host and obtained the data XNQOSINFO &xqi = m_pXnQos->axnqosinfo[0]; MM_GameDetails_QOS_t gd = { xqi.pbData, xqi.cbData, xqi.wRttMedInMsecs }; KeyValues *pHostGameDetails = g_pMatchFramework->GetMatchNetworkMsgController()->UnpackGameDetailsFromQOS( &gd ); KeyValues::AutoDelete autodelete_pHostGameDetails( pHostGameDetails ); // Output unpacked game details from host KeyValuesDumpAsDevMsg( pHostGameDetails, 1, 2 ); m_pDelegate = new CX360LobbyAsyncOperation_LobbyConnect( pHostGameDetails, m_xsiQosInfo ); return; } Assert( !m_pDelegate ); m_eState = AOS_FAILED; } protected: XSESSION_INFO m_xsiQosInfo; XNADDR const *m_pXnaddr; XNKID const *m_pXnkid; XNKEY const *m_pXnkey; }; class CX360LobbyAsyncOperation_LobbyDiscoverAndQosAndConnect : public CX360AsyncOperationDelegating< CX360LobbyAsyncOperation > { public: explicit CX360LobbyAsyncOperation_LobbyDiscoverAndQosAndConnect( uint64 const &uiSessionId ) { XNKID xnkid = ( XNKID const & ) uiSessionId; int iCtrlr = XBX_GetPrimaryUserId(); DWORD dwSize = 0; DWORD ret = g_pMatchExtensions->GetIXOnline()->XSessionSearchByID( xnkid, iCtrlr, &dwSize, NULL, NULL ); if( ret != ERROR_INSUFFICIENT_BUFFER ) { DevWarning( "XSessionSearchByID failed to determine size (code 0x%08X)\n", ret ); Assert( 0 ); m_eState = AOS_FAILED; m_result = ret; return; } // Allocate buffer m_bufSearchResultHeader.EnsureCapacity( dwSize ); ZeroMemory( m_bufSearchResultHeader.Base(), dwSize ); ret = g_pMatchExtensions->GetIXOnline()->XSessionSearchByID( xnkid, iCtrlr, &dwSize, (XSESSION_SEARCHRESULT_HEADER *) m_bufSearchResultHeader.Base(), &m_xOverlapped ); if( ret != ERROR_IO_PENDING ) { DevWarning( "XSessionSearchByID failed to initiate search (code 0x%08X)\n", ret ); Assert( 0 ); m_eState = AOS_FAILED; m_result = ret; return; } } public: virtual void OnFinished( DWORD dwRetCode, DWORD dwResult ) { TX360AsyncOperationDelegating::OnFinished( dwRetCode, dwResult ); if ( m_eState == AOS_SUCCEEDED && !m_pDelegate ) { // We successfully resolved our XNKID XSESSION_SEARCHRESULT_HEADER *xsh = ( XSESSION_SEARCHRESULT_HEADER * ) m_bufSearchResultHeader.Base(); if( xsh->dwSearchResults >= 1) { m_lobby.m_xiInfo = xsh->pResults[0].info; m_pDelegate = new CX360LobbyAsyncOperation_LobbyQosAndConnect( m_lobby.m_xiInfo ); return; } } Assert( !m_pDelegate ); m_eState = AOS_FAILED; } protected: CUtlBuffer m_bufSearchResultHeader; }; void MMX360_LobbyConnect( KeyValues *pSettings, IX360LobbyAsyncOperation **ppOperation ) { Assert( pSettings ); Assert( ppOperation ); if ( ppOperation ) *ppOperation = NULL; else return; if ( !pSettings ) return; // Parse the information from the settings uint64 uiSessionID = pSettings->GetUint64( "options/sessionid", 0ull ); char const *szSessionInfo = pSettings->GetString( "options/sessioninfo", NULL ); KeyValues *pSessionHostData = ( KeyValues * ) pSettings->GetPtr( "options/sessionHostData", NULL ); // Handle the case when we have session info and it is up to date XSESSION_INFO xsi = {0}; if ( szSessionInfo ) MMX360_SessionInfoFromString( xsi, szSessionInfo ); if ( ( const uint64 & ) xsi.sessionID != ( const uint64 & ) uiSessionID ) { // Need to discover session info first *ppOperation = new CX360LobbyAsyncOperation_LobbyDiscoverAndQosAndConnect( uiSessionID ); return; } if ( !pSessionHostData && !Q_stricmp( "LIVE", pSettings->GetString( "system/network" ) ) ) { // QOS and connect using the host-supplied information *ppOperation = new CX360LobbyAsyncOperation_LobbyQosAndConnect( xsi ); return; } // Otherwise use the client-settings hoping that they match the host-settings if ( !pSessionHostData ) pSessionHostData = pSettings; // Connect immediately with session information that we have *ppOperation = new CX360LobbyAsyncOperation_LobbyConnect( pSessionHostData, xsi ); } // // Host migration implementation // struct CX360LobbyMigrateHandleImpl { CX360LobbyMigrateOperation_t *m_pListener; XSESSION_INFO m_xsi; static void Finished( XOVERLAPPED *pxOverlapped, void *pvThis ) { CX360LobbyMigrateHandleImpl *pHandle = ( CX360LobbyMigrateHandleImpl * ) pvThis; if ( pHandle->m_pListener ) { XGetOverlappedResult( pxOverlapped, &pHandle->m_pListener->m_ret, FALSE ); pHandle->m_pListener->m_pLobby->m_xiInfo = pHandle->m_xsi; pHandle->m_pListener->m_bFinished = true; } delete pHandle; } }; CX360LobbyMigrateHandle_t MMX360_LobbyMigrateHost( CX360LobbyObject &lobby, CX360LobbyMigrateOperation_t *pOperation ) { if ( !XNetXnKidIsSystemLink( &lobby.m_xiInfo.sessionID ) ) { CX360LobbyMigrateHandleImpl *pHandleImpl = new CX360LobbyMigrateHandleImpl; pHandleImpl->m_pListener = pOperation; pHandleImpl->m_xsi = lobby.m_xiInfo; if ( pOperation ) { pOperation->m_bFinished = false; pOperation->m_ret = ERROR_IO_PENDING; pOperation->m_pLobby = &lobby; } // Submit XSessionMigrateHost DWORD ret = g_pMatchExtensions->GetIXOnline()->XSessionMigrateHost( lobby.m_hHandle, XBX_GetPrimaryUserId(), &pHandleImpl->m_xsi, MMX360_NewOverlappedDormant( CX360LobbyMigrateHandleImpl::Finished, pHandleImpl ) ); if ( ret != ERROR_SUCCESS && ret != ERROR_IO_PENDING ) { DevWarning( "XSessionMigrateHost(hosting) failed (code 0x%08X)\n", ret ); Assert( 0 ); } return pHandleImpl; } else { DevWarning( "XSessionMigrateHost(hosting) unavailable because the session is SystemLink\n" ); return NULL; } } CX360LobbyMigrateHandle_t MMX360_LobbyMigrateClient( CX360LobbyObject &lobby, XSESSION_INFO const &xsiNewHost, CX360LobbyMigrateOperation_t *pOperation ) { if ( !XNetXnKidIsSystemLink( &lobby.m_xiInfo.sessionID ) ) { CX360LobbyMigrateHandleImpl *pHandleImpl = new CX360LobbyMigrateHandleImpl; pHandleImpl->m_pListener = pOperation; pHandleImpl->m_xsi = xsiNewHost; if ( pOperation ) { pOperation->m_bFinished = false; pOperation->m_ret = ERROR_IO_PENDING; pOperation->m_pLobby = &lobby; } // Submit XSessionMigrateHost DWORD ret = g_pMatchExtensions->GetIXOnline()->XSessionMigrateHost( lobby.m_hHandle, XUSER_INDEX_NONE, &pHandleImpl->m_xsi, MMX360_NewOverlappedDormant( CX360LobbyMigrateHandleImpl::Finished, pHandleImpl ) ); if ( ret != ERROR_SUCCESS && ret != ERROR_IO_PENDING ) { DevWarning( "XSessionMigrateHost(client) failed (code 0x%08X)\n", ret ); Assert( 0 ); } return pHandleImpl; } else { DevWarning( "XSessionMigrateHost(client) unavailable because the session is SystemLink\n" ); return NULL; } } CX360LobbyMigrateOperation_t * MMX360_LobbyMigrateSetListener( CX360LobbyMigrateHandle_t hMigrateCall, CX360LobbyMigrateOperation_t *pOperation ) { CX360LobbyMigrateHandleImpl *pHandle = ( CX360LobbyMigrateHandleImpl * ) hMigrateCall; CX360LobbyMigrateOperation_t *pOldListener = NULL; if ( pHandle ) { pOldListener = pHandle->m_pListener; if ( pOperation && pHandle->m_pListener ) *pOperation = *pHandle->m_pListener; pHandle->m_pListener = pOperation; } return pOldListener; } // // Join and Leave implementation // static DWORD MMX360_Helper_LobbyJoin( CX360LobbyObject &lobby, XUID xuid ) { static const BOOL bPrivate = TRUE; int iCtrlr = MMX360_GetUserCtrlrIndex( xuid ); DevMsg( "Session %llx: ADDED %llx%s\n", lobby.m_uiNonce, xuid, ( iCtrlr >= 0 ) ? " local" : "" ); if ( iCtrlr >= 0 ) { DWORD *pdwUserIndex = new DWORD( iCtrlr ); return g_pMatchExtensions->GetIXOnline()->XSessionJoinLocal( lobby.m_hHandle, 1, pdwUserIndex, &bPrivate, MMX360_NewOverlappedDormant( OnCompleted_DeleteData< DWORD >, pdwUserIndex ) ); } else { XUID *pXuids = new XUID( xuid ); return g_pMatchExtensions->GetIXOnline()->XSessionJoinRemote( lobby.m_hHandle, 1, pXuids, &bPrivate, MMX360_NewOverlappedDormant( OnCompleted_DeleteData< XUID >, pXuids ) ); } } static DWORD MMX360_Helper_LobbyLeave( CX360LobbyObject &lobby, XUID xuid ) { int iCtrlr = MMX360_GetUserCtrlrIndex( xuid ); DevMsg( "Session %llx: LEAVE %llx%s\n", lobby.m_uiNonce, xuid, ( iCtrlr >= 0 ) ? " local" : "" ); if ( iCtrlr >= 0 ) { DWORD *pdwUserIndex = new DWORD( iCtrlr ); return g_pMatchExtensions->GetIXOnline()->XSessionLeaveLocal( lobby.m_hHandle, 1, pdwUserIndex, MMX360_NewOverlappedDormant( OnCompleted_DeleteData< DWORD >, pdwUserIndex ) ); } else { XUID *pXuids = new XUID( xuid ); return g_pMatchExtensions->GetIXOnline()->XSessionLeaveRemote( lobby.m_hHandle, 1, pXuids, MMX360_NewOverlappedDormant( OnCompleted_DeleteData< XUID >, pXuids ) ); } } static void MMX360_Helper_LobbyJoinLeaveMembers( KeyValues *pSettings, CX360LobbyObject &lobby, DWORD (*pfn)( CX360LobbyObject&, XUID ), int idxMachineStart, int idxMachineEnd ) { if ( !pSettings ) return; KeyValues *kvMembers = pSettings->FindKey( "members" ); if ( !kvMembers ) return; int numMachines = kvMembers->GetInt( "numMachines", 0 ); if ( idxMachineEnd < 0 || idxMachineEnd >= numMachines ) idxMachineEnd = numMachines - 1; for ( int k = idxMachineStart; k <= idxMachineEnd; ++ k ) { KeyValues *kvMachine = kvMembers->FindKey( CFmtStr( "machine%d", k ) ); if ( !kvMachine ) continue; int numPlayers = kvMachine->GetInt( "numPlayers", 0 ); for ( int j = 0; j < numPlayers; ++ j ) { KeyValues *kvPlayer = kvMachine->FindKey( CFmtStr( "player%d", j ) ); if ( !kvPlayer ) continue; XUID xuid = kvPlayer->GetUint64( "xuid", 0ull ); if ( !xuid ) continue; DWORD ret = pfn( lobby, xuid ); if ( ERROR_SUCCESS != ret && ERROR_IO_PENDING != ret ) { DevWarning( "XSessionJoin/Leave (pfn=%p) failed (code 0x%08X)!\n", pfn, ret ); Assert( 0 ); } } } } void MMX360_LobbyJoinMembers( KeyValues *pSettings, CX360LobbyObject &lobby, int idxMachineStart, int idxMachineEnd ) { MMX360_Helper_LobbyJoinLeaveMembers( pSettings, lobby, MMX360_Helper_LobbyJoin, idxMachineStart, idxMachineEnd ); } void MMX360_LobbyLeaveMembers( KeyValues *pSettings, CX360LobbyObject &lobby, int idxMachineStart, int idxMachineEnd ) { MMX360_Helper_LobbyJoinLeaveMembers( pSettings, lobby, MMX360_Helper_LobbyLeave, idxMachineStart, idxMachineEnd ); } bool MMX360_LobbySetActiveGameplayState( CX360LobbyObject &lobby, bool bActive, char const *szSecureServerAddress ) { if ( bActive == lobby.m_bXSessionStarted ) { DevWarning( "LobbySetActiveGameplayState called in same state '%s', ignored!\n", bActive ? "active" : "inactive" ); Assert( bActive != lobby.m_bXSessionStarted ); return false; } DWORD ret = ERROR_SUCCESS; if ( bActive ) { g_pMatchFramework->GetEventsSubscription()->BroadcastEvent( new KeyValues( "OnProfilesWriteOpportunity", "reason", "sessionstart" ) ); ret = g_pMatchExtensions->GetIXOnline()->XSessionStart( lobby.m_hHandle, 0, MMX360_NewOverlappedDormant() ); // Parse the address if ( szSecureServerAddress ) { if ( char const *szExternalPeer = StringAfterPrefix( szSecureServerAddress, "SESSIONINFO " ) ) { MMX360_SessionInfoFromString( lobby.m_xiExternalPeer, szExternalPeer ); } else { netadr_t na; na.SetFromString( szSecureServerAddress ); lobby.m_inaSecure.s_addr = na.GetIPNetworkByteOrder(); } } } else { g_pMatchFramework->GetEventsSubscription()->BroadcastEvent( new KeyValues( "OnProfilesWriteOpportunity", "reason", "sessionend" ) ); // Asynchronous call to XSessionEnd ret = g_pMatchExtensions->GetIXOnline()->XSessionEnd( lobby.m_hHandle, MMX360_NewOverlappedDormant() ); if ( lobby.m_inaSecure.s_addr ) { g_pMatchExtensions->GetIXOnline()->XNetUnregisterInAddr( lobby.m_inaSecure ); lobby.m_inaSecure.s_addr = 0; } if ( 0ull != ( const uint64 & ) lobby.m_xiExternalPeer.sessionID ) { g_pMatchExtensions->GetIXOnline()->XNetUnregisterKey( &lobby.m_xiExternalPeer.sessionID ); memset( &lobby.m_xiExternalPeer, 0, sizeof( lobby.m_xiExternalPeer ) ); } } if ( ret != ERROR_SUCCESS && ret != ERROR_IO_PENDING ) { DevWarning( "LobbySetActiveGameplayState failed to become '%s', code = 0x%08X!\n", bActive ? "active" : "inactive", ret ); return false; } else { lobby.m_bXSessionStarted = bActive; // Update active gameplay sessions stack CX360ActiveGameplaySession_t ags( lobby.m_hHandle ); for ( int k = 0; k < s_arrActiveGameplaySessions.Count(); ++ k ) { CX360ActiveGameplaySession_t const &x = s_arrActiveGameplaySessions[k]; if ( x.m_hHandle == ags.m_hHandle ) { s_arrActiveGameplaySessions.Remove( k -- ); } } if ( lobby.m_bXSessionStarted ) { s_arrActiveGameplaySessions.AddToTail( ags ); } return true; } } void MMX360_CancelOverlapped( XOVERLAPPED *pxOverlapped ) { g_pMatchExtensions->GetIXOnline()->XCancelOverlapped( pxOverlapped ); } #endif