From 3bf9df6b2785fa6d951086978a3e66f49427166a Mon Sep 17 00:00:00 2001 From: FluorescentCIAAfricanAmerican <0934gj3049fk@protonmail.com> Date: Wed, 22 Apr 2020 12:56:21 -0400 Subject: 1 --- gcsdk/gcinterface.cpp | 1085 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1085 insertions(+) create mode 100644 gcsdk/gcinterface.cpp (limited to 'gcsdk/gcinterface.cpp') diff --git a/gcsdk/gcinterface.cpp b/gcsdk/gcinterface.cpp new file mode 100644 index 0000000..f3d408b --- /dev/null +++ b/gcsdk/gcinterface.cpp @@ -0,0 +1,1085 @@ +//====== 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 + + +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 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( 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 *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 \ No newline at end of file -- cgit v1.2.3