//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: // // $NoKeywords: $ // //=============================================================================// // ServicesDlg.cpp : implementation file // #include "stdafx.h" #include "ServicesDlg.h" #include "vmpi.h" #include "bitbuf.h" #include "tier1/strtools.h" #include "patchtimeout.h" #include "SetPasswordDlg.h" #include "vmpi_browser_helpers.h" #include #ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif #define SERVICE_OFF_TIMEOUT (20*1000) // If we haven't heard from a service in this long, // then we assume the service is off. #define SERVICES_PING_INTERVAL (3*1000) // ping the services every so often #define SERVICE_MAX_UPDATE_INTERVAL (8*1000) // Update each service in the listbox at least this often. int V_AfxMessageBox( int mbType, const char *pFormat, ... ) { char msg[4096]; va_list marker; va_start( marker, pFormat ); _vsnprintf( msg, sizeof( msg ), pFormat, marker ); va_end( marker ); return AfxMessageBox( msg, mbType ); } bool CServiceInfo::IsOff() const { return (Plat_MSTime() - m_LastPingTimeMS) > SERVICE_OFF_TIMEOUT; } // Returns the argument following pName. // If pName is the last argument on the command line, returns pEndArgDefault. // Returns NULL if there is no argument with pName. const char* FindArg( const char *pName, const char *pEndArgDefault="" ) { for ( int i=0; i < __argc; i++ ) { if ( stricmp( pName, __argv[i] ) == 0 ) { if ( (i+1) < __argc ) return __argv[i+1]; else return pEndArgDefault; } } return NULL; } void AppendStr( char *dest, int destSize, const char *pFormat, ... ) { char str[4096]; va_list marker; va_start( marker, pFormat ); _vsnprintf( str, sizeof( str ), pFormat, marker ); va_end( marker ); str[sizeof( str ) - 1] = 0; V_strncat( dest, str, destSize ); } 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; } const char* GetStatusString( CServiceInfo *pInfo ) { if ( pInfo->IsOff() ) return "off"; else if ( pInfo->m_iState == VMPI_STATE_BUSY ) return "busy"; else if ( pInfo->m_iState == VMPI_STATE_PATCHING ) return "patching"; else if ( pInfo->m_iState == VMPI_STATE_DISABLED ) return "disabled"; else if ( pInfo->m_iState == VMPI_STATE_SCREENSAVER_DISABLED ) return "disabled (screensaver)"; else if ( pInfo->m_iState == VMPI_STATE_DOWNLOADING ) return "downloading"; else return "idle"; } // --------------------------------------------------------------------------------------------------------- // // Column sort functions. // --------------------------------------------------------------------------------------------------------- // typedef int (CALLBACK *ServicesSortFn)( LPARAM iItem1, LPARAM iItem2, LPARAM lpParam ); static int CALLBACK SortByName( LPARAM iItem1, LPARAM iItem2, LPARAM lpParam ) { CServiceInfo *pInfo1 = (CServiceInfo*)iItem1; CServiceInfo *pInfo2 = (CServiceInfo*)iItem2; return strcmp( pInfo1->m_ComputerName, pInfo2->m_ComputerName ); } static int CALLBACK SortByStatus( LPARAM iItem1, LPARAM iItem2, LPARAM lpParam ) { CServiceInfo *pInfo1 = (CServiceInfo*)iItem1; CServiceInfo *pInfo2 = (CServiceInfo*)iItem2; return -strcmp( GetStatusString( pInfo2 ), GetStatusString( pInfo1 ) ); } static int CALLBACK SortByRunningTime( LPARAM iItem1, LPARAM iItem2, LPARAM lpParam ) { CServiceInfo *pInfo1 = (CServiceInfo*)iItem1; CServiceInfo *pInfo2 = (CServiceInfo*)iItem2; int v1 = pInfo1->m_LiveTimeMS / (1000*60); // Sort on minutes so it doesn't constantly resort the list. int v2 = pInfo2->m_LiveTimeMS / (1000*60); if ( v2 > v1 ) return 1; else if ( v2 < v1 ) return -1; else return 0; } static int CALLBACK SortByWorkerAppRunningTime( LPARAM iItem1, LPARAM iItem2, LPARAM lpParam ) { CServiceInfo *pInfo1 = (CServiceInfo*)iItem1; CServiceInfo *pInfo2 = (CServiceInfo*)iItem2; int v1 = pInfo1->m_WorkerAppTimeMS / (1000*60); // Sort on minutes so it doesn't constantly resort the list. int v2 = pInfo2->m_WorkerAppTimeMS / (1000*60); if ( v2 > v1 ) return 1; else if ( v2 < v1 ) return -1; else return 0; } static int CALLBACK SortByMasterName( LPARAM iItem1, LPARAM iItem2, LPARAM lpParam ) { CServiceInfo *pInfo1 = (CServiceInfo*)iItem1; CServiceInfo *pInfo2 = (CServiceInfo*)iItem2; return -strcmp( pInfo2->m_MasterName, pInfo1->m_MasterName ); } static int CALLBACK SortByProtocol( LPARAM iItem1, LPARAM iItem2, LPARAM lpParam ) { CServiceInfo *pInfo1 = (CServiceInfo*)iItem1; CServiceInfo *pInfo2 = (CServiceInfo*)iItem2; return pInfo1->m_ProtocolVersion > pInfo2->m_ProtocolVersion; } static int CALLBACK SortByPassword( LPARAM iItem1, LPARAM iItem2, LPARAM lpParam ) { CServiceInfo *pInfo1 = (CServiceInfo*)iItem1; CServiceInfo *pInfo2 = (CServiceInfo*)iItem2; return -strcmp( pInfo2->m_Password, pInfo1->m_Password ); } static int CALLBACK SortByServiceVersion( LPARAM iItem1, LPARAM iItem2, LPARAM lpParam ) { CServiceInfo *pInfo1 = (CServiceInfo*)iItem1; CServiceInfo *pInfo2 = (CServiceInfo*)iItem2; return -strcmp( pInfo2->m_ServiceVersion, pInfo1->m_ServiceVersion ); } static int CALLBACK SortByExe( LPARAM iItem1, LPARAM iItem2, LPARAM lpParam ) { CServiceInfo *pInfo1 = (CServiceInfo*)iItem1; CServiceInfo *pInfo2 = (CServiceInfo*)iItem2; return -strcmp( pInfo2->m_ExeName, pInfo1->m_ExeName ); } static int CALLBACK SortByMap( LPARAM iItem1, LPARAM iItem2, LPARAM lpParam ) { CServiceInfo *pInfo1 = (CServiceInfo*)iItem1; CServiceInfo *pInfo2 = (CServiceInfo*)iItem2; return -strcmp( pInfo2->m_MapName, pInfo1->m_MapName ); } static int CALLBACK SortByCPUPercentage( LPARAM iItem1, LPARAM iItem2, LPARAM lpParam ) { CServiceInfo *pInfo1 = (CServiceInfo*)iItem1; CServiceInfo *pInfo2 = (CServiceInfo*)iItem2; if ( pInfo2->m_CPUPercentage > pInfo1->m_CPUPercentage ) return 1; else if ( pInfo2->m_CPUPercentage < pInfo1->m_CPUPercentage ) return -1; else return 0; } static int CALLBACK SortByMemUsage( LPARAM iItem1, LPARAM iItem2, LPARAM lpParam ) { CServiceInfo *pInfo1 = (CServiceInfo*)iItem1; CServiceInfo *pInfo2 = (CServiceInfo*)iItem2; if ( pInfo2->m_MemUsageMB > pInfo1->m_MemUsageMB ) return 1; else if ( pInfo2->m_MemUsageMB < pInfo1->m_MemUsageMB ) return -1; else return 0; } // --------------------------------------------------------------------------------------------------------- // // Column information. // --------------------------------------------------------------------------------------------------------- // struct { char *pText; int width; ServicesSortFn sortFn; } g_ColumnInfos[] = { {"Computer Name", 150, SortByName}, {"Status", 95, SortByStatus}, {"Service Run Time", 100, SortByRunningTime}, {"Worker App Run Time", 125, SortByWorkerAppRunningTime}, {"Master", 150, SortByMasterName}, {"Password", 60, SortByPassword}, {"Protocol", 60, SortByProtocol}, {"Version", 50, SortByServiceVersion}, {"CPU", 50, SortByCPUPercentage}, {"Memory (MB)", 80, SortByMemUsage}, {"Exe", 80, SortByExe}, {"Map", 90, SortByMap}, }; #define COLUMN_COMPUTER_NAME 0 #define COLUMN_STATUS 1 #define COLUMN_RUNNING_TIME 2 #define COLUMN_WORKER_APP_RUNNING_TIME 3 #define COLUMN_MASTER_NAME 4 #define COLUMN_PASSWORD 5 #define COLUMN_PROTOCOL_VERSION 6 #define COLUMN_SERVICE_VERSION 7 #define COLUMN_CPU_PERCENTAGE 8 #define COLUMN_MEM_USAGE 9 #define COLUMN_EXE_NAME 10 #define COLUMN_MAP_NAME 11 // Used to sort all the columns. // When they click on a column, we add that index to entry 0 in here. // Then when sorting, if 2 columns are equal, we move to the next sort function. CUtlVector g_SortColumns; static void PushSortColumn( int iColumn ) { // First, get rid of all entries with this same index. int iPrev = g_SortColumns.Find( iColumn ); if ( iPrev != g_SortColumns.InvalidIndex() ) g_SortColumns.Remove( iPrev ); // Now add this one. g_SortColumns.AddToTail( iColumn ); } static int CALLBACK MainSortFn( LPARAM iItem1, LPARAM iItem2, LPARAM lpParam ) { int curStatus = 0; for ( int i = (int)g_SortColumns.Count()-1; i >= 0; i-- ) { curStatus = g_ColumnInfos[g_SortColumns[i]].sortFn( iItem1, iItem2, lpParam ); if ( curStatus != 0 ) break; } return curStatus; } ///////////////////////////////////////////////////////////////////////////// // CServicesDlg dialog CServicesDlg::CServicesDlg(CWnd* pParent /*=NULL*/) : CIdleDialog(CServicesDlg::IDD, pParent) { //{{AFX_DATA_INIT(CServicesDlg) // NOTE: the ClassWizard will add member initialization here //}}AFX_DATA_INIT m_pServicesPingSocket = NULL; } void CServicesDlg::DoDataExchange(CDataExchange* pDX) { CDialog::DoDataExchange(pDX); //{{AFX_DATA_MAP(CServicesDlg) DDX_Control(pDX, IDC_NUM_SERVICES, m_NumServicesControl); DDX_Control(pDX, IDC_NUM_DISABLED_SERVICES, m_NumDisabledServicesControl); DDX_Control(pDX, IDC_NUM_WORKING_SERVICES, m_NumWorkingServicesControl); DDX_Control(pDX, IDC_NUM_WAITING_SERVICES, m_NumWaitingServicesControl); DDX_Control(pDX, IDC_NUM_OFF_SERVICES, m_NumOffServicesControl); DDX_Control(pDX, IDC_CURRENT_PASSWORD, m_PasswordDisplay); DDX_Control(pDX, IDC_SERVICES_LIST, m_ServicesList); //}}AFX_DATA_MAP } BEGIN_MESSAGE_MAP(CServicesDlg, CIdleDialog) //{{AFX_MSG_MAP(CServicesDlg) ON_BN_CLICKED(ID_PATCH_SERVICES, OnPatchServices) ON_BN_CLICKED(ID_STOP_SERVICES, OnStopServices) ON_BN_CLICKED(ID_STOP_JOBS, OnStopJobs) ON_BN_CLICKED(ID_FILTER_BY_PASSWORD, OnFilterByPassword) ON_BN_CLICKED(ID_FORCE_PASSWORD, OnForcePassword) ON_BN_CLICKED(ID_COPY_TO_CLIPBOARD, OnCopyToClipboard) ON_NOTIFY(NM_DBLCLK, IDC_SERVICES_LIST, OnDblclkServicesList) ON_WM_SIZE() //}}AFX_MSG_MAP END_MESSAGE_MAP() ///////////////////////////////////////////////////////////////////////////// // CServicesDlg message handlers BOOL CServicesDlg::OnInitDialog() { CDialog::OnInitDialog(); // Initially, just sort by computer name. PushSortColumn( COLUMN_COMPUTER_NAME ); HICON hIcon = LoadIcon( AfxGetInstanceHandle(), MAKEINTRESOURCE( IDR_MAINFRAME ) ); SetIcon( hIcon, true ); m_ServicesList.SetExtendedStyle( LVS_EX_FULLROWSELECT ); // Setup the headers. for ( int i=0; i < ARRAYSIZE( g_ColumnInfos ); i++ ) { m_ServicesList.InsertColumn( i, g_ColumnInfos[i].pText, LVCFMT_LEFT, g_ColumnInfos[i].width, i ); } m_pServicesPingSocket = CreateIPSocket(); if ( m_pServicesPingSocket ) { m_pServicesPingSocket->BindToAny( 0 ); } m_dwLastServicesPing = GetTickCount() - SERVICES_PING_INTERVAL; StartIdleProcessing( 100 ); // get idle messages every half second m_AnchorMgr.AddAnchor( this, GetDlgItem( IDC_NUM_SERVICES_LABEL ), ANCHOR_LEFT, ANCHOR_BOTTOM, ANCHOR_LEFT, ANCHOR_BOTTOM ); m_AnchorMgr.AddAnchor( this, GetDlgItem( IDC_NUM_SERVICES ), ANCHOR_LEFT, ANCHOR_BOTTOM, ANCHOR_LEFT, ANCHOR_BOTTOM ); m_AnchorMgr.AddAnchor( this, GetDlgItem( IDC_NUM_DISABLED_SERVICES_LABEL ), ANCHOR_LEFT, ANCHOR_BOTTOM, ANCHOR_LEFT, ANCHOR_BOTTOM ); m_AnchorMgr.AddAnchor( this, GetDlgItem( IDC_NUM_DISABLED_SERVICES ), ANCHOR_LEFT, ANCHOR_BOTTOM, ANCHOR_LEFT, ANCHOR_BOTTOM ); m_AnchorMgr.AddAnchor( this, GetDlgItem( IDC_NUM_WORKING_SERVICES_LABEL ), ANCHOR_LEFT, ANCHOR_BOTTOM, ANCHOR_LEFT, ANCHOR_BOTTOM ); m_AnchorMgr.AddAnchor( this, GetDlgItem( IDC_NUM_WORKING_SERVICES ), ANCHOR_LEFT, ANCHOR_BOTTOM, ANCHOR_LEFT, ANCHOR_BOTTOM ); m_AnchorMgr.AddAnchor( this, GetDlgItem( IDC_NUM_WAITING_SERVICES_LABEL ), ANCHOR_LEFT, ANCHOR_BOTTOM, ANCHOR_LEFT, ANCHOR_BOTTOM ); m_AnchorMgr.AddAnchor( this, GetDlgItem( IDC_NUM_WAITING_SERVICES ), ANCHOR_LEFT, ANCHOR_BOTTOM, ANCHOR_LEFT, ANCHOR_BOTTOM ); m_AnchorMgr.AddAnchor( this, GetDlgItem( IDC_NUM_OFF_SERVICES_LABEL ), ANCHOR_LEFT, ANCHOR_BOTTOM, ANCHOR_LEFT, ANCHOR_BOTTOM ); m_AnchorMgr.AddAnchor( this, GetDlgItem( IDC_NUM_OFF_SERVICES ), ANCHOR_LEFT, ANCHOR_BOTTOM, ANCHOR_LEFT, ANCHOR_BOTTOM ); m_AnchorMgr.AddAnchor( this, GetDlgItem( IDC_CURRENT_PASSWORD_LABEL ), ANCHOR_LEFT, ANCHOR_BOTTOM, ANCHOR_LEFT, ANCHOR_BOTTOM ); m_AnchorMgr.AddAnchor( this, GetDlgItem( IDC_CURRENT_PASSWORD ), ANCHOR_LEFT, ANCHOR_BOTTOM, ANCHOR_LEFT, ANCHOR_BOTTOM ); m_AnchorMgr.AddAnchor( this, GetDlgItem( ID_PATCH_SERVICES ), ANCHOR_LEFT, ANCHOR_BOTTOM, ANCHOR_LEFT, ANCHOR_BOTTOM ); m_AnchorMgr.AddAnchor( this, GetDlgItem( ID_STOP_SERVICES ), ANCHOR_LEFT, ANCHOR_BOTTOM, ANCHOR_LEFT, ANCHOR_BOTTOM ); m_AnchorMgr.AddAnchor( this, GetDlgItem( ID_STOP_JOBS ), ANCHOR_LEFT, ANCHOR_BOTTOM, ANCHOR_LEFT, ANCHOR_BOTTOM ); m_AnchorMgr.AddAnchor( this, GetDlgItem( ID_FORCE_PASSWORD ), ANCHOR_LEFT, ANCHOR_BOTTOM, ANCHOR_LEFT, ANCHOR_BOTTOM ); m_AnchorMgr.AddAnchor( this, GetDlgItem( ID_FILTER_BY_PASSWORD ), ANCHOR_LEFT, ANCHOR_BOTTOM, ANCHOR_LEFT, ANCHOR_BOTTOM ); m_AnchorMgr.AddAnchor( this, GetDlgItem( ID_COPY_TO_CLIPBOARD ), ANCHOR_LEFT, ANCHOR_BOTTOM, ANCHOR_LEFT, ANCHOR_BOTTOM ); m_AnchorMgr.AddAnchor( this, GetDlgItem( IDC_SERVICES_LIST ), ANCHOR_LEFT, ANCHOR_TOP, ANCHOR_RIGHT, ANCHOR_BOTTOM ); // Unless they specify admin mode, hide all the controls that can mess with the services. if ( !FindArg( "-Admin" ) ) { ::ShowWindow( ::GetDlgItem( m_hWnd, ID_PATCH_SERVICES ), SW_HIDE ); ::ShowWindow( ::GetDlgItem( m_hWnd, ID_STOP_SERVICES ), SW_HIDE ); ::ShowWindow( ::GetDlgItem( m_hWnd, ID_STOP_JOBS ), SW_HIDE ); ::ShowWindow( ::GetDlgItem( m_hWnd, ID_FORCE_PASSWORD ), SW_HIDE ); } m_NetViewThread.Init(); return TRUE; // return TRUE unless you set the focus to a control // EXCEPTION: OCX Property Pages should return FALSE } void CServicesDlg::BuildVMPIPingPacket( CUtlVector &out, char cPacketID, unsigned char protocolVersion, bool bIgnorePassword ) { out.Purge(); out.AddToTail( protocolVersion ); const char *pPassword = m_Password; if ( pPassword[0] == 0 || bIgnorePassword ) { // If they haven't set a password to filter by, then we want all services to report in. out.AddToTail( VMPI_PASSWORD_OVERRIDE ); out.AddToTail( 0 ); } else { out.AddMultipleToTail( strlen( pPassword ) + 1, pPassword ); // password. } out.AddToTail( cPacketID ); } void CServicesDlg::UpdateServicesFromNetMessages() { while ( 1 ) { char in[1024]; CIPAddr ipFrom; int len = m_pServicesPingSocket->RecvFrom( in, sizeof( in ), &ipFrom ); if ( len < 4 ) break; bf_read buf( in, len ); unsigned char protocolVersion = buf.ReadByte(); if ( protocolVersion == 4 || protocolVersion == VMPI_PROTOCOL_VERSION ) // Protocol version 4 is almost the same. { int packetID = buf.ReadByte(); if ( packetID == VMPI_PING_RESPONSE ) { int iState = buf.ReadByte(); unsigned long liveTimeMS = (unsigned long)buf.ReadLong(); int iPort = buf.ReadLong(); char computerName[512]; buf.ReadString( computerName, sizeof( computerName ) ); char masterName[512]; if ( buf.GetNumBitsLeft() ) buf.ReadString( masterName, sizeof( masterName ) ); else masterName[0] = 0; unsigned long workerAppTimeMS = 0; if ( buf.GetNumBitsLeft() ) workerAppTimeMS = buf.ReadLong(); char password[512] = {0}; if ( protocolVersion == VMPI_PROTOCOL_VERSION ) buf.ReadString( password, sizeof( password ) ); char serviceVersion[32]; if ( protocolVersion == VMPI_PROTOCOL_VERSION ) buf.ReadString( serviceVersion, sizeof( serviceVersion ) ); else V_strncpy( serviceVersion, "old", sizeof( serviceVersion ) ); int cpuPercentage = -1; if ( buf.GetNumBytesLeft() >= 1 ) cpuPercentage = buf.ReadByte(); char exeName[512]; if ( buf.GetNumBytesLeft() >= 1 ) buf.ReadString( exeName, sizeof( exeName ) ); else V_strncpy( exeName, "-", sizeof( exeName ) ); short memUsage = -1; if ( buf.GetNumBytesLeft() >= 2 ) memUsage = buf.ReadShort(); char mapName[256]; if ( buf.GetNumBytesLeft() >= 1 ) buf.ReadString( mapName, sizeof( mapName ) ); else V_strncpy( mapName, "-", sizeof( mapName ) ); CServiceInfo *pInfo = FindServiceByComputerName( computerName ); if ( !pInfo ) { pInfo = new CServiceInfo; m_Services.AddToTail( pInfo ); pInfo->m_ComputerName = computerName; pInfo->m_pLastStatusText = NULL; pInfo->m_Addr.port = iPort; pInfo->m_LastPingTimeMS = Plat_MSTime(); pInfo->m_MasterName = "?"; int iItem = m_ServicesList.InsertItem( COLUMN_COMPUTER_NAME, pInfo->m_ComputerName, NULL ); m_ServicesList.SetItemData( iItem, (DWORD)pInfo ); // Update the display of # of services. UpdateServiceCountDisplay(); } pInfo->m_ProtocolVersion = protocolVersion; V_strncpy( pInfo->m_ServiceVersion, serviceVersion, sizeof( pInfo->m_ServiceVersion ) ); pInfo->m_Addr = ipFrom; pInfo->m_CPUPercentage = cpuPercentage; pInfo->m_MemUsageMB = memUsage; pInfo->m_ExeName = exeName; pInfo->m_MapName = mapName; pInfo->m_MasterName = masterName; pInfo->m_LiveTimeMS = liveTimeMS; pInfo->m_WorkerAppTimeMS = workerAppTimeMS; pInfo->m_iState = iState; pInfo->m_LastPingTimeMS = Plat_MSTime(); pInfo->m_Password = password; UpdateServiceInListbox( pInfo ); } } } } void CServicesDlg::UpdateServicesFromNetView() { CUtlVector computerNames; m_NetViewThread.GetComputerNames( computerNames ); for ( int i=0; i < computerNames.Count(); i++ ) { CServiceInfo *pInfo = FindServiceByComputerName( computerNames[i] ); if ( !pInfo ) { pInfo = new CServiceInfo; m_Services.AddToTail( pInfo ); pInfo->m_ComputerName = computerNames[i]; pInfo->m_LastPingTimeMS = Plat_MSTime() - SERVICE_OFF_TIMEOUT*2; // so it's marked as "off" pInfo->m_LastLiveTimeMS = Plat_MSTime(); pInfo->m_LiveTimeMS = 0; pInfo->m_WorkerAppTimeMS = 0; pInfo->m_ProtocolVersion = 0; pInfo->m_ServiceVersion[0] = 0; pInfo->m_CPUPercentage = -1; pInfo->m_MemUsageMB = -1; int iItem = m_ServicesList.InsertItem( COLUMN_COMPUTER_NAME, pInfo->m_ComputerName, NULL ); m_ServicesList.SetItemData( iItem, (DWORD)pInfo ); // Update the display of # of services. UpdateServiceCountDisplay(); UpdateServiceInListbox( pInfo ); } } computerNames.PurgeAndDeleteElements(); } void CServicesDlg::OnIdle() { DWORD curTime = GetTickCount(); if ( !m_pServicesPingSocket ) return; // Broadcast out to all the services? if ( curTime - m_dwLastServicesPing >= SERVICES_PING_INTERVAL ) { m_dwLastServicesPing = curTime; for ( int i=VMPI_SERVICE_PORT; i <= VMPI_LAST_SERVICE_PORT; i++ ) { CUtlVector data; BuildVMPIPingPacket( data, VMPI_PING_REQUEST ); m_pServicesPingSocket->Broadcast( data.Base(), data.Count(), i ); // Also send out a version 4 one because we understand a version 4 response. BuildVMPIPingPacket( data, VMPI_PING_REQUEST, 4 ); m_pServicesPingSocket->Broadcast( data.Base(), data.Count(), i ); } UpdateServiceCountDisplay(); } m_ServicesList.SetRedraw( false ); // Check for messages from services. UpdateServicesFromNetMessages(); // Issue a "net view" command, parse the output, and add any computers it lists. // This lets us figure out which PCs on the network are not running the service. UpdateServicesFromNetView(); FOR_EACH_LL( m_Services, iService ) { CServiceInfo *pInfo = m_Services[iService]; if ( Plat_MSTime() - pInfo->m_LastUpdateTime > SERVICE_MAX_UPDATE_INTERVAL ) { UpdateServiceInListbox( pInfo ); } } if ( m_bListChanged ) { ResortItems(); m_bListChanged = false; } m_ServicesList.SetRedraw( true ); } CServiceInfo* CServicesDlg::FindServiceByComputerName( const char *pComputerName ) { FOR_EACH_LL( m_Services, i ) { if ( Q_stricmp( m_Services[i]->m_ComputerName, pComputerName ) == 0 ) return m_Services[i]; } return NULL; } void CServicesDlg::SendToSelectedServices( const char *pData, int len ) { POSITION pos = m_ServicesList.GetFirstSelectedItemPosition(); while ( pos ) { int iItem = m_ServicesList.GetNextSelectedItem( pos ); CServiceInfo *pInfo = (CServiceInfo*)m_ServicesList.GetItemData( iItem ); m_pServicesPingSocket->SendTo( &pInfo->m_Addr, pData, len ); } } void UpdateItemText( CListCtrl &ctrl, int iItem, int iColumn, const char *pNewVal ) { CString str = ctrl.GetItemText( iItem, iColumn ); if ( V_stricmp( str, pNewVal ) != 0 ) ctrl.SetItemText( iItem, iColumn, pNewVal ); } void CServicesDlg::UpdateServiceInListbox( CServiceInfo *pInfo ) { // First, find this item in the listbox. LVFINDINFO info; info.flags = LVFI_PARAM; info.lParam = (LPARAM)pInfo; int iItem = m_ServicesList.FindItem( &info ); if ( iItem != -1 ) { UpdateItemText( m_ServicesList, iItem, COLUMN_COMPUTER_NAME, pInfo->m_ComputerName ); const char *pText = GetStatusString( pInfo ); UpdateItemText( m_ServicesList, iItem, COLUMN_STATUS, pText ); char timeStr[512]; FormatTimeString( pInfo->m_LiveTimeMS / 1000, timeStr, sizeof( timeStr ) ); UpdateItemText( m_ServicesList, iItem, COLUMN_RUNNING_TIME, timeStr ); FormatTimeString( pInfo->m_WorkerAppTimeMS / 1000, timeStr, sizeof( timeStr ) ); UpdateItemText( m_ServicesList, iItem, COLUMN_WORKER_APP_RUNNING_TIME, timeStr ); UpdateItemText( m_ServicesList, iItem, COLUMN_MASTER_NAME, pInfo->m_MasterName ); char str[512]; V_snprintf( str, sizeof( str ), "%d", pInfo->m_ProtocolVersion ); UpdateItemText( m_ServicesList, iItem, COLUMN_PROTOCOL_VERSION, str ); UpdateItemText( m_ServicesList, iItem, COLUMN_PASSWORD, pInfo->m_Password ); UpdateItemText( m_ServicesList, iItem, COLUMN_SERVICE_VERSION, pInfo->m_ServiceVersion ); if ( pInfo->m_CPUPercentage == -1 ) V_snprintf( str, sizeof( str ), "-" ); else if ( pInfo->m_CPUPercentage == 101 ) V_snprintf( str, sizeof( str ), "(err)" ); else V_snprintf( str, sizeof( str ), "%d%%", pInfo->m_CPUPercentage ); UpdateItemText( m_ServicesList, iItem, COLUMN_CPU_PERCENTAGE, str ); UpdateItemText( m_ServicesList, iItem, COLUMN_EXE_NAME, pInfo->m_ExeName ); UpdateItemText( m_ServicesList, iItem, COLUMN_MAP_NAME, pInfo->m_MapName ); if ( pInfo->m_MemUsageMB == -1 ) V_snprintf( str, sizeof( str ), "-" ); else V_snprintf( str, sizeof( str ), "%d", pInfo->m_MemUsageMB ); UpdateItemText( m_ServicesList, iItem, COLUMN_MEM_USAGE, str ); pInfo->m_pLastStatusText = pText; pInfo->m_LastLiveTimeMS = pInfo->m_LiveTimeMS; pInfo->m_LastMasterName = pInfo->m_MasterName; pInfo->m_LastUpdateTime = Plat_MSTime(); // Detect changes. if ( !m_bListChanged && iItem > 0 ) { CServiceInfo *pPrevItem = (CServiceInfo*)m_ServicesList.GetItemData( iItem-1 ); if ( pPrevItem && MainSortFn( (LPARAM)pPrevItem, (LPARAM)pInfo, NULL ) > 0 ) m_bListChanged = true; } if ( !m_bListChanged && (iItem+1) < m_ServicesList.GetItemCount() ) { CServiceInfo *pNextItem = (CServiceInfo*)m_ServicesList.GetItemData( iItem+1 ); if ( pNextItem && MainSortFn( (LPARAM)pInfo, (LPARAM)pNextItem, NULL ) > 0 ) m_bListChanged = true; } } } void CServicesDlg::ResortItems() { m_ServicesList.SortItems( MainSortFn, (LPARAM)this ); } void CServicesDlg::UpdateServiceCountDisplay() { char str[512]; Q_snprintf( str, sizeof( str ), "%d", m_Services.Count() ); m_NumServicesControl.SetWindowText( str ); // Now count the various types. int nDisabled = 0, nWorking = 0, nWaiting = 0, nOff = 0; FOR_EACH_LL( m_Services, i ) { if ( m_Services[i]->IsOff() ) { ++nOff; } else if ( m_Services[i]->m_iState == VMPI_STATE_BUSY || m_Services[i]->m_iState == VMPI_STATE_DOWNLOADING ) { ++nWorking; } else if ( m_Services[i]->m_iState == VMPI_STATE_IDLE ) { ++nWaiting; } else { ++nDisabled; } } Q_snprintf( str, sizeof( str ), "%d", nDisabled ); m_NumDisabledServicesControl.SetWindowText( str ); Q_snprintf( str, sizeof( str ), "%d", nWorking ); m_NumWorkingServicesControl.SetWindowText( str ); Q_snprintf( str, sizeof( str ), "%d", nWaiting ); m_NumWaitingServicesControl.SetWindowText( str ); Q_snprintf( str, sizeof( str ), "%d", nOff ); m_NumOffServicesControl.SetWindowText( str ); } // This monstrosity is here because of the way they bundle string resources into groups in an exe file. // See http://support.microsoft.com/kb/q196774/. bool FindStringResourceEx( HINSTANCE hinst, UINT uId, UINT langId, char *pStr, int outLen ) { // Convert the string ID into a bundle number bool bRet = false; HRSRC hrsrc = FindResourceEx(hinst, RT_STRING, MAKEINTRESOURCE(uId / 16 + 1), langId); if (hrsrc) { HGLOBAL hglob = LoadResource(hinst, hrsrc); if (hglob) { LPCWSTR pwsz = reinterpret_cast( LockResource(hglob) ); if (pwsz) { // okay now walk the string table for (UINT i = 0; i < (uId & 15); i++) { pwsz += 1 + (UINT)*pwsz; } // First word in the resource is the length and the rest is the data. int nChars = min( (int)pwsz[0], outLen-1 ); ++pwsz; V_wcstostr( pwsz, nChars, pStr, outLen ); pStr[nChars] = 0; bRet = true; } FreeResource(hglob); } } return bRet; } int CheckServiceVersion( const char *pPatchDir, char *pServiceVersion, int maxServiceVersionLen ) { char filename[MAX_PATH]; V_ComposeFileName( pPatchDir, "vmpi_service.exe", filename, sizeof( filename ) ); int ret = IDCANCEL; HINSTANCE hInst = LoadLibrary( filename ); if ( hInst ) { bool bFound = FindStringResourceEx( hInst, VMPI_SERVICE_IDS_VERSION_STRING, MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL), pServiceVersion, maxServiceVersionLen ); if ( bFound ) { ret = V_AfxMessageBox( MB_YESNOCANCEL, "Service version in %s is %s.\n\nIs this correct?", filename, pServiceVersion ); } else { V_AfxMessageBox( MB_OK, "Can't get IDS_VERSION_STRING resource from %s.", filename ); } FreeLibrary( hInst ); } else { V_AfxMessageBox( MB_OK, "Can't load %s to get service version.", filename ); } return ret; } void CServicesDlg::OnPatchServices() { // Inquire about the timeout. CPatchTimeout dlg; dlg.m_PatchDirectory = "\\\\fileserver\\vmpi\\testservice"; dlg.m_VMPITransferDirectory = dlg.m_PatchDirectory; dlg.m_bForcePatch = false; TryAgain:; if ( dlg.DoModal() == IDOK ) { // Launch the transfer app. char commandLine[32 * 1024] = {0}; char transferExe[MAX_PATH]; V_ComposeFileName( dlg.m_VMPITransferDirectory, "vmpi_transfer.exe", transferExe, sizeof( transferExe ) ); if ( _access( transferExe, 0 ) != 0 ) { V_AfxMessageBox( MB_OK, "Can't find '%s' to run the patch.", transferExe ); goto TryAgain; } char strServiceVersion[64]; int ret = CheckServiceVersion( dlg.m_PatchDirectory, strServiceVersion, sizeof( strServiceVersion ) ); if ( ret == IDCANCEL ) return; else if ( ret == IDNO ) goto TryAgain; AppendStr( commandLine, sizeof( commandLine ), "\"%s\" -PatchHost", transferExe ); AppendStr( commandLine, sizeof( commandLine ), " -mpi_PatchVersion %s", strServiceVersion ); AppendStr( commandLine, sizeof( commandLine ), " -mpi_PatchDirectory \"%s\"", (const char*)dlg.m_PatchDirectory ); if ( dlg.m_bForcePatch ) AppendStr( commandLine, sizeof( commandLine ), " -mpi_ForcePatch" ); // Collect the list of addresses. CUtlVector addrs; POSITION pos = m_ServicesList.GetFirstSelectedItemPosition(); while ( pos ) { int iItem = m_ServicesList.GetNextSelectedItem( pos ); CServiceInfo *pInfo = (CServiceInfo*)m_ServicesList.GetItemData( iItem ); if ( pInfo->m_Addr.ip[0] != 0 ) // "off" services won't have an IP addrs.AddToTail( pInfo->m_Addr ); } if ( addrs.Count() == 0 ) { AfxMessageBox( "No workers selected, or they all are off." ); return; } AppendStr( commandLine, sizeof( commandLine ), " -mpi_PatchWorkers %d", addrs.Count() ); for ( int i=0; i < addrs.Count(); i++ ) { AppendStr( commandLine, sizeof( commandLine ), " %d.%d.%d.%d", addrs[i].ip[0], addrs[i].ip[1], addrs[i].ip[2], addrs[i].ip[3] ); } STARTUPINFO si; memset( &si, 0, sizeof( si ) ); si.cb = sizeof( si ); PROCESS_INFORMATION pi; memset( &pi, 0, sizeof( pi ) ); if ( CreateProcess( NULL, commandLine, NULL, NULL, false, 0, NULL, (const char *)dlg.m_PatchDirectory, &si, &pi ) ) { CloseHandle( pi.hProcess ); CloseHandle( pi.hThread ); V_AfxMessageBox( MB_OK, "Patch master successfully started.\nServices patching now.\nClose the patch master console app when finished." ); } else { V_AfxMessageBox( MB_OK, "Error starting patch master: %s", GetLastErrorString() ); } } } void CServicesDlg::OnStopServices() { if ( MessageBox( "Warning: if you stop these services, you won't be able to control them from this application, and must restart them manually. Contine?", "Warning", MB_YESNO ) == IDYES ) { CUtlVector data; BuildVMPIPingPacket( data, VMPI_STOP_SERVICE ); SendToSelectedServices( data.Base(), data.Count() ); } } void CServicesDlg::OnStopJobs() { CUtlVector data; BuildVMPIPingPacket( data, VMPI_KILL_PROCESS ); SendToSelectedServices( data.Base(), data.Count() ); } void CServicesDlg::OnFilterByPassword() { CSetPasswordDlg dlg( IDD_SET_PASSWORD ); dlg.m_Password = m_Password; if ( dlg.DoModal() == IDOK ) { m_Password = dlg.m_Password; m_PasswordDisplay.SetWindowText( m_Password ); m_Services.PurgeAndDeleteElements(); m_ServicesList.DeleteAllItems(); UpdateServiceCountDisplay(); // Re-ping everyone immediately. m_dwLastServicesPing = GetTickCount() - SERVICES_PING_INTERVAL; } } // This sets a new password on the selected services. void CServicesDlg::OnForcePassword() { CSetPasswordDlg dlg( IDD_FORCE_PASSWORD ); dlg.m_Password = "password"; if ( dlg.DoModal() == IDOK ) { CUtlVector data; BuildVMPIPingPacket( data, VMPI_FORCE_PASSWORD_CHANGE, VMPI_PROTOCOL_VERSION, true ); const char *pNewPassword = dlg.m_Password; data.AddMultipleToTail( V_strlen( pNewPassword ) + 1, pNewPassword ); SendToSelectedServices( data.Base(), data.Count() ); } } void CServicesDlg::OnDblclkServicesList(NMHDR* pNMHDR, LRESULT* pResult) { POSITION pos = m_ServicesList.GetFirstSelectedItemPosition(); if ( pos ) { int iItem = m_ServicesList.GetNextSelectedItem( pos ); if ( iItem != -1 ) { CServiceInfo *pInfo = (CServiceInfo*)m_ServicesList.GetItemData( iItem ); if ( pInfo ) { // Launch vmpi_browser_job_search and have it auto-select this worker. char cmdLine[1024]; Q_snprintf( cmdLine, sizeof( cmdLine ), "vmpi_job_search -SelectWorker %s", (const char*)pInfo->m_ComputerName ); STARTUPINFO si; memset( &si, 0, sizeof( si ) ); si.cb = sizeof( si ); PROCESS_INFORMATION pi; memset( &pi, 0, sizeof( pi ) ); if ( !CreateProcess( NULL, (char*)(const char*)cmdLine, NULL, // security NULL, TRUE, 0, // flags NULL, // environment NULL, // current directory &si, &pi ) ) { char err[512]; Q_snprintf( err, sizeof( err ), "Can't run '%s'", (LPCTSTR)cmdLine ); MessageBox( err, "Error", MB_OK ); } } } } *pResult = 0; } void CServicesDlg::OnSize(UINT nType, int cx, int cy) { CIdleDialog::OnSize(nType, cx, cy); m_AnchorMgr.UpdateAnchors( this ); } BOOL CServicesDlg::OnNotify(WPARAM wParam, LPARAM lParam, LRESULT* pResult) { NMHDR *pHdr = (NMHDR*)lParam; if ( pHdr->idFrom == IDC_SERVICES_LIST ) { if ( pHdr->code == LVN_COLUMNCLICK ) { LPNMLISTVIEW pListView = (LPNMLISTVIEW)lParam; // Now sort by this column. int iSortColumn = max( 0, min( pListView->iSubItem, (int)ARRAYSIZE( g_ColumnInfos ) - 1 ) ); PushSortColumn( iSortColumn ); ResortItems(); } } return CIdleDialog::OnNotify(wParam, lParam, pResult); } void CServicesDlg::BuildClipboardText( CUtlVector &clipboardText ) { // Add the header information. CHeaderCtrl *pHeader = m_ServicesList.GetHeaderCtrl(); for ( int i=0; i < pHeader->GetItemCount(); i++ ) { char tempBuffer[512]; HDITEM item; memset( &item, 0, sizeof( item ) ); item.mask = HDI_TEXT; item.pszText = tempBuffer; item.cchTextMax = sizeof( tempBuffer ) - 1; if ( !pHeader->GetItem( i, &item ) ) item.pszText = ""; clipboardText.AddMultipleToTail( strlen( item.pszText ), item.pszText ); clipboardText.AddToTail( '\t' ); } clipboardText.AddMultipleToTail( 2, "\r\n" ); // Now add each line of data. int nItem = -1; while ( (nItem = m_ServicesList.GetNextItem( nItem, LVNI_ALL )) != -1 ) { char tempBuffer[512]; LVITEM item; memset( &item, 0, sizeof( item ) ); item.mask = LVIF_TEXT; item.iItem = nItem; item.pszText = tempBuffer; item.cchTextMax = sizeof( tempBuffer ) - 1; for ( int i=0; i < pHeader->GetItemCount(); i++ ) { item.iSubItem = i; if ( !m_ServicesList.GetItem( &item ) ) { item.pszText = ""; } clipboardText.AddMultipleToTail( strlen( item.pszText ), item.pszText ); clipboardText.AddToTail( '\t' ); } clipboardText.AddMultipleToTail( 2, "\r\n" ); } clipboardText.AddToTail( 0 ); } void CServicesDlg::OnCopyToClipboard() { // Open and clear the clipboard. if ( !OpenClipboard() ) return; EmptyClipboard(); // Setup the clipboard text. CUtlVector clipboardText; BuildClipboardText( clipboardText ); // Put the clipboard text into a global memory object. HANDLE hMem = GlobalAlloc( GMEM_MOVEABLE, clipboardText.Count() ); void *ptr = GlobalLock( hMem ); memcpy( ptr, clipboardText.Base(), clipboardText.Count() ); GlobalUnlock( hMem ); // Put it in the clipboard. SetClipboardData( CF_TEXT, hMem ); // Cleanup. GlobalFree( hMem ); CloseClipboard(); }