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.

646 lines
18 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose: Configuration utility
  4. //
  5. //===========================================================================//
  6. #include <windows.h>
  7. #include <io.h>
  8. #include <stdio.h>
  9. #include <vgui/ILocalize.h>
  10. #include <vgui/ISurface.h>
  11. #include <vgui/IVGui.h>
  12. #include <vgui_controls/Panel.h>
  13. #include "tier0/icommandline.h"
  14. #include "inputsystem/iinputsystem.h"
  15. #include "appframework/tier3app.h"
  16. #include "vconfig_main.h"
  17. #include "VConfigDialog.h"
  18. #include "ConfigManager.h"
  19. #include "steam/steam_api.h"
  20. #include <iregistry.h>
  21. // memdbgon must be the last include file in a .cpp file!!!
  22. #include <tier0/memdbgon.h>
  23. #define VCONFIG_MAIN_PATH_ID "MAIN"
  24. CVConfigDialog *g_pMainFrame = 0;
  25. char g_engineDir[50];
  26. // Dummy window
  27. static WNDCLASS staticWndclass = { NULL };
  28. static ATOM staticWndclassAtom = 0;
  29. static HWND staticHwnd = 0;
  30. // List of our game configs, as read from the gameconfig.txt file
  31. CGameConfigManager g_ConfigManager;
  32. CUtlVector<CGameConfig *> g_Configs;
  33. HANDLE g_dwChangeHandle = NULL;
  34. CSteamAPIContext g_SteamAPIContext;
  35. CSteamAPIContext *steamapicontext = &g_SteamAPIContext;
  36. //-----------------------------------------------------------------------------
  37. // Purpose: Copy a string into a CUtlVector of characters
  38. //-----------------------------------------------------------------------------
  39. void UtlStrcpy( CUtlVector<char> &dest, const char *pSrc )
  40. {
  41. dest.EnsureCount( (int) (strlen( pSrc ) + 1) );
  42. Q_strncpy( dest.Base(), pSrc, dest.Count() );
  43. }
  44. //-----------------------------------------------------------------------------
  45. // Purpose:
  46. // Output : const char
  47. //-----------------------------------------------------------------------------
  48. const char *GetBaseDirectory( void )
  49. {
  50. static char path[MAX_PATH] = {0};
  51. if ( path[0] == 0 )
  52. {
  53. GetModuleFileName( (HMODULE)GetAppInstance(), path, sizeof( path ) );
  54. Q_StripLastDir( path, sizeof( path ) ); // Get rid of the filename.
  55. Q_StripTrailingSlash( path );
  56. }
  57. return path;
  58. }
  59. // Fetch the engine version for when running in steam.
  60. void GetEngineVersion(char* pcEngineVer, int nSize)
  61. {
  62. IRegistry *reg = InstanceRegistry( "Source SDK" );
  63. Assert( reg );
  64. V_strncpy( pcEngineVer, reg->ReadString( "EngineVer", "orangebox" ), nSize );
  65. ReleaseInstancedRegistry( reg );
  66. }
  67. //-----------------------------------------------------------------------------
  68. // Purpose: Add a new configuration with proper defaults to a keyvalue block
  69. //-----------------------------------------------------------------------------
  70. bool AddConfig( int configID )
  71. {
  72. // Find the games block of the keyvalues
  73. KeyValues *gameBlock = g_ConfigManager.GetGameBlock();
  74. if ( gameBlock == NULL )
  75. {
  76. Assert( 0 );
  77. return false;
  78. }
  79. // Set to defaults
  80. defaultConfigInfo_t newInfo;
  81. memset( &newInfo, 0, sizeof( newInfo ) );
  82. // Data for building the new configuration
  83. const char *pModName = g_Configs[configID]->m_Name.Base();
  84. const char *pModDirectory = g_Configs[configID]->m_ModDir.Base();
  85. // Mod name
  86. Q_strncpy( newInfo.gameName, pModName, sizeof( newInfo.gameName ) );
  87. // FGD
  88. Q_strncpy( newInfo.FGD, "base.fgd", sizeof( newInfo.FGD ) );
  89. // Get the base directory
  90. Q_FileBase( pModDirectory, newInfo.gameDir, sizeof( newInfo.gameDir ) );
  91. // Default executable
  92. Q_strncpy( newInfo.exeName, "hl2.exe", sizeof( newInfo.exeName ) );
  93. char szPath[MAX_PATH];
  94. Q_strncpy( szPath, pModDirectory, sizeof( szPath ) );
  95. Q_StripLastDir( szPath, sizeof( szPath ) );
  96. Q_StripTrailingSlash( szPath );
  97. char fullDir[MAX_PATH];
  98. g_ConfigManager.GetRootGameDirectory( fullDir, sizeof( fullDir ), g_ConfigManager.GetRootDirectory() );
  99. return g_ConfigManager.AddDefaultConfig( newInfo, gameBlock, szPath, fullDir );
  100. }
  101. //-----------------------------------------------------------------------------
  102. // Purpose: Remove a configuration from the data block
  103. //-----------------------------------------------------------------------------
  104. bool RemoveConfig( int configID )
  105. {
  106. if ( !g_ConfigManager.IsLoaded() )
  107. return false;
  108. // Find the games block of the keyvalues
  109. KeyValues *gameBlock = g_ConfigManager.GetGameBlock();
  110. if ( gameBlock == NULL )
  111. {
  112. Assert( 0 );
  113. return false;
  114. }
  115. int i = 0;
  116. // Iterate through all subkeys
  117. for ( KeyValues *pGame=gameBlock->GetFirstTrueSubKey(); pGame; pGame=pGame->GetNextTrueSubKey(), i++ )
  118. {
  119. if ( i == configID )
  120. {
  121. KeyValues *pOldGame = pGame;
  122. pGame = pGame->GetNextTrueSubKey();
  123. gameBlock->RemoveSubKey( pOldGame );
  124. pOldGame->deleteThis();
  125. if ( pGame == NULL )
  126. return true;
  127. }
  128. }
  129. return false;
  130. }
  131. //-----------------------------------------------------------------------------
  132. // Purpose: Updates the internal data of the keyvalue buffer with the edited info
  133. // Output : Returns true on success, false on failure.
  134. //-----------------------------------------------------------------------------
  135. bool UpdateConfigs( void )
  136. {
  137. if ( !g_ConfigManager.IsLoaded() )
  138. return false;
  139. // Find the games block of the keyvalues
  140. KeyValues *gameBlock = g_ConfigManager.GetGameBlock();
  141. if ( gameBlock == NULL )
  142. {
  143. Assert( 0 );
  144. return false;
  145. }
  146. int i = 0;
  147. // Stomp parsed data onto the contained keyvalues
  148. for ( KeyValues *pGame=gameBlock->GetFirstTrueSubKey(); pGame != NULL; pGame=pGame->GetNextTrueSubKey(), i++ )
  149. {
  150. pGame->SetName( g_Configs[i]->m_Name.Base() );
  151. pGame->SetString( TOKEN_GAME_DIRECTORY, g_Configs[i]->m_ModDir.Base() );
  152. }
  153. return true;
  154. }
  155. //-----------------------------------------------------------------------------
  156. // Purpose: Saves out changes to the config file
  157. // Output : Returns true on success, false on failure.
  158. //-----------------------------------------------------------------------------
  159. bool SaveConfigs( void )
  160. {
  161. // Move the internal changes up to the base data stored in the config manager
  162. if ( UpdateConfigs() == false )
  163. return false;
  164. // Save out the data
  165. if ( g_ConfigManager.SaveConfigs() == false )
  166. return false;
  167. return true;
  168. }
  169. //-----------------------------------------------------------------------------
  170. // Purpose: Read the information we use out of the configs
  171. // Output : Returns true on success, false on failure.
  172. //-----------------------------------------------------------------------------
  173. bool ParseConfigs( void )
  174. {
  175. if ( !g_ConfigManager.IsLoaded() )
  176. return false;
  177. // Find the games block of the keyvalues
  178. KeyValues *gameBlock = g_ConfigManager.GetGameBlock();
  179. if ( gameBlock == NULL )
  180. {
  181. Assert( 0 );
  182. return false;
  183. }
  184. // Iterate through all subkeys
  185. for ( KeyValues *pGame=gameBlock->GetFirstTrueSubKey(); pGame; pGame=pGame->GetNextTrueSubKey() )
  186. {
  187. const char *pName = pGame->GetName();
  188. const char *pDir = pGame->GetString( TOKEN_GAME_DIRECTORY );
  189. CGameConfig *newConfig = new CGameConfig( pName, pDir );
  190. g_Configs.AddToTail( newConfig );
  191. }
  192. return true;
  193. }
  194. //-----------------------------------------------------------------------------
  195. // Purpose: Startup our file watch
  196. //-----------------------------------------------------------------------------
  197. void UpdateConfigsStatus_Init( void )
  198. {
  199. // Watch our config file for changes
  200. if ( g_dwChangeHandle == NULL)
  201. {
  202. char szConfigDir[MAX_PATH];
  203. Q_strncpy( szConfigDir, GetBaseDirectory(), sizeof( szConfigDir ) );
  204. g_dwChangeHandle = FindFirstChangeNotification(
  205. szConfigDir, // directory to watch
  206. false, // watch the subtree
  207. FILE_NOTIFY_CHANGE_LAST_WRITE ); // watch file and dir name changes
  208. if ( g_dwChangeHandle == INVALID_HANDLE_VALUE )
  209. {
  210. // FIXME: Unable to watch the file
  211. }
  212. }
  213. }
  214. //-----------------------------------------------------------------------------
  215. // Purpose: Reload and re-parse our configuration data
  216. //-----------------------------------------------------------------------------
  217. void ReloadConfigs( bool bNoWarning /*= false*/ )
  218. {
  219. g_Configs.PurgeAndDeleteElements();
  220. ParseConfigs();
  221. g_pMainFrame->PopulateConfigList( bNoWarning );
  222. }
  223. //-----------------------------------------------------------------------------
  224. // Purpose: Update our status
  225. //-----------------------------------------------------------------------------
  226. void UpdateConfigsStatus( void )
  227. {
  228. // Wait for notification.
  229. DWORD dwWaitStatus = WaitForSingleObject( g_dwChangeHandle, 0 );
  230. if ( dwWaitStatus == WAIT_OBJECT_0 )
  231. {
  232. // Something in the watched folder changed!
  233. if ( g_pMainFrame != NULL )
  234. {
  235. // Reload the configs
  236. g_ConfigManager.LoadConfigs();
  237. // Reparse the configurations
  238. ReloadConfigs();
  239. }
  240. // Start the next update
  241. if ( FindNextChangeNotification( g_dwChangeHandle ) == FALSE )
  242. {
  243. // This means that something unknown happened to our search handle!
  244. Assert( 0 );
  245. return;
  246. }
  247. }
  248. }
  249. //-----------------------------------------------------------------------------
  250. // Purpose: Stop watching the file
  251. //-----------------------------------------------------------------------------
  252. void UpdateConfigsStatus_Shutdown( void )
  253. {
  254. FindCloseChangeNotification( g_dwChangeHandle );
  255. }
  256. //-----------------------------------------------------------------------------
  257. // Purpose: Message handler for dummy app
  258. //-----------------------------------------------------------------------------
  259. static LRESULT CALLBACK messageProc( HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam )
  260. {
  261. // See if we've gotten a VPROJECT change
  262. if ( msg == WM_SETTINGCHANGE )
  263. {
  264. if ( g_pMainFrame != NULL )
  265. {
  266. // Reset the list and pop an error if they've chosen something we don't understand
  267. g_pMainFrame->PopulateConfigList();
  268. }
  269. }
  270. return ::DefWindowProc(hwnd,msg,wparam,lparam);
  271. }
  272. //-----------------------------------------------------------------------------
  273. // Purpose: Creates a dummy window that handles windows messages
  274. //-----------------------------------------------------------------------------
  275. void CreateMessageWindow( void )
  276. {
  277. // Make and register a very simple window class
  278. memset(&staticWndclass, 0, sizeof(staticWndclass));
  279. staticWndclass.style = 0;
  280. staticWndclass.lpfnWndProc = messageProc;
  281. staticWndclass.hInstance = GetModuleHandle(NULL);
  282. staticWndclass.lpszClassName = "VConfig_Window";
  283. staticWndclassAtom = ::RegisterClass( &staticWndclass );
  284. // Create an empty window just for message handling
  285. staticHwnd = CreateWindowEx(0, "VConfig_Window", "Hidden Window", 0, 0, 0, 1, 1, NULL, NULL, GetModuleHandle(NULL), NULL);
  286. }
  287. //-----------------------------------------------------------------------------
  288. // Purpose:
  289. //-----------------------------------------------------------------------------
  290. void ShutdownMessageWindow( void )
  291. {
  292. // Kill our windows instance
  293. ::DestroyWindow( staticHwnd );
  294. ::UnregisterClass("VConfig_Window", ::GetModuleHandle(NULL));
  295. }
  296. //-----------------------------------------------------------------------------
  297. // Sets up, shuts down vgui
  298. //-----------------------------------------------------------------------------
  299. bool InitializeVGUI( void )
  300. {
  301. vgui::ivgui()->SetSleep(false);
  302. // Init the surface
  303. vgui::Panel *pPanel = new vgui::Panel( NULL, "TopPanel" );
  304. pPanel->SetVisible(true);
  305. vgui::surface()->SetEmbeddedPanel(pPanel->GetVPanel());
  306. // load the scheme
  307. vgui::scheme()->LoadSchemeFromFile( "vconfig_scheme.res", NULL );
  308. // localization
  309. g_pVGuiLocalize->AddFile( "resource/platform_%language%.txt");
  310. g_pVGuiLocalize->AddFile( "vgui/resource/vgui_%language%.txt" );
  311. g_pVGuiLocalize->AddFile( "vconfig_english.txt");
  312. // Start vgui
  313. vgui::ivgui()->Start();
  314. // add our main window
  315. g_pMainFrame = new CVConfigDialog( pPanel, "VConfigDialog" );
  316. // show main window
  317. g_pMainFrame->MoveToCenterOfScreen();
  318. g_pMainFrame->Activate();
  319. g_pMainFrame->SetSizeable( false );
  320. g_pMainFrame->SetMenuButtonVisible( true );
  321. return true;
  322. }
  323. //-----------------------------------------------------------------------------
  324. // Purpose: Stop VGUI
  325. //-----------------------------------------------------------------------------
  326. void ShutdownVGUI( void )
  327. {
  328. delete g_pMainFrame;
  329. }
  330. //-----------------------------------------------------------------------------
  331. // Points the maya script to the appropriate place
  332. //-----------------------------------------------------------------------------
  333. void SetMayaScriptSettings( )
  334. {
  335. char pMayaScriptPath[ MAX_PATH ];
  336. Q_snprintf( pMayaScriptPath, sizeof(pMayaScriptPath), "%%VPROJECT%%\\..\\sdktools\\maya\\scripts" );
  337. SetVConfigRegistrySetting( "MAYA_SCRIPT_PATH", pMayaScriptPath, false );
  338. }
  339. //-----------------------------------------------------------------------------
  340. // Points the XSI script to the appropriate place
  341. //-----------------------------------------------------------------------------
  342. void SetXSIScriptSettings( )
  343. {
  344. // Determine the currently installed version of XSI
  345. char *pXSIVersion = "5.1";
  346. // FIXME: We need a way of knowing the current version of XSI being used
  347. // so we can set up the appropriate search paths. There's no easy way of doing this currently
  348. // so I'm defining my own environment variable
  349. char pXSIVersionBuf[ MAX_PATH ];
  350. if ( GetVConfigRegistrySetting( "XSI_VERSION", pXSIVersionBuf, sizeof(pXSIVersionBuf) ) )
  351. {
  352. pXSIVersion = pXSIVersionBuf;
  353. }
  354. char pXSIPluginPath[ MAX_PATH ];
  355. Q_snprintf( pXSIPluginPath, sizeof(pXSIPluginPath), "%%VPROJECT%%\\..\\sdktools\\xsi\\%s\\valvesource", pXSIVersion );
  356. SetVConfigRegistrySetting( "XSI_PLUGINS", pXSIPluginPath, false );
  357. SetVConfigRegistrySetting( "XSI_VERSION", pXSIVersion, false );
  358. }
  359. //-----------------------------------------------------------------------------
  360. // Points the XSI script to the appropriate place
  361. //-----------------------------------------------------------------------------
  362. #define VPROJECT_BIN_PATH "%vproject%\\..\\bin"
  363. void SetPathSettings( )
  364. {
  365. char pPathBuf[ MAX_PATH*32 ];
  366. if ( GetVConfigRegistrySetting( "PATH", pPathBuf, sizeof(pPathBuf) ) )
  367. {
  368. Q_FixSlashes( pPathBuf );
  369. const char *pPath = pPathBuf;
  370. const char *pFound = Q_stristr( pPath, VPROJECT_BIN_PATH );
  371. int nLen = Q_strlen( VPROJECT_BIN_PATH );
  372. while ( pFound )
  373. {
  374. if ( pFound[nLen] == '\\' )
  375. {
  376. ++nLen;
  377. }
  378. if ( !pFound[nLen] || pFound[nLen] == ';' )
  379. return;
  380. pPath += nLen;
  381. pFound = Q_stristr( pPath, VPROJECT_BIN_PATH );
  382. }
  383. Q_strncat( pPathBuf, ";%VPROJECT%\\..\\bin", sizeof(pPathBuf) );
  384. }
  385. else
  386. {
  387. Q_strncpy( pPathBuf, "%VPROJECT%\\..\\bin", sizeof(pPathBuf) );
  388. }
  389. SetVConfigRegistrySetting( "PATH", pPathBuf, false );
  390. }
  391. //-----------------------------------------------------------------------------
  392. // Spew func
  393. //-----------------------------------------------------------------------------
  394. SpewRetval_t VConfig_SpewOutputFunc( SpewType_t type, char const *pMsg )
  395. {
  396. #ifdef _DEBUG
  397. OutputDebugString( pMsg );
  398. #endif
  399. switch( type )
  400. {
  401. case SPEW_ERROR:
  402. ::MessageBox( NULL, pMsg, "VConfig Error", MB_OK );
  403. return SPEW_ABORT;
  404. case SPEW_ASSERT:
  405. return SPEW_DEBUGGER;
  406. }
  407. return SPEW_CONTINUE;
  408. }
  409. //-----------------------------------------------------------------------------
  410. // The application object
  411. //-----------------------------------------------------------------------------
  412. class CVConfigApp : public CVguiSteamApp
  413. {
  414. typedef CVguiSteamApp BaseClass;
  415. public:
  416. // Methods of IApplication
  417. virtual bool Create();
  418. virtual bool PreInit();
  419. virtual int Main();
  420. virtual void PostShutdown();
  421. virtual void Destroy() {}
  422. };
  423. DEFINE_WINDOWED_STEAM_APPLICATION_OBJECT( CVConfigApp );
  424. //-----------------------------------------------------------------------------
  425. // The application object
  426. //-----------------------------------------------------------------------------
  427. bool CVConfigApp::Create()
  428. {
  429. SpewOutputFunc( VConfig_SpewOutputFunc );
  430. // If they pass in -game, just set the registry key to the value they asked for.
  431. const char *pSetGame = CommandLine()->ParmValue( "-game" );
  432. if ( pSetGame )
  433. {
  434. SetMayaScriptSettings( );
  435. SetXSIScriptSettings( );
  436. SetPathSettings( );
  437. SetVConfigRegistrySetting( GAMEDIR_TOKEN, pSetGame );
  438. return false;
  439. }
  440. AppSystemInfo_t appSystems[] =
  441. {
  442. { "inputsystem.dll", INPUTSYSTEM_INTERFACE_VERSION },
  443. { "vgui2.dll", VGUI_IVGUI_INTERFACE_VERSION },
  444. { "", "" } // Required to terminate the list
  445. };
  446. return AddSystems( appSystems );
  447. }
  448. //-----------------------------------------------------------------------------
  449. // Pre-init
  450. //-----------------------------------------------------------------------------
  451. bool CVConfigApp::PreInit()
  452. {
  453. if ( !BaseClass::PreInit() )
  454. return false;
  455. // Create a window to capture messages
  456. CreateMessageWindow();
  457. // Make sure we're using the proper environment variable
  458. ConvertObsoleteVConfigRegistrySetting( GAMEDIR_TOKEN );
  459. FileSystem_SetErrorMode( FS_ERRORMODE_AUTO );
  460. // We only want to use the gameinfo.txt that is in the bin\vconfig directory.
  461. char dirName[MAX_PATH];
  462. Q_strncpy( dirName, GetBaseDirectory(), sizeof( dirName ) );
  463. Q_AppendSlash( dirName, sizeof( dirName ) );
  464. Q_strncat( dirName, "vconfig", sizeof( dirName ), COPY_ALL_CHARACTERS );
  465. if ( !SetupSearchPaths( dirName, true, true ) )
  466. {
  467. ::MessageBox( NULL, "Error", "Unable to initialize file system\n", MB_OK );
  468. return false;
  469. }
  470. // Load our configs
  471. if ( g_ConfigManager.LoadConfigs() == false )
  472. {
  473. ::MessageBox( NULL, "Error", "Unable to load configuration file\n", MB_OK );
  474. return false;
  475. }
  476. // Parse them for internal use
  477. if ( ParseConfigs() == false )
  478. {
  479. ::MessageBox( NULL, "Error", "Unable to parse configuration file\n", MB_OK );
  480. return false;
  481. }
  482. // Start looking for file updates
  483. UpdateConfigsStatus_Init();
  484. // the "base dir" so we can scan mod name
  485. g_pFullFileSystem->AddSearchPath( GetBaseDirectory(), VCONFIG_MAIN_PATH_ID );
  486. // the main platform dir
  487. g_pFullFileSystem->AddSearchPath( "platform","PLATFORM", PATH_ADD_TO_HEAD );
  488. return true;
  489. }
  490. //-----------------------------------------------------------------------------
  491. // Pre-init
  492. //-----------------------------------------------------------------------------
  493. void CVConfigApp::PostShutdown()
  494. {
  495. // Stop our message window
  496. ShutdownMessageWindow();
  497. // Clear our configs
  498. g_Configs.PurgeAndDeleteElements();
  499. // Stop file notifications
  500. UpdateConfigsStatus_Shutdown();
  501. BaseClass::PostShutdown();
  502. }
  503. //-----------------------------------------------------------------------------
  504. // Purpose: Main function
  505. //-----------------------------------------------------------------------------
  506. int CVConfigApp::Main()
  507. {
  508. if ( !InitializeVGUI() )
  509. return 0;
  510. SteamAPI_InitSafe();
  511. SteamAPI_SetTryCatchCallbacks( false ); // We don't use exceptions, so tell steam not to use try/catch in callback handlers
  512. g_SteamAPIContext.Init();
  513. GetEngineVersion( g_engineDir, sizeof( g_engineDir ) );
  514. // Run the app
  515. while ( vgui::ivgui()->IsRunning() )
  516. {
  517. Sleep( 10 );
  518. UpdateConfigsStatus();
  519. vgui::ivgui()->RunFrame();
  520. }
  521. ShutdownVGUI();
  522. return 1;
  523. }