//========= 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 }