//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: // //=============================================================================// #include #include "SDKLauncherDialog.h" #include "configs.h" #include #include #include #include #include #include #include #include #include #include #include "vgui_controls/button.h" #include #include #include #include #include #include #include "CreateModWizard.h" #include "filesystem_tools.h" #include "min_footprint_files.h" #include "ConfigManager.h" #include "filesystem_tools.h" #include #include #include "sdklauncher_main.h" // memdbgon must be the last include file in a .cpp file!!! #include using namespace vgui; CSDKLauncherDialog *g_pSDKLauncherDialog = NULL; CGameConfigManager g_ConfigManager; char g_engineDir[50]; //----------------------------------------------------------------------------- // Purpose: Retrieves a URL out of the external localization file and opens it in a // web browser. // Input : *lpszLocalName - Localized name of the URL to open via shell execution //----------------------------------------------------------------------------- void OpenLocalizedURL( const char *lpszLocalName ) { // Find and convert the localized unicode string char pURLStr[MAX_PATH]; wchar_t *pURLUni = g_pVGuiLocalize->Find( lpszLocalName ); g_pVGuiLocalize->ConvertUnicodeToANSI( pURLUni, pURLStr, sizeof( pURLStr ) ); // Open the URL through the shell vgui::system()->ShellExecute( "open", pURLStr ); } class CResetConfirmationMessageBox : public vgui::Frame { public: typedef vgui::Frame BaseClass; CResetConfirmationMessageBox( Panel *pParent, const char *pPanelName ) : BaseClass( pParent, pPanelName ) { SetSize( 200, 200 ); SetMinimumSize( 250, 100 ); SetSizeable( false ); LoadControlSettings( "resetconfirmbox.res" ); } void OnCommand( const char *command ) { if ( Q_stricmp( command, "ResetConfigs" ) == 0 ) { Close(); if ( GetVParent() ) { PostMessage( GetVParent(), new KeyValues( "Command", "command", "ResetConfigs")); } } else if ( Q_stricmp( command, "MoreInfo" ) == 0 ) { OpenLocalizedURL( "URL_Reset_Config" ); } BaseClass::OnCommand( command ); } }; class CConversionInfoMessageBox : public vgui::Frame { public: typedef vgui::Frame BaseClass; CConversionInfoMessageBox( Panel *pParent, const char *pPanelName ) : BaseClass( pParent, pPanelName ) { SetSize( 200, 200 ); SetMinimumSize( 250, 100 ); SetSizeable( false ); LoadControlSettings( "convinfobox.res" ); } virtual void OnCommand( const char *command ) { BaseClass::OnCommand( command ); // For some weird reason, this dialog can if ( Q_stricmp( command, "ShowFAQ" ) == 0 ) { OpenLocalizedURL( "URL_Convert_INI" ); } } }; class CGameInfoMessageBox : public vgui::Frame { public: typedef vgui::Frame BaseClass; CGameInfoMessageBox( Panel *pParent, const char *pPanelName ) : BaseClass( pParent, pPanelName ) { SetSize( 200, 200 ); SetMinimumSize( 250, 100 ); LoadControlSettings( "hl2infobox.res" ); } virtual void OnCommand( const char *command ) { BaseClass::OnCommand( command ); // For some weird reason, this dialog can if ( Q_stricmp( command, "RunAnyway" ) == 0 ) { m_pDialog->Launch( m_iActiveItem, true ); MarkForDeletion(); } else if ( Q_stricmp( command, "Troubleshooting" ) == 0 ) { OpenLocalizedURL( "URL_SDK_FAQ" ); MarkForDeletion(); } } CSDKLauncherDialog *m_pDialog; int m_iActiveItem; }; class CMinFootprintRefreshConfirmationDialog : public vgui::Frame { public: typedef vgui::Frame BaseClass; CMinFootprintRefreshConfirmationDialog( Panel *pParent, const char *pPanelName ) : BaseClass( pParent, pPanelName ) { SetSizeable( false ); LoadControlSettings( "min_footprint_confirm_box.res" ); m_hOldModalSurface = input()->GetAppModalSurface(); input()->SetAppModalSurface( GetVPanel() ); } ~CMinFootprintRefreshConfirmationDialog() { input()->SetAppModalSurface( m_hOldModalSurface ); } void OnCommand( const char *command ) { BaseClass::OnCommand( command ); if ( Q_stricmp( command, "Continue" ) == 0 ) { PostMessage( g_pSDKLauncherDialog, new KeyValues( "Command", "command", "RefreshMinFootprint" ) ); MarkForDeletion(); } else if ( Q_stricmp( command, "Close" ) == 0 ) { MarkForDeletion(); } } VPANEL m_hOldModalSurface; }; //----------------------------------------------------------------------------- // Purpose: Constructor //----------------------------------------------------------------------------- CSDKLauncherDialog::CSDKLauncherDialog(vgui::Panel *parent, const char *name) : BaseClass(parent, name) { Assert( !g_pSDKLauncherDialog ); g_pSDKLauncherDialog = this; SetSize(384, 480); SetMinimumSize(250, 200); SetMinimizeButtonVisible( true ); m_pImageList = new ImageList( true ); m_pMediaList = new SectionedListPanel(this, "MediaList"); m_pMediaList->AddActionSignalTarget( this ); m_pMediaList->SetImageList( m_pImageList, true ); m_pContextMenu = new Menu( m_pMediaList, "AppsContextMenu" ); m_pCurrentGameCombo = new ComboBox( this, "CurrentGameList", 8, false ); m_pCurrentGameCombo->AddActionSignalTarget( this ); m_pCurrentEngineCombo = new ComboBox( this, "CurrentEngineList", 4, false ); m_pCurrentEngineCombo->AddActionSignalTarget( this ); PopulateMediaList(); LoadControlSettings( "SDKLauncherDialog.res" ); GetEngineVersion( g_engineDir, sizeof( g_engineDir ) ); PopulateCurrentEngineCombo( false ); RefreshConfigs(); } CSDKLauncherDialog::~CSDKLauncherDialog() { delete m_pMediaList; g_pSDKLauncherDialog = NULL; } //----------------------------------------------------------------------------- // Purpose: kills the whole app on close //----------------------------------------------------------------------------- void CSDKLauncherDialog::OnClose() { BaseClass::OnClose(); ivgui()->Stop(); } //----------------------------------------------------------------------------- // Purpose: loads media list from file //----------------------------------------------------------------------------- void CSDKLauncherDialog::PopulateMediaList() { KeyValues *dataFile = new KeyValues("Media"); dataFile->UsesEscapeSequences( true ); if (dataFile->LoadFromFile( g_pFullFileSystem, "MediaList.txt", NULL ) ) { // Load all the images. KeyValues *pImages = dataFile->FindKey( "Images" ); if ( !pImages ) Error( "MediaList.txt missing Images key." ); for ( KeyValues *pCur=pImages->GetFirstTrueSubKey(); pCur; pCur=pCur->GetNextTrueSubKey() ) { IImage *pImage = scheme()->GetImage( pCur->GetString( "Image", "" ), false ); int iIndex = pCur->GetInt( "Index", -1 ); if ( pImage && iIndex != -1 ) { m_pImageList->SetImageAtIndex( iIndex, pImage ); } } // Load all the sections. KeyValues *pSections = dataFile->FindKey( "Sections" ); if ( !pSections ) Error( "MediaList.txt missing Sections key." ); for ( KeyValues *pSection=pSections->GetFirstTrueSubKey(); pSection; pSection=pSection->GetNextTrueSubKey() ) { int id = pSection->GetInt( "id" ); const char *pName = pSection->GetString( "Name" ); m_pMediaList->AddSection( id, pName ); m_pMediaList->AddColumnToSection( id, "Image", "", SectionedListPanel::COLUMN_IMAGE, 20 ); m_pMediaList->AddColumnToSection( id, "Text", pName, 0, 200 ); // Set all the rows. for ( KeyValues *kv = pSection->GetFirstTrueSubKey(); kv != NULL; kv=kv->GetNextTrueSubKey() ) { m_pMediaList->AddItem( id, kv ); } } } dataFile->deleteThis(); } void CSDKLauncherDialog::Launch( int hActiveListItem, bool bForce ) { if (!m_pMediaList->IsItemIDValid(hActiveListItem)) return; // display the file KeyValues *item = m_pMediaList->GetItemData( hActiveListItem ); if ( !item ) return; // Is this a ShellExecute or a program they want to run? const char *pStr = item->GetString( "InlineProgram", NULL ); if ( pStr ) { if ( Q_stricmp( pStr, "CreateMod" ) == 0 ) { if ( !V_stricmp( g_engineDir, "ep1" ) || !V_stricmp( g_engineDir, "source2007" ) ) { RunCreateModWizard( false ); } else { VGUIMessageBox( this, "Unable to Run 'Create A Mod' Wizard", "Support for creating total conversions are not available using this engine versions." ); } } else if ( Q_stricmp( pStr, "refresh_min_footprint" ) == 0 ) { CMinFootprintRefreshConfirmationDialog *pDlg = new CMinFootprintRefreshConfirmationDialog( this, "RefreshConfirmation" ); pDlg->RequestFocus(); pDlg->SetVisible( true ); pDlg->MoveToCenterOfScreen(); } else if ( Q_stricmp( pStr, "reset_configs" ) == 0 ) { CResetConfirmationMessageBox *pDlg = new CResetConfirmationMessageBox( this, "ResetConfirmation" ); pDlg->RequestFocus(); pDlg->SetVisible( true ); pDlg->MoveToCenterOfScreen(); input()->SetAppModalSurface( pDlg->GetVPanel() ); } else { Error( "Unknown InlineProgram value: %s", pStr ); } return; } pStr = item->GetString( "ShellExecute", NULL ); if ( pStr ) { // Replace tokens we know about like %basedir%. system()->ShellExecute( "open", pStr ); return; } pStr = item->GetString( "Program", NULL ); if ( pStr ) { // Get our current config. KeyValues *kv = m_pCurrentGameCombo->GetActiveItemUserData(); if ( !kv ) { VGUIMessageBox( this, "Error", "No game configurations to run with." ); return; } const char *pModDir = kv->GetString( "ModDir", NULL ); if ( !pModDir ) { VGUIMessageBox( this, "Error", "Missing 'ModDir' key/value." ); return; } bool bRun = true; if ( !bForce && Q_stristr( pStr, "%gamedir%" ) ) { // Make sure the currently-selected gamedir is valid and has a gameinfo.txt in it. char testStr[MAX_PATH]; Q_strncpy( testStr, pModDir, sizeof( testStr ) ); Q_AppendSlash( testStr, sizeof( testStr ) ); Q_strncat( testStr, "gameinfo.txt", sizeof( testStr ), COPY_ALL_CHARACTERS ); if ( _access( testStr, 0 ) != 0 ) { CGameInfoMessageBox *dlg = new CGameInfoMessageBox( this, "GameInfoMessageBox" ); dlg->m_pDialog = this; dlg->m_iActiveItem = hActiveListItem; dlg->RequestFocus(); dlg->SetVisible( true ); dlg->MoveToCenterOfScreen(); input()->SetAppModalSurface( dlg->GetVPanel() ); bRun = false; } } if ( bRun ) { // Get the program name and its arguments. char programNameTemp[1024], programName[1024], launchDirectory[1024]; SubstituteBaseDir( pStr, programNameTemp, sizeof( programNameTemp ) ); V_StrSubst( programNameTemp, "%gamedir%", pModDir, programName, sizeof( programName ) ); V_strncpy( programNameTemp, programName, sizeof( programNameTemp ) ); V_StrSubst( programNameTemp, "%enginedir%", g_engineDir , programName, sizeof( programName ) ); V_strncpy( launchDirectory, GetSDKLauncherBaseDirectory(), sizeof( launchDirectory ) ); V_strncat( launchDirectory, "\\bin\\", sizeof( launchDirectory ) ); V_strncat( launchDirectory, g_engineDir, sizeof( launchDirectory ) ); // Check to see if we're running in tools mode if ( NULL != V_strstr( programName, "-tools" ) ) { // We can't run tools mode in engine versions earlier than OB if ( !V_strcmp( g_engineDir, "ep1" ) ) { VGUIMessageBox( this, "Error", "Source Engine Tools is not compatible with the selected engine version." ); return; } // If we are running the engine tools then change our launch directory to the game directory V_strncpy( launchDirectory, pModDir, sizeof( launchDirectory ) ); V_StripLastDir( launchDirectory, sizeof( launchDirectory ) ); V_strncat( launchDirectory, "bin", sizeof( launchDirectory ) ); } STARTUPINFO si; memset( &si, 0, sizeof( si ) ); si.cb = sizeof( si ); PROCESS_INFORMATION pi; memset( &pi, 0, sizeof( pi ) ); DWORD dwFlags = 0; if ( !CreateProcess( 0, programName, NULL, // security NULL, TRUE, dwFlags, // flags NULL, // environment launchDirectory, // current directory &si, &pi ) ) { ::MessageBoxA( NULL, GetLastWindowsErrorString(), "Error", MB_OK | MB_ICONINFORMATION | MB_APPLMODAL ); } } } } void CSDKLauncherDialog::OnCommand( const char *command ) { if ( Q_stricmp( command, "LaunchButton" ) == 0 ) { Launch( m_pMediaList->GetSelectedItem(), false ); } else if ( Q_stricmp( command, "ResetConfigs" ) == 0 ) { ResetConfigs(); } else if ( Q_stricmp( command, "RefreshMinFootprint" ) == 0 ) { DumpMinFootprintFiles( true ); } BaseClass::OnCommand( command ); } void CSDKLauncherDialog::OnItemDoubleLeftClick( int iItem ) { Launch( iItem, false ); } void CSDKLauncherDialog::OnItemContextMenu( int hActiveListItem ) { if (!m_pMediaList->IsItemIDValid(hActiveListItem)) return; // display the file KeyValues *item = m_pMediaList->GetItemData( hActiveListItem ); if ( !item ) return; // Build the context menu. m_pContextMenu->DeleteAllItems(); // Is this a ShellExecute or a program they want to run? const char *pStr = item->GetString( "ShellExecute", NULL ); if ( pStr ) m_pContextMenu->AddMenuItem( "RunGame", "Open In Web Browser", new KeyValues("ItemDoubleLeftClick", "itemID", hActiveListItem), this); else m_pContextMenu->AddMenuItem( "RunGame", "Launch", new KeyValues("ItemDoubleLeftClick", "itemID", hActiveListItem), this); int menuWide, menuTall; m_pContextMenu->SetVisible(true); m_pContextMenu->InvalidateLayout(true); m_pContextMenu->GetSize(menuWide, menuTall); // work out where the cursor is and therefore the best place to put the menu int wide, tall; surface()->GetScreenSize(wide, tall); int cx, cy; input()->GetCursorPos(cx, cy); if (wide - menuWide > cx) { // menu hanging right if (tall - menuTall > cy) { // menu hanging down m_pContextMenu->SetPos(cx, cy); } else { // menu hanging up m_pContextMenu->SetPos(cx, cy - menuTall); } } else { // menu hanging left if (tall - menuTall > cy) { // menu hanging down m_pContextMenu->SetPos(cx - menuWide, cy); } else { // menu hanging up m_pContextMenu->SetPos(cx - menuWide, cy - menuTall); } } m_pContextMenu->RequestFocus(); m_pContextMenu->MoveToFront(); } bool CSDKLauncherDialog::ParseConfigs( CUtlVector &configs ) { if ( !g_ConfigManager.IsLoaded() ) return false; // Find the games block of the keyvalues KeyValues *gameBlock = g_ConfigManager.GetGameBlock(); if ( gameBlock == NULL ) { 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(); UtlStrcpy( newConfig->m_Name, pName ); UtlStrcpy( newConfig->m_ModDir, pDir ); configs.AddToTail( newConfig ); } return true; } void CSDKLauncherDialog::PopulateCurrentEngineCombo( bool bSelectLast ) { int nActiveEngine = 0; m_pCurrentEngineCombo->DeleteAllItems(); // Add ep1 engine KeyValues *kv = new KeyValues( "values" ); kv = new KeyValues( "values" ); kv->SetString( "EngineVer", "ep1" ); m_pCurrentEngineCombo->AddItem( "Source Engine 2006", kv ); kv->deleteThis(); if ( !V_strcmp( g_engineDir, "ep1" ) ) { nActiveEngine = 0; } // Add ep2 engine kv = new KeyValues( "values" ); kv->SetString( "EngineVer", "source2007" ); m_pCurrentEngineCombo->AddItem( "Source Engine 2007", kv ); kv->deleteThis(); if ( !V_strcmp( g_engineDir, "source2007" ) ) { nActiveEngine = 1; } // Add SP engine kv = new KeyValues( "values" ); kv->SetString( "EngineVer", "source2009" ); m_pCurrentEngineCombo->AddItem( "Source Engine 2009", kv ); kv->deleteThis(); if ( !V_strcmp( g_engineDir, "source2009" ) ) { nActiveEngine = 2; } // Add MP engine kv = new KeyValues( "values" ); kv->SetString( "EngineVer", "orangebox" ); m_pCurrentEngineCombo->AddItem( "Source Engine MP", kv ); kv->deleteThis(); if ( !V_strcmp( g_engineDir, "orangebox" ) ) { nActiveEngine = 3; } // Determine active configuration m_pCurrentEngineCombo->ActivateItem( nActiveEngine ); } void CSDKLauncherDialog::SetCurrentGame( const char* pcCurrentGame ) { int activeConfig = -1; for ( int i=0; i < m_pCurrentGameCombo->GetItemCount(); i++ ) { KeyValues *kv = m_pCurrentGameCombo->GetItemUserData( i ); // Check to see if this is our currently active game if ( Q_stricmp( kv->GetString( "ModDir" ), pcCurrentGame ) == 0 ) { activeConfig = i; continue; } } if ( activeConfig > -1 ) { // Activate our currently selected game m_pCurrentGameCombo->ActivateItem( activeConfig ); } else { // If the VCONFIG value for the game is not found then repopulate PopulateCurrentGameCombo( false ); } } void CSDKLauncherDialog::PopulateCurrentGameCombo( bool bSelectLast ) { m_pCurrentGameCombo->DeleteAllItems(); CUtlVector configs; ParseConfigs( configs ); char szGame[MAX_PATH]; GetVConfigRegistrySetting( GAMEDIR_TOKEN, szGame, sizeof( szGame ) ); int activeConfig = -1; CUtlVector itemIDs; itemIDs.SetSize( configs.Count() ); for ( int i=0; i < configs.Count(); i++ ) { KeyValues *kv = new KeyValues( "values" ); kv->SetString( "ModDir", configs[i]->m_ModDir.Base() ); kv->SetPtr( "panel", m_pCurrentGameCombo ); // Check to see if this is our currently active game if ( Q_stricmp( configs[i]->m_ModDir.Base(), szGame ) == 0 ) { activeConfig = i; } itemIDs[i] = m_pCurrentGameCombo->AddItem( configs[i]->m_Name.Base(), kv ); kv->deleteThis(); } // Activate the correct entry if valid if ( itemIDs.Count() > 0 ) { m_pCurrentGameCombo->SetEnabled( true ); if ( bSelectLast ) { m_pCurrentGameCombo->ActivateItem( itemIDs[itemIDs.Count()-1] ); } else { if ( activeConfig > -1 ) { // Activate our currently selected game m_pCurrentGameCombo->ActivateItem( activeConfig ); } else { // Always default to the first otherwise m_pCurrentGameCombo->ActivateItem( 0 ); } } } else { m_pCurrentGameCombo->SetEnabled( false ); } configs.PurgeAndDeleteElements(); } //----------------------------------------------------------------------------- // Purpose: Selection has changed in the active config drop-down, set the environment //----------------------------------------------------------------------------- void CSDKLauncherDialog::OnTextChanged( KeyValues *pkv ) { const vgui::ComboBox* combo = (vgui::ComboBox*)pkv->GetPtr("panel"); if ( combo == m_pCurrentGameCombo) { KeyValues *kv = m_pCurrentGameCombo->GetActiveItemUserData(); if ( kv ) { const char *modDir = kv->GetString( "ModDir" ); SetVConfigRegistrySetting( GAMEDIR_TOKEN, modDir, true ); } } else if ( combo == m_pCurrentEngineCombo ) { KeyValues *kv = m_pCurrentEngineCombo->GetActiveItemUserData(); if ( kv ) { const char *engineDir = kv->GetString( "EngineVer" ); SetEngineVersion( engineDir ); RefreshConfigs(); } } } //----------------------------------------------------------------------------- // Purpose: Refresh our configs after a file change outside our process //----------------------------------------------------------------------------- void CSDKLauncherDialog::RefreshConfigs( void ) { char szGameConfigDir[MAX_PATH]; V_strcpy_safe( szGameConfigDir, GetSDKLauncherBinDirectory() ); V_AppendSlash( szGameConfigDir, MAX_PATH ); V_strncat( szGameConfigDir, g_engineDir, MAX_PATH ); V_AppendSlash( szGameConfigDir, MAX_PATH ); V_strncat( szGameConfigDir, "bin", MAX_PATH ); // Set directory in which GameConfig.txt is found g_ConfigManager.SetBaseDirectory( szGameConfigDir ); // Tell the config manager which games to put in the config by default if ( !stricmp( g_engineDir, "ep1" ) ) { g_ConfigManager.SetSDKEpoch( EP1 ); } else if ( !stricmp( g_engineDir, "source2007" ) ) { g_ConfigManager.SetSDKEpoch( EP2 ); } else if ( !stricmp( g_engineDir, "source2009" ) ) { g_ConfigManager.SetSDKEpoch( SP2009 ); } else { g_ConfigManager.SetSDKEpoch( MP2009 ); } // Load configurations if ( g_ConfigManager.LoadConfigs( szGameConfigDir ) == false ) { m_pCurrentGameCombo->DeleteAllItems(); m_pCurrentGameCombo->SetEnabled( false ); } else { m_pCurrentGameCombo->SetEnabled( true ); if ( g_ConfigManager.WasConvertedOnLoad() ) { // Notify of a conversion CConversionInfoMessageBox *pDlg = new CConversionInfoMessageBox( this, "ConversionInfo" ); pDlg->RequestFocus(); pDlg->SetVisible( true ); pDlg->MoveToCenterOfScreen(); input()->SetAppModalSurface( pDlg->GetVPanel() ); } } // Dump the current settings and reparse the change PopulateCurrentGameCombo( false ); } //----------------------------------------------------------------------------- // Purpose: Reset our config files //----------------------------------------------------------------------------- void CSDKLauncherDialog::ResetConfigs( void ) { // Reset the configs g_ConfigManager.ResetConfigs(); // Refresh the listing PopulateCurrentGameCombo( false ); // Notify the user VGUIMessageBox( this, "Complete", "Your game configurations have successfully been reset to their default values." ); } void CSDKLauncherDialog::GetEngineVersion(char* pcEngineVer, int nSize) { IRegistry *reg = InstanceRegistry( "Source SDK" ); Assert( reg ); V_strncpy( pcEngineVer, reg->ReadString( "EngineVer", "orangebox" ), nSize ); ReleaseInstancedRegistry( reg ); } void CSDKLauncherDialog::SetEngineVersion(const char *pcEngineVer) { IRegistry *reg = InstanceRegistry( "Source SDK" ); Assert( reg ); reg->WriteString( "EngineVer", pcEngineVer ); ReleaseInstancedRegistry( reg ); // Set the global to the same value as the registry V_strncpy( g_engineDir, pcEngineVer, sizeof( g_engineDir ) ); }