source-engine/sdklauncher/Main.cpp

685 lines
18 KiB
C++
Raw Permalink Normal View History

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