diff options
Diffstat (limited to 'utils/vmpi/vmpi_service')
| -rw-r--r-- | utils/vmpi/vmpi_service/StdAfx.cpp | 15 | ||||
| -rw-r--r-- | utils/vmpi/vmpi_service/StdAfx.h | 35 | ||||
| -rw-r--r-- | utils/vmpi/vmpi_service/perf_counters.cpp | 500 | ||||
| -rw-r--r-- | utils/vmpi/vmpi_service/perf_counters.h | 28 | ||||
| -rw-r--r-- | utils/vmpi/vmpi_service/resource.h | 18 | ||||
| -rw-r--r-- | utils/vmpi/vmpi_service/service_conn_mgr.cpp | 234 | ||||
| -rw-r--r-- | utils/vmpi/vmpi_service/service_conn_mgr.h | 92 | ||||
| -rw-r--r-- | utils/vmpi/vmpi_service/service_helpers.cpp | 181 | ||||
| -rw-r--r-- | utils/vmpi/vmpi_service/service_helpers.h | 43 | ||||
| -rw-r--r-- | utils/vmpi/vmpi_service/vmpi_service.cpp | 1710 | ||||
| -rw-r--r-- | utils/vmpi/vmpi_service/vmpi_service.h | 19 | ||||
| -rw-r--r-- | utils/vmpi/vmpi_service/vmpi_service.rc | 74 | ||||
| -rw-r--r-- | utils/vmpi/vmpi_service/vmpi_service.vpc | 60 |
13 files changed, 3009 insertions, 0 deletions
diff --git a/utils/vmpi/vmpi_service/StdAfx.cpp b/utils/vmpi/vmpi_service/StdAfx.cpp new file mode 100644 index 0000000..f3f31b0 --- /dev/null +++ b/utils/vmpi/vmpi_service/StdAfx.cpp @@ -0,0 +1,15 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +// stdafx.cpp : source file that includes just the standard includes +// vmpi_service.pch will be the pre-compiled header +// stdafx.obj will contain the pre-compiled type information + +#include "stdafx.h" + +// TODO: reference any additional headers you need in STDAFX.H +// and not in this file diff --git a/utils/vmpi/vmpi_service/StdAfx.h b/utils/vmpi/vmpi_service/StdAfx.h new file mode 100644 index 0000000..b490c37 --- /dev/null +++ b/utils/vmpi/vmpi_service/StdAfx.h @@ -0,0 +1,35 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +// stdafx.h : include file for standard system include files, +// or project specific include files that are used frequently, but +// are changed infrequently +// + +#if !defined(AFX_STDAFX_H__A1923E9A_F174_4448_8004_33888CD7DC88__INCLUDED_) +#define AFX_STDAFX_H__A1923E9A_F174_4448_8004_33888CD7DC88__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 + +#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers + +#include <windows.h> +#include <winsock2.h> +#include <shellapi.h> +#include <winuser.h> +#include "basetypes.h" +#include <stdio.h> +#include "iphelpers.h" + +// TODO: reference additional headers your program requires here + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_STDAFX_H__A1923E9A_F174_4448_8004_33888CD7DC88__INCLUDED_) diff --git a/utils/vmpi/vmpi_service/perf_counters.cpp b/utils/vmpi/vmpi_service/perf_counters.cpp new file mode 100644 index 0000000..3d98d2a --- /dev/null +++ b/utils/vmpi/vmpi_service/perf_counters.cpp @@ -0,0 +1,500 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= + +#include "stdafx.h" +#include "tier1/utldict.h" +#include <pdh.h> +#include <pdhmsg.h> +#include "perf_counters.h" + + +#if 1 + +class CPerfTracker : public IPerfTracker +{ +public: + + CPerfTracker() + { + m_hProcessorTimeCounter = NULL; + m_dwProcessID = 0; + if ( PdhOpenQuery( NULL, 0, &m_hQuery ) != ERROR_SUCCESS ) + m_hQuery = NULL; + + SYSTEM_INFO info; + GetSystemInfo( &info ); + m_nProcessors = (int)info.dwNumberOfProcessors; + } + + ~CPerfTracker() + { + if ( m_hQuery ) + PdhCloseQuery( m_hQuery ); + } + + virtual void Init( unsigned long dwProcessID ) + { + Term(); + + m_dwProcessID = dwProcessID; + + char instanceName[512]; + if ( GetInstanceNameFromProcessID( m_dwProcessID, instanceName, sizeof( instanceName ) ) ) + { + // Create a counter to watch this process' time. + char str[512]; + V_snprintf( str, sizeof( str ), "\\Process(%s)\\%% Processor Time", instanceName ); + if ( PdhAddCounter( m_hQuery, str, 0, &m_hProcessorTimeCounter ) != ERROR_SUCCESS ) + { + m_hProcessorTimeCounter = NULL; + } + + V_snprintf( str, sizeof( str ), "\\Process(%s)\\Private Bytes", instanceName ); + if ( PdhAddCounter( m_hQuery, str, 0, &m_hPrivateBytesCounter ) != ERROR_SUCCESS ) + { + m_hPrivateBytesCounter = NULL; + } + } + } + + void Term() + { + if ( m_hProcessorTimeCounter ) + PdhRemoveCounter( m_hProcessorTimeCounter ); + + if ( m_hPrivateBytesCounter ) + PdhRemoveCounter( m_hPrivateBytesCounter ); + + m_hProcessorTimeCounter = NULL; + m_hPrivateBytesCounter = NULL; + } + + virtual void Release() + { + delete this; + } + + virtual unsigned long GetProcessID() + { + return m_dwProcessID; + } + + virtual void GetPerfData( int &processorPercentage, int &memoryUsageMegabytes ) + { + processorPercentage = 101; + memoryUsageMegabytes = 0; + + // Collect query data.. + PDH_STATUS ret = PdhCollectQueryData( m_hQuery ); + if ( ret != ERROR_SUCCESS ) + return; + + // Check processor usage. + DWORD dwType; + PDH_FMT_COUNTERVALUE counterValue; + if ( PdhGetFormattedCounterValue( m_hProcessorTimeCounter, PDH_FMT_LONG | PDH_FMT_NOCAP100, &dwType, &counterValue ) == ERROR_SUCCESS ) + processorPercentage = counterValue.longValue / m_nProcessors; + else + processorPercentage = 101; + + // Check memory usage. + if ( PdhGetFormattedCounterValue( m_hPrivateBytesCounter, PDH_FMT_DOUBLE | PDH_FMT_NOCAP100, &dwType, &counterValue ) == ERROR_SUCCESS ) + memoryUsageMegabytes = (int)(counterValue.doubleValue / (1024.0 * 1024.0)); + else + memoryUsageMegabytes = 0; + } + + +private: + + bool GetInstanceNameFromProcessID( DWORD processID, char *instanceName, int instanceNameLen ) + { + instanceName[0] = 0; + + bool bRet = false; + + // This refreshes the object list. If we don't do this, it won't get new process IDs correctly. + DWORD dummy = 0; + PdhEnumObjects( NULL, NULL, NULL, &dummy, PERF_DETAIL_NOVICE, true ); + + // Find out how much data we need. + DWORD counterListLen=2, instanceListLen=2; + char *counterList = new char[counterListLen]; + char *instanceList = new char[instanceListLen]; + PDH_STATUS stat = PdhEnumObjectItems( NULL, NULL, "Process", counterList, &counterListLen, instanceList, &instanceListLen, PERF_DETAIL_NOVICE, 0 ); + if ( stat == PDH_MORE_DATA ) + { + delete [] counterList; + delete [] instanceList; + char *counterList = new char[counterListLen]; + char *instanceList = new char[instanceListLen]; + + stat = PdhEnumObjectItems( NULL, NULL, "Process", counterList, &counterListLen, instanceList, &instanceListLen, PERF_DETAIL_NOVICE, 0 ); + if ( stat == ERROR_SUCCESS ) + { + // We need the # of each one.. + CUtlDict<int,int> counts; + + // The instance name list is a bunch of strings terminated with nulls. The final one has two nulls after it. + // Walk through the list and get the process ID associated with each instance name. + const char *pCur = instanceList; + while ( *pCur ) + { + int index = counts.Find( pCur ); + if ( index == counts.InvalidIndex() ) + counts.Insert( pCur, 1 ); + else + counts[index]++; + + pCur += strlen( pCur ) + 1; + } + + // Each instance (like "vrad") might have multiple versions, like if you're running multiple vrad processes at the same time. + for ( int i=counts.First(); i != counts.InvalidIndex(); i=counts.Next( i ) ) + { + const char *pInstanceName = counts.GetElementName( i ); + int nInstances = counts[i]; + for ( int iInstance=0; iInstance < nInstances; iInstance++ ) + { + char testInstanceName[256], fullObjectName[256]; + V_snprintf( testInstanceName, sizeof( testInstanceName ), "%s#%d", pInstanceName, iInstance ); + V_snprintf( fullObjectName, sizeof( fullObjectName ), "\\Process(%s)\\ID Process", testInstanceName ); + + HCOUNTER hCounter = NULL; + stat = PdhAddCounter( m_hQuery, fullObjectName, 0, &hCounter ); + if ( stat == ERROR_SUCCESS ) + { + stat = PdhCollectQueryData( m_hQuery ); + if ( stat == ERROR_SUCCESS ) + { + DWORD dwType; + PDH_FMT_COUNTERVALUE counterValue; + stat = PdhGetFormattedCounterValue( hCounter, PDH_FMT_LONG, &dwType, &counterValue ); + if ( stat == 0 && counterValue.longValue == (long)processID ) + { + // Finall! We found it. + V_strncpy( instanceName, testInstanceName, instanceNameLen ); + bRet = true; + PdhRemoveCounter( hCounter ); + break; + } + } + + PdhRemoveCounter( hCounter ); + } + } + + if ( bRet ) + break; + } + } + + delete [] counterList; + delete [] instanceList; + } + + return bRet; + } + +private: + DWORD m_dwProcessID; + PDH_HQUERY m_hQuery; + HCOUNTER m_hProcessorTimeCounter; + HCOUNTER m_hPrivateBytesCounter; + int m_nProcessors; +}; + + +IPerfTracker* CreatePerfTracker() +{ + return new CPerfTracker; +} + + +#else + +#include <winperf.h> + +// --------------------------------------------------------------------------------------------------------------------- // +// NOTE: THIS IS THE OLD, UGLY WAY TO DO IT. +// --------------------------------------------------------------------------------------------------------------------- // + +class CPerfTracker +{ +public: + CPerfTracker(); + void Init( unsigned long dwProcessID ); + + unsigned long GetProcessID(); + + // Get the percentage of CPU time that the process is using. + int GetCPUPercentage(); + +private: + DWORD m_dwProcessID; + LONGLONG m_lnOldValue; + LARGE_INTEGER m_OldPerfTime100nSec; + int m_nProcessors; +}; + +#define TOTALBYTES 100*1024 +#define BYTEINCREMENT 10*1024 + +#define SYSTEM_OBJECT_INDEX 2 // 'System' object +#define PROCESS_OBJECT_INDEX 230 // 'Process' object +#define PROCESSOR_OBJECT_INDEX 238 // 'Processor' object +#define TOTAL_PROCESSOR_TIME_COUNTER_INDEX 240 // '% Total processor time' counter (valid in WinNT under 'System' object) +#define PROCESSOR_TIME_COUNTER_INDEX 6 // '% processor time' counter (for Win2K/XP) + + +// +// The performance data is accessed through the registry key +// HKEY_PEFORMANCE_DATA. +// However, although we use the registry to collect performance data, +// the data is not stored in the registry database. +// Instead, calling the registry functions with the HKEY_PEFORMANCE_DATA key +// causes the system to collect the data from the appropriate system +// object managers. +// +// QueryPerformanceData allocates memory block for getting the +// performance data. +// +// +void QueryPerformanceData(PERF_DATA_BLOCK **pPerfData, DWORD dwObjectIndex, DWORD dwCounterIndex) +{ + // + // Since i want to use the same allocated area for each query, + // i declare CBuffer as static. + // The allocated is changed only when RegQueryValueEx return ERROR_MORE_DATA + // + static CUtlVector<char> Buffer; + if ( Buffer.Count() == 0 ) + Buffer.SetSize( TOTALBYTES ); + + DWORD BufferSize = Buffer.Count(); + LONG lRes; + + char keyName[32]; + V_snprintf(keyName, sizeof(keyName), "%d",dwObjectIndex); + + memset( Buffer.Base(), 0, Buffer.Count() ); + while( (lRes = RegQueryValueEx( HKEY_PERFORMANCE_DATA, + keyName, + NULL, + NULL, + (LPBYTE)Buffer.Base(), + &BufferSize )) == ERROR_MORE_DATA ) + { + // Get a buffer that is big enough. + BufferSize += BYTEINCREMENT; + Buffer.SetSize( BufferSize ); + } + + *pPerfData = (PPERF_DATA_BLOCK)Buffer.Base(); +} + + +/***************************************************************** + * * + * Functions used to navigate through the performance data. * + * * + *****************************************************************/ + +inline PPERF_OBJECT_TYPE FirstObject( PPERF_DATA_BLOCK PerfData ) +{ + return( (PPERF_OBJECT_TYPE)((PBYTE)PerfData + PerfData->HeaderLength) ); +} + +inline PPERF_OBJECT_TYPE NextObject( PPERF_OBJECT_TYPE PerfObj ) +{ + return( (PPERF_OBJECT_TYPE)((PBYTE)PerfObj + PerfObj->TotalByteLength) ); +} + +inline PPERF_COUNTER_DEFINITION FirstCounter( PPERF_OBJECT_TYPE PerfObj ) +{ + return( (PPERF_COUNTER_DEFINITION) ((PBYTE)PerfObj + PerfObj->HeaderLength) ); +} + +inline PPERF_COUNTER_DEFINITION NextCounter( PPERF_COUNTER_DEFINITION PerfCntr ) +{ + return( (PPERF_COUNTER_DEFINITION)((PBYTE)PerfCntr + PerfCntr->ByteLength) ); +} + +inline PPERF_INSTANCE_DEFINITION FirstInstance( PPERF_OBJECT_TYPE PerfObj ) +{ + return( (PPERF_INSTANCE_DEFINITION)((PBYTE)PerfObj + PerfObj->DefinitionLength) ); +} + +inline PPERF_INSTANCE_DEFINITION NextInstance( PPERF_INSTANCE_DEFINITION PerfInst ) +{ + PPERF_COUNTER_BLOCK PerfCntrBlk; + + PerfCntrBlk = (PPERF_COUNTER_BLOCK)((PBYTE)PerfInst + PerfInst->ByteLength); + + return( (PPERF_INSTANCE_DEFINITION)((PBYTE)PerfCntrBlk + PerfCntrBlk->ByteLength) ); +} + + +template<class T> +T GetCounterValueForProcessID(PPERF_OBJECT_TYPE pPerfObj, DWORD dwCounterIndex, DWORD dwProcessID) +{ + unsigned long PROC_ID_COUNTER = 784; + + BOOL bProcessIDExist = FALSE; + PPERF_COUNTER_DEFINITION pPerfCntr = NULL; + PPERF_COUNTER_DEFINITION pTheRequestedPerfCntr = NULL; + PPERF_COUNTER_DEFINITION pProcIDPerfCntr = NULL; + PPERF_INSTANCE_DEFINITION pPerfInst = NULL; + PPERF_COUNTER_BLOCK pCounterBlock = NULL; + + // Get the first counter. + + pPerfCntr = FirstCounter( pPerfObj ); + + for( DWORD j=0; j < pPerfObj->NumCounters; j++ ) + { + if (pPerfCntr->CounterNameTitleIndex == PROC_ID_COUNTER) + { + pProcIDPerfCntr = pPerfCntr; + if (pTheRequestedPerfCntr) + break; + } + + if (pPerfCntr->CounterNameTitleIndex == dwCounterIndex) + { + pTheRequestedPerfCntr = pPerfCntr; + if (pProcIDPerfCntr) + break; + } + + // Get the next counter. + + pPerfCntr = NextCounter( pPerfCntr ); + } + + if( pPerfObj->NumInstances == PERF_NO_INSTANCES ) + { + pCounterBlock = (PPERF_COUNTER_BLOCK) ((LPBYTE) pPerfObj + pPerfObj->DefinitionLength); + } + else + { + pPerfInst = FirstInstance( pPerfObj ); + + for( int k=0; k < pPerfObj->NumInstances; k++ ) + { + pCounterBlock = (PPERF_COUNTER_BLOCK) ((LPBYTE) pPerfInst + pPerfInst->ByteLength); + if (pCounterBlock) + { + DWORD processID = *(DWORD*)((LPBYTE) pCounterBlock + pProcIDPerfCntr->CounterOffset); + if (processID == dwProcessID) + { + bProcessIDExist = TRUE; + break; + } + } + + // Get the next instance. + pPerfInst = NextInstance( pPerfInst ); + } + } + + if (bProcessIDExist && pCounterBlock) + { + T *lnValue = NULL; + lnValue = (T*)((LPBYTE) pCounterBlock + pTheRequestedPerfCntr->CounterOffset); + return *lnValue; + } + return -1; +} + + +template<class T> +T GetCounterValueForProcessID(PERF_DATA_BLOCK **pPerfData, DWORD dwObjectIndex, DWORD dwCounterIndex, DWORD dwProcessID) +{ + QueryPerformanceData(pPerfData, dwObjectIndex, dwCounterIndex); + + PPERF_OBJECT_TYPE pPerfObj = NULL; + T lnValue = {0}; + + // Get the first object type. + pPerfObj = FirstObject( *pPerfData ); + + // Look for the given object index + + for( DWORD i=0; i < (*pPerfData)->NumObjectTypes; i++ ) + { + + if (pPerfObj->ObjectNameTitleIndex == dwObjectIndex) + { + lnValue = GetCounterValueForProcessID<T>(pPerfObj, dwCounterIndex, dwProcessID); + break; + } + + pPerfObj = NextObject( pPerfObj ); + } + return lnValue; +} + + +// ------------------------------------------------------------------------------------------- // +// CPerfTracker implementation. +// ------------------------------------------------------------------------------------------- // + +CPerfTracker::CPerfTracker() +{ + Init( 0 ); + + SYSTEM_INFO info; + GetSystemInfo( &info ); + m_nProcessors = (int)info.dwNumberOfProcessors; +} + + +void CPerfTracker::Init( unsigned long dwProcessID ) +{ + m_dwProcessID = dwProcessID; + m_lnOldValue = 0; +} + + +unsigned long CPerfTracker::GetProcessID() +{ + return m_dwProcessID; +} + + +int CPerfTracker::GetCPUPercentage() +{ + DWORD dwObjectIndex = PROCESS_OBJECT_INDEX; + DWORD dwCpuUsageIndex = PROCESSOR_TIME_COUNTER_INDEX; + + PPERF_DATA_BLOCK pPerfData = NULL; + LONGLONG lnNewValue = GetCounterValueForProcessID<LONGLONG>( &pPerfData, dwObjectIndex, dwCpuUsageIndex, m_dwProcessID ); + LARGE_INTEGER NewPerfTime100nSec = pPerfData->PerfTime100nSec; + + if ( m_lnOldValue == 0 ) + { + m_lnOldValue = lnNewValue; + m_OldPerfTime100nSec = NewPerfTime100nSec; + return 0; + } + + LONGLONG lnValueDelta = lnNewValue - m_lnOldValue; + double DeltaPerfTime100nSec = (double)NewPerfTime100nSec.QuadPart - (double)m_OldPerfTime100nSec.QuadPart; + + m_lnOldValue = lnNewValue; + m_OldPerfTime100nSec = NewPerfTime100nSec; + + double a = (double)lnValueDelta / DeltaPerfTime100nSec; + + int CpuUsage = (int) (a*100); + if (CpuUsage < 0) + return 0; + + return CpuUsage / m_nProcessors; +} + +#endif
\ No newline at end of file diff --git a/utils/vmpi/vmpi_service/perf_counters.h b/utils/vmpi/vmpi_service/perf_counters.h new file mode 100644 index 0000000..aa80014 --- /dev/null +++ b/utils/vmpi/vmpi_service/perf_counters.h @@ -0,0 +1,28 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= + +#ifndef PERF_COUNTERS_H +#define PERF_COUNTERS_H +#ifdef _WIN32 +#pragma once +#endif + + +class IPerfTracker +{ +public: + virtual void Init( unsigned long dwProcessID ) = 0; + virtual void Release() = 0; + + virtual unsigned long GetProcessID() = 0; + virtual void GetPerfData( int &processorPercentage, int &memoryUsageMegabytes ) = 0; +}; + + +IPerfTracker* CreatePerfTracker(); + + +#endif // PERF_COUNTERS_H diff --git a/utils/vmpi/vmpi_service/resource.h b/utils/vmpi/vmpi_service/resource.h new file mode 100644 index 0000000..492680b --- /dev/null +++ b/utils/vmpi/vmpi_service/resource.h @@ -0,0 +1,18 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by Vmpi_service.rc +// +#define IDS_STRING102 102 +#define IDS_VERSION_STRING 102 // *** If this changes, change the matching value in vmpi_defs.h! + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 103 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/utils/vmpi/vmpi_service/service_conn_mgr.cpp b/utils/vmpi/vmpi_service/service_conn_mgr.cpp new file mode 100644 index 0000000..c8b48ca --- /dev/null +++ b/utils/vmpi/vmpi_service/service_conn_mgr.cpp @@ -0,0 +1,234 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include "stdafx.h" +#include "service_conn_mgr.h" +#include "vmpi.h" +#include "tier0/dbg.h" +#include "tcpsocket_helpers.h" + + +#define SERVICECONNMGR_CONNECT_ATTEMPT_INTERVAL 1000 + + + +// ------------------------------------------------------------------------------------------- // +// CServiceConn. +// ------------------------------------------------------------------------------------------- // + +CServiceConn::CServiceConn() +{ + m_pSocket = NULL; +} + + +CServiceConn::~CServiceConn() +{ + if ( m_pSocket ) + m_pSocket->Release(); +} + + +// ------------------------------------------------------------------------------------------- // +// CServiceConnMgr. +// ------------------------------------------------------------------------------------------- // + + +CServiceConnMgr::CServiceConnMgr() +{ + m_bServer = false; + m_bShuttingDown = false; + m_pListenSocket = NULL; +} + + +CServiceConnMgr::~CServiceConnMgr() +{ + Term(); +} + + +bool CServiceConnMgr::InitServer() +{ + Term(); + + m_bServer = true; + + // Create a socket to listen on. + for ( int iPort=VMPI_SERVICE_FIRST_UI_PORT; iPort <= VMPI_SERVICE_LAST_UI_PORT; iPort++ ) + { + m_pListenSocket = CreateTCPListenSocketEmu( iPort, 5 ); + if ( m_pListenSocket ) + break; + } + if ( !m_pListenSocket ) + return false; + + return true; +} + + +bool CServiceConnMgr::InitClient() +{ + Term(); + + m_bServer = false; + + AttemptConnect(); + return true; +} + + +void CServiceConnMgr::Term() +{ + m_bShuttingDown = true; // This prevents some reentrancy. + + // Get rid of our registry key. + if ( m_pListenSocket ) + { + m_pListenSocket->Release(); + m_pListenSocket = NULL; + } + + m_Connections.PurgeAndDeleteElements(); + + m_bShuttingDown = false; +} + + +bool CServiceConnMgr::IsConnected() +{ + return m_Connections.Count() != 0; +} + + +void CServiceConnMgr::Update() +{ + DWORD curTime = GetTickCount(); + + // Connect if we're an unconnected client. + if ( m_bServer ) + { + if ( m_pListenSocket ) + { + // Listen for more connections. + while ( 1 ) + { + CIPAddr addr; + ITCPSocket *pSocket = m_pListenSocket->UpdateListen( &addr ); + if ( !pSocket ) + break; + + CServiceConn *pConn = new CServiceConn; + pConn->m_ID = m_Connections.AddToTail( pConn ); + pConn->m_LastRecvTime = curTime; + pConn->m_pSocket = pSocket; + + OnNewConnection( pConn->m_ID ); + } + } + } + else + { + if ( !IsConnected() && curTime - m_LastConnectAttemptTime >= SERVICECONNMGR_CONNECT_ATTEMPT_INTERVAL ) + { + AttemptConnect(); + } + } + + // Check for timeouts and send acks. + int iNext; + for ( int iCur=m_Connections.Head(); iCur != m_Connections.InvalidIndex(); iCur=iNext ) + { + iNext = m_Connections.Next( iCur ); + CServiceConn *pConn = m_Connections[iCur]; + + if ( pConn->m_pSocket->IsConnected() ) + { + DWORD startTime = GetTickCount(); + CUtlVector<unsigned char> data; + while ( pConn->m_pSocket->Recv( data ) ) + { + HandlePacket( (char*)data.Base(), data.Count() ); + + // Don't sit in this loop too long. + if ( (GetTickCount() - startTime) > 50 ) + break; + } + } + else + { + OnTerminateConnection( iCur ); + m_Connections.Remove( iCur ); + delete pConn; + } + } +} + + +void CServiceConnMgr::SendPacket( int id, const void *pData, int len ) +{ + if ( id == -1 ) + { + FOR_EACH_LL( m_Connections, i ) + { + m_Connections[i]->m_pSocket->Send( pData, len ); + } + } + else + { + m_Connections[id]->m_pSocket->Send( pData, len ); + } +} + + +void CServiceConnMgr::AttemptConnect() +{ + m_LastConnectAttemptTime = GetTickCount(); + + + ITCPSocket *pSocket = NULL; + for ( int iPort=VMPI_SERVICE_FIRST_UI_PORT; iPort <= VMPI_SERVICE_LAST_UI_PORT; iPort++ ) + { + pSocket = CreateTCPSocketEmu(); + if ( !pSocket || !pSocket->BindToAny( 0 ) ) + return; + + CIPAddr addr( 127, 0, 0, 1, iPort ); + if ( TCPSocket_Connect( pSocket, &addr, 0.1 ) ) + break; + + pSocket->Release(); + pSocket = NULL; + } + + if ( pSocket ) + { + CServiceConn *pConn = new CServiceConn; + pConn->m_ID = m_Connections.AddToTail( pConn ); + pConn->m_LastRecvTime = GetTickCount(); + pConn->m_pSocket = pSocket; + + OnNewConnection( pConn->m_ID ); + } +} + + +void CServiceConnMgr::OnNewConnection( int id ) +{ +} + + +void CServiceConnMgr::OnTerminateConnection( int id ) +{ +} + + +void CServiceConnMgr::HandlePacket( const char *pData, int len ) +{ +} + diff --git a/utils/vmpi/vmpi_service/service_conn_mgr.h b/utils/vmpi/vmpi_service/service_conn_mgr.h new file mode 100644 index 0000000..c5f9558 --- /dev/null +++ b/utils/vmpi/vmpi_service/service_conn_mgr.h @@ -0,0 +1,92 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef SERVICE_CONN_MGR_H +#define SERVICE_CONN_MGR_H +#ifdef _WIN32 +#pragma once +#endif + + +#include "utllinkedlist.h" +#include "utlvector.h" +#include "tcpsocket.h" +#include "ThreadedTCPSocketEmu.h" + + +class CServiceConn +{ +public: + CServiceConn(); + ~CServiceConn(); + + int m_ID; + ITCPSocket *m_pSocket; + DWORD m_LastRecvTime; +}; + + +// ------------------------------------------------------------------------------------------ // +// CServiceConnMgr. This class manages connections to all the UIs (there should only be one UI at +// any given time, but it's conceivable that multiple people can be logged into NT servers +// simultaneously). +// ------------------------------------------------------------------------------------------ // + +class CServiceConnMgr +{ +public: + + CServiceConnMgr(); + ~CServiceConnMgr(); + + bool InitServer(); // Registers as a systemwide server. + bool InitClient(); // Connects to the server. + void Term(); + + // Returns true if there are any connections. If you used InitClient() and there are + // no connections, it will continuously try to connect with a server. + bool IsConnected(); + + // This should be called as often as possible. It checks for dead connections and it + // handles incoming packets from UIs. + void Update(); + + // This sends out a message. If id is -1, then it sends to all connections. + void SendPacket( int id, const void *pData, int len ); + + +// Overridables. +public: + + virtual void OnNewConnection( int id ); + virtual void OnTerminateConnection( int id ); + virtual void HandlePacket( const char *pData, int len ); + + +private: + + void AttemptConnect(); + + +private: + + CUtlLinkedList<CServiceConn*, int> m_Connections; + + bool m_bShuttingDown; + + // This tells if we're running as a client or server. + bool m_bServer; + + // If we're a client, this is the last time we tried to connect. + DWORD m_LastConnectAttemptTime; + + // If we're the server, this is the socket we listen on. + ITCPListenSocket *m_pListenSocket; +}; + + +#endif // SERVICE_CONN_MGR_H diff --git a/utils/vmpi/vmpi_service/service_helpers.cpp b/utils/vmpi/vmpi_service/service_helpers.cpp new file mode 100644 index 0000000..a20c5df --- /dev/null +++ b/utils/vmpi/vmpi_service/service_helpers.cpp @@ -0,0 +1,181 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include "stdafx.h" +#include "service_helpers.h" + + +static CRITICAL_SECTION g_CtrlHandlerMutex; + +static void (*g_pInternalServiceFn)( void *pParam ) = NULL; +static void *g_pInternalServiceParam = NULL; + +static volatile bool g_bShouldExit = false; + + +SERVICE_STATUS MyServiceStatus; +SERVICE_STATUS_HANDLE MyServiceStatusHandle = NULL; + + +void WINAPI MyServiceCtrlHandler( DWORD Opcode ) +{ + DWORD status; + + switch(Opcode) + { + case SERVICE_CONTROL_STOP: + // Do whatever it takes to stop here. + ServiceHelpers_ExitEarly(); + + MyServiceStatus.dwWin32ExitCode = 0; + MyServiceStatus.dwCurrentState = SERVICE_STOPPED; + + if ( !SetServiceStatus( MyServiceStatusHandle, &MyServiceStatus) ) + { + status = GetLastError(); + Msg( "[MY_SERVICE] SetServiceStatus error %ld\n", status ); + } + + Msg( "[MY_SERVICE] Leaving MyService \n" ); + return; + + case SERVICE_CONTROL_INTERROGATE: + // Fall through to send current status. + break; + + default: + Msg("[MY_SERVICE] Unrecognized opcode %ld\n", Opcode ); + } + + // Send current status. + if ( !SetServiceStatus( MyServiceStatusHandle, &MyServiceStatus ) ) + { + status = GetLastError(); + Msg( "[MY_SERVICE] SetServiceStatus error %ld\n", status ); + } +} + + +void WINAPI MyServiceStart( DWORD argc, LPTSTR *argv ) +{ + DWORD status; + + MyServiceStatus.dwServiceType = SERVICE_WIN32; + MyServiceStatus.dwCurrentState = SERVICE_START_PENDING; + MyServiceStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP; + MyServiceStatus.dwWin32ExitCode = 0; + MyServiceStatus.dwServiceSpecificExitCode = 0; + MyServiceStatus.dwCheckPoint = 0; + MyServiceStatus.dwWaitHint = 0; + + MyServiceStatusHandle = RegisterServiceCtrlHandler( "MyService", MyServiceCtrlHandler ); + if ( MyServiceStatusHandle == (SERVICE_STATUS_HANDLE)0 ) + { + Msg("[MY_SERVICE] RegisterServiceCtrlHandler failed %d\n", GetLastError() ); + return; + } + + // Initialization complete - report running status. + MyServiceStatus.dwCurrentState = SERVICE_RUNNING; + + if ( !SetServiceStatus( MyServiceStatusHandle, &MyServiceStatus ) ) + { + status = GetLastError(); + Msg( "[MY_SERVICE] SetServiceStatus error %ld\n", status ); + } + + + // Run the app's main in-thread loop. + g_pInternalServiceFn( g_pInternalServiceParam ); + + + // Tell the SCM that we're stopped. + MyServiceStatus.dwCurrentState = SERVICE_STOPPED; + MyServiceStatus.dwWin32ExitCode = NO_ERROR; + MyServiceStatus.dwServiceSpecificExitCode = 0; + SetServiceStatus( MyServiceStatusHandle, &MyServiceStatus ); + + + // This is where the service does its work. + Msg( "[MY_SERVICE] Returning the Main Thread \n" ); +} + + +void ServiceHelpers_Init() +{ + InitializeCriticalSection( &g_CtrlHandlerMutex ); +} + + +bool ServiceHelpers_StartService( const char *pServiceName, void (*pFn)( void *pParam ), void *pParam ) +{ + // Ok, just run the service. + const SERVICE_TABLE_ENTRY DispatchTable[2] = + { + { (char*)pServiceName, MyServiceStart }, + { NULL, NULL } + }; + + g_pInternalServiceFn = pFn; + g_pInternalServiceParam = pParam; + + if ( StartServiceCtrlDispatcher( DispatchTable ) ) + { + return true; + } + else + { + Msg( "StartServiceCtrlDispatcher error = '%s'\n", GetLastErrorString() ); + return false; + } +} + + +void ServiceHelpers_ExitEarly() +{ + EnterCriticalSection( &g_CtrlHandlerMutex ); + g_bShouldExit = true; + LeaveCriticalSection( &g_CtrlHandlerMutex ); +} + + +bool ServiceHelpers_ShouldExit() +{ + EnterCriticalSection( &g_CtrlHandlerMutex ); + bool bRet = g_bShouldExit; + LeaveCriticalSection( &g_CtrlHandlerMutex ); + + return bRet; +} + + +char* GetLastErrorString() +{ + static char err[2048]; + + LPVOID lpMsgBuf; + FormatMessage( + FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, + GetLastError(), + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language + (LPTSTR) &lpMsgBuf, + 0, + NULL + ); + + strncpy( err, (char*)lpMsgBuf, sizeof( err ) ); + LocalFree( lpMsgBuf ); + + err[ sizeof( err ) - 1 ] = 0; + + return err; +} + + diff --git a/utils/vmpi/vmpi_service/service_helpers.h b/utils/vmpi/vmpi_service/service_helpers.h new file mode 100644 index 0000000..dc70f28 --- /dev/null +++ b/utils/vmpi/vmpi_service/service_helpers.h @@ -0,0 +1,43 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef SERVICE_HELPERS_H +#define SERVICE_HELPERS_H +#ifdef _WIN32 +#pragma once +#endif + + +// Call this if you want to use the ExitEarly() and ShouldExit() helpers. +void ServiceHelpers_Init(); + +// Start this app in the service control manager. +// +// The service will run in a thread. If the service starts successfully, then +// it will call pFn and pass in pParam. Inside there, you should loop until +// ShouldServiceExit() returns true. +bool ServiceHelpers_StartService( const char *pServiceName, void (*pFn)( void *pParam ), void *pParam ); + + +// Call this to exit the service early. This will make ShouldServiceExit() return true, +// and your main thread function should pick it up and exit. +// +// NOTE: this can be used even if the service isn't running as long as you call ServiceHelpers_Init(). +void ServiceHelpers_ExitEarly(); + +// Your thread loop should call this each time around. If this function returns true, +// then your thread function should return, causing the service to exit. +// +// NOTE: this can be used even if the service isn't running as long as you call ServiceHelpers_Init(). +bool ServiceHelpers_ShouldExit(); + + +// This function wants a better home. +char* GetLastErrorString(); + + +#endif // SERVICE_HELPERS_H diff --git a/utils/vmpi/vmpi_service/vmpi_service.cpp b/utils/vmpi/vmpi_service/vmpi_service.cpp new file mode 100644 index 0000000..03f7eea --- /dev/null +++ b/utils/vmpi/vmpi_service/vmpi_service.cpp @@ -0,0 +1,1710 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +// vmpi_service.cpp : Defines the entry point for the console application. +// + +#include "stdafx.h" +#include "vmpi.h" +#include "iphelpers.h" +#include "bitbuf.h" +#include "tier1/strtools.h" +#include "interface.h" +#include "ilaunchabledll.h" +#include "resource.h" +#include "consolewnd.h" +#include <io.h> +#include "utllinkedlist.h" +#include "service_helpers.h" +#include "vmpi_filesystem.h" +#include "service_conn_mgr.h" +#include "resource.h" +#include "perf_counters.h" +#include "tier0/icommandline.h" + + +// If we couldn't get into a job (maybe they weren't accepting more workers at the time), +// then we wait this long and retry the connection. +#define JOB_MEMORY_DURATION 60 + + +char g_VersionString[64]; // From the IDS_VERSION_STRING string. +HKEY g_hVMPIServiceKey = NULL; // HKML/Software/Valve/VMPI + +double g_flLastKillProcessTime = 0; + +char *g_pPassword = NULL; // Set if this service is using a pw. +ISocket *g_pSocket = NULL; +int g_SocketPort = -1; // Which port we were able to bind the port on. + +char g_RunningProcess_ExeName[MAX_PATH] = {0}; +char g_RunningProcess_MapName[MAX_PATH] = {0}; +HANDLE g_hRunningProcess = NULL; +HANDLE g_hRunningThread = NULL; +DWORD g_dwRunningProcessId = 0; +IPerfTracker *g_pPerfTracker = NULL; // Tracks CPU usage. + +// When this is true, it will launch new processes invisibly. +bool g_bHideNewProcessWindows = true; + +HINSTANCE g_hInstance = NULL; +int g_iBoundPort = -1; + +bool g_bScreensaverMode = false; // If this is true, then it'll act like the service is disabled while + // a screensaver isn't running. + +// If this is on, it runs the exes out of c:/hl2/bin instead of the network. If the exes are built in debug, +// this makes it possible to catch nasty crashes. +bool g_bSuperDebugMode = false; + + +bool g_bScreensaverRunning = false; // Updated each frame to tell if the screensaver is running. + + +// GetTickCount() at the time the app was started. +DWORD g_AppStartTime = 0; + +// GetTickCount() at the time the service ran a worker app. +DWORD g_CreateProcessTime = 0; + + +CIPAddr g_CurRespondAddr; +int g_CurJobID[4]; +int g_CurJobPriority = -1; // VMPI priority of the currently-running job. + +// The directory we're running in. +char g_BaseAppPath[MAX_PATH]; +char g_FileCachePath[MAX_PATH]; // [base app path]\vmpi_service_cache. + + +// Different modes this app can run in. +#define RUNMODE_INSTALL 0 +#define RUNMODE_CONSOLE 1 +#define RUNMODE_SERVICE 2 + +int g_RunMode = RUNMODE_CONSOLE; + +bool g_bMinimized = false; // true if they run with -minimized. +int g_iCurState = VMPI_SERVICE_STATE_IDLE; +char g_CurMasterName[512] = {0}; + + + +////// +// This block of variables is setup while we wait for the downloader to finish. +// When the downloading is complete, we launch the app using these variables. +////// + +#ifdef _DEBUG + #define MAX_DOWNLOADER_TIME_ALLOWED 300000 // If the downloader takes longer than this, kill it. +#else + #define MAX_DOWNLOADER_TIME_ALLOWED 30 // If the downloader takes longer than this, kill it. +#endif + +// If this is non-NULL, then there is NOT a VMPI worker app running currently.. the downloader +HANDLE g_Waiting_hProcess = NULL; +float g_Waiting_StartTime = 0; +CUtlVector<char*> g_Waiting_Argv; +int g_Waiting_Priority = 0; +bool g_Waiting_bShowAppWindow = false; +bool g_Waiting_bPatching = 0; // If this is nonzero, then we're downloading so we can apply a patch. + + +// Used to track the services browsers that have been talking to us lately. +#define SERVICES_BROWSER_TIMEOUT 10 // We remove a services browser from the list if we don't hear from it for this long. +class CServicesBrowserInfo +{ +public: + float m_flLastPingTime; // Last time they talked to us. + CIPAddr m_Addr; // Their IP address. +}; +CUtlVector<CServicesBrowserInfo> g_ServicesBrowsers; + + +void HandlePacket_KILL_PROCESS( const CIPAddr *ipFrom ); +void KillRunningProcess( const char *pReason, bool bGoToIdle ); +void LoadStateFromRegistry(); +void SaveStateToRegistry(); + + +void SetPassword( const char *pPassword ) +{ + delete [] g_pPassword; + if ( pPassword ) + { + int len = V_strlen( pPassword ) + 1; + g_pPassword = new char[len]; + V_strncpy( g_pPassword, pPassword, len ); + } + else + { + g_pPassword = NULL; + } +} + + +// ------------------------------------------------------------------------------------------ // +// This handles connection to clients. +// ------------------------------------------------------------------------------------------ // + +class CVMPIServiceConnMgr : public CServiceConnMgr +{ +public: + + virtual void OnNewConnection( int id ); + virtual void HandlePacket( const char *pData, int len ); + void SendCurStateTo( int id ); + + +public: + + void AddConsoleOutput( const char *pMsg ); + + void SetAppState( int iState ); +}; + + +void CVMPIServiceConnMgr::AddConsoleOutput( const char *pMsg ) +{ + // Tell clients of the new text string. + CUtlVector<char> data; + data.AddToTail( VMPI_SERVICE_UI_PROTOCOL_VERSION ); + data.AddToTail( VMPI_SERVICE_TO_UI_CONSOLE_TEXT ); + data.AddMultipleToTail( strlen( pMsg ) + 1, pMsg ); + SendPacket( -1, data.Base(), data.Count() ); +} + + +void CVMPIServiceConnMgr::SetAppState( int iState ) +{ + // Update our state and send it to the clients. + g_iCurState = iState; + SendCurStateTo( -1 ); +} + + +void CVMPIServiceConnMgr::OnNewConnection( int id ) +{ + // Send our current state to the new connection. + Msg( "(debug) Made a new connection!\n" ); + SendCurStateTo( id ); +} + + +void CVMPIServiceConnMgr::SendCurStateTo( int id ) +{ + CUtlVector<char> data; + data.AddToTail( VMPI_SERVICE_UI_PROTOCOL_VERSION ); + data.AddToTail( VMPI_SERVICE_TO_UI_STATE ); + data.AddMultipleToTail( sizeof( g_iCurState ), (char*)&g_iCurState ); + data.AddToTail( (char)g_bScreensaverMode ); + + if ( g_pPassword ) + data.AddMultipleToTail( strlen( g_pPassword ) + 1, g_pPassword ); + else + data.AddToTail( 0 ); + + SendPacket( -1, data.Base(), data.Count() ); +} + + +void CVMPIServiceConnMgr::HandlePacket( const char *pData, int len ) +{ + switch( pData[0] ) + { + case VMPI_KILL_PROCESS: + { + HandlePacket_KILL_PROCESS( NULL ); + } + break; + + case VMPI_SERVICE_DISABLE: + { + KillRunningProcess( "Got a VMPI_SERVICE_DISABLE packet", true ); + SetAppState( VMPI_SERVICE_STATE_DISABLED ); + SaveStateToRegistry(); + } + break; + + case VMPI_SERVICE_ENABLE: + { + if ( g_iCurState == VMPI_SERVICE_STATE_DISABLED ) + { + SetAppState( VMPI_SERVICE_STATE_IDLE ); + } + SaveStateToRegistry(); + } + break; + + case VMPI_SERVICE_UPDATE_PASSWORD: + { + const char *pStr = pData + 1; + SetPassword( pStr ); + + // Send out the new state. + SendCurStateTo( -1 ); + } + break; + + case VMPI_SERVICE_SCREENSAVER_MODE: + { + g_bScreensaverMode = (pData[1] != 0); + SendCurStateTo( -1 ); + SaveStateToRegistry(); + } + break; + + case VMPI_SERVICE_EXIT: + { + Msg( "Got a VMPI_SERVICE_EXIT packet.\n "); + ServiceHelpers_ExitEarly(); + } + break; + } +} + +// This is allocated by the service thread and only used in there. +CVMPIServiceConnMgr *g_pConnMgr = NULL; + + +// ------------------------------------------------------------------------------------------ // +// Persistent state stuff. +// ------------------------------------------------------------------------------------------ // + +void LoadStateFromRegistry() +{ + if ( g_hVMPIServiceKey ) + { + DWORD val = 0; + DWORD type = REG_DWORD; + DWORD size = sizeof( val ); + + if ( RegQueryValueEx( + g_hVMPIServiceKey, + "ScreensaverMode", + 0, + &type, + (unsigned char*)&val, + &size ) == ERROR_SUCCESS && + type == REG_DWORD && + size == sizeof( val ) ) + { + g_bScreensaverMode = (val != 0); + } + + if ( RegQueryValueEx( + g_hVMPIServiceKey, + "Disabled", + 0, + &type, + (unsigned char*)&val, + &size ) == ERROR_SUCCESS && + type == REG_DWORD && + size == sizeof( val ) && + val != 0 ) + { + if ( g_pConnMgr ) + g_pConnMgr->SetAppState( VMPI_SERVICE_STATE_DISABLED ); + } + } +} + +void SaveStateToRegistry() +{ + if ( g_hVMPIServiceKey ) + { + DWORD val; + + val = g_bScreensaverMode; + RegSetValueEx( + g_hVMPIServiceKey, + "ScreensaverMode", + 0, + REG_DWORD, + (unsigned char*)&val, + sizeof( val ) ); + + val = (g_iCurState == VMPI_SERVICE_STATE_DISABLED); + RegSetValueEx( + g_hVMPIServiceKey, + "Disabled", + 0, + REG_DWORD, + (unsigned char*)&val, + sizeof( val ) ); + } +} + + +// ------------------------------------------------------------------------------------------ // +// Helper functions. +// ------------------------------------------------------------------------------------------ // + +char* FindArg( int argc, char **argv, const char *pArgName, char *pDefaultValue="" ) +{ + for ( int i=0; i < argc; i++ ) + { + if ( stricmp( argv[i], pArgName ) == 0 ) + { + if ( (i+1) >= argc ) + return pDefaultValue; + else + return argv[i+1]; + } + } + return NULL; +} + + +SpewRetval_t MySpewOutputFunc( SpewType_t spewType, const char *pMsg ) +{ + // Put the message in status.txt. +#ifdef VMPI_SERVICE_LOGS + static FILE *fp = fopen( "c:\\vmpi_service.log", "wt" ); + if ( fp ) + { + fprintf( fp, "%s", pMsg ); + fflush( fp ); + } +#endif + + + // Print it to the console. + if ( g_pConnMgr ) + g_pConnMgr->AddConsoleOutput( pMsg ); + + // Output to the debug console. + OutputDebugString( pMsg ); + + if ( spewType == SPEW_ASSERT ) + return SPEW_DEBUGGER; + else if( spewType == SPEW_ERROR ) + return SPEW_ABORT; + else + return SPEW_CONTINUE; +} + + +char* CopyString( const char *pStr ) +{ + int len = V_strlen( pStr ) + 1; + char *pRet = new char[len]; + V_strncpy( pRet, pStr, len ); + return pRet; +} + +void AppendArg( CUtlVector<char*> &newArgv, const char *pIn ) +{ + newArgv.AddToTail( CopyString( pIn ) ); +} + + +void SendStartStatus( bool bStatus ) +{ + for ( int i=0; i < 3; i++ ) + { + char data[4096]; + bf_write dataBuf( data, sizeof( data ) ); + dataBuf.WriteByte( VMPI_PROTOCOL_VERSION ); + dataBuf.WriteByte( VMPI_NOTIFY_START_STATUS ); + dataBuf.WriteBytes( g_CurJobID, sizeof( g_CurJobID ) ); + dataBuf.WriteByte( bStatus ); + g_pSocket->SendTo( &g_CurRespondAddr, data, dataBuf.GetNumBytesWritten() ); + + Sleep( 50 ); + } +} + + +void SendEndStatus() +{ + for ( int i=0; i < 3; i++ ) + { + char data[4096]; + bf_write dataBuf( data, sizeof( data ) ); + dataBuf.WriteByte( VMPI_PROTOCOL_VERSION ); + dataBuf.WriteByte( VMPI_NOTIFY_END_STATUS ); + dataBuf.WriteBytes( g_CurJobID, sizeof( g_CurJobID ) ); + g_pSocket->SendTo( &g_CurRespondAddr, data, dataBuf.GetNumBytesWritten() ); + + Sleep( 50 ); + } +} + + +void KillRunningProcess( const char *pReason, bool bGoToIdle ) +{ + // Kill the downloader if it's running. + if ( g_Waiting_hProcess ) + { + TerminateProcess( g_Waiting_hProcess, 1 ); + CloseHandle( g_Waiting_hProcess ); + g_Waiting_hProcess = NULL; + } + + if ( !g_hRunningProcess ) + return; + + if ( pReason ) + Msg( pReason ); + + SendEndStatus(); + TerminateProcess( g_hRunningProcess, 1 ); + g_RunningProcess_ExeName[0] = 0; + g_RunningProcess_MapName[0] = 0; + + // Yep. Now we can start a new one. + CloseHandle( g_hRunningThread ); + g_hRunningThread = NULL; + + CloseHandle( g_hRunningProcess ); + g_hRunningProcess = NULL; + + g_CurJobPriority = -1; + + if ( bGoToIdle ) + if ( g_pConnMgr ) + g_pConnMgr->SetAppState( VMPI_SERVICE_STATE_IDLE ); +} + + +// ------------------------------------------------------------------------------------------ // +// Job memory stuff. +// ------------------------------------------------------------------------------------------ // + +// CJobMemory is used to track which jobs we ran (or tried to run). +// We remember which jobs we did because Winsock likes to queue up the job packets on +// our socket, so if we don't remember which jobs we ran, we'd run the job a bunch of times. +class CJobMemory +{ +public: + int m_ID[4]; // Random ID that comes from the server. + float m_Time; +}; + +CUtlLinkedList<CJobMemory, int> g_JobMemories; + + +bool FindJobMemory( int id[4] ) +{ + int iNext; + for ( int i=g_JobMemories.Head(); i != g_JobMemories.InvalidIndex(); i=iNext ) + { + iNext = g_JobMemories.Next( i ); + + CJobMemory *pJob = &g_JobMemories[i]; + if ( memcmp( pJob->m_ID, id, sizeof( pJob->m_ID ) ) == 0 ) + return true; + } + return false; +} + + +void TimeoutJobIDs() +{ + double flCurTime = Plat_FloatTime(); + + int iNext; + for ( int i=g_JobMemories.Head(); i != g_JobMemories.InvalidIndex(); i=iNext ) + { + iNext = g_JobMemories.Next( i ); + + if ( (flCurTime - g_JobMemories[i].m_Time) > JOB_MEMORY_DURATION ) + g_JobMemories.Remove( i ); + } +} + + +void AddJobMemory( int id[4] ) +{ + TimeoutJobIDs(); + + CJobMemory job; + memcpy( job.m_ID, id, sizeof( job.m_ID ) ); + job.m_Time = Plat_FloatTime(); + g_JobMemories.AddToTail( job ); +} + + +bool CheckJobID( bf_read &buf, int jobID[4] ) +{ + TimeoutJobIDs(); + + jobID[0] = buf.ReadLong(); + jobID[1] = buf.ReadLong(); + jobID[2] = buf.ReadLong(); + jobID[3] = buf.ReadLong(); + if ( FindJobMemory( jobID ) || buf.IsOverflowed() ) + { + return false; + } + + return true; +} + + + + +// ------------------------------------------------------------------------------------------ // +// The main VMPI code. +// ------------------------------------------------------------------------------------------ // + +void VMPI_Waiter_Term() +{ + KillRunningProcess( NULL, false ); + if ( g_pConnMgr ) + { + g_pConnMgr->Term(); + delete g_pConnMgr; + g_pConnMgr = NULL; + } + + if ( g_pSocket ) + { + g_pSocket->Release(); + g_pSocket = NULL; + } + + g_pPerfTracker->Release(); + g_pPerfTracker = NULL; +} + + +bool VMPI_Waiter_Init() +{ + // Run as idle priority. + HKEY hKey = NULL; + RegCreateKey( HKEY_LOCAL_MACHINE, VMPI_SERVICE_KEY, &hKey ); + DWORD dwVal = 0; + DWORD dummyType = REG_DWORD; + DWORD dwValLen = sizeof( dwVal ); + if ( RegQueryValueEx( hKey, "LowPriority", NULL, &dummyType, (LPBYTE)&dwVal, &dwValLen ) == ERROR_SUCCESS ) + { + if ( dwVal ) + { + SetPriorityClass( GetCurrentProcess(), IDLE_PRIORITY_CLASS ); + } + } + else + { + RegSetValueEx( hKey, "LowPriority", 0, REG_DWORD, (unsigned char*)&dwVal, sizeof( dwVal ) ); + } + + g_pConnMgr = new CVMPIServiceConnMgr; + + if ( !g_pConnMgr->InitServer() ) + Msg( "ERROR INITIALIZING CONNMGR\n" ); + + g_pSocket = CreateIPSocket(); + if ( !g_pSocket ) + { + Msg( "Error creating a socket.\n" ); + return false; + } + + // Bind to the first port we find in the range [VMPI_SERVICE_PORT, VMPI_LAST_SERVICE_PORT]. + int iTest; + for ( iTest=VMPI_SERVICE_PORT; iTest <= VMPI_LAST_SERVICE_PORT; iTest++ ) + { + g_SocketPort = iTest; + if ( g_pSocket->BindToAny( iTest ) ) + break; + } + if ( iTest == VMPI_LAST_SERVICE_PORT ) + { + Msg( "Error binding a socket to port %d.\n", VMPI_SERVICE_PORT ); + VMPI_Waiter_Term(); + return false; + } + + g_iBoundPort = iTest; + g_pPerfTracker = CreatePerfTracker(); + return true; +} + + +void RunInDLL( const char *pFilename, CUtlVector<char*> &newArgv ) +{ + if ( g_pConnMgr ) + g_pConnMgr->SetAppState( VMPI_SERVICE_STATE_BUSY ); + + bool bSuccess = false; + CSysModule *pModule = Sys_LoadModule( pFilename ); + if ( pModule ) + { + CreateInterfaceFn fn = Sys_GetFactory( pModule ); + if ( fn ) + { + ILaunchableDLL *pDLL = (ILaunchableDLL*)fn( LAUNCHABLE_DLL_INTERFACE_VERSION, NULL ); + if( pDLL ) + { + // Do this here because the executables we would have launched usually would do it. + CommandLine()->CreateCmdLine( newArgv.Count(), newArgv.Base() ); + pDLL->main( newArgv.Count(), newArgv.Base() ); + bSuccess = true; + SpewOutputFunc( MySpewOutputFunc ); + } + } + + Sys_UnloadModule( pModule ); + } + + if ( !bSuccess ) + { + Msg( "Error running VRAD (or VVIS) out of DLL '%s'\n", pFilename ); + } + + if ( g_pConnMgr ) + g_pConnMgr->SetAppState( VMPI_SERVICE_STATE_IDLE ); +} + + +void GetArgsFromBuffer( + bf_read &buf, + CUtlVector<char*> &newArgv, + bool *bShowAppWindow ) +{ + int nArgs = buf.ReadWord(); + + bool bSpewArgs = false; + + for ( int iArg=0; iArg < nArgs; iArg++ ) + { + char argStr[512]; + buf.ReadString( argStr, sizeof( argStr ) ); + + AppendArg( newArgv, argStr ); + if ( stricmp( argStr, "-mpi_verbose" ) == 0 ) + bSpewArgs = true; + + if ( stricmp( argStr, "-mpi_ShowAppWindow" ) == 0 ) + *bShowAppWindow = true; + } + + if ( bSpewArgs ) + { + Msg( "nArgs: %d\n", newArgv.Count() ); + for ( int i=0; i < newArgv.Count(); i++ ) + Msg( "Arg %d: %s\n", i, newArgv[i] ); + } +} + + +bool GetDLLFilename( CUtlVector<char*> &newArgv, char pDLLFilename[MAX_PATH] ) +{ + char *argStr = newArgv[0]; + int argLen = strlen( argStr ); + if ( argLen <= 4 ) + return false; + + if ( Q_stricmp( &argStr[argLen-4], ".exe" ) != 0 ) + return false; + + char baseFilename[MAX_PATH]; + Q_strncpy( baseFilename, argStr, MAX_PATH ); + baseFilename[ min( MAX_PATH-1, argLen-4 ) ] = 0; + + // First try _dll.dll (src_main), then try .dll (rel). + V_snprintf( pDLLFilename, MAX_PATH, "%s_dll.dll", baseFilename ); + if ( _access( pDLLFilename, 0 ) != 0 ) + { + V_snprintf( pDLLFilename, MAX_PATH, "%s.dll", baseFilename ); + } + + return true; +} + +void BuildCommandLineFromArgs( CUtlVector<char*> &newArgv, char *pOut, int outLen ) +{ + pOut[0] = 0; + + for ( int i=0; i < newArgv.Count(); i++ ) + { + char argStr[512]; + if ( strlen( newArgv[i] ) > 0 && newArgv[i][strlen(newArgv[i])-1] == '\\' ) + Q_snprintf( argStr, sizeof( argStr ), "\"%s\\\" ", newArgv[i] ); + else + Q_snprintf( argStr, sizeof( argStr ), "\"%s\" ", newArgv[i] ); + + Q_strncat( pOut, argStr, outLen, COPY_ALL_CHARACTERS ); + } +} + +bool RunProcessFromArgs( CUtlVector<char*> &newArgv, bool bShowAppWindow, bool bCreateSuspended, const char *pWorkingDir, PROCESS_INFORMATION *pOut ) +{ + char commandLine[2048]; + BuildCommandLineFromArgs( newArgv, commandLine, sizeof( commandLine ) ); + + Msg( "Running '%s'\n", commandLine ); + + STARTUPINFO si; + memset( &si, 0, sizeof( si ) ); + si.cb = sizeof( si ); + + memset( pOut, 0, sizeof( *pOut ) ); + + DWORD dwFlags = 0;//IDLE_PRIORITY_CLASS; + if ( bShowAppWindow ) + dwFlags |= CREATE_NEW_CONSOLE; + else + dwFlags |= CREATE_NO_WINDOW; + + if ( bCreateSuspended ) + dwFlags |= CREATE_SUSPENDED; + + UINT oldMode = SetErrorMode( SEM_NOOPENFILEERRORBOX | SEM_FAILCRITICALERRORS ); + + BOOL bRet = CreateProcess( + NULL, + commandLine, + NULL, // security + NULL, + TRUE, + dwFlags | IDLE_PRIORITY_CLASS, // flags + NULL, // environment + pWorkingDir, + &si, + pOut ); + + SetErrorMode( oldMode ); + return (bRet != FALSE); +} + + +void RunProcessAtCommandLine( + CUtlVector<char*> &newArgv, + bool bShowAppWindow, + bool bCreateSuspended, + int iPriority ) +{ + // current directory (use c:\\ because we don't want it to accidentally share + // DLLs like vstdlib with us). PROCESS_INFORMATION pi; + PROCESS_INFORMATION pi; + if ( RunProcessFromArgs( newArgv, bShowAppWindow, bCreateSuspended, g_FileCachePath, &pi ) ) + { + if ( g_pConnMgr ) + g_pConnMgr->SetAppState( VMPI_SERVICE_STATE_BUSY ); + + if ( newArgv.Count() > 0 && newArgv[0] ) + { + V_FileBase( newArgv[0], g_RunningProcess_ExeName, sizeof( g_RunningProcess_ExeName ) ); + + if ( V_stricmp( g_RunningProcess_ExeName, "vrad" ) == 0 || V_stricmp( g_RunningProcess_ExeName, "vvis" ) == 0 ) + V_FileBase( newArgv[newArgv.Count()-1], g_RunningProcess_MapName, sizeof( g_RunningProcess_MapName ) ); + } + + g_hRunningProcess = pi.hProcess; + g_hRunningThread = pi.hThread; + g_dwRunningProcessId = pi.dwProcessId; + g_pPerfTracker->Init( g_dwRunningProcessId ); + g_CurJobPriority = iPriority; + g_CreateProcessTime = GetTickCount(); + + SendStartStatus( true ); + } + else + { + Msg( " - ERROR in CreateProcess (%s)!\n", GetLastErrorString() ); + SendStartStatus( false ); + g_CurJobPriority = -1; + g_RunningProcess_ExeName[0] = 0; + g_RunningProcess_MapName[0] = 0; + } +} + + +bool WaitForProcessToExit() +{ + if ( g_hRunningProcess ) + { + // Did the process complete yet? + if ( WaitForSingleObject( g_hRunningProcess, 0 ) == WAIT_TIMEOUT ) + { + // Nope.. keep waiting. + return true; + } + else + { + Msg( "Finished!\n "); + + SendEndStatus(); + + // Change back to the 'waiting' icon. + if ( g_pConnMgr ) + g_pConnMgr->SetAppState( VMPI_SERVICE_STATE_IDLE ); + g_CurJobPriority = -1; + + // Yep. Now we can start a new one. + CloseHandle( g_hRunningThread ); + CloseHandle( g_hRunningProcess ); + g_hRunningProcess = g_hRunningThread = NULL; + g_RunningProcess_ExeName[0] = g_RunningProcess_MapName[0] = 0; + } + } + + return false; +} + + +void HandleWindowMessages() +{ + MSG msg; + while ( PeekMessage( &msg, NULL, 0, 0, PM_REMOVE ) ) + { + TranslateMessage( &msg ); + DispatchMessage( &msg ); + } +} + +void GetRunningProcessStats( int &processorPercentage, int &memoryUsageMegabytes ) +{ + static int lastProcessorPercentage = 0; + static int lastMemory = 0; + if ( g_hRunningProcess ) + { + // Only update this every couple seconds. It's not too expensive (about 800 microseconds), but we don't + // need to do it a whole lot. + static DWORD lastReturnTime = GetTickCount(); + DWORD curTime = GetTickCount(); + if ( (curTime - lastReturnTime) >= 1000 ) + { + lastReturnTime = curTime; + g_pPerfTracker->GetPerfData( lastProcessorPercentage, lastMemory ); + } + } + else + { + lastProcessorPercentage = lastMemory = 0; + } + + processorPercentage = lastProcessorPercentage; + memoryUsageMegabytes = lastMemory; +} + + +void BuildPingHeader( CUtlVector<char> &data, char packetID, int iState ) +{ + // Figure out the computer's name. + char computerName[128]; + DWORD computerNameLen = sizeof( computerName ); + GetComputerName( computerName, &computerNameLen ); + + // Ping back at them. + data.AddToTail( VMPI_PROTOCOL_VERSION ); + data.AddToTail( packetID ); + data.AddToTail( (char)iState ); + + DWORD liveTime = GetTickCount() - g_AppStartTime; + data.AddMultipleToTail( sizeof( liveTime ), (char*)&liveTime ); + + data.AddMultipleToTail( sizeof( g_SocketPort ), (char*)&g_SocketPort ); + data.AddMultipleToTail( strlen( computerName ) + 1, computerName ); + + if ( g_hRunningProcess ) + data.AddMultipleToTail( strlen( g_CurMasterName ) + 1, g_CurMasterName ); + else + data.AddMultipleToTail( 1, "" ); + + // Write in how long the worker app has been running. + DWORD appRunTime = 0; + if ( g_hRunningProcess ) + appRunTime = GetTickCount() - g_CreateProcessTime; + + data.AddMultipleToTail( sizeof( appRunTime ), (char*)&appRunTime ); + + // Finally, write the password. + if ( g_pPassword ) + data.AddMultipleToTail( strlen( g_pPassword ) + 1, g_pPassword ); + else + data.AddToTail( 0 ); + + data.AddMultipleToTail( V_strlen( g_VersionString ) + 1, g_VersionString ); + + int processorPercentage, memoryUsageMegabytes; + GetRunningProcessStats( processorPercentage, memoryUsageMegabytes ); + + // Write processor percentage. + data.AddToTail( (char)processorPercentage ); + + // Write the EXE name. + data.AddMultipleToTail( V_strlen( g_RunningProcess_ExeName ) + 1, g_RunningProcess_ExeName ); + + // Write memory usage. + short memUsageShort = (short)memoryUsageMegabytes; + data.AddMultipleToTail( sizeof( memUsageShort ), (const char*)&memUsageShort ); + + // Write the map name. + data.AddMultipleToTail( V_strlen( g_RunningProcess_MapName ) + 1, g_RunningProcess_MapName ); +} + + +// This tracks a list +void AddServicesBrowserIP( const CIPAddr &ipFrom ) +{ + for ( int i=0; i < g_ServicesBrowsers.Count(); i++ ) + { + if ( g_ServicesBrowsers[i].m_Addr == ipFrom ) + { + g_ServicesBrowsers[i].m_flLastPingTime = Plat_FloatTime(); + return; + } + } + CServicesBrowserInfo info; + info.m_Addr = ipFrom; + info.m_flLastPingTime = Plat_FloatTime(); + g_ServicesBrowsers.AddToTail( info ); +} + + +void UpdateServicesBrowserIPs() +{ + double curTime = Plat_FloatTime(); + for ( int i=0; i < g_ServicesBrowsers.Count(); i++ ) + { + if ( (curTime - g_ServicesBrowsers[i].m_flLastPingTime) >= SERVICES_BROWSER_TIMEOUT ) + { + g_ServicesBrowsers.Remove( i ); + --i; + break; + } + } +} + + +void SendStateToServicesBrowsers() +{ + int curState; + if ( g_hRunningProcess ) + { + if ( g_Waiting_bPatching ) + curState = VMPI_STATE_PATCHING; + else + curState = VMPI_STATE_BUSY; + } + else if ( g_Waiting_hProcess ) + { + if ( g_Waiting_bPatching ) + curState = VMPI_STATE_PATCHING; + else + curState = VMPI_STATE_DOWNLOADING; + } + else if ( g_iCurState == VMPI_SERVICE_STATE_DISABLED ) + { + curState = VMPI_STATE_DISABLED; + } + else if ( g_bScreensaverMode && !g_bScreensaverRunning ) + { + curState = VMPI_STATE_SCREENSAVER_DISABLED; + } + else + { + curState = VMPI_STATE_IDLE; + } + + CUtlVector<char> data; + BuildPingHeader( data, VMPI_PING_RESPONSE, curState ); + + for ( int i=0; i < g_ServicesBrowsers.Count(); i++ ) + { + g_pSocket->SendTo( &g_ServicesBrowsers[i].m_Addr, data.Base(), data.Count() ); + } +} + + +void StopUI() +{ + char cPacket[2] = {VMPI_SERVICE_UI_PROTOCOL_VERSION, VMPI_SERVICE_TO_UI_EXIT}; + if ( g_pConnMgr ) + g_pConnMgr->SendPacket( -1, &cPacket, sizeof( cPacket ) ); + + // Wait for a bit for the connection to go away. + DWORD startTime = GetTickCount(); + while ( GetTickCount()-startTime < 2000 ) + { + if ( g_pConnMgr ) + { + g_pConnMgr->Update(); + if ( !g_pConnMgr->IsConnected() ) + break; + else + Sleep( 10 ); + } + } +} + + +void CheckScreensaverRunning() +{ + // We want to let patching finish even if we're in screensaver mode. + if ( g_Waiting_hProcess && g_Waiting_bPatching ) + return; + + BOOL bRunning = false; + SystemParametersInfo( SPI_GETSCREENSAVERRUNNING, 0, &bRunning, 0 ); + + g_bScreensaverRunning = (bRunning != 0); + if ( !g_bScreensaverRunning && g_bScreensaverMode ) + { + KillRunningProcess( "Screensaver not running", true ); + } +} + + +void AdjustSuperDebugArgs( CUtlVector<char*> &args ) +{ + // Get the directory this exe was run out of. + char filename[512]; + if ( GetModuleFileName( GetModuleHandle( NULL ), filename, sizeof( filename ) ) == 0 ) + return; + + char *pLastSlash = filename; + char *pCurPos = filename; + while ( *pCurPos ) + { + if ( *pCurPos == '/' || *pCurPos == '\\' ) + pLastSlash = pCurPos; + ++pCurPos; + } + *pLastSlash = 0; + + // In superdebug mode, run it out of c:/hl2/bin. + const char *pBase = args[0]; + const char *pBaseCur = pBase; + while ( *pBaseCur ) + { + if ( *pBaseCur == '/' || *pBaseCur == '\\' || *pBaseCur == ':' ) + { + pBase = pBaseCur+1; + pBaseCur = pBase; + } + ++pBaseCur; + } + + int maxLen = 64 + strlen( pBase ) + 1; + char *pNewFilename = new char[maxLen]; + _snprintf( pNewFilename, maxLen, "%s\\%s", filename, pBase ); + delete args[0]; + args[0] = pNewFilename; + + + // Now insert -allowdebug. + const char *pAllowDebug = "-allowdebug"; + char *pToInsert = new char[ strlen( pAllowDebug ) + 1 ]; + strcpy( pToInsert, pAllowDebug ); + args.InsertAfter( 0, pToInsert ); + +} + + +// -------------------------------------------------------------------------------- // +// Purpose: Launches vmpi_transfer.exe to download the required +// files from the master so we can launch. +// +// If successful, it sets hProcess to the process handle of the downloader. +// When that process terminates, we look for [cache dir]\ReadyToGo.txt and if it's +// there, then we start the job. +// -------------------------------------------------------------------------------- // +bool StartDownloadingAppFiles( + CUtlVector<char*> &newArgv, + char *cacheDir, + int cacheDirLen, + bool bShowAppWindow, + HANDLE *hProcess, + bool bPatching ) +{ + *hProcess = NULL; + + V_strncpy( cacheDir, g_FileCachePath, cacheDirLen ); + + // For now, cache dir is always the same. It's [current directory]\cache. + if ( _access( cacheDir, 0 ) != 0 ) + { + if ( !CreateDirectory( cacheDir, NULL ) && GetLastError() != ERROR_ALREADY_EXISTS ) + { + Warning( "Unable to create cache directory: %s.\n", cacheDir ); + return false; + } + } + + // Clear all the files in the directory. + char searchStr[MAX_PATH]; + V_ComposeFileName( cacheDir, "*.*", searchStr, sizeof( searchStr ) ); + _finddata_t findData; + intptr_t ret = _findfirst( searchStr, &findData ); + if ( ret != -1 ) + { + do + { + if ( findData.name[0] == '.' ) + continue; + + char fullFilename[MAX_PATH]; + V_ComposeFileName( cacheDir, findData.name, fullFilename, sizeof( fullFilename ) ); + if ( _unlink( fullFilename ) != 0 ) + { + Warning( "_unlink( %s ) failed.\n", fullFilename ); + return false; + } + } while ( _findnext( ret, &findData ) == 0 ); + + _findclose( ret ); + } + + // Change the EXE name to an absolute path to exactly where it is in the cache directory. + int maxExeNameLen = 1024; + char *pExeName = new char[maxExeNameLen]; + if ( bPatching ) + { + V_ComposeFileName( cacheDir, "vmpi_service_install.exe", pExeName, maxExeNameLen ); + + // Add args for the installer. + newArgv.InsertAfter( 0, CopyString( "-DontTouchUI" ) ); + + // When patching, we can't start the UI and the installer can't because we're running in the local system account + // and the UI is running on the account of whoever logged in. So what we do is send a message to the UI telling it + // to run <cacheDir>\WaitAndRestart and restart itself in N seconds. + newArgv.InsertAfter( 0, CopyString( "-Install_Quiet" ) ); + } + else + { + V_ComposeFileName( cacheDir, newArgv[0], pExeName, maxExeNameLen ); + } + + delete newArgv[0]; + newArgv[0] = pExeName; + + char fullExeFilename[MAX_PATH]; + V_ComposeFileName( g_BaseAppPath, "vmpi_transfer.exe", fullExeFilename, sizeof( fullExeFilename ) ); + + CUtlVector<char*> downloaderArgs; + downloaderArgs.AddToTail( fullExeFilename ); +#if defined( _DEBUG ) + downloaderArgs.AddToTail( "-allowdebug" ); +#endif + downloaderArgs.AddToTail( "-CachePath" ); // Tell it where to download the files to. + downloaderArgs.AddToTail( cacheDir ); + + // Pass all the -mpi_worker, -mpi_file, -mpi_filebase args into the downloader app. + for ( int i=1; i < (int)newArgv.Count()-1; i++ ) + { + if ( V_stricmp( newArgv[i], "-mpi_filebase" ) == 0 || V_stricmp( newArgv[i], "-mpi_file" ) == 0 ) + { + downloaderArgs.AddToTail( newArgv[i] ); + downloaderArgs.AddToTail( newArgv[i+1] ); + newArgv.Remove( i ); + newArgv.Remove( i ); + --i; + } + else if ( V_stricmp( newArgv[i], "-mpi_worker" ) == 0 ) + { + // We need this arg so it knows what IP to connect to, but we want to leave it in the final launch args too. + downloaderArgs.AddToTail( newArgv[i] ); + downloaderArgs.AddToTail( newArgv[i+1] ); + ++i; + } + } + + // Transfer each file. + PROCESS_INFORMATION pi; + if ( !RunProcessFromArgs( downloaderArgs, bShowAppWindow, false, g_BaseAppPath, &pi ) ) + return false; + + *hProcess = pi.hProcess; + return true; +} + + +void SendPatchCommandToUIs( DWORD dwInstallerProcessId ) +{ + Msg( "SendPatchCommandToUIs\n "); + + CUtlVector<char> data; + data.AddToTail( VMPI_SERVICE_UI_PROTOCOL_VERSION ); + data.AddToTail( VMPI_SERVICE_TO_UI_PATCHING ); + + // This arg tells the UI whether to exit after running the command or not. + data.AddToTail( 1 ); + + // First argument is the working directory, which is the cache path in this case. + data.AddMultipleToTail( V_strlen( g_FileCachePath ) + 1, g_FileCachePath ); + + // Second argument is the command line. + char waitAndRestartExe[MAX_PATH], serviceUIExe[MAX_PATH], commandLine[1024 * 8]; + V_ComposeFileName( g_FileCachePath, "WaitAndRestart.exe", waitAndRestartExe, sizeof( waitAndRestartExe ) ); + V_ComposeFileName( g_BaseAppPath, "vmpi_service_ui.exe", serviceUIExe, sizeof( serviceUIExe ) ); // We're running the UI from the same directory this exe is in. + char strSeconds[64]; + V_snprintf( strSeconds, sizeof( strSeconds ), "*%lu", dwInstallerProcessId ); + + // IMPORTANT to use BuildCommandLineFromArgs here because it'll handle slashes and quotes correctly. + // If we don't do that, the command often won't work. + CUtlVector<char*> args; + args.AddToTail( waitAndRestartExe ); + args.AddToTail( strSeconds ); + args.AddToTail( g_BaseAppPath ); + args.AddToTail( serviceUIExe ); + BuildCommandLineFromArgs( args, commandLine, sizeof( commandLine ) ); + data.AddMultipleToTail( V_strlen( commandLine ) + 1, commandLine ); + + if ( g_pConnMgr ) + { + g_pConnMgr->SendPacket( -1, data.Base(), data.Count() ); + Sleep( 1000 ); // Make sure this packet goes out. + } +} + + +// Returns true if the service was just patched and should exit. +bool CheckDownloaderFinished() +{ + if ( !g_Waiting_hProcess ) + return false; + + // Check if the downloader has timed out and kill it if necessary. + if ( Plat_FloatTime() - g_Waiting_StartTime > MAX_DOWNLOADER_TIME_ALLOWED ) + { + TerminateProcess( g_Waiting_hProcess, 1 ); + CloseHandle( g_Waiting_hProcess ); + g_Waiting_hProcess = NULL; + return false; + } + + // Check if it's done. + if ( WaitForSingleObject( g_Waiting_hProcess, 0 ) != WAIT_OBJECT_0 ) + return false; + + CloseHandle( g_Waiting_hProcess ); + g_Waiting_hProcess = NULL; + + // Ok, it's done. Did it finish successfully? + char testFilename[MAX_PATH]; + V_ComposeFileName( g_FileCachePath, "ReadyToGo.txt", testFilename, sizeof( testFilename ) ); + if ( _access( testFilename, 0 ) != 0 ) + return false; + + // Ok, the downloader finished successfully. Run the worker app. + if ( g_bSuperDebugMode ) + AdjustSuperDebugArgs( g_Waiting_Argv ); + + // Figure out the name of the master machine. + V_strncpy( g_CurMasterName, "<unknown>", sizeof( g_CurMasterName ) ); + for ( int iArg=1; iArg < g_Waiting_Argv.Count()-1; iArg++ ) + { + if ( stricmp( g_Waiting_Argv[iArg], "-mpi_MasterName" ) == 0 ) + { + Q_strncpy( g_CurMasterName, g_Waiting_Argv[iArg+1], sizeof( g_CurMasterName ) ); + } + } + + char DLLFilename[MAX_PATH]; + if ( FindArg( __argc, __argv, "-TryDLLMode" ) && + g_RunMode == RUNMODE_CONSOLE && + GetDLLFilename( g_Waiting_Argv, DLLFilename ) && + !g_Waiting_bPatching ) + { + // This is just a helper for debugging. If it's VRAD, we can run it + // in-process as a DLL instead of running it as a separate EXE. + RunInDLL( DLLFilename, g_Waiting_Argv ); + } + else + { + // Run the (hopefully!) MPI app they specified. + RunProcessAtCommandLine( g_Waiting_Argv, g_Waiting_bShowAppWindow, g_Waiting_bPatching, g_Waiting_Priority ); + + if ( g_Waiting_bPatching ) + { + // Tell any currently-running UI apps to patch themselves and quit ASAP so the installer can finish. + SendPatchCommandToUIs( g_dwRunningProcessId ); + + ResumeThread( g_hRunningThread ); // We started the installer suspended so we could make sure we'd send out the patch command. + + // We just ran the installer, but let's forget about it, otherwise we'll kill its process when we exit here. + CloseHandle( g_hRunningProcess ); + CloseHandle( g_hRunningThread ) ; + g_hRunningProcess = g_hRunningThread = NULL; + g_RunningProcess_ExeName[0] = 0; + g_RunningProcess_MapName[0] = 0; + + ServiceHelpers_ExitEarly(); + return true; + } + } + + g_Waiting_Argv.PurgeAndDeleteElements(); + return false; +} + + +void HandlePacket_LOOKING_FOR_WORKERS( bf_read &buf, const CIPAddr &ipFrom ) +{ + // If we're downloading files for a job request, don't process any more "looking for workers" packets. + if ( g_Waiting_hProcess ) + return; + + // This will be a nonzero-length string if patching. + char versionString[512]; + buf.ReadString( versionString, sizeof( versionString ) ); + + int iPort = buf.ReadShort(); + int iPriority = buf.ReadShort(); + + // Make sure we don't run the same job more than once. + if ( !CheckJobID( buf, g_CurJobID ) ) + return; + + CUtlVector<char*> newArgv; + GetArgsFromBuffer( buf, newArgv, &g_Waiting_bShowAppWindow ); + + bool bForcePatch = false; + if ( buf.GetNumBytesLeft() >= 1 ) + bForcePatch = (buf.ReadByte() != 0); + + int iDownloaderPort = iPort; + if ( buf.GetNumBytesLeft() >= 2 ) + iDownloaderPort = buf.ReadShort(); + + // Add these arguments after the executable filename to tell the program + // that it's an MPI worker and who to connect to. + char strDownloaderIP[128], strMainIP[128]; + V_snprintf( strDownloaderIP, sizeof( strDownloaderIP ), "%d.%d.%d.%d:%d", ipFrom.ip[0], ipFrom.ip[1], ipFrom.ip[2], ipFrom.ip[3], iDownloaderPort ); + V_snprintf( strMainIP, sizeof( strMainIP ), "%d.%d.%d.%d:%d", ipFrom.ip[0], ipFrom.ip[1], ipFrom.ip[2], ipFrom.ip[3], iPort ); + + // (-mpi is already on the command line of whoever ran the app). + // AppendArg( commandLine, sizeof( commandLine ), "-mpi" ); + newArgv.InsertAfter( 0, CopyString( "-mpi_worker" ) ); + newArgv.InsertAfter( 1, CopyString( strDownloaderIP ) ); + + + // If the version string is set, then this is a patch. + bool bPatching = false; + if ( versionString[0] != 0 ) + { + bPatching = true; + + // Check that we haven't applied this patch version yet. This case usually happens right after we've applied a patch + // and we're restarting. The vmpi_transfer master is still pinging us telling us to patch, but we don't want to + // reapply this patch. + if ( atof( versionString ) <= atof( g_VersionString ) && !bForcePatch ) + { + newArgv.PurgeAndDeleteElements(); + return; + } + + // Ok, it's a new version. Get rid of whatever was running before. + KillRunningProcess( "Starting a patch..", true ); + } + + // If there's already a job running, only interrupt it if this new one has a higher priority. + if ( WaitForProcessToExit() ) + { + if ( iPriority > g_CurJobPriority ) + { + KillRunningProcess( "Interrupted by a higher priority process", true ); + } + else + { + // This means we're already running a job with equal to or greater priority than + // the one that has been requested. We're going to ignore this request. + newArgv.PurgeAndDeleteElements(); + return; + } + } + + // Responses go here. + g_CurRespondAddr = ipFrom; + + // Also look for -mpi_ShowAppWindow in the args to the service. + if ( !g_Waiting_bShowAppWindow && FindArg( __argc, __argv, "-mpi_ShowAppWindow" ) ) + g_Waiting_bShowAppWindow = true; + + // Copy all the files from the master and put them in our cache dir to run with. + char cacheDir[MAX_PATH]; + if ( StartDownloadingAppFiles( newArgv, cacheDir, sizeof( cacheDir ), g_Waiting_bShowAppWindow, &g_Waiting_hProcess, bPatching ) ) + { + // After it's downloaded, we want it to switch to the main connection port. + if ( newArgv.Count() >= 3 && V_stricmp( newArgv[2], strDownloaderIP ) == 0 ) + { + delete newArgv[2]; + newArgv[2] = CopyString( strMainIP ); + } + + g_Waiting_StartTime = Plat_FloatTime(); + g_Waiting_Argv.PurgeAndDeleteElements(); + g_Waiting_Argv = newArgv; + g_Waiting_Priority = iPriority; + g_Waiting_bPatching = bPatching; + newArgv.Purge(); + } + else + { + newArgv.PurgeAndDeleteElements(); + } + + // Remember that we tried to run this job so we don't try to run it again. + AddJobMemory( g_CurJobID ); + + SendStateToServicesBrowsers(); +} + + +void HandlePacket_STOP_SERVICE( bf_read &buf, const CIPAddr &ipFrom ) +{ + Msg( "Got a STOP_SERVICE packet. Shutting down...\n" ); + + CWaitTimer timer( 1 ); + while ( 1 ) + { + AddServicesBrowserIP( ipFrom ); + SendStateToServicesBrowsers(); + + if ( timer.ShouldKeepWaiting() ) + Sleep( 200 ); + else + break; + } + + StopUI(); + ServiceHelpers_ExitEarly(); +} + + +void HandlePacket_KILL_PROCESS( const CIPAddr *ipFrom ) +{ + if ( Plat_FloatTime() - g_flLastKillProcessTime > 5 ) + { + KillRunningProcess( "Got a KILL_PROCESS packet. Stopping the worker executable.\n", true ); + + if ( ipFrom ) + { + AddServicesBrowserIP( *ipFrom ); + SendStateToServicesBrowsers(); + } + + g_flLastKillProcessTime = Plat_FloatTime(); + } +} + + +void HandlePacket_FORCE_PASSWORD_CHANGE( bf_read &buf, const CIPAddr &ipFrom ) +{ + char newPassword[512]; + buf.ReadString( newPassword, sizeof( newPassword ) ); + + Msg( "Got a FORCE_PASSWORD_CHANGE (%s) packet.\n", newPassword ); + + SetPassword( newPassword ); + if ( g_pConnMgr ) + g_pConnMgr->SendCurStateTo( -1 ); +} + + +void VMPI_Waiter_Update() +{ + CheckScreensaverRunning(); + HandleWindowMessages(); + UpdateServicesBrowserIPs(); + + while ( 1 ) + { + WaitForProcessToExit(); + if ( CheckDownloaderFinished() ) + return; + + // Recv off the socket first so it clears the queue while we're waiting for the process to exit. + char data[4096]; + CIPAddr ipFrom; + int len = g_pSocket->RecvFrom( data, sizeof( data ), &ipFrom ); + + // Any incoming packets? + if ( len <= 0 ) + break; + + bf_read buf( data, len ); + if ( buf.ReadByte() != VMPI_PROTOCOL_VERSION ) + continue; + + // Only handle packets with the right password. + char pwString[256]; + buf.ReadString( pwString, sizeof( pwString ) ); + + int packetID = buf.ReadByte(); + + if ( pwString[0] == VMPI_PASSWORD_OVERRIDE ) + { + // Always process these packets regardless of the password (these usually come from + // the installer when it is trying to stop a previously-running instance). + } + else if ( packetID == VMPI_LOOKING_FOR_WORKERS ) + { + if ( pwString[0] == 0 ) + { + if ( g_pPassword && g_pPassword[0] != 0 ) + continue; + } + else + { + if ( !g_pPassword || stricmp( g_pPassword, pwString ) != 0 ) + continue; + } + } + + // VMPI_KILL_PROCESS is checked before everything. + if ( packetID == VMPI_KILL_PROCESS ) + { + HandlePacket_KILL_PROCESS( &ipFrom ); + } + else if ( packetID == VMPI_PING_REQUEST ) + { + AddServicesBrowserIP( ipFrom ); + SendStateToServicesBrowsers(); + } + else if ( packetID == VMPI_STOP_SERVICE ) + { + HandlePacket_STOP_SERVICE( buf, ipFrom ); + return; + } + else if ( packetID == VMPI_SERVICE_PATCH ) + { + // The key to doing this here is that we ignore whether we're disabled or in screensaver mode.. we always handle + // the patch command (unless we've already handled this job ID OR if we've already applied this patch version). + HandlePacket_LOOKING_FOR_WORKERS( buf, ipFrom ); + } + else if ( packetID == VMPI_FORCE_PASSWORD_CHANGE ) + { + HandlePacket_FORCE_PASSWORD_CHANGE( buf, ipFrom ); + } + + // If they've told us not to wait for jobs, then ignore the packet. + if ( g_iCurState == VMPI_SERVICE_STATE_DISABLED || (g_bScreensaverMode && !g_bScreensaverRunning) ) + continue; + + if ( packetID == VMPI_LOOKING_FOR_WORKERS ) + { + HandlePacket_LOOKING_FOR_WORKERS( buf, ipFrom ); + } + } +} + + +// ------------------------------------------------------------------------------------------------ // +// Startup and service code. +// ------------------------------------------------------------------------------------------------ // + +void RunMainLoop() +{ + // This is the service's main loop. + while ( 1 ) + { + // If the service has been told to exit, then just exit. + if ( ServiceHelpers_ShouldExit() ) + break; + + VMPI_Waiter_Update(); + g_pConnMgr->Update(); + + Sleep( 50 ); + } +} + + +void InternalRunService() +{ + if ( !VMPI_Waiter_Init() ) + return; + + RunMainLoop(); + VMPI_Waiter_Term(); +} + + +// This function runs us as a console app. Useful for debugging or if you want to run more +// than one instance of VRAD on the same machine. +void RunAsNonServiceApp() +{ + InternalRunService(); +} + + +// This function runs inside the service thread. +void ServiceThreadFn( void *pParam ) +{ + InternalRunService(); +} + + +// This function works with the service manager and runs as a system service. +void RunService() +{ + if( !ServiceHelpers_StartService( VMPI_SERVICE_NAME_INTERNAL, ServiceThreadFn, NULL ) ) + { + Msg( "Service manager not started. Running as console app.\n" ); + g_RunMode = RUNMODE_CONSOLE; + InternalRunService(); + } +} + +int APIENTRY WinMain(HINSTANCE hInstance, + HINSTANCE hPrevInstance, + LPSTR lpCmdLine, + int nCmdShow) +{ + // Hook spew output. + SpewOutputFunc( MySpewOutputFunc ); + + // Get access to the registry.. + RegCreateKey( HKEY_LOCAL_MACHINE, VMPI_SERVICE_KEY, &g_hVMPIServiceKey ); + + // Setup our version string. + LoadString( hInstance, VMPI_SERVICE_IDS_VERSION_STRING, g_VersionString, sizeof( g_VersionString ) ); + + // Setup the base app path. + if ( !GetModuleFileName( GetModuleHandle( NULL ), g_BaseAppPath, sizeof( g_BaseAppPath ) ) ) + { + Warning( "GetModuleFileName failed.\n" ); + return false; + } + V_StripLastDir( g_BaseAppPath, sizeof( g_BaseAppPath ) ); + + // Setup the cache path. + V_ComposeFileName( g_BaseAppPath, "vmpi_service_cache", g_FileCachePath, sizeof( g_FileCachePath ) ); + + + const char *pArg = FindArg( __argc, __argv, "-mpi_pw", NULL ); + SetPassword( pArg ); + + if ( FindArg( __argc, __argv, "-console" ) ) + { + g_RunMode = RUNMODE_CONSOLE; + } + else + { + g_RunMode = RUNMODE_SERVICE; + } + + if ( FindArg( __argc, __argv, "-superdebug" ) ) + g_bSuperDebugMode = true; + + g_AppStartTime = GetTickCount(); + g_bMinimized = FindArg( __argc, __argv, "-minimized" ) != NULL; + + ServiceHelpers_Init(); + g_hInstance = hInstance; + + LoadStateFromRegistry(); + + // Install the service? + if ( g_RunMode == RUNMODE_CONSOLE ) + { + RunAsNonServiceApp(); + } + else + { + RunService(); + } + + return 0; +} + diff --git a/utils/vmpi/vmpi_service/vmpi_service.h b/utils/vmpi/vmpi_service/vmpi_service.h new file mode 100644 index 0000000..17c8178 --- /dev/null +++ b/utils/vmpi/vmpi_service/vmpi_service.h @@ -0,0 +1,19 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// + +#if !defined(AFX_VMPI_SERVICE_H__0EE084DB_9164_4DC2_9E95_CF25D32AAA7B__INCLUDED_) +#define AFX_VMPI_SERVICE_H__0EE084DB_9164_4DC2_9E95_CF25D32AAA7B__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 + +#include "resource.h" + + +#endif // !defined(AFX_VMPI_SERVICE_H__0EE084DB_9164_4DC2_9E95_CF25D32AAA7B__INCLUDED_) diff --git a/utils/vmpi/vmpi_service/vmpi_service.rc b/utils/vmpi/vmpi_service/vmpi_service.rc new file mode 100644 index 0000000..bb7debf --- /dev/null +++ b/utils/vmpi/vmpi_service/vmpi_service.rc @@ -0,0 +1,74 @@ +// Microsoft Visual C++ generated resource script. +// +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "afxres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (U.S.) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +#ifdef _WIN32 +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US +#pragma code_page(1252) +#endif //_WIN32 + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""afxres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// String Table +// + +STRINGTABLE +BEGIN + IDS_VERSION_STRING "3.3" +END + +#endif // English (U.S.) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/utils/vmpi/vmpi_service/vmpi_service.vpc b/utils/vmpi/vmpi_service/vmpi_service.vpc new file mode 100644 index 0000000..7c5d4ba --- /dev/null +++ b/utils/vmpi/vmpi_service/vmpi_service.vpc @@ -0,0 +1,60 @@ +//----------------------------------------------------------------------------- +// VMPI_SERVICE.VPC +// +// Project Script +//----------------------------------------------------------------------------- + +$Macro SRCDIR "..\..\.." +$Macro OUTBINDIR "$SRCDIR\..\game\bin" + +$Include "$SRCDIR\vpc_scripts\source_exe_base.vpc" + +$Configuration +{ + $Compiler + { + $AdditionalIncludeDirectories "$BASE,..\..\common,..\" + $PreprocessorDefinitions "$BASE;PROTECTED_THINGS_DISABLE" + } + + $Linker + { + $AdditionalDependencies "$BASE pdh.lib ws2_32.lib odbc32.lib odbccp32.lib" + } +} + +$Project "Vmpi_service" +{ + $Folder "Source Files" + { + $File "..\iphelpers.cpp" + $File "service_conn_mgr.cpp" + $File "service_helpers.cpp" + $File "perf_counters.cpp" + $File "vmpi_service.rc" + $File "StdAfx.cpp" + $File "..\tcpsocket_helpers.cpp" + $File "..\ThreadedTCPSocket.cpp" + $File "..\ThreadedTCPSocketEmu.cpp" + $File "..\threadhelpers.cpp" + $File "vmpi_service.cpp" + } + + $Folder "Header Files" + { + $File "service_conn_mgr.h" + $File "service_helpers.h" + $File "perf_counters.h" + $File "StdAfx.h" + $File "resource.h" + $File "vmpi_service.h" + } + + $Folder "Resource Files" + { + $File "..\vmpi_service_ui\idi_busy_icon.ico" + $File "..\vmpi_service_ui\idi_disabled_icon.ico" + $File "..\vmpi_service_ui\idi_waiting_icon.ico" + $File "..\vmpi_service_ui\vmpi_service.ico" + } +} |