|
|
//========= 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; }
|