Team Fortress 2 Source Code as on 22/4/2020
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1710 lines
43 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. // $NoKeywords: $
  6. //
  7. //=============================================================================//
  8. // vmpi_service.cpp : Defines the entry point for the console application.
  9. //
  10. #include "stdafx.h"
  11. #include "vmpi.h"
  12. #include "iphelpers.h"
  13. #include "bitbuf.h"
  14. #include "tier1/strtools.h"
  15. #include "interface.h"
  16. #include "ilaunchabledll.h"
  17. #include "resource.h"
  18. #include "consolewnd.h"
  19. #include <io.h>
  20. #include "utllinkedlist.h"
  21. #include "service_helpers.h"
  22. #include "vmpi_filesystem.h"
  23. #include "service_conn_mgr.h"
  24. #include "resource.h"
  25. #include "perf_counters.h"
  26. #include "tier0/icommandline.h"
  27. // If we couldn't get into a job (maybe they weren't accepting more workers at the time),
  28. // then we wait this long and retry the connection.
  29. #define JOB_MEMORY_DURATION 60
  30. char g_VersionString[64]; // From the IDS_VERSION_STRING string.
  31. HKEY g_hVMPIServiceKey = NULL; // HKML/Software/Valve/VMPI
  32. double g_flLastKillProcessTime = 0;
  33. char *g_pPassword = NULL; // Set if this service is using a pw.
  34. ISocket *g_pSocket = NULL;
  35. int g_SocketPort = -1; // Which port we were able to bind the port on.
  36. char g_RunningProcess_ExeName[MAX_PATH] = {0};
  37. char g_RunningProcess_MapName[MAX_PATH] = {0};
  38. HANDLE g_hRunningProcess = NULL;
  39. HANDLE g_hRunningThread = NULL;
  40. DWORD g_dwRunningProcessId = 0;
  41. IPerfTracker *g_pPerfTracker = NULL; // Tracks CPU usage.
  42. // When this is true, it will launch new processes invisibly.
  43. bool g_bHideNewProcessWindows = true;
  44. HINSTANCE g_hInstance = NULL;
  45. int g_iBoundPort = -1;
  46. bool g_bScreensaverMode = false; // If this is true, then it'll act like the service is disabled while
  47. // a screensaver isn't running.
  48. // If this is on, it runs the exes out of c:/hl2/bin instead of the network. If the exes are built in debug,
  49. // this makes it possible to catch nasty crashes.
  50. bool g_bSuperDebugMode = false;
  51. bool g_bScreensaverRunning = false; // Updated each frame to tell if the screensaver is running.
  52. // GetTickCount() at the time the app was started.
  53. DWORD g_AppStartTime = 0;
  54. // GetTickCount() at the time the service ran a worker app.
  55. DWORD g_CreateProcessTime = 0;
  56. CIPAddr g_CurRespondAddr;
  57. int g_CurJobID[4];
  58. int g_CurJobPriority = -1; // VMPI priority of the currently-running job.
  59. // The directory we're running in.
  60. char g_BaseAppPath[MAX_PATH];
  61. char g_FileCachePath[MAX_PATH]; // [base app path]\vmpi_service_cache.
  62. // Different modes this app can run in.
  63. #define RUNMODE_INSTALL 0
  64. #define RUNMODE_CONSOLE 1
  65. #define RUNMODE_SERVICE 2
  66. int g_RunMode = RUNMODE_CONSOLE;
  67. bool g_bMinimized = false; // true if they run with -minimized.
  68. int g_iCurState = VMPI_SERVICE_STATE_IDLE;
  69. char g_CurMasterName[512] = {0};
  70. //////
  71. // This block of variables is setup while we wait for the downloader to finish.
  72. // When the downloading is complete, we launch the app using these variables.
  73. //////
  74. #ifdef _DEBUG
  75. #define MAX_DOWNLOADER_TIME_ALLOWED 300000 // If the downloader takes longer than this, kill it.
  76. #else
  77. #define MAX_DOWNLOADER_TIME_ALLOWED 30 // If the downloader takes longer than this, kill it.
  78. #endif
  79. // If this is non-NULL, then there is NOT a VMPI worker app running currently.. the downloader
  80. HANDLE g_Waiting_hProcess = NULL;
  81. float g_Waiting_StartTime = 0;
  82. CUtlVector<char*> g_Waiting_Argv;
  83. int g_Waiting_Priority = 0;
  84. bool g_Waiting_bShowAppWindow = false;
  85. bool g_Waiting_bPatching = 0; // If this is nonzero, then we're downloading so we can apply a patch.
  86. // Used to track the services browsers that have been talking to us lately.
  87. #define SERVICES_BROWSER_TIMEOUT 10 // We remove a services browser from the list if we don't hear from it for this long.
  88. class CServicesBrowserInfo
  89. {
  90. public:
  91. float m_flLastPingTime; // Last time they talked to us.
  92. CIPAddr m_Addr; // Their IP address.
  93. };
  94. CUtlVector<CServicesBrowserInfo> g_ServicesBrowsers;
  95. void HandlePacket_KILL_PROCESS( const CIPAddr *ipFrom );
  96. void KillRunningProcess( const char *pReason, bool bGoToIdle );
  97. void LoadStateFromRegistry();
  98. void SaveStateToRegistry();
  99. void SetPassword( const char *pPassword )
  100. {
  101. delete [] g_pPassword;
  102. if ( pPassword )
  103. {
  104. int len = V_strlen( pPassword ) + 1;
  105. g_pPassword = new char[len];
  106. V_strncpy( g_pPassword, pPassword, len );
  107. }
  108. else
  109. {
  110. g_pPassword = NULL;
  111. }
  112. }
  113. // ------------------------------------------------------------------------------------------ //
  114. // This handles connection to clients.
  115. // ------------------------------------------------------------------------------------------ //
  116. class CVMPIServiceConnMgr : public CServiceConnMgr
  117. {
  118. public:
  119. virtual void OnNewConnection( int id );
  120. virtual void HandlePacket( const char *pData, int len );
  121. void SendCurStateTo( int id );
  122. public:
  123. void AddConsoleOutput( const char *pMsg );
  124. void SetAppState( int iState );
  125. };
  126. void CVMPIServiceConnMgr::AddConsoleOutput( const char *pMsg )
  127. {
  128. // Tell clients of the new text string.
  129. CUtlVector<char> data;
  130. data.AddToTail( VMPI_SERVICE_UI_PROTOCOL_VERSION );
  131. data.AddToTail( VMPI_SERVICE_TO_UI_CONSOLE_TEXT );
  132. data.AddMultipleToTail( strlen( pMsg ) + 1, pMsg );
  133. SendPacket( -1, data.Base(), data.Count() );
  134. }
  135. void CVMPIServiceConnMgr::SetAppState( int iState )
  136. {
  137. // Update our state and send it to the clients.
  138. g_iCurState = iState;
  139. SendCurStateTo( -1 );
  140. }
  141. void CVMPIServiceConnMgr::OnNewConnection( int id )
  142. {
  143. // Send our current state to the new connection.
  144. Msg( "(debug) Made a new connection!\n" );
  145. SendCurStateTo( id );
  146. }
  147. void CVMPIServiceConnMgr::SendCurStateTo( int id )
  148. {
  149. CUtlVector<char> data;
  150. data.AddToTail( VMPI_SERVICE_UI_PROTOCOL_VERSION );
  151. data.AddToTail( VMPI_SERVICE_TO_UI_STATE );
  152. data.AddMultipleToTail( sizeof( g_iCurState ), (char*)&g_iCurState );
  153. data.AddToTail( (char)g_bScreensaverMode );
  154. if ( g_pPassword )
  155. data.AddMultipleToTail( strlen( g_pPassword ) + 1, g_pPassword );
  156. else
  157. data.AddToTail( 0 );
  158. SendPacket( -1, data.Base(), data.Count() );
  159. }
  160. void CVMPIServiceConnMgr::HandlePacket( const char *pData, int len )
  161. {
  162. switch( pData[0] )
  163. {
  164. case VMPI_KILL_PROCESS:
  165. {
  166. HandlePacket_KILL_PROCESS( NULL );
  167. }
  168. break;
  169. case VMPI_SERVICE_DISABLE:
  170. {
  171. KillRunningProcess( "Got a VMPI_SERVICE_DISABLE packet", true );
  172. SetAppState( VMPI_SERVICE_STATE_DISABLED );
  173. SaveStateToRegistry();
  174. }
  175. break;
  176. case VMPI_SERVICE_ENABLE:
  177. {
  178. if ( g_iCurState == VMPI_SERVICE_STATE_DISABLED )
  179. {
  180. SetAppState( VMPI_SERVICE_STATE_IDLE );
  181. }
  182. SaveStateToRegistry();
  183. }
  184. break;
  185. case VMPI_SERVICE_UPDATE_PASSWORD:
  186. {
  187. const char *pStr = pData + 1;
  188. SetPassword( pStr );
  189. // Send out the new state.
  190. SendCurStateTo( -1 );
  191. }
  192. break;
  193. case VMPI_SERVICE_SCREENSAVER_MODE:
  194. {
  195. g_bScreensaverMode = (pData[1] != 0);
  196. SendCurStateTo( -1 );
  197. SaveStateToRegistry();
  198. }
  199. break;
  200. case VMPI_SERVICE_EXIT:
  201. {
  202. Msg( "Got a VMPI_SERVICE_EXIT packet.\n ");
  203. ServiceHelpers_ExitEarly();
  204. }
  205. break;
  206. }
  207. }
  208. // This is allocated by the service thread and only used in there.
  209. CVMPIServiceConnMgr *g_pConnMgr = NULL;
  210. // ------------------------------------------------------------------------------------------ //
  211. // Persistent state stuff.
  212. // ------------------------------------------------------------------------------------------ //
  213. void LoadStateFromRegistry()
  214. {
  215. if ( g_hVMPIServiceKey )
  216. {
  217. DWORD val = 0;
  218. DWORD type = REG_DWORD;
  219. DWORD size = sizeof( val );
  220. if ( RegQueryValueEx(
  221. g_hVMPIServiceKey,
  222. "ScreensaverMode",
  223. 0,
  224. &type,
  225. (unsigned char*)&val,
  226. &size ) == ERROR_SUCCESS &&
  227. type == REG_DWORD &&
  228. size == sizeof( val ) )
  229. {
  230. g_bScreensaverMode = (val != 0);
  231. }
  232. if ( RegQueryValueEx(
  233. g_hVMPIServiceKey,
  234. "Disabled",
  235. 0,
  236. &type,
  237. (unsigned char*)&val,
  238. &size ) == ERROR_SUCCESS &&
  239. type == REG_DWORD &&
  240. size == sizeof( val ) &&
  241. val != 0 )
  242. {
  243. if ( g_pConnMgr )
  244. g_pConnMgr->SetAppState( VMPI_SERVICE_STATE_DISABLED );
  245. }
  246. }
  247. }
  248. void SaveStateToRegistry()
  249. {
  250. if ( g_hVMPIServiceKey )
  251. {
  252. DWORD val;
  253. val = g_bScreensaverMode;
  254. RegSetValueEx(
  255. g_hVMPIServiceKey,
  256. "ScreensaverMode",
  257. 0,
  258. REG_DWORD,
  259. (unsigned char*)&val,
  260. sizeof( val ) );
  261. val = (g_iCurState == VMPI_SERVICE_STATE_DISABLED);
  262. RegSetValueEx(
  263. g_hVMPIServiceKey,
  264. "Disabled",
  265. 0,
  266. REG_DWORD,
  267. (unsigned char*)&val,
  268. sizeof( val ) );
  269. }
  270. }
  271. // ------------------------------------------------------------------------------------------ //
  272. // Helper functions.
  273. // ------------------------------------------------------------------------------------------ //
  274. char* FindArg( int argc, char **argv, const char *pArgName, char *pDefaultValue="" )
  275. {
  276. for ( int i=0; i < argc; i++ )
  277. {
  278. if ( stricmp( argv[i], pArgName ) == 0 )
  279. {
  280. if ( (i+1) >= argc )
  281. return pDefaultValue;
  282. else
  283. return argv[i+1];
  284. }
  285. }
  286. return NULL;
  287. }
  288. SpewRetval_t MySpewOutputFunc( SpewType_t spewType, const char *pMsg )
  289. {
  290. // Put the message in status.txt.
  291. #ifdef VMPI_SERVICE_LOGS
  292. static FILE *fp = fopen( "c:\\vmpi_service.log", "wt" );
  293. if ( fp )
  294. {
  295. fprintf( fp, "%s", pMsg );
  296. fflush( fp );
  297. }
  298. #endif
  299. // Print it to the console.
  300. if ( g_pConnMgr )
  301. g_pConnMgr->AddConsoleOutput( pMsg );
  302. // Output to the debug console.
  303. OutputDebugString( pMsg );
  304. if ( spewType == SPEW_ASSERT )
  305. return SPEW_DEBUGGER;
  306. else if( spewType == SPEW_ERROR )
  307. return SPEW_ABORT;
  308. else
  309. return SPEW_CONTINUE;
  310. }
  311. char* CopyString( const char *pStr )
  312. {
  313. int len = V_strlen( pStr ) + 1;
  314. char *pRet = new char[len];
  315. V_strncpy( pRet, pStr, len );
  316. return pRet;
  317. }
  318. void AppendArg( CUtlVector<char*> &newArgv, const char *pIn )
  319. {
  320. newArgv.AddToTail( CopyString( pIn ) );
  321. }
  322. void SendStartStatus( bool bStatus )
  323. {
  324. for ( int i=0; i < 3; i++ )
  325. {
  326. char data[4096];
  327. bf_write dataBuf( data, sizeof( data ) );
  328. dataBuf.WriteByte( VMPI_PROTOCOL_VERSION );
  329. dataBuf.WriteByte( VMPI_NOTIFY_START_STATUS );
  330. dataBuf.WriteBytes( g_CurJobID, sizeof( g_CurJobID ) );
  331. dataBuf.WriteByte( bStatus );
  332. g_pSocket->SendTo( &g_CurRespondAddr, data, dataBuf.GetNumBytesWritten() );
  333. Sleep( 50 );
  334. }
  335. }
  336. void SendEndStatus()
  337. {
  338. for ( int i=0; i < 3; i++ )
  339. {
  340. char data[4096];
  341. bf_write dataBuf( data, sizeof( data ) );
  342. dataBuf.WriteByte( VMPI_PROTOCOL_VERSION );
  343. dataBuf.WriteByte( VMPI_NOTIFY_END_STATUS );
  344. dataBuf.WriteBytes( g_CurJobID, sizeof( g_CurJobID ) );
  345. g_pSocket->SendTo( &g_CurRespondAddr, data, dataBuf.GetNumBytesWritten() );
  346. Sleep( 50 );
  347. }
  348. }
  349. void KillRunningProcess( const char *pReason, bool bGoToIdle )
  350. {
  351. // Kill the downloader if it's running.
  352. if ( g_Waiting_hProcess )
  353. {
  354. TerminateProcess( g_Waiting_hProcess, 1 );
  355. CloseHandle( g_Waiting_hProcess );
  356. g_Waiting_hProcess = NULL;
  357. }
  358. if ( !g_hRunningProcess )
  359. return;
  360. if ( pReason )
  361. Msg( pReason );
  362. SendEndStatus();
  363. TerminateProcess( g_hRunningProcess, 1 );
  364. g_RunningProcess_ExeName[0] = 0;
  365. g_RunningProcess_MapName[0] = 0;
  366. // Yep. Now we can start a new one.
  367. CloseHandle( g_hRunningThread );
  368. g_hRunningThread = NULL;
  369. CloseHandle( g_hRunningProcess );
  370. g_hRunningProcess = NULL;
  371. g_CurJobPriority = -1;
  372. if ( bGoToIdle )
  373. if ( g_pConnMgr )
  374. g_pConnMgr->SetAppState( VMPI_SERVICE_STATE_IDLE );
  375. }
  376. // ------------------------------------------------------------------------------------------ //
  377. // Job memory stuff.
  378. // ------------------------------------------------------------------------------------------ //
  379. // CJobMemory is used to track which jobs we ran (or tried to run).
  380. // We remember which jobs we did because Winsock likes to queue up the job packets on
  381. // our socket, so if we don't remember which jobs we ran, we'd run the job a bunch of times.
  382. class CJobMemory
  383. {
  384. public:
  385. int m_ID[4]; // Random ID that comes from the server.
  386. float m_Time;
  387. };
  388. CUtlLinkedList<CJobMemory, int> g_JobMemories;
  389. bool FindJobMemory( int id[4] )
  390. {
  391. int iNext;
  392. for ( int i=g_JobMemories.Head(); i != g_JobMemories.InvalidIndex(); i=iNext )
  393. {
  394. iNext = g_JobMemories.Next( i );
  395. CJobMemory *pJob = &g_JobMemories[i];
  396. if ( memcmp( pJob->m_ID, id, sizeof( pJob->m_ID ) ) == 0 )
  397. return true;
  398. }
  399. return false;
  400. }
  401. void TimeoutJobIDs()
  402. {
  403. double flCurTime = Plat_FloatTime();
  404. int iNext;
  405. for ( int i=g_JobMemories.Head(); i != g_JobMemories.InvalidIndex(); i=iNext )
  406. {
  407. iNext = g_JobMemories.Next( i );
  408. if ( (flCurTime - g_JobMemories[i].m_Time) > JOB_MEMORY_DURATION )
  409. g_JobMemories.Remove( i );
  410. }
  411. }
  412. void AddJobMemory( int id[4] )
  413. {
  414. TimeoutJobIDs();
  415. CJobMemory job;
  416. memcpy( job.m_ID, id, sizeof( job.m_ID ) );
  417. job.m_Time = Plat_FloatTime();
  418. g_JobMemories.AddToTail( job );
  419. }
  420. bool CheckJobID( bf_read &buf, int jobID[4] )
  421. {
  422. TimeoutJobIDs();
  423. jobID[0] = buf.ReadLong();
  424. jobID[1] = buf.ReadLong();
  425. jobID[2] = buf.ReadLong();
  426. jobID[3] = buf.ReadLong();
  427. if ( FindJobMemory( jobID ) || buf.IsOverflowed() )
  428. {
  429. return false;
  430. }
  431. return true;
  432. }
  433. // ------------------------------------------------------------------------------------------ //
  434. // The main VMPI code.
  435. // ------------------------------------------------------------------------------------------ //
  436. void VMPI_Waiter_Term()
  437. {
  438. KillRunningProcess( NULL, false );
  439. if ( g_pConnMgr )
  440. {
  441. g_pConnMgr->Term();
  442. delete g_pConnMgr;
  443. g_pConnMgr = NULL;
  444. }
  445. if ( g_pSocket )
  446. {
  447. g_pSocket->Release();
  448. g_pSocket = NULL;
  449. }
  450. g_pPerfTracker->Release();
  451. g_pPerfTracker = NULL;
  452. }
  453. bool VMPI_Waiter_Init()
  454. {
  455. // Run as idle priority.
  456. HKEY hKey = NULL;
  457. RegCreateKey( HKEY_LOCAL_MACHINE, VMPI_SERVICE_KEY, &hKey );
  458. DWORD dwVal = 0;
  459. DWORD dummyType = REG_DWORD;
  460. DWORD dwValLen = sizeof( dwVal );
  461. if ( RegQueryValueEx( hKey, "LowPriority", NULL, &dummyType, (LPBYTE)&dwVal, &dwValLen ) == ERROR_SUCCESS )
  462. {
  463. if ( dwVal )
  464. {
  465. SetPriorityClass( GetCurrentProcess(), IDLE_PRIORITY_CLASS );
  466. }
  467. }
  468. else
  469. {
  470. RegSetValueEx( hKey, "LowPriority", 0, REG_DWORD, (unsigned char*)&dwVal, sizeof( dwVal ) );
  471. }
  472. g_pConnMgr = new CVMPIServiceConnMgr;
  473. if ( !g_pConnMgr->InitServer() )
  474. Msg( "ERROR INITIALIZING CONNMGR\n" );
  475. g_pSocket = CreateIPSocket();
  476. if ( !g_pSocket )
  477. {
  478. Msg( "Error creating a socket.\n" );
  479. return false;
  480. }
  481. // Bind to the first port we find in the range [VMPI_SERVICE_PORT, VMPI_LAST_SERVICE_PORT].
  482. int iTest;
  483. for ( iTest=VMPI_SERVICE_PORT; iTest <= VMPI_LAST_SERVICE_PORT; iTest++ )
  484. {
  485. g_SocketPort = iTest;
  486. if ( g_pSocket->BindToAny( iTest ) )
  487. break;
  488. }
  489. if ( iTest == VMPI_LAST_SERVICE_PORT )
  490. {
  491. Msg( "Error binding a socket to port %d.\n", VMPI_SERVICE_PORT );
  492. VMPI_Waiter_Term();
  493. return false;
  494. }
  495. g_iBoundPort = iTest;
  496. g_pPerfTracker = CreatePerfTracker();
  497. return true;
  498. }
  499. void RunInDLL( const char *pFilename, CUtlVector<char*> &newArgv )
  500. {
  501. if ( g_pConnMgr )
  502. g_pConnMgr->SetAppState( VMPI_SERVICE_STATE_BUSY );
  503. bool bSuccess = false;
  504. CSysModule *pModule = Sys_LoadModule( pFilename );
  505. if ( pModule )
  506. {
  507. CreateInterfaceFn fn = Sys_GetFactory( pModule );
  508. if ( fn )
  509. {
  510. ILaunchableDLL *pDLL = (ILaunchableDLL*)fn( LAUNCHABLE_DLL_INTERFACE_VERSION, NULL );
  511. if( pDLL )
  512. {
  513. // Do this here because the executables we would have launched usually would do it.
  514. CommandLine()->CreateCmdLine( newArgv.Count(), newArgv.Base() );
  515. pDLL->main( newArgv.Count(), newArgv.Base() );
  516. bSuccess = true;
  517. SpewOutputFunc( MySpewOutputFunc );
  518. }
  519. }
  520. Sys_UnloadModule( pModule );
  521. }
  522. if ( !bSuccess )
  523. {
  524. Msg( "Error running VRAD (or VVIS) out of DLL '%s'\n", pFilename );
  525. }
  526. if ( g_pConnMgr )
  527. g_pConnMgr->SetAppState( VMPI_SERVICE_STATE_IDLE );
  528. }
  529. void GetArgsFromBuffer(
  530. bf_read &buf,
  531. CUtlVector<char*> &newArgv,
  532. bool *bShowAppWindow )
  533. {
  534. int nArgs = buf.ReadWord();
  535. bool bSpewArgs = false;
  536. for ( int iArg=0; iArg < nArgs; iArg++ )
  537. {
  538. char argStr[512];
  539. buf.ReadString( argStr, sizeof( argStr ) );
  540. AppendArg( newArgv, argStr );
  541. if ( stricmp( argStr, "-mpi_verbose" ) == 0 )
  542. bSpewArgs = true;
  543. if ( stricmp( argStr, "-mpi_ShowAppWindow" ) == 0 )
  544. *bShowAppWindow = true;
  545. }
  546. if ( bSpewArgs )
  547. {
  548. Msg( "nArgs: %d\n", newArgv.Count() );
  549. for ( int i=0; i < newArgv.Count(); i++ )
  550. Msg( "Arg %d: %s\n", i, newArgv[i] );
  551. }
  552. }
  553. bool GetDLLFilename( CUtlVector<char*> &newArgv, char pDLLFilename[MAX_PATH] )
  554. {
  555. char *argStr = newArgv[0];
  556. int argLen = strlen( argStr );
  557. if ( argLen <= 4 )
  558. return false;
  559. if ( Q_stricmp( &argStr[argLen-4], ".exe" ) != 0 )
  560. return false;
  561. char baseFilename[MAX_PATH];
  562. Q_strncpy( baseFilename, argStr, MAX_PATH );
  563. baseFilename[ min( MAX_PATH-1, argLen-4 ) ] = 0;
  564. // First try _dll.dll (src_main), then try .dll (rel).
  565. V_snprintf( pDLLFilename, MAX_PATH, "%s_dll.dll", baseFilename );
  566. if ( _access( pDLLFilename, 0 ) != 0 )
  567. {
  568. V_snprintf( pDLLFilename, MAX_PATH, "%s.dll", baseFilename );
  569. }
  570. return true;
  571. }
  572. void BuildCommandLineFromArgs( CUtlVector<char*> &newArgv, char *pOut, int outLen )
  573. {
  574. pOut[0] = 0;
  575. for ( int i=0; i < newArgv.Count(); i++ )
  576. {
  577. char argStr[512];
  578. if ( strlen( newArgv[i] ) > 0 && newArgv[i][strlen(newArgv[i])-1] == '\\' )
  579. Q_snprintf( argStr, sizeof( argStr ), "\"%s\\\" ", newArgv[i] );
  580. else
  581. Q_snprintf( argStr, sizeof( argStr ), "\"%s\" ", newArgv[i] );
  582. Q_strncat( pOut, argStr, outLen, COPY_ALL_CHARACTERS );
  583. }
  584. }
  585. bool RunProcessFromArgs( CUtlVector<char*> &newArgv, bool bShowAppWindow, bool bCreateSuspended, const char *pWorkingDir, PROCESS_INFORMATION *pOut )
  586. {
  587. char commandLine[2048];
  588. BuildCommandLineFromArgs( newArgv, commandLine, sizeof( commandLine ) );
  589. Msg( "Running '%s'\n", commandLine );
  590. STARTUPINFO si;
  591. memset( &si, 0, sizeof( si ) );
  592. si.cb = sizeof( si );
  593. memset( pOut, 0, sizeof( *pOut ) );
  594. DWORD dwFlags = 0;//IDLE_PRIORITY_CLASS;
  595. if ( bShowAppWindow )
  596. dwFlags |= CREATE_NEW_CONSOLE;
  597. else
  598. dwFlags |= CREATE_NO_WINDOW;
  599. if ( bCreateSuspended )
  600. dwFlags |= CREATE_SUSPENDED;
  601. UINT oldMode = SetErrorMode( SEM_NOOPENFILEERRORBOX | SEM_FAILCRITICALERRORS );
  602. BOOL bRet = CreateProcess(
  603. NULL,
  604. commandLine,
  605. NULL, // security
  606. NULL,
  607. TRUE,
  608. dwFlags | IDLE_PRIORITY_CLASS, // flags
  609. NULL, // environment
  610. pWorkingDir,
  611. &si,
  612. pOut );
  613. SetErrorMode( oldMode );
  614. return (bRet != FALSE);
  615. }
  616. void RunProcessAtCommandLine(
  617. CUtlVector<char*> &newArgv,
  618. bool bShowAppWindow,
  619. bool bCreateSuspended,
  620. int iPriority )
  621. {
  622. // current directory (use c:\\ because we don't want it to accidentally share
  623. // DLLs like vstdlib with us). PROCESS_INFORMATION pi;
  624. PROCESS_INFORMATION pi;
  625. if ( RunProcessFromArgs( newArgv, bShowAppWindow, bCreateSuspended, g_FileCachePath, &pi ) )
  626. {
  627. if ( g_pConnMgr )
  628. g_pConnMgr->SetAppState( VMPI_SERVICE_STATE_BUSY );
  629. if ( newArgv.Count() > 0 && newArgv[0] )
  630. {
  631. V_FileBase( newArgv[0], g_RunningProcess_ExeName, sizeof( g_RunningProcess_ExeName ) );
  632. if ( V_stricmp( g_RunningProcess_ExeName, "vrad" ) == 0 || V_stricmp( g_RunningProcess_ExeName, "vvis" ) == 0 )
  633. V_FileBase( newArgv[newArgv.Count()-1], g_RunningProcess_MapName, sizeof( g_RunningProcess_MapName ) );
  634. }
  635. g_hRunningProcess = pi.hProcess;
  636. g_hRunningThread = pi.hThread;
  637. g_dwRunningProcessId = pi.dwProcessId;
  638. g_pPerfTracker->Init( g_dwRunningProcessId );
  639. g_CurJobPriority = iPriority;
  640. g_CreateProcessTime = GetTickCount();
  641. SendStartStatus( true );
  642. }
  643. else
  644. {
  645. Msg( " - ERROR in CreateProcess (%s)!\n", GetLastErrorString() );
  646. SendStartStatus( false );
  647. g_CurJobPriority = -1;
  648. g_RunningProcess_ExeName[0] = 0;
  649. g_RunningProcess_MapName[0] = 0;
  650. }
  651. }
  652. bool WaitForProcessToExit()
  653. {
  654. if ( g_hRunningProcess )
  655. {
  656. // Did the process complete yet?
  657. if ( WaitForSingleObject( g_hRunningProcess, 0 ) == WAIT_TIMEOUT )
  658. {
  659. // Nope.. keep waiting.
  660. return true;
  661. }
  662. else
  663. {
  664. Msg( "Finished!\n ");
  665. SendEndStatus();
  666. // Change back to the 'waiting' icon.
  667. if ( g_pConnMgr )
  668. g_pConnMgr->SetAppState( VMPI_SERVICE_STATE_IDLE );
  669. g_CurJobPriority = -1;
  670. // Yep. Now we can start a new one.
  671. CloseHandle( g_hRunningThread );
  672. CloseHandle( g_hRunningProcess );
  673. g_hRunningProcess = g_hRunningThread = NULL;
  674. g_RunningProcess_ExeName[0] = g_RunningProcess_MapName[0] = 0;
  675. }
  676. }
  677. return false;
  678. }
  679. void HandleWindowMessages()
  680. {
  681. MSG msg;
  682. while ( PeekMessage( &msg, NULL, 0, 0, PM_REMOVE ) )
  683. {
  684. TranslateMessage( &msg );
  685. DispatchMessage( &msg );
  686. }
  687. }
  688. void GetRunningProcessStats( int &processorPercentage, int &memoryUsageMegabytes )
  689. {
  690. static int lastProcessorPercentage = 0;
  691. static int lastMemory = 0;
  692. if ( g_hRunningProcess )
  693. {
  694. // Only update this every couple seconds. It's not too expensive (about 800 microseconds), but we don't
  695. // need to do it a whole lot.
  696. static DWORD lastReturnTime = GetTickCount();
  697. DWORD curTime = GetTickCount();
  698. if ( (curTime - lastReturnTime) >= 1000 )
  699. {
  700. lastReturnTime = curTime;
  701. g_pPerfTracker->GetPerfData( lastProcessorPercentage, lastMemory );
  702. }
  703. }
  704. else
  705. {
  706. lastProcessorPercentage = lastMemory = 0;
  707. }
  708. processorPercentage = lastProcessorPercentage;
  709. memoryUsageMegabytes = lastMemory;
  710. }
  711. void BuildPingHeader( CUtlVector<char> &data, char packetID, int iState )
  712. {
  713. // Figure out the computer's name.
  714. char computerName[128];
  715. DWORD computerNameLen = sizeof( computerName );
  716. GetComputerName( computerName, &computerNameLen );
  717. // Ping back at them.
  718. data.AddToTail( VMPI_PROTOCOL_VERSION );
  719. data.AddToTail( packetID );
  720. data.AddToTail( (char)iState );
  721. DWORD liveTime = GetTickCount() - g_AppStartTime;
  722. data.AddMultipleToTail( sizeof( liveTime ), (char*)&liveTime );
  723. data.AddMultipleToTail( sizeof( g_SocketPort ), (char*)&g_SocketPort );
  724. data.AddMultipleToTail( strlen( computerName ) + 1, computerName );
  725. if ( g_hRunningProcess )
  726. data.AddMultipleToTail( strlen( g_CurMasterName ) + 1, g_CurMasterName );
  727. else
  728. data.AddMultipleToTail( 1, "" );
  729. // Write in how long the worker app has been running.
  730. DWORD appRunTime = 0;
  731. if ( g_hRunningProcess )
  732. appRunTime = GetTickCount() - g_CreateProcessTime;
  733. data.AddMultipleToTail( sizeof( appRunTime ), (char*)&appRunTime );
  734. // Finally, write the password.
  735. if ( g_pPassword )
  736. data.AddMultipleToTail( strlen( g_pPassword ) + 1, g_pPassword );
  737. else
  738. data.AddToTail( 0 );
  739. data.AddMultipleToTail( V_strlen( g_VersionString ) + 1, g_VersionString );
  740. int processorPercentage, memoryUsageMegabytes;
  741. GetRunningProcessStats( processorPercentage, memoryUsageMegabytes );
  742. // Write processor percentage.
  743. data.AddToTail( (char)processorPercentage );
  744. // Write the EXE name.
  745. data.AddMultipleToTail( V_strlen( g_RunningProcess_ExeName ) + 1, g_RunningProcess_ExeName );
  746. // Write memory usage.
  747. short memUsageShort = (short)memoryUsageMegabytes;
  748. data.AddMultipleToTail( sizeof( memUsageShort ), (const char*)&memUsageShort );
  749. // Write the map name.
  750. data.AddMultipleToTail( V_strlen( g_RunningProcess_MapName ) + 1, g_RunningProcess_MapName );
  751. }
  752. // This tracks a list
  753. void AddServicesBrowserIP( const CIPAddr &ipFrom )
  754. {
  755. for ( int i=0; i < g_ServicesBrowsers.Count(); i++ )
  756. {
  757. if ( g_ServicesBrowsers[i].m_Addr == ipFrom )
  758. {
  759. g_ServicesBrowsers[i].m_flLastPingTime = Plat_FloatTime();
  760. return;
  761. }
  762. }
  763. CServicesBrowserInfo info;
  764. info.m_Addr = ipFrom;
  765. info.m_flLastPingTime = Plat_FloatTime();
  766. g_ServicesBrowsers.AddToTail( info );
  767. }
  768. void UpdateServicesBrowserIPs()
  769. {
  770. double curTime = Plat_FloatTime();
  771. for ( int i=0; i < g_ServicesBrowsers.Count(); i++ )
  772. {
  773. if ( (curTime - g_ServicesBrowsers[i].m_flLastPingTime) >= SERVICES_BROWSER_TIMEOUT )
  774. {
  775. g_ServicesBrowsers.Remove( i );
  776. --i;
  777. break;
  778. }
  779. }
  780. }
  781. void SendStateToServicesBrowsers()
  782. {
  783. int curState;
  784. if ( g_hRunningProcess )
  785. {
  786. if ( g_Waiting_bPatching )
  787. curState = VMPI_STATE_PATCHING;
  788. else
  789. curState = VMPI_STATE_BUSY;
  790. }
  791. else if ( g_Waiting_hProcess )
  792. {
  793. if ( g_Waiting_bPatching )
  794. curState = VMPI_STATE_PATCHING;
  795. else
  796. curState = VMPI_STATE_DOWNLOADING;
  797. }
  798. else if ( g_iCurState == VMPI_SERVICE_STATE_DISABLED )
  799. {
  800. curState = VMPI_STATE_DISABLED;
  801. }
  802. else if ( g_bScreensaverMode && !g_bScreensaverRunning )
  803. {
  804. curState = VMPI_STATE_SCREENSAVER_DISABLED;
  805. }
  806. else
  807. {
  808. curState = VMPI_STATE_IDLE;
  809. }
  810. CUtlVector<char> data;
  811. BuildPingHeader( data, VMPI_PING_RESPONSE, curState );
  812. for ( int i=0; i < g_ServicesBrowsers.Count(); i++ )
  813. {
  814. g_pSocket->SendTo( &g_ServicesBrowsers[i].m_Addr, data.Base(), data.Count() );
  815. }
  816. }
  817. void StopUI()
  818. {
  819. char cPacket[2] = {VMPI_SERVICE_UI_PROTOCOL_VERSION, VMPI_SERVICE_TO_UI_EXIT};
  820. if ( g_pConnMgr )
  821. g_pConnMgr->SendPacket( -1, &cPacket, sizeof( cPacket ) );
  822. // Wait for a bit for the connection to go away.
  823. DWORD startTime = GetTickCount();
  824. while ( GetTickCount()-startTime < 2000 )
  825. {
  826. if ( g_pConnMgr )
  827. {
  828. g_pConnMgr->Update();
  829. if ( !g_pConnMgr->IsConnected() )
  830. break;
  831. else
  832. Sleep( 10 );
  833. }
  834. }
  835. }
  836. void CheckScreensaverRunning()
  837. {
  838. // We want to let patching finish even if we're in screensaver mode.
  839. if ( g_Waiting_hProcess && g_Waiting_bPatching )
  840. return;
  841. BOOL bRunning = false;
  842. SystemParametersInfo( SPI_GETSCREENSAVERRUNNING, 0, &bRunning, 0 );
  843. g_bScreensaverRunning = (bRunning != 0);
  844. if ( !g_bScreensaverRunning && g_bScreensaverMode )
  845. {
  846. KillRunningProcess( "Screensaver not running", true );
  847. }
  848. }
  849. void AdjustSuperDebugArgs( CUtlVector<char*> &args )
  850. {
  851. // Get the directory this exe was run out of.
  852. char filename[512];
  853. if ( GetModuleFileName( GetModuleHandle( NULL ), filename, sizeof( filename ) ) == 0 )
  854. return;
  855. char *pLastSlash = filename;
  856. char *pCurPos = filename;
  857. while ( *pCurPos )
  858. {
  859. if ( *pCurPos == '/' || *pCurPos == '\\' )
  860. pLastSlash = pCurPos;
  861. ++pCurPos;
  862. }
  863. *pLastSlash = 0;
  864. // In superdebug mode, run it out of c:/hl2/bin.
  865. const char *pBase = args[0];
  866. const char *pBaseCur = pBase;
  867. while ( *pBaseCur )
  868. {
  869. if ( *pBaseCur == '/' || *pBaseCur == '\\' || *pBaseCur == ':' )
  870. {
  871. pBase = pBaseCur+1;
  872. pBaseCur = pBase;
  873. }
  874. ++pBaseCur;
  875. }
  876. int maxLen = 64 + strlen( pBase ) + 1;
  877. char *pNewFilename = new char[maxLen];
  878. _snprintf( pNewFilename, maxLen, "%s\\%s", filename, pBase );
  879. delete args[0];
  880. args[0] = pNewFilename;
  881. // Now insert -allowdebug.
  882. const char *pAllowDebug = "-allowdebug";
  883. char *pToInsert = new char[ strlen( pAllowDebug ) + 1 ];
  884. strcpy( pToInsert, pAllowDebug );
  885. args.InsertAfter( 0, pToInsert );
  886. }
  887. // -------------------------------------------------------------------------------- //
  888. // Purpose: Launches vmpi_transfer.exe to download the required
  889. // files from the master so we can launch.
  890. //
  891. // If successful, it sets hProcess to the process handle of the downloader.
  892. // When that process terminates, we look for [cache dir]\ReadyToGo.txt and if it's
  893. // there, then we start the job.
  894. // -------------------------------------------------------------------------------- //
  895. bool StartDownloadingAppFiles(
  896. CUtlVector<char*> &newArgv,
  897. char *cacheDir,
  898. int cacheDirLen,
  899. bool bShowAppWindow,
  900. HANDLE *hProcess,
  901. bool bPatching )
  902. {
  903. *hProcess = NULL;
  904. V_strncpy( cacheDir, g_FileCachePath, cacheDirLen );
  905. // For now, cache dir is always the same. It's [current directory]\cache.
  906. if ( _access( cacheDir, 0 ) != 0 )
  907. {
  908. if ( !CreateDirectory( cacheDir, NULL ) && GetLastError() != ERROR_ALREADY_EXISTS )
  909. {
  910. Warning( "Unable to create cache directory: %s.\n", cacheDir );
  911. return false;
  912. }
  913. }
  914. // Clear all the files in the directory.
  915. char searchStr[MAX_PATH];
  916. V_ComposeFileName( cacheDir, "*.*", searchStr, sizeof( searchStr ) );
  917. _finddata_t findData;
  918. intptr_t ret = _findfirst( searchStr, &findData );
  919. if ( ret != -1 )
  920. {
  921. do
  922. {
  923. if ( findData.name[0] == '.' )
  924. continue;
  925. char fullFilename[MAX_PATH];
  926. V_ComposeFileName( cacheDir, findData.name, fullFilename, sizeof( fullFilename ) );
  927. if ( _unlink( fullFilename ) != 0 )
  928. {
  929. Warning( "_unlink( %s ) failed.\n", fullFilename );
  930. return false;
  931. }
  932. } while ( _findnext( ret, &findData ) == 0 );
  933. _findclose( ret );
  934. }
  935. // Change the EXE name to an absolute path to exactly where it is in the cache directory.
  936. int maxExeNameLen = 1024;
  937. char *pExeName = new char[maxExeNameLen];
  938. if ( bPatching )
  939. {
  940. V_ComposeFileName( cacheDir, "vmpi_service_install.exe", pExeName, maxExeNameLen );
  941. // Add args for the installer.
  942. newArgv.InsertAfter( 0, CopyString( "-DontTouchUI" ) );
  943. // When patching, we can't start the UI and the installer can't because we're running in the local system account
  944. // 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
  945. // to run <cacheDir>\WaitAndRestart and restart itself in N seconds.
  946. newArgv.InsertAfter( 0, CopyString( "-Install_Quiet" ) );
  947. }
  948. else
  949. {
  950. V_ComposeFileName( cacheDir, newArgv[0], pExeName, maxExeNameLen );
  951. }
  952. delete newArgv[0];
  953. newArgv[0] = pExeName;
  954. char fullExeFilename[MAX_PATH];
  955. V_ComposeFileName( g_BaseAppPath, "vmpi_transfer.exe", fullExeFilename, sizeof( fullExeFilename ) );
  956. CUtlVector<char*> downloaderArgs;
  957. downloaderArgs.AddToTail( fullExeFilename );
  958. #if defined( _DEBUG )
  959. downloaderArgs.AddToTail( "-allowdebug" );
  960. #endif
  961. downloaderArgs.AddToTail( "-CachePath" ); // Tell it where to download the files to.
  962. downloaderArgs.AddToTail( cacheDir );
  963. // Pass all the -mpi_worker, -mpi_file, -mpi_filebase args into the downloader app.
  964. for ( int i=1; i < (int)newArgv.Count()-1; i++ )
  965. {
  966. if ( V_stricmp( newArgv[i], "-mpi_filebase" ) == 0 || V_stricmp( newArgv[i], "-mpi_file" ) == 0 )
  967. {
  968. downloaderArgs.AddToTail( newArgv[i] );
  969. downloaderArgs.AddToTail( newArgv[i+1] );
  970. newArgv.Remove( i );
  971. newArgv.Remove( i );
  972. --i;
  973. }
  974. else if ( V_stricmp( newArgv[i], "-mpi_worker" ) == 0 )
  975. {
  976. // We need this arg so it knows what IP to connect to, but we want to leave it in the final launch args too.
  977. downloaderArgs.AddToTail( newArgv[i] );
  978. downloaderArgs.AddToTail( newArgv[i+1] );
  979. ++i;
  980. }
  981. }
  982. // Transfer each file.
  983. PROCESS_INFORMATION pi;
  984. if ( !RunProcessFromArgs( downloaderArgs, bShowAppWindow, false, g_BaseAppPath, &pi ) )
  985. return false;
  986. *hProcess = pi.hProcess;
  987. return true;
  988. }
  989. void SendPatchCommandToUIs( DWORD dwInstallerProcessId )
  990. {
  991. Msg( "SendPatchCommandToUIs\n ");
  992. CUtlVector<char> data;
  993. data.AddToTail( VMPI_SERVICE_UI_PROTOCOL_VERSION );
  994. data.AddToTail( VMPI_SERVICE_TO_UI_PATCHING );
  995. // This arg tells the UI whether to exit after running the command or not.
  996. data.AddToTail( 1 );
  997. // First argument is the working directory, which is the cache path in this case.
  998. data.AddMultipleToTail( V_strlen( g_FileCachePath ) + 1, g_FileCachePath );
  999. // Second argument is the command line.
  1000. char waitAndRestartExe[MAX_PATH], serviceUIExe[MAX_PATH], commandLine[1024 * 8];
  1001. V_ComposeFileName( g_FileCachePath, "WaitAndRestart.exe", waitAndRestartExe, sizeof( waitAndRestartExe ) );
  1002. V_ComposeFileName( g_BaseAppPath, "vmpi_service_ui.exe", serviceUIExe, sizeof( serviceUIExe ) ); // We're running the UI from the same directory this exe is in.
  1003. char strSeconds[64];
  1004. V_snprintf( strSeconds, sizeof( strSeconds ), "*%lu", dwInstallerProcessId );
  1005. // IMPORTANT to use BuildCommandLineFromArgs here because it'll handle slashes and quotes correctly.
  1006. // If we don't do that, the command often won't work.
  1007. CUtlVector<char*> args;
  1008. args.AddToTail( waitAndRestartExe );
  1009. args.AddToTail( strSeconds );
  1010. args.AddToTail( g_BaseAppPath );
  1011. args.AddToTail( serviceUIExe );
  1012. BuildCommandLineFromArgs( args, commandLine, sizeof( commandLine ) );
  1013. data.AddMultipleToTail( V_strlen( commandLine ) + 1, commandLine );
  1014. if ( g_pConnMgr )
  1015. {
  1016. g_pConnMgr->SendPacket( -1, data.Base(), data.Count() );
  1017. Sleep( 1000 ); // Make sure this packet goes out.
  1018. }
  1019. }
  1020. // Returns true if the service was just patched and should exit.
  1021. bool CheckDownloaderFinished()
  1022. {
  1023. if ( !g_Waiting_hProcess )
  1024. return false;
  1025. // Check if the downloader has timed out and kill it if necessary.
  1026. if ( Plat_FloatTime() - g_Waiting_StartTime > MAX_DOWNLOADER_TIME_ALLOWED )
  1027. {
  1028. TerminateProcess( g_Waiting_hProcess, 1 );
  1029. CloseHandle( g_Waiting_hProcess );
  1030. g_Waiting_hProcess = NULL;
  1031. return false;
  1032. }
  1033. // Check if it's done.
  1034. if ( WaitForSingleObject( g_Waiting_hProcess, 0 ) != WAIT_OBJECT_0 )
  1035. return false;
  1036. CloseHandle( g_Waiting_hProcess );
  1037. g_Waiting_hProcess = NULL;
  1038. // Ok, it's done. Did it finish successfully?
  1039. char testFilename[MAX_PATH];
  1040. V_ComposeFileName( g_FileCachePath, "ReadyToGo.txt", testFilename, sizeof( testFilename ) );
  1041. if ( _access( testFilename, 0 ) != 0 )
  1042. return false;
  1043. // Ok, the downloader finished successfully. Run the worker app.
  1044. if ( g_bSuperDebugMode )
  1045. AdjustSuperDebugArgs( g_Waiting_Argv );
  1046. // Figure out the name of the master machine.
  1047. V_strncpy( g_CurMasterName, "<unknown>", sizeof( g_CurMasterName ) );
  1048. for ( int iArg=1; iArg < g_Waiting_Argv.Count()-1; iArg++ )
  1049. {
  1050. if ( stricmp( g_Waiting_Argv[iArg], "-mpi_MasterName" ) == 0 )
  1051. {
  1052. Q_strncpy( g_CurMasterName, g_Waiting_Argv[iArg+1], sizeof( g_CurMasterName ) );
  1053. }
  1054. }
  1055. char DLLFilename[MAX_PATH];
  1056. if ( FindArg( __argc, __argv, "-TryDLLMode" ) &&
  1057. g_RunMode == RUNMODE_CONSOLE &&
  1058. GetDLLFilename( g_Waiting_Argv, DLLFilename ) &&
  1059. !g_Waiting_bPatching )
  1060. {
  1061. // This is just a helper for debugging. If it's VRAD, we can run it
  1062. // in-process as a DLL instead of running it as a separate EXE.
  1063. RunInDLL( DLLFilename, g_Waiting_Argv );
  1064. }
  1065. else
  1066. {
  1067. // Run the (hopefully!) MPI app they specified.
  1068. RunProcessAtCommandLine( g_Waiting_Argv, g_Waiting_bShowAppWindow, g_Waiting_bPatching, g_Waiting_Priority );
  1069. if ( g_Waiting_bPatching )
  1070. {
  1071. // Tell any currently-running UI apps to patch themselves and quit ASAP so the installer can finish.
  1072. SendPatchCommandToUIs( g_dwRunningProcessId );
  1073. ResumeThread( g_hRunningThread ); // We started the installer suspended so we could make sure we'd send out the patch command.
  1074. // We just ran the installer, but let's forget about it, otherwise we'll kill its process when we exit here.
  1075. CloseHandle( g_hRunningProcess );
  1076. CloseHandle( g_hRunningThread ) ;
  1077. g_hRunningProcess = g_hRunningThread = NULL;
  1078. g_RunningProcess_ExeName[0] = 0;
  1079. g_RunningProcess_MapName[0] = 0;
  1080. ServiceHelpers_ExitEarly();
  1081. return true;
  1082. }
  1083. }
  1084. g_Waiting_Argv.PurgeAndDeleteElements();
  1085. return false;
  1086. }
  1087. void HandlePacket_LOOKING_FOR_WORKERS( bf_read &buf, const CIPAddr &ipFrom )
  1088. {
  1089. // If we're downloading files for a job request, don't process any more "looking for workers" packets.
  1090. if ( g_Waiting_hProcess )
  1091. return;
  1092. // This will be a nonzero-length string if patching.
  1093. char versionString[512];
  1094. buf.ReadString( versionString, sizeof( versionString ) );
  1095. int iPort = buf.ReadShort();
  1096. int iPriority = buf.ReadShort();
  1097. // Make sure we don't run the same job more than once.
  1098. if ( !CheckJobID( buf, g_CurJobID ) )
  1099. return;
  1100. CUtlVector<char*> newArgv;
  1101. GetArgsFromBuffer( buf, newArgv, &g_Waiting_bShowAppWindow );
  1102. bool bForcePatch = false;
  1103. if ( buf.GetNumBytesLeft() >= 1 )
  1104. bForcePatch = (buf.ReadByte() != 0);
  1105. int iDownloaderPort = iPort;
  1106. if ( buf.GetNumBytesLeft() >= 2 )
  1107. iDownloaderPort = buf.ReadShort();
  1108. // Add these arguments after the executable filename to tell the program
  1109. // that it's an MPI worker and who to connect to.
  1110. char strDownloaderIP[128], strMainIP[128];
  1111. V_snprintf( strDownloaderIP, sizeof( strDownloaderIP ), "%d.%d.%d.%d:%d", ipFrom.ip[0], ipFrom.ip[1], ipFrom.ip[2], ipFrom.ip[3], iDownloaderPort );
  1112. V_snprintf( strMainIP, sizeof( strMainIP ), "%d.%d.%d.%d:%d", ipFrom.ip[0], ipFrom.ip[1], ipFrom.ip[2], ipFrom.ip[3], iPort );
  1113. // (-mpi is already on the command line of whoever ran the app).
  1114. // AppendArg( commandLine, sizeof( commandLine ), "-mpi" );
  1115. newArgv.InsertAfter( 0, CopyString( "-mpi_worker" ) );
  1116. newArgv.InsertAfter( 1, CopyString( strDownloaderIP ) );
  1117. // If the version string is set, then this is a patch.
  1118. bool bPatching = false;
  1119. if ( versionString[0] != 0 )
  1120. {
  1121. bPatching = true;
  1122. // Check that we haven't applied this patch version yet. This case usually happens right after we've applied a patch
  1123. // and we're restarting. The vmpi_transfer master is still pinging us telling us to patch, but we don't want to
  1124. // reapply this patch.
  1125. if ( atof( versionString ) <= atof( g_VersionString ) && !bForcePatch )
  1126. {
  1127. newArgv.PurgeAndDeleteElements();
  1128. return;
  1129. }
  1130. // Ok, it's a new version. Get rid of whatever was running before.
  1131. KillRunningProcess( "Starting a patch..", true );
  1132. }
  1133. // If there's already a job running, only interrupt it if this new one has a higher priority.
  1134. if ( WaitForProcessToExit() )
  1135. {
  1136. if ( iPriority > g_CurJobPriority )
  1137. {
  1138. KillRunningProcess( "Interrupted by a higher priority process", true );
  1139. }
  1140. else
  1141. {
  1142. // This means we're already running a job with equal to or greater priority than
  1143. // the one that has been requested. We're going to ignore this request.
  1144. newArgv.PurgeAndDeleteElements();
  1145. return;
  1146. }
  1147. }
  1148. // Responses go here.
  1149. g_CurRespondAddr = ipFrom;
  1150. // Also look for -mpi_ShowAppWindow in the args to the service.
  1151. if ( !g_Waiting_bShowAppWindow && FindArg( __argc, __argv, "-mpi_ShowAppWindow" ) )
  1152. g_Waiting_bShowAppWindow = true;
  1153. // Copy all the files from the master and put them in our cache dir to run with.
  1154. char cacheDir[MAX_PATH];
  1155. if ( StartDownloadingAppFiles( newArgv, cacheDir, sizeof( cacheDir ), g_Waiting_bShowAppWindow, &g_Waiting_hProcess, bPatching ) )
  1156. {
  1157. // After it's downloaded, we want it to switch to the main connection port.
  1158. if ( newArgv.Count() >= 3 && V_stricmp( newArgv[2], strDownloaderIP ) == 0 )
  1159. {
  1160. delete newArgv[2];
  1161. newArgv[2] = CopyString( strMainIP );
  1162. }
  1163. g_Waiting_StartTime = Plat_FloatTime();
  1164. g_Waiting_Argv.PurgeAndDeleteElements();
  1165. g_Waiting_Argv = newArgv;
  1166. g_Waiting_Priority = iPriority;
  1167. g_Waiting_bPatching = bPatching;
  1168. newArgv.Purge();
  1169. }
  1170. else
  1171. {
  1172. newArgv.PurgeAndDeleteElements();
  1173. }
  1174. // Remember that we tried to run this job so we don't try to run it again.
  1175. AddJobMemory( g_CurJobID );
  1176. SendStateToServicesBrowsers();
  1177. }
  1178. void HandlePacket_STOP_SERVICE( bf_read &buf, const CIPAddr &ipFrom )
  1179. {
  1180. Msg( "Got a STOP_SERVICE packet. Shutting down...\n" );
  1181. CWaitTimer timer( 1 );
  1182. while ( 1 )
  1183. {
  1184. AddServicesBrowserIP( ipFrom );
  1185. SendStateToServicesBrowsers();
  1186. if ( timer.ShouldKeepWaiting() )
  1187. Sleep( 200 );
  1188. else
  1189. break;
  1190. }
  1191. StopUI();
  1192. ServiceHelpers_ExitEarly();
  1193. }
  1194. void HandlePacket_KILL_PROCESS( const CIPAddr *ipFrom )
  1195. {
  1196. if ( Plat_FloatTime() - g_flLastKillProcessTime > 5 )
  1197. {
  1198. KillRunningProcess( "Got a KILL_PROCESS packet. Stopping the worker executable.\n", true );
  1199. if ( ipFrom )
  1200. {
  1201. AddServicesBrowserIP( *ipFrom );
  1202. SendStateToServicesBrowsers();
  1203. }
  1204. g_flLastKillProcessTime = Plat_FloatTime();
  1205. }
  1206. }
  1207. void HandlePacket_FORCE_PASSWORD_CHANGE( bf_read &buf, const CIPAddr &ipFrom )
  1208. {
  1209. char newPassword[512];
  1210. buf.ReadString( newPassword, sizeof( newPassword ) );
  1211. Msg( "Got a FORCE_PASSWORD_CHANGE (%s) packet.\n", newPassword );
  1212. SetPassword( newPassword );
  1213. if ( g_pConnMgr )
  1214. g_pConnMgr->SendCurStateTo( -1 );
  1215. }
  1216. void VMPI_Waiter_Update()
  1217. {
  1218. CheckScreensaverRunning();
  1219. HandleWindowMessages();
  1220. UpdateServicesBrowserIPs();
  1221. while ( 1 )
  1222. {
  1223. WaitForProcessToExit();
  1224. if ( CheckDownloaderFinished() )
  1225. return;
  1226. // Recv off the socket first so it clears the queue while we're waiting for the process to exit.
  1227. char data[4096];
  1228. CIPAddr ipFrom;
  1229. int len = g_pSocket->RecvFrom( data, sizeof( data ), &ipFrom );
  1230. // Any incoming packets?
  1231. if ( len <= 0 )
  1232. break;
  1233. bf_read buf( data, len );
  1234. if ( buf.ReadByte() != VMPI_PROTOCOL_VERSION )
  1235. continue;
  1236. // Only handle packets with the right password.
  1237. char pwString[256];
  1238. buf.ReadString( pwString, sizeof( pwString ) );
  1239. int packetID = buf.ReadByte();
  1240. if ( pwString[0] == VMPI_PASSWORD_OVERRIDE )
  1241. {
  1242. // Always process these packets regardless of the password (these usually come from
  1243. // the installer when it is trying to stop a previously-running instance).
  1244. }
  1245. else if ( packetID == VMPI_LOOKING_FOR_WORKERS )
  1246. {
  1247. if ( pwString[0] == 0 )
  1248. {
  1249. if ( g_pPassword && g_pPassword[0] != 0 )
  1250. continue;
  1251. }
  1252. else
  1253. {
  1254. if ( !g_pPassword || stricmp( g_pPassword, pwString ) != 0 )
  1255. continue;
  1256. }
  1257. }
  1258. // VMPI_KILL_PROCESS is checked before everything.
  1259. if ( packetID == VMPI_KILL_PROCESS )
  1260. {
  1261. HandlePacket_KILL_PROCESS( &ipFrom );
  1262. }
  1263. else if ( packetID == VMPI_PING_REQUEST )
  1264. {
  1265. AddServicesBrowserIP( ipFrom );
  1266. SendStateToServicesBrowsers();
  1267. }
  1268. else if ( packetID == VMPI_STOP_SERVICE )
  1269. {
  1270. HandlePacket_STOP_SERVICE( buf, ipFrom );
  1271. return;
  1272. }
  1273. else if ( packetID == VMPI_SERVICE_PATCH )
  1274. {
  1275. // The key to doing this here is that we ignore whether we're disabled or in screensaver mode.. we always handle
  1276. // the patch command (unless we've already handled this job ID OR if we've already applied this patch version).
  1277. HandlePacket_LOOKING_FOR_WORKERS( buf, ipFrom );
  1278. }
  1279. else if ( packetID == VMPI_FORCE_PASSWORD_CHANGE )
  1280. {
  1281. HandlePacket_FORCE_PASSWORD_CHANGE( buf, ipFrom );
  1282. }
  1283. // If they've told us not to wait for jobs, then ignore the packet.
  1284. if ( g_iCurState == VMPI_SERVICE_STATE_DISABLED || (g_bScreensaverMode && !g_bScreensaverRunning) )
  1285. continue;
  1286. if ( packetID == VMPI_LOOKING_FOR_WORKERS )
  1287. {
  1288. HandlePacket_LOOKING_FOR_WORKERS( buf, ipFrom );
  1289. }
  1290. }
  1291. }
  1292. // ------------------------------------------------------------------------------------------------ //
  1293. // Startup and service code.
  1294. // ------------------------------------------------------------------------------------------------ //
  1295. void RunMainLoop()
  1296. {
  1297. // This is the service's main loop.
  1298. while ( 1 )
  1299. {
  1300. // If the service has been told to exit, then just exit.
  1301. if ( ServiceHelpers_ShouldExit() )
  1302. break;
  1303. VMPI_Waiter_Update();
  1304. g_pConnMgr->Update();
  1305. Sleep( 50 );
  1306. }
  1307. }
  1308. void InternalRunService()
  1309. {
  1310. if ( !VMPI_Waiter_Init() )
  1311. return;
  1312. RunMainLoop();
  1313. VMPI_Waiter_Term();
  1314. }
  1315. // This function runs us as a console app. Useful for debugging or if you want to run more
  1316. // than one instance of VRAD on the same machine.
  1317. void RunAsNonServiceApp()
  1318. {
  1319. InternalRunService();
  1320. }
  1321. // This function runs inside the service thread.
  1322. void ServiceThreadFn( void *pParam )
  1323. {
  1324. InternalRunService();
  1325. }
  1326. // This function works with the service manager and runs as a system service.
  1327. void RunService()
  1328. {
  1329. if( !ServiceHelpers_StartService( VMPI_SERVICE_NAME_INTERNAL, ServiceThreadFn, NULL ) )
  1330. {
  1331. Msg( "Service manager not started. Running as console app.\n" );
  1332. g_RunMode = RUNMODE_CONSOLE;
  1333. InternalRunService();
  1334. }
  1335. }
  1336. int APIENTRY WinMain(HINSTANCE hInstance,
  1337. HINSTANCE hPrevInstance,
  1338. LPSTR lpCmdLine,
  1339. int nCmdShow)
  1340. {
  1341. // Hook spew output.
  1342. SpewOutputFunc( MySpewOutputFunc );
  1343. // Get access to the registry..
  1344. RegCreateKey( HKEY_LOCAL_MACHINE, VMPI_SERVICE_KEY, &g_hVMPIServiceKey );
  1345. // Setup our version string.
  1346. LoadString( hInstance, VMPI_SERVICE_IDS_VERSION_STRING, g_VersionString, sizeof( g_VersionString ) );
  1347. // Setup the base app path.
  1348. if ( !GetModuleFileName( GetModuleHandle( NULL ), g_BaseAppPath, sizeof( g_BaseAppPath ) ) )
  1349. {
  1350. Warning( "GetModuleFileName failed.\n" );
  1351. return false;
  1352. }
  1353. V_StripLastDir( g_BaseAppPath, sizeof( g_BaseAppPath ) );
  1354. // Setup the cache path.
  1355. V_ComposeFileName( g_BaseAppPath, "vmpi_service_cache", g_FileCachePath, sizeof( g_FileCachePath ) );
  1356. const char *pArg = FindArg( __argc, __argv, "-mpi_pw", NULL );
  1357. SetPassword( pArg );
  1358. if ( FindArg( __argc, __argv, "-console" ) )
  1359. {
  1360. g_RunMode = RUNMODE_CONSOLE;
  1361. }
  1362. else
  1363. {
  1364. g_RunMode = RUNMODE_SERVICE;
  1365. }
  1366. if ( FindArg( __argc, __argv, "-superdebug" ) )
  1367. g_bSuperDebugMode = true;
  1368. g_AppStartTime = GetTickCount();
  1369. g_bMinimized = FindArg( __argc, __argv, "-minimized" ) != NULL;
  1370. ServiceHelpers_Init();
  1371. g_hInstance = hInstance;
  1372. LoadStateFromRegistry();
  1373. // Install the service?
  1374. if ( g_RunMode == RUNMODE_CONSOLE )
  1375. {
  1376. RunAsNonServiceApp();
  1377. }
  1378. else
  1379. {
  1380. RunService();
  1381. }
  1382. return 0;
  1383. }