summaryrefslogtreecommitdiff
path: root/utils/vmpi/vmpi_service
diff options
context:
space:
mode:
authorFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
committerFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
commit3bf9df6b2785fa6d951086978a3e66f49427166a (patch)
tree2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /utils/vmpi/vmpi_service
downloadarchived-source-engine-2018-hl2-src-master.tar.xz
archived-source-engine-2018-hl2-src-master.zip
Diffstat (limited to 'utils/vmpi/vmpi_service')
-rw-r--r--utils/vmpi/vmpi_service/StdAfx.cpp15
-rw-r--r--utils/vmpi/vmpi_service/StdAfx.h35
-rw-r--r--utils/vmpi/vmpi_service/perf_counters.cpp500
-rw-r--r--utils/vmpi/vmpi_service/perf_counters.h28
-rw-r--r--utils/vmpi/vmpi_service/resource.h18
-rw-r--r--utils/vmpi/vmpi_service/service_conn_mgr.cpp234
-rw-r--r--utils/vmpi/vmpi_service/service_conn_mgr.h92
-rw-r--r--utils/vmpi/vmpi_service/service_helpers.cpp181
-rw-r--r--utils/vmpi/vmpi_service/service_helpers.h43
-rw-r--r--utils/vmpi/vmpi_service/vmpi_service.cpp1710
-rw-r--r--utils/vmpi/vmpi_service/vmpi_service.h19
-rw-r--r--utils/vmpi/vmpi_service/vmpi_service.rc74
-rw-r--r--utils/vmpi/vmpi_service/vmpi_service.vpc60
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"
+ }
+}