Files
cool-source-archive/launcher/launcher.cpp
EnderZip 0f2710630c added launcher, still need custom launcher to work.
oh and changed the buildnum to something better
2021-09-19 09:35:10 +07:00

1321 lines
36 KiB
C++

//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// Defines the entry point for the application.
//
//===========================================================================//
#if defined( _WIN32 ) && !defined( _X360 )
#include <windows.h>
#include "shlwapi.h" // registry stuff
#include <direct.h>
#elif defined ( LINUX ) || defined( OSX )
#define O_EXLOCK 0
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <locale.h>
#elif defined ( _X360 )
#else
#error
#endif
#include "appframework/ilaunchermgr.h"
#include <stdio.h>
#include "tier0/icommandline.h"
#include "engine_launcher_api.h"
#include "tier0/vcrmode.h"
#include "ifilesystem.h"
#include "tier1/interface.h"
#include "tier0/dbg.h"
#include "iregistry.h"
#include "appframework/IAppSystem.h"
#include "appframework/AppFramework.h"
#include <vgui/VGUI.h>
#include <vgui/ISurface.h>
#include "tier0/platform.h"
#include "tier0/memalloc.h"
#include "filesystem.h"
#include "tier1/utlrbtree.h"
#include "materialsystem/imaterialsystem.h"
#include "istudiorender.h"
#include "vgui/IVGui.h"
#include "IHammer.h"
#include "datacache/idatacache.h"
#include "datacache/imdlcache.h"
#include "vphysics_interface.h"
#include "filesystem_init.h"
#include "vstdlib/iprocessutils.h"
#include "video/ivideoservices.h"
#include "tier1/tier1.h"
#include "tier2/tier2.h"
#include "tier3/tier3.h"
#include "p4lib/ip4.h"
#include "inputsystem/iinputsystem.h"
#include "filesystem/IQueuedLoader.h"
#include "reslistgenerator.h"
#include "tier1/fmtstr.h"
#include "sourcevr/isourcevirtualreality.h"
#define VERSION_SAFE_STEAM_API_INTERFACES
#include "steam/steam_api.h"
#if defined( USE_SDL )
#include "SDL.h"
#if !defined( _WIN32 )
#define MB_OK 0x00000001
#define MB_SYSTEMMODAL 0x00000002
#define MB_ICONERROR 0x00000004
int MessageBox(HWND hWnd, const char* message, const char* header, unsigned uType);
#endif // _WIN32
#endif // USE_SDL
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
#define DEFAULT_HL2_GAMEDIR "hl2"
#if defined( USE_SDL )
extern void* CreateSDLMgr();
#endif
//-----------------------------------------------------------------------------
// Modules...
//-----------------------------------------------------------------------------
static IEngineAPI* g_pEngineAPI;
static IHammer* g_pHammer;
bool g_bTextMode = false;
static char g_szBasedir[MAX_PATH];
static char g_szGamedir[MAX_PATH];
// copied from sys.h
struct FileAssociationInfo
{
char const* extension;
char const* command_to_issue;
};
static FileAssociationInfo g_FileAssociations[] =
{
{ ".dem", "playdemo" },
{ ".sav", "load" },
{ ".bsp", "map" },
};
#ifdef _WIN32
#pragma warning(disable:4073)
#pragma init_seg(lib)
#endif
class CLeakDump
{
public:
CLeakDump()
: m_bCheckLeaks(false)
{
}
~CLeakDump()
{
if (m_bCheckLeaks)
{
MemAlloc_DumpStats();
}
}
bool m_bCheckLeaks;
} g_LeakDump;
//-----------------------------------------------------------------------------
// Spew function!
//-----------------------------------------------------------------------------
SpewRetval_t LauncherDefaultSpewFunc(SpewType_t spewType, char const* pMsg)
{
#ifndef _CERT
#ifdef WIN32
OutputDebugStringA(pMsg);
#endif
switch (spewType)
{
case SPEW_MESSAGE:
case SPEW_LOG:
return SPEW_CONTINUE;
case SPEW_WARNING:
if (!stricmp(GetSpewOutputGroup(), "init"))
{
#if defined( WIN32 ) || defined( USE_SDL )
::MessageBox(NULL, pMsg, "Warning!", MB_OK | MB_SYSTEMMODAL | MB_ICONERROR);
#endif
}
return SPEW_CONTINUE;
case SPEW_ASSERT:
if (!ShouldUseNewAssertDialog())
{
#if defined( WIN32 ) || defined( USE_SDL )
::MessageBox(NULL, pMsg, "Assert!", MB_OK | MB_SYSTEMMODAL | MB_ICONERROR);
#endif
}
return SPEW_DEBUGGER;
case SPEW_ERROR:
default:
#if defined( WIN32 ) || defined( USE_SDL )
::MessageBox(NULL, pMsg, "Error!", MB_OK | MB_SYSTEMMODAL | MB_ICONERROR);
#endif
_exit(1);
}
#endif
}
//-----------------------------------------------------------------------------
// Implementation of VCRHelpers.
//-----------------------------------------------------------------------------
class CVCRHelpers : public IVCRHelpers
{
public:
virtual void ErrorMessage(const char* pMsg)
{
#if defined( WIN32 ) || defined( LINUX )
NOVCR(::MessageBox(NULL, pMsg, "VCR Error", MB_OK));
#endif
}
virtual void* GetMainWindow()
{
return NULL;
}
};
static CVCRHelpers g_VCRHelpers;
//-----------------------------------------------------------------------------
// Purpose: Return the game directory
// Output : char
//-----------------------------------------------------------------------------
char* GetGameDirectory(void)
{
return g_szGamedir;
}
void SetGameDirectory(const char* game)
{
Q_strncpy(g_szGamedir, game, sizeof(g_szGamedir));
}
//-----------------------------------------------------------------------------
// Gets the executable name
//-----------------------------------------------------------------------------
bool GetExecutableName(char* out, int outSize)
{
#ifdef WIN32
if (!::GetModuleFileName((HINSTANCE)GetModuleHandle(NULL), out, outSize))
{
return false;
}
return true;
#endif
}
//-----------------------------------------------------------------------------
// Purpose: Return the base directory
// Output : char
//-----------------------------------------------------------------------------
char* GetBaseDirectory(void)
{
return g_szBasedir;
}
//-----------------------------------------------------------------------------
// Purpose: Determine the directory where this .exe is running from
//-----------------------------------------------------------------------------
void UTIL_ComputeBaseDir()
{
g_szBasedir[0] = 0;
if (IsX360())
{
char const* pBaseDir = CommandLine()->ParmValue("-basedir");
if (pBaseDir)
{
strcpy(g_szBasedir, pBaseDir);
}
}
if (!g_szBasedir[0] && GetExecutableName(g_szBasedir, sizeof(g_szBasedir)))
{
char* pBuffer = strrchr(g_szBasedir, '\\');
if (*pBuffer)
{
*(pBuffer + 1) = '\0';
}
int j = strlen(g_szBasedir);
if (j > 0)
{
if ((g_szBasedir[j - 1] == '\\') ||
(g_szBasedir[j - 1] == '/'))
{
g_szBasedir[j - 1] = 0;
}
}
}
if (IsPC())
{
char const* pOverrideDir = CommandLine()->CheckParm("-basedir");
if (pOverrideDir)
{
strcpy(g_szBasedir, pOverrideDir);
}
}
#ifdef WIN32
Q_strlower(g_szBasedir);
#endif
Q_FixSlashes(g_szBasedir);
}
#ifdef WIN32
BOOL WINAPI MyHandlerRoutine(DWORD dwCtrlType)
{
#if !defined( _X360 )
TerminateProcess(GetCurrentProcess(), 2);
#endif
return TRUE;
}
#endif
void InitTextMode()
{
#ifdef WIN32
AllocConsole();
SetConsoleCtrlHandler(MyHandlerRoutine, TRUE);
freopen("CONIN$", "rb", stdin); // reopen stdin handle as console window input
freopen("CONOUT$", "wb", stdout); // reopen stout handle as console window output
freopen("CONOUT$", "wb", stderr); // reopen stderr handle as console window output
#endif
}
void SortResList(char const* pchFileName, char const* pchSearchPath);
#define ALL_RESLIST_FILE "all.lst"
#define ENGINE_RESLIST_FILE "engine.lst"
// create file to dump out to
class CLogAllFiles
{
public:
CLogAllFiles();
void Init();
void Shutdown();
void LogFile(const char* fullPathFileName, const char* options);
private:
static void LogAllFilesFunc(const char* fullPathFileName, const char* options);
void LogToAllReslist(char const* line);
bool m_bActive;
char m_szCurrentDir[_MAX_PATH];
// persistent across restarts
CUtlRBTree< CUtlString, int > m_Logged;
CUtlString m_sResListDir;
CUtlString m_sFullGamePath;
};
static CLogAllFiles g_LogFiles;
static bool AllLogLessFunc(CUtlString const& pLHS, CUtlString const& pRHS)
{
return CaselessStringLessThan(pLHS.Get(), pRHS.Get());
}
CLogAllFiles::CLogAllFiles() :
m_bActive(false),
m_Logged(0, 0, AllLogLessFunc)
{
MEM_ALLOC_CREDIT();
m_sResListDir = "reslists";
}
void CLogAllFiles::Init()
{
if (IsX360())
{
return;
}
// Can't do this in edit mode
if (CommandLine()->CheckParm("-edit"))
{
return;
}
if (!CommandLine()->CheckParm("-makereslists"))
{
return;
}
m_bActive = true;
char const* pszDir = NULL;
if (CommandLine()->CheckParm("-reslistdir", &pszDir) && pszDir)
{
char szDir[MAX_PATH];
Q_strncpy(szDir, pszDir, sizeof(szDir));
Q_StripTrailingSlash(szDir);
#ifdef WIN32
Q_strlower(szDir);
#endif
Q_FixSlashes(szDir);
if (Q_strlen(szDir) > 0)
{
m_sResListDir = szDir;
}
}
// game directory has not been established yet, must derive ourselves
char path[MAX_PATH];
Q_snprintf(path, sizeof(path), "%s/%s", GetBaseDirectory(), CommandLine()->ParmValue("-game", "hl2"));
Q_FixSlashes(path);
#ifdef WIN32
Q_strlower(path);
#endif
m_sFullGamePath = path;
// create file to dump out to
char szDir[MAX_PATH];
V_snprintf(szDir, sizeof(szDir), "%s\\%s", m_sFullGamePath.String(), m_sResListDir.String());
g_pFullFileSystem->CreateDirHierarchy(szDir, "GAME");
g_pFullFileSystem->AddLoggingFunc(&LogAllFilesFunc);
if (!CommandLine()->FindParm("-startmap") && !CommandLine()->FindParm("-startstage"))
{
m_Logged.RemoveAll();
g_pFullFileSystem->RemoveFile(CFmtStr("%s\\%s\\%s", m_sFullGamePath.String(), m_sResListDir.String(), ALL_RESLIST_FILE), "GAME");
}
#ifdef WIN32
::GetCurrentDirectory(sizeof(m_szCurrentDir), m_szCurrentDir);
Q_strncat(m_szCurrentDir, "\\", sizeof(m_szCurrentDir), 1);
_strlwr(m_szCurrentDir);
#endif
}
void CLogAllFiles::Shutdown()
{
if (!m_bActive)
return;
m_bActive = false;
if (CommandLine()->CheckParm("-makereslists"))
{
g_pFullFileSystem->RemoveLoggingFunc(&LogAllFilesFunc);
}
// Now load and sort all.lst
SortResList(CFmtStr("%s\\%s\\%s", m_sFullGamePath.String(), m_sResListDir.String(), ALL_RESLIST_FILE), "GAME");
// Now load and sort engine.lst
SortResList(CFmtStr("%s\\%s\\%s", m_sFullGamePath.String(), m_sResListDir.String(), ENGINE_RESLIST_FILE), "GAME");
m_Logged.Purge();
}
void CLogAllFiles::LogToAllReslist(char const* line)
{
// Open for append, write data, close.
FileHandle_t fh = g_pFullFileSystem->Open(CFmtStr("%s\\%s\\%s", m_sFullGamePath.String(), m_sResListDir.String(), ALL_RESLIST_FILE), "at", "GAME");
if (fh != FILESYSTEM_INVALID_HANDLE)
{
g_pFullFileSystem->Write("\"", 1, fh);
g_pFullFileSystem->Write(line, Q_strlen(line), fh);
g_pFullFileSystem->Write("\"\n", 2, fh);
g_pFullFileSystem->Close(fh);
}
}
void CLogAllFiles::LogFile(const char* fullPathFileName, const char* options)
{
if (!m_bActive)
{
Assert(0);
return;
}
// write out to log file
Assert(fullPathFileName[1] == ':');
int idx = m_Logged.Find(fullPathFileName);
if (idx != m_Logged.InvalidIndex())
{
return;
}
m_Logged.Insert(fullPathFileName);
// make it relative to our root directory
const char* relative = Q_stristr(fullPathFileName, GetBaseDirectory());
if (relative)
{
relative += (Q_strlen(GetBaseDirectory()) + 1);
char rel[MAX_PATH];
Q_strncpy(rel, relative, sizeof(rel));
#ifdef WIN32
Q_strlower(rel);
#endif
Q_FixSlashes(rel);
LogToAllReslist(rel);
}
}
//-----------------------------------------------------------------------------
// Purpose: callback function from filesystem
//-----------------------------------------------------------------------------
void CLogAllFiles::LogAllFilesFunc(const char* fullPathFileName, const char* options)
{
g_LogFiles.LogFile(fullPathFileName, options);
}
//-----------------------------------------------------------------------------
// Purpose: This is a bit of a hack because it appears
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
static bool IsWin98OrOlder()
{
bool retval = false;
#if defined( WIN32 ) && !defined( _X360 )
OSVERSIONINFOEX osvi;
ZeroMemory(&osvi, sizeof(OSVERSIONINFOEX));
osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
BOOL bOsVersionInfoEx = GetVersionEx((OSVERSIONINFO*)&osvi);
if (!bOsVersionInfoEx)
{
// If OSVERSIONINFOEX doesn't work, try OSVERSIONINFO.
osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
if (!GetVersionEx((OSVERSIONINFO*)&osvi))
{
Error("IsWin98OrOlder: Unable to get OS version information");
}
}
switch (osvi.dwPlatformId)
{
case VER_PLATFORM_WIN32_NT:
// NT, XP, Win2K, etc. all OK for SSE
break;
case VER_PLATFORM_WIN32_WINDOWS:
// Win95, 98, Me can't do SSE
retval = true;
break;
case VER_PLATFORM_WIN32s:
// Can't really run this way I don't think...
retval = true;
break;
default:
break;
}
#endif
return retval;
}
//-----------------------------------------------------------------------------
// Purpose: Figure out if Steam is running, then load the GameOverlayRenderer.dll
//-----------------------------------------------------------------------------
void TryToLoadSteamOverlayDLL()
{
#if defined( WIN32 ) && !defined( _X360 )
// First, check if the module is already loaded, perhaps because we were run from Steam directly
HMODULE hMod = GetModuleHandle("GameOverlayRenderer" DLL_EXT_STRING);
if (hMod)
{
return;
}
if (0 == GetEnvironmentVariableA("SteamGameId", NULL, 0))
{
// Initializing the Steam client API has the side effect of setting up the AppId
// which is immediately queried in GameOverlayRenderer.dll's DllMain entry point
if (SteamAPI_InitSafe())
{
const char* pchSteamInstallPath = SteamAPI_GetSteamInstallPath();
if (pchSteamInstallPath)
{
char rgchSteamPath[MAX_PATH];
V_ComposeFileName(pchSteamInstallPath, "GameOverlayRenderer" DLL_EXT_STRING, rgchSteamPath, Q_ARRAYSIZE(rgchSteamPath));
// This could fail, but we can't fix it if it does so just ignore failures
LoadLibrary(rgchSteamPath);
}
SteamAPI_Shutdown();
}
}
#endif
}
//-----------------------------------------------------------------------------
// Inner loop: initialize, shutdown main systems, load steam to
//-----------------------------------------------------------------------------
class CSourceAppSystemGroup : public CSteamAppSystemGroup
{
public:
// Methods of IApplication
virtual bool Create();
virtual bool PreInit();
virtual int Main();
virtual void PostShutdown();
virtual void Destroy();
private:
const char* DetermineDefaultMod();
const char* DetermineDefaultGame();
bool m_bEditMode;
};
//-----------------------------------------------------------------------------
// The dirty disk error report function
//-----------------------------------------------------------------------------
void ReportDirtyDiskNoMaterialSystem()
{
// removed
}
//-----------------------------------------------------------------------------
// Instantiate all main libraries
//-----------------------------------------------------------------------------
bool CSourceAppSystemGroup::Create()
{
IFileSystem* pFileSystem = (IFileSystem*)FindSystem(FILESYSTEM_INTERFACE_VERSION);
pFileSystem->InstallDirtyDiskReportFunc(ReportDirtyDiskNoMaterialSystem);
#ifdef WIN32
CoInitialize(NULL);
#endif
// Are we running in edit mode?
m_bEditMode = CommandLine()->CheckParm("-edit");
double st = Plat_FloatTime();
AppSystemInfo_t appSystems[] =
{
{ "engine" DLL_EXT_STRING, CVAR_QUERY_INTERFACE_VERSION }, // NOTE: This one must be first!!
{ "inputsystem" DLL_EXT_STRING, INPUTSYSTEM_INTERFACE_VERSION },
{ "filesystem_stdio" DLL_EXT_STRING, FILESYSTEM_INTERFACE_VERSION },
{ "materialsystem" DLL_EXT_STRING, MATERIAL_SYSTEM_INTERFACE_VERSION },
{ "datacache" DLL_EXT_STRING, DATACACHE_INTERFACE_VERSION },
{ "datacache" DLL_EXT_STRING, MDLCACHE_INTERFACE_VERSION },
{ "datacache" DLL_EXT_STRING, STUDIO_DATA_CACHE_INTERFACE_VERSION },
{ "studiorender" DLL_EXT_STRING, STUDIO_RENDER_INTERFACE_VERSION },
{ "vphysics" DLL_EXT_STRING, VPHYSICS_INTERFACE_VERSION },
{ "video_services" DLL_EXT_STRING, VIDEO_SERVICES_INTERFACE_VERSION },
// NOTE: This has to occur before vgui2.dll so it replaces vgui2's surface implementation
{ "vguimatsurface" DLL_EXT_STRING, VGUI_SURFACE_INTERFACE_VERSION },
{ "vgui2" DLL_EXT_STRING, VGUI_IVGUI_INTERFACE_VERSION },
{ "engine" DLL_EXT_STRING, VENGINE_LAUNCHER_API_VERSION },
{ "", "" } // Required to terminate the list
};
#if defined( USE_SDL )
AddSystem((IAppSystem*)CreateSDLMgr(), SDLMGR_INTERFACE_VERSION);
#endif
if (!AddSystems(appSystems))
return false;
// This will be NULL for games that don't support VR. That's ok. Just don't load the DLL
AppModule_t sourceVRModule = LoadModule("sourcevr" DLL_EXT_STRING);
if (sourceVRModule != APP_MODULE_INVALID)
{
AddSystem(sourceVRModule, SOURCE_VIRTUAL_REALITY_INTERFACE_VERSION);
}
// pull in our filesystem dll to pull the queued loader from it, we need to do it this way due to the
// steam/stdio split for our steam filesystem
char pFileSystemDLL[MAX_PATH];
bool bSteam;
if (FileSystem_GetFileSystemDLLName(pFileSystemDLL, MAX_PATH, bSteam) != FS_OK)
return false;
AppModule_t fileSystemModule = LoadModule(pFileSystemDLL);
AddSystem(fileSystemModule, QUEUEDLOADER_INTERFACE_VERSION);
// Hook in datamodel and p4 control if we're running with -tools
if (IsPC() && ((CommandLine()->FindParm("-tools") && !CommandLine()->FindParm("-nop4")) || CommandLine()->FindParm("-p4")))
{
#ifdef STAGING_ONLY
AppModule_t p4libModule = LoadModule("p4lib" DLL_EXT_STRING);
IP4* p4 = (IP4*)AddSystem(p4libModule, P4_INTERFACE_VERSION);
// If we are running with -steam then that means the tools are being used by an SDK user. Don't exit in this case!
if (!p4 && !CommandLine()->FindParm("-steam"))
{
return false;
}
#endif // STAGING_ONLY
AppModule_t vstdlibModule = LoadModule("vstdlib" DLL_EXT_STRING);
IProcessUtils* processUtils = (IProcessUtils*)AddSystem(vstdlibModule, PROCESS_UTILS_INTERFACE_VERSION);
if (!processUtils)
return false;
}
// Connect to iterfaces loaded in AddSystems that we need locally
IMaterialSystem* pMaterialSystem = (IMaterialSystem*)FindSystem(MATERIAL_SYSTEM_INTERFACE_VERSION);
if (!pMaterialSystem)
return false;
g_pEngineAPI = (IEngineAPI*)FindSystem(VENGINE_LAUNCHER_API_VERSION);
// Load the hammer DLL if we're in editor mode
#if defined( _WIN32 ) && defined( STAGING_ONLY )
if (m_bEditMode)
{
AppModule_t hammerModule = LoadModule("hammer_dll" DLL_EXT_STRING);
g_pHammer = (IHammer*)AddSystem(hammerModule, INTERFACEVERSION_HAMMER);
if (!g_pHammer)
{
return false;
}
}
#endif // defined( _WIN32 ) && defined( STAGING_ONLY )
// Load up the appropriate shader DLL
// This has to be done before connection.
char const* pDLLName = "shaderapidx9" DLL_EXT_STRING;
if (CommandLine()->FindParm("-noshaderapi"))
{
pDLLName = "shaderapiempty" DLL_EXT_STRING;
}
pMaterialSystem->SetShaderAPI(pDLLName);
double elapsed = Plat_FloatTime() - st;
COM_TimestampedLog("LoadAppSystems: Took %.4f secs to load libraries and get factories.", (float)elapsed);
return true;
}
bool CSourceAppSystemGroup::PreInit()
{
CreateInterfaceFn factory = GetFactory();
ConnectTier1Libraries(&factory, 1);
ConVar_Register();
ConnectTier2Libraries(&factory, 1);
ConnectTier3Libraries(&factory, 1);
if (!g_pFullFileSystem || !g_pMaterialSystem)
return false;
CFSSteamSetupInfo steamInfo;
steamInfo.m_bToolsMode = false;
steamInfo.m_bSetSteamDLLPath = false;
steamInfo.m_bSteam = g_pFullFileSystem->IsSteam();
steamInfo.m_bOnlyUseDirectoryName = true;
steamInfo.m_pDirectoryName = DetermineDefaultMod();
if (!steamInfo.m_pDirectoryName)
{
steamInfo.m_pDirectoryName = DetermineDefaultGame();
if (!steamInfo.m_pDirectoryName)
{
Error("FileSystem_LoadFileSystemModule: no -defaultgamedir or -game specified.");
}
}
if (FileSystem_SetupSteamEnvironment(steamInfo) != FS_OK)
return false;
CFSMountContentInfo fsInfo;
fsInfo.m_pFileSystem = g_pFullFileSystem;
fsInfo.m_bToolsMode = m_bEditMode;
fsInfo.m_pDirectoryName = steamInfo.m_GameInfoPath;
if (FileSystem_MountContent(fsInfo) != FS_OK)
return false;
if (IsPC() || !IsX360())
{
fsInfo.m_pFileSystem->AddSearchPath("platform", "PLATFORM");
}
else
{
// 360 needs absolute paths
FileSystem_AddSearchPath_Platform(g_pFullFileSystem, steamInfo.m_GameInfoPath);
}
if (IsPC())
{
// This will get called multiple times due to being here, but only the first one will do anything
reslistgenerator->Init(GetBaseDirectory(), CommandLine()->ParmValue("-game", "hl2"));
// This will also get called each time, but will actually fix up the command line as needed
reslistgenerator->SetupCommandLine();
}
// FIXME: Logfiles is mod-specific, needs to move into the engine.
g_LogFiles.Init();
// Required to run through the editor
if (m_bEditMode)
{
g_pMaterialSystem->EnableEditorMaterials();
}
StartupInfo_t info;
info.m_pInstance = GetAppInstance();
info.m_pBaseDirectory = GetBaseDirectory();
info.m_pInitialMod = DetermineDefaultMod();
info.m_pInitialGame = DetermineDefaultGame();
info.m_pParentAppSystemGroup = this;
info.m_bTextMode = g_bTextMode;
g_pEngineAPI->SetStartupInfo(info);
return true;
}
int CSourceAppSystemGroup::Main()
{
return g_pEngineAPI->Run();
}
void CSourceAppSystemGroup::PostShutdown()
{
// FIXME: Logfiles is mod-specific, needs to move into the engine.
g_LogFiles.Shutdown();
reslistgenerator->Shutdown();
DisconnectTier3Libraries();
DisconnectTier2Libraries();
ConVar_Unregister();
DisconnectTier1Libraries();
}
void CSourceAppSystemGroup::Destroy()
{
g_pEngineAPI = NULL;
g_pMaterialSystem = NULL;
g_pHammer = NULL;
#ifdef WIN32
CoUninitialize();
#endif
}
//-----------------------------------------------------------------------------
// Determines the initial mod to use at load time.
// We eventually (hopefully) will be able to switch mods at runtime
// because the engine/hammer integration really wants this feature.
//-----------------------------------------------------------------------------
const char* CSourceAppSystemGroup::DetermineDefaultMod()
{
if (!m_bEditMode)
{
return CommandLine()->ParmValue("-game", DEFAULT_HL2_GAMEDIR);
}
return g_pHammer->GetDefaultMod();
}
const char* CSourceAppSystemGroup::DetermineDefaultGame()
{
if (!m_bEditMode)
{
return CommandLine()->ParmValue("-defaultgamedir", DEFAULT_HL2_GAMEDIR);
}
return g_pHammer->GetDefaultGame();
}
//-----------------------------------------------------------------------------
// MessageBox for SDL/OSX
//-----------------------------------------------------------------------------
#if defined( USE_SDL ) && !defined( _WIN32 )
int MessageBox(HWND hWnd, const char* message, const char* header, unsigned uType)
{
SDL_ShowSimpleMessageBox(0, header, message, GetAssertDialogParent());
return 0;
}
#endif
//-----------------------------------------------------------------------------
// Allow only one windowed source app to run at a time
//-----------------------------------------------------------------------------
#ifdef WIN32
HANDLE g_hMutex = NULL;
#endif
bool GrabSourceMutex()
{
#ifdef WIN32
if (IsPC())
{
// don't allow more than one instance to run
g_hMutex = ::CreateMutex(NULL, FALSE, TEXT("hl2_singleton_mutex"));
unsigned int waitResult = ::WaitForSingleObject(g_hMutex, 0);
// Here, we have the mutex
if (waitResult == WAIT_OBJECT_0 || waitResult == WAIT_ABANDONED)
return true;
// couldn't get the mutex, we must be running another instance
::CloseHandle(g_hMutex);
return false;
}
#endif
return true;
}
void ReleaseSourceMutex()
{
#ifdef WIN32
if (IsPC() && g_hMutex)
{
::ReleaseMutex(g_hMutex);
::CloseHandle(g_hMutex);
g_hMutex = NULL;
}
#elif defined(POSIX)
if (g_lockfd != -1)
{
close(g_lockfd);
g_lockfd = -1;
unlink(g_lockFilename);
}
#endif
}
// Remove all but the last -game parameter.
// This is for mods based off something other than Half-Life 2 (like HL2MP mods).
// The Steam UI does 'steam -applaunch 320 -game c:\steam\steamapps\sourcemods\modname', but applaunch inserts
// its own -game parameter, which would supercede the one we really want if we didn't intercede here.
void RemoveSpuriousGameParameters()
{
// Find the last -game parameter.
int nGameArgs = 0;
char lastGameArg[MAX_PATH];
for (int i = 0; i < CommandLine()->ParmCount() - 1; i++)
{
if (Q_stricmp(CommandLine()->GetParm(i), "-game") == 0)
{
Q_snprintf(lastGameArg, sizeof(lastGameArg), "\"%s\"", CommandLine()->GetParm(i + 1));
++nGameArgs;
++i;
}
}
// We only care if > 1 was specified.
if (nGameArgs > 1)
{
CommandLine()->RemoveParm("-game");
CommandLine()->AppendParm("-game", lastGameArg);
}
}
/*
============
va
does a varargs printf into a temp buffer, so I don't need to have
varargs versions of all text functions.
============
*/
static char* va(char* format, ...)
{
va_list argptr;
static char string[8][512];
static int curstring = 0;
curstring = (curstring + 1) % 8;
va_start(argptr, format);
Q_vsnprintf(string[curstring], sizeof(string[curstring]), format, argptr);
va_end(argptr);
return string[curstring];
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : *param -
// Output : static char const
//-----------------------------------------------------------------------------
static char const* Cmd_TranslateFileAssociation(char const* param)
{
static char sz[512];
char* retval = NULL;
char temp[512];
Q_strncpy(temp, param, sizeof(temp));
Q_FixSlashes(temp);
#ifdef WIN32
Q_strlower(temp);
#endif
const char* extension = V_GetFileExtension(temp);
// must have an extension to map
if (!extension)
return retval;
extension--; // back up so we have the . in the extension
int c = ARRAYSIZE(g_FileAssociations);
for (int i = 0; i < c; i++)
{
FileAssociationInfo& info = g_FileAssociations[i];
if (!Q_strcmp(extension, info.extension) &&
!CommandLine()->FindParm(va("+%s", info.command_to_issue)))
{
// Translate if haven't already got one of these commands
Q_strncpy(sz, temp, sizeof(sz));
Q_FileBase(sz, temp, sizeof(sz));
Q_snprintf(sz, sizeof(sz), "%s %s", info.command_to_issue, temp);
retval = sz;
break;
}
}
// return null if no translation, otherwise return commands
return retval;
}
//-----------------------------------------------------------------------------
// Purpose: Converts all the convar args into a convar command
// Input : none
// Output : const char * series of convars
//-----------------------------------------------------------------------------
static const char* BuildCommand()
{
static CUtlBuffer build(0, 0, CUtlBuffer::TEXT_BUFFER);
build.Clear();
// arg[0] is the executable name
for (int i = 1; i < CommandLine()->ParmCount(); i++)
{
const char* szParm = CommandLine()->GetParm(i);
if (!szParm) continue;
if (szParm[0] == '-')
{
// skip -XXX options and eat their args
const char* szValue = CommandLine()->ParmValue(szParm);
if (szValue) i++;
continue;
}
if (szParm[0] == '+')
{
// convert +XXX options and stuff them into the build buffer
const char* szValue = CommandLine()->ParmValue(szParm);
if (szValue)
{
build.PutString(va("%s %s;", szParm + 1, szValue));
i++;
}
else
{
build.PutString(szParm + 1);
build.PutChar(';');
}
}
else
{
// singleton values, convert to command
char const* translated = Cmd_TranslateFileAssociation(CommandLine()->GetParm(i));
if (translated)
{
build.PutString(translated);
build.PutChar(';');
}
}
}
build.PutChar('\0');
return (const char*)build.Base();
}
//-----------------------------------------------------------------------------
// Purpose: The real entry point for the application
// Input : hInstance -
// hPrevInstance -
// lpCmdLine -
// nCmdShow -
// Output : int APIENTRY
//-----------------------------------------------------------------------------
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
#ifdef WIN32
SetAppInstance(hInstance);
#endif
// Hook the debug output stuff.
SpewOutputFunc(LauncherDefaultSpewFunc);
if (0 && IsWin98OrOlder())
{
Error("This build does not currently run under Windows 98/Me.");
return -1;
}
// Quickly check the hardware key, essentially a warning shot.
if (!Plat_VerifyHardwareKeyPrompt())
{
return -1;
}
const char* filename;
CommandLine()->CreateCmdLine(IsPC() ? VCRHook_GetCommandLine() : lpCmdLine);
// If we're using -default command line parameters, get rid of DX8 settings.
if (CommandLine()->CheckParm("-default"))
{
CommandLine()->RemoveParm("-dxlevel");
CommandLine()->RemoveParm("-maxdxlevel");
CommandLine()->RemoveParm("+mat_dxlevel");
}
// Figure out the directory the executable is running from
UTIL_ComputeBaseDir();
// This call is to emulate steam's injection of the GameOverlay DLL into our process if we
// are running from the command line directly, this allows the same experience the user gets
// to be present when running from perforce, the call has no effect on X360
TryToLoadSteamOverlayDLL();
// Start VCR mode?
if (CommandLine()->CheckParm("-vcrrecord", &filename))
{
if (!VCRStart(filename, true, &g_VCRHelpers))
{
Error("-vcrrecord: can't open '%s' for writing.\n", filename);
return -1;
}
}
else if (CommandLine()->CheckParm("-vcrplayback", &filename))
{
if (!VCRStart(filename, false, &g_VCRHelpers))
{
Error("-vcrplayback: can't open '%s' for reading.\n", filename);
return -1;
}
}
// See the function for why we do this.
RemoveSpuriousGameParameters();
#ifdef WIN32
if (IsPC())
{
// initialize winsock
WSAData wsaData;
int nError = ::WSAStartup(MAKEWORD(2, 0), &wsaData);
if (nError)
{
Msg("Warning! Failed to start Winsock via WSAStartup = 0x%x.\n", nError);
}
}
#endif
// Run in text mode? (No graphics or sound).
// NOTE: Currently crashing the game, will look into later.
//if (CommandLine()->CheckParm("-textmode"))
//{
// g_bTextMode = true;
// InitTextMode();
//}
#ifdef WIN32
else
{
int retval = -1;
// Can only run one windowed source app at a time
if (!GrabSourceMutex())
{
// Allow the user to explicitly say they want to be able to run multiple instances of the source mutex.
// Useful for side-by-side comparisons of different renderers.
bool multiRun = CommandLine()->CheckParm("-multirun") != NULL;
// We're going to hijack the existing session and load a new savegame into it. This will mainly occur when users click on links in Bugzilla that will automatically copy saves and load them
// directly from the web browser. The -hijack command prevents the launcher from objecting that there is already an instance of the game.
if (CommandLine()->CheckParm("-hijack"))
{
HWND hwndEngine = FindWindow("Valve001", NULL);
// Can't find the engine
if (hwndEngine == NULL)
{
::MessageBox(NULL, "The modified entity keyvalues could not be sent to the Source Engine because the engine does not appear to be running.", "Source Engine Not Running", MB_OK | MB_ICONEXCLAMATION);
}
else
{
const char* szCommand = BuildCommand();
//
// Fill out the data structure to send to the engine.
//
COPYDATASTRUCT copyData;
copyData.cbData = strlen(szCommand) + 1;
copyData.dwData = 0;
copyData.lpData = (void*)szCommand;
if (!::SendMessage(hwndEngine, WM_COPYDATA, 0, (LPARAM)&copyData))
{
::MessageBox(NULL, "The Source Engine was found running, but did not accept the request to load a savegame. It may be an old version of the engine that does not support this functionality.", "Source Engine Declined Request", MB_OK | MB_ICONEXCLAMATION);
}
else
{
retval = 0;
}
free((void*)szCommand);
}
}
else
{
if (!multiRun) {
::MessageBox(NULL, "Only one instance of the game can be running at one time.", "Source - Warning", MB_ICONINFORMATION | MB_OK);
}
}
if (!multiRun) {
return retval;
}
}
}
#endif
#ifdef WIN32
// Make low priority?
// NOTE: This currently is causing a crash. Will look into this later.
/*
if (CommandLine()->CheckParm("-low"))
{
SetPriorityClass(GetCurrentProcess(), IDLE_PRIORITY_CLASS);
}
else if (CommandLine()->CheckParm("-high"))
{
SetPriorityClass(GetCurrentProcess(), HIGH_PRIORITY_CLASS);
}
*/
#endif
// If game is not run from Steam then add -insecure in order to avoid client timeout message
if (CommandLine()->CheckParm("-steam") == NULL)
{
CommandLine()->AppendParm("-insecure", NULL);
}
// Figure out the directory the executable is running from
// and make that be the current working directory
_chdir(GetBaseDirectory());
g_LeakDump.m_bCheckLeaks = CommandLine()->CheckParm("-leakcheck") ? true : false;
bool bRestart = true;
while (bRestart)
{
bRestart = false;
CSourceAppSystemGroup sourceSystems;
CSteamApplication steamApplication(&sourceSystems);
int nRetval = steamApplication.Run();
if (steamApplication.GetErrorStage() == CSourceAppSystemGroup::INITIALIZATION)
{
bRestart = (nRetval == INIT_RESTART);
}
else if (nRetval == RUN_RESTART)
{
bRestart = true;
}
bool bReslistCycle = false;
if (!bRestart)
{
bReslistCycle = reslistgenerator->ShouldContinue();
bRestart = bReslistCycle;
}
if (!bReslistCycle)
{
// Remove any overrides in case settings changed
// NOTE: This, sadly, is also causing a crash. Will look into, later.
/*
CommandLine()->RemoveParm("-w");
CommandLine()->RemoveParm("-h");
CommandLine()->RemoveParm("-width");
CommandLine()->RemoveParm("-height");
CommandLine()->RemoveParm("-sw");
CommandLine()->RemoveParm("-startwindowed");
CommandLine()->RemoveParm("-windowed");
CommandLine()->RemoveParm("-window");
CommandLine()->RemoveParm("-full");
CommandLine()->RemoveParm("-fullscreen");
CommandLine()->RemoveParm("-dxlevel");
CommandLine()->RemoveParm("-autoconfig");
CommandLine()->RemoveParm("+mat_hdr_level");
*/
}
}
#ifdef WIN32
if (IsPC())
{
// shutdown winsock
int nError = ::WSACleanup();
if (nError)
{
Msg("Warning! Failed to complete WSACleanup = 0x%x.\n", nError);
}
}
#endif
// Allow other source apps to run
ReleaseSourceMutex();
// Now that the mutex has been released, check HKEY_CURRENT_USER\Software\Valve\Source\Relaunch URL. If there is a URL here, exec it.
// This supports the capability of immediately re-launching the the game via Steam in a different audio language
HKEY hKey;
if (RegOpenKeyEx(HKEY_CURRENT_USER, "Software\\Valve\\Source", NULL, KEY_ALL_ACCESS, &hKey) == ERROR_SUCCESS)
{
char szValue[MAX_PATH];
DWORD dwValueLen = MAX_PATH;
if (RegQueryValueEx(hKey, "Relaunch URL", NULL, NULL, (unsigned char*)szValue, &dwValueLen) == ERROR_SUCCESS)
{
ShellExecute(0, "open", szValue, 0, 0, SW_SHOW);
RegDeleteValue(hKey, "Relaunch URL");
}
RegCloseKey(hKey);
}
return 0;
}