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
646 lines
18 KiB
//========= Copyright Valve Corporation, All rights reserved. ============// |
|
// |
|
// Purpose: Configuration utility |
|
// |
|
//===========================================================================// |
|
|
|
#include <windows.h> |
|
#include <io.h> |
|
#include <stdio.h> |
|
|
|
#include <vgui/ILocalize.h> |
|
#include <vgui/ISurface.h> |
|
#include <vgui/IVGui.h> |
|
#include <vgui_controls/Panel.h> |
|
|
|
#include "tier0/icommandline.h" |
|
#include "inputsystem/iinputsystem.h" |
|
#include "appframework/tier3app.h" |
|
#include "vconfig_main.h" |
|
#include "VConfigDialog.h" |
|
#include "ConfigManager.h" |
|
#include "steam/steam_api.h" |
|
#include <iregistry.h> |
|
|
|
// memdbgon must be the last include file in a .cpp file!!! |
|
#include <tier0/memdbgon.h> |
|
|
|
#define VCONFIG_MAIN_PATH_ID "MAIN" |
|
|
|
CVConfigDialog *g_pMainFrame = 0; |
|
char g_engineDir[50]; |
|
|
|
|
|
// Dummy window |
|
static WNDCLASS staticWndclass = { NULL }; |
|
static ATOM staticWndclassAtom = 0; |
|
static HWND staticHwnd = 0; |
|
|
|
// List of our game configs, as read from the gameconfig.txt file |
|
CGameConfigManager g_ConfigManager; |
|
CUtlVector<CGameConfig *> g_Configs; |
|
HANDLE g_dwChangeHandle = NULL; |
|
CSteamAPIContext g_SteamAPIContext; |
|
CSteamAPIContext *steamapicontext = &g_SteamAPIContext; |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Copy a string into a CUtlVector of characters |
|
//----------------------------------------------------------------------------- |
|
void UtlStrcpy( CUtlVector<char> &dest, const char *pSrc ) |
|
{ |
|
dest.EnsureCount( (int) (strlen( pSrc ) + 1) ); |
|
Q_strncpy( dest.Base(), pSrc, dest.Count() ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Output : const char |
|
//----------------------------------------------------------------------------- |
|
const char *GetBaseDirectory( void ) |
|
{ |
|
static char path[MAX_PATH] = {0}; |
|
if ( path[0] == 0 ) |
|
{ |
|
GetModuleFileName( (HMODULE)GetAppInstance(), path, sizeof( path ) ); |
|
Q_StripLastDir( path, sizeof( path ) ); // Get rid of the filename. |
|
Q_StripTrailingSlash( path ); |
|
} |
|
return path; |
|
} |
|
|
|
// Fetch the engine version for when running in steam. |
|
void GetEngineVersion(char* pcEngineVer, int nSize) |
|
{ |
|
IRegistry *reg = InstanceRegistry( "Source SDK" ); |
|
Assert( reg ); |
|
V_strncpy( pcEngineVer, reg->ReadString( "EngineVer", "orangebox" ), nSize ); |
|
ReleaseInstancedRegistry( reg ); |
|
} |
|
//----------------------------------------------------------------------------- |
|
// Purpose: Add a new configuration with proper defaults to a keyvalue block |
|
//----------------------------------------------------------------------------- |
|
bool AddConfig( int configID ) |
|
{ |
|
// Find the games block of the keyvalues |
|
KeyValues *gameBlock = g_ConfigManager.GetGameBlock(); |
|
|
|
if ( gameBlock == NULL ) |
|
{ |
|
Assert( 0 ); |
|
return false; |
|
} |
|
|
|
// Set to defaults |
|
defaultConfigInfo_t newInfo; |
|
memset( &newInfo, 0, sizeof( newInfo ) ); |
|
|
|
// Data for building the new configuration |
|
const char *pModName = g_Configs[configID]->m_Name.Base(); |
|
const char *pModDirectory = g_Configs[configID]->m_ModDir.Base(); |
|
|
|
// Mod name |
|
Q_strncpy( newInfo.gameName, pModName, sizeof( newInfo.gameName ) ); |
|
|
|
// FGD |
|
Q_strncpy( newInfo.FGD, "base.fgd", sizeof( newInfo.FGD ) ); |
|
|
|
// Get the base directory |
|
Q_FileBase( pModDirectory, newInfo.gameDir, sizeof( newInfo.gameDir ) ); |
|
|
|
// Default executable |
|
Q_strncpy( newInfo.exeName, "hl2.exe", sizeof( newInfo.exeName ) ); |
|
|
|
char szPath[MAX_PATH]; |
|
Q_strncpy( szPath, pModDirectory, sizeof( szPath ) ); |
|
Q_StripLastDir( szPath, sizeof( szPath ) ); |
|
Q_StripTrailingSlash( szPath ); |
|
|
|
char fullDir[MAX_PATH]; |
|
g_ConfigManager.GetRootGameDirectory( fullDir, sizeof( fullDir ), g_ConfigManager.GetRootDirectory() ); |
|
|
|
return g_ConfigManager.AddDefaultConfig( newInfo, gameBlock, szPath, fullDir ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Remove a configuration from the data block |
|
//----------------------------------------------------------------------------- |
|
bool RemoveConfig( int configID ) |
|
{ |
|
if ( !g_ConfigManager.IsLoaded() ) |
|
return false; |
|
|
|
// Find the games block of the keyvalues |
|
KeyValues *gameBlock = g_ConfigManager.GetGameBlock(); |
|
|
|
if ( gameBlock == NULL ) |
|
{ |
|
Assert( 0 ); |
|
return false; |
|
} |
|
|
|
int i = 0; |
|
|
|
// Iterate through all subkeys |
|
for ( KeyValues *pGame=gameBlock->GetFirstTrueSubKey(); pGame; pGame=pGame->GetNextTrueSubKey(), i++ ) |
|
{ |
|
if ( i == configID ) |
|
{ |
|
KeyValues *pOldGame = pGame; |
|
pGame = pGame->GetNextTrueSubKey(); |
|
|
|
gameBlock->RemoveSubKey( pOldGame ); |
|
pOldGame->deleteThis(); |
|
|
|
if ( pGame == NULL ) |
|
return true; |
|
} |
|
} |
|
|
|
return false; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Updates the internal data of the keyvalue buffer with the edited info |
|
// Output : Returns true on success, false on failure. |
|
//----------------------------------------------------------------------------- |
|
bool UpdateConfigs( void ) |
|
{ |
|
if ( !g_ConfigManager.IsLoaded() ) |
|
return false; |
|
|
|
// Find the games block of the keyvalues |
|
KeyValues *gameBlock = g_ConfigManager.GetGameBlock(); |
|
|
|
if ( gameBlock == NULL ) |
|
{ |
|
Assert( 0 ); |
|
return false; |
|
} |
|
|
|
int i = 0; |
|
|
|
// Stomp parsed data onto the contained keyvalues |
|
for ( KeyValues *pGame=gameBlock->GetFirstTrueSubKey(); pGame != NULL; pGame=pGame->GetNextTrueSubKey(), i++ ) |
|
{ |
|
pGame->SetName( g_Configs[i]->m_Name.Base() ); |
|
pGame->SetString( TOKEN_GAME_DIRECTORY, g_Configs[i]->m_ModDir.Base() ); |
|
} |
|
|
|
return true; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Saves out changes to the config file |
|
// Output : Returns true on success, false on failure. |
|
//----------------------------------------------------------------------------- |
|
bool SaveConfigs( void ) |
|
{ |
|
// Move the internal changes up to the base data stored in the config manager |
|
if ( UpdateConfigs() == false ) |
|
return false; |
|
|
|
// Save out the data |
|
if ( g_ConfigManager.SaveConfigs() == false ) |
|
return false; |
|
|
|
return true; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Read the information we use out of the configs |
|
// Output : Returns true on success, false on failure. |
|
//----------------------------------------------------------------------------- |
|
bool ParseConfigs( void ) |
|
{ |
|
if ( !g_ConfigManager.IsLoaded() ) |
|
return false; |
|
|
|
// Find the games block of the keyvalues |
|
KeyValues *gameBlock = g_ConfigManager.GetGameBlock(); |
|
|
|
if ( gameBlock == NULL ) |
|
{ |
|
Assert( 0 ); |
|
return false; |
|
} |
|
|
|
// Iterate through all subkeys |
|
for ( KeyValues *pGame=gameBlock->GetFirstTrueSubKey(); pGame; pGame=pGame->GetNextTrueSubKey() ) |
|
{ |
|
const char *pName = pGame->GetName(); |
|
const char *pDir = pGame->GetString( TOKEN_GAME_DIRECTORY ); |
|
|
|
CGameConfig *newConfig = new CGameConfig( pName, pDir ); |
|
g_Configs.AddToTail( newConfig ); |
|
} |
|
|
|
return true; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Startup our file watch |
|
//----------------------------------------------------------------------------- |
|
void UpdateConfigsStatus_Init( void ) |
|
{ |
|
// Watch our config file for changes |
|
if ( g_dwChangeHandle == NULL) |
|
{ |
|
char szConfigDir[MAX_PATH]; |
|
Q_strncpy( szConfigDir, GetBaseDirectory(), sizeof( szConfigDir ) ); |
|
|
|
g_dwChangeHandle = FindFirstChangeNotification( |
|
szConfigDir, // directory to watch |
|
false, // watch the subtree |
|
FILE_NOTIFY_CHANGE_LAST_WRITE ); // watch file and dir name changes |
|
|
|
if ( g_dwChangeHandle == INVALID_HANDLE_VALUE ) |
|
{ |
|
// FIXME: Unable to watch the file |
|
} |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Reload and re-parse our configuration data |
|
//----------------------------------------------------------------------------- |
|
void ReloadConfigs( bool bNoWarning /*= false*/ ) |
|
{ |
|
g_Configs.PurgeAndDeleteElements(); |
|
ParseConfigs(); |
|
g_pMainFrame->PopulateConfigList( bNoWarning ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Update our status |
|
//----------------------------------------------------------------------------- |
|
void UpdateConfigsStatus( void ) |
|
{ |
|
// Wait for notification. |
|
DWORD dwWaitStatus = WaitForSingleObject( g_dwChangeHandle, 0 ); |
|
|
|
if ( dwWaitStatus == WAIT_OBJECT_0 ) |
|
{ |
|
// Something in the watched folder changed! |
|
if ( g_pMainFrame != NULL ) |
|
{ |
|
// Reload the configs |
|
g_ConfigManager.LoadConfigs(); |
|
|
|
// Reparse the configurations |
|
ReloadConfigs(); |
|
} |
|
|
|
// Start the next update |
|
if ( FindNextChangeNotification( g_dwChangeHandle ) == FALSE ) |
|
{ |
|
// This means that something unknown happened to our search handle! |
|
Assert( 0 ); |
|
return; |
|
} |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Stop watching the file |
|
//----------------------------------------------------------------------------- |
|
void UpdateConfigsStatus_Shutdown( void ) |
|
{ |
|
FindCloseChangeNotification( g_dwChangeHandle ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Message handler for dummy app |
|
//----------------------------------------------------------------------------- |
|
static LRESULT CALLBACK messageProc( HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam ) |
|
{ |
|
// See if we've gotten a VPROJECT change |
|
if ( msg == WM_SETTINGCHANGE ) |
|
{ |
|
if ( g_pMainFrame != NULL ) |
|
{ |
|
// Reset the list and pop an error if they've chosen something we don't understand |
|
g_pMainFrame->PopulateConfigList(); |
|
} |
|
} |
|
|
|
return ::DefWindowProc(hwnd,msg,wparam,lparam); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Creates a dummy window that handles windows messages |
|
//----------------------------------------------------------------------------- |
|
void CreateMessageWindow( void ) |
|
{ |
|
// Make and register a very simple window class |
|
memset(&staticWndclass, 0, sizeof(staticWndclass)); |
|
staticWndclass.style = 0; |
|
staticWndclass.lpfnWndProc = messageProc; |
|
staticWndclass.hInstance = GetModuleHandle(NULL); |
|
staticWndclass.lpszClassName = "VConfig_Window"; |
|
staticWndclassAtom = ::RegisterClass( &staticWndclass ); |
|
|
|
// Create an empty window just for message handling |
|
staticHwnd = CreateWindowEx(0, "VConfig_Window", "Hidden Window", 0, 0, 0, 1, 1, NULL, NULL, GetModuleHandle(NULL), NULL); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void ShutdownMessageWindow( void ) |
|
{ |
|
// Kill our windows instance |
|
::DestroyWindow( staticHwnd ); |
|
::UnregisterClass("VConfig_Window", ::GetModuleHandle(NULL)); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Sets up, shuts down vgui |
|
//----------------------------------------------------------------------------- |
|
bool InitializeVGUI( void ) |
|
{ |
|
vgui::ivgui()->SetSleep(false); |
|
|
|
// Init the surface |
|
vgui::Panel *pPanel = new vgui::Panel( NULL, "TopPanel" ); |
|
pPanel->SetVisible(true); |
|
|
|
vgui::surface()->SetEmbeddedPanel(pPanel->GetVPanel()); |
|
|
|
// load the scheme |
|
vgui::scheme()->LoadSchemeFromFile( "vconfig_scheme.res", NULL ); |
|
|
|
// localization |
|
g_pVGuiLocalize->AddFile( "resource/platform_%language%.txt"); |
|
g_pVGuiLocalize->AddFile( "vgui/resource/vgui_%language%.txt" ); |
|
g_pVGuiLocalize->AddFile( "vconfig_english.txt"); |
|
|
|
// Start vgui |
|
vgui::ivgui()->Start(); |
|
|
|
// add our main window |
|
g_pMainFrame = new CVConfigDialog( pPanel, "VConfigDialog" ); |
|
|
|
// show main window |
|
g_pMainFrame->MoveToCenterOfScreen(); |
|
g_pMainFrame->Activate(); |
|
g_pMainFrame->SetSizeable( false ); |
|
g_pMainFrame->SetMenuButtonVisible( true ); |
|
|
|
return true; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Stop VGUI |
|
//----------------------------------------------------------------------------- |
|
void ShutdownVGUI( void ) |
|
{ |
|
delete g_pMainFrame; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Points the maya script to the appropriate place |
|
//----------------------------------------------------------------------------- |
|
void SetMayaScriptSettings( ) |
|
{ |
|
char pMayaScriptPath[ MAX_PATH ]; |
|
Q_snprintf( pMayaScriptPath, sizeof(pMayaScriptPath), "%%VPROJECT%%\\..\\sdktools\\maya\\scripts" ); |
|
SetVConfigRegistrySetting( "MAYA_SCRIPT_PATH", pMayaScriptPath, false ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Points the XSI script to the appropriate place |
|
//----------------------------------------------------------------------------- |
|
void SetXSIScriptSettings( ) |
|
{ |
|
// Determine the currently installed version of XSI |
|
char *pXSIVersion = "5.1"; |
|
|
|
// FIXME: We need a way of knowing the current version of XSI being used |
|
// so we can set up the appropriate search paths. There's no easy way of doing this currently |
|
// so I'm defining my own environment variable |
|
char pXSIVersionBuf[ MAX_PATH ]; |
|
if ( GetVConfigRegistrySetting( "XSI_VERSION", pXSIVersionBuf, sizeof(pXSIVersionBuf) ) ) |
|
{ |
|
pXSIVersion = pXSIVersionBuf; |
|
} |
|
|
|
char pXSIPluginPath[ MAX_PATH ]; |
|
Q_snprintf( pXSIPluginPath, sizeof(pXSIPluginPath), "%%VPROJECT%%\\..\\sdktools\\xsi\\%s\\valvesource", pXSIVersion ); |
|
SetVConfigRegistrySetting( "XSI_PLUGINS", pXSIPluginPath, false ); |
|
SetVConfigRegistrySetting( "XSI_VERSION", pXSIVersion, false ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Points the XSI script to the appropriate place |
|
//----------------------------------------------------------------------------- |
|
#define VPROJECT_BIN_PATH "%vproject%\\..\\bin" |
|
|
|
void SetPathSettings( ) |
|
{ |
|
char pPathBuf[ MAX_PATH*32 ]; |
|
if ( GetVConfigRegistrySetting( "PATH", pPathBuf, sizeof(pPathBuf) ) ) |
|
{ |
|
Q_FixSlashes( pPathBuf ); |
|
const char *pPath = pPathBuf; |
|
const char *pFound = Q_stristr( pPath, VPROJECT_BIN_PATH ); |
|
int nLen = Q_strlen( VPROJECT_BIN_PATH ); |
|
while ( pFound ) |
|
{ |
|
if ( pFound[nLen] == '\\' ) |
|
{ |
|
++nLen; |
|
} |
|
if ( !pFound[nLen] || pFound[nLen] == ';' ) |
|
return; |
|
|
|
pPath += nLen; |
|
pFound = Q_stristr( pPath, VPROJECT_BIN_PATH ); |
|
} |
|
|
|
Q_strncat( pPathBuf, ";%VPROJECT%\\..\\bin", sizeof(pPathBuf) ); |
|
} |
|
else |
|
{ |
|
Q_strncpy( pPathBuf, "%VPROJECT%\\..\\bin", sizeof(pPathBuf) ); |
|
} |
|
|
|
SetVConfigRegistrySetting( "PATH", pPathBuf, false ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Spew func |
|
//----------------------------------------------------------------------------- |
|
SpewRetval_t VConfig_SpewOutputFunc( SpewType_t type, char const *pMsg ) |
|
{ |
|
#ifdef _DEBUG |
|
OutputDebugString( pMsg ); |
|
#endif |
|
|
|
switch( type ) |
|
{ |
|
case SPEW_ERROR: |
|
::MessageBox( NULL, pMsg, "VConfig Error", MB_OK ); |
|
return SPEW_ABORT; |
|
|
|
case SPEW_ASSERT: |
|
return SPEW_DEBUGGER; |
|
} |
|
|
|
return SPEW_CONTINUE; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// The application object |
|
//----------------------------------------------------------------------------- |
|
class CVConfigApp : public CVguiSteamApp |
|
{ |
|
typedef CVguiSteamApp BaseClass; |
|
|
|
public: |
|
// Methods of IApplication |
|
virtual bool Create(); |
|
virtual bool PreInit(); |
|
virtual int Main(); |
|
virtual void PostShutdown(); |
|
virtual void Destroy() {} |
|
}; |
|
|
|
DEFINE_WINDOWED_STEAM_APPLICATION_OBJECT( CVConfigApp ); |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// The application object |
|
//----------------------------------------------------------------------------- |
|
bool CVConfigApp::Create() |
|
{ |
|
SpewOutputFunc( VConfig_SpewOutputFunc ); |
|
|
|
// If they pass in -game, just set the registry key to the value they asked for. |
|
const char *pSetGame = CommandLine()->ParmValue( "-game" ); |
|
if ( pSetGame ) |
|
{ |
|
SetMayaScriptSettings( ); |
|
SetXSIScriptSettings( ); |
|
SetPathSettings( ); |
|
SetVConfigRegistrySetting( GAMEDIR_TOKEN, pSetGame ); |
|
return false; |
|
} |
|
|
|
AppSystemInfo_t appSystems[] = |
|
{ |
|
{ "inputsystem.dll", INPUTSYSTEM_INTERFACE_VERSION }, |
|
{ "vgui2.dll", VGUI_IVGUI_INTERFACE_VERSION }, |
|
{ "", "" } // Required to terminate the list |
|
}; |
|
|
|
return AddSystems( appSystems ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Pre-init |
|
//----------------------------------------------------------------------------- |
|
bool CVConfigApp::PreInit() |
|
{ |
|
if ( !BaseClass::PreInit() ) |
|
return false; |
|
|
|
// Create a window to capture messages |
|
CreateMessageWindow(); |
|
|
|
// Make sure we're using the proper environment variable |
|
ConvertObsoleteVConfigRegistrySetting( GAMEDIR_TOKEN ); |
|
|
|
FileSystem_SetErrorMode( FS_ERRORMODE_AUTO ); |
|
|
|
// We only want to use the gameinfo.txt that is in the bin\vconfig directory. |
|
char dirName[MAX_PATH]; |
|
Q_strncpy( dirName, GetBaseDirectory(), sizeof( dirName ) ); |
|
Q_AppendSlash( dirName, sizeof( dirName ) ); |
|
Q_strncat( dirName, "vconfig", sizeof( dirName ), COPY_ALL_CHARACTERS ); |
|
|
|
if ( !SetupSearchPaths( dirName, true, true ) ) |
|
{ |
|
::MessageBox( NULL, "Error", "Unable to initialize file system\n", MB_OK ); |
|
return false; |
|
} |
|
|
|
// Load our configs |
|
if ( g_ConfigManager.LoadConfigs() == false ) |
|
{ |
|
::MessageBox( NULL, "Error", "Unable to load configuration file\n", MB_OK ); |
|
return false; |
|
} |
|
|
|
// Parse them for internal use |
|
if ( ParseConfigs() == false ) |
|
{ |
|
::MessageBox( NULL, "Error", "Unable to parse configuration file\n", MB_OK ); |
|
return false; |
|
} |
|
|
|
// Start looking for file updates |
|
UpdateConfigsStatus_Init(); |
|
|
|
// the "base dir" so we can scan mod name |
|
g_pFullFileSystem->AddSearchPath( GetBaseDirectory(), VCONFIG_MAIN_PATH_ID ); |
|
|
|
// the main platform dir |
|
g_pFullFileSystem->AddSearchPath( "platform","PLATFORM", PATH_ADD_TO_HEAD ); |
|
|
|
return true; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Pre-init |
|
//----------------------------------------------------------------------------- |
|
void CVConfigApp::PostShutdown() |
|
{ |
|
// Stop our message window |
|
ShutdownMessageWindow(); |
|
|
|
// Clear our configs |
|
g_Configs.PurgeAndDeleteElements(); |
|
|
|
// Stop file notifications |
|
UpdateConfigsStatus_Shutdown(); |
|
|
|
BaseClass::PostShutdown(); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Main function |
|
//----------------------------------------------------------------------------- |
|
int CVConfigApp::Main() |
|
{ |
|
if ( !InitializeVGUI() ) |
|
return 0; |
|
|
|
SteamAPI_InitSafe(); |
|
SteamAPI_SetTryCatchCallbacks( false ); // We don't use exceptions, so tell steam not to use try/catch in callback handlers |
|
g_SteamAPIContext.Init(); |
|
|
|
GetEngineVersion( g_engineDir, sizeof( g_engineDir ) ); |
|
|
|
// Run the app |
|
while ( vgui::ivgui()->IsRunning() ) |
|
{ |
|
Sleep( 10 ); |
|
UpdateConfigsStatus(); |
|
vgui::ivgui()->RunFrame(); |
|
} |
|
|
|
ShutdownVGUI(); |
|
|
|
return 1; |
|
} |
|
|
|
|