//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: // //===========================================================================// #include #include #include #include #include #include #include #include #include #include "SDKLauncherDialog.h" #include "appframework/tier3app.h" #include "tier0/icommandline.h" #include "filesystem_tools.h" #include "sdklauncher_main.h" #include "configs.h" #include "min_footprint_files.h" #include "CreateModWizard.h" #include "inputsystem/iinputsystem.h" #include #include // Since windows redefines MessageBox. typedef vgui::MessageBox vguiMessageBox; #include #include "steam/steam_api.h" // memdbgon must be the last include file in a .cpp file!!! #include HANDLE g_dwChangeHandle = NULL; #define DEFAULTGAMEDIR_KEYNAME "DefaultGameDir" // Dummy window static WNDCLASS staticWndclass = { NULL }; static ATOM staticWndclassAtom = 0; static HWND staticHwnd = 0; CSteamAPIContext g_SteamAPIContext; CSteamAPIContext *steamapicontext = &g_SteamAPIContext; // This is the base engine + mod-specific game dir (e.g. "c:\tf2\mytfmod\") char gamedir[1024]; extern char g_engineDir[50]; CSDKLauncherDialog *g_pMainFrame = 0; bool g_bAutoHL2Mod = false; bool g_bModWizard_CmdLineFields = false; char g_ModWizard_CmdLine_ModDir[MAX_PATH]; char g_ModWizard_CmdLine_ModName[256]; bool g_bAppQuit = false; //----------------------------------------------------------------------------- // 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 ) { char szCurrentGame[MAX_PATH]; // Get VCONFIG from the registry GetVConfigRegistrySetting( GAMEDIR_TOKEN, szCurrentGame, sizeof( szCurrentGame ) ); g_pMainFrame->SetCurrentGame( szCurrentGame ); } } return ::DefWindowProc(hwnd,msg,wparam,lparam); } const char* GetLastWindowsErrorString() { static char err[2048]; LPVOID lpMsgBuf; FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language (LPTSTR) &lpMsgBuf, 0, NULL ); strncpy( err, (char*)lpMsgBuf, sizeof( err ) ); LocalFree( lpMsgBuf ); err[ sizeof( err ) - 1 ] = 0; return err; } //----------------------------------------------------------------------------- // 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 = "SDKLauncher_Window"; staticWndclassAtom = ::RegisterClass( &staticWndclass ); // Create an empty window just for message handling staticHwnd = CreateWindowEx(0, "SDKLauncher_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)); } SpewRetval_t SDKLauncherSpewOutputFunc( SpewType_t spewType, char const *pMsg ) { #ifdef _WIN32 OutputDebugString( pMsg ); #endif if (spewType == SPEW_ERROR) { // In Windows vgui mode, make a message box or they won't ever see the error. #ifdef _WIN32 MessageBox( NULL, pMsg, "Error", MB_OK | MB_TASKMODAL ); TerminateProcess( GetCurrentProcess(), 1 ); #elif _LINUX _exit(1); #else #error "Implement me" #endif return SPEW_ABORT; } if (spewType == SPEW_ASSERT) { if ( CommandLine()->FindParm( "-noassert" ) == 0 ) return SPEW_DEBUGGER; else return SPEW_CONTINUE; } return SPEW_CONTINUE; } const char* GetSDKLauncherBinDirectory() { 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; } const char* GetSDKToolsBinDirectory( ) { 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. V_strncat( path, g_engineDir, sizeof( path ) ); V_strncat( path, "\\bin", sizeof( path ) ); } return path; } const char* GetSDKLauncherBaseDirectory() { static char basedir[512] = {0}; if ( basedir[0] == 0 ) { Q_strncpy( basedir, GetSDKLauncherBinDirectory(), sizeof( basedir ) ); Q_StripLastDir( basedir, sizeof( basedir ) ); // Get rid of the bin directory. Q_StripTrailingSlash( basedir ); } return basedir; } void SubstituteBaseDir( const char *pIn, char *pOut, int outLen ) { Q_StrSubst( pIn, "%basedir%", GetSDKLauncherBaseDirectory(), pOut, outLen ); } CUtlVector g_FileData; CUtlVector g_ReplacementData[2]; CUtlVector* GetFileStringWithReplacements( const char *pInputFilename, const char **ppReplacements, int nReplacements, int &dataWriteLen ) { Assert( nReplacements % 2 == 0 ); // Read in the file data. FileHandle_t hFile = g_pFullFileSystem->Open( pInputFilename, "rb" ); if ( !hFile ) { return false; } g_FileData.SetSize( g_pFullFileSystem->Size( hFile ) ); g_pFullFileSystem->Read( g_FileData.Base(), g_FileData.Count(), hFile ); g_pFullFileSystem->Close( hFile ); CUtlVector *pCurData = &g_FileData; dataWriteLen = g_FileData.Count(); if ( nReplacements ) { // Null-terminate it. g_FileData.AddToTail( 0 ); // Apply all the string substitutions. int iCurCount = g_FileData.Count() * 2; g_ReplacementData[0].EnsureCount( iCurCount ); g_ReplacementData[1].EnsureCount( iCurCount ); for ( int i=0; i < nReplacements/2; i++ ) { for ( int iTestCount=0; iTestCount < 64; iTestCount++ ) { if ( Q_StrSubst( pCurData->Base(), ppReplacements[i*2], ppReplacements[i*2+1], g_ReplacementData[i&1].Base(), g_ReplacementData[i&1].Count() ) ) break; // Ok, we would overflow the string.. add more space to do the string substitution into. iCurCount += 2048; g_ReplacementData[0].EnsureCount( iCurCount ); g_ReplacementData[1].EnsureCount( iCurCount ); } pCurData = &g_ReplacementData[i&1]; dataWriteLen = strlen( pCurData->Base() ); } } return pCurData; } bool CopyWithReplacements( const char *pInputFilename, const char **ppReplacements, int nReplacements, const char *pOutputFilenameFormat, ... ) { int dataWriteLen; CUtlVector *pCurData = GetFileStringWithReplacements( pInputFilename, ppReplacements, nReplacements, dataWriteLen ); if ( !pCurData ) { char msg[512]; Q_snprintf( msg, sizeof( msg ), "Can't open %s for reading.", pInputFilename ); ::MessageBox( NULL, msg, "Error", MB_OK ); return false; } // Get the output filename. char outFilename[MAX_PATH]; va_list marker; va_start( marker, pOutputFilenameFormat ); Q_vsnprintf( outFilename, sizeof( outFilename ), pOutputFilenameFormat, marker ); va_end( marker ); // Write it out. I'd like to use IFileSystem, but Steam lowercases all filenames, which screws case-sensitive linux // (since the linux makefiles are tuned to the casing in Perforce). FILE *hFile = fopen( outFilename, "wb" ); if ( !hFile ) { char msg[512]; Q_snprintf( msg, sizeof( msg ), "Can't open %s for writing.", outFilename ); ::MessageBox( NULL, msg, "Error", MB_OK ); return false; } fwrite( pCurData->Base(), 1, dataWriteLen, hFile ); fclose( hFile ); return true; } int InitializeVGui() { vgui::ivgui()->SetSleep(false); // find our configuration directory char szConfigDir[512]; const char *steamPath = getenv("SteamInstallPath"); if (steamPath) { // put the config dir directly under steam Q_snprintf(szConfigDir, sizeof(szConfigDir), "%s/config", steamPath); } else { // we're not running steam, so just put the config dir under the platform Q_strncpy( szConfigDir, "platform/config", sizeof(szConfigDir)); } g_pFullFileSystem->CreateDirHierarchy("config", "PLATFORM"); g_pFullFileSystem->AddSearchPath(szConfigDir, "CONFIG", PATH_ADD_TO_HEAD); // initialize the user configuration file vgui::system()->SetUserConfigFile("DedicatedServerDialogConfig.vdf", "CONFIG"); // 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("Resource/sdklauncher_scheme.res", NULL); // localization g_pVGuiLocalize->AddFile( "resource/platform_english.txt" ); g_pVGuiLocalize->AddFile( "vgui/resource/vgui_english.txt" ); g_pVGuiLocalize->AddFile( "sdklauncher_english.txt" ); // Start vgui vgui::ivgui()->Start(); // add our main window g_pMainFrame = new CSDKLauncherDialog(pPanel, "SDKLauncherDialog"); // show main window g_pMainFrame->MoveToCenterOfScreen(); g_pMainFrame->Activate(); return 0; } void ShutdownVGui() { delete g_pMainFrame; } KeyValues* LoadGameDirsFile() { char filename[MAX_PATH]; Q_snprintf( filename, sizeof( filename ), "%ssdklauncher_gamedirs.txt", gamedir ); KeyValues *dataFile = new KeyValues("gamedirs"); dataFile->UsesEscapeSequences( true ); dataFile->LoadFromFile( g_pFullFileSystem, filename, NULL ); return dataFile; } bool SaveGameDirsFile( KeyValues *pFile ) { char filename[MAX_PATH]; Q_snprintf( filename, sizeof( filename ), "%ssdklauncher_gamedirs.txt", gamedir ); return pFile->SaveToFile( g_pFullFileSystem, filename ); } class CModalPreserveMessageBox : public vguiMessageBox { public: CModalPreserveMessageBox(const char *title, const char *text, vgui::Panel *parent) : vguiMessageBox( title, text, parent ) { m_PrevAppFocusPanel = vgui::input()->GetAppModalSurface(); } ~CModalPreserveMessageBox() { vgui::input()->SetAppModalSurface( m_PrevAppFocusPanel ); } public: vgui::VPANEL m_PrevAppFocusPanel; }; void VGUIMessageBox( vgui::Panel *pParent, const char *pTitle, const char *pMsg, ... ) { char msg[4096]; va_list marker; va_start( marker, pMsg ); Q_vsnprintf( msg, sizeof( msg ), pMsg, marker ); va_end( marker ); vguiMessageBox *dlg = new CModalPreserveMessageBox( pTitle, msg, pParent ); dlg->DoModal(); dlg->RequestFocus(); } //----------------------------------------------------------------------------- // 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, GetSDKLauncherBinDirectory(), sizeof( szConfigDir ) ); Q_strncat ( szConfigDir, "\\", MAX_PATH ); Q_strncat ( szConfigDir, g_engineDir, MAX_PATH ); Q_strncat ( szConfigDir, "\\bin", MAX_PATH ); g_dwChangeHandle = FindFirstChangeNotification( szConfigDir, // directory to watch false, // watch the subtree (FILE_NOTIFY_CHANGE_DIR_NAME|FILE_NOTIFY_CHANGE_FILE_NAME|FILE_NOTIFY_CHANGE_LAST_WRITE|FILE_NOTIFY_CHANGE_SIZE|FILE_NOTIFY_CHANGE_ATTRIBUTES)); // watch file and dir name changes if ( g_dwChangeHandle == INVALID_HANDLE_VALUE ) { // FIXME: Unable to watch the file } } } //----------------------------------------------------------------------------- // 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 ) { g_pMainFrame->RefreshConfigs(); } // 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 ); } void QuickLaunchCommandLine( char *pCommandLine ) { 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, pCommandLine, NULL, // security NULL, TRUE, dwFlags, // flags NULL, // environment GetSDKLauncherBaseDirectory(), // current directory &si, &pi ) ) { ::MessageBoxA( NULL, GetLastWindowsErrorString(), "Error", MB_OK | MB_ICONINFORMATION | MB_APPLMODAL ); } } bool RunQuickLaunch() { char cmdLine[512]; if ( CommandLine()->FindParm( "-runhammer" ) ) { Q_snprintf( cmdLine, sizeof( cmdLine ), "\"%s\\%s\\bin\\hammer.exe\"", GetSDKLauncherBinDirectory(), g_engineDir ); QuickLaunchCommandLine( cmdLine ); return true; } else if ( CommandLine()->FindParm( "-runmodelviewer" ) ) { Q_snprintf( cmdLine, sizeof( cmdLine ), "\"%s\\%s\\bin\\hlmv.exe\"", GetSDKLauncherBinDirectory(), g_engineDir ); QuickLaunchCommandLine( cmdLine ); return true; } else if ( CommandLine()->FindParm( "-runfaceposer" ) ) { Q_snprintf( cmdLine, sizeof( cmdLine ), "\"%s\\%s\\bin\\hlfaceposer.exe\"", GetSDKLauncherBinDirectory(), g_engineDir ); QuickLaunchCommandLine( cmdLine ); return true; } return false; } void CheckCreateModParameters() { if ( CommandLine()->FindParm( "-AutoHL2Mod" ) ) g_bAutoHL2Mod = true; int iParm = CommandLine()->FindParm( "-CreateMod" ); if ( iParm == 0 ) return; if ( (iParm + 2) < CommandLine()->ParmCount() ) { // Set it up so the mod wizard can skip the mod dir/mod name panel. g_bModWizard_CmdLineFields = true; Q_strncpy( g_ModWizard_CmdLine_ModDir, CommandLine()->GetParm( iParm + 1 ), sizeof( g_ModWizard_CmdLine_ModDir ) ); Q_strncpy( g_ModWizard_CmdLine_ModName, CommandLine()->GetParm( iParm + 2 ), sizeof( g_ModWizard_CmdLine_ModName ) ); RunCreateModWizard( true ); } } //----------------------------------------------------------------------------- // The application object //----------------------------------------------------------------------------- class CSDKLauncherApp : 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( CSDKLauncherApp ); //----------------------------------------------------------------------------- // The application object //----------------------------------------------------------------------------- bool CSDKLauncherApp::Create() { SpewOutputFunc( SDKLauncherSpewOutputFunc ); AppSystemInfo_t appSystems[] = { { "inputsystem.dll", INPUTSYSTEM_INTERFACE_VERSION }, { "vgui2.dll", VGUI_IVGUI_INTERFACE_VERSION }, { "", "" } // Required to terminate the list }; return AddSystems( appSystems ); } //----------------------------------------------------------------------------- // Purpose: Entry point //----------------------------------------------------------------------------- bool CSDKLauncherApp::PreInit() { if ( !BaseClass::PreInit() ) return false; // Make sure we're using the proper environment variable ConvertObsoleteVConfigRegistrySetting( GAMEDIR_TOKEN ); if ( !CommandLine()->ParmValue( "-game" ) ) { Error( "SDKLauncher requires -game on the command line." ); return false; } // winsock aware WSAData wsaData; WSAStartup( MAKEWORD(2,0), &wsaData ); // Create a window to capture messages CreateMessageWindow(); FileSystem_SetErrorMode( FS_ERRORMODE_AUTO ); if ( !BaseClass::SetupSearchPaths( NULL, false, true ) ) { ::MessageBox( NULL, "Error", "Unable to initialize file system\n", MB_OK ); return false; } // Set gamedir. Q_MakeAbsolutePath( gamedir, sizeof( gamedir ), GetGameInfoPath() ); Q_AppendSlash( gamedir, sizeof( gamedir ) ); // the "base dir" so we can scan mod name g_pFullFileSystem->AddSearchPath(GetSDKLauncherBaseDirectory(), SDKLAUNCHER_MAIN_PATH_ID); // the main platform dir g_pFullFileSystem->AddSearchPath("platform","PLATFORM", PATH_ADD_TO_HEAD); return true; } void CSDKLauncherApp::PostShutdown() { // Stop our message window ShutdownMessageWindow(); ::WSACleanup(); BaseClass::PostShutdown(); } //----------------------------------------------------------------------------- // Purpose: Entry point //----------------------------------------------------------------------------- int CSDKLauncherApp::Main() { SetVConfigRegistrySetting( "sourcesdk", GetSDKLauncherBaseDirectory() ); // If they just want to run Hammer or hlmv, just do that and exit. if ( RunQuickLaunch() ) return 1; // Run app frame loop int ret = InitializeVGui(); if ( ret != 0 ) return ret; DumpMinFootprintFiles( false ); SteamAPI_InitSafe(); SteamAPI_SetTryCatchCallbacks( false ); // We don't use exceptions, so tell steam not to use try/catch in callback handlers g_SteamAPIContext.Init(); // Start looking for file updates // UpdateConfigsStatus_Init(); // Check if they want to run the Create Mod wizard right off the bat. CheckCreateModParameters(); while ( vgui::ivgui()->IsRunning() && !g_bAppQuit ) { Sleep( 10 ); // UpdateConfigsStatus(); vgui::ivgui()->RunFrame(); } ShutdownVGui(); // UpdateConfigsStatus_Shutdown(); return 1; }