Modified source engine (2017) developed by valve and leaked in 2020. Not for commercial purporses
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

763 lines
20 KiB

//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: Xbox console link
//
//=====================================================================================//
#include "xbox/xbox_console.h"
#include "xbox/xbox_vxconsole.h"
#include "tier0/threadtools.h"
#include "tier0/tslist.h"
#include "tier0/ICommandLine.h"
#include "tier0/memdbgon.h"
// all redirecting funneled here, stop redirecting in this module only
#undef OutputDebugStringA
#pragma comment( lib, "xbdm.lib" )
struct DebugString_t
{
unsigned int color;
char *pString;
};
#define XBX_DBGCOMMANDPREFIX "XCMD"
#define XBX_DBGRESPONSEPREFIX "XACK"
#define XBX_DBGPRINTPREFIX "XPRT"
#define XBX_DBGCOLORPREFIX "XCLR"
#define XBX_MAX_RCMDLENGTH 256
#define XBX_MAX_MESSAGE 2048
CThreadFastMutex g_xbx_dbgChannelMutex;
CThreadFastMutex g_xbx_dbgCommandHandlerMutex;
static char g_xbx_dbgRemoteBuf[XBX_MAX_RCMDLENGTH];
static HANDLE g_xbx_dbgValidEvent;
static HANDLE g_xbx_dbgCmdCompleteEvent;
bool g_xbx_bUseVXConsoleOutput = true;
bool g_xbx_bDoSyncOutput;
static ThreadHandle_t g_xbx_hDebugThread;
CTSQueue<DebugString_t> g_xbx_DebugStringQueue;
extern CInterlockedInt g_xbx_numProfileCounters;
extern unsigned int g_xbx_profileCounters[];
extern char g_xbx_profileName[];
int g_xbx_freeMemory;
_inline bool XBX_NoXBDM() { return false; }
static CXboxConsole XboxConsole;
DLL_EXPORT IXboxConsole *GetConsoleInterface()
{
return &XboxConsole;
}
//-----------------------------------------------------------------------------
// Low level string output.
// Input string should be stack based, can get clobbered.
//-----------------------------------------------------------------------------
static void OutputStringToDevice( unsigned int color, char *pString, bool bRemoteValid )
{
if ( !bRemoteValid )
{
// local debug only
OutputDebugStringA( pString );
return;
}
// remote debug valid
// non pure colors don't translate well - find closest pure hue
unsigned int bestColor = color;
int r = ( bestColor & 0xFF );
int g = ( bestColor >> 8 ) & 0xFF;
int b = ( bestColor >> 16 ) & 0xFF;
if ( ( r && r != 255 ) || ( g && g != 255 ) || ( b && b != 255 ) )
{
int r0, g0, b0;
unsigned int minDist = 0xFFFFFFFF;
for ( int i=0; i<8; i++ )
{
r0 = g0 = b0 = 0;
if ( i&4 )
r0 = 255;
if ( i&2 )
g0 = 255;
if ( i&1 )
b0 = 255;
unsigned int d = ( r-r0 )*( r-r0 ) + ( g-g0 )*( g-g0 ) + ( b-b0 )*( b-b0 );
if ( minDist > d )
{
minDist = d;
bestColor = XMAKECOLOR( r0, g0, b0 );
}
}
}
// create color string
char colorString[16];
sprintf( colorString, XBX_DBGCOLORPREFIX "[%8.8x]", bestColor );
// chunk line out, for each cr
char strBuffer[XBX_MAX_RCMDLENGTH];
char *pStart = pString;
char *pEnd = pStart + strlen( pStart );
char *pNext;
while ( pStart < pEnd )
{
pNext = strchr( pStart, '\n' );
if ( !pNext )
pNext = pEnd;
else
*pNext = '\0';
int length = _snprintf( strBuffer, XBX_MAX_RCMDLENGTH, "%s!%s%s", XBX_DBGPRINTPREFIX, colorString, pStart );
if ( length == -1 )
{
strBuffer[sizeof( strBuffer )-1] = '\0';
}
// Send the string
DmSendNotificationString( strBuffer );
// advance past cr
pStart = pNext+1;
}
}
//-----------------------------------------------------------------------------
// XBX_IsConsoleConnected
//
//-----------------------------------------------------------------------------
bool CXboxConsole::IsConsoleConnected()
{
bool bConnected;
if ( g_xbx_dbgValidEvent == NULL )
{
// init was never called
return false;
}
AUTO_LOCK_FM( g_xbx_dbgChannelMutex );
bConnected = ( WaitForSingleObject( g_xbx_dbgValidEvent, 0 ) == WAIT_OBJECT_0 );
return bConnected;
}
//-----------------------------------------------------------------------------
// Output string to listening console. Queues output for slave thread.
// Needs to be lightweight.
//-----------------------------------------------------------------------------
void CXboxConsole::DebugString( unsigned int color, const char* pFormat, ... )
{
if ( XBX_NoXBDM() )
return;
va_list args;
char szStringBuffer[XBX_MAX_MESSAGE];
int length;
// resolve string
va_start( args, pFormat );
length = _vsnprintf( szStringBuffer, sizeof( szStringBuffer ), pFormat, args );
if ( length == -1 )
{
szStringBuffer[sizeof( szStringBuffer ) - 1] = '\0';
}
va_end( args );
if ( !g_xbx_bDoSyncOutput )
{
// queue string for delayed output
DebugString_t debugString;
debugString.color = color;
debugString.pString = strdup( szStringBuffer );
g_xbx_DebugStringQueue.PushItem( debugString );
}
else
{
bool bRemoteValid = g_xbx_bUseVXConsoleOutput && XBX_IsConsoleConnected();
OutputStringToDevice( color, szStringBuffer, bRemoteValid );
}
}
//-----------------------------------------------------------------------------
// Waits for debug queue to drain.
//-----------------------------------------------------------------------------
void CXboxConsole::FlushDebugOutput()
{
while ( g_xbx_DebugStringQueue.Count() != 0 )
{
Sleep( 1 );
}
}
//-----------------------------------------------------------------------------
// _xdbg_strlen
//
// Critical section safe.
//-----------------------------------------------------------------------------
int _xdbg_strlen( const CHAR* str )
{
const CHAR* strEnd = str;
while( *strEnd )
strEnd++;
return strEnd - str;
}
//-----------------------------------------------------------------------------
// _xdbg_tolower
//
// Critical section safe.
//-----------------------------------------------------------------------------
inline CHAR _xdbg_tolower( CHAR ch )
{
if( ch >= 'A' && ch <= 'Z' )
return ch - ( 'A' - 'a' );
else
return ch;
}
//-----------------------------------------------------------------------------
// _xdbg_strnicmp
//
// Critical section safe.
//-----------------------------------------------------------------------------
BOOL _xdbg_strnicmp( const CHAR* str1, const CHAR* str2, int n )
{
while ( ( _xdbg_tolower( *str1 ) == _xdbg_tolower( *str2 ) ) && *str1 && n > 0 )
{
--n;
++str1;
++str2;
}
return ( !n || _xdbg_tolower( *str1 ) == _xdbg_tolower( *str2 ) );
}
//-----------------------------------------------------------------------------
// _xdbg_strcpy
//
// Critical section safe.
//-----------------------------------------------------------------------------
VOID _xdbg_strcpy( CHAR* strDest, const CHAR* strSrc )
{
while ( ( *strDest++ = *strSrc++ ) != 0 );
}
//-----------------------------------------------------------------------------
// _xdbg_strcpyn
//
// Critical section safe.
//-----------------------------------------------------------------------------
VOID _xdbg_strcpyn( CHAR* strDest, const CHAR* strSrc, int numChars )
{
while ( numChars>0 && ( *strDest++ = *strSrc++ ) != 0 )
numChars--;
}
//-----------------------------------------------------------------------------
// _xdbg_gettoken
//
// Critical section safe.
//-----------------------------------------------------------------------------
void _xdbg_gettoken( CHAR** tokenStream, CHAR* token, int tokenSize )
{
int c;
int len;
CHAR* data;
len = 0;
// skip prefix whitespace
data = *tokenStream;
while ( ( c = *data ) <= ' ' )
{
if ( !c )
goto cleanUp;
data++;
}
// parse a token
do
{
if ( len < tokenSize )
token[len++] = c;
data++;
c = *data;
} while ( c > ' ' );
if ( len >= tokenSize )
len = 0;
cleanUp:
token[len] = '\0';
*tokenStream = data;
}
//-----------------------------------------------------------------------------
// _xdbg_tokenize
//
// Critical section safe.
//-----------------------------------------------------------------------------
int _xdbg_tokenize( CHAR* tokenStream, CHAR** tokens, int maxTokens )
{
char token[64];
// tokenize stream into seperate tokens
int numTokens = 0;
while ( 1 )
{
tokens[numTokens++] = tokenStream;
if ( numTokens >= maxTokens )
break;
_xdbg_gettoken( &tokenStream, token, sizeof( token ) );
if ( !tokenStream[0] || !token[0] )
break;
*tokenStream = '\0';
tokenStream++;
}
return ( numTokens );
}
//-----------------------------------------------------------------------------
// _xdbg_findtoken
//
// Critical section safe. Returns -1 if not found
//-----------------------------------------------------------------------------
int _xdbg_findtoken( CHAR** tokens, int numTokens, CHAR* token )
{
int i;
int len;
len = _xdbg_strlen( token );
for ( i=0; i<numTokens; i++ )
{
if ( _xdbg_strnicmp( tokens[i], token, len ) )
return i;
}
// not found
return -1;
}
//-----------------------------------------------------------------------------
// _DebugCommandHandler
//
//-----------------------------------------------------------------------------
HRESULT __stdcall _DebugCommandHandler( const CHAR* strCommand, CHAR* strResponse, DWORD dwResponseLen, PDM_CMDCONT pdmcc )
{
CHAR buff[256];
CHAR* args[8];
int numArgs;
AUTO_LOCK_FM( g_xbx_dbgCommandHandlerMutex );
// skip over the command prefix and the exclamation mark
strCommand += _xdbg_strlen( XBX_DBGCOMMANDPREFIX ) + 1;
if ( strCommand[0] == '\0' )
{
// just a ping
goto cleanUp;
}
// get command and optional arguments
_xdbg_strcpyn( buff, strCommand, sizeof( buff ) );
numArgs = _xdbg_tokenize( buff, args, sizeof( args )/sizeof( CHAR* ) );
if ( _xdbg_strnicmp( args[0], "__connect__", 11 ) )
{
if ( numArgs > 1 && atoi( args[1] ) == VXCONSOLE_PROTOCOL_VERSION )
{
// initial connect - respond that we're connected
_xdbg_strcpyn( strResponse, XBX_DBGRESPONSEPREFIX "Connected To Application.", dwResponseLen );
SetEvent( g_xbx_dbgValidEvent );
// notify convar system to send its commands
// allows vxconsole to re-connect during game
_xdbg_strcpy( g_xbx_dbgRemoteBuf, "getcvars" );
XBX_QueueEvent( XEV_REMOTECMD, ( int )g_xbx_dbgRemoteBuf, 0, 0 );
}
else
{
_xdbg_strcpyn( strResponse, XBX_DBGRESPONSEPREFIX "Rejecting Connection: Wrong Protocol Version.", dwResponseLen );
}
goto cleanUp;
}
if ( _xdbg_strnicmp( args[0], "__disconnect__", 14 ) )
{
// respond that we're disconnected
_xdbg_strcpyn( strResponse, XBX_DBGRESPONSEPREFIX "Disconnected.", dwResponseLen );
ResetEvent( g_xbx_dbgValidEvent );
goto cleanUp;
}
if ( _xdbg_strnicmp( args[0], "__complete__", 12 ) )
{
// remote server has finished command - respond to acknowledge
_xdbg_strcpyn( strResponse, XBX_DBGRESPONSEPREFIX "OK", dwResponseLen );
// set the complete event - allows expected synchronous calling mechanism
SetEvent( g_xbx_dbgCmdCompleteEvent );
goto cleanUp;
}
if ( _xdbg_strnicmp( args[0], "__memory__", 10 ) )
{
// get a current stat of available memory
MEMORYSTATUS stat;
GlobalMemoryStatus( &stat );
g_xbx_freeMemory = stat.dwAvailPhys;
if ( _xdbg_findtoken( args, numArgs, "quiet" ) > 0 )
{
_xdbg_strcpyn( strResponse, XBX_DBGRESPONSEPREFIX "OK", dwResponseLen );
}
else
{
// 32 MB is reserved and fixed by OS, so not reporting
_snprintf( strResponse, dwResponseLen, XBX_DBGRESPONSEPREFIX "Available: %.2f MB, Used: %.2f MB, Free: %.2f MB",
stat.dwTotalPhys/( 1024.0f*1024.0f ) - 32.0f,
( stat.dwTotalPhys - stat.dwAvailPhys )/( 1024.0f*1024.0f ) - 32.0f,
stat.dwAvailPhys/( 1024.0f*1024.0f ) );
}
goto cleanUp;
}
if ( g_xbx_dbgRemoteBuf[0] )
{
// previous command still pending
_xdbg_strcpyn( strResponse, XBX_DBGRESPONSEPREFIX "Cannot execute: Previous command still pending", dwResponseLen );
}
else
{
// add the command to the event queue to be processed by main app
_xdbg_strcpy( g_xbx_dbgRemoteBuf, strCommand );
XBX_QueueEvent( XEV_REMOTECMD, ( int )g_xbx_dbgRemoteBuf, 0, 0 );
_xdbg_strcpyn( strResponse, XBX_DBGRESPONSEPREFIX "OK", dwResponseLen );
}
cleanUp:
return XBDM_NOERR;
}
//-----------------------------------------------------------------------------
// XBX_SendRemoteCommand
//
//-----------------------------------------------------------------------------
void CXboxConsole::SendRemoteCommand( const char *pCommand, bool async )
{
char cmdString[XBX_MAX_RCMDLENGTH];
if ( XBX_NoXBDM() || !IsConsoleConnected() )
return;
AUTO_LOCK_FM( g_xbx_dbgChannelMutex );
_snprintf( cmdString, sizeof( cmdString ), "%s!%s", XBX_DBGCOMMANDPREFIX, pCommand );
HRESULT hr = DmSendNotificationString( cmdString );
if ( FAILED( hr ) )
{
XBX_Error( "XBX_SendRemoteCommand: failed on %s", cmdString );
}
// wait for command completion
if ( !async )
{
DWORD timeout;
if ( !strnicmp( pCommand, "Assert()", 8 ) )
{
// the assert is waiting for user to make selection
timeout = INFINITE;
}
else
{
// no vxconsole operation should take this long
timeout = 15000;
}
if ( WaitForSingleObject( g_xbx_dbgCmdCompleteEvent, timeout ) == WAIT_TIMEOUT )
{
// we have no choice but to dump core
DmCrashDump( false );
}
}
}
//-----------------------------------------------------------------------------
// Handle delayed VXConsole transactions
//
//-----------------------------------------------------------------------------
static unsigned _DebugThreadFunc( void *pParam )
{
while ( 1 )
{
Sleep( 10 );
if ( !g_xbx_DebugStringQueue.Count() && !g_xbx_numProfileCounters && !g_xbx_freeMemory )
{
continue;
}
if ( g_xbx_numProfileCounters )
{
// build and send asynchronously
char dbgCommand[XBX_MAX_RCMDLENGTH];
_snprintf( dbgCommand, sizeof( dbgCommand ), "SetProfileData() %s 0x%8.8x", g_xbx_profileName, g_xbx_profileCounters );
XBX_SendRemoteCommand( dbgCommand, true );
// mark as sent
g_xbx_numProfileCounters = 0;
}
if ( g_xbx_freeMemory )
{
// build and send asynchronously
char dbgCommand[XBX_MAX_RCMDLENGTH];
_snprintf( dbgCommand, sizeof( dbgCommand ), "FreeMemory() 0x%8.8x", g_xbx_freeMemory );
XBX_SendRemoteCommand( dbgCommand, true );
// mark as sent
g_xbx_freeMemory = 0;
}
bool bRemoteValid = g_xbx_bUseVXConsoleOutput && XBX_IsConsoleConnected();
while ( 1 )
{
DebugString_t debugString;
if ( !g_xbx_DebugStringQueue.PopItem( &debugString ) )
{
break;
}
OutputStringToDevice( debugString.color, debugString.pString, bRemoteValid );
free( debugString.pString );
}
}
return 0;
}
//-----------------------------------------------------------------------------
// XBX_InitConsoleMonitor
//
//-----------------------------------------------------------------------------
void CXboxConsole::InitConsoleMonitor( bool bWaitForConnect )
{
if ( XBX_NoXBDM() )
return;
// create our events
g_xbx_dbgValidEvent = CreateEvent( XBOX_DONTCARE, TRUE, FALSE, NULL );
g_xbx_dbgCmdCompleteEvent = CreateEvent( XBOX_DONTCARE, FALSE, FALSE, NULL );
// register our command handler with the debug monitor
HRESULT hr = DmRegisterCommandProcessor( XBX_DBGCOMMANDPREFIX, _DebugCommandHandler );
if ( FAILED( hr ) )
{
XBX_Error( "XBX_InitConsoleMonitor: failed to register command processor" );
}
// user can have output bypass slave thread
g_xbx_bDoSyncOutput = CommandLine()->FindParm( "-syncoutput" ) != 0;
// create a slave thread to do delayed VXConsole transactions
ThreadId_t threadID;
g_xbx_hDebugThread = CreateSimpleThread( _DebugThreadFunc, NULL, &threadID, 16*1024 );
ThreadSetDebugName( threadID, "DebugThread" );
ThreadSetAffinity( g_xbx_hDebugThread, XBOX_PROCESSOR_5 );
if ( bWaitForConnect )
{
XBX_DebugString( XBX_CLR_DEFAULT, "Waiting For VXConsole Connection...\n" );
WaitForSingleObject( g_xbx_dbgValidEvent, INFINITE );
}
}
//-----------------------------------------------------------------------------
// Sends a disconnect signal to possibly attached VXConsole.
//-----------------------------------------------------------------------------
void CXboxConsole::DisconnectConsoleMonitor()
{
if ( XBX_NoXBDM() )
return;
// caller is trying to safely stop vxconsole traffic, disconnect must be synchronous
XBX_SendRemoteCommand( "Disconnect()", false );
}
bool CXboxConsole::GetXboxName( char *pName, unsigned *pLength )
{
return ( DmGetXboxName( pName, (DWORD *)pLength ) == XBDM_NOERR );
}
void CXboxConsole::CrashDump( bool b )
{
DmCrashDump(b);
}
//-----------------------------------------------------------------------------
// Walk to a specific module and dump size info
//-----------------------------------------------------------------------------
int CXboxConsole::DumpModuleSize( const char *pName )
{
HRESULT error;
PDM_WALK_MODULES pWalkMod = NULL;
DMN_MODLOAD modLoad;
int size = 0;
// iterate and find match
do
{
error = DmWalkLoadedModules( &pWalkMod, &modLoad );
if ( XBDM_NOERR == error && !stricmp( modLoad.Name, pName ) )
{
Msg( "0x%8.8x, %5.2f MB, %s\n", modLoad.BaseAddress, modLoad.Size/( 1024.0f*1024.0f ), modLoad.Name );
size = modLoad.Size;
error = XBDM_ENDOFLIST;
}
}
while ( XBDM_NOERR == error );
DmCloseLoadedModules( pWalkMod );
if ( error != XBDM_ENDOFLIST )
{
Warning( "DmWalkLoadedModules() failed.\n" );
}
return size;
}
//-----------------------------------------------------------------------------
// 360 spew sizes of dll modules
//-----------------------------------------------------------------------------
char const* HACK_stristr( char const* pStr, char const* pSearch ) // hack because moved code from above vstdlib
{
AssertValidStringPtr(pStr);
AssertValidStringPtr(pSearch);
if (!pStr || !pSearch)
return 0;
char const* pLetter = pStr;
// Check the entire string
while (*pLetter != 0)
{
// Skip over non-matches
if (tolower((unsigned char)*pLetter) == tolower((unsigned char)*pSearch))
{
// Check for match
char const* pMatch = pLetter + 1;
char const* pTest = pSearch + 1;
while (*pTest != 0)
{
// We've run off the end; don't bother.
if (*pMatch == 0)
return 0;
if (tolower((unsigned char)*pMatch) != tolower((unsigned char)*pTest))
break;
++pMatch;
++pTest;
}
// Found a match!
if (*pTest == 0)
return pLetter;
}
++pLetter;
}
return 0;
}
void CXboxConsole::DumpDllInfo( const char *pBasePath )
{
// Directories containing dlls
static char *dllDirs[] =
{
"bin",
"hl2\\bin",
"tf\\bin",
"portal\\bin",
"episodic\\bin"
};
char binPath[MAX_PATH];
char dllPath[MAX_PATH];
char searchPath[MAX_PATH];
HMODULE hModule;
WIN32_FIND_DATA wfd;
HANDLE hFind;
Msg( "Dumping Module Sizes...\n" );
for ( int i = 0; i < ARRAYSIZE( dllDirs ); ++i )
{
int totalSize = 0;
_snprintf( binPath, sizeof( binPath ), "%s\\%s", pBasePath, dllDirs[i] );
_snprintf( searchPath, sizeof( binPath ), "%s\\*.dll", binPath );
// show the directory we're searching
Msg( "\nDirectory: %s\n\n", binPath );
// Start the find and check for failure.
hFind = FindFirstFile( searchPath, &wfd );
if ( INVALID_HANDLE_VALUE == hFind )
{
Warning( "No Files Found.\n" );
}
else
{
// Load and unload each dll individually.
do
{
if ( !HACK_stristr( wfd.cFileName, "_360.dll" ) )
{
// exclude explicit pc dlls
// FindFirstFile does not support a spec mask of *_360.dll on the Xbox HDD
continue;
}
_snprintf( dllPath, sizeof( dllPath ), "%s\\%s", binPath, wfd.cFileName );
hModule = LoadLibrary( dllPath );
if ( hModule )
{
totalSize += DumpModuleSize( wfd.cFileName );
FreeLibrary( hModule );
}
else
{
Warning( "Failed to load: %s\n", dllPath );
}
}
while( FindNextFile( hFind, &wfd ) );
FindClose( hFind );
Msg( "Total Size: %.2f MB\n", totalSize/( 1024.0f*1024.0f ) );
}
}
}
void CXboxConsole::OutputDebugString( const char *p )
{
::OutputDebugStringA( p );
}
bool CXboxConsole::IsDebuggerPresent()
{
return ( DmIsDebuggerPresent() != 0 );
}