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