summaryrefslogtreecommitdiff
path: root/sdklauncher/Main.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'sdklauncher/Main.cpp')
-rw-r--r--sdklauncher/Main.cpp684
1 files changed, 684 insertions, 0 deletions
diff --git a/sdklauncher/Main.cpp b/sdklauncher/Main.cpp
new file mode 100644
index 0000000..6a6b443
--- /dev/null
+++ b/sdklauncher/Main.cpp
@@ -0,0 +1,684 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//===========================================================================//
+
+#include <vgui/ILocalize.h>
+#include <vgui/IScheme.h>
+#include <vgui/ISurface.h>
+#include <vgui/IVGui.h>
+#include <vgui/IInput.h>
+#include <vgui/isystem.h>
+
+#include <vgui_controls/MessageBox.h>
+#include <vgui_controls/Controls.h>
+#include <vgui_controls/Panel.h>
+
+#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 <io.h>
+#include <stdio.h>
+
+// Since windows redefines MessageBox.
+typedef vgui::MessageBox vguiMessageBox;
+
+#include <winsock2.h>
+#include "steam/steam_api.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include <tier0/memdbgon.h>
+
+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<char> g_FileData;
+CUtlVector<char> g_ReplacementData[2];
+
+CUtlVector<char>* 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<char> *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<char> *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;
+}
+