1085 lines
36 KiB
C++
1085 lines
36 KiB
C++
//====== Copyright ©, Valve Corporation, All rights reserved. =================
|
|
//
|
|
// Purpose: Defines the GC interface exposed to the host
|
|
//
|
|
//=============================================================================
|
|
|
|
|
|
#include "stdafx.h"
|
|
#include "winlite.h"
|
|
|
|
#include "tier0/minidump.h"
|
|
#include "tier1/interface.h"
|
|
#include "appframework/iappsystemgroup.h"
|
|
#include "filesystem.h"
|
|
#include "vstdlib/cvar.h"
|
|
#include "signal.h"
|
|
|
|
#include "gcsdk/steamextra/rtime.h"
|
|
#include "gcsdk/directory.h"
|
|
#include "gcsdk/gcinterface.h"
|
|
|
|
|
|
#define WINDOWS_LEAN_AND_MEAN
|
|
#if !defined( _WIN32_WINNT )
|
|
#define _WIN32_WINNT 0x0403
|
|
#endif
|
|
#include <windows.h>
|
|
|
|
|
|
namespace GCSDK
|
|
{
|
|
static GCConVar cv_assert_minidump_window( "assert_minidump_window", "28800", "Size of the minidump window in seconds. Each unique assert will dump at most assert_max_minidumps_in_window times in this many seconds" );
|
|
static GCConVar cv_assert_max_minidumps_in_window( "assert_max_minidumps_in_window", "5", "The amount of times each unique assert will write a dump in assert_minidump_window seconds" );
|
|
static GCConVar enable_assert_minidumps( "enable_assert_minidumps", "1", "An emergency shutoff to prevent the recording or tracking of asserts" );
|
|
static GCConVar filter_blank_lines( "filter_blank_lines", "1", "Prevents blank lines from being written or logged" );
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Creates a global pointer to the interface and exposes it to the host
|
|
//-----------------------------------------------------------------------------
|
|
CGCInterface g_GCInterface;
|
|
EXPOSE_SINGLE_INTERFACE_GLOBALVAR( CGCInterface, IGameCoordinator, GAMECOORDINATOR_INTERFACE_VERSION, g_GCInterface );
|
|
|
|
int32 CGCInterface::CDisableAssertRateLimit::s_nDisabledCount = 0;
|
|
|
|
// Force the linker to include this even though we're in a static lib
|
|
void ForceIncludeGCInterface()
|
|
{
|
|
#pragma comment( linker, "/INCLUDE:" __FUNCDNAME__ )
|
|
void *pUnused = &__g_CreateCGCInterfaceIGameCoordinator_reg;
|
|
pUnused = NULL;
|
|
|
|
#ifdef DEBUG
|
|
// Adds a note for the deploy tool to not let it prop with a debug GCSDK
|
|
printf( "is a debug binary" );
|
|
#endif
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Overrides the spew func used by Msg and DMsg to print to the console
|
|
//-----------------------------------------------------------------------------
|
|
class CConsoleLoggingListener : public ILoggingListener
|
|
{
|
|
public:
|
|
virtual void Log( const LoggingContext_t *pContext, const tchar *pMessage )
|
|
{
|
|
const char *pszFmt = ( sizeof( tchar ) == sizeof( char ) ) ? "%hs" : "%ls";
|
|
switch ( pContext->m_Severity )
|
|
{
|
|
default:
|
|
case LS_MESSAGE:
|
|
EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, pszFmt, pMessage );
|
|
break;
|
|
|
|
case LS_WARNING:
|
|
EmitWarning( SPEW_CONSOLE, SPEW_ALWAYS, pszFmt, pMessage );
|
|
break;
|
|
|
|
case LS_ERROR:
|
|
case LS_HIGHEST_SEVERITY:
|
|
EmitError( SPEW_CONSOLE, pszFmt, pMessage );
|
|
break;
|
|
|
|
case LS_ASSERT:
|
|
//if this assert is in a job, display the name of the job as well
|
|
if ( ThreadInMainThread() && ( g_pJobCur != NULL ) )
|
|
{
|
|
pszFmt = ( sizeof( tchar ) == sizeof( char ) ) ? "[Job %s] %hs" : "[Job %s] %ls";
|
|
EmitAssertError( SPEW_CONSOLE, pszFmt, g_pJobCur->GetName(), pMessage );
|
|
}
|
|
else
|
|
{
|
|
EmitAssertError( SPEW_CONSOLE, pszFmt, pMessage );
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
};
|
|
static CNonFatalLoggingResponsePolicy s_NonFatalLoggingResponsePolicy;
|
|
static CConsoleLoggingListener s_ConsoleLoggingListener;
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Prints an assert to the console
|
|
//-----------------------------------------------------------------------------
|
|
class CGCAssertionFailureListener : public IAssertionFailureListener
|
|
{
|
|
public:
|
|
CGCAssertionFailureListener( void )
|
|
: IAssertionFailureListener( false )
|
|
{
|
|
}
|
|
|
|
virtual void *AssertionFailure( const char *pFormattedMsg, const tchar *pchFile, int nLine, const tchar *pchFunction, const tchar *pchRawExpression, int nInstanceReportCount, AssertionType_t nType, bool bFatal ) OVERRIDE
|
|
{
|
|
if ( Plat_IsInDebugSession() )
|
|
return NULL;
|
|
|
|
bool bShouldWriteMinidump = false;
|
|
GGCInterface()->RecordAssert( pchFile, nLine, pFormattedMsg, &bShouldWriteMinidump );
|
|
|
|
return bShouldWriteMinidump ? this : NULL;
|
|
}
|
|
|
|
virtual void MiniDumpHandler( const MiniDumpHandlerData_t &HandlerData, const char *pFormattedMsg, const tchar *pchFile, int nLine, const tchar *pchFunction, const tchar *pchRawExpression, int nInstanceReportCount, AssertionType_t nType, bool bFatal ) OVERRIDE
|
|
{
|
|
//re-route to default minidump handler (treat it the same as a crash)
|
|
CFmtStr minidumpNameToken( "assert_%s_%d", V_GetFileName( pchFile ), nLine );
|
|
MiniDumpOptionalData_t optionalData( minidumpNameToken.Access() );
|
|
|
|
MiniDumpHandlerData_t modifiableHandlerData( HandlerData );
|
|
modifiableHandlerData.SetOptionalData( optionalData );
|
|
|
|
//write to disk
|
|
Tier0GenericMiniDumpHandlerEx( modifiableHandlerData, NULL, MINIDUMP_ADDITIONAL_FLAG_PRINT_MESSAGE );
|
|
}
|
|
};
|
|
static CGCAssertionFailureListener sg_GCAssertionFailureHandler;
|
|
|
|
|
|
static void ProtobufLogHandler( ::google::protobuf::LogLevel level, const char* filename, int line, const std::string& message )
|
|
{
|
|
EG_MSG( g_EGMessages, "Protobuf %s(%d): %s\n", filename, line, message.c_str() );
|
|
AssertFatalMsg( level != google::protobuf::LOGLEVEL_FATAL, "Fatal protobuf assert %s(%d): %s", filename, line, message.c_str() );
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Initializes the underlying libraries
|
|
//-----------------------------------------------------------------------------
|
|
static class CGCAppSystemGroup : public CAppSystemGroup
|
|
{
|
|
public:
|
|
CGCAppSystemGroup() {}
|
|
void SetPath ( const char *pchBinaryPath ) { m_sBinaryPath = pchBinaryPath; }
|
|
|
|
// Implementation of IAppSystemGroup
|
|
virtual bool Create() OVERRIDE
|
|
{
|
|
AppModule_t cvarModule = LoadModule( VStdLib_GetICVarFactory() );
|
|
AddSystem( cvarModule, CVAR_INTERFACE_VERSION );
|
|
|
|
AppSystemInfo_t appSystems[] =
|
|
{
|
|
{ "filesystem_stdio.dll", FILESYSTEM_INTERFACE_VERSION },
|
|
{ "", "" } // Required to terminate the list
|
|
};
|
|
|
|
CUtlVector<CUtlString> vecFullPaths;
|
|
AppSystemInfo_t *pSystem = appSystems;
|
|
while( pSystem->m_pModuleName[0] != '\0' )
|
|
{
|
|
CUtlString &strNewPath = vecFullPaths[ vecFullPaths.AddToTail() ];
|
|
strNewPath.Format( "%s%s%s", m_sBinaryPath.Get(), CORRECT_PATH_SEPARATOR_S, pSystem->m_pModuleName );
|
|
pSystem->m_pModuleName = strNewPath.Get();
|
|
|
|
pSystem++;
|
|
}
|
|
|
|
return AddSystems( appSystems );
|
|
}
|
|
|
|
virtual bool PreInit() OVERRIDE
|
|
{
|
|
CreateInterfaceFn factory = GetFactory();
|
|
ConnectTier1Libraries( &factory, 1 );
|
|
ConnectTier2Libraries( &factory, 1 );
|
|
|
|
if( !g_pFullFileSystem )
|
|
return false;
|
|
|
|
if ( !g_pCVar )
|
|
return false;
|
|
|
|
ConVar_Register();
|
|
|
|
return true;
|
|
}
|
|
|
|
virtual void PostShutdown() OVERRIDE
|
|
{
|
|
ConVar_Unregister();
|
|
DisconnectTier2Libraries();
|
|
DisconnectTier1Libraries();
|
|
}
|
|
|
|
virtual void Destroy() OVERRIDE {}
|
|
|
|
// this should never be called
|
|
virtual int Main( ) OVERRIDE { return -1; }
|
|
|
|
private:
|
|
CUtlString m_sBinaryPath;
|
|
} g_gcAppSystemGroup;
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Gets the global instance
|
|
//-----------------------------------------------------------------------------
|
|
CGCInterface *GGCInterface()
|
|
{
|
|
return &g_GCInterface;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Constructor
|
|
//-----------------------------------------------------------------------------
|
|
CGCInterface::CGCInterface()
|
|
: m_pGCHost( NULL )
|
|
, m_pGC( NULL )
|
|
, m_pGCDirProcess( NULL )
|
|
, m_nAppID( k_uAppIdInvalid )
|
|
, m_eUniverse( k_EUniverseInvalid )
|
|
, m_bDevMode( false )
|
|
, m_ullGID( 0 )
|
|
, m_bLogCaptureEnabled( false )
|
|
, m_nVersion( 0 )
|
|
, m_hParentProcess( NULL )
|
|
{
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Destructor
|
|
//-----------------------------------------------------------------------------
|
|
CGCInterface::~CGCInterface()
|
|
{
|
|
m_BlockEmitStrings.PurgeAndDeleteElements();
|
|
ClearAssertInfo();
|
|
delete m_pGC;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Gets the actual GC referred to by the interface
|
|
//-----------------------------------------------------------------------------
|
|
IGameCoordinator *CGCInterface::GetGC()
|
|
{
|
|
return m_pGC;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Returns true if the GC is running in a dev environment
|
|
//-----------------------------------------------------------------------------
|
|
bool CGCInterface::BIsDevMode() const
|
|
{
|
|
return m_bDevMode;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Gets the GC's appID
|
|
//-----------------------------------------------------------------------------
|
|
AppId_t CGCInterface::GetAppID() const
|
|
{
|
|
return m_nAppID;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Gets the directory gc.dll is running in
|
|
//-----------------------------------------------------------------------------
|
|
const char *CGCInterface::GetGCDLLPath() const
|
|
{
|
|
return m_sGCDLLPath;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Reads the config KV from the disk
|
|
//-----------------------------------------------------------------------------
|
|
bool CGCInterface::BReadConfigDirectory( KeyValuesAD& configValues )
|
|
{
|
|
// Read the config file
|
|
const char *pchBaseConfigName = NULL;
|
|
|
|
switch( GetUniverse() )
|
|
{
|
|
case k_EUniversePublic: pchBaseConfigName = "gcconfig_public.vdf"; break;
|
|
case k_EUniverseBeta: pchBaseConfigName = "gcconfig_beta.vdf"; break;
|
|
case k_EUniverseInternal: pchBaseConfigName = "gcconfig_internal.vdf"; break;
|
|
case k_EUniverseDev: pchBaseConfigName = "gcconfig_dev.vdf"; break;
|
|
}
|
|
|
|
if( !pchBaseConfigName || !configValues->LoadFromFile( g_pFullFileSystem, pchBaseConfigName, "CONFIG" ) )
|
|
{
|
|
GCSDK::EmitError( SPEW_GC, "Unable to read config file: %s. Aborting.\n", pchBaseConfigName ? pchBaseConfigName : "unknown universe specified" );
|
|
return false;
|
|
}
|
|
|
|
//load up our directory
|
|
if ( !GDirectory()->BInit( configValues->FindKey( "directory" ) ) )
|
|
{
|
|
GCSDK::EmitError( SPEW_GC, "Unable to find 'directory' key within config file %s.\n", pchBaseConfigName );
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool CGCInterface::BReadConvars( KeyValuesAD& configValues )
|
|
{
|
|
//load the standard global convars
|
|
InitConVars( configValues->FindKey( "convars" ) );
|
|
|
|
//we can't load more if we don't have a directory as we don't know our GC type
|
|
if( !m_pGCDirProcess )
|
|
{
|
|
AssertMsg( false, "Attempted to read console variables without any GC type specified" );
|
|
return false;
|
|
}
|
|
|
|
//get convars for this specific configuration name
|
|
InitConVars( configValues->FindKey( CFmtStr( "%s-process-convars", m_pGCDirProcess->GetName() ) ) );
|
|
|
|
//now load the GC type specific convars. Note that these can stomp, so we must do all of them in sequence
|
|
for( uint32 nInstance = 0; nInstance < m_pGCDirProcess->GetTypeInstanceCount(); nInstance++ )
|
|
{
|
|
const char* pszTypeName = GDirectory()->GetNameForGCType( m_pGCDirProcess->GetTypeInstance( nInstance )->GetType() );
|
|
InitConVars( configValues->FindKey( CFmtStr( "%s-type-convars", pszTypeName ) ) );
|
|
}
|
|
|
|
//see if they have a special config associated with this GC
|
|
if( m_pGCDirProcess->GetConfig( ) )
|
|
{
|
|
const char* pszAdditionalConvars = m_pGCDirProcess->GetConfig()->GetString( "convars", NULL );
|
|
if( pszAdditionalConvars )
|
|
{
|
|
//now load the convars that are specific to this instance
|
|
InitConVars( configValues->FindKey( CFmtStr( "%s-convars", pszAdditionalConvars ) ) );
|
|
}
|
|
}
|
|
|
|
|
|
if ( k_EUniverseDev != GetUniverse() )
|
|
{
|
|
// See if there's a convar override file
|
|
KeyValuesAD pkvSavedConvars( "convars" );
|
|
|
|
if( pkvSavedConvars->LoadFromFile( g_pFullFileSystem, CFmtStr( "%u_%s_%s_savedconvars.vdf", GetAppID(), m_pGCDirProcess->GetName(), PchNameFromEUniverse( GetUniverse() ) ), "CONFIG" ) )
|
|
{
|
|
InitConVars( pkvSavedConvars );
|
|
}
|
|
else
|
|
{
|
|
EmitInfo( SPEW_GC, SPEW_ALWAYS, LOG_ALWAYS, "Unable to read saved convars file. Continuing with defaults.\n" );
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Sets the values of convars from the given KV
|
|
//-----------------------------------------------------------------------------
|
|
void CGCInterface::InitConVars( KeyValues *pkvConvars )
|
|
{
|
|
// init all the convars
|
|
if( !pkvConvars )
|
|
return;
|
|
|
|
FOR_EACH_VALUE( pkvConvars, pkvVar )
|
|
{
|
|
if ( !pkvVar->GetString() )
|
|
{
|
|
EmitWarning( SPEW_CONSOLE, SPEW_ALWAYS, "variable %s missing value, skipping\n", pkvVar->GetName() );
|
|
}
|
|
|
|
ConVar *pVar = NULL;
|
|
const char *pchSuffix = V_strrchr( pkvVar->GetName(), '_' );
|
|
if ( NULL != pchSuffix && 0 == V_strcmp( pchSuffix, CFmtStr( "_%u", GetAppID() ) ) )
|
|
{
|
|
pVar = g_pCVar->FindVar( pkvVar->GetName() );
|
|
}
|
|
else
|
|
{
|
|
pVar = g_pCVar->FindVar( CFmtStr( "%s_%u", pkvVar->GetName(), GetAppID() ) );
|
|
}
|
|
|
|
if ( !pVar )
|
|
{
|
|
EmitWarning( SPEW_CONSOLE, SPEW_ALWAYS, "config file references unknown convar %s\n", pkvVar->GetName() );
|
|
}
|
|
else
|
|
{
|
|
pVar->SetValue( pkvVar->GetString() );
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Writes the current non-default convars to disk
|
|
//-----------------------------------------------------------------------------
|
|
bool CGCInterface::BSaveConvars()
|
|
{
|
|
//do nothing if we haven't loaded the directory
|
|
if( !m_pGCDirProcess )
|
|
return false;
|
|
|
|
// copy all the non-default convars to the config
|
|
KeyValuesAD pkvConvars( "convars" );
|
|
|
|
ICvar::Iterator iter( g_pCVar );
|
|
for ( iter.SetFirst(); iter.IsValid(); iter.Next() )
|
|
{
|
|
const ConCommandBase *pCommand = iter.Get();
|
|
const GCConVar *pVar = dynamic_cast<const GCConVar *>( pCommand );
|
|
|
|
if( pVar && 0 != Q_strcmp( pVar->GetString(), pVar->GetDefault() ) )
|
|
{
|
|
KeyValues *pkvVar = pkvConvars->FindKey( pVar->GetBaseName(), true );
|
|
pkvVar->SetStringValue( pVar->GetString() );
|
|
}
|
|
}
|
|
|
|
return pkvConvars->SaveToFile( g_pFullFileSystem, CFmtStr( "%u_%s_%s_savedconvars.vdf", GetAppID(), m_pGCDirProcess->GetName(), PchNameFromEUniverse( GetUniverse() ) ), "CONFIG" );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Construct a Steam ID for a client, given an account ID
|
|
//-----------------------------------------------------------------------------
|
|
CSteamID CGCInterface::ConstructSteamIDForClient( AccountID_t unAccountID ) const
|
|
{
|
|
return CSteamID( unAccountID, m_eUniverse, k_EAccountTypeIndividual );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void CGCInterface::ClearAssertWindowCounts()
|
|
{
|
|
FOR_EACH_DICT_FAST( m_dictAsserts, nCurrFile )
|
|
{
|
|
FOR_EACH_VEC( *m_dictAsserts[ nCurrFile ], nCurrAssert )
|
|
{
|
|
( *m_dictAsserts[ nCurrFile ] )[ nCurrAssert ]->m_nWindowFired = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void CGCInterface::ClearAssertInfo()
|
|
{
|
|
FOR_EACH_DICT_FAST( m_dictAsserts, nCurrAssert )
|
|
{
|
|
m_dictAsserts[ nCurrAssert ]->PurgeAndDeleteElements();
|
|
}
|
|
m_dictAsserts.PurgeAndDeleteElements();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Records an assert and optionally passes back if we should write
|
|
// a minidump based on it
|
|
//-----------------------------------------------------------------------------
|
|
void CGCInterface::RecordAssert( const char *pchFile, int nLine, const char *pchMessage, bool *pbShouldWriteMinidump )
|
|
{
|
|
//assume we are not writing a dump by default
|
|
if( pbShouldWriteMinidump )
|
|
*pbShouldWriteMinidump = false;
|
|
|
|
//handle an emergency disable of asserts
|
|
if( !enable_assert_minidumps.GetBool() )
|
|
return;
|
|
|
|
//get our entry in our map
|
|
int iDict = m_dictAsserts.Find( pchFile );
|
|
if ( !m_dictAsserts.IsValidIndex( iDict ) )
|
|
{
|
|
iDict = m_dictAsserts.Insert( pchFile, new CUtlVector< AssertInfo_t* > );
|
|
}
|
|
|
|
CUtlVector< AssertInfo_t* > &vecAsserts = *m_dictAsserts[iDict];
|
|
//see if we have an entry for this line already
|
|
AssertInfo_t* pAssert = NULL;
|
|
FOR_EACH_VEC( vecAsserts, nCurrAssert )
|
|
{
|
|
if( ( uint32 )nLine == vecAsserts[ nCurrAssert ]->m_nLine )
|
|
{
|
|
pAssert = vecAsserts[ nCurrAssert ];
|
|
break;
|
|
}
|
|
}
|
|
|
|
//one wasn't already in the list, so we need to create and insert it
|
|
if( !pAssert )
|
|
{
|
|
pAssert = new AssertInfo_t;
|
|
pAssert->m_nLine = nLine;
|
|
pAssert->m_sMsg = pchMessage;
|
|
pAssert->m_nWindowFired = 0;
|
|
pAssert->m_nTotalFired = 0;
|
|
pAssert->m_nTotalRecorded = 0;
|
|
vecAsserts.AddToTail( pAssert );
|
|
|
|
//also, remove any newlines from the asserts. The default assert inserts them and this creates problems for a lot of the exporting of the data from SQL into Excel
|
|
pAssert->m_sMsg = pAssert->m_sMsg.Replace( '\n', ' ' );
|
|
}
|
|
|
|
//update our stats
|
|
pAssert->m_nTotalFired++;
|
|
pAssert->m_nWindowFired++;
|
|
|
|
//remove any recorded asserts that are older than our window, so that we can record new asserts
|
|
int nStale = 0;
|
|
CUtlVector< RTime32 >& vecTimes = pAssert->m_vRecordTimes;
|
|
const RTime32 nStaleTime = CRTime::RTime32TimeCur() - (uint32)cv_assert_minidump_window.GetInt();
|
|
while ( ( nStale < vecTimes.Count() ) && ( vecTimes[nStale] < nStaleTime ) )
|
|
{
|
|
nStale++;
|
|
}
|
|
vecTimes.RemoveMultipleFromHead( nStale );
|
|
|
|
//see if we have room in how many asserts we want to track, if so, we want to record this assert
|
|
if ( ( vecTimes.Count() < cv_assert_max_minidumps_in_window.GetInt() ) || ( CDisableAssertRateLimit::s_nDisabledCount > 0 ) )
|
|
{
|
|
vecTimes.AddToTail( CRTime::RTime32TimeCur() );
|
|
pAssert->m_nTotalRecorded++;
|
|
if( pbShouldWriteMinidump )
|
|
*pbShouldWriteMinidump = true;
|
|
}
|
|
}
|
|
|
|
//flag indicating whether or not we should force a crash if we encounter an exit
|
|
static bool g_bCrashIfExitDetected = false;
|
|
|
|
//callback handler registered to force a crash on exit conditions so we can track when/why the GC ever exits
|
|
static void GCForceCrash( bool bForceCrash )
|
|
{
|
|
if( bForceCrash )
|
|
{
|
|
//we just want to initiate a crash, so that we can get a call stack
|
|
int* pForceCrash = NULL;
|
|
*pForceCrash = 100;
|
|
}
|
|
}
|
|
|
|
static void ExitHandler() { GCForceCrash( g_bCrashIfExitDetected ); }
|
|
static void AbortHandler( int ) { GCForceCrash( true ); }
|
|
static void PureCallHandler() { GCForceCrash( true ); }
|
|
|
|
static void InvalidCRTParamHandler(const wchar_t* expression,
|
|
const wchar_t* function,
|
|
const wchar_t* file,
|
|
unsigned int line,
|
|
uintptr_t pReserved) { GCForceCrash( true ); }
|
|
|
|
static void InstallExceptionHandlers( bool bCrashOnNormalExit )
|
|
{
|
|
//don't crash on exit while in dev universe
|
|
g_bCrashIfExitDetected = bCrashOnNormalExit;
|
|
Plat_CollectMiniDumpsForFatalErrors();
|
|
//and register one with the at exit handler
|
|
atexit( ExitHandler );
|
|
//and register an abort handler
|
|
signal( SIGABRT, AbortHandler );
|
|
//CRT invalid parameter handler
|
|
_set_invalid_parameter_handler( InvalidCRTParamHandler );
|
|
//Pure virtual function call handler
|
|
_set_purecall_handler( PureCallHandler );
|
|
|
|
MiniDumpRegisterForUnhandledExceptions();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Loads the config, figures out what GC we should be running, and
|
|
// creates it
|
|
//-----------------------------------------------------------------------------
|
|
bool CGCInterface::BAsyncInit( uint32 unAppID, const char *pchDebugName, int iGCIndex, IGameCoordinatorHost *pHost )
|
|
{
|
|
//called to handle registration of exception handlers so that we will always crash rather than an unexpected termination
|
|
InstallExceptionHandlers( pHost->GetUniverse() != k_EUniverseDev );
|
|
|
|
// Make sure we can't deploy debug GCs outside the dev environment
|
|
#ifdef _DEBUG
|
|
if ( pHost->GetUniverse() != k_EUniverseDev )
|
|
{
|
|
pHost->EmitSpew( SPEW_GC.GetName(), SPEW_ERROR, SPEW_ALWAYS, LOG_ALWAYS,
|
|
CFmtStr( "The GC for App %u is a debug binary. Shutting down.\n", unAppID ) );
|
|
return false;
|
|
}
|
|
#endif
|
|
|
|
//report if we are 64 or 32 bit for easier tracking during transition
|
|
COMPILE_TIME_ASSERT( sizeof( tchar ) == sizeof( char ) );
|
|
pHost->EmitSpew( SPEW_GC.GetName(), SPEW_ERROR, SPEW_ALWAYS, LOG_ALWAYS, CFmtStr( "Initializing %d bit GC, Dir Index %d, PID:%u\n", ( uint32 )( sizeof( void* ) * 8 ), iGCIndex, GetCurrentProcessId() ).Access() );
|
|
pHost->EmitSpew( SPEW_GC.GetName(), SPEW_ERROR, SPEW_ALWAYS, LOG_ALWAYS, CFmtStr1024( "Command Line: %s\n", Plat_GetCommandLine() ).Access() );
|
|
|
|
CommandLine()->CreateCmdLine( Plat_GetCommandLine() );
|
|
|
|
//get our machine name
|
|
{
|
|
char szMachineName[ MAX_COMPUTERNAME_LENGTH + 1 ];
|
|
DWORD nBufferSize = ARRAYSIZE( szMachineName );
|
|
GetComputerName( szMachineName, &nBufferSize );
|
|
m_sMachineName = szMachineName;
|
|
}
|
|
|
|
//open our a handle to our parent
|
|
{
|
|
uint32 nParentPID = MAX( 0, CommandLine()->ParmValue( "-parentpid", 0 ) );
|
|
if( nParentPID == 0 )
|
|
{
|
|
pHost->EmitSpew( SPEW_GC.GetName(), SPEW_ERROR, SPEW_ALWAYS, LOG_ALWAYS, "Parent process ID was not specified via -parentpid, unable to get information about the launching process\n" );
|
|
}
|
|
else
|
|
{
|
|
m_hParentProcess = OpenProcess( PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, nParentPID );
|
|
if( !m_hParentProcess )
|
|
{
|
|
pHost->EmitSpew( SPEW_GC.GetName(), SPEW_ERROR, SPEW_ALWAYS, LOG_ALWAYS, "Unable to open the parent process with read access. Unable to get information about the launching process\n" );
|
|
}
|
|
}
|
|
}
|
|
|
|
static bool s_bInitCalled = false;
|
|
if ( s_bInitCalled )
|
|
{
|
|
pHost->EmitSpew( SPEW_GC.GetName(), SPEW_ERROR, SPEW_ALWAYS, LOG_ALWAYS, "BInit called twice on the game IGameCoordinator" );
|
|
return false;
|
|
}
|
|
s_bInitCalled = true;
|
|
|
|
// Set basic variables
|
|
m_pGCHost = pHost;
|
|
m_nAppID = unAppID;
|
|
m_sDebugName = pchDebugName;
|
|
m_eUniverse = (EUniverse)m_pGCHost->GetUniverse();
|
|
|
|
// Initialize core systems
|
|
CRTime::UpdateRealTime();
|
|
RandomSeed( CRTime::RTime32TimeCur() );
|
|
|
|
// Gets the path our dll is loaded from
|
|
HMODULE hModuleGC;
|
|
char rgchGCModuleFile[MAX_PATH+1] = ".\\";
|
|
char rgchGCModulePath[MAX_PATH+1] = ".\\";
|
|
if ( ::GetModuleHandleExA( GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, (LPCSTR)&g_GCInterface, &hModuleGC ) )
|
|
{
|
|
::GetModuleFileNameA( hModuleGC, rgchGCModuleFile, MAX_PATH );
|
|
V_strcpy_safe( rgchGCModulePath, rgchGCModuleFile );
|
|
if ( char *pSlash = strrchr( rgchGCModulePath, '\\' ) )
|
|
pSlash[1] = 0;
|
|
}
|
|
// Full path to GC.DLL (with final slash) is now in rgchGCModulePath
|
|
m_sGCDLLPath = rgchGCModulePath;
|
|
|
|
CFmtStr sContentPath = rgchGCModulePath;
|
|
CFmtStr sBinaryPath = rgchGCModulePath;
|
|
if( Q_stristr( rgchGCModulePath, "bin\\gc\\x64" ) != NULL )
|
|
{
|
|
Q_MakeAbsolutePath( sContentPath.Access(), FMTSTR_STD_LEN, "..\\..\\..", rgchGCModulePath );
|
|
Q_MakeAbsolutePath( sBinaryPath.Access(), FMTSTR_STD_LEN, "..\\..\\..\\..\\bin\\x64", rgchGCModulePath );
|
|
m_bDevMode = true;
|
|
m_sDevBinaryName = rgchGCModuleFile;
|
|
}
|
|
else if( Q_stristr( rgchGCModulePath, "bin\\gc" ) != NULL )
|
|
{
|
|
Q_MakeAbsolutePath( sContentPath.Access(), FMTSTR_STD_LEN, "..\\..", rgchGCModulePath );
|
|
Q_MakeAbsolutePath( sBinaryPath.Access(), FMTSTR_STD_LEN, "..\\..\\..\\bin", rgchGCModulePath );
|
|
m_bDevMode = true;
|
|
m_sDevBinaryName = rgchGCModuleFile;
|
|
}
|
|
else
|
|
{
|
|
//launch through standard GC, so try and extract the version from our path (not a great solution, should extend interface so that
|
|
//the GCH provides us with the version it expects). The format is ....\vNNN\ so try and extract that
|
|
CUtlString sGCPath = rgchGCModulePath;
|
|
sGCPath.StripTrailingSlash();
|
|
if ( const char *pSlash = strrchr( sGCPath.Get(), '\\' ) )
|
|
{
|
|
//skip over the slash, and verify that we have a 'v' before the version
|
|
if( tolower( pSlash[ 1 ] ) == 'v' )
|
|
{
|
|
//grab the version number
|
|
m_nVersion = ( uint32 )max( 0, atoi( pSlash + 2 ) );
|
|
}
|
|
}
|
|
}
|
|
|
|
// Starts logging
|
|
LoggingSystem_PushLoggingState();
|
|
LoggingSystem_SetLoggingResponsePolicy( &s_NonFatalLoggingResponsePolicy );
|
|
LoggingSystem_RegisterLoggingListener( &s_ConsoleLoggingListener );
|
|
|
|
// Select folder and prefix for dumps (using just the dir index for now, but update this later once we have the name
|
|
Tier0GenericMiniDumpHandler_SetFilenamePrefix( CFmtStr( "dumps\\gc%d_idx%d", unAppID, iGCIndex ) );
|
|
|
|
// Make sure dialogs don't come up and hang the process in production
|
|
if ( !m_bDevMode )
|
|
{
|
|
Plat_EnableHeadlessMode();
|
|
}
|
|
RegisterAssertionFailureListener( &sg_GCAssertionFailureHandler );
|
|
|
|
// Initialize GIDs
|
|
m_ullGID = 0;
|
|
m_ullGID = (uint64)iGCIndex << 56; // 8 bits of process id
|
|
m_ullGID |= (uint64)CRTime::RTime32TimeCur() << 24; // 32 bits of UTC time in seconds
|
|
// 24 bits/second of incremental counter space
|
|
|
|
// This system assumes there are less than 256 GCs. Make sure of that
|
|
AssertMsg( iGCIndex >= 0 && iGCIndex < 256, "iGCIndex out of range. There can only be 256 GC processes for an app" );
|
|
if ( iGCIndex < 0 || iGCIndex >= 256 )
|
|
return false;
|
|
|
|
// Make sure the protobuf library won't exitprocess without dumping
|
|
::google::protobuf::SetLogHandler( ProtobufLogHandler );
|
|
|
|
g_gcAppSystemGroup.SetPath( sBinaryPath );
|
|
if( g_gcAppSystemGroup.Startup() < 0 )
|
|
return false;
|
|
|
|
g_pFullFileSystem->AddSearchPath( sContentPath, "GAME" );
|
|
g_pFullFileSystem->AddSearchPath( rgchGCModulePath, "CONFIG" ); // config files go with gc.dll
|
|
|
|
// load the config file first thing so that we can use it for all the other startup code
|
|
KeyValuesAD configKeys( "config" );
|
|
if( !BReadConfigDirectory( configKeys ) )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Find this GC in the config and create it
|
|
m_pGCDirProcess = GDirectory()->GetProcess( iGCIndex );
|
|
if ( NULL == m_pGCDirProcess )
|
|
{
|
|
GCSDK::EmitError( SPEW_CONSOLE, "Failed to start GC for index %d.\n", iGCIndex );
|
|
return false;
|
|
}
|
|
|
|
CDirectory::GCFactory_t pfnFactory = GDirectory()->GetFactoryForProcessType( m_pGCDirProcess->GetProcessType() );
|
|
if ( NULL == pfnFactory )
|
|
{
|
|
GCSDK::EmitError( SPEW_CONSOLE, "Failed to start GC for index %d (type %s). Got a NULL factory function, likely missing registration for this type\n", iGCIndex, m_pGCDirProcess->GetProcessType() );
|
|
return false;
|
|
}
|
|
|
|
//now that we have more information about which GC we are, update our minidump name to reflect this
|
|
Tier0GenericMiniDumpHandler_SetFilenamePrefix( CFmtStr( "dumps\\gc%d_%s", unAppID, m_pGCDirProcess->GetName() ) );
|
|
|
|
//now that we know our GC type, we can actually load up our convars (which are dependent on this info)
|
|
if( !BReadConvars( configKeys ) )
|
|
return false;
|
|
|
|
// Init the GC. Not passing along the host because the interface layer owns it
|
|
// and chooses what to expose
|
|
m_pGC = pfnFactory( m_pGCDirProcess );
|
|
return m_pGC->BAsyncInit( unAppID, pchDebugName, iGCIndex, NULL );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Generates a number that's guaranteed unique across all GC processes
|
|
// for this app. It is also guaranteed to have never been used by previous
|
|
// processes.
|
|
//-----------------------------------------------------------------------------
|
|
GID_t CGCInterface::GenerateGID()
|
|
{
|
|
return ++m_ullGID;
|
|
}
|
|
|
|
//we have the GC index encoded in the high bits when we init the gid, so just extract that
|
|
uint32 CGCInterface::GetGCDirIndexFromGID( GID_t gid )
|
|
{
|
|
return ( uint32 )( gid >> 56 );
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Gets the universe the GC is currently running in
|
|
//-----------------------------------------------------------------------------
|
|
EUniverse CGCInterface::GetUniverse() const
|
|
{
|
|
// Gets the current universe
|
|
return m_eUniverse;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Wrappers for GCHost functions that allow GCInterface to hook the calls
|
|
//-----------------------------------------------------------------------------
|
|
bool CGCInterface::BProcessSystemMessage( uint32 unGCSysMsgType, const void *pubData, uint32 cubData )
|
|
{
|
|
AssertMsg( unGCSysMsgType != 0, "Message sent without an ID. Did you use the wrong constructor?" );
|
|
|
|
//track this message that we are sending (always just strip off the protobuff flag so it works with all message types)
|
|
g_theMessageList.TallySendMessage( unGCSysMsgType & ~k_EMsgProtoBufFlag, cubData, eMsgSource_System );
|
|
|
|
VPROF_BUDGET( "GCHost", VPROF_BUDGETGROUP_STEAM );
|
|
{
|
|
VPROF_BUDGET( "GCHost - ProcessSystemMessage", VPROF_BUDGETGROUP_STEAM );
|
|
return m_pGCHost->BProcessSystemMessage( unGCSysMsgType, pubData, cubData );
|
|
}
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
bool CGCInterface::BSendMessageToClient( uint64 ullSteamID, uint32 unMsgType, const void *pubData, uint32 cubData )
|
|
{
|
|
AssertMsg( unMsgType != 0, "Message sent without an ID. Did you use the wrong constructor?" );
|
|
|
|
//sanity check on our side that we are sending with a valid steam ID. Useful to catch message failures on the GC side since otherwise it must be caught in the GCH side
|
|
if( ullSteamID == k_steamIDNil.ConvertToUint64() )
|
|
{
|
|
AssertMsg( false, "Message %d sent to invalid steam ID. This message will not be processed.", unMsgType & ~k_EMsgProtoBufFlag );
|
|
return false;
|
|
}
|
|
|
|
//track this message that we are sending (always just strip off the protobuff flag so it works with all message types)
|
|
g_theMessageList.TallySendMessage( unMsgType & ~k_EMsgProtoBufFlag, cubData, eMsgSource_Client );
|
|
|
|
VPROF_BUDGET( "GCHost", VPROF_BUDGETGROUP_STEAM );
|
|
{
|
|
VPROF_BUDGET( "GCHost - SendMessageToClient", VPROF_BUDGETGROUP_STEAM );
|
|
return m_pGCHost->BSendMessageToClient( ullSteamID, unMsgType, pubData, cubData );
|
|
}
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
bool CGCInterface::BSendMessageToGC( int iGCServerIDTarget, uint32 unMsgType, const void *pubData, uint32 cubData )
|
|
{
|
|
AssertMsg( unMsgType != 0, "Message sent without an ID. Did you use the wrong constructor?" );
|
|
|
|
//track this message that we are sending (always just strip off the protobuff flag so it works with all message types)
|
|
g_theMessageList.TallySendMessage( unMsgType & ~k_EMsgProtoBufFlag, cubData, eMsgSource_GC );
|
|
|
|
VPROF_BUDGET( "GCHost", VPROF_BUDGETGROUP_STEAM );
|
|
{
|
|
VPROF_BUDGET( "GCHost - SendMessageToGC", VPROF_BUDGETGROUP_STEAM );
|
|
return m_pGCHost->BSendMessageToGC( iGCServerIDTarget, unMsgType, pubData, cubData );
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void CGCInterface::AddBlockEmitString( const char* pszStr, bool bBlockConsole, bool bBlockLog )
|
|
{
|
|
BlockString_t* pStr = new BlockString_t;
|
|
pStr->m_sStr = pszStr;
|
|
pStr->m_bBlockConsole = bBlockConsole;
|
|
pStr->m_bBlockLog = bBlockLog;
|
|
m_BlockEmitStrings.AddToTail( pStr );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void CGCInterface::ClearBlockEmitStrings()
|
|
{
|
|
m_BlockEmitStrings.PurgeAndDeleteElements();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void CGCInterface::EmitSpew( const char *pchGroupName, SpewType_t spewType, int iSpewLevel, int iLevelLog, const char *pchMsg )
|
|
{
|
|
//see if this output is being squelched by going through our blocked string
|
|
FOR_EACH_VEC( m_BlockEmitStrings, nStr )
|
|
{
|
|
//if our output contains the blocked string, turn off the severity for that level
|
|
const BlockString_t* pStr = m_BlockEmitStrings[ nStr ];
|
|
if( V_stristr( pchMsg, pStr->m_sStr ) != NULL )
|
|
{
|
|
if( pStr->m_bBlockConsole )
|
|
iSpewLevel = SPEW_NEVER;
|
|
if( pStr->m_bBlockLog )
|
|
iLevelLog = LOG_NEVER;
|
|
}
|
|
}
|
|
|
|
//see if this is just a blank line
|
|
if( filter_blank_lines.GetBool() && pchMsg )
|
|
{
|
|
bool bIsBlankLine = true;
|
|
for( const char* pszCurrChar = pchMsg; *pszCurrChar; pszCurrChar++ )
|
|
{
|
|
if( !V_isspace( *pszCurrChar ) )
|
|
{
|
|
bIsBlankLine = false;
|
|
break;
|
|
}
|
|
}
|
|
if( bIsBlankLine )
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
if ( m_bLogCaptureEnabled )
|
|
{
|
|
m_vecLogCapture[m_vecLogCapture.AddToTail()].Set( pchMsg );
|
|
}
|
|
|
|
VPROF_BUDGET( "GCHost", VPROF_BUDGETGROUP_STEAM );
|
|
{
|
|
VPROF_BUDGET( "GCHost - EmitMessage", VPROF_BUDGETGROUP_STEAM );
|
|
m_pGCHost->EmitSpew( pchGroupName, spewType, iSpewLevel, iLevelLog, pchMsg );
|
|
}
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void CGCInterface::AsyncSQLQuery( IGCSQLQuery *pQuery, int eSchemaCatalog )
|
|
{
|
|
VPROF_BUDGET( "GCHost", VPROF_BUDGETGROUP_STEAM );
|
|
{
|
|
VPROF_BUDGET( "GCHost - SQLQuery", VPROF_BUDGETGROUP_STEAM );
|
|
m_pGCHost->AsyncSQLQuery( pQuery, eSchemaCatalog );
|
|
}
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void CGCInterface::SetStartupComplete( bool bSuccess )
|
|
{
|
|
m_pGCHost->StartupComplete( bSuccess );
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void CGCInterface::SetShutdownComplete()
|
|
{
|
|
m_pGCHost->ShutdownComplete();
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Passthrough implementations of the rest of the interface
|
|
//-----------------------------------------------------------------------------
|
|
void CGCInterface::Unload()
|
|
{
|
|
if ( m_pGC )
|
|
m_pGC->Unload();
|
|
|
|
g_gcAppSystemGroup.Shutdown();
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
bool CGCInterface::BAsyncShutdown()
|
|
{
|
|
bool bResult = false;
|
|
if ( m_pGC )
|
|
bResult = m_pGC->BAsyncShutdown();
|
|
|
|
//if they have requested a shutdown, go ahead and allow exit
|
|
g_bCrashIfExitDetected = false;
|
|
|
|
return bResult;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
bool CGCInterface::BMainLoopOncePerFrame( uint64 ulLimitMicroseconds )
|
|
{
|
|
if ( !m_pGC )
|
|
return false;
|
|
else
|
|
return m_pGC->BMainLoopOncePerFrame( ulLimitMicroseconds );
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
bool CGCInterface::BMainLoopUntilFrameCompletion( uint64 ulLimitMicroseconds )
|
|
{
|
|
if ( !m_pGC )
|
|
return false;
|
|
else
|
|
return m_pGC->BMainLoopUntilFrameCompletion( ulLimitMicroseconds );
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void CGCInterface::HandleMessageFromClient( uint64 ullSenderID, uint32 unMsgType, void *pubData, uint32 cubData )
|
|
{
|
|
if ( NULL == pubData || 0 == cubData )
|
|
{
|
|
EG_ERROR( g_EGMessages, "Received invalid message from user %s. MessageID: %u pubData: %p cubData: %u\n", CSteamID( ullSenderID ).Render(), unMsgType, pubData, cubData );
|
|
}
|
|
else if ( m_pGC )
|
|
{
|
|
g_theMessageList.TallyReceiveMessage( unMsgType & ~k_EMsgProtoBufFlag, cubData, eMsgSource_Client );
|
|
m_pGC->HandleMessageFromClient( ullSenderID, unMsgType, pubData, cubData );
|
|
}
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void CGCInterface::HandleMessageFromSystem( uint32 unGCSysMsgType, void *pubData, uint32 cubData )
|
|
{
|
|
if ( NULL == pubData || 0 == cubData )
|
|
{
|
|
EG_ERROR( g_EGMessages, "Received invalid message from system. MessageID: %u pubData: %p cubData: %u\n", unGCSysMsgType, pubData, cubData );
|
|
}
|
|
else if ( m_pGC )
|
|
{
|
|
g_theMessageList.TallyReceiveMessage( unGCSysMsgType & ~k_EMsgProtoBufFlag, cubData, eMsgSource_System );
|
|
m_pGC->HandleMessageFromSystem( unGCSysMsgType, pubData, cubData );
|
|
}
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void CGCInterface::HandleMessageFromGC( int iGCServerIDSender, uint32 unMsgType, void *pubData, uint32 cubData )
|
|
{
|
|
if ( NULL == pubData || 0 == cubData )
|
|
{
|
|
EG_ERROR( g_EGMessages, "Received invalid message from GC. MessageID: %u pubData: %p cubData: %u\n", iGCServerIDSender, pubData, cubData );
|
|
}
|
|
else if ( m_pGC )
|
|
{
|
|
g_theMessageList.TallyReceiveMessage( unMsgType & ~k_EMsgProtoBufFlag, cubData, eMsgSource_GC );
|
|
m_pGC->HandleMessageFromGC( iGCServerIDSender, unMsgType, pubData, cubData );
|
|
}
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void CGCInterface::StartLogCapture()
|
|
{
|
|
m_bLogCaptureEnabled = true;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void CGCInterface::EndLogCapture()
|
|
{
|
|
m_bLogCaptureEnabled = false;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
const CUtlVector<CUtlString> *CGCInterface::GetLogCapture()
|
|
{
|
|
return &m_vecLogCapture;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void CGCInterface::ClearLogCapture()
|
|
{
|
|
m_vecLogCapture.RemoveAll();
|
|
}
|
|
|
|
|
|
}
|
|
|
|
//For the GC, we want to force a crash when we get stack corruption errors so
|
|
// that we can analyze the dumps and fix the problem. To disable this behavior,
|
|
// comment out the function below.
|
|
|
|
// TEMP: Disabled on VS2013+ because I didn't know how to fix the linking problems
|
|
#if defined( _MSC_VER ) && ( _MSC_VER < 1800 )
|
|
extern "C"
|
|
{
|
|
#if defined (_X86_)
|
|
__declspec(noreturn) void __cdecl __report_gsfailure(void)
|
|
#else /* defined (_X86_) */
|
|
__declspec(noreturn) void __cdecl __report_gsfailure(ULONGLONG StackCookie)
|
|
#endif /* defined (_X86_) */
|
|
{
|
|
MiniDumpOptionalData_t optionalData( _T("stack_corruption") );
|
|
InvokeMiniDumpHandler( NULL, &optionalData );
|
|
static uint32* pNull = NULL;
|
|
*pNull = 0;
|
|
}
|
|
}
|
|
#endif |