Counter Strike : Global Offensive Source Code
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

1177 lines
34 KiB

//========= Copyright © 1996-2009, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
//=====================================================================================//
#ifndef _X360
#include "xbox/xboxstubs.h"
#endif
#include "mm_framework.h"
#include "proto_oob.h"
#include "fmtstr.h"
#include "vstdlib/random.h"
#include "mathlib/IceKey.H"
#include "filesystem.h"
#if !defined( NO_STEAM )
#include "steam_datacenterjobs.h"
#endif
// NOTE: This has to be the last file included!
#include "tier0/memdbgon.h"
static ConVar mm_datacenter_update_interval( "mm_datacenter_update_interval", "3600", FCVAR_DEVELOPMENTONLY, "Interval between datacenter stats updates." );
static ConVar mm_datacenter_retry_interval( "mm_datacenter_retry_interval", "75", FCVAR_DEVELOPMENTONLY, "Interval between datacenter stats retries." );
static ConVar mm_datacenter_retry_infochunks_attempts( "mm_datacenter_retry_infochunks_attempts", "3", FCVAR_DEVELOPMENTONLY, "How many times can we retry retrieving each info chunk before failing." );
static ConVar mm_datacenter_query_delay( "mm_datacenter_query_delay", "2", FCVAR_DEVELOPMENTONLY, "Delay after datacenter update is enabled before data is actually queried." );
static ConVar mm_datacenter_report_version( "mm_datacenter_report_version", "5", FCVAR_DEVELOPMENTONLY, "Data version to report to DC." );
static ConVar mm_datacenter_delay_mount_frames( "mm_datacenter_delay_mount_frames", "6", FCVAR_DEVELOPMENTONLY, "How many frames to delay before attempting to mount the xlsp patch." );
static CDatacenter g_Datacenter;
CDatacenter *g_pDatacenter = &g_Datacenter;
CON_COMMAND( mm_datacenter_debugprint, "Shows information retrieved from data center" )
{
KeyValuesDumpAsDevMsg( g_pDatacenter->GetDataInfo(), 1 );
KeyValuesDumpAsDevMsg( g_pDatacenter->GetStats(), 1 );
}
//
// Buffer encryption/decryption
//
#ifdef _GAMECONSOLE
bool DecryptBuffer( CUtlBuffer &bufCypherText, CUtlBuffer &bufPlainText, unsigned int uiXorMask1 = 0 )
{
if ( bufCypherText.TellPut() < 20 )
return false;
int numBytes = bufCypherText.GetInt();
int iRandom = bufCypherText.GetInt() ^ 0xce135ef8 ^ uiXorMask1;
int iRandom2 = bufCypherText.GetInt() ^ 0x7ea55bb0;
if ( numBytes < 0 ||
bufCypherText.TellGet() + (numBytes + 7)/8 + 8 > bufCypherText.TellPut() )
return false;
IceKey cipher(1); /* medium encryption level */
unsigned char ucEncryptionKey[8] = { 0 };
*( int * )&ucEncryptionKey[ 0 ] = iRandom;
*( int * )&ucEncryptionKey[ 4 ] = iRandom2;
cipher.set( ucEncryptionKey );
bufPlainText.EnsureCapacity( numBytes + 8 + 8 );
unsigned char *pvPlainText = ( unsigned char * ) bufPlainText.PeekPut();
unsigned char *pvCypher = ( unsigned char * ) bufCypherText.PeekGet();
for ( int k = 0; k < numBytes;
k += 8, pvPlainText += 8, pvCypher += 8 )
{
cipher.decrypt( pvCypher, pvPlainText );
}
unsigned char ucDecryptedKey[8] = {0};
cipher.decrypt( pvCypher, ucDecryptedKey );
if ( memcmp( ucDecryptedKey, ucEncryptionKey, sizeof( ucDecryptedKey ) ) )
return false;
bufPlainText.SeekPut( bufPlainText.SEEK_HEAD, numBytes );
return true;
}
void EncryptBuffer( CUtlBuffer &bufPlainText, CUtlBuffer &bufCypherText, unsigned int uiXorMask1 = 0 )
{
int numBytes = bufPlainText.TellPut();
float flRandom = Plat_FloatTime();
int iRandom = *reinterpret_cast< int * >( &flRandom );
int iRandom2 = reinterpret_cast< int >( bufPlainText.Base() ) ^ 0x5ef8ce13;
// Function was written to allow bufCypherText to contain pre-existing data; this simply appends
// the encrypted buffer to the end.
// Add ( numBytes + 7 ) bytes since data is encrypted in blocks of 8 bytes, so numBytes is rounded up to multiple of 8
// Add 12 bytes for numBytes and 2 random salt values
// Add 16 bytes for double-encryption of encryption key
// Add 1 byte to ensure there's room for NULL termination at the end (avoid re-allocation)
bufCypherText.EnsureCapacity( bufCypherText.TellPut() + numBytes + 7 + 12 + 16 + 1 );
bufCypherText.PutInt( numBytes ); // TellPut() + 4
bufCypherText.PutInt( iRandom ^ 0xce135ef8 ^ uiXorMask1 ); // TellPut() + 8
bufCypherText.PutInt( iRandom2 ^ 0x7ea55bb0 ); // TellPut() + 12
IceKey cipher(1); /* medium encryption level */
unsigned char ucEncryptionKey[8] = { 0 };
*( int * )&ucEncryptionKey[ 0 ] = iRandom;
*( int * )&ucEncryptionKey[ 4 ] = iRandom2;
cipher.set( ucEncryptionKey );
// Add some padding to the end of bufPlainText so the source data is padded out to 8 bytes
bufPlainText.Put( ucEncryptionKey, sizeof( ucEncryptionKey ) );
// Write directly into cypher buffer and increment Put pointer later
unsigned char *pvPlainText = ( unsigned char * ) bufPlainText.PeekGet();
unsigned char *pvCypher = ( unsigned char * ) bufCypherText.PeekPut(); // getting pointer to TellPut() + 12
int numBytesWritten = 0;
for ( int k = 0; k < numBytes;
k += 8, pvPlainText += 8, pvCypher += 8, numBytesWritten += 8 )
{
cipher.encrypt( pvPlainText, pvCypher ); // writing numBytes rounded to multiple of 8, 8 bytes at a time (TellPut() + 12 + numBytes)
}
cipher.encrypt( ucEncryptionKey, pvCypher ); // TellPut() + 12 + ceil(numBytes, 8)
numBytesWritten += 8;
cipher.encrypt( ucEncryptionKey, pvCypher + 8 ); // TellPut() + 12 + ceil(numBytes, 8) + 8
numBytesWritten += 8;
bufCypherText.SeekPut( bufCypherText.SEEK_CURRENT, numBytesWritten );
}
#ifndef _CERT
CON_COMMAND( mm_datacenter_encrypt_file, "" )
{
if ( args.ArgC() != 5 )
{
Warning( "Incorrect mm_datacenter_encrypt_file syntax!\n" );
Warning( " mm_datacenter_encrypt_file D:\\update\\in.txt D:\\update\\out.bin 20100305 3589\n" );
return;
}
char const *szIn = args.Arg( 1 );
char const *szOut = args.Arg( 2 );
KeyValues *kv = new KeyValues( "" );
KeyValues::AutoDelete autodelete_kv( kv );
if ( !kv->LoadFromFile( g_pFullFileSystem, szIn ) )
{
Warning( "Failed to load '%s'\n", szIn );
return;
}
CUtlBuffer bufOut;
bufOut.ActivateByteSwapping( !CByteswap::IsMachineBigEndian() );
if ( !kv->WriteAsBinary( bufOut ) )
{
Warning( "Failed to serialize kv!\n" );
return;
}
uint uiXorMaskDecrypt = Q_atoi( args.Arg( 3 ) ) ^ Q_atoi( args.Arg( 4 ) );
CUtlBuffer bufDcCrypt;
EncryptBuffer( bufOut, bufDcCrypt, uiXorMaskDecrypt );
if ( !g_pFullFileSystem->WriteFile( szOut, NULL, bufDcCrypt ) )
{
Warning( "Failed to save '%s'\n", szOut );
return;
}
Msg( "Successfully encrypted '%s'\n", szOut );
}
#endif
#endif
//
// Datacenter implementation
//
CDatacenter::CDatacenter() :
m_pInfoChunks( NULL ),
m_pDataInfo( NULL ),
#ifdef _X360
m_pXlspConnection( NULL ),
m_pXlspBatch( NULL ),
m_nVersionStored( 0 ),
m_nVersionApplied( 0 ),
m_numDelayedMountAttempts( 0 ),
m_flDcRequestDelayUntil( 0.0f ),
#elif !defined( NO_STEAM ) && !defined( NO_STEAM_GAMECOORDINATOR ) && !defined( SWDS )
m_JobIDDataRequest( k_GIDNil ),
#endif
m_flNextSearchTime( 0.0f ),
m_bCanReachDatacenter( true ),
m_eState( STATE_IDLE )
{
#ifdef _X360
memset( m_bStorageDeviceAvail, 0, sizeof( m_bStorageDeviceAvail ) );
#endif
}
CDatacenter::~CDatacenter()
{
if ( m_pInfoChunks )
m_pInfoChunks->deleteThis();
m_pInfoChunks = NULL;
if ( m_pDataInfo )
m_pDataInfo->deleteThis();
m_pDataInfo = NULL;
#ifdef _X360
Assert( !m_pXlspConnection );
Assert( !m_pXlspBatch );
#elif !defined( NO_STEAM ) && !defined( NO_STEAM_GAMECOORDINATOR ) && !defined( SWDS )
if ( GGCClient() )
{
GCSDK::CJob *pJob = GGCClient()->GetJobMgr().GetPJob( m_JobIDDataRequest );
delete pJob;
}
#endif
}
void CDatacenter::PushAwayNextUpdate()
{
// Push away the next update to prevent start/stop updates
float flNextUpdateTime = Plat_FloatTime() + mm_datacenter_query_delay.GetFloat();
if ( flNextUpdateTime > m_flNextSearchTime )
m_flNextSearchTime = flNextUpdateTime;
}
void CDatacenter::EnableUpdate( bool bEnable )
{
DevMsg( "Datacenter::EnableUpdate( %d ), current state = %d\n", bEnable, m_eState );
if ( bEnable && m_eState == STATE_PAUSED )
{
m_eState = STATE_IDLE;
PushAwayNextUpdate();
}
if ( !bEnable )
{
RequestStop();
m_eState = STATE_PAUSED;
}
}
KeyValues * CDatacenter::GetDataInfo()
{
return m_pInfoChunks;
}
KeyValues * CDatacenter::GetStats()
{
return m_pInfoChunks ? m_pInfoChunks->FindKey( "stat" ) : NULL;
}
//
// CreateCmdBatch
// creates a new instance of cmd batch to communicate
// with datacenter backend
//
IDatacenterCmdBatch * CDatacenter::CreateCmdBatch( bool bMustSupportPII )
{
CDatacenterCmdBatchImpl *pBatch = new CDatacenterCmdBatchImpl( this, bMustSupportPII );
m_arrCmdBatchObjects.AddToTail( pBatch );
return pBatch;
}
//
// CanReachDatacenter
// returns true if we were able to establish a connection with the
// datacenter backend regardless if it returned valid data or not.
bool CDatacenter::CanReachDatacenter()
{
return m_bCanReachDatacenter;
}
void CDatacenter::OnCmdBatchReleased( CDatacenterCmdBatchImpl *pCmdBatch )
{
m_arrCmdBatchObjects.FindAndRemove( pCmdBatch );
}
void CDatacenter::OnEvent( KeyValues *pEvent )
{
char const *szEvent = pEvent->GetName();
if ( !V_stricmp( szEvent, "OnProfileStorageAvailable" ) )
{
OnStorageDeviceAvailable( pEvent->GetInt( "iController" ) );
}
#ifdef _X360
else if ( !V_stricmp( szEvent, "OnProfilesChanged" ) )
{
memset( m_bStorageDeviceAvail, 0, sizeof( m_bStorageDeviceAvail ) );
}
#endif
}
void CDatacenter::OnStorageDeviceAvailable( int iCtrlr )
{
#ifdef _X360
DWORD nStorageDevice = XBX_GetStorageDeviceId( iCtrlr );
if ( !XBX_DescribeStorageDevice( nStorageDevice ) )
return;
if ( iCtrlr >= 0 && iCtrlr < XUSER_MAX_COUNT )
m_bStorageDeviceAvail[ iCtrlr ] = true;
// Build the config name we're looking for
char strFileName[MAX_PATH];
XBX_MakeStorageContainerRoot( iCtrlr, XBX_USER_SETTINGS_CONTAINER_DRIVE, strFileName, sizeof( strFileName ) );
int nLen = strlen( strFileName );
// Call through normal API function once the content container is opened
Q_snprintf( strFileName + nLen, sizeof(strFileName) - nLen, ":\\%08X_dc.nfo", g_pMatchFramework->GetMatchTitle()->GetTitleID() );
CUtlBuffer bufDcInfoCrypt, bufDcInfo;
if ( !g_pFullFileSystem->ReadFile( strFileName, NULL, bufDcInfoCrypt ) )
{
DevMsg( "CDatacenter::OnStorageDeviceAvailable - ctrlr%d has no dc.nfo\n", iCtrlr );
return;
}
if ( !DecryptBuffer( bufDcInfoCrypt, bufDcInfo ) )
{
DevMsg( "CDatacenter::OnStorageDeviceAvailable - ctrlr%d dc.nfo decrypt failed\n", iCtrlr );
return;
}
// Try reading key values info
KeyValues *pKv = new KeyValues( "" );
bufDcInfo.ActivateByteSwapping( !CByteswap::IsMachineBigEndian() );
if ( !pKv->ReadAsBinary( bufDcInfo ) )
{
DevMsg( "CDatacenter::OnStorageDeviceAvailable - ctrlr%d kv read failed\n", iCtrlr );
pKv->deleteThis();
return;
}
// Check if the client is running the required TU
char const *szTuRequired = pKv->GetString( "turequired" );
if ( Q_stricmp( szTuRequired, MatchSession_GetTuInstalledString() ) )
{
DevMsg( "CDatacenter::OnStorageDeviceAvailable - ctrlr%d has dc.nfo for wrong TU version (turequired = %s, current tu = %s)\n",
iCtrlr, szTuRequired, MatchSession_GetTuInstalledString() );
pKv->deleteThis();
return;
}
// Check version of the key values
int nVersionStored = pKv->GetInt( "version", 0 );
if ( m_pInfoChunks->GetInt( "version", 0 ) >= nVersionStored )
{
DevMsg( "CDatacenter::OnStorageDeviceAvailable - ctrlr%d has stale dc.nfo (version = %d, current version = %d)\n",
iCtrlr, nVersionStored, m_pInfoChunks->GetInt( "version", 0 ) );
m_nVersionStored = MAX( m_nVersionStored, nVersionStored );
pKv->deleteThis();
return;
}
// Key values obtained from storage are fresh
if ( m_pInfoChunks )
m_pInfoChunks->deleteThis();
m_pInfoChunks = pKv;
DevMsg( "CDatacenter::OnStorageDeviceAvailable - ctrlr%d has valid dc.nfo (version = %d)\n", iCtrlr, nVersionStored );
m_nVersionStored = nVersionStored;
OnDatacenterInfoUpdated();
#endif
}
void CDatacenter::StorageDeviceWriteInfo( int iCtrlr )
{
#ifdef _X360
float flTimeStart;
flTimeStart = Plat_FloatTime();
// Build the config name we're looking for
char strFileName[MAX_PATH];
XBX_MakeStorageContainerRoot( iCtrlr, XBX_USER_SETTINGS_CONTAINER_DRIVE, strFileName, sizeof( strFileName ) );
int nLen = strlen( strFileName );
// Call through normal API function once the content container is opened
Q_snprintf( strFileName + nLen, sizeof(strFileName) - nLen, ":\\%08X_dc.nfo", g_pMatchFramework->GetMatchTitle()->GetTitleID() );
//
// Serialize our data
//
CUtlBuffer bufDcInfo;
bufDcInfo.ActivateByteSwapping( !CByteswap::IsMachineBigEndian() );
if ( !m_pInfoChunks->WriteAsBinary( bufDcInfo ) )
return;
CUtlBuffer bufDcCrypt;
EncryptBuffer( bufDcInfo, bufDcCrypt );
g_pFullFileSystem->WriteFile( strFileName, NULL, bufDcCrypt );
// Finish container writes
g_pMatchExtensions->GetIXboxSystem()->FinishContainerWrites( iCtrlr );
DevMsg( "CDatacenter::StorageDeviceWriteInfo finished in %.2f sec\n", Plat_FloatTime() - flTimeStart );
#endif
}
void CDatacenter::TrySaveInfoToUserStorage()
{
#ifdef _X360
static ConVarRef host_write_last_time( "host_write_last_time" );
for ( int k = 0; k < ( int ) XBX_GetNumGameUsers(); ++ k )
{
int iCtrlr = XBX_GetUserId( k );
if ( iCtrlr >= 0 && iCtrlr < XUSER_MAX_COUNT &&
m_bStorageDeviceAvail[ iCtrlr ] && m_pInfoChunks &&
( Plat_FloatTime() - host_write_last_time.GetFloat() > 3.05f ) &&
( m_pInfoChunks->GetInt( "version", 0 ) > m_nVersionStored ) )
{
m_bStorageDeviceAvail[ iCtrlr ] = false;
StorageDeviceWriteInfo( iCtrlr );
return;
}
}
#endif
}
void CDatacenter::Update()
{
#ifdef _X360
#elif !defined( NO_STEAM ) && !defined( NO_STEAM_GAMECOORDINATOR ) && !defined( SWDS )
// Give a time-slice to the GCClient, which is used by Steam to communicate with the datacenter
if ( GGCClient() && !IsLocalClientConnectedToServer() )
{
GGCClient()->BMainLoop( k_nThousand );
}
#endif
switch ( m_eState )
{
case STATE_IDLE:
if ( Plat_FloatTime() > m_flNextSearchTime &&
!IsLocalClientConnectedToServer() )
RequestStart();
else
{
TrySaveInfoToUserStorage();
#ifdef _X360
if ( m_numDelayedMountAttempts > 0 )
{
if ( !IsLocalClientConnectedToServer() )
{
if ( m_numDelayedMountAttempts <= 2 )
{
m_numDelayedMountAttempts -= 2; // if knocking it down to 0, then will allow a retry next frame, otherwise will knock it into negative and will not allow a retry
OnDatacenterInfoUpdated();
}
else
{
-- m_numDelayedMountAttempts;
}
}
else
{
// User connected to server, reset delayed mount attempts
m_numDelayedMountAttempts = mm_datacenter_delay_mount_frames.GetInt(); // attempt to mount again when disconnected
}
}
#endif
}
break;
case STATE_REQUESTING_DATA:
case STATE_REQUESTING_CHUNKS:
RequestUpdate();
break;
case STATE_PAUSED:
// paused
break;
}
// Update all the contained cmd batches
for ( int k = 0; k < m_arrCmdBatchObjects.Count(); ++ k )
{
m_arrCmdBatchObjects[k]->Update();
}
}
void CDatacenter::RequestStart()
{
#ifdef _X360
if ( XBX_GetPrimaryUserId() == XBX_INVALID_USER_ID )
return;
if ( !XBX_GetNumGameUsers() || XBX_GetPrimaryUserIsGuest() )
return;
IPlayerLocal *pLocalPlayer = g_pPlayerManager->GetLocalPlayer( XBX_GetPrimaryUserId() );
if ( !pLocalPlayer )
return;
// We are about to send the request, inject a delay here so that
// we had enough time to discover and mount DLC
if ( !m_flDcRequestDelayUntil )
{
m_flDcRequestDelayUntil = Plat_FloatTime();
g_pMatchFramework->GetMatchSystem()->GetDlcManager()->RequestDlcUpdate();
return;
}
else if ( ( Plat_FloatTime() < m_flDcRequestDelayUntil + mm_datacenter_query_delay.GetFloat() ) ||
( !g_pMatchFramework->GetMatchSystem()->GetDlcManager()->IsDlcUpdateFinished() ) )
{
return; // waiting for the first-time request delay
}
#elif !defined( NO_STEAM ) && !defined( NO_STEAM_GAMECOORDINATOR ) && !defined( SWDS )
if ( !GGCClient() )
return;
// Avoid stacking requests
if ( GGCClient()->GetJobMgr().BJobExists( m_JobIDDataRequest ) )
return;
GCSDK::CJob *pJob = new CGCClientJobDataRequest();
m_JobIDDataRequest = pJob->GetJobID();
pJob->StartJob( NULL );
#else
#endif
#ifdef _X360
m_pXlspConnection = new CXlspConnection( true );
CUtlVector< KeyValues * > arrCommands;
if ( KeyValues *cmd = new KeyValues( "datarequest" ) )
{
// Game title id
cmd->SetInt( "titleid", g_pMatchFramework->GetMatchTitle()->GetTitleID() );
// Data request fields
cmd->SetInt( "version", m_pInfoChunks->GetInt( "version", 0 ) );
cmd->SetInt( "verrprt", mm_datacenter_report_version.GetInt() );
cmd->SetUint64( "dlcmask", g_pMatchFramework->GetMatchSystem()->GetDlcManager()->GetDataInfo()->GetUint64( "@info/installed" ) );
// XUID
cmd->SetUint64( "xuid", pLocalPlayer->GetXUID() );
// Obfuscated name
char chName[ 2 * MAX_PLAYER_NAME_LENGTH ] = {0};
Q_strncpy( chName + 1, pLocalPlayer->GetName(), ARRAYSIZE( chName ) - 1 );
chName[0] = RandomInt( 5, 15 );
for ( char *pch = chName + 1; *pch; ++ pch )
( *pch ) = ( *pch ) ^ chName[0];
cmd->SetString( "name", chName );
// Prepare user privileges
struct Priv_t { XPRIVILEGE_TYPE ePriv, eFriendsOnly; uint64 iFlag; };
Priv_t arrPrivs[] = {
{ XPRIVILEGE_MULTIPLAYER_SESSIONS, XPRIVILEGE_MULTIPLAYER_SESSIONS, 1ull << 0 },
{ XPRIVILEGE_PURCHASE_CONTENT, XPRIVILEGE_PURCHASE_CONTENT, 1ull << 4 },
{ XPRIVILEGE_TRADE_CONTENT, XPRIVILEGE_TRADE_CONTENT, 1ull << 8 },
{ XPRIVILEGE_CONTENT_AUTHOR, XPRIVILEGE_CONTENT_AUTHOR, 1ull << 12 },
{ XPRIVILEGE_COMMUNICATIONS, XPRIVILEGE_COMMUNICATIONS_FRIENDS_ONLY, 1ull << 16 },
{ XPRIVILEGE_PROFILE_VIEWING, XPRIVILEGE_PROFILE_VIEWING_FRIENDS_ONLY, 1ull << 20 },
{ XPRIVILEGE_USER_CREATED_CONTENT, XPRIVILEGE_USER_CREATED_CONTENT_FRIENDS_ONLY, 1ull << 24 },
{ XPRIVILEGE_PRESENCE, XPRIVILEGE_PRESENCE_FRIENDS_ONLY, 1ull << 28 },
{ XPRIVILEGE_VIDEO_COMMUNICATIONS, XPRIVILEGE_VIDEO_COMMUNICATIONS_FRIENDS_ONLY, 1ull << 32 },
};
uint64 uiPrivilegesMask = 0ull;
for ( int jj = 0; jj < ARRAYSIZE( arrPrivs ); ++ jj )
{
uint64 uiThisPriv = 0ull;
BOOL bPriv = FALSE;
Priv_t const &priv = arrPrivs[jj];
DWORD dwResult = XUserCheckPrivilege( XBX_GetPrimaryUserId(), priv.ePriv, &bPriv );
if ( dwResult == ERROR_SUCCESS )
{
uiThisPriv |= priv.iFlag;
if ( bPriv )
{
uiThisPriv |= ( priv.iFlag << 1 );
}
else if ( priv.eFriendsOnly != priv.ePriv )
{
dwResult = XUserCheckPrivilege( XBX_GetPrimaryUserId(), priv.eFriendsOnly, &bPriv );
if ( dwResult == ERROR_SUCCESS )
{
uiThisPriv |= ( priv.iFlag << 2 );
if ( bPriv )
uiThisPriv |= ( priv.iFlag << 3 );
}
}
}
uiPrivilegesMask |= uiThisPriv;
}
cmd->SetUint64( "priv", uiPrivilegesMask );
// LV
cmd->SetInt( "lv", !!g_pMatchExtensions->GetIVEngineClient()->IsLowViolence() );
// Console rgn/locale
cmd->SetInt( "xrgn", XGetGameRegion() );
cmd->SetInt( "xlng", XGetLanguage() );
cmd->SetInt( "xloc", XGetLocale() );
// Video mode
XVIDEO_MODE xvid;
XGetVideoMode( &xvid );
cmd->SetInt( "scrw", xvid.dwDisplayWidth );
cmd->SetInt( "scrh", xvid.dwDisplayHeight );
cmd->SetInt( "vidi", xvid.fIsInterlaced );
cmd->SetInt( "vidw", xvid.fIsWideScreen );
cmd->SetInt( "vidh", xvid.fIsHiDef );
cmd->SetInt( "vids", xvid.VideoStandard );
cmd->SetFloat( "scrr", xvid.RefreshRate );
// Sound mode
static ConVarRef snd_surround_speakers( "snd_surround_speakers" );
cmd->SetInt( "snd", snd_surround_speakers.GetInt() );
// Controllers
int uMaskControllersConnected = 0;
for ( int k = 0; k < XUSER_MAX_COUNT; ++ k )
{
XINPUT_CAPABILITIES caps;
if ( XInputGetCapabilities( k, XINPUT_FLAG_GAMEPAD, &caps ) == ERROR_SUCCESS )
{
uMaskControllersConnected |= ( 1 << k );
}
}
cmd->SetInt( "joy", uMaskControllersConnected );
// Profile info
const UserProfileData &ups = pLocalPlayer->GetPlayerProfileData();
cmd->SetInt( "urgn", ups.region );
cmd->SetInt( "uach", ups.achearned );
cmd->SetInt( "uzon", ups.zone );
cmd->SetInt( "ucrd", ups.cred );
cmd->SetInt( "utit", ups.titlesplayed );
cmd->SetInt( "udif", ups.difficulty );
cmd->SetInt( "usns", ups.sensitivity );
cmd->SetInt( "uyax", ups.yaxis );
cmd->SetInt( "utia", ups.titleachearned );
cmd->SetInt( "utic", ups.titlecred );
// Datacenter information
cmd->SetInt( "*dcpgmi", 0 );
cmd->SetInt( "*dcpgme", 0 );
cmd->SetInt( "*dcpgbu", 0 );
cmd->SetInt( "*dcbwup", 0 );
cmd->SetInt( "*dcbwdn", 0 );
cmd->SetInt( "*net", 0 );
cmd->SetInt( "*nat", 0 );
cmd->SetUint64( "*mac", 0 );
cmd->SetUint64( "*diskDsn", 0 );
cmd->SetUint64( "*diskDnfo", 0 );
cmd->SetUint64( "*diskCnfo", 0 );
cmd->SetUint64( "*diskHnfo", 0 );
cmd->SetUint64( "*disk1nfo", 0 );
// Let title extend it
g_pMMF->GetMatchTitleGameSettingsMgr()->ExtendDatacenterReport( cmd, "datarequest" );
arrCommands.AddToTail( cmd );
}
m_pXlspBatch = new CXlspConnectionCmdBatch( m_pXlspConnection, arrCommands );
#endif
#if !defined( _PS3 ) && !defined( NO_STEAM_GAMECOORDINATOR )
DevMsg( "Datacenter::RequestStart, time %.2f\n", Plat_FloatTime() );
#endif
m_eState = STATE_REQUESTING_DATA;
}
void CDatacenter::RequestStop()
{
#if !defined( _PS3 ) && !defined( NO_STEAM_GAMECOORDINATOR )
DevMsg( "Datacenter::RequestStop, time %.2f, state %d\n", Plat_FloatTime(), m_eState );
#endif
bool bWasRequestingData = false;
#ifdef _X360
if ( m_pXlspBatch )
{
m_pXlspBatch->Destroy();
m_pXlspBatch = NULL;
}
if ( m_pXlspConnection )
{
m_pXlspConnection->Destroy();
m_pXlspConnection = NULL;
bWasRequestingData = true;
}
#elif !defined( NO_STEAM ) && !defined( NO_STEAM_GAMECOORDINATOR ) && !defined( SWDS )
if ( GGCClient() )
{
bWasRequestingData = GGCClient()->GetJobMgr().BJobExists( m_JobIDDataRequest );
}
m_JobIDDataRequest = k_GIDNil;
#endif
if ( bWasRequestingData )
m_flNextSearchTime = Plat_FloatTime() + mm_datacenter_retry_interval.GetFloat();
m_eState = STATE_IDLE;
}
void CDatacenter::RequestUpdate()
{
bool bSuccessfulUpdate = false;
#ifdef _X360
m_pXlspBatch->Update();
if ( !m_pXlspBatch->IsFinished() )
return;
CUtlVector< KeyValues * > &arrResults = m_pXlspBatch->GetResults();
DevMsg( "Datacenter::RequestUpdate, time %.2f, state %d, batch finished: results %d, error %d\n",
Plat_FloatTime(), m_eState, arrResults.Count(), m_pXlspConnection->HasError() );
switch ( m_eState )
{
case STATE_REQUESTING_DATA:
if ( m_pXlspBatch->HasAllResults() )
{
// We received information about chunks and datacenter info
// "datarequest" command succeeded
if ( m_pDataInfo )
m_pDataInfo->deleteThis();
m_pDataInfo = arrResults[0]->MakeCopy();
// Special handling for base disk turequired
char const *szTuRequiredDc = m_pDataInfo->GetString( "turequired" );
if ( !V_strcmp( szTuRequiredDc, "0" ) )
m_pDataInfo->SetString( "turequired", "00000000" );
DevMsg( "Datacenter::RequestUpdate - data info received\n" );
KeyValuesDumpAsDevMsg( m_pDataInfo, 1 );
// Destroy prev batch
m_pXlspBatch->Destroy();
m_pXlspBatch = NULL;
// Prepare the new batch
CUtlVector< KeyValues * > arrBatch;
if ( KeyValues *pBatch2 = m_pDataInfo->FindKey( "chunks" ) )
{
bool bTitleSupportsXlspPatch = !!( g_pMatchFramework->GetMatchTitle()->GetTitleSettingsFlags() & MATCHTITLE_XLSPPATCH_SUPPORTED );
while ( KeyValues *pReq = pBatch2->GetFirstTrueSubKey() )
{
int numChunks = pReq->GetInt( "chunks", 0 );
if ( KeyValues *pChunks = pReq->FindKey( "chunks" ) )
{
pReq->RemoveSubKey( pChunks );
pReq->SetInt( "version", m_pDataInfo->GetInt( "version" ) );
pChunks->deleteThis();
}
bool bShouldRequest = true;
if ( !V_stricmp( "data_patch", pReq->GetName() ) && !bTitleSupportsXlspPatch )
bShouldRequest = false;
if ( bShouldRequest )
{
if ( !numChunks )
{
arrBatch.AddToTail( pReq->MakeCopy() );
}
else
{
for ( int k = 0; k < numChunks; ++ k )
{
pReq->SetInt( "chunk", k + 1 );
arrBatch.AddToTail( pReq->MakeCopy() );
}
}
}
pBatch2->RemoveSubKey( pReq );
pReq->deleteThis();
}
}
if ( !arrBatch.Count() )
{
bSuccessfulUpdate = true;
}
// Start our new batch
else
{
DevMsg( "Datacenter::RequestUpdate - info chunks - requesting %d packets\n", arrBatch.Count() );
m_pXlspBatch = new CXlspConnectionCmdBatch( m_pXlspConnection, arrBatch, mm_datacenter_retry_infochunks_attempts.GetInt() );
m_eState = STATE_REQUESTING_CHUNKS;
return;
}
}
break;
case STATE_REQUESTING_CHUNKS:
bSuccessfulUpdate = m_pXlspBatch->HasAllResults();
DevMsg( "Datacenter::RequestUpdate - info chunks - received %d packets\n", arrResults.Count() );
if ( bSuccessfulUpdate && arrResults.Count() )
{
if ( m_pInfoChunks )
m_pInfoChunks->deleteThis();
m_pInfoChunks = m_pDataInfo->MakeCopy();
for ( int k = 0; k < arrResults.Count(); ++ k )
{
m_pInfoChunks->MergeFrom( arrResults[k], KeyValues::MERGE_KV_UPDATE );
DevMsg( 2, "-- info chunk %d\n", k + 1 );
KeyValuesDumpAsDevMsg( arrResults[k], 1, 2 );
}
DevMsg( 2, "-- Full info chunks:\n" );
KeyValuesDumpAsDevMsg( m_pInfoChunks, 1,2 );
}
break;
}
#elif !defined( NO_STEAM ) && !defined( NO_STEAM_GAMECOORDINATOR ) && !defined( SWDS )
if ( GGCClient() )
{
CGCClientJobDataRequest *pJob = (CGCClientJobDataRequest *)GGCClient()->GetJobMgr().GetPJob( m_JobIDDataRequest );
if ( pJob )
{
if ( !pJob->BComplete() )
return;
bSuccessfulUpdate = pJob->BSuccess();
if ( bSuccessfulUpdate )
{
if ( m_pDataInfo )
m_pDataInfo->deleteThis();
if ( m_pInfoChunks )
m_pInfoChunks->deleteThis();
m_pDataInfo = pJob->GetResults()->MakeCopy();
m_pInfoChunks = pJob->GetResults()->MakeCopy();
}
pJob->Finish();
}
}
#else
#endif
RequestStop();
#if !defined( _PS3 ) && !defined( NO_STEAM_GAMECOORDINATOR )
DevMsg( "Datacenter::RequestUpdate %s\n", bSuccessfulUpdate ? "successful" : "failed" );
#endif
m_bCanReachDatacenter = bSuccessfulUpdate;
if ( bSuccessfulUpdate )
{
m_flNextSearchTime = Plat_FloatTime() + mm_datacenter_update_interval.GetFloat();
OnDatacenterInfoUpdated();
}
}
static void UnpackPatchBinary( KeyValues *pPatch, CUtlBuffer &buf )
{
int nSize = pPatch->GetInt( "size" );
if ( !nSize )
{
buf.Purge();
return;
}
buf.EnsureCapacity( nSize + sizeof( uint64 ) ); // include extra room for alignments
buf.SeekPut( buf.SEEK_HEAD, nSize ); // set the data size
unsigned char *pchBuffer = ( unsigned char * ) buf.Base();
memset( pchBuffer, 0, nSize );
for ( KeyValues *sub = pPatch->GetFirstTrueSubKey(); sub; sub = sub->GetNextTrueSubKey() )
{
for( KeyValues *val = sub->GetFirstValue(); val; val = val->GetNextValue() )
{
if ( !val->GetName()[0] && val->GetDataType() == KeyValues::TYPE_UINT64 )
{
if ( !nSize )
{
nSize -= sizeof( uint64 );
goto unpack_error;
}
uint64 ui = val->GetUint64();
for ( int k = 0; k < MIN( nSize, sizeof( ui ) ); ++ k )
{
pchBuffer[k] = ( unsigned char )( ( ui >> ( 8 * k ) ) & 0xFF );
}
nSize -= MIN( nSize, sizeof( ui ) );
pchBuffer += MIN( nSize, sizeof( ui ) );
}
}
}
// If all the bytes were correctly written to buffer, then the unpack succeeded
if ( !nSize )
return;
unpack_error:
// Transmitted patch indicated a size different than transmitted data!
DevWarning( "UnpackPatchBinary error: %d size indicated, but %d bytes failed to unpack!\n", buf.TellPut(), nSize );
buf.Purge();
return;
}
void CDatacenter::OnDatacenterInfoUpdated()
{
#ifdef _X360
// Check if the client is running the required TU
char const *szTuRequired = m_pInfoChunks->GetString( "turequired" );
if ( Q_stricmp( szTuRequired, MatchSession_GetTuInstalledString() ) )
{
DevWarning( "CDatacenter::OnDatacenterInfoUpdated - wrong TU version (datacenter requires = %s, installed = %s)\n",
szTuRequired, MatchSession_GetTuInstalledString() );
return;
}
// Don't try to mount the update in the middle of the game
if ( IsLocalClientConnectedToServer() )
{
m_numDelayedMountAttempts = mm_datacenter_delay_mount_frames.GetInt(); // attempt to mount again when disconnected
return;
}
#endif
// Downloaded update version
int nUpdateVersion = m_pInfoChunks->GetInt( "version", 0 );
bool bXlspPatchFileMounted = false;
#ifdef _X360
// Filesystem needs to mount new patch binary
if ( KeyValues *pPatch = m_pInfoChunks->FindKey( "patch" ) )
{
if ( nUpdateVersion > m_nVersionApplied )
{
CUtlBuffer bufPatchData;
UnpackPatchBinary( pPatch, bufPatchData );
DevMsg( "CDatacenter::OnDatacenterInfoUpdated mounting patch binary data: %d bytes at %p\n", bufPatchData.TellPut(), bufPatchData.Base() );
if ( g_pFullFileSystem->AddXLSPUpdateSearchPath( bufPatchData.Base(), bufPatchData.TellPut() ) )
{
DevMsg( "CDatacenter::OnDatacenterInfoUpdated successfully mounted patch binary data (version %d -> %d)\n", m_nVersionApplied, nUpdateVersion );
m_nVersionApplied = nUpdateVersion;
bXlspPatchFileMounted = true;
// Try to apply cvar section
CUtlBuffer bufCvarCrypt, bufCvarInfo;
bool bMainFilePresent = g_pFullFileSystem->ReadFile( "scripts/main.nut", NULL, bufCvarCrypt );
uint uiXorMaskDecrypt = Q_atoi( szTuRequired ) ^ bufCvarCrypt.TellPut();
if ( bMainFilePresent &&
DecryptBuffer( bufCvarCrypt, bufCvarInfo, uiXorMaskDecrypt ) )
{
KeyValues *cvkv = new KeyValues( "" );
KeyValues::AutoDelete autodelete_cvkv( cvkv );
bufCvarInfo.ActivateByteSwapping( !CByteswap::IsMachineBigEndian() );
if ( cvkv->ReadAsBinary( bufCvarInfo ) )
{
// Process the cvar section and patch them up
for ( KeyValues *kvName = cvkv->FindKey( "cvar" )->GetFirstValue(); kvName; kvName = kvName->GetNextValue() )
{
ConVarRef cvRef( kvName->GetName(), true ); // deliberately non-static, enumerating in a loop
if ( !cvRef.IsValid() )
{
#ifndef _CERT
DevWarning( "CDatacenter::OnDatacenterInfoUpdated failed to update cvar '%s' = '%s'\n", kvName->GetName(), kvName->GetString() );
#endif
}
else
{
#ifndef _CERT
DevMsg( "CDatacenter::OnDatacenterInfoUpdated updating cvar '%s' = '%s' -> '%s'\n", kvName->GetName(), cvRef.GetString(), kvName->GetString() );
#endif
cvRef.SetValue( kvName->GetString() );
}
}
}
}
}
else
{
DevMsg( "CDatacenter::OnDatacenterInfoUpdated failed to mount version %d (was %d)\n", nUpdateVersion, m_nVersionApplied );
// Give ourselves a retry attempt
++ m_numDelayedMountAttempts;
}
}
}
#endif
// Signal all other subscribers about the update
if ( KeyValues *newEvent = new KeyValues( "OnDatacenterUpdate", "version", nUpdateVersion ) )
{
if ( bXlspPatchFileMounted )
newEvent->SetInt( "filesystempatch", 1 );
g_pMatchFramework->GetEventsSubscription()->BroadcastEvent( newEvent );
}
}
//////////////////////////////////////////////////////////////////////////
//
// CDatacenterCmdBatchImpl
//
CDatacenterCmdBatchImpl::CDatacenterCmdBatchImpl( CDatacenter *pParent, bool bMustSupportPII ) :
#ifdef _X360
m_pXlspConnection( NULL ),
m_pXlspBatch( NULL ),
#endif
m_pParent( pParent ),
m_arrCommands(),
m_numRetriesAllowedPerCmd( 0 ),
m_flRetryCmdTimeout( 0 ),
m_bDestroyWhenFinished( true ),
m_bMustSupportPII( bMustSupportPII )
{
}
void CDatacenterCmdBatchImpl::AddCommand( KeyValues *pCommand )
{
if ( !pCommand )
return;
#ifdef _X360
if ( m_pXlspBatch )
{
Warning( "CDatacenterCmdBatchImpl::AddCommand after already initiated batch processing!\n" );
Assert( 0 );
return;
}
#endif
m_arrCommands.AddToTail( pCommand->MakeCopy() );
}
bool CDatacenterCmdBatchImpl::IsFinished()
{
#ifdef _X360
return m_pXlspBatch && m_pXlspBatch->IsFinished();
#else
return true;
#endif
}
int CDatacenterCmdBatchImpl::GetNumResults()
{
#ifdef _X360
return m_pXlspBatch ? m_pXlspBatch->GetResults().Count() : 0;
#else
return 0;
#endif
}
KeyValues * CDatacenterCmdBatchImpl::GetResult( int idx )
{
#ifdef _X360
if ( !m_pXlspBatch )
return NULL;
if ( !m_pXlspBatch->GetResults().IsValidIndex( idx ) )
return NULL;
return m_pXlspBatch->GetResults()[idx];
#else
return NULL;
#endif
}
void CDatacenterCmdBatchImpl::Destroy()
{
if ( m_pParent )
m_pParent->OnCmdBatchReleased( this );
for ( int k = 0; k < m_arrCommands.Count(); ++ k )
{
m_arrCommands[k]->deleteThis();
}
m_arrCommands.Purge();
#ifdef _X360
if ( m_pXlspBatch )
{
m_pXlspBatch->Destroy();
m_pXlspBatch = NULL;
}
if ( m_pXlspConnection )
{
m_pXlspConnection->Destroy();
m_pXlspConnection = NULL;
}
#endif
delete this;
}
void CDatacenterCmdBatchImpl::SetDestroyWhenFinished( bool bDestroyWhenFinished )
{
m_bDestroyWhenFinished = bDestroyWhenFinished;
}
void CDatacenterCmdBatchImpl::SetNumRetriesAllowedPerCmd( int numRetriesAllowed )
{
m_numRetriesAllowedPerCmd = numRetriesAllowed;
}
void CDatacenterCmdBatchImpl::SetRetryCmdTimeout( float flRetryCmdTimeout )
{
m_flRetryCmdTimeout = flRetryCmdTimeout;
}
void CDatacenterCmdBatchImpl::Update()
{
#ifdef _X360
if ( !m_pXlspBatch && m_arrCommands.Count() )
{
// Need to kick off XLSP batch processing
m_pXlspConnection = new CXlspConnection( m_bMustSupportPII );
m_pXlspBatch = new CXlspConnectionCmdBatch( m_pXlspConnection, m_arrCommands, m_numRetriesAllowedPerCmd, m_flRetryCmdTimeout );
return;
}
if ( m_pXlspBatch )
{
m_pXlspBatch->Update();
if ( m_pXlspBatch->IsFinished() )
{
// Detach this cmd batch processor from the frame updates
if ( m_pParent )
m_pParent->OnCmdBatchReleased( this );
// Notify listeners
// Signal that we are finished with cmd batch
KeyValues *pNotify = new KeyValues( "OnDatacenterCmdBatchUpdate", "update", "finished" );
pNotify->SetPtr( "cmdbatch", this );
pNotify->SetInt( "results", m_pXlspBatch->GetResults().Count() );
g_pMatchFramework->GetEventsSubscription()->BroadcastEvent( pNotify );
// Destroy ourselves since we are finished
if ( m_bDestroyWhenFinished )
this->Destroy();
}
}
#else
// Destroy ourselves since we cannot do any work anyway
if ( m_bDestroyWhenFinished )
this->Destroy();
#endif
}