//===== Copyright © 1996-2009, Valve Corporation, All rights reserved. ======// // // Purpose: // //===========================================================================// #include "mm_framework.h" #include "filesystem.h" // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" #ifdef _X360 //----------------------------------------------------------------------------- // Purpose: Adjust our rate based on our quality of service //----------------------------------------------------------------------------- static ConVar mm_clientrateupdate_enabled( "mm_clientrateupdate_enabled", "1", 0, "Automatically update the client rate based on Xbox LIVE QoS" ); static ConVar mm_clientrateupdate_adjust( "mm_clientrateupdate_adjust", "0.6", 0, "Downstream rate adjustment" ); static ConVar mm_clientrateupdate_minimum( "mm_clientrateupdate_minimum", "20000", 0, "Minimum supported rate, Xbox TCR requires 40kbps" ); static ConVar mm_clientrateupdate_maximum( "mm_clientrateupdate_maximum", "30000", 0, "Maximum supported rate" ); static ConVar mm_clientrateupdate_qos_timeout( "mm_clientrateupdate_qos_timeout", "20", 0, "How long to wait for QOS to be determined" ); static void AdjustClientRateBasedOnQoS( DWORD dwDnBitsPerSec ) { if ( !mm_clientrateupdate_enabled.GetBool() ) return; static ConVarRef cl_rate( "rate" ); int desiredRate = (int)( ( dwDnBitsPerSec / 8.0f ) * mm_clientrateupdate_adjust.GetFloat() ); desiredRate = clamp( desiredRate, mm_clientrateupdate_minimum.GetInt(), mm_clientrateupdate_maximum.GetInt() ); // Update the client rate ConColorMsg( Color( 255, 0, 255, 255 ), "[QoS] Bandwidth %d bps, Updating client rate to %d\n", dwDnBitsPerSec, desiredRate ); cl_rate.SetValue( desiredRate ); } struct RateAdjustmentAsyncCall { // X360 peer XNADDR apxna; XNADDR const *papxna; XNKID apxnkid; XNKID const *papxnkid; XNKEY apxnkey; XNKEY const *papxnkey; // XLSP server IN_ADDR ina; DWORD dwServiceId; // QOS handle XNQOS *pQOS; // Time when QOS probe started float flTimeStarted; } *g_pRateAdjustmentAsyncCall = NULL; void MatchSession_RateAdjustmentUpdate_Release() { if ( !g_pRateAdjustmentAsyncCall ) return; if ( g_pRateAdjustmentAsyncCall->pQOS ) g_pMatchExtensions->GetIXOnline()->XNetQosRelease( g_pRateAdjustmentAsyncCall->pQOS ); delete g_pRateAdjustmentAsyncCall; g_pRateAdjustmentAsyncCall = NULL; } // Keeps adjusting client side rate setting based on QOS with server void MatchSession_RateAdjustmentUpdate() { if ( !g_pRateAdjustmentAsyncCall ) return; if ( g_pRateAdjustmentAsyncCall->pQOS->cxnqosPending && Plat_FloatTime() < g_pRateAdjustmentAsyncCall->flTimeStarted + mm_clientrateupdate_qos_timeout.GetFloat() ) return; ConColorMsg( Color( 255, 0, 255, 255 ), "[QoS] Rate adjustment query %s\n", g_pRateAdjustmentAsyncCall->pQOS->cxnqosPending ? "timed out" : "completed" ); // QOS finished or timed out XNQOSINFO &xni = g_pRateAdjustmentAsyncCall->pQOS->axnqosinfo[0]; AdjustClientRateBasedOnQoS( xni.dwDnBitsPerSec ); MatchSession_RateAdjustmentUpdate_Release(); } void MatchSession_RateAdjustmentUpdate_Start( IN_ADDR const &ina ) { MatchSession_RateAdjustmentUpdate_Release(); g_pRateAdjustmentAsyncCall = new RateAdjustmentAsyncCall; ZeroMemory( g_pRateAdjustmentAsyncCall, sizeof( *g_pRateAdjustmentAsyncCall ) ); g_pRateAdjustmentAsyncCall->ina = ina; g_pRateAdjustmentAsyncCall->dwServiceId = g_pMatchFramework->GetMatchTitle()->GetTitleServiceID(); g_pRateAdjustmentAsyncCall->flTimeStarted = Plat_FloatTime(); ConColorMsg( Color( 255, 0, 255, 255 ), "[QoS] Rate adjustment query scheduled for XLSP server: %08X\n", ina.s_addr ); INT ret = g_pMatchExtensions->GetIXOnline()->XNetQosLookup( 0, NULL, NULL, NULL, 1, &g_pRateAdjustmentAsyncCall->ina, &g_pRateAdjustmentAsyncCall->dwServiceId, 2, 0, 0, NULL, &g_pRateAdjustmentAsyncCall->pQOS ); if ( ret != ERROR_SUCCESS ) { g_pRateAdjustmentAsyncCall->flTimeStarted = 0.0f; } } void MatchSession_RateAdjustmentUpdate_Start( XSESSION_INFO const &xsi ) { MatchSession_RateAdjustmentUpdate_Release(); g_pRateAdjustmentAsyncCall = new RateAdjustmentAsyncCall; ZeroMemory( g_pRateAdjustmentAsyncCall, sizeof( *g_pRateAdjustmentAsyncCall ) ); g_pRateAdjustmentAsyncCall->apxna = xsi.hostAddress; g_pRateAdjustmentAsyncCall->papxna = &g_pRateAdjustmentAsyncCall->apxna; g_pRateAdjustmentAsyncCall->apxnkid = xsi.sessionID; g_pRateAdjustmentAsyncCall->papxnkid = &g_pRateAdjustmentAsyncCall->apxnkid; g_pRateAdjustmentAsyncCall->apxnkey = xsi.keyExchangeKey; g_pRateAdjustmentAsyncCall->papxnkey = &g_pRateAdjustmentAsyncCall->apxnkey; g_pRateAdjustmentAsyncCall->flTimeStarted = Plat_FloatTime(); ConColorMsg( Color( 255, 0, 255, 255 ), "[QoS] Rate adjustment query scheduled for Xbox 360 peer: %08X/%08X\n", xsi.hostAddress.ina.s_addr, xsi.hostAddress.inaOnline.s_addr ); INT ret = g_pMatchExtensions->GetIXOnline()->XNetQosLookup( 1, &g_pRateAdjustmentAsyncCall->papxna, &g_pRateAdjustmentAsyncCall->papxnkid, &g_pRateAdjustmentAsyncCall->papxnkey, 0, NULL, NULL, 2, 0, 0, NULL, &g_pRateAdjustmentAsyncCall->pQOS ); if ( ret != ERROR_SUCCESS ) { g_pRateAdjustmentAsyncCall->flTimeStarted = 0.0f; } } #endif void MatchSession_BroadcastSessionSettingsUpdate( KeyValues *pUpdateDeletePackage ) { KeyValues *notify = new KeyValues( "OnMatchSessionUpdate" ); notify->SetString( "state", "updated" ); if ( KeyValues *kvUpdate = pUpdateDeletePackage->FindKey( "update" ) ) notify->AddSubKey( kvUpdate->MakeCopy() ); if ( KeyValues *kvDelete = pUpdateDeletePackage->FindKey( "delete" ) ) notify->AddSubKey( kvDelete->MakeCopy() ); g_pMatchEventsSubscription->BroadcastEvent( notify ); } ConVar cl_session( "cl_session", "", FCVAR_USERINFO | FCVAR_HIDDEN | FCVAR_SERVER_CAN_EXECUTE | FCVAR_DEVELOPMENTONLY ); void MatchSession_PrepareClientForConnect( KeyValues *pSettings, uint64 uiReservationCookieOverride ) { char chSession[64]; sprintf( chSession, "$%llx", uiReservationCookieOverride ? uiReservationCookieOverride : g_pMatchFramework->GetMatchSession()->GetSessionSystemData()-> GetUint64( "xuidReserve", 0ull ) ); cl_session.SetValue( chSession ); g_pMatchFramework->GetMatchTitle()->PrepareClientForConnect( pSettings ); } static bool MatchSession_ResolveServerInfo_Helper_DsResult( KeyValues *pSettings, CSysSessionBase *pSysSession, MatchSessionServerInfo_t &info, uint uiResolveFlags, uint64 ullCrypt ) { #ifdef _X360 // On dedicated servers host should have given us an insecure // address representing our Title Server char const *szInsecureServerAddr = pSettings->GetString( "server/adrInsecure" ); netadr_t inetInsecure; inetInsecure.SetFromString( szInsecureServerAddr ); IN_ADDR inaddrInsecure; inaddrInsecure.s_addr = inetInsecure.GetIPNetworkByteOrder(); if ( ( uiResolveFlags & ( info.RESOLVE_DSRESULT | info.RESOLVE_QOS_RATE_PROBE ) ) == info.RESOLVE_QOS_RATE_PROBE ) { // We are not required to resolve the DSRESULT, just submit the QOS rate probe MatchSession_RateAdjustmentUpdate_Start( inaddrInsecure ); return true; } char const *szServerType = pSettings->GetString( "server/server", "listen" ); if ( !Q_stricmp( szServerType, "listen" ) ) { info.m_dsResult.m_bDedicated = false; return true; } if ( !Q_stricmp( szServerType, "externalpeer" ) ) { info.m_dsResult.m_bDedicated = false; return true; } Q_strncpy( info.m_dsResult.m_szInsecureSendableServerAddress, szInsecureServerAddr, ARRAYSIZE( info.m_dsResult.m_szInsecureSendableServerAddress ) ); // Map it to a secure address IN_ADDR inaddrSecure; DWORD ret = ERROR_FUNCTION_FAILED; if ( CommandLine()->FindParm( "-xlsp_fake_gateway" ) ) { inaddrSecure = inaddrInsecure; ret = ERROR_SUCCESS; } else { ret = g_pMatchExtensions->GetIXOnline()->XNetServerToInAddr( inaddrInsecure, g_pMatchFramework->GetMatchTitle()->GetTitleServiceID(), &inaddrSecure ); } if ( ret != ERROR_SUCCESS ) { DevWarning( "Failed to resolve XLSP secure address (code = 0x%08X, insecure = %s/%s)!\n", ret, inetInsecure.ToString(), szInsecureServerAddr ); return false; } else { netadr_t inetSecure = inetInsecure; inetSecure.SetIP( inaddrSecure.s_addr ); DevMsg( "Resolved XLSP secure address %s, insecure address was %s.\n", inetSecure.ToString(), szInsecureServerAddr ); Q_strncpy( info.m_dsResult.m_szConnectionString, inetSecure.ToString(), ARRAYSIZE( info.m_dsResult.m_szConnectionString ) ); info.m_dsResult.m_bDedicated = true; // Start QOS rate calculation for the dedicated XLSP server MatchSession_RateAdjustmentUpdate_Start( inaddrInsecure ); } #elif !defined( NO_STEAM ) char const *szAddress = pSettings->GetString( "server/adronline", "0.0.0.0" ); if ( char const *szDecrypted = MatchSession_DecryptAddressString( szAddress, ullCrypt ) ) szAddress = szDecrypted; Q_strncpy( info.m_dsResult.m_szPublicConnectionString, szAddress, ARRAYSIZE( info.m_dsResult.m_szPublicConnectionString ) ); szAddress = pSettings->GetString( "server/adrlocal", "0.0.0.0" ); if ( char const *szDecrypted = MatchSession_DecryptAddressString( szAddress, ullCrypt ) ) szAddress = szDecrypted; Q_strncpy( info.m_dsResult.m_szPrivateConnectionString, szAddress, ARRAYSIZE( info.m_dsResult.m_szPrivateConnectionString ) ); #endif return true; } static bool MatchSession_ResolveServerInfo_Helper_ConnectString( KeyValues *pSettings, CSysSessionBase *pSysSession, MatchSessionServerInfo_t &info, uint uiResolveFlags ) { // // Prepare the connect command // #ifdef _X360 char const *szServerType = pSettings->GetString( "server/server", "listen" ); if ( !Q_stricmp( "externalpeer", szServerType ) && !( uiResolveFlags & info.RESOLVE_ALLOW_EXTPEER ) ) pSysSession = NULL; char const *szConnectionString = info.m_dsResult.m_szConnectionString; if ( info.m_dsResult.m_bDedicated ) { info.m_szSecureServerAddress = info.m_dsResult.m_szConnectionString; } else if ( CSysSessionClient *pSysSessionClient = dynamic_cast< CSysSessionClient * >( pSysSession ) ) { XSESSION_INFO xsi = {0}; szConnectionString = pSysSessionClient->GetHostNetworkAddress( xsi ); if ( !szConnectionString ) { DevWarning( "MatchSession_ResolveServerInfo_Helper_ConnectString::GetHostNetworkAddress failed!\n" ); return false; } // Start QOS rate calculation for our session host X360 xnaddr MatchSession_RateAdjustmentUpdate_Start( xsi ); } else if ( char const *szSessionInfo = pSettings->GetString( "server/sessioninfo", NULL ) ) { // We don't have a dedicated server and don't allow to use external peer directly, // register security keys XSESSION_INFO xsi = {0}; MMX360_SessionInfoFromString( xsi, szSessionInfo ); // Resolve XNADDR IN_ADDR inaddrRemote; g_pMatchExtensions->GetIXOnline()->XNetRegisterKey( &xsi.sessionID, &xsi.keyExchangeKey ); if ( int err = g_pMatchExtensions->GetIXOnline()->XNetXnAddrToInAddr( &xsi.hostAddress, &xsi.sessionID, &inaddrRemote ) ) { DevWarning( "MatchSession_ResolveServerInfo_Helper_ConnectString::XNetXnAddrToInAddr" " failed to resolve XNADDR ( code 0x%08X, sessioninfo = %s )\n", err, szSessionInfo ); g_pMatchExtensions->GetIXOnline()->XNetUnregisterKey( &xsi.sessionID ); return false; } // Initiate secure connection and key exchange if ( int err = g_pMatchExtensions->GetIXOnline()->XNetConnect( inaddrRemote ) ) { DevWarning( "MatchSession_ResolveServerInfo_Helper_ConnectString::XNetConnect" " failed to start key exchange ( code 0x%08X, sessioninfo = %s )\n", err, szSessionInfo ); // Secure IN_ADDR associations are removed implicitly when their key gets unregistered g_pMatchExtensions->GetIXOnline()->XNetUnregisterKey( &xsi.sessionID ); return false; } // // Prepare connection string // netadr_t inetAddr; inetAddr.SetType( NA_IP ); inetAddr.SetIPAndPort( inaddrRemote.s_addr, 0 ); // Now we know the address for the game to connect Q_strncpy( info.m_dsResult.m_szConnectionString, inetAddr.ToString( true ), ARRAYSIZE( info.m_dsResult.m_szConnectionString ) ); // // Remember all the settings needed to deallocate the secure association // info.m_szSecureServerAddress = info.m_dsResult.m_szInsecureSendableServerAddress; Q_snprintf( info.m_dsResult.m_szInsecureSendableServerAddress, ARRAYSIZE( info.m_dsResult.m_szInsecureSendableServerAddress ), "SESSIONINFO %s", szSessionInfo ); // Start QOS rate calculation for opponents session host X360 remote xnaddr MatchSession_RateAdjustmentUpdate_Start( xsi ); } else return false; Q_snprintf( info.m_szConnectCmd, sizeof( info.m_szConnectCmd ), "connect_splitscreen %s %s %d\n", szConnectionString, szConnectionString, XBX_GetNumGameUsers() ); #elif !defined( NO_STEAM ) Q_snprintf( info.m_szConnectCmd, sizeof( info.m_szConnectCmd ), "connect %s %s\n", info.m_dsResult.m_szPublicConnectionString, info.m_dsResult.m_szPrivateConnectionString ); #endif info.m_xuidJingle = pSettings->GetUint64( "server/xuid", 0ull ); if ( uint64 uiReservationCookieOverride = pSettings->GetUint64( "server/reservationid", 0ull ) ) info.m_uiReservationCookie = uiReservationCookieOverride; else if ( pSysSession ) info.m_uiReservationCookie = pSysSession->GetReservationCookie(); else info.m_uiReservationCookie = 0ull; return true; } bool MatchSession_ResolveServerInfo( KeyValues *pSettings, CSysSessionBase *pSysSession, MatchSessionServerInfo_t &info, uint uiResolveFlags, uint64 ullCrypt ) { if ( ( uiResolveFlags & ( info.RESOLVE_DSRESULT | info.RESOLVE_QOS_RATE_PROBE ) ) && !MatchSession_ResolveServerInfo_Helper_DsResult( pSettings, pSysSession, info, uiResolveFlags, ullCrypt ) ) return false; if ( ( uiResolveFlags & info.RESOLVE_CONNECTSTRING ) && !MatchSession_ResolveServerInfo_Helper_ConnectString( pSettings, pSysSession, info, uiResolveFlags ) ) return false; return true; } ConVar mm_tu_string( "mm_tu_string", "00000000" ); uint64 MatchSession_GetMachineFlags() { uint64 uiFlags = 0; if ( IsPS3() ) uiFlags |= MACHINE_PLATFORM_PS3; return uiFlags; } char const * MatchSession_GetTuInstalledString() { return mm_tu_string.GetString(); } char const * MatchSession_EncryptAddressString( char const *szAddress, uint64 ullCrypt ) { if ( !szAddress || !*szAddress ) return NULL; if ( !ullCrypt ) return NULL; if ( szAddress[0] == ':' ) return NULL; if ( szAddress[ 0 ] == '$' ) return NULL; static unsigned char s_chData[256]; int nLen = Q_strlen( szAddress ); if ( nLen >= ARRAYSIZE( s_chData )/2 - 1 ) return NULL; // Copy the address s_chData[0] = '$'; for ( int j = 0; j < nLen; ++ j ) { uint8 uiVal = uint8( szAddress[j] ) ^ uint8( reinterpret_cast< uint8 * >(&ullCrypt)[ j % sizeof( uint64 ) ] ); Q_snprintf( (char*)( s_chData + 1 + 2*j ), 3, "%02X", ( uint32 ) uiVal ); } return (char*) s_chData; } char const * MatchSession_DecryptAddressString( char const *szAddress, uint64 ullCrypt ) { if ( !szAddress || !*szAddress ) return NULL; if ( !ullCrypt ) return NULL; if ( szAddress[ 0 ] != '$' ) return NULL; static unsigned char s_chData[ 256 ]; int nLen = Q_strlen( szAddress ); if ( nLen*2 + 2 >= ARRAYSIZE( s_chData ) ) return NULL; // Copy the address for ( int j = 0; j < nLen/2; ++j ) { uint32 uiVal; if ( !sscanf( szAddress + 1 + 2*j, "%02X", &uiVal ) ) return NULL; if ( uiVal > 0xFF ) return NULL; uiVal = uint8( uiVal ) ^ uint8( reinterpret_cast< uint8 * >(&ullCrypt)[ j % sizeof( uint64 ) ] ); if ( !uiVal ) return NULL; s_chData[j] = uiVal; } s_chData[nLen/2] = 0; return (char*) s_chData; } CON_COMMAND( mm_debugprint, "Show debug information about current matchmaking session" ) { if ( IMatchSession *pIMatchSession = g_pMMF->GetMatchSession() ) { ( ( IMatchSessionInternal * ) pIMatchSession )->DebugPrint(); } else { DevMsg( "No match session.\n" ); } }