|
|
//===== 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" ); } }
|