source-engine/utils/vconfig/main.cpp

647 lines
18 KiB
C++
Raw Normal View History

2020-04-22 16:56:21 +00:00
//========= 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;
}