866 lines
24 KiB
C++
866 lines
24 KiB
C++
//===== Copyright © 1996-2009, Valve Corporation, All rights reserved. ======//
|
|
//
|
|
// Purpose:
|
|
//
|
|
//===========================================================================//
|
|
|
|
#include "mm_framework.h"
|
|
|
|
#include "vstdlib/random.h"
|
|
|
|
#include "protocol.h"
|
|
#include "proto_oob.h"
|
|
#include "filesystem.h"
|
|
|
|
// memdbgon must be the last include file in a .cpp file!!!
|
|
#include "tier0/memdbgon.h"
|
|
|
|
|
|
ConVar mm_dedicated_xlsp_max_dcs( "mm_dedicated_xlsp_max_dcs", "25", FCVAR_DEVELOPMENTONLY, "Max number of XLSP datacenters supported" );
|
|
ConVar mm_dedicated_xlsp_force_dc( "mm_dedicated_xlsp_force_dc", "", FCVAR_DEVELOPMENTONLY, "Name of XLSP datacenter to force connection to" );
|
|
ConVar mm_dedicated_xlsp_timeout( "mm_dedicated_xlsp_timeout", "20", FCVAR_DEVELOPMENTONLY, "Timeout for XLSP operations" );
|
|
ConVar mm_dedicated_xlsp_command_timeout( "mm_dedicated_xlsp_command_timeout", "10", FCVAR_DEVELOPMENTONLY, "Timeout for XLSP command" );
|
|
|
|
ConVar mm_dedicated_xlsp_force_dc_offset( "mm_dedicated_xlsp_force_dc_offset", "0", FCVAR_DEVELOPMENTONLY, "Offset of XLSP datacenter master to debug master servers" );
|
|
|
|
|
|
static int s_nXlspConnectionCmdReplyId = 0x10000000;
|
|
static int GetNextXlspConnectionCmdReplyId()
|
|
{
|
|
return ++ s_nXlspConnectionCmdReplyId;
|
|
}
|
|
|
|
#ifdef _X360
|
|
|
|
|
|
//
|
|
// Datacenter implementation
|
|
//
|
|
|
|
static int GetBucketedRTT( int iRTT )
|
|
{
|
|
static int s_latencyBucketLevels[] = { 5, 25, 50, 100, 200, 0xFFFFFF };
|
|
for ( int k = 0; k < ARRAYSIZE( s_latencyBucketLevels ); ++ k )
|
|
{
|
|
if ( iRTT <= s_latencyBucketLevels[ k ] )
|
|
return s_latencyBucketLevels[ k ];
|
|
}
|
|
return s_latencyBucketLevels[ ARRAYSIZE( s_latencyBucketLevels ) - 1 ];
|
|
}
|
|
|
|
int CXlspDatacenter::Compare( CXlspDatacenter const *dc1, CXlspDatacenter const *dc2 )
|
|
{
|
|
int nPing1 = dc1->m_nPingBucket, nPing2 = dc2->m_nPingBucket;
|
|
|
|
if ( nPing1 != nPing2 )
|
|
return ( nPing1 < nPing2 ) ? -1 : 1;
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
bool CXlspDatacenter::ParseServerInfo()
|
|
{
|
|
char *pToken = V_stristr( m_xsi.szServerInfo, "**" );
|
|
if ( !pToken )
|
|
return false;
|
|
|
|
// Get our bare gateway name
|
|
int nTokenLength = pToken - m_xsi.szServerInfo;
|
|
nTokenLength = MIN( sizeof( m_szGatewayName ) - 1, nTokenLength );
|
|
sprintf( m_szGatewayName, "%.*s", nTokenLength, m_xsi.szServerInfo );
|
|
|
|
// parse out the gateway information
|
|
// get the master server's port and range
|
|
pToken += 2;
|
|
int nSupportsPII = 0;
|
|
sscanf( pToken, "%d_%d_%d", &m_nMasterServerPortStart, &m_numMasterServers, &nSupportsPII );
|
|
m_bSupportsPII = ( nSupportsPII != 0 );
|
|
|
|
if ( !m_nMasterServerPortStart || !m_numMasterServers )
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
void CXlspDatacenter::Destroy()
|
|
{
|
|
if ( m_adrSecure.s_addr )
|
|
{
|
|
g_pMatchExtensions->GetIXOnline()->XNetUnregisterInAddr( m_adrSecure );
|
|
}
|
|
|
|
Q_memset( this, 0, sizeof( *this ) );
|
|
}
|
|
|
|
//
|
|
// XLSP title servers enumeration implementation
|
|
//
|
|
|
|
CXlspTitleServers::CXlspTitleServers( int nPingLimit, bool bMustSupportPII ) :
|
|
m_hEnumerate( NULL ),
|
|
m_numServers( 0 ),
|
|
m_pQos( NULL ),
|
|
m_eState( STATE_INIT ),
|
|
m_flTimeout( 0.0f ),
|
|
m_nPingLimit( nPingLimit ),
|
|
m_bMustSupportPII( bMustSupportPII ),
|
|
m_pCancelOverlappedJob( NULL )
|
|
{
|
|
ZeroMemory( &m_xOverlapped, sizeof( m_xOverlapped ) );
|
|
}
|
|
|
|
CXlspTitleServers::~CXlspTitleServers()
|
|
{
|
|
}
|
|
|
|
void CXlspTitleServers::Destroy()
|
|
{
|
|
switch ( m_eState )
|
|
{
|
|
case STATE_XLSP_ENUMERATE_DCS:
|
|
m_pCancelOverlappedJob = ThreadExecute( MMX360_CancelOverlapped, &m_xOverlapped ); // UpdateDormantOperations will clean the rest
|
|
break;
|
|
case STATE_XLSP_QOS_DCS:
|
|
g_pMatchExtensions->GetIXOnline()->XNetQosRelease( m_pQos );
|
|
m_pQos = NULL;
|
|
break;
|
|
}
|
|
|
|
if ( !m_pCancelOverlappedJob )
|
|
delete this;
|
|
else
|
|
MMX360_RegisterDormant( this ); // keep running UpdateDormantOperation frame loop
|
|
}
|
|
|
|
bool CXlspTitleServers::UpdateDormantOperation()
|
|
{
|
|
if ( !m_pCancelOverlappedJob->IsFinished() )
|
|
return true; // keep running dormant
|
|
|
|
m_pCancelOverlappedJob->Release();
|
|
m_pCancelOverlappedJob = NULL;
|
|
|
|
CloseHandle( m_hEnumerate );
|
|
m_hEnumerate = NULL;
|
|
delete this;
|
|
return false; // destroyed the object, remove from dormant list
|
|
}
|
|
|
|
void CXlspTitleServers::Update()
|
|
{
|
|
switch ( m_eState )
|
|
{
|
|
case STATE_INIT:
|
|
m_flTimeout = Plat_FloatTime() + mm_dedicated_xlsp_timeout.GetFloat();
|
|
if ( EnumerateDcs( m_hEnumerate, m_xOverlapped, m_bufXlspEnumerateDcs ) )
|
|
m_eState = STATE_XLSP_ENUMERATE_DCS;
|
|
else
|
|
m_eState = STATE_FINISHED;
|
|
break;
|
|
|
|
case STATE_XLSP_ENUMERATE_DCS:
|
|
if ( !XHasOverlappedIoCompleted( &m_xOverlapped ) )
|
|
{
|
|
if ( Plat_FloatTime() > m_flTimeout )
|
|
{
|
|
// Enumeration timeout elapsed
|
|
m_pCancelOverlappedJob = ThreadExecute( MMX360_CancelOverlapped, &m_xOverlapped ); // UpdateDormantOperations will clean the rest
|
|
m_eState = STATE_FINISHED;
|
|
}
|
|
return;
|
|
}
|
|
|
|
m_flTimeout = Plat_FloatTime() + mm_dedicated_xlsp_timeout.GetFloat();
|
|
if ( ExecuteQosDcs( m_hEnumerate, m_xOverlapped, m_bufXlspEnumerateDcs, m_numServers, m_pQos ) )
|
|
m_eState = STATE_XLSP_QOS_DCS;
|
|
else
|
|
m_eState = STATE_FINISHED;
|
|
break;
|
|
|
|
case STATE_XLSP_QOS_DCS:
|
|
if ( CommandLine()->FindParm( "-xlsp_fake_gateway" ) || Plat_FloatTime() > m_flTimeout ||
|
|
!m_pQos->cxnqosPending )
|
|
{
|
|
ParseDatacentersFromQos( m_arrDcs, m_bufXlspEnumerateDcs, m_pQos );
|
|
m_eState = STATE_FINISHED;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
bool CXlspTitleServers::EnumerateDcs( HANDLE &hEnumerate, XOVERLAPPED &xOverlapped, CUtlBuffer &bufResults )
|
|
{
|
|
// If we're using a fake xlsp server, then we don't want to do any of this.
|
|
if ( CommandLine()->FindParm( "-xlsp_fake_gateway" ) )
|
|
{
|
|
return true;
|
|
}
|
|
|
|
int numDcs = mm_dedicated_xlsp_max_dcs.GetInt();
|
|
DevMsg( "Enumerating XLSP datacenters (%d max supported)...\n", numDcs );
|
|
|
|
//
|
|
// Create enumerator
|
|
//
|
|
DWORD numBytes = 0;
|
|
DWORD ret = g_pMatchExtensions->GetIXOnline()->XTitleServerCreateEnumerator( NULL, numDcs, &numBytes, &hEnumerate );
|
|
if ( ret != ERROR_SUCCESS )
|
|
{
|
|
DevWarning( "XTitleServerCreateEnumerator failed (code = 0x%08X)\n", ret );
|
|
hEnumerate = NULL;
|
|
return false;
|
|
}
|
|
|
|
// Allocate results buffer
|
|
bufResults.EnsureCapacity( numBytes );
|
|
XTITLE_SERVER_INFO *pXSI = ( XTITLE_SERVER_INFO * ) bufResults.Base();
|
|
ZeroMemory( pXSI, numBytes );
|
|
ZeroMemory( &xOverlapped, sizeof( XOVERLAPPED ) );
|
|
|
|
//
|
|
// Enumerate
|
|
//
|
|
ret = XEnumerate( hEnumerate, pXSI, numBytes, NULL, &xOverlapped );
|
|
if ( ret != ERROR_IO_PENDING )
|
|
{
|
|
DevWarning( "XEnumerate of XTitleServerCreateEnumerator failed (code = 0x%08X)\n", ret );
|
|
CloseHandle( hEnumerate );
|
|
hEnumerate = NULL;
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool CXlspTitleServers::ExecuteQosDcs( HANDLE &hEnumerate, XOVERLAPPED &xOverlapped, CUtlBuffer &bufResults, DWORD &numServers, XNQOS *&pQos )
|
|
{
|
|
// If we're using a fake xlsp server, then we don't want to do any of this.
|
|
if ( CommandLine()->FindParm( "-xlsp_fake_gateway" ) )
|
|
{
|
|
return true;
|
|
}
|
|
|
|
numServers = 0;
|
|
XGetOverlappedResult( &xOverlapped, &numServers, TRUE );
|
|
CloseHandle( hEnumerate );
|
|
hEnumerate = NULL;
|
|
|
|
DevMsg( "Xlsp_OnEnumerateDcsCompleted found %d datacenters.\n", numServers );
|
|
if ( !numServers )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
//
|
|
// Prepare for QOS lookup to the datacenters
|
|
//
|
|
XTITLE_SERVER_INFO *pXSI = ( XTITLE_SERVER_INFO * ) bufResults.Base();
|
|
DWORD dwServiceId = g_pMatchFramework->GetMatchTitle()->GetTitleServiceID();
|
|
CUtlVector< IN_ADDR > qosAddr;
|
|
CUtlVector< DWORD > qosServiceId;
|
|
for ( DWORD k = 0; k < numServers; ++ k )
|
|
{
|
|
qosAddr.AddToTail( pXSI[k].inaServer );
|
|
qosServiceId.AddToTail( dwServiceId );
|
|
}
|
|
|
|
//
|
|
// Submit QOS lookup
|
|
//
|
|
pQos = NULL;
|
|
DWORD ret = g_pMatchExtensions->GetIXOnline()->XNetQosLookup( 0, NULL, NULL, NULL,
|
|
numServers, qosAddr.Base(), qosServiceId.Base(),
|
|
8, 0, 0,
|
|
NULL, &pQos );
|
|
if ( ret != ERROR_SUCCESS )
|
|
{
|
|
DevWarning( "XNetQosLookup failed to start for XLSP datacenters, code = 0x%08X!\n", ret );
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void CXlspTitleServers::ParseDatacentersFromQos( CUtlVector< CXlspDatacenter > &arrDcs, CUtlBuffer &bufResults, XNQOS *&pQos )
|
|
{
|
|
// If we're using a fake xlsp server, then we don't want to do any of this and instead add our fake server to the datacenter list.
|
|
if ( CommandLine()->FindParm( "-xlsp_fake_gateway" ) )
|
|
{
|
|
netadr_t gatewayAdr;
|
|
gatewayAdr.SetFromString( CommandLine()->GetParm( CommandLine()->FindParm( "-xlsp_fake_gateway" ) + 1) );
|
|
|
|
char szSG[200];
|
|
sprintf(szSG, "%s**%d_1", gatewayAdr.ToString(true), gatewayAdr.GetPort() );
|
|
|
|
CXlspDatacenter dc;
|
|
ZeroMemory( &dc, sizeof( dc ) );
|
|
Q_strncpy( dc.m_xsi.szServerInfo, szSG, sizeof(dc.m_xsi.szServerInfo) );
|
|
dc.m_xsi.inaServer.S_un.S_addr = gatewayAdr.GetIPNetworkByteOrder();
|
|
dc.ParseServerInfo();
|
|
arrDcs.AddToTail( dc );
|
|
return;
|
|
}
|
|
|
|
if ( !pQos )
|
|
return;
|
|
|
|
XTITLE_SERVER_INFO *pXSI = ( XTITLE_SERVER_INFO * ) bufResults.Base();
|
|
|
|
for ( DWORD k = 0; k < pQos->cxnqos; ++ k )
|
|
{
|
|
// Datacenter info
|
|
CXlspDatacenter dc;
|
|
ZeroMemory( &dc, sizeof( dc ) );
|
|
dc.m_xsi = pXSI[k];
|
|
dc.m_qos = pQos->axnqosinfo[k];
|
|
|
|
// Cull centers that failed to be contacted
|
|
uint uiRequiredQosFlags = ( XNET_XNQOSINFO_COMPLETE | XNET_XNQOSINFO_TARGET_CONTACTED );
|
|
if ( ( ( dc.m_qos.bFlags & uiRequiredQosFlags ) != uiRequiredQosFlags ) ||
|
|
( dc.m_qos.bFlags & XNET_XNQOSINFO_TARGET_DISABLED ) )
|
|
{
|
|
DevWarning( "XLSP datacenter %d `%s` failed connection probe (0x%08X).\n", k, dc.m_xsi.szServerInfo, dc.m_qos.bFlags );
|
|
continue;
|
|
}
|
|
|
|
// Cull any non-conformant XLSP center names
|
|
if ( !dc.ParseServerInfo() )
|
|
{
|
|
DevWarning( "XLSP datacenter %d `%s` has non-conformant server info.\n", k, dc.m_xsi.szServerInfo );
|
|
continue;
|
|
}
|
|
|
|
// Check if PII is required
|
|
if ( m_bMustSupportPII && !dc.m_bSupportsPII )
|
|
{
|
|
DevWarning( "XLSP datacenter %d `%s` does not support PII.\n", k, dc.m_xsi.szServerInfo );
|
|
continue;
|
|
}
|
|
|
|
// Check if we are forcing a specific datacenter
|
|
bool bForcedUse = false;
|
|
if ( char const *szForcedDcName = mm_dedicated_xlsp_force_dc.GetString() )
|
|
{
|
|
if ( *szForcedDcName && !Q_stricmp( szForcedDcName, dc.m_szGatewayName ) )
|
|
bForcedUse = true;
|
|
|
|
if ( *szForcedDcName && !bForcedUse )
|
|
{
|
|
// Gateway doesn't match forced datacenter
|
|
DevWarning( "XLSP datacenter %d `%s` is ignored because we are forcing datacenter name `%s`.\n", k, dc.m_xsi.szServerInfo, szForcedDcName );
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// Check ping
|
|
if ( m_nPingLimit > 0 && dc.m_qos.wRttMedInMsecs > m_nPingLimit && !bForcedUse )
|
|
{
|
|
DevWarning( "XLSP datacenter %d `%s` is ignored because its ping %d is greater than max allowed %d.\n",
|
|
k, dc.m_xsi.szServerInfo, dc.m_qos.wRttMedInMsecs, m_nPingLimit );
|
|
continue;
|
|
}
|
|
|
|
// Remeber the datacenter as a potential candidate
|
|
dc.m_nPingBucket = GetBucketedRTT( dc.m_qos.wRttMedInMsecs );
|
|
DevMsg( "XLSP datacenter %d `%s` accepted, ping %d [<= %d]\n",
|
|
k, dc.m_szGatewayName, dc.m_qos.wRttMedInMsecs, dc.m_nPingBucket );
|
|
|
|
arrDcs.AddToTail( dc );
|
|
}
|
|
|
|
// Release the QOS query
|
|
g_pMatchExtensions->GetIXOnline()->XNetQosRelease( pQos );
|
|
pQos = NULL;
|
|
}
|
|
|
|
bool CXlspTitleServers::IsSearchCompleted() const
|
|
{
|
|
return m_eState == STATE_FINISHED;
|
|
}
|
|
|
|
CUtlVector< CXlspDatacenter > & CXlspTitleServers::GetDatacenters()
|
|
{
|
|
Assert( IsSearchCompleted() );
|
|
return m_arrDcs;
|
|
}
|
|
|
|
|
|
|
|
//
|
|
// XLSP connection implementation
|
|
//
|
|
|
|
CXlspConnection::CXlspConnection( bool bMustSupportPII ) :
|
|
m_pTitleServers( NULL ),
|
|
m_pCmdResult( NULL ),
|
|
m_flTimeout( 0.0f ),
|
|
m_eState( STATE_INIT ),
|
|
m_idCmdReplyId( 0 ),
|
|
m_numCmdRetriesAllowed( 0 ),
|
|
m_flCmdRetryTimeout( 0 ),
|
|
m_bMustSupportPII( bMustSupportPII )
|
|
{
|
|
ZeroMemory( &m_dc, sizeof( m_dc ) );
|
|
}
|
|
|
|
CXlspConnection::~CXlspConnection()
|
|
{
|
|
;
|
|
}
|
|
|
|
void CXlspConnection::Destroy()
|
|
{
|
|
if ( m_pTitleServers )
|
|
m_pTitleServers->Destroy();
|
|
m_pTitleServers = NULL;
|
|
|
|
if ( m_eState >= STATE_CONNECTED )
|
|
m_dc.Destroy();
|
|
|
|
if ( m_eState == STATE_RUNNINGCMD )
|
|
g_pMatchEventsSubscription->Unsubscribe( this );
|
|
|
|
if ( m_pCmdResult )
|
|
m_pCmdResult->deleteThis();
|
|
m_pCmdResult = NULL;
|
|
|
|
delete this;
|
|
}
|
|
|
|
bool CXlspConnection::IsConnected() const
|
|
{
|
|
return m_eState == STATE_CONNECTED || m_eState == STATE_RUNNINGCMD;
|
|
}
|
|
|
|
bool CXlspConnection::HasError() const
|
|
{
|
|
return m_eState == STATE_ERROR;
|
|
}
|
|
|
|
static int XlspConnection_CompareDcs( CXlspDatacenter const *dc1, CXlspDatacenter const *dc2 )
|
|
{
|
|
int nPing1 = dc1->m_nPingBucket, nPing2 = dc2->m_nPingBucket;
|
|
|
|
if ( nPing1 != nPing2 )
|
|
return ( nPing1 < nPing2 ) ? -1 : 1;
|
|
|
|
// Compare by name
|
|
if ( int iNameCmp = Q_stricmp( dc1->m_szGatewayName, dc2->m_szGatewayName ) )
|
|
return iNameCmp;
|
|
|
|
// Compare by IP addr
|
|
int iAddrCmp = dc1->m_xsi.inaServer.s_addr - dc2->m_xsi.inaServer.s_addr;
|
|
if ( iAddrCmp )
|
|
return iAddrCmp;
|
|
|
|
return 0;
|
|
}
|
|
|
|
void CXlspConnection::ConnectXlspDc()
|
|
{
|
|
CUtlVector< CXlspDatacenter > &arrDcs = m_pTitleServers->GetDatacenters();
|
|
arrDcs.Sort( XlspConnection_CompareDcs );
|
|
|
|
for ( int k = 0; k < arrDcs.Count(); ++ k )
|
|
{
|
|
m_dc = arrDcs[k];
|
|
|
|
DevMsg( "[XLSP] Connecting to %s:%d (%d masters) - ping %d [<= %d]\n"
|
|
" ProbesXmit=%3d ProbesRecv=%3d\n"
|
|
" RttMinInMsecs=%3d RttMedInMsecs=%3d\n"
|
|
" UpBitsPerSec=%6d DnBitsPerSec=%6d\n",
|
|
m_dc.m_szGatewayName, m_dc.m_nMasterServerPortStart, m_dc.m_numMasterServers, m_dc.m_qos.wRttMedInMsecs, m_dc.m_nPingBucket,
|
|
m_dc.m_qos.cProbesXmit, m_dc.m_qos.cProbesRecv,
|
|
m_dc.m_qos.wRttMinInMsecs, m_dc.m_qos.wRttMedInMsecs,
|
|
m_dc.m_qos.dwUpBitsPerSec, m_dc.m_qos.dwDnBitsPerSec );
|
|
|
|
//
|
|
// Resolve the secure address
|
|
//
|
|
DWORD ret = ERROR_SUCCESS;
|
|
if ( CommandLine()->FindParm( "-xlsp_fake_gateway" ) )
|
|
{
|
|
m_dc.m_adrSecure = m_dc.m_xsi.inaServer;
|
|
}
|
|
else
|
|
{
|
|
ret = g_pMatchExtensions->GetIXOnline()->XNetServerToInAddr( m_dc.m_xsi.inaServer, g_pMatchFramework->GetMatchTitle()->GetTitleServiceID(), &m_dc.m_adrSecure );
|
|
}
|
|
|
|
if ( ret != ERROR_SUCCESS )
|
|
{
|
|
DevWarning( "Failed to resolve XLSP secure address (code = 0x%08X)!\n", ret );
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
DevMsg( "Resolved XLSP server address\n" );
|
|
m_eState = STATE_CONNECTED;
|
|
|
|
m_pTitleServers->Destroy();
|
|
m_pTitleServers = NULL;
|
|
return;
|
|
}
|
|
}
|
|
|
|
ZeroMemory( &m_dc, sizeof( m_dc ) );
|
|
m_eState = STATE_ERROR;
|
|
}
|
|
|
|
CXlspDatacenter const & CXlspConnection::GetDatacenter() const
|
|
{
|
|
return m_dc;
|
|
}
|
|
|
|
void CXlspConnection::ResolveCmdSystemValues( KeyValues *pCommand )
|
|
{
|
|
// Expand the command data based on DC fields
|
|
if ( KeyValues *pExp = pCommand->FindKey( "*dcpgmi" ) )
|
|
pExp->SetInt( NULL, m_dc.m_qos.wRttMinInMsecs );
|
|
if ( KeyValues *pExp = pCommand->FindKey( "*dcpgme" ) )
|
|
pExp->SetInt( NULL, m_dc.m_qos.wRttMedInMsecs );
|
|
if ( KeyValues *pExp = pCommand->FindKey( "*dcpgbu" ) )
|
|
pExp->SetInt( NULL, m_dc.m_nPingBucket );
|
|
if ( KeyValues *pExp = pCommand->FindKey( "*dcbwup" ) )
|
|
pExp->SetInt( NULL, m_dc.m_qos.dwUpBitsPerSec );
|
|
if ( KeyValues *pExp = pCommand->FindKey( "*dcbwdn" ) )
|
|
pExp->SetInt( NULL, m_dc.m_qos.dwDnBitsPerSec );
|
|
if ( KeyValues *pExp = pCommand->FindKey( "*net" ) )
|
|
pExp->SetInt( NULL, g_pMatchExtensions->GetIXOnline()->XNetGetEthernetLinkStatus() );
|
|
if ( KeyValues *pExp = pCommand->FindKey( "*nat" ) )
|
|
pExp->SetInt( NULL, g_pMatchExtensions->GetIXOnline()->XOnlineGetNatType() );
|
|
if ( KeyValues *pExp = pCommand->FindKey( "*mac" ) )
|
|
{
|
|
// Console MAC address
|
|
XNADDR xnaddr;
|
|
uint64 uiMachineId = 0ull;
|
|
if ( XNET_GET_XNADDR_PENDING == g_pMatchExtensions->GetIXOnline()->XNetGetTitleXnAddr( &xnaddr ) ||
|
|
g_pMatchExtensions->GetIXOnline()->XNetXnAddrToMachineId( &xnaddr, &uiMachineId ) )
|
|
uiMachineId = 0ull;
|
|
pExp->SetUint64( NULL, uiMachineId );
|
|
}
|
|
if ( KeyValues *pExp = pCommand->FindKey( "*diskDsn" ) )
|
|
{
|
|
// Serial number of GAME volume
|
|
struct VolumeInformation_t {
|
|
char chVolumeName[128];
|
|
char chFsName[128];
|
|
DWORD dwVolumeSerial;
|
|
DWORD dwMaxComponentLen;
|
|
DWORD dwFsFlags;
|
|
} vi;
|
|
memset( &vi, 0, sizeof( vi ) );
|
|
if ( !GetVolumeInformation( "d:\\",
|
|
vi.chVolumeName, sizeof( vi.chVolumeName ) - 1,
|
|
&vi.dwVolumeSerial, &vi.dwMaxComponentLen, &vi.dwFsFlags,
|
|
vi.chFsName, sizeof( vi.chFsName ) - 1
|
|
) )
|
|
{
|
|
memset( &vi, 0, sizeof( vi ) );
|
|
}
|
|
|
|
uint64 uiDsn = vi.dwVolumeSerial;
|
|
if ( g_pFullFileSystem && g_pFullFileSystem->IsDVDHosted() )
|
|
uiDsn |= ( 1ull << 33 ); // DVD hosted
|
|
|
|
pExp->SetUint64( NULL, uiDsn );
|
|
}
|
|
if ( KeyValues *pExp = pCommand->FindKey( "*diskDnfo" ) )
|
|
{
|
|
// Space information of GAME volume
|
|
struct FreeSpace_t
|
|
{
|
|
ULARGE_INTEGER ulFreeTitle, ulTotal, ulFree;
|
|
} fs;
|
|
memset( &fs, 0, sizeof( fs ) );
|
|
if ( !GetDiskFreeSpaceEx( "d:\\",
|
|
&fs.ulFreeTitle, &fs.ulTotal, &fs.ulFree ) )
|
|
{
|
|
memset( &fs, 0, sizeof( fs ) );
|
|
}
|
|
uint32 uiTotalMbs = fs.ulTotal.QuadPart / ( 1024 * 1024 );
|
|
uint32 uiFreeMbs = fs.ulFree.QuadPart / ( 1024 * 1024 );
|
|
pExp->SetUint64( NULL, uint64( uiTotalMbs ) | ( uint64( uiFreeMbs ) << 32 ) );
|
|
}
|
|
if ( KeyValues *pExp = pCommand->FindKey( "*diskCnfo" ) )
|
|
{
|
|
// Space information of CACHE partition
|
|
struct FreeSpace_t
|
|
{
|
|
ULARGE_INTEGER ulFreeTitle, ulTotal, ulFree;
|
|
} fs;
|
|
memset( &fs, 0, sizeof( fs ) );
|
|
if ( !GetDiskFreeSpaceEx( "cache:\\",
|
|
&fs.ulFreeTitle, &fs.ulTotal, &fs.ulFree ) )
|
|
{
|
|
memset( &fs, 0, sizeof( fs ) );
|
|
}
|
|
uint32 uiTotalMbs = fs.ulTotal.QuadPart / ( 1024 * 1024 );
|
|
uint32 uiFreeMbs = fs.ulFree.QuadPart / ( 1024 * 1024 );
|
|
pExp->SetUint64( NULL, uint64( uiTotalMbs ) | ( uint64( uiFreeMbs ) << 32 ) );
|
|
}
|
|
if ( KeyValues *pExp = pCommand->FindKey( "*diskHnfo" ) )
|
|
{
|
|
// Space information of HDD volume
|
|
XDEVICE_DATA xdd;
|
|
memset( &xdd, 0, sizeof( xdd ) );
|
|
xdd.DeviceID = 1;
|
|
if ( XContentGetDeviceData( xdd.DeviceID, &xdd ) )
|
|
{
|
|
memset( &xdd, 0, sizeof( xdd ) );
|
|
}
|
|
uint32 uiTotalMbs = xdd.ulDeviceBytes / ( 1024 * 1024 );
|
|
uint32 uiFreeMbs = xdd.ulDeviceFreeBytes / ( 1024 * 1024 );
|
|
pExp->SetUint64( NULL, uint64( uiTotalMbs ) | ( uint64( uiFreeMbs ) << 32 ) );
|
|
}
|
|
if ( KeyValues *pExp = pCommand->FindKey( "*disk1nfo" ) )
|
|
{
|
|
// Space information of user-chosen storage device
|
|
XDEVICE_DATA xdd;
|
|
memset( &xdd, 0, sizeof( xdd ) );
|
|
xdd.DeviceID = XBX_GetStorageDeviceId( XBX_GetPrimaryUserId() );
|
|
if ( XContentGetDeviceData( xdd.DeviceID, &xdd ) )
|
|
{
|
|
memset( &xdd, 0, sizeof( xdd ) );
|
|
}
|
|
uint32 uiTotalMbs = xdd.ulDeviceBytes / ( 1024 * 1024 );
|
|
uint32 uiFreeMbs = xdd.ulDeviceFreeBytes / ( 1024 * 1024 );
|
|
pExp->SetUint64( NULL, uint64( uiTotalMbs ) | ( uint64( uiFreeMbs ) << 32 ) );
|
|
}
|
|
}
|
|
|
|
netadr_t CXlspConnection::GetXlspDcAddress()
|
|
{
|
|
//
|
|
// Convert DC address to netadr_t on a random master port
|
|
//
|
|
netadr_t inetAddr;
|
|
inetAddr.SetType( NA_IP );
|
|
inetAddr.SetIPAndPort( m_dc.m_adrSecure.s_addr,
|
|
m_dc.m_nMasterServerPortStart + RandomInt( 0, m_dc.m_numMasterServers - 1 )
|
|
+ mm_dedicated_xlsp_force_dc_offset.GetInt() );
|
|
return inetAddr;
|
|
}
|
|
|
|
bool CXlspConnection::ExecuteCmd( KeyValues *pCommand, int numRetriesAllowed, float flCmdRetryTimeout )
|
|
{
|
|
if ( !pCommand )
|
|
return false;
|
|
|
|
if ( m_eState != STATE_CONNECTED )
|
|
{
|
|
Assert( !"CXlspConnection::ExecuteCmd while not connected to XLSP server!\n" );
|
|
return false;
|
|
}
|
|
|
|
ResolveCmdSystemValues( pCommand );
|
|
|
|
// Serialize the command
|
|
CUtlBuffer bufBinData;
|
|
bufBinData.ActivateByteSwapping( !CByteswap::IsMachineBigEndian() );
|
|
if ( !pCommand->WriteAsBinary( bufBinData ) )
|
|
return false;
|
|
|
|
// Destroy the previous result data
|
|
if ( m_pCmdResult )
|
|
m_pCmdResult->deleteThis();
|
|
m_pCmdResult = NULL;
|
|
|
|
m_idCmdReplyId = GetNextXlspConnectionCmdReplyId();
|
|
m_numCmdRetriesAllowed = numRetriesAllowed;
|
|
m_flCmdRetryTimeout = ( ( flCmdRetryTimeout > 0 ) ? flCmdRetryTimeout : mm_dedicated_xlsp_command_timeout.GetFloat() );
|
|
|
|
//
|
|
// Prepare the request payload
|
|
//
|
|
char msg_buffer[ INetSupport::NC_MAX_ROUTABLE_PAYLOAD ];
|
|
bf_write msg( msg_buffer, sizeof( msg_buffer ) );
|
|
|
|
msg.WriteByte( A2A_KV_CMD );
|
|
msg.WriteByte( A2A_KV_VERSION );
|
|
|
|
// Xbox 360 -> Master server
|
|
msg.WriteLong( MAKE_4BYTES( 'X', '-', 'M', '1' ) );
|
|
|
|
msg.WriteLong( m_idCmdReplyId );
|
|
msg.WriteLong( m_dc.m_adrSecure.s_addr ); // datacenter's challenge
|
|
msg.WriteLong( 0 );
|
|
msg.WriteLong( bufBinData.TellMaxPut() );
|
|
msg.WriteBytes( bufBinData.Base(), bufBinData.TellMaxPut() ); // datacenter command
|
|
|
|
DevMsg( 2, "Xbox->XLSP: 0x%08X 0x%08X ( %u bytes, %d retries allowed )\n", m_idCmdReplyId, (uint32) m_dc.m_adrSecure.s_addr, msg.GetNumBytesWritten(), m_numCmdRetriesAllowed );
|
|
KeyValuesDumpAsDevMsg( pCommand, 1, 2 );
|
|
|
|
g_pMatchExtensions->GetINetSupport()->SendPacket( NULL, INetSupport::NS_SOCK_CLIENT,
|
|
GetXlspDcAddress(), msg.GetData(), msg.GetNumBytesWritten() );
|
|
|
|
if ( m_numCmdRetriesAllowed > 0 )
|
|
{
|
|
m_bufCmdRetry.SetCount( msg.GetNumBytesWritten() );
|
|
memcpy( m_bufCmdRetry.Base(), msg.GetData(), msg.GetNumBytesWritten() );
|
|
}
|
|
else
|
|
{
|
|
m_bufCmdRetry.Purge();
|
|
}
|
|
|
|
g_pMatchEventsSubscription->Subscribe( this );
|
|
m_eState = STATE_RUNNINGCMD;
|
|
m_flTimeout = Plat_FloatTime() + m_flCmdRetryTimeout;
|
|
return true;
|
|
}
|
|
|
|
void CXlspConnection::OnEvent( KeyValues *pEvent )
|
|
{
|
|
char const *szName = pEvent->GetName();
|
|
|
|
if ( !Q_stricmp( "A2A_KV_CMD", szName ) )
|
|
{
|
|
// Master server -> Xbox 360
|
|
if ( pEvent->GetInt( "header" ) == MAKE_4BYTES( 'M', '-', 'X', '1' ) &&
|
|
pEvent->GetInt( "replyid" ) == m_idCmdReplyId )
|
|
{
|
|
g_pMatchEventsSubscription->Unsubscribe( this );
|
|
m_eState = STATE_CONNECTED;
|
|
|
|
m_pCmdResult = pEvent->GetFirstTrueSubKey();
|
|
if ( !m_pCmdResult )
|
|
m_pCmdResult = pEvent;
|
|
|
|
// Keep it as a copy
|
|
m_pCmdResult = m_pCmdResult->MakeCopy();
|
|
|
|
DevMsg( 2, "Xbox<<XLSP: 0x%08X ( %u bytes )\n", m_idCmdReplyId, pEvent->GetInt( "size" ) );
|
|
// KeyValuesDumpAsDevMsg( m_pCmdResult, 1, 2 );
|
|
}
|
|
}
|
|
}
|
|
|
|
void CXlspConnection::Update()
|
|
{
|
|
switch ( m_eState )
|
|
{
|
|
case STATE_INIT:
|
|
m_eState = STATE_CONNECTING;
|
|
m_pTitleServers = new CXlspTitleServers( 0, m_bMustSupportPII ); // no ping limitation
|
|
break;
|
|
|
|
case STATE_CONNECTING:
|
|
m_pTitleServers->Update();
|
|
if ( m_pTitleServers->IsSearchCompleted() )
|
|
ConnectXlspDc();
|
|
break;
|
|
|
|
case STATE_RUNNINGCMD:
|
|
if ( Plat_FloatTime() > m_flTimeout )
|
|
{
|
|
if ( m_numCmdRetriesAllowed > 0 )
|
|
{
|
|
// Retry and increase timeout
|
|
-- m_numCmdRetriesAllowed;
|
|
m_flTimeout = Plat_FloatTime() + m_flCmdRetryTimeout;
|
|
DevMsg( 2, "Xbox->XLSP: 0x%08X 0x%08X ( %u bytes, %d retries remaining )\n", m_idCmdReplyId, (uint32) m_dc.m_adrSecure.s_addr, m_bufCmdRetry.Count(), m_numCmdRetriesAllowed );
|
|
g_pMatchExtensions->GetINetSupport()->SendPacket( NULL, INetSupport::NS_SOCK_CLIENT,
|
|
GetXlspDcAddress(), m_bufCmdRetry.Base(), m_bufCmdRetry.Count() );
|
|
return;
|
|
}
|
|
DevWarning( 2, "Xbox->XLSP: 0x%08X 0x%08X - TIMED OUT!\n", m_idCmdReplyId, (uint32) m_dc.m_adrSecure.s_addr );
|
|
g_pMatchEventsSubscription->Unsubscribe( this );
|
|
m_eState = STATE_ERROR;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
//
|
|
// Connection batch implementation
|
|
//
|
|
|
|
CXlspConnectionCmdBatch::CXlspConnectionCmdBatch( CXlspConnection *pConnection, CUtlVector<KeyValues*> &arrCommands, int numRetriesAllowedPerEachCmd, float flCommandTimeout ) :
|
|
m_eState( STATE_BATCH_WAITING ),
|
|
m_iCommand( 0 ),
|
|
m_pXlspConn( pConnection ),
|
|
m_numRetriesAllowedPerEachCmd( numRetriesAllowedPerEachCmd ),
|
|
m_flCommandTimeout( ( flCommandTimeout > 0 ) ? flCommandTimeout : mm_dedicated_xlsp_command_timeout.GetFloat() )
|
|
{
|
|
m_arrCommands.Swap( arrCommands );
|
|
}
|
|
|
|
CXlspConnectionCmdBatch::~CXlspConnectionCmdBatch()
|
|
{
|
|
}
|
|
|
|
bool CXlspConnectionCmdBatch::IsFinished() const
|
|
{
|
|
return m_eState >= STATE_FINISHED;
|
|
}
|
|
|
|
bool CXlspConnectionCmdBatch::HasAllResults() const
|
|
{
|
|
return IsFinished() && m_arrCommands.Count() == m_arrResults.Count();
|
|
}
|
|
|
|
void CXlspConnectionCmdBatch::Update()
|
|
{
|
|
m_pXlspConn->Update();
|
|
|
|
if ( m_pXlspConn->HasError() )
|
|
{
|
|
m_eState = STATE_FINISHED;
|
|
}
|
|
|
|
switch ( m_eState )
|
|
{
|
|
case STATE_BATCH_WAITING:
|
|
if ( m_pXlspConn->IsConnected() )
|
|
{
|
|
RunNextCmd();
|
|
}
|
|
break;
|
|
|
|
case STATE_RUNNINGCMD:
|
|
if ( KeyValues *pCmdResult = m_pXlspConn->GetCmdResult() )
|
|
{
|
|
m_arrResults.AddToTail( pCmdResult->MakeCopy() );
|
|
RunNextCmd();
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
void CXlspConnectionCmdBatch::RunNextCmd()
|
|
{
|
|
if ( m_iCommand >= m_arrCommands.Count() ||
|
|
m_pXlspConn->HasError() ||
|
|
!m_pXlspConn->ExecuteCmd( m_arrCommands[ m_iCommand ], m_numRetriesAllowedPerEachCmd, m_flCommandTimeout ) )
|
|
{
|
|
m_eState = STATE_FINISHED;
|
|
}
|
|
else
|
|
{
|
|
++ m_iCommand;
|
|
m_eState = STATE_RUNNINGCMD;
|
|
}
|
|
}
|
|
|
|
void CXlspConnectionCmdBatch::Destroy()
|
|
{
|
|
for ( int k = 0; k < m_arrCommands.Count(); ++ k )
|
|
m_arrCommands[k]->deleteThis();
|
|
|
|
for ( int k = 0; k < m_arrResults.Count(); ++ k )
|
|
m_arrResults[k]->deleteThis();
|
|
|
|
m_arrCommands.Purge();
|
|
m_arrResults.Purge();
|
|
|
|
m_pXlspConn = NULL;
|
|
|
|
delete this;
|
|
}
|
|
|
|
|
|
#endif // _X360
|
|
|