//========= Copyright (c), Valve LLC, All rights reserved. ============ // // Purpose: // // $NoKeywords: $ //============================================================================= #include "stdafx.h" #include "gcsystemaccess.h" #include "gcjob.h" #include "gcsdk/gcreportprinter.h" // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" namespace GCSDK { static GCConVar gcaccess_enable( "gcaccess_enable", "1", "Kill switch that disables all gcaccess tracking and systems" ); DECLARE_GC_EMIT_GROUP( g_EGAccess, access ); //----------------------------------------------------------------------------------------------------------------------------------------- GCAccessSystem_t GenerateUniqueGCAccessID() { static GCAccessSystem_t s_nID = 0; return s_nID++; } //----------------------------------------------------------------------------------------------------------------------------------------- // CGCAccessSystem //----------------------------------------------------------------------------------------------------------------------------------------- //a GCAccess system that has been registered class CGCAccessSystem { public: //the name of the system for display CUtlString m_sName; //the unique ID for this system GCAccessSystem_t m_nID; //which actions should be enabled or disabled for this system in particular uint32 m_nActions; uint32 m_nSuppressActions; struct AccessStats_t { AccessStats_t(); void Add( const AccessStats_t& rhs ); //how many raw accesses have we had? uint32 m_nNumValid; //how many failed because the context didn't have rights? uint32 m_nNumFail; //how many failed because the associated ID wasn't valid? uint32 m_nNumFailID; }; struct TrackedJob_t { CUtlString m_sContext; AccessStats_t m_Stats; }; //which jobs have violated this system access rights CUtlHashMapLarge< uintp, TrackedJob_t > m_Jobs; }; //----------------------------------------------------------------------------------------------------------------------------------------- // CGCAccessContext //----------------------------------------------------------------------------------------------------------------------------------------- CGCAccessContext::CGCAccessContext() : m_nActions( 0 ), m_nSuppressActions( 0 ) { } void CGCAccessContext::Init( const char* pszName, uint32 nActions, uint32 nSuppressActions ) { m_sName = pszName; m_nActions = nActions; m_nSuppressActions = nSuppressActions; } void CGCAccessContext::AddSystem( GCAccessSystem_t nSystem ) { m_nSystems.InsertIfNotFound( nSystem ); } bool CGCAccessContext::HasSystem( GCAccessSystem_t system ) const { return ( m_nSystems.Find( system ) != m_nSystems.InvalidIndex() ); } bool CGCAccessContext::IsSubsetOf( const CGCAccessContext& context ) const { FOR_EACH_VEC( m_nSystems, nCurrSystem ) { if( !context.HasSystem( m_nSystems[ nCurrSystem ] ) ) return false; } return true; } void CGCAccessContext::AddSystemsFrom( const CGCAccessContext& context ) { FOR_EACH_VEC( context.m_nSystems, nCurrSystem ) { AddSystem( context.m_nSystems[ nCurrSystem ] ); } } void CGCAccessContext::SetAction( EGCAccessAction eAction, bool bSet ) { if( bSet ) m_nActions |= eAction; else m_nActions &= ~( uint32 )eAction; } void CGCAccessContext::SetSuppressAction( EGCAccessAction eAction, bool bSet ) { if( bSet ) m_nSuppressActions |= eAction; else m_nSuppressActions &= ~( uint32 )eAction; } //----------------------------------------------------------------------------------------------------------------------------------------- // CGCAccess //----------------------------------------------------------------------------------------------------------------------------------------- static CGCAccess g_GCAccess; CGCAccess& GGCAccess() { return g_GCAccess; } CGCAccessSystem::AccessStats_t::AccessStats_t() : m_nNumFail( 0 ) , m_nNumFailID( 0 ) , m_nNumValid( 0 ) {} void CGCAccessSystem::AccessStats_t::Add( const AccessStats_t& rhs ) { m_nNumFail += rhs.m_nNumFail; m_nNumFailID += rhs.m_nNumFailID; m_nNumValid += rhs.m_nNumValid; } CGCAccess::CGCAccess() : m_nActions( GCAccessAction_TrackSuccess | GCAccessAction_TrackFail| GCAccessAction_ReturnFail ), m_nSuppressActions( 0 ) { m_GlobalContext.Init( "Global" ); } void CGCAccess::RegisterSystem( const char* pszName, GCAccessSystem_t nID, uint32 nActions, uint32 nSupressActions ) { //make sure that we don't have a conflict int nIndex = m_Systems.Find( nID ); if( nIndex != m_Systems.InvalidIndex() ) { // !FIXME! // DOTAMERGE AssertMsg varargs AssertMsg3( false, "Multiple systems conflciting in Register System: ID %d, original: %s, new %s", nID, m_Systems[ nIndex ]->m_sName.String(), pszName ); return; } CGCAccessSystem* pSystem = new CGCAccessSystem; pSystem->m_sName = pszName; pSystem->m_nID = nID; pSystem->m_nActions = nActions; pSystem->m_nSuppressActions = nSupressActions; m_Systems.Insert( nID, pSystem ); } bool CGCAccess::ValidateAccess( GCAccessSystem_t nSystem ) { //global kill switch if( !gcaccess_enable.GetBool() ) return true; return InternalValidateAccess( nSystem, CSteamID(), CSteamID() ); } bool CGCAccess::ValidateSteamIDAccess( GCAccessSystem_t nSystem, CSteamID steamID ) { //global kill switch if( !gcaccess_enable.GetBool() ) return true; CSteamID expectedID = ( g_pJobCur ) ? g_pJobCur->SOVALIDATE_GetSteamID() : steamID; return InternalValidateAccess( nSystem, steamID, expectedID ); } bool CGCAccess::InternalValidateAccess( GCAccessSystem_t nSystem, CSteamID steamID, CSteamID expectedID ) { //see if tracking for this system is disabled. This list is almost always empty, so just use a linear search for now. FOR_EACH_VEC( m_SuppressAccess, nAction ) { if( m_SuppressAccess[ nAction ].m_nSystem == nSystem ) return true; } //assume the global context const CGCAccessContext* pContext = &m_GlobalContext; const char* pszJobName = "[global]"; //if we have a job, use it's context if( g_pJobCur ) { const CGCJob* pJob = static_cast< const CGCJob* >( g_pJobCur ); pszJobName = pJob->GetName(); if( pJob->GetContext() ) pContext = pJob->GetContext(); } //is this a valid system to access bool bValidSteamID = ( steamID == expectedID ); bool bValidSystem = pContext->HasSystem( nSystem ); bool bValidAccess = ( bValidSystem && bValidSteamID ); //determine the actions we want to do for tracking uint32 nActions = pContext->GetActions() | m_nActions; uint32 nSuppressActions = pContext->GetSuppressActions() | m_nSuppressActions; //look up the system that we are accessing CGCAccessSystem* pSystem = NULL; int nSystemIndex = m_Systems.Find( nSystem ); if( nSystemIndex == m_Systems.InvalidIndex() ) { // !FIXME! DOTAMERGE // AssertMsg varargs AssertMsg1( false, "Error: Tracking a system that has not been registered. Make sure to register system %u", nSystem ); } else { //make sure that we have a stat entry for this job pSystem = m_Systems[ nSystemIndex ]; nActions |= pSystem->m_nActions; nSuppressActions |= pSystem->m_nSuppressActions; } //remove any suppressed actions nActions &= ~nSuppressActions; //see if we need to track this access bool bTrackSuccess = ( nActions & GCAccessAction_TrackSuccess ) && bValidAccess; bool bTrackFail = ( nActions & GCAccessAction_TrackFail ) && !bValidAccess; if( ( bTrackSuccess || bTrackFail ) && pSystem ) { //make sure that we have a stat entry for this job int nJobIndex = pSystem->m_Jobs.Find( ( uintp )pszJobName ); if( nJobIndex == pSystem->m_Jobs.InvalidIndex() ) { nJobIndex = pSystem->m_Jobs.Insert( ( uintp )pszJobName ); pSystem->m_Jobs[ nJobIndex ].m_sContext = pContext->GetName(); } //update our stats based upon what was valid and what wasn't CGCAccessSystem::AccessStats_t& stats = pSystem->m_Jobs[ nJobIndex ].m_Stats; if( bTrackSuccess ) stats.m_nNumValid++; if( bTrackFail ) { if( !bValidSteamID ) stats.m_nNumFailID++; if( !bValidSystem ) stats.m_nNumFail++; } } //see if this is a single access we want to try and track FOR_EACH_VEC( m_SingleAsserts, nCurrAssert ) { SingleAssert_t* pAssert = m_SingleAsserts[ nCurrAssert ]; if( nSystem == pAssert->m_System ) { if( V_stricmp( ( pAssert->m_bContext ) ? pContext->GetName() : pszJobName, pAssert->m_sContextOrJob ) == 0 ) { //log this assert { // !FIXME! DOTAMERGE //CGCInterface::CDisableAssertRateLimit disableAssertLimit; // !FIXME! DOTAMERGE // AssertMsg varargs AssertMsg3( false, "GCSystemAccess Caught %s (context %s) calling into %s", pszJobName, pContext->GetName(), pSystem ? pSystem->m_sName.String() : "unknown" ); } //and clear it delete pAssert; m_SingleAsserts.FastRemove( nCurrAssert ); } } } //at this point, if it is a valid access, just bail if( bValidAccess ) return true; //otherwise, handle the failure case if( nActions & ( GCAccessAction_Msg | GCAccessAction_Assert ) ) { int nSystemIndex = m_Systems.Find( nSystem ); const char* pszSystem = ( nSystemIndex != m_Systems.InvalidIndex() ) ? m_Systems[ nSystemIndex ]->m_sName.String() : ""; //display a message based upon if it was a system or ID violation CFmtStr1024 szMsg; if( !bValidSystem ) { szMsg.sprintf( "Job %s Accessed invalid system %s (%u) while in context %s\n", pszJobName, pszSystem, nSystem, pContext->GetName() ); } else { szMsg.sprintf( "Job %s Accessed invalid steam ID %s but expected %s in system %s (%u) while in context %s\n", pszJobName, steamID.RenderLink(), expectedID.RenderLink(), pszSystem, nSystem, pContext->GetName() ); } if( nActions & GCAccessAction_Msg ) EG_MSG( g_EGAccess, "%s", szMsg.String() ); if( nActions & GCAccessAction_Assert ) { // !FIXME! DOTAMERGE // AssertMsg varargs AssertMsg1( false, "%s", szMsg.String() ); } } return !( nActions & GCAccessAction_ReturnFail ); } void CGCAccess::SuppressAccess( GCAccessSystem_t nSystem, bool bEnable ) { //see if it is an existing item already suppressed that we need to modify the ref count of FOR_EACH_VEC( m_SuppressAccess, nAccess ) { if( m_SuppressAccess[ nAccess ].m_nSystem == nSystem ) { if( bEnable ) { //to enable, we want to decrease the disable count, or remove if we are fully re-enabled if( m_SuppressAccess[ nAccess ].m_nCount <= 1 ) m_SuppressAccess.FastRemove( nAccess ); else m_SuppressAccess[ nAccess ].m_nCount--; } else { m_SuppressAccess[ nAccess ].m_nCount++; } return; } } //only add it to the list if we are disabling if( !bEnable ) { SuppressAccess_t access; access.m_nSystem = nSystem; access.m_nCount = 1; m_SuppressAccess.AddToTail( access ); } } void CGCAccess::ClearSystemStats() { FOR_EACH_MAP_FAST( m_Systems, nSystem ) { m_Systems[ nSystem ]->m_Jobs.Purge(); } } bool CGCAccess::CatchSingleAssert( const char* pszSystem, bool bContext, const char* pszContextOrJob ) { //find the system we are referencing so we don't add invalid breakpoints if we can avoid it GCAccessSystem_t nSystemID = ( GCAccessSystem_t )-1; FOR_EACH_MAP_FAST( m_Systems, nSystem ) { if( V_stricmp( m_Systems[ nSystem ]->m_sName, pszSystem ) == 0 ) { nSystemID = m_Systems.Key( nSystem ); break; } } if( nSystemID == ( GCAccessSystem_t )-1 ) return false; SingleAssert_t* pAssert = new SingleAssert_t; pAssert->m_System = nSystemID; pAssert->m_bContext = bContext; pAssert->m_sContextOrJob = pszContextOrJob; m_SingleAsserts.AddToTail( pAssert ); return true; } void CGCAccess::ClearSingleAsserts() { m_SingleAsserts.PurgeAndDeleteElements(); } //given a display type and a stats object, determines if it meets the criteria static bool ShouldDisplayStats( CGCAccess::EDisplay eDisplay, const CGCAccessSystem::AccessStats_t& stats ) { if( eDisplay == CGCAccess::eDisplay_Referenced ) return ( stats.m_nNumFail + stats.m_nNumFailID + stats.m_nNumValid ) > 0; if( eDisplay == CGCAccess::eDisplay_Violations ) return ( stats.m_nNumFail + stats.m_nNumFailID ) > 0; if( eDisplay == CGCAccess::eDisplay_IDViolations ) return stats.m_nNumFailID > 0; //unknown, so just assume all return true; } void CGCAccess::ReportJobs( const char* pszContext, EDisplay eDisplay ) const { //collect all of the job stats into a single summary table CUtlHashMapLarge< uintp, CGCAccessSystem::TrackedJob_t > jobs; FOR_EACH_MAP_FAST( m_Systems, nSystem ) { const CGCAccessSystem* pSystem = m_Systems[ nSystem ]; FOR_EACH_MAP_FAST( pSystem->m_Jobs, nJob ) { const CGCAccessSystem::TrackedJob_t& job = pSystem->m_Jobs[ nJob ]; //skip any contexts we don't care about if( pszContext && ( V_stricmp( pszContext, job.m_sContext ) != 0 ) ) continue; if( !ShouldDisplayStats( eDisplay, job.m_Stats ) ) continue; int nIndex = jobs.Find( pSystem->m_Jobs.Key( nJob ) ); if( nIndex == jobs.InvalidIndex() ) { nIndex = jobs.Insert( pSystem->m_Jobs.Key( nJob ) ); jobs[ nIndex ].m_sContext = job.m_sContext; } jobs[ nIndex ].m_Stats.Add( job.m_Stats ); } } CGCReportPrinter rp; rp.AddStringColumn( "Job" ); rp.AddStringColumn( "Context" ); rp.AddIntColumn( "Valid", CGCReportPrinter::eSummary_Total ); rp.AddIntColumn( "Invalid", CGCReportPrinter::eSummary_Total ); rp.AddIntColumn( "InvalidID", CGCReportPrinter::eSummary_Total ); FOR_EACH_MAP_FAST( jobs, nJob ) { rp.StrValue( ( const char* )jobs.Key( nJob ), CFmtStr( "gcaccess_dump_job \"%s\" %d", ( const char* )jobs.Key( nJob ), eDisplay ).String() ); rp.StrValue( jobs[ nJob ].m_sContext, CFmtStr( "gcaccess_dump_context \"%s\" %d", jobs[ nJob ].m_sContext.String(), eDisplay ).String() ); rp.IntValue( jobs[ nJob ].m_Stats.m_nNumValid ); rp.IntValue( jobs[ nJob ].m_Stats.m_nNumFail ); rp.IntValue( jobs[ nJob ].m_Stats.m_nNumFailID ); rp.CommitRow(); } rp.SortReport( "Job", false ); rp.PrintReport( SPEW_CONSOLE ); } void CGCAccess::ReportSystems( const char* pszContext, EDisplay eDisplay ) const { CGCReportPrinter rp; rp.AddStringColumn( "System" ); rp.AddIntColumn( "ID", CGCReportPrinter::eSummary_None ); rp.AddIntColumn( "Jobs", CGCReportPrinter::eSummary_None ); rp.AddIntColumn( "Valid", CGCReportPrinter::eSummary_Total ); rp.AddIntColumn( "Invalid", CGCReportPrinter::eSummary_Total ); rp.AddIntColumn( "InvalidID", CGCReportPrinter::eSummary_Total ); FOR_EACH_MAP_FAST( m_Systems, nSystem ) { const CGCAccessSystem* pSystem = m_Systems[ nSystem ]; CGCAccessSystem::AccessStats_t stats; FOR_EACH_MAP_FAST( pSystem->m_Jobs, nJob ) { const CGCAccessSystem::TrackedJob_t& job = pSystem->m_Jobs[ nJob ]; //skip any contexts we don't care about if( pszContext && ( V_stricmp( pszContext, job.m_sContext ) != 0 ) ) continue; stats.Add( job.m_Stats ); } if( !ShouldDisplayStats( eDisplay, stats ) ) continue; rp.StrValue( pSystem->m_sName, CFmtStr( "gcaccess_dump_system \"%s\" %d", pSystem->m_sName.String(), eDisplay ).String() ); rp.IntValue( pSystem->m_nID ); rp.IntValue( pSystem->m_Jobs.Count() ); rp.IntValue( stats.m_nNumValid ); rp.IntValue( stats.m_nNumFail ); rp.IntValue( stats.m_nNumFailID ); rp.CommitRow(); } rp.SortReport( "System", false ); rp.PrintReport( SPEW_CONSOLE ); } void CGCAccess::FullReport( const char* pszSystemFilter, const char* pszContextFilter, const char* pszJobFilter, EDisplay eDisplay ) const { CGCReportPrinter rp; rp.AddStringColumn( "Job" ); rp.AddStringColumn( "Context" ); rp.AddStringColumn( "System" ); rp.AddIntColumn( "Valid", CGCReportPrinter::eSummary_Total ); rp.AddIntColumn( "Invalid", CGCReportPrinter::eSummary_Total ); rp.AddIntColumn( "InvalidID", CGCReportPrinter::eSummary_Total ); FOR_EACH_MAP_FAST( m_Systems, nSystem ) { const CGCAccessSystem* pSystem = m_Systems[ nSystem ]; if( pszSystemFilter && V_stricmp( pszSystemFilter, pSystem->m_sName ) ) continue; FOR_EACH_MAP_FAST( pSystem->m_Jobs, nJob ) { const CGCAccessSystem::AccessStats_t& stats = pSystem->m_Jobs[ nJob ].m_Stats; const char* pszJob = ( const char* )pSystem->m_Jobs.Key( nJob ); const char* pszContext = ( const char* )pSystem->m_Jobs[ nJob ].m_sContext; if( !ShouldDisplayStats( eDisplay, stats ) ) continue; if( pszJobFilter && V_stricmp( pszJobFilter, pszJob ) ) continue; if( pszContextFilter && V_stricmp( pszContextFilter, pszContext ) ) continue; rp.StrValue( pszJob, CFmtStr( "gcaccess_dump_job \"%s\" %d", pszJob, eDisplay ).String() ); rp.StrValue( pszContext, CFmtStr( "gcaccess_dump_context \"%s\" %d", pszContext, eDisplay ).String() ); rp.StrValue( pSystem->m_sName, CFmtStr( "gcaccess_dump_system \"%s\" %d", pSystem->m_sName.String(), eDisplay ).String() ); rp.IntValue( stats.m_nNumValid ); rp.IntValue( stats.m_nNumFail ); rp.IntValue( stats.m_nNumFailID ); rp.CommitRow(); } } rp.SortReport( "Context", false ); rp.PrintReport( SPEW_CONSOLE ); } void CGCAccess::DependencyReport( const char* pszSystem, EDisplay eDisplay ) const { //first off, we need to find the system we are scanning for dependencies on const CGCAccessSystem* pMatchSystem = NULL; FOR_EACH_MAP_FAST( m_Systems, nSystem ) { const CGCAccessSystem* pSystem = m_Systems[ nSystem ]; if( V_stricmp( pszSystem, pSystem->m_sName ) == 0 ) { pMatchSystem = pSystem; break; } } if( !pMatchSystem ) { EG_MSG( SPEW_CONSOLE, "Unable to find system %s for dependency report\n", pszSystem ); return; } //now generate our report CGCReportPrinter rp; rp.AddStringColumn( "Job" ); rp.AddStringColumn( "Context" ); rp.AddStringColumn( "System" ); rp.AddIntColumn( "Valid", CGCReportPrinter::eSummary_Total ); rp.AddIntColumn( "Invalid", CGCReportPrinter::eSummary_Total ); rp.AddIntColumn( "InvalidID", CGCReportPrinter::eSummary_Total ); FOR_EACH_MAP_FAST( m_Systems, nSystem ) { const CGCAccessSystem* pSystem = m_Systems[ nSystem ]; //skip ourself if( pSystem == pMatchSystem ) continue; FOR_EACH_MAP_FAST( pSystem->m_Jobs, nJob ) { const CGCAccessSystem::AccessStats_t& stats = pSystem->m_Jobs[ nJob ].m_Stats; const char* pszJob = ( const char* )pSystem->m_Jobs.Key( nJob ); const char* pszContext = ( const char* )pSystem->m_Jobs[ nJob ].m_sContext; //skip any job that isn't using our match system if( !pMatchSystem->m_Jobs.HasElement( ( uintp )pszJob ) ) continue; if( !ShouldDisplayStats( eDisplay, stats ) ) continue; rp.StrValue( pszJob, CFmtStr( "gcaccess_dump_job \"%s\" %d", pszJob, eDisplay ).String() ); rp.StrValue( pszContext, CFmtStr( "gcaccess_dump_context \"%s\" %d", pszContext, eDisplay ).String() ); rp.StrValue( pSystem->m_sName, CFmtStr( "gcaccess_dump_system \"%s\" %d", pSystem->m_sName.String(), eDisplay ).String() ); rp.IntValue( stats.m_nNumValid ); rp.IntValue( stats.m_nNumFail ); rp.IntValue( stats.m_nNumFailID ); rp.CommitRow(); } } rp.SortReport( "System", false ); rp.PrintReport( SPEW_CONSOLE ); } GC_CON_COMMAND_PARAMS( gcaccess_dump_job, 1, " Dumps all of the accesses associated with the specified job" ) { CGCAccess::EDisplay eDisplay = ( args.ArgC() >= 3 ) ? ( CGCAccess::EDisplay )V_atoi( args[ 2 ] ) : CGCAccess::eDisplay_Referenced; GGCAccess().FullReport( NULL, NULL, args[ 1 ], eDisplay ); } GC_CON_COMMAND_PARAMS( gcaccess_dump_context, 1, " Dumps all of the accesses associated with the specified context" ) { CGCAccess::EDisplay eDisplay = ( args.ArgC() >= 3 ) ? ( CGCAccess::EDisplay )V_atoi( args[ 2 ] ) : CGCAccess::eDisplay_Referenced; GGCAccess().FullReport( NULL, args[ 1 ], NULL, eDisplay ); } GC_CON_COMMAND_PARAMS( gcaccess_dump_system, 1, " Dumps all of the accesses associated with the specified system" ) { CGCAccess::EDisplay eDisplay = ( args.ArgC() >= 3 ) ? ( CGCAccess::EDisplay )V_atoi( args[ 2 ] ) : CGCAccess::eDisplay_Referenced; GGCAccess().FullReport( args[ 1 ], NULL, NULL, eDisplay ); } GC_CON_COMMAND_PARAMS( gcaccess_dump_context_jobs, 1, " Dumps all of the accesses associated with the specified context" ) { CGCAccess::EDisplay eDisplay = ( args.ArgC() >= 3 ) ? ( CGCAccess::EDisplay )V_atoi( args[ 2 ] ) : CGCAccess::eDisplay_Referenced; GGCAccess().ReportJobs( args[ 1 ], eDisplay ); } GC_CON_COMMAND_PARAMS( gcaccess_dump_context_systems, 1, " Dumps all of the accesses associated with the specified context" ) { CGCAccess::EDisplay eDisplay = ( args.ArgC() >= 3 ) ? ( CGCAccess::EDisplay )V_atoi( args[ 2 ] ) : CGCAccess::eDisplay_Referenced; GGCAccess().ReportSystems( args[ 1 ], eDisplay ); } GC_CON_COMMAND_PARAMS( gcaccess_dump_system_dependencies, 1, " This will dump out for all jobs that reference the specified system, what other systems they reference" ) { CGCAccess::EDisplay eDisplay = ( args.ArgC() >= 3 ) ? ( CGCAccess::EDisplay )V_atoi( args[ 2 ] ) : CGCAccess::eDisplay_Referenced; GGCAccess().DependencyReport( args[ 1 ], eDisplay ); } GC_CON_COMMAND( gcaccess_dump_jobs, "Dumps all the jobs that have been tracked with the gcaccess system as well as count of violations" ) { CGCAccess::EDisplay eDisplay = ( args.ArgC() >= 2 ) ? ( CGCAccess::EDisplay )V_atoi( args[ 1 ] ) : CGCAccess::eDisplay_Referenced; GGCAccess().ReportJobs( NULL, eDisplay); } GC_CON_COMMAND( gcaccess_dump_systems, "Dumps all the systems that have been tracked with the gcaccess system as well as count of violations" ) { CGCAccess::EDisplay eDisplay = ( args.ArgC() >= 2 ) ? ( CGCAccess::EDisplay )V_atoi( args[ 1 ] ) : CGCAccess::eDisplay_Referenced; GGCAccess().ReportSystems( NULL, eDisplay ); } GC_CON_COMMAND( gcaccess_dump_full, "Dumps all the information tracked by the gcaccess" ) { CGCAccess::EDisplay eDisplay = ( args.ArgC() >= 2 ) ? ( CGCAccess::EDisplay )V_atoi( args[ 1 ] ) : CGCAccess::eDisplay_Referenced; GGCAccess().FullReport( NULL, NULL, NULL, eDisplay ); } GC_CON_COMMAND_PARAMS( gcaccess_catch_context, 2, " - Catches and generates an assert when the specified context calls into the provided system. This will only generate a single assert" ) { if( !GGCAccess().CatchSingleAssert( args[ 1 ], true, args[ 2 ] ) ) EG_MSG( SPEW_CONSOLE, "Unable to register catch, likely \'%s\' is not a valid system.", args[ 1 ] ); } GC_CON_COMMAND_PARAMS( gcaccess_catch_job, 2, " - Catches and generates an assert when the specified job calls into the provided system. This will only generate a single assert" ) { if( !GGCAccess().CatchSingleAssert( args[ 1 ], false, args[ 2 ] ) ) EG_MSG( SPEW_CONSOLE, "Unable to register catch, likely \'%s\' is not a valid system.", args[ 1 ] ); } GC_CON_COMMAND( gcaccess_catch_clear, "Clears all previously specified catches" ) { GGCAccess().ClearSingleAsserts(); } } //namespace GCSDK