summaryrefslogtreecommitdiff
path: root/engine/sys_dll2.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'engine/sys_dll2.cpp')
-rw-r--r--engine/sys_dll2.cpp2571
1 files changed, 2571 insertions, 0 deletions
diff --git a/engine/sys_dll2.cpp b/engine/sys_dll2.cpp
new file mode 100644
index 0000000..30fbe4a
--- /dev/null
+++ b/engine/sys_dll2.cpp
@@ -0,0 +1,2571 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//===========================================================================//
+#define DISABLE_PROTECTED_THINGS
+
+#if defined( USE_SDL )
+#include "appframework/ilaunchermgr.h"
+#endif
+
+#if defined( _WIN32 ) && !defined( _X360 )
+#include "winlite.h"
+#include <Psapi.h>
+#endif
+
+#if defined( OSX )
+#include <sys/sysctl.h>
+#endif
+
+#if defined( POSIX )
+#include <setjmp.h>
+#include <signal.h>
+#endif
+
+#include <stdarg.h>
+#include "quakedef.h"
+#include "idedicatedexports.h"
+#include "engine_launcher_api.h"
+#include "ivideomode.h"
+#include "common.h"
+#include "iregistry.h"
+#include "keys.h"
+#include "cdll_engine_int.h"
+#include "traceinit.h"
+#include "iengine.h"
+#include "igame.h"
+#include "tier0/etwprof.h"
+#include "tier0/vcrmode.h"
+#include "tier0/icommandline.h"
+#include "tier0/minidump.h"
+#include "engine_hlds_api.h"
+#include "filesystem_engine.h"
+#include "cl_main.h"
+#include "client.h"
+#include "tier3/tier3.h"
+#include "MapReslistGenerator.h"
+#include "toolframework/itoolframework.h"
+#include "sourcevr/isourcevirtualreality.h"
+#include "DevShotGenerator.h"
+#include "gl_shader.h"
+#include "l_studio.h"
+#include "IHammer.h"
+#include "sys_dll.h"
+#include "materialsystem/materialsystem_config.h"
+#include "server.h"
+#include "video/ivideoservices.h"
+#include "datacache/idatacache.h"
+#include "vphysics_interface.h"
+#include "inputsystem/iinputsystem.h"
+#include "appframework/IAppSystemGroup.h"
+#include "tier0/systeminformation.h"
+#include "host_cmd.h"
+#ifdef _WIN32
+#include "VGuiMatSurface/IMatSystemSurface.h"
+#endif
+
+#ifdef GPROFILER
+#include "gperftools/profiler.h"
+#endif
+
+// This is here just for legacy support of older .dlls!!!
+#include "SoundEmitterSystem/isoundemittersystembase.h"
+#include "eiface.h"
+#include "tier1/fmtstr.h"
+#include "steam/steam_api.h"
+
+#ifndef SWDS
+#include "sys_mainwind.h"
+#include "vgui/ISystem.h"
+#include "vgui_controls/Controls.h"
+#include "IGameUIFuncs.h"
+#include "cl_steamauth.h"
+#endif // SWDS
+
+#if defined(_WIN32)
+#include <eh.h>
+#endif
+
+#if POSIX
+#include <dlfcn.h>
+#endif
+
+#if defined( _X360 )
+#include "xbox/xbox_win32stubs.h"
+#else
+#include "xbox/xboxstubs.h"
+#endif
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+//-----------------------------------------------------------------------------
+// Globals
+//-----------------------------------------------------------------------------
+IDedicatedExports *dedicated = NULL;
+extern CreateInterfaceFn g_AppSystemFactory;
+IHammer *g_pHammer = NULL;
+IPhysics *g_pPhysics = NULL;
+ISourceVirtualReality *g_pSourceVR = NULL;
+#if defined( USE_SDL )
+ILauncherMgr *g_pLauncherMgr = NULL;
+#endif
+
+#ifndef SWDS
+extern CreateInterfaceFn g_ClientFactory;
+#endif
+
+static SteamInfVersionInfo_t g_SteamInfIDVersionInfo;
+const SteamInfVersionInfo_t& GetSteamInfIDVersionInfo()
+{
+ Assert( g_SteamInfIDVersionInfo.AppID != k_uAppIdInvalid );
+ return g_SteamInfIDVersionInfo;
+}
+
+int build_number( void )
+{
+ return GetSteamInfIDVersionInfo().ServerVersion;
+}
+
+//-----------------------------------------------------------------------------
+// Forward declarations
+//-----------------------------------------------------------------------------
+void Host_GetHostInfo(float *fps, int *nActive, int *nMaxPlayers, char *pszMap, int maxlen );
+const char *Key_BindingForKey( int keynum );
+void COM_ShutdownFileSystem( void );
+void COM_InitFilesystem( const char *pFullModPath );
+void Host_ReadPreStartupConfiguration();
+
+//-----------------------------------------------------------------------------
+// ConVars and console commands
+//-----------------------------------------------------------------------------
+#ifndef SWDS
+//-----------------------------------------------------------------------------
+// Purpose: exports an interface that can be used by the launcher to run the engine
+// this is the exported function when compiled as a blob
+//-----------------------------------------------------------------------------
+void EXPORT F( IEngineAPI **api )
+{
+ CreateInterfaceFn factory = Sys_GetFactoryThis(); // This silly construction is necessary to prevent the LTCG compiler from crashing.
+ *api = ( IEngineAPI * )(factory(VENGINE_LAUNCHER_API_VERSION, NULL));
+}
+#endif // SWDS
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void ClearIOStates( void )
+{
+#ifndef SWDS
+ if ( g_ClientDLL )
+ {
+ g_ClientDLL->IN_ClearStates();
+ }
+#endif
+}
+
+//-----------------------------------------------------------------------------
+// The SDK launches the game with the full path to gameinfo.txt, so we need
+// to strip off the path.
+//-----------------------------------------------------------------------------
+const char *GetModDirFromPath( const char *pszPath )
+{
+ char *pszSlash = Q_strrchr( pszPath, '\\' );
+ if ( pszSlash )
+ {
+ return pszSlash + 1;
+ }
+ else if ( ( pszSlash = Q_strrchr( pszPath, '/' ) ) != NULL )
+ {
+ return pszSlash + 1;
+ }
+
+ // Must just be a mod directory already.
+ return pszPath;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Main entry
+//-----------------------------------------------------------------------------
+#ifndef SWDS
+#include "gl_matsysiface.h"
+#endif
+
+//-----------------------------------------------------------------------------
+// Inner loop: initialize, shutdown main systems, load steam to
+//-----------------------------------------------------------------------------
+class CModAppSystemGroup : public CAppSystemGroup
+{
+ typedef CAppSystemGroup BaseClass;
+public:
+ // constructor
+ CModAppSystemGroup( bool bServerOnly, CAppSystemGroup *pParentAppSystem = NULL )
+ : BaseClass( pParentAppSystem ),
+ m_bServerOnly( bServerOnly )
+ {
+ }
+
+ CreateInterfaceFn GetFactory()
+ {
+ return CAppSystemGroup::GetFactory();
+ }
+
+ // Methods of IApplication
+ virtual bool Create();
+ virtual bool PreInit();
+ virtual int Main();
+ virtual void PostShutdown();
+ virtual void Destroy();
+
+private:
+
+ bool IsServerOnly() const
+ {
+ return m_bServerOnly;
+ }
+ bool ModuleAlreadyInList( CUtlVector< AppSystemInfo_t >& list, const char *moduleName, const char *interfaceName );
+
+ bool AddLegacySystems();
+ bool m_bServerOnly;
+};
+
+#if defined( STAGING_ONLY )
+CON_COMMAND( bigalloc, "huge alloc crash" )
+{
+ Msg( "pre-crash %d\n", MemAlloc_MemoryAllocFailed() );
+ // Alloc a bit less than UINT_MAX so there is room for heap headers in the malloc functions.
+ void *buf = malloc( UINT_MAX - 0x4000 );
+ Msg( "post-alloc %d. buf: %p\n", MemAlloc_MemoryAllocFailed(), buf );
+ *(int *)buf = 0;
+}
+#endif
+
+extern void S_ClearBuffer();
+extern char g_minidumpinfo[ 4096 ];
+extern PAGED_POOL_INFO_t g_pagedpoolinfo;
+extern bool g_bUpdateMinidumpComment;
+void GetSpew( char *buf, size_t buflen );
+
+extern int gHostSpawnCount;
+extern int g_nMapLoadCount;
+extern int g_HostServerAbortCount;
+extern int g_HostErrorCount;
+extern int g_HostEndDemo;
+
+// Turn this to 1 to allow for expanded spew in minidump comments.
+static ConVar sys_minidumpexpandedspew( "sys_minidumpexpandedspew", "1" );
+
+#ifdef IS_WINDOWS_PC
+
+extern "C" void __cdecl FailSafe( unsigned int uStructuredExceptionCode, struct _EXCEPTION_POINTERS * pExceptionInfo )
+{
+ // Nothing, this just catches a crash when creating the comment block
+}
+
+#endif
+
+#if defined( POSIX )
+
+static sigjmp_buf g_mark;
+static void posix_signal_handler( int i )
+{
+ siglongjmp( g_mark, -1 );
+}
+
+#define DO_TRY if ( sigsetjmp( g_mark, 1 ) == 0 )
+#define DO_CATCH else
+
+#if defined( OSX )
+#define __sighandler_t sig_t
+#endif
+
+#else
+
+#define DO_TRY try
+#define DO_CATCH catch ( ... )
+
+#endif // POSIX
+
+//-----------------------------------------------------------------------------
+// Purpose: Check whether any mods are loaded.
+// Currently looks for metamod and sourcemod.
+//-----------------------------------------------------------------------------
+static bool IsSourceModLoaded()
+{
+#if defined( _WIN32 )
+ static const char *s_pFileNames[] = { "metamod.2.tf2.dll", "sourcemod.2.tf2.dll", "sdkhooks.ext.2.ep2v.dll", "sdkhooks.ext.2.tf2.dll" };
+
+ for ( size_t i = 0; i < Q_ARRAYSIZE( s_pFileNames ); i++ )
+ {
+ // GetModuleHandle function returns a handle to a mapped module
+ // without incrementing its reference count.
+ if ( GetModuleHandleA( s_pFileNames[ i ] ) )
+ return true;
+ }
+#else
+ FILE *fh = fopen( "/proc/self/maps", "r" );
+
+ if ( fh )
+ {
+ char buf[ 1024 ];
+ static const char *s_pFileNames[] = { "metamod.2.tf2.so", "sourcemod.2.tf2.so", "sdkhooks.ext.2.ep2v.so", "sdkhooks.ext.2.tf2.so" };
+
+ while ( fgets( buf, sizeof( buf ), fh ) )
+ {
+ for ( size_t i = 0; i < Q_ARRAYSIZE( s_pFileNames ); i++ )
+ {
+ if ( strstr( buf, s_pFileNames[ i ] ) )
+ {
+ fclose( fh );
+ return true;
+ }
+ }
+ }
+
+ fclose( fh );
+ }
+#endif
+
+ return false;
+}
+
+template< int _SIZE >
+class CErrorText
+{
+public:
+ CErrorText() : m_bIsDedicatedServer( false ) {}
+ ~CErrorText() {}
+
+ void Steam_SetMiniDumpComment()
+ {
+#if !defined( NO_STEAM )
+ SteamAPI_SetMiniDumpComment( m_errorText );
+#endif
+ }
+
+ void CommentCat( const char * str )
+ {
+ V_strcat_safe( m_errorText, str );
+ }
+
+ void CommentPrintf( const char *fmt, ... )
+ {
+ va_list args;
+ va_start( args, fmt );
+
+ size_t len = strlen( m_errorText );
+ vsnprintf( m_errorText + len, sizeof( m_errorText ) - len - 1, fmt, args );
+ m_errorText[ sizeof( m_errorText ) - 1 ] = 0;
+
+ va_end( args );
+ }
+
+ void BuildComment( char const *pchSysErrorText, bool bRealCrash )
+ {
+ // Try and detect whether this
+ bool bSourceModLoaded = false;
+ if ( m_bIsDedicatedServer )
+ {
+ bSourceModLoaded = IsSourceModLoaded();
+ if ( bSourceModLoaded )
+ {
+ AppId_t AppId = GetSteamInfIDVersionInfo().ServerAppID;
+ // Bump up the number and report the crash. This should be something
+ // like 232251 (instead of 232250). 232251 is for the TF2 Windows client,
+ // but we actually report those crashes under ID 440, so this should be ok.
+ SteamAPI_SetBreakpadAppID( AppId + 1 );
+ }
+ }
+
+#ifdef IS_WINDOWS_PC
+ // This warning only applies if you want to catch structured exceptions (crashes)
+ // using C++ exceptions. We do not want to do that so we can build with C++ exceptions
+ // completely disabled, and just suppress this warning.
+ // warning C4535: calling _set_se_translator() requires /EHa
+ #pragma warning( suppress : 4535 )
+ _se_translator_function curfilter = _set_se_translator( &FailSafe );
+#elif defined( POSIX )
+ // Only need to worry about this function crashing when we're dealing with a real crash.
+ __sighandler_t curfilter = bRealCrash ? signal( SIGSEGV, posix_signal_handler ) : 0;
+#endif
+
+ DO_TRY
+ {
+ Q_memset( m_errorText, 0x00, sizeof( m_errorText ) );
+
+ if ( pchSysErrorText )
+ {
+ CommentCat( "Sys_Error( " );
+ CommentCat( pchSysErrorText );
+
+ // Trim trailing \n.
+ int len = V_strlen( m_errorText );
+ if ( len > 0 && m_errorText[ len - 1 ] == '\n' )
+ m_errorText[ len - 1 ] = 0;
+
+ CommentCat( " )\n" );
+ }
+ else
+ {
+ CommentCat( "Crash\n" );
+ }
+ CommentPrintf( "Uptime( %f )\n", Plat_FloatTime() );
+ CommentPrintf( "SourceMod:%d,DS:%d,Crash:%d\n\n", bSourceModLoaded, m_bIsDedicatedServer, bRealCrash );
+
+ // Add g_minidumpinfo from CL_SetSteamCrashComment().
+ CommentCat( g_minidumpinfo );
+
+ // Latch in case extended stuff below crashes
+ Steam_SetMiniDumpComment();
+
+ // Add Memory Status
+ BuildCommentMemStatus();
+
+ // Spew out paged pool stuff, etc.
+ PAGED_POOL_INFO_t ppi_info;
+ if ( Plat_GetPagedPoolInfo( &ppi_info ) != SYSCALL_UNSUPPORTED )
+ {
+ CommentPrintf( "\nPaged Pool\nprev PP PAGES: used: %lu, free %lu\nfinal PP PAGES: used: %lu, free %lu\n",
+ g_pagedpoolinfo.numPagesUsed, g_pagedpoolinfo.numPagesFree,
+ ppi_info.numPagesUsed, ppi_info.numPagesFree );
+ }
+
+ CommentPrintf( "memallocfail? = %u\nActive: %s\nSpawnCount %d MapLoad Count %d\nError count %d, end demo %d, abort count %d\n",
+ MemAlloc_MemoryAllocFailed(),
+ ( game && game->IsActiveApp() ) ? "active" : "inactive",
+ gHostSpawnCount,
+ g_nMapLoadCount,
+ g_HostErrorCount,
+ g_HostEndDemo,
+ g_HostServerAbortCount );
+
+ // Latch in case extended stuff below crashes
+ Steam_SetMiniDumpComment();
+
+ // Add user comment strings. 4096 is just a large sanity number we should
+ // never ever reach (currently our minidump supports 32 of these.)
+ for( int i = 0; i < 4096; i++ )
+ {
+ const char *pUserStreamInfo = MinidumpUserStreamInfoGet( i );
+ if( !pUserStreamInfo )
+ break;
+
+ if ( pUserStreamInfo[ 0 ] )
+ CommentPrintf( "%s", pUserStreamInfo );
+ }
+
+ bool bExtendedSpew = sys_minidumpexpandedspew.GetBool();
+ if ( bExtendedSpew )
+ {
+ BuildCommentExtended();
+ Steam_SetMiniDumpComment();
+
+#if defined( LINUX )
+ if ( bRealCrash )
+ {
+ // bRealCrash is set when we're actually making a comment for a dump or error.
+ AddFileToComment( "/proc/meminfo" );
+ AddFileToComment( "/proc/self/status" );
+ Steam_SetMiniDumpComment();
+
+ // Useful, but really big, so disable for now.
+ //$ AddFileToComment( "/proc/self/maps" );
+ }
+#endif
+ }
+ }
+ DO_CATCH
+ {
+ // Oh oh
+ }
+
+#ifdef IS_WINDOWS_PC
+ _set_se_translator( curfilter );
+#elif defined( POSIX )
+ if ( bRealCrash )
+ signal( SIGSEGV, curfilter );
+#endif
+ }
+
+ void BuildCommentMemStatus()
+ {
+#ifdef _WIN32
+ const double MbDiv = 1024.0 * 1024.0;
+
+ MEMORYSTATUSEX memStat;
+ ZeroMemory( &memStat, sizeof( MEMORYSTATUSEX ) );
+ memStat.dwLength = sizeof( MEMORYSTATUSEX );
+
+ if ( GlobalMemoryStatusEx( &memStat ) )
+ {
+ CommentPrintf( "\nMemory\nmemusage( %d %% )\ntotalPhysical Mb(%.2f)\nfreePhysical Mb(%.2f)\ntotalPaging Mb(%.2f)\nfreePaging Mb(%.2f)\ntotalVirtualMem Mb(%.2f)\nfreeVirtualMem Mb(%.2f)\nextendedVirtualFree Mb(%.2f)\n",
+ memStat.dwMemoryLoad,
+ (double)memStat.ullTotalPhys / MbDiv,
+ (double)memStat.ullAvailPhys / MbDiv,
+ (double)memStat.ullTotalPageFile / MbDiv,
+ (double)memStat.ullAvailPageFile / MbDiv,
+ (double)memStat.ullTotalVirtual / MbDiv,
+ (double)memStat.ullAvailVirtual / MbDiv,
+ (double)memStat.ullAvailExtendedVirtual / MbDiv);
+ }
+
+ HINSTANCE hInst = LoadLibrary( "Psapi.dll" );
+ if ( hInst )
+ {
+ typedef BOOL (WINAPI *GetProcessMemoryInfoFn)(HANDLE, PPROCESS_MEMORY_COUNTERS, DWORD);
+ GetProcessMemoryInfoFn fn = (GetProcessMemoryInfoFn)GetProcAddress( hInst, "GetProcessMemoryInfo" );
+ if ( fn )
+ {
+ PROCESS_MEMORY_COUNTERS counters;
+
+ ZeroMemory( &counters, sizeof( PROCESS_MEMORY_COUNTERS ) );
+ counters.cb = sizeof( PROCESS_MEMORY_COUNTERS );
+
+ if ( fn( GetCurrentProcess(), &counters, sizeof( PROCESS_MEMORY_COUNTERS ) ) )
+ {
+ CommentPrintf( "\nProcess Memory\nWorkingSetSize Mb(%.2f)\nQuotaPagedPoolUsage Mb(%.2f)\nQuotaNonPagedPoolUsage: Mb(%.2f)\nPagefileUsage: Mb(%.2f)\n",
+ (double)counters.WorkingSetSize / MbDiv,
+ (double)counters.QuotaPagedPoolUsage / MbDiv,
+ (double)counters.QuotaNonPagedPoolUsage / MbDiv,
+ (double)counters.PagefileUsage / MbDiv );
+ }
+ }
+
+ FreeLibrary( hInst );
+ }
+
+#elif defined( OSX )
+
+ static const struct
+ {
+ int ctl;
+ const char *name;
+ } s_ctl_names[] =
+ {
+ #define _XTAG( _x ) { _x, #_x }
+ _XTAG( HW_PHYSMEM ),
+ _XTAG( HW_USERMEM ),
+ _XTAG( HW_MEMSIZE ),
+ _XTAG( HW_AVAILCPU ),
+ #undef _XTAG
+ };
+
+ for ( size_t i = 0; i < Q_ARRAYSIZE( s_ctl_names ); i++ )
+ {
+ uint64_t val = 0;
+ size_t len = sizeof( val );
+ int mib[] = { CTL_HW, s_ctl_names[ i ].ctl };
+
+ if ( sysctl( mib, Q_ARRAYSIZE( mib ), &val, &len, NULL, 0 ) == 0 )
+ {
+ CommentPrintf( " %s: %" PRIu64 "\n", s_ctl_names[ i ].name, val );
+ }
+ }
+
+#endif
+ }
+
+ void BuildCommentExtended()
+ {
+ try
+ {
+ CommentCat( "\nConVars (non-default)\n\n" );
+ CommentPrintf( "%s %s %s\n", "var", "value", "default" );
+
+ for ( const ConCommandBase *var = g_pCVar->GetCommands() ; var ; var = var->GetNext())
+ {
+ if ( var->IsCommand() )
+ continue;
+
+ ConVar *pCvar = ( ConVar * )var;
+ if ( pCvar->IsFlagSet( FCVAR_SERVER_CANNOT_QUERY | FCVAR_PROTECTED ) )
+ continue;
+
+ if ( !(pCvar->IsFlagSet( FCVAR_NEVER_AS_STRING ) ) )
+ {
+ char var1[ MAX_OSPATH ];
+ char var2[ MAX_OSPATH ];
+
+ Q_strncpy( var1, Host_CleanupConVarStringValue( pCvar->GetString() ), sizeof( var1 ) );
+ Q_strncpy( var2, Host_CleanupConVarStringValue( pCvar->GetDefault() ), sizeof( var2 ) );
+
+ if ( !Q_stricmp( var1, var2 ) )
+ continue;
+ }
+ else
+ {
+ if ( pCvar->GetFloat() == Q_atof( pCvar->GetDefault() ) )
+ continue;
+ }
+
+ if ( !(pCvar->IsFlagSet( FCVAR_NEVER_AS_STRING ) ) )
+ CommentPrintf( "%s '%s' '%s'\n", pCvar->GetName(), Host_CleanupConVarStringValue( pCvar->GetString() ), pCvar->GetDefault() );
+ else
+ CommentPrintf( "%s '%f' '%f'\n", pCvar->GetName(), pCvar->GetFloat(), Q_atof( pCvar->GetDefault() ) );
+ }
+
+ CommentCat( "\nConsole History (reversed)\n\n" );
+
+ // Get console
+ int len = V_strlen( m_errorText );
+ if ( len < sizeof( m_errorText ) )
+ {
+ GetSpew( m_errorText + len, sizeof( m_errorText ) - len - 1 );
+ m_errorText[ sizeof( m_errorText ) - 1 ] = 0;
+ }
+ }
+ catch ( ... )
+ {
+ CommentCat( "Exception thrown building console/convar history.\n" );
+ }
+ }
+
+#if defined( LINUX )
+
+ void AddFileToComment( const char *filename )
+ {
+ CommentPrintf( "\n%s:\n", filename );
+
+ int nStart = Q_strlen( m_errorText );
+ int nMaxLen = sizeof( m_errorText ) - nStart - 1;
+
+ if ( nMaxLen > 0 )
+ {
+ FILE *fh = fopen( filename, "r" );
+
+ if ( fh )
+ {
+ size_t ret = fread( m_errorText + nStart, 1, nMaxLen, fh );
+ fclose( fh );
+
+ // Replace tab characters with spaces.
+ for ( size_t i = 0; i < ret; i++ )
+ {
+ if ( m_errorText[ nStart + i ] == '\t' )
+ m_errorText[ nStart + i ] = ' ';
+ }
+ }
+
+ // Entire buffer should have been zeroed out, but just super sure...
+ m_errorText[ sizeof( m_errorText ) - 1 ] = 0;
+ }
+ }
+
+#endif // LINUX
+
+public:
+ char m_errorText[ _SIZE ];
+ bool m_bIsDedicatedServer;
+};
+
+#if defined( _X360 )
+static CErrorText<3500> errorText;
+#else
+static CErrorText<95000> errorText;
+#endif
+
+void BuildMinidumpComment( char const *pchSysErrorText, bool bRealCrash )
+{
+#if !defined(NO_STEAM)
+ /*
+ // Uncomment this code if you are testing max minidump comment length issues
+ // It allows you to asked for a dummy comment of a certain length
+ int nCommentLength = CommandLine()->ParmValue( "-commentlen", 0 );
+ if ( nCommentLength > 0 )
+ {
+ nCommentLength = MIN( nCommentLength, 128*1024 );
+ char *cbuf = new char[ nCommentLength + 1 ];
+ for ( int i = 0; i < nCommentLength; ++i )
+ {
+ cbuf[ i ] = (char)('0' + (i % 10));
+ }
+ cbuf[ nCommentLength ] = 0;
+ SteamAPI_SetMiniDumpComment( cbuf );
+ delete[] cbuf;
+ return;
+ }
+ */
+ errorText.BuildComment( pchSysErrorText, bRealCrash );
+#endif
+}
+
+#if defined( POSIX )
+
+static void PosixPreMinidumpCallback( void *context )
+{
+ BuildMinidumpComment( NULL, true );
+}
+
+#endif
+
+//-----------------------------------------------------------------------------
+// Purpose: Attempt to initialize appid/steam.inf/minidump information. May only return partial information if called
+// before Filesystem is ready.
+//
+// The desire is to be able to call this ASAP to init basic minidump and AppID info, then re-call later on when
+// the filesystem is setup, so full version # information and such can be propagated to the minidump system.
+// (Currently, SDK mods will generally only have partial information prior to filesystem init)
+//-----------------------------------------------------------------------------
+// steam.inf keys.
+#define VERSION_KEY "PatchVersion="
+#define PRODUCT_KEY "ProductName="
+#define SERVER_VERSION_KEY "ServerVersion="
+#define APPID_KEY "AppID="
+#define SERVER_APPID_KEY "ServerAppID="
+enum eSteamInfoInit
+{
+ eSteamInfo_Uninitialized,
+ eSteamInfo_Partial,
+ eSteamInfo_Initialized
+};
+static eSteamInfoInit Sys_TryInitSteamInfo( void *pvAPI, SteamInfVersionInfo_t& VerInfo, const char *pchMod, const char *pchBaseDir, bool bDedicated )
+{
+ static eSteamInfoInit initState = eSteamInfo_Uninitialized;
+
+ eSteamInfoInit previousInitState = initState;
+
+ //
+ //
+ // Initialize with some defaults.
+ VerInfo.ClientVersion = 0;
+ VerInfo.ServerVersion = 0;
+ V_strcpy_safe( VerInfo.szVersionString, "valve" );
+ V_strcpy_safe( VerInfo.szProductString, "1.0.1.0" );
+ VerInfo.AppID = k_uAppIdInvalid;
+ VerInfo.ServerAppID = k_uAppIdInvalid;
+
+ // Filesystem may or may not be up
+ CUtlBuffer infBuf;
+ bool bFoundInf = false;
+ if ( g_pFileSystem )
+ {
+ FileHandle_t fh;
+ fh = g_pFileSystem->Open( "steam.inf", "rb", "GAME" );
+ bFoundInf = fh && g_pFileSystem->ReadToBuffer( fh, infBuf );
+ }
+
+ if ( !bFoundInf )
+ {
+ // We may try to load the steam.inf BEFORE we turn on the filesystem, so use raw filesystem API's here.
+ char szFullPath[ MAX_PATH ] = { 0 };
+ char szModSteamInfPath[ MAX_PATH ] = { 0 };
+ V_ComposeFileName( pchMod, "steam.inf", szModSteamInfPath, sizeof( szModSteamInfPath ) );
+ V_MakeAbsolutePath( szFullPath, sizeof( szFullPath ), szModSteamInfPath, pchBaseDir );
+
+ // Try opening steam.inf
+ FILE *fp = fopen( szFullPath, "rb" );
+ if ( fp )
+ {
+ // Read steam.inf data.
+ fseek( fp, 0, SEEK_END );
+ size_t bufsize = ftell( fp );
+ fseek( fp, 0, SEEK_SET );
+
+ infBuf.EnsureCapacity( bufsize + 1 );
+
+ size_t iBytesRead = fread( infBuf.Base(), 1, bufsize, fp );
+ ((char *)infBuf.Base())[iBytesRead] = 0;
+ infBuf.SeekPut( CUtlBuffer::SEEK_CURRENT, iBytesRead + 1 );
+ fclose( fp );
+
+ bFoundInf = ( iBytesRead == bufsize );
+ }
+ }
+
+ if ( bFoundInf )
+ {
+ const char *pbuf = (const char*)infBuf.Base();
+ while ( 1 )
+ {
+ pbuf = COM_Parse( pbuf );
+ if ( !pbuf || !com_token[ 0 ] )
+ break;
+
+ if ( !Q_strnicmp( com_token, VERSION_KEY, Q_strlen( VERSION_KEY ) ) )
+ {
+ V_strcpy_safe( VerInfo.szVersionString, com_token + Q_strlen( VERSION_KEY ) );
+ VerInfo.ClientVersion = atoi( VerInfo.szVersionString );
+ }
+ else if ( !Q_strnicmp( com_token, PRODUCT_KEY, Q_strlen( PRODUCT_KEY ) ) )
+ {
+ V_strcpy_safe( VerInfo.szProductString, com_token + Q_strlen( PRODUCT_KEY ) );
+ }
+ else if ( !Q_strnicmp( com_token, SERVER_VERSION_KEY, Q_strlen( SERVER_VERSION_KEY ) ) )
+ {
+ VerInfo.ServerVersion = atoi( com_token + Q_strlen( SERVER_VERSION_KEY ) );
+ }
+ else if ( !Q_strnicmp( com_token, APPID_KEY, Q_strlen( APPID_KEY ) ) )
+ {
+ VerInfo.AppID = atoi( com_token + Q_strlen( APPID_KEY ) );
+ }
+ else if ( !Q_strnicmp( com_token, SERVER_APPID_KEY, Q_strlen( SERVER_APPID_KEY ) ) )
+ {
+ VerInfo.ServerAppID = atoi( com_token + Q_strlen( SERVER_APPID_KEY ) );
+ }
+ }
+
+ // If we found a steam.inf we're as good as we're going to get, but don't tell callers we're fully initialized
+ // if it doesn't at least have an AppID
+ initState = ( VerInfo.AppID != k_uAppIdInvalid ) ? eSteamInfo_Initialized : eSteamInfo_Partial;
+ }
+ else if ( !bDedicated )
+ {
+ // Opening steam.inf failed - try to open gameinfo.txt and read in just SteamAppId from that.
+ // (gameinfo.txt lacks the dedicated server steamid, so we'll just have to live until filesystem init to setup
+ // breakpad there when we hit this case)
+ char szModGameinfoPath[ MAX_PATH ] = { 0 };
+ char szFullPath[ MAX_PATH ] = { 0 };
+ V_ComposeFileName( pchMod, "gameinfo.txt", szModGameinfoPath, sizeof( szModGameinfoPath ) );
+ V_MakeAbsolutePath( szFullPath, sizeof( szFullPath ), szModGameinfoPath, pchBaseDir );
+
+ // Try opening gameinfo.txt
+ FILE *fp = fopen( szFullPath, "rb" );
+ if( fp )
+ {
+ fseek( fp, 0, SEEK_END );
+ size_t bufsize = ftell( fp );
+ fseek( fp, 0, SEEK_SET );
+
+ char *buffer = ( char * )_alloca( bufsize + 1 );
+
+ size_t iBytesRead = fread( buffer, 1, bufsize, fp );
+ buffer[ iBytesRead ] = 0;
+ fclose( fp );
+
+ KeyValuesAD pkvGameInfo( "gameinfo" );
+ if ( pkvGameInfo->LoadFromBuffer( "gameinfo.txt", buffer ) )
+ {
+ VerInfo.AppID = (AppId_t)pkvGameInfo->GetInt( "FileSystem/SteamAppId", k_uAppIdInvalid );
+ }
+ }
+
+ initState = eSteamInfo_Partial;
+ }
+
+ // In partial state the ServerAppID might be unknown, but if we found the full steam.inf and it's not set, it shares AppID.
+ if ( initState == eSteamInfo_Initialized && VerInfo.ServerAppID == k_uAppIdInvalid )
+ VerInfo.ServerAppID = VerInfo.AppID;
+
+#if !defined(_X360)
+ if ( VerInfo.AppID )
+ {
+ // steamclient.dll doesn't know about steam.inf files in mod folder,
+ // it accepts a steam_appid.txt in the root directory if the game is
+ // not started through Steam. So we create one there containing the
+ // current AppID
+ FILE *fh = fopen( "steam_appid.txt", "wb" );
+ if ( fh )
+ {
+ CFmtStrN< 128 > strAppID( "%u\n", VerInfo.AppID );
+
+ fwrite( strAppID.Get(), strAppID.Length() + 1, 1, fh );
+ fclose( fh );
+ }
+ }
+#endif // !_X360
+
+ //
+ // Update minidump info if we have more information than before
+ //
+
+#ifndef NO_STEAM
+ // If -nobreakpad was specified or we found metamod or sourcemod, don't register breakpad.
+ bool bUseBreakpad = !CommandLine()->FindParm( "-nobreakpad" ) && ( !bDedicated || !IsSourceModLoaded() );
+ AppId_t BreakpadAppId = bDedicated ? VerInfo.ServerAppID : VerInfo.AppID;
+ Assert( BreakpadAppId != k_uAppIdInvalid || initState < eSteamInfo_Initialized );
+ if ( BreakpadAppId != k_uAppIdInvalid && initState > previousInitState && bUseBreakpad )
+ {
+ void *pvMiniDumpContext = NULL;
+ PFNPreMinidumpCallback pfnPreMinidumpCallback = NULL;
+ bool bFullMemoryDump = !bDedicated && IsWindows() && CommandLine()->FindParm( "-full_memory_dumps" );
+
+#if defined( POSIX )
+ // On Windows we're relying on the try/except to build the minidump comment. On Linux, we don't have that
+ // so we need to register the minidumpcallback handler here.
+ pvMiniDumpContext = pvAPI;
+ pfnPreMinidumpCallback = PosixPreMinidumpCallback;
+#endif
+
+ CFmtStrN<128> pchVersion( "%d", build_number() );
+ Msg( "Using Breakpad minidump system. Version: %s AppID: %u\n", pchVersion.Get(), BreakpadAppId );
+
+ // We can filter various crash dumps differently in the Socorro backend code:
+ // Steam/min/web/crash_reporter/socorro/scripts/config/collectorconfig.py
+ SteamAPI_SetBreakpadAppID( BreakpadAppId );
+ SteamAPI_UseBreakpadCrashHandler( pchVersion, __DATE__, __TIME__, bFullMemoryDump, pvMiniDumpContext, pfnPreMinidumpCallback );
+
+ // Tell errorText class if this is dedicated server.
+ errorText.m_bIsDedicatedServer = bDedicated;
+ }
+#endif // NO_STEAM
+
+ MinidumpUserStreamInfoSetHeader( "%sLaunching \"%s\"\n", ( bDedicated ? "DedicatedServerAPI " : "" ), CommandLine()->GetCmdLine() );
+
+
+ return initState;
+}
+
+#ifndef SWDS
+
+//-----------------------------------------------------------------------------
+//
+// Main engine interface exposed to launcher
+//
+//-----------------------------------------------------------------------------
+class CEngineAPI : public CTier3AppSystem< IEngineAPI >
+{
+ typedef CTier3AppSystem< IEngineAPI > BaseClass;
+
+public:
+ virtual bool Connect( CreateInterfaceFn factory );
+ virtual void Disconnect();
+ virtual void *QueryInterface( const char *pInterfaceName );
+ virtual InitReturnVal_t Init();
+ virtual void Shutdown();
+
+ // This function must be called before init
+ virtual void SetStartupInfo( StartupInfo_t &info );
+
+ virtual int Run( );
+
+ // Sets the engine to run in a particular editor window
+ virtual void SetEngineWindow( void *hWnd );
+
+ // Posts a console command
+ virtual void PostConsoleCommand( const char *pConsoleCommand );
+
+ // Are we running the simulation?
+ virtual bool IsRunningSimulation( ) const;
+
+ // Start/stop running the simulation
+ virtual void ActivateSimulation( bool bActive );
+
+ // Reset the map we're on
+ virtual void SetMap( const char *pMapName );
+
+ bool MainLoop();
+
+ int RunListenServer();
+
+private:
+
+ // Hooks a particular mod up to the registry
+ void SetRegistryMod( const char *pModName );
+
+ // One-time setup, based on the initially selected mod
+ // FIXME: This should move into the launcher!
+ bool OnStartup( void *pInstance, const char *pStartupModName );
+ void OnShutdown();
+
+ // Initialization, shutdown of a mod.
+ bool ModInit( const char *pModName, const char *pGameDir );
+ void ModShutdown();
+
+ // Initializes, shuts down the registry
+ bool InitRegistry( const char *pModName );
+ void ShutdownRegistry();
+
+ // Handles there being an error setting up the video mode
+ InitReturnVal_t HandleSetModeError();
+
+ // Initializes, shuts down VR
+ bool InitVR();
+ void ShutdownVR();
+
+ // Purpose: Message pump when running stand-alone
+ void PumpMessages();
+
+ // Purpose: Message pump when running with the editor
+ void PumpMessagesEditMode( bool &bIdle, long &lIdleCount );
+
+ // Activate/deactivates edit mode shaders
+ void ActivateEditModeShaders( bool bActive );
+
+private:
+ void *m_hEditorHWnd;
+ bool m_bRunningSimulation;
+ bool m_bSupportsVR;
+ StartupInfo_t m_StartupInfo;
+};
+
+
+//-----------------------------------------------------------------------------
+// Singleton interface
+//-----------------------------------------------------------------------------
+static CEngineAPI s_EngineAPI;
+EXPOSE_SINGLE_INTERFACE_GLOBALVAR( CEngineAPI, IEngineAPI, VENGINE_LAUNCHER_API_VERSION, s_EngineAPI );
+
+
+//-----------------------------------------------------------------------------
+// Connect, disconnect
+//-----------------------------------------------------------------------------
+bool CEngineAPI::Connect( CreateInterfaceFn factory )
+{
+ // Store off the app system factory...
+ g_AppSystemFactory = factory;
+
+ if ( !BaseClass::Connect( factory ) )
+ return false;
+
+ g_pFileSystem = g_pFullFileSystem;
+ if ( !g_pFileSystem )
+ return false;
+
+ g_pFileSystem->SetWarningFunc( Warning );
+
+ if ( !Shader_Connect( true ) )
+ return false;
+
+ g_pPhysics = (IPhysics*)factory( VPHYSICS_INTERFACE_VERSION, NULL );
+
+ if ( !g_pStudioRender || !g_pDataCache || !g_pPhysics || !g_pMDLCache || !g_pMatSystemSurface || !g_pInputSystem /* || !g_pVideo */ )
+ {
+ Warning( "Engine wasn't able to acquire required interfaces!\n" );
+ return false;
+ }
+
+ if (!g_pStudioRender)
+ {
+ Sys_Error( "Unable to init studio render system version %s\n", STUDIO_RENDER_INTERFACE_VERSION );
+ return false;
+ }
+
+ g_pHammer = (IHammer*)factory( INTERFACEVERSION_HAMMER, NULL );
+
+#if defined( USE_SDL )
+ g_pLauncherMgr = (ILauncherMgr *)factory( SDLMGR_INTERFACE_VERSION, NULL );
+#endif
+
+ ConnectMDLCacheNotify();
+
+ return true;
+}
+
+void CEngineAPI::Disconnect()
+{
+ DisconnectMDLCacheNotify();
+
+#if !defined( SWDS )
+ TRACESHUTDOWN( Steam3Client().Shutdown() );
+#endif
+
+ g_pHammer = NULL;
+ g_pPhysics = NULL;
+
+ Shader_Disconnect();
+
+ g_pFileSystem = NULL;
+
+ BaseClass::Disconnect();
+
+ g_AppSystemFactory = NULL;
+}
+
+
+//-----------------------------------------------------------------------------
+// Query interface
+//-----------------------------------------------------------------------------
+void *CEngineAPI::QueryInterface( const char *pInterfaceName )
+{
+ // Loading the engine DLL mounts *all* engine interfaces
+ CreateInterfaceFn factory = Sys_GetFactoryThis(); // This silly construction is necessary
+ return factory( pInterfaceName, NULL ); // to prevent the LTCG compiler from crashing.
+}
+
+
+//-----------------------------------------------------------------------------
+// Sets startup info
+//-----------------------------------------------------------------------------
+void CEngineAPI::SetStartupInfo( StartupInfo_t &info )
+{
+ // Setup and write out steam_appid.txt before we launch
+ bool bDedicated = false; // Dedicated comes through CDedicatedServerAPI
+ eSteamInfoInit steamInfo = Sys_TryInitSteamInfo( this, g_SteamInfIDVersionInfo, info.m_pInitialMod, info.m_pBaseDirectory, bDedicated );
+
+ g_bTextMode = info.m_bTextMode;
+
+ // Set up the engineparms_t which contains global information about the mod
+ host_parms.basedir = const_cast<char*>( info.m_pBaseDirectory );
+
+ // Copy off all the startup info
+ m_StartupInfo = info;
+
+#if !defined( SWDS )
+ // turn on the Steam3 API early so we can query app data up front
+ TRACEINIT( Steam3Client().Activate(), Steam3Client().Shutdown() );
+#endif
+
+ // Needs to be done prior to init material system config
+ TRACEINIT( COM_InitFilesystem( m_StartupInfo.m_pInitialMod ), COM_ShutdownFileSystem() );
+
+ if ( steamInfo != eSteamInfo_Initialized )
+ {
+ // Try again with filesystem available. This is commonly needed for SDK mods which need the filesystem to find
+ // their steam.inf, due to mounting SDK search paths.
+ steamInfo = Sys_TryInitSteamInfo( this, g_SteamInfIDVersionInfo, info.m_pInitialMod, info.m_pBaseDirectory, bDedicated );
+ Assert( steamInfo == eSteamInfo_Initialized );
+ if ( steamInfo != eSteamInfo_Initialized )
+ {
+ Warning( "Failed to find steam.inf or equivalent steam info. May not have proper information to connect to Steam.\n" );
+ }
+ }
+
+ m_bSupportsVR = false;
+ if ( IsPC() )
+ {
+ KeyValues *modinfo = new KeyValues("ModInfo");
+ if ( modinfo->LoadFromFile( g_pFileSystem, "gameinfo.txt" ) )
+ {
+ // Enable file tracking - client always does this in case it connects to a pure server.
+ // server only does this if sv_pure is set
+ // If it's not singleplayer_only
+ if ( V_stricmp( modinfo->GetString("type", "singleplayer_only"), "singleplayer_only") == 0 )
+ {
+ DevMsg( "Disabling whitelist file tracking in filesystem...\n" );
+ g_pFileSystem->EnableWhitelistFileTracking( false, false, false );
+ }
+ else
+ {
+ DevMsg( "Enabling whitelist file tracking in filesystem...\n" );
+ g_pFileSystem->EnableWhitelistFileTracking( true, false, false );
+ }
+
+ m_bSupportsVR = modinfo->GetInt( "supportsvr" ) > 0 && CommandLine()->CheckParm( "-vr" );
+ if ( m_bSupportsVR )
+ {
+ // This also has to happen before CreateGameWindow to know where to put
+ // the window and how big to make it
+ if ( InitVR() )
+ {
+ if ( Steam3Client().SteamUtils() )
+ {
+ if ( Steam3Client().SteamUtils()->IsSteamRunningInVR() && g_pSourceVR->IsHmdConnected() )
+ {
+ int nForceVRAdapterIndex = g_pSourceVR->GetVRModeAdapter();
+ materials->SetAdapter( nForceVRAdapterIndex, 0 );
+
+ g_pSourceVR->SetShouldForceVRMode();
+ }
+ }
+ }
+ }
+
+ }
+ modinfo->deleteThis();
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Init, shutdown
+//-----------------------------------------------------------------------------
+InitReturnVal_t CEngineAPI::Init()
+{
+ if ( CommandLine()->FindParm( "-sv_benchmark" ) != 0 )
+ {
+ Plat_SetBenchmarkMode( true );
+ }
+
+ InitReturnVal_t nRetVal = BaseClass::Init();
+ if ( nRetVal != INIT_OK )
+ return nRetVal;
+
+ m_bRunningSimulation = false;
+
+ // Initialize the FPU control word
+#if defined(WIN32) && !defined( SWDS ) && !defined( _X360 )
+ _asm
+ {
+ fninit
+ }
+#endif
+
+ SetupFPUControlWord();
+
+ // This creates the videomode singleton object, it doesn't depend on the registry
+ VideoMode_Create();
+
+ // Initialize the editor hwnd to render into
+ m_hEditorHWnd = NULL;
+
+ // One-time setup
+ // FIXME: OnStartup + OnShutdown should be removed + moved into the launcher
+ // or the launcher code should be merged into the engine into the code in OnStartup/OnShutdown
+ if ( !OnStartup( m_StartupInfo.m_pInstance, m_StartupInfo.m_pInitialMod ) )
+ {
+ return HandleSetModeError();
+ }
+
+ return INIT_OK;
+}
+
+void CEngineAPI::Shutdown()
+{
+ VideoMode_Destroy();
+ BaseClass::Shutdown();
+}
+
+
+//-----------------------------------------------------------------------------
+// Sets the engine to run in a particular editor window
+//-----------------------------------------------------------------------------
+void CEngineAPI::SetEngineWindow( void *hWnd )
+{
+ if ( !InEditMode() )
+ return;
+
+ // Detach input from the previous editor window
+ game->InputDetachFromGameWindow();
+
+ m_hEditorHWnd = hWnd;
+ videomode->SetGameWindow( m_hEditorHWnd );
+}
+
+
+//-----------------------------------------------------------------------------
+// Posts a console command
+//-----------------------------------------------------------------------------
+void CEngineAPI::PostConsoleCommand( const char *pCommand )
+{
+ Cbuf_AddText( pCommand );
+}
+
+
+//-----------------------------------------------------------------------------
+// Is the engine currently rinning?
+//-----------------------------------------------------------------------------
+bool CEngineAPI::IsRunningSimulation() const
+{
+ return (eng->GetState() == IEngine::DLL_ACTIVE);
+}
+
+
+//-----------------------------------------------------------------------------
+// Reset the map we're on
+//-----------------------------------------------------------------------------
+void CEngineAPI::SetMap( const char *pMapName )
+{
+// if ( !Q_stricmp( sv.mapname, pMapName ) )
+// return;
+
+ char buf[MAX_PATH];
+ Q_snprintf( buf, MAX_PATH, "map %s", pMapName );
+ Cbuf_AddText( buf );
+}
+
+
+//-----------------------------------------------------------------------------
+// Start/stop running the simulation
+//-----------------------------------------------------------------------------
+void CEngineAPI::ActivateSimulation( bool bActive )
+{
+ // FIXME: Not sure what will happen in this case
+ if ( ( eng->GetState() != IEngine::DLL_ACTIVE ) &&
+ ( eng->GetState() != IEngine::DLL_PAUSED ) )
+ {
+ return;
+ }
+
+ bool bCurrentlyActive = (eng->GetState() != IEngine::DLL_PAUSED);
+ if ( bActive == bCurrentlyActive )
+ return;
+
+ // FIXME: Should attachment/detachment be part of the state machine in IEngine?
+ if ( !bActive )
+ {
+ eng->SetNextState( IEngine::DLL_PAUSED );
+
+ // Detach input from the previous editor window
+ game->InputDetachFromGameWindow();
+ }
+ else
+ {
+ eng->SetNextState( IEngine::DLL_ACTIVE );
+
+ // Start accepting input from the new window
+ // FIXME: What if the attachment fails?
+ game->InputAttachToGameWindow();
+ }
+}
+
+static void MoveConsoleWindowToFront()
+{
+#ifdef _WIN32
+ // Move the window to the front.
+ HINSTANCE hInst = LoadLibrary( "kernel32.dll" );
+ if ( hInst )
+ {
+ typedef HWND (*GetConsoleWindowFn)();
+ GetConsoleWindowFn fn = (GetConsoleWindowFn)GetProcAddress( hInst, "GetConsoleWindow" );
+ if ( fn )
+ {
+ HWND hwnd = fn();
+ ShowWindow( hwnd, SW_SHOW );
+ UpdateWindow( hwnd );
+ SetWindowPos( hwnd, HWND_TOP, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW );
+ }
+ FreeLibrary( hInst );
+ }
+#endif
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Message pump when running stand-alone
+//-----------------------------------------------------------------------------
+void CEngineAPI::PumpMessages()
+{
+ // This message pumping happens in SDL if SDL is enabled.
+#if defined( PLATFORM_WINDOWS ) && !defined( USE_SDL )
+ MSG msg;
+ while ( PeekMessage( &msg, NULL, 0, 0, PM_REMOVE ) )
+ {
+ TranslateMessage( &msg );
+ DispatchMessage( &msg );
+ }
+#endif
+
+#if defined( USE_SDL )
+ g_pLauncherMgr->PumpWindowsMessageLoop();
+#endif
+
+ // Get input from attached devices
+ g_pInputSystem->PollInputState();
+
+ if ( IsX360() )
+ {
+ // handle Xbox system messages
+ XBX_ProcessEvents();
+ }
+
+ // NOTE: Under some implementations of Win9x,
+ // dispatching messages can cause the FPU control word to change
+ if ( IsPC() )
+ {
+ SetupFPUControlWord();
+ }
+
+ game->DispatchAllStoredGameMessages();
+
+ if ( IsPC() )
+ {
+ static bool s_bFirstRun = true;
+ if ( s_bFirstRun )
+ {
+ s_bFirstRun = false;
+ MoveConsoleWindowToFront();
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Message pump when running stand-alone
+//-----------------------------------------------------------------------------
+void CEngineAPI::PumpMessagesEditMode( bool &bIdle, long &lIdleCount )
+{
+
+ if ( bIdle && !g_pHammer->HammerOnIdle( lIdleCount++ ) )
+ {
+ bIdle = false;
+ }
+
+ // Get input from attached devices
+ g_pInputSystem->PollInputState();
+
+#ifdef WIN32
+ MSG msg;
+ while ( PeekMessage(&msg, NULL, 0, 0, PM_REMOVE) )
+ {
+ if ( msg.message == WM_QUIT )
+ {
+ eng->SetQuitting( IEngine::QUIT_TODESKTOP );
+ break;
+ }
+
+ if ( !g_pHammer->HammerPreTranslateMessage(&msg) )
+ {
+ TranslateMessage(&msg);
+ DispatchMessage(&msg);
+ }
+
+ // Reset idle state after pumping idle message.
+ if ( g_pHammer->HammerIsIdleMessage(&msg) )
+ {
+ bIdle = true;
+ lIdleCount = 0;
+ }
+ }
+#elif defined( USE_SDL )
+ Error( "Not supported" );
+#else
+#error
+#endif
+
+
+ // NOTE: Under some implementations of Win9x,
+ // dispatching messages can cause the FPU control word to change
+ SetupFPUControlWord();
+
+ game->DispatchAllStoredGameMessages();
+}
+
+//-----------------------------------------------------------------------------
+// Activate/deactivates edit mode shaders
+//-----------------------------------------------------------------------------
+void CEngineAPI::ActivateEditModeShaders( bool bActive )
+{
+ if ( InEditMode() && ( g_pMaterialSystemConfig->bEditMode != bActive ) )
+ {
+ MaterialSystem_Config_t config = *g_pMaterialSystemConfig;
+ config.bEditMode = bActive;
+ OverrideMaterialSystemConfig( config );
+ }
+}
+
+
+#ifdef GPROFILER
+static bool g_gprofiling = false;
+
+CON_COMMAND( gprofilerstart, "Starts the gperftools profiler recording to the specified file." )
+{
+ if ( g_gprofiling )
+ {
+ Msg( "Profiling is already started.\n" );
+ return;
+ }
+
+ char buffer[500];
+ const char* profname = buffer;
+ if ( args.ArgC() < 2 )
+ {
+ static const char *s_pszHomeDir = getenv("HOME");
+ if ( !s_pszHomeDir )
+ {
+ Msg( "Syntax: gprofile <outputfilename>\n" );
+ return;
+ }
+
+ // Use the current date and time to create a unique file name.time_t t = time(NULL);
+ time_t t = time(NULL);
+ struct tm tm = *localtime(&t);
+
+ V_sprintf_safe( buffer, "%s/valveprofile_%4d_%02d_%02d_%02d.%02d.%02d.prof", s_pszHomeDir,
+ tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec );
+ // profname already points to buffer.
+ }
+ else
+ {
+ profname = args[1];
+ }
+
+ int result = ProfilerStart( profname );
+ if ( result )
+ {
+ Msg( "Profiling started successfully. Recording to %s. Stop profiling with gprofilerstop.\n", profname );
+ g_gprofiling = true;
+ }
+ else
+ {
+ Msg( "Profiling to %s failed to start - errno = %d.\n", profname, errno );
+ }
+}
+
+CON_COMMAND( gprofilerstop, "Stops the gperftools profiler." )
+{
+ if ( g_gprofiling )
+ {
+ ProfilerStop();
+ Msg( "Stopped profiling.\n" );
+ g_gprofiling = false;
+ }
+}
+#endif
+
+
+void StopGProfiler()
+{
+#ifdef GPROFILER
+ gprofilerstop( CCommand() );
+#endif
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Message pump
+//-----------------------------------------------------------------------------
+bool CEngineAPI::MainLoop()
+{
+ bool bIdle = true;
+ long lIdleCount = 0;
+
+ // Main message pump
+ while ( true )
+ {
+ // Pump messages unless someone wants to quit
+ if ( eng->GetQuitting() != IEngine::QUIT_NOTQUITTING )
+ {
+ // We have to explicitly stop the profiler since otherwise symbol
+ // resolution doesn't work correctly.
+ StopGProfiler();
+ if ( eng->GetQuitting() != IEngine::QUIT_TODESKTOP )
+ return true;
+ return false;
+ }
+
+ // Pump the message loop
+ if ( !InEditMode() )
+ {
+ PumpMessages();
+ }
+ else
+ {
+ PumpMessagesEditMode( bIdle, lIdleCount );
+ }
+
+ // Run engine frame + hammer frame
+ if ( !InEditMode() || m_hEditorHWnd )
+ {
+ VCRSyncToken( "Frame" );
+
+ // Deactivate edit mode shaders
+ ActivateEditModeShaders( false );
+
+ eng->Frame();
+
+ // Reactivate edit mode shaders (in Edit mode only...)
+ ActivateEditModeShaders( true );
+ }
+
+ if ( InEditMode() )
+ {
+ g_pHammer->RunFrame();
+ }
+ }
+
+ return false;
+}
+
+
+//-----------------------------------------------------------------------------
+// Initializes, shuts down the registry
+//-----------------------------------------------------------------------------
+bool CEngineAPI::InitRegistry( const char *pModName )
+{
+ if ( IsPC() )
+ {
+ char szRegSubPath[MAX_PATH];
+ Q_snprintf( szRegSubPath, sizeof(szRegSubPath), "%s\\%s", "Source", pModName );
+ return registry->Init( szRegSubPath );
+ }
+ return true;
+}
+
+void CEngineAPI::ShutdownRegistry( )
+{
+ if ( IsPC() )
+ {
+ registry->Shutdown( );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Initializes, shuts down VR (via sourcevr.dll)
+//-----------------------------------------------------------------------------
+bool CEngineAPI::InitVR()
+{
+ if ( m_bSupportsVR )
+ {
+ g_pSourceVR = (ISourceVirtualReality *)g_AppSystemFactory( SOURCE_VIRTUAL_REALITY_INTERFACE_VERSION, NULL );
+ if ( g_pSourceVR )
+ {
+ // make sure that the sourcevr DLL we loaded is secure. If not, don't
+ // let this client connect to secure servers.
+ if ( !Host_AllowLoadModule( "sourcevr" DLL_EXT_STRING, "EXECUTABLE_PATH", false ) )
+ {
+ Warning( "Preventing connections to secure servers because sourcevr.dll is not signed.\n" );
+ Host_DisallowSecureServers();
+ }
+ }
+ }
+ return true;
+}
+
+
+void CEngineAPI::ShutdownVR()
+{
+}
+
+
+//-----------------------------------------------------------------------------
+// One-time setup, based on the initially selected mod
+// FIXME: This should move into the launcher!
+//-----------------------------------------------------------------------------
+bool CEngineAPI::OnStartup( void *pInstance, const char *pStartupModName )
+{
+ // This fixes a bug on certain machines where the input will
+ // stop coming in for about 1 second when someone hits a key.
+ // (true means to disable priority boost)
+#ifdef WIN32
+ if ( IsPC() )
+ {
+ SetThreadPriorityBoost( GetCurrentThread(), true );
+ }
+#endif
+
+ // FIXME: Turn videomode + game into IAppSystems?
+
+ // Try to create the window
+ COM_TimestampedLog( "game->Init" );
+
+ // This has to happen before CreateGameWindow to set up the instance
+ // for use by the code that creates the window
+ if ( !game->Init( pInstance ) )
+ {
+ goto onStartupError;
+ }
+
+ // Try to create the window
+ COM_TimestampedLog( "videomode->Init" );
+
+ // This needs to be after Shader_Init and registry->Init
+ // This way mods can have different default video settings
+ if ( !videomode->Init( ) )
+ {
+ goto onStartupShutdownGame;
+ }
+
+ // We need to access the registry to get various settings (specifically,
+ // InitMaterialSystemConfig requires it).
+ if ( !InitRegistry( pStartupModName ) )
+ {
+ goto onStartupShutdownVideoMode;
+ }
+
+ materials->ModInit();
+
+ // Setup the material system config record, CreateGameWindow depends on it
+ // (when we're running stand-alone)
+ InitMaterialSystemConfig( InEditMode() );
+
+#if defined( _X360 )
+ XBX_NotifyCreateListener( XNOTIFY_SYSTEM|XNOTIFY_LIVE|XNOTIFY_XMP );
+#endif
+
+ ShutdownRegistry();
+ return true;
+
+ // Various error conditions
+onStartupShutdownVideoMode:
+ videomode->Shutdown();
+
+onStartupShutdownGame:
+ game->Shutdown();
+
+onStartupError:
+ return false;
+}
+
+
+//-----------------------------------------------------------------------------
+// One-time shutdown (shuts down stuff set up in OnStartup)
+// FIXME: This should move into the launcher!
+//-----------------------------------------------------------------------------
+void CEngineAPI::OnShutdown()
+{
+ if ( videomode )
+ {
+ videomode->Shutdown();
+ }
+
+ ShutdownVR();
+
+ // Shut down the game
+ game->Shutdown();
+
+ materials->ModShutdown();
+ TRACESHUTDOWN( COM_ShutdownFileSystem() );
+}
+
+static bool IsValveMod( const char *pModName )
+{
+ // Figure out if we're running a Valve mod or not.
+ return ( Q_stricmp( GetCurrentMod(), "cstrike" ) == 0 ||
+ Q_stricmp( GetCurrentMod(), "dod" ) == 0 ||
+ Q_stricmp( GetCurrentMod(), "hl1mp" ) == 0 ||
+ Q_stricmp( GetCurrentMod(), "tf" ) == 0 ||
+ Q_stricmp( GetCurrentMod(), "tf_beta" ) == 0 ||
+ Q_stricmp( GetCurrentMod(), "hl2mp" ) == 0 );
+}
+
+//-----------------------------------------------------------------------------
+// Initialization, shutdown of a mod.
+//-----------------------------------------------------------------------------
+bool CEngineAPI::ModInit( const char *pModName, const char *pGameDir )
+{
+ // Set up the engineparms_t which contains global information about the mod
+ host_parms.mod = COM_StringCopy( GetModDirFromPath( pModName ) );
+ host_parms.game = COM_StringCopy( pGameDir );
+
+ // By default, restrict server commands in Valve games and don't restrict them in mods.
+ cl.m_bRestrictServerCommands = IsValveMod( host_parms.mod );
+ cl.m_bRestrictClientCommands = cl.m_bRestrictServerCommands;
+
+ // build the registry path we're going to use for this mod
+ InitRegistry( pModName );
+
+ // This sets up the game search path, depends on host_parms
+ TRACEINIT( MapReslistGenerator_Init(), MapReslistGenerator_Shutdown() );
+#if !defined( _X360 )
+ TRACEINIT( DevShotGenerator_Init(), DevShotGenerator_Shutdown() );
+#endif
+
+ // Slam cvars based on mod/config.cfg
+ Host_ReadPreStartupConfiguration();
+
+ bool bWindowed = g_pMaterialSystemConfig->Windowed();
+ if( g_pMaterialSystemConfig->m_nVRModeAdapter != -1 )
+ {
+ // at init time we never want to start up full screen
+ bWindowed = true;
+ }
+
+ // Create the game window now that we have a search path
+ // FIXME: Deal with initial window width + height better
+ if ( !videomode || !videomode->CreateGameWindow( g_pMaterialSystemConfig->m_VideoMode.m_Width, g_pMaterialSystemConfig->m_VideoMode.m_Height, bWindowed ) )
+ {
+ return false;
+ }
+
+ return true;
+}
+
+void CEngineAPI::ModShutdown()
+{
+ COM_StringFree(host_parms.mod);
+ COM_StringFree(host_parms.game);
+
+ // Stop accepting input from the window
+ game->InputDetachFromGameWindow();
+
+#if !defined( _X360 )
+ TRACESHUTDOWN( DevShotGenerator_Shutdown() );
+#endif
+ TRACESHUTDOWN( MapReslistGenerator_Shutdown() );
+
+ ShutdownRegistry();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Handles there being an error setting up the video mode
+// Output : Returns true on if the engine should restart, false if it should quit
+//-----------------------------------------------------------------------------
+InitReturnVal_t CEngineAPI::HandleSetModeError()
+{
+ // show an error, see if the user wants to restart
+ if ( CommandLine()->FindParm( "-safe" ) )
+ {
+ Sys_MessageBox( "Failed to set video mode.\n\nThis game has a minimum requirement of DirectX 7.0 compatible hardware.\n", "Video mode error", false );
+ return INIT_FAILED;
+ }
+
+ if ( CommandLine()->FindParm( "-autoconfig" ) )
+ {
+ if ( Sys_MessageBox( "Failed to set video mode - falling back to safe mode settings.\n\nGame will now restart with the new video settings.", "Video - safe mode fallback", true ))
+ {
+ CommandLine()->AppendParm( "-safe", NULL );
+ return (InitReturnVal_t)INIT_RESTART;
+ }
+ return INIT_FAILED;
+ }
+
+ if ( Sys_MessageBox( "Failed to set video mode - resetting to defaults.\n\nGame will now restart with the new video settings.", "Video mode warning", true ) )
+ {
+ CommandLine()->AppendParm( "-autoconfig", NULL );
+ return (InitReturnVal_t)INIT_RESTART;
+ }
+
+ return INIT_FAILED;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Main loop for non-dedicated servers
+//-----------------------------------------------------------------------------
+int CEngineAPI::RunListenServer()
+{
+ //
+ // NOTE: Systems set up here should depend on the mod
+ // Systems which are mod-independent should be set up in the launcher or Init()
+ //
+
+ // Innocent until proven guilty
+ int nRunResult = RUN_OK;
+
+ // Happens every time we start up and shut down a mod
+ if ( ModInit( m_StartupInfo.m_pInitialMod, m_StartupInfo.m_pInitialGame ) )
+ {
+ CModAppSystemGroup modAppSystemGroup( false, m_StartupInfo.m_pParentAppSystemGroup );
+
+ // Store off the app system factory...
+ g_AppSystemFactory = modAppSystemGroup.GetFactory();
+
+ nRunResult = modAppSystemGroup.Run();
+
+ g_AppSystemFactory = NULL;
+
+ // Shuts down the mod
+ ModShutdown();
+
+ // Disconnects from the editor window
+ videomode->SetGameWindow( NULL );
+ }
+
+ // Closes down things that were set up in OnStartup
+ // FIXME: OnStartup + OnShutdown should be removed + moved into the launcher
+ // or the launcher code should be merged into the engine into the code in OnStartup/OnShutdown
+ OnShutdown();
+
+ return nRunResult;
+}
+
+static void StaticRunListenServer( void *arg )
+{
+ *(int *)arg = s_EngineAPI.RunListenServer();
+}
+
+
+
+// This function is set as the crash handler for unhandled exceptions and as the minidump
+// handler for to be used by all of tier0's crash recording. This function
+// adds a game-specific minidump comment and ensures that the SteamAPI function is
+// used to save the minidump so that crashes are uploaded. SteamAPI has previously
+// been configured to use breakpad by calling SteamAPI_UseBreakpadCrashHandler.
+extern "C" void __cdecl WriteSteamMiniDumpWithComment( unsigned int uStructuredExceptionCode,
+ struct _EXCEPTION_POINTERS * pExceptionInfo,
+ const char *pszFilenameSuffix )
+{
+ // TODO: dynamically set the minidump comment from contextual info about the crash (i.e current VPROF node)?
+#if !defined( NO_STEAM )
+
+ if ( g_bUpdateMinidumpComment )
+ {
+ BuildMinidumpComment( NULL, true );
+ }
+
+ SteamAPI_WriteMiniDump( uStructuredExceptionCode, pExceptionInfo, build_number() );
+ // Clear DSound Buffers so the sound doesn't loop while the game shuts down
+ try
+ {
+ S_ClearBuffer();
+ }
+ catch ( ... )
+ {
+ }
+#endif
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Main
+//-----------------------------------------------------------------------------
+int CEngineAPI::Run()
+{
+ if ( CommandLine()->FindParm( "-insecure" ) || CommandLine()->FindParm( "-textmode" ) )
+ {
+ Host_DisallowSecureServers();
+ }
+
+#ifdef _X360
+ return RunListenServer(); // don't handle exceptions on 360 (because if we do then minidumps won't work at all)
+#elif defined ( _WIN32 )
+ // Ensure that we crash when we do something naughty in a callback
+ // such as a window proc. Otherwise on a 64-bit OS the crashes will be
+ // silently swallowed.
+ EnableCrashingOnCrashes();
+
+ // Set the default minidump handling function. This is necessary so that Steam
+ // will upload crashes, with comments.
+ SetMiniDumpFunction( WriteSteamMiniDumpWithComment );
+
+ // Catch unhandled crashes. A normal __try/__except block will not work across
+ // the kernel callback boundary, but this does. To be clear, __try/__except
+ // and try/catch will usually not catch exceptions in a WindowProc or other
+ // callback that is called from kernel mode because 64-bit Windows cannot handle
+ // throwing exceptions across that boundary. See this article for details:
+ // http://blog.paulbetts.org/index.php/2010/07/20/the-case-of-the-disappearing-onload-exception-user-mode-callback-exceptions-in-x64/
+ // Note that the unhandled exception function is not called when running
+ // under a debugger, but that's fine because in that case we don't care about
+ // recording minidumps.
+ // The try/catch block still makes sense because it is a more reliable way
+ // of catching exceptions that aren't in callbacks.
+ // The unhandled exception filter will also catch crashes in threads that
+ // don't have a try/catch or __try/__except block.
+ bool noMinidumps = CommandLine()->FindParm( "-nominidumps");
+ if ( !noMinidumps )
+ MinidumpSetUnhandledExceptionFunction( WriteSteamMiniDumpWithComment );
+
+ if ( !Plat_IsInDebugSession() && !noMinidumps )
+ {
+ int nRetVal = RUN_OK;
+ CatchAndWriteMiniDumpForVoidPtrFn( StaticRunListenServer, &nRetVal, true );
+ return nRetVal;
+ }
+ else
+ {
+ return RunListenServer();
+ }
+#else
+ return RunListenServer();
+#endif
+}
+#endif // SWDS
+
+bool g_bUsingLegacyAppSystems = false;
+
+bool CModAppSystemGroup::AddLegacySystems()
+{
+ g_bUsingLegacyAppSystems = true;
+
+ AppSystemInfo_t appSystems[] =
+ {
+ { "soundemittersystem", SOUNDEMITTERSYSTEM_INTERFACE_VERSION },
+ { "", "" } // Required to terminate the list
+ };
+
+ if ( !AddSystems( appSystems ) )
+ return false;
+
+#if !defined( DEDICATED )
+// if ( CommandLine()->FindParm( "-tools" ) )
+ {
+ AppModule_t toolFrameworkModule = LoadModule( "engine" DLL_EXT_STRING );
+
+ if ( !AddSystem( toolFrameworkModule, VTOOLFRAMEWORK_INTERFACE_VERSION ) )
+ return false;
+ }
+#endif
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Instantiate all main libraries
+//-----------------------------------------------------------------------------
+bool CModAppSystemGroup::Create()
+{
+#ifndef SWDS
+ if ( !IsServerOnly() )
+{
+ if ( !ClientDLL_Load() )
+ return false;
+}
+#endif
+
+ if ( !ServerDLL_Load( IsServerOnly() ) )
+ return false;
+
+ IClientDLLSharedAppSystems *clientSharedSystems = 0;
+
+#ifndef SWDS
+ if ( !IsServerOnly() )
+ {
+ clientSharedSystems = ( IClientDLLSharedAppSystems * )g_ClientFactory( CLIENT_DLL_SHARED_APPSYSTEMS, NULL );
+ if ( !clientSharedSystems )
+ return AddLegacySystems();
+ }
+#endif
+
+ IServerDLLSharedAppSystems *serverSharedSystems = ( IServerDLLSharedAppSystems * )g_ServerFactory( SERVER_DLL_SHARED_APPSYSTEMS, NULL );
+ if ( !serverSharedSystems )
+ {
+ Assert( !"Expected both game and client .dlls to have or not have shared app systems interfaces!!!" );
+ return AddLegacySystems();
+ }
+
+ // Load game and client .dlls and build list then
+ CUtlVector< AppSystemInfo_t > systems;
+
+ int i;
+ int serverCount = serverSharedSystems->Count();
+ for ( i = 0 ; i < serverCount; ++i )
+ {
+ const char *dllName = serverSharedSystems->GetDllName( i );
+ const char *interfaceName = serverSharedSystems->GetInterfaceName( i );
+
+ AppSystemInfo_t info;
+ info.m_pModuleName = dllName;
+ info.m_pInterfaceName = interfaceName;
+
+ systems.AddToTail( info );
+ }
+
+ if ( !IsServerOnly() )
+ {
+ int clientCount = clientSharedSystems->Count();
+ for ( i = 0 ; i < clientCount; ++i )
+ {
+ const char *dllName = clientSharedSystems->GetDllName( i );
+ const char *interfaceName = clientSharedSystems->GetInterfaceName( i );
+
+ if ( ModuleAlreadyInList( systems, dllName, interfaceName ) )
+ continue;
+
+ AppSystemInfo_t info;
+ info.m_pModuleName = dllName;
+ info.m_pInterfaceName = interfaceName;
+
+ systems.AddToTail( info );
+ }
+ }
+
+ AppSystemInfo_t info;
+ info.m_pModuleName = "";
+ info.m_pInterfaceName = "";
+ systems.AddToTail( info );
+
+ if ( !AddSystems( systems.Base() ) )
+ return false;
+
+#if !defined( DEDICATED )
+// if ( CommandLine()->FindParm( "-tools" ) )
+ {
+ AppModule_t toolFrameworkModule = LoadModule( "engine" DLL_EXT_STRING );
+
+ if ( !AddSystem( toolFrameworkModule, VTOOLFRAMEWORK_INTERFACE_VERSION ) )
+ return false;
+ }
+#endif
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Fixme, we might need to verify if the interface names differ for the client versus the server
+// Input : list -
+// *moduleName -
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CModAppSystemGroup::ModuleAlreadyInList( CUtlVector< AppSystemInfo_t >& list, const char *moduleName, const char *interfaceName )
+{
+ for ( int i = 0; i < list.Count(); ++i )
+ {
+ if ( !Q_stricmp( list[ i ].m_pModuleName, moduleName ) )
+ {
+ if ( Q_stricmp( list[ i ].m_pInterfaceName, interfaceName ) )
+ {
+ Error( "Game and client .dlls requesting different versions '%s' vs. '%s' from '%s'\n",
+ list[ i ].m_pInterfaceName, interfaceName, moduleName );
+ }
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool CModAppSystemGroup::PreInit()
+{
+ return true;
+}
+
+void SV_ShutdownGameDLL();
+int CModAppSystemGroup::Main()
+{
+ int nRunResult = RUN_OK;
+
+ if ( IsServerOnly() )
+ {
+ // Start up the game engine
+ if ( eng->Load( true, host_parms.basedir ) )
+ {
+ // If we're using STEAM, pass the map cycle list as resource hints...
+ // Dedicated server drives frame loop manually
+ dedicated->RunServer();
+
+ SV_ShutdownGameDLL();
+ }
+ }
+ else
+ {
+ eng->SetQuitting( IEngine::QUIT_NOTQUITTING );
+
+ COM_TimestampedLog( "eng->Load" );
+
+ // Start up the game engine
+ static const char engineLoadMessage[] = "Calling CEngine::Load";
+ int64 nStartTime = ETWBegin( engineLoadMessage );
+ if ( eng->Load( false, host_parms.basedir ) )
+ {
+#if !defined(SWDS)
+ ETWEnd( engineLoadMessage, nStartTime );
+ toolframework->ServerInit( g_ServerFactory );
+
+ if ( s_EngineAPI.MainLoop() )
+ {
+ nRunResult = RUN_RESTART;
+ }
+
+ // unload systems
+ eng->Unload();
+
+ toolframework->ServerShutdown();
+#endif
+ SV_ShutdownGameDLL();
+ }
+ }
+
+ return nRunResult;
+}
+
+void CModAppSystemGroup::PostShutdown()
+{
+}
+
+void CModAppSystemGroup::Destroy()
+{
+ // unload game and client .dlls
+ ServerDLL_Unload();
+#ifndef SWDS
+ if ( !IsServerOnly() )
+ {
+ ClientDLL_Unload();
+ }
+#endif
+}
+
+//-----------------------------------------------------------------------------
+//
+// Purpose: Expose engine interface to launcher for dedicated servers
+//
+//-----------------------------------------------------------------------------
+class CDedicatedServerAPI : public CTier3AppSystem< IDedicatedServerAPI >
+{
+ typedef CTier3AppSystem< IDedicatedServerAPI > BaseClass;
+
+public:
+ CDedicatedServerAPI() :
+ m_pDedicatedServer( 0 )
+ {
+ }
+ virtual bool Connect( CreateInterfaceFn factory );
+ virtual void Disconnect();
+ virtual void *QueryInterface( const char *pInterfaceName );
+
+ virtual bool ModInit( ModInfo_t &info );
+ virtual void ModShutdown( void );
+
+ virtual bool RunFrame( void );
+
+ virtual void AddConsoleText( char *text );
+ virtual void UpdateStatus(float *fps, int *nActive, int *nMaxPlayers, char *pszMap, int maxlen );
+ virtual void UpdateHostname(char *pszHostname, int maxlen);
+
+ CModAppSystemGroup *m_pDedicatedServer;
+};
+
+//-----------------------------------------------------------------------------
+// Singleton
+//-----------------------------------------------------------------------------
+EXPOSE_SINGLE_INTERFACE( CDedicatedServerAPI, IDedicatedServerAPI, VENGINE_HLDS_API_VERSION );
+
+#define LONG_TICK_TIME 0.12f // about 8/66ths of a second
+#define MIN_TIME_BETWEEN_DUMPED_TICKS 5.0f;
+#define MAX_DUMPS_PER_LONG_TICK 10
+void Sys_Sleep ( int msec );
+
+bool g_bLongTickWatcherThreadEnabled = false;
+bool g_bQuitLongTickWatcherThread = false;
+int g_bTotalDumps = 0;
+
+DWORD __stdcall LongTickWatcherThread( void *voidPtr )
+{
+ int nLastTick = 0;
+ double flWarnTickTime = 0.0f;
+ double flNextPossibleDumpTime = Plat_FloatTime() + MIN_TIME_BETWEEN_DUMPED_TICKS;
+ int nNumDumpsThisTick = 0;
+
+ while ( eng->GetQuitting() == IEngine::QUIT_NOTQUITTING && !g_bQuitLongTickWatcherThread )
+ {
+ if ( sv.m_State == ss_active && sv.m_bSimulatingTicks )
+ {
+ int curTick = sv.m_nTickCount;
+ double curTime = Plat_FloatTime();
+ if ( nLastTick > 0 && nLastTick == curTick )
+ {
+ if ( curTime > flNextPossibleDumpTime && curTime > flWarnTickTime && nNumDumpsThisTick < MAX_DUMPS_PER_LONG_TICK )
+ {
+ nNumDumpsThisTick++;
+ g_bTotalDumps++;
+ Warning( "Long tick after tick %i. Writing minidump #%i (%i total).\n", nLastTick, nNumDumpsThisTick, g_bTotalDumps );
+
+ if ( nNumDumpsThisTick == MAX_DUMPS_PER_LONG_TICK )
+ {
+ Msg( "Not writing any more minidumps for this tick.\n" );
+ }
+
+ // If you're debugging a minidump and you ended up here, you probably want to switch to the main thread.
+ WriteMiniDump( "longtick" );
+ }
+ }
+
+ if ( nLastTick != curTick )
+ {
+ if ( nNumDumpsThisTick )
+ {
+ Msg( "Long tick lasted about %.1f seconds.\n", curTime - (flWarnTickTime - LONG_TICK_TIME) );
+ nNumDumpsThisTick = 0;
+ flNextPossibleDumpTime = curTime + MIN_TIME_BETWEEN_DUMPED_TICKS;
+ }
+
+ nLastTick = curTick;
+ flWarnTickTime = curTime + LONG_TICK_TIME;
+ }
+ }
+ else
+ {
+ nLastTick = 0;
+ }
+
+ if ( nNumDumpsThisTick )
+ {
+ // We'll write the next minidump 0.06 seconds from now.
+ Sys_Sleep( 60 );
+ }
+ else
+ {
+ // Check tick progress every 1/100th of a second.
+ Sys_Sleep( 10 );
+ }
+ }
+
+ g_bLongTickWatcherThreadEnabled = false;
+ g_bQuitLongTickWatcherThread = false;
+
+ return 0;
+}
+
+bool EnableLongTickWatcher()
+{
+ bool bRet = false;
+ if ( !g_bLongTickWatcherThreadEnabled )
+ {
+ g_bQuitLongTickWatcherThread = false;
+ g_bLongTickWatcherThreadEnabled = true;
+
+ DWORD nThreadID;
+ VCRHook_CreateThread(NULL, 0,
+#ifdef POSIX
+ (void*)
+#endif
+ LongTickWatcherThread, NULL, 0, (unsigned long int *)&nThreadID );
+
+ bRet = true;
+ }
+ else if ( g_bQuitLongTickWatcherThread )
+ {
+ Msg( "Cannot create a new long tick watcher while waiting for an old one to terminate.\n" );
+ }
+ else
+ {
+ Msg( "The long tick watcher thread is already running.\n" );
+ }
+
+ return bRet;
+}
+
+//-----------------------------------------------------------------------------
+// Dedicated server entrypoint
+//-----------------------------------------------------------------------------
+bool CDedicatedServerAPI::Connect( CreateInterfaceFn factory )
+{
+ if ( CommandLine()->FindParm( "-sv_benchmark" ) != 0 )
+ {
+ Plat_SetBenchmarkMode( true );
+ }
+
+ if ( CommandLine()->FindParm( "-dumplongticks" ) )
+ {
+ Msg( "-dumplongticks found on command line. Activating long tick watcher thread.\n" );
+ EnableLongTickWatcher();
+ }
+
+ // Store off the app system factory...
+ g_AppSystemFactory = factory;
+
+ if ( !BaseClass::Connect( factory ) )
+ return false;
+
+ dedicated = ( IDedicatedExports * )factory( VENGINE_DEDICATEDEXPORTS_API_VERSION, NULL );
+ if ( !dedicated )
+ return false;
+
+ g_pFileSystem = g_pFullFileSystem;
+ g_pFileSystem->SetWarningFunc( Warning );
+
+ if ( !Shader_Connect( false ) )
+ return false;
+
+ if ( !g_pStudioRender )
+ {
+ Sys_Error( "Unable to init studio render system version %s\n", STUDIO_RENDER_INTERFACE_VERSION );
+ return false;
+ }
+
+ g_pPhysics = (IPhysics*)factory( VPHYSICS_INTERFACE_VERSION, NULL );
+
+ if ( !g_pDataCache || !g_pPhysics || !g_pMDLCache )
+ {
+ Warning( "Engine wasn't able to acquire required interfaces!\n" );
+ return false;
+ }
+
+ ConnectMDLCacheNotify();
+ return true;
+}
+
+void CDedicatedServerAPI::Disconnect()
+{
+ DisconnectMDLCacheNotify();
+
+ g_pPhysics = NULL;
+
+ Shader_Disconnect();
+
+ g_pFileSystem = NULL;
+
+ ConVar_Unregister();
+
+ dedicated = NULL;
+
+ BaseClass::Disconnect();
+
+ g_AppSystemFactory = NULL;
+}
+
+//-----------------------------------------------------------------------------
+// Query interface
+//-----------------------------------------------------------------------------
+void *CDedicatedServerAPI::QueryInterface( const char *pInterfaceName )
+{
+ // Loading the engine DLL mounts *all* engine interfaces
+ CreateInterfaceFn factory = Sys_GetFactoryThis(); // This silly construction is necessary
+ return factory( pInterfaceName, NULL ); // to prevent the LTCG compiler from crashing.
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : type - 0 == normal, 1 == dedicated server
+// *instance -
+// *basedir -
+// *cmdline -
+// launcherFactory -
+//-----------------------------------------------------------------------------
+bool CDedicatedServerAPI::ModInit( ModInfo_t &info )
+{
+ // Setup and write out steam_appid.txt before we launch
+ bool bDedicated = true;
+ eSteamInfoInit steamInfo = Sys_TryInitSteamInfo( this, g_SteamInfIDVersionInfo, info.m_pInitialMod, info.m_pBaseDirectory, bDedicated );
+
+ eng->SetQuitting( IEngine::QUIT_NOTQUITTING );
+
+ // Set up the engineparms_t which contains global information about the mod
+ host_parms.basedir = const_cast<char*>(info.m_pBaseDirectory);
+ host_parms.mod = const_cast<char*>(GetModDirFromPath(info.m_pInitialMod));
+ host_parms.game = const_cast<char*>(info.m_pInitialGame);
+
+ g_bTextMode = info.m_bTextMode;
+
+ TRACEINIT( COM_InitFilesystem( info.m_pInitialMod ), COM_ShutdownFileSystem() );
+
+ if ( steamInfo != eSteamInfo_Initialized )
+ {
+ // Try again with filesystem available. This is commonly needed for SDK mods which need the filesystem to find
+ // their steam.inf, due to mounting SDK search paths.
+ steamInfo = Sys_TryInitSteamInfo( this, g_SteamInfIDVersionInfo, info.m_pInitialMod, info.m_pBaseDirectory, bDedicated );
+ Assert( steamInfo == eSteamInfo_Initialized );
+ if ( steamInfo != eSteamInfo_Initialized )
+ {
+ Warning( "Failed to find steam.inf or equivalent steam info. May not have proper information to connect to Steam.\n" );
+ }
+ }
+
+ // set this up as early as possible, if the server isn't going to run pure, stop CRCing bits as we load them
+ // this happens even before the ConCommand's are processed, but we need to be sure to either CRC every file
+ // that is loaded, or not bother doing any
+ // Note that this mirrors g_sv_pure_mode from sv_main.cpp
+ int pure_mode = 1; // default to on, +sv_pure 0 or -sv_pure 0 will turn it off
+ if ( CommandLine()->CheckParm("+sv_pure") )
+ pure_mode = CommandLine()->ParmValue( "+sv_pure", 1 );
+ else if ( CommandLine()->CheckParm("-sv_pure") )
+ pure_mode = CommandLine()->ParmValue( "-sv_pure", 1 );
+ if ( pure_mode )
+ g_pFullFileSystem->EnableWhitelistFileTracking( true, true, CommandLine()->FindParm( "-sv_pure_verify_hashes" ) ? true : false );
+ else
+ g_pFullFileSystem->EnableWhitelistFileTracking( false, false, false );
+
+ materials->ModInit();
+
+ // Setup the material system config record, CreateGameWindow depends on it
+ // (when we're running stand-alone)
+#ifndef SWDS
+ InitMaterialSystemConfig( true ); // !!should this be called standalone or not?
+#endif
+
+ // Initialize general game stuff and create the main window
+ if ( game->Init( NULL ) )
+ {
+ m_pDedicatedServer = new CModAppSystemGroup( true, info.m_pParentAppSystemGroup );
+
+ // Store off the app system factory...
+ g_AppSystemFactory = m_pDedicatedServer->GetFactory();
+
+ m_pDedicatedServer->Run();
+ return true;
+ }
+
+ return false;
+}
+
+void CDedicatedServerAPI::ModShutdown( void )
+{
+ if ( m_pDedicatedServer )
+ {
+ delete m_pDedicatedServer;
+ m_pDedicatedServer = NULL;
+ }
+
+ g_AppSystemFactory = NULL;
+
+ // Unload GL, Sound, etc.
+ eng->Unload();
+
+ // Shut down memory, etc.
+ game->Shutdown();
+
+ materials->ModShutdown();
+ TRACESHUTDOWN( COM_ShutdownFileSystem() );
+}
+
+bool CDedicatedServerAPI::RunFrame( void )
+{
+ // Bail if someone wants to quit.
+ if ( eng->GetQuitting() != IEngine::QUIT_NOTQUITTING )
+ {
+ return false;
+ }
+
+ // Run engine frame
+ eng->Frame();
+ return true;
+}
+
+void CDedicatedServerAPI::AddConsoleText( char *text )
+{
+ Cbuf_AddText( text );
+}
+
+void CDedicatedServerAPI::UpdateStatus(float *fps, int *nActive, int *nMaxPlayers, char *pszMap, int maxlen )
+{
+ Host_GetHostInfo( fps, nActive, nMaxPlayers, pszMap, maxlen );
+}
+
+void CDedicatedServerAPI::UpdateHostname(char *pszHostname, int maxlen)
+{
+ if ( pszHostname && ( maxlen > 0 ) )
+ {
+ Q_strncpy( pszHostname, sv.GetName(), maxlen );
+ }
+}
+
+#ifndef SWDS
+
+class CGameUIFuncs : public IGameUIFuncs
+{
+public:
+ bool IsKeyDown( const char *keyname, bool& isdown )
+ {
+ isdown = false;
+ if ( !g_ClientDLL )
+ return false;
+
+ return g_ClientDLL->IN_IsKeyDown( keyname, isdown );
+ }
+
+ const char *GetBindingForButtonCode( ButtonCode_t code )
+ {
+ return ::Key_BindingForKey( code );
+ }
+
+ virtual ButtonCode_t GetButtonCodeForBind( const char *bind )
+ {
+ const char *pKeyName = Key_NameForBinding( bind );
+ if ( !pKeyName )
+ return KEY_NONE;
+ return g_pInputSystem->StringToButtonCode( pKeyName ) ;
+ }
+
+ void GetVideoModes( struct vmode_s **ppListStart, int *pCount )
+ {
+ if ( videomode )
+ {
+ *pCount = videomode->GetModeCount();
+ *ppListStart = videomode->GetMode( 0 );
+ }
+ else
+ {
+ *pCount = 0;
+ *ppListStart = NULL;
+ }
+ }
+
+ void GetDesktopResolution( int &width, int &height )
+ {
+ int refreshrate;
+ game->GetDesktopInfo( width, height, refreshrate );
+ }
+
+ virtual void SetFriendsID( uint friendsID, const char *friendsName )
+ {
+ cl.SetFriendsID( friendsID, friendsName );
+ }
+
+ bool IsConnectedToVACSecureServer()
+ {
+ if ( cl.IsConnected() )
+ return Steam3Client().BGSSecure();
+ return false;
+ }
+};
+
+EXPOSE_SINGLE_INTERFACE( CGameUIFuncs, IGameUIFuncs, VENGINE_GAMEUIFUNCS_VERSION );
+
+#endif
+
+CON_COMMAND( dumplongticks, "Enables generating minidumps on long ticks." )
+{
+ int enable = atoi( args[1] );
+ if ( args.ArgC() == 1 || enable )
+ {
+ if ( EnableLongTickWatcher() )
+ {
+ Msg( "Long tick watcher thread created. Use \"dumplongticks 0\" to disable.\n" );
+ }
+ }
+ else
+ {
+ // disable watcher thread if enabled
+ if ( g_bLongTickWatcherThreadEnabled && !g_bQuitLongTickWatcherThread )
+ {
+ Msg( "Disabling the long tick watcher.\n" );
+ g_bQuitLongTickWatcherThread = true;
+ }
+ else
+ {
+ Msg( "The long tick watcher is already disabled.\n" );
+ }
+ }
+}
+