source-engine/tier0/vcrmode_posix.cpp

971 lines
19 KiB
C++
Raw Normal View History

2020-04-22 16:56:21 +00:00
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//
//=============================================================================//
#include <time.h>
#include <assert.h>
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include "tier0/threadtools.h"
#define PROTECTED_THINGS_DISABLE
#include "tier0/vcrmode.h"
#include "tier0/dbg.h"
#include "extendedtrace.h"
// FIXME: We totally have a bad tier dependency here
#include "inputsystem/InputEnums.h"
#define VCRFILE_VERSION 2
#define VCR_RuntimeAssert(x) VCR_RuntimeAssertFn(x, #x)
#define PvAlloc malloc
bool g_bExpectingWindowProcCalls = false;
IVCRHelpers *g_pHelpers = 0;
FILE *g_pVCRFile = NULL;
VCRMode_t g_VCRMode = VCR_Disabled;
VCRMode_t g_OldVCRMode = (VCRMode_t)-1; // Stored temporarily between SetEnabled(0)/SetEnabled(1) blocks.
int g_iCurEvent = 0;
int g_CurFilePos = 0; // So it knows when we're done playing back.
int g_FileLen = 0;
VCREvent g_LastReadEvent = (VCREvent)-1; // Last VCR_ReadEvent() call.
int g_bVCREnabled = 0;
// ---------------------------------------------------------------------- //
// Internal functions.
// ---------------------------------------------------------------------- //
static void VCR_Error( const char *pFormat, ... )
{
#if defined( _DEBUG )
DebuggerBreak();
#endif
char str[256];
va_list marker;
va_start( marker, pFormat );
_snprintf( str, sizeof( str ), pFormat, marker );
va_end( marker );
g_pHelpers->ErrorMessage( str );
VCREnd();
}
static void VCR_RuntimeAssertFn(int bAssert, char const *pStr)
{
if(!bAssert)
{
VCR_Error( "*** VCR ASSERT FAILED: %s ***\n", pStr );
}
}
static void VCR_Read(void *pDest, int size)
{
if(!g_pVCRFile)
{
memset(pDest, 0, size);
return;
}
fread(pDest, 1, size, g_pVCRFile);
g_CurFilePos += size;
VCR_RuntimeAssert(g_CurFilePos <= g_FileLen);
if(g_CurFilePos >= g_FileLen)
{
VCREnd();
}
}
template<class T>
static void VCR_ReadVal(T &val)
{
VCR_Read(&val, sizeof(val));
}
static void VCR_Write(void const *pSrc, int size)
{
fwrite(pSrc, 1, size, g_pVCRFile);
fflush(g_pVCRFile);
}
template<class T>
static void VCR_WriteVal(T &val)
{
2021-11-16 20:46:29 +00:00
VCR_Write((void const*)&val, sizeof(val));
2020-04-22 16:56:21 +00:00
}
// Hook from ExtendedTrace.cpp
bool g_bTraceRead = false;
void OutputDebugStringFormat( const char *pMsg, ... )
{
char msg[4096];
va_list marker;
va_start( marker, pMsg );
_vsnprintf( msg, sizeof( msg )-1, pMsg, marker );
va_end( marker );
int len = strlen( msg );
if ( g_bTraceRead )
{
char tempData[4096];
2021-11-16 20:46:29 +00:00
uint tempLen;
2020-04-22 16:56:21 +00:00
VCR_ReadVal( tempLen );
VCR_RuntimeAssert( tempLen <= sizeof( tempData ) );
VCR_Read( tempData, tempLen );
tempData[tempLen] = 0;
fprintf( stderr, "FILE: " );
fprintf( stderr, "%s", tempData );
VCR_RuntimeAssert( memcmp( msg, tempData, len ) == 0 );
}
else
{
VCR_WriteVal( len );
VCR_Write( msg, len );
}
}
static VCREvent VCR_ReadEvent()
{
g_bTraceRead = true;
//STACKTRACE();
char event;
VCR_Read(&event, 1);
g_LastReadEvent = (VCREvent)event;
return (VCREvent)event;
}
static void VCR_WriteEvent(VCREvent event)
{
g_bTraceRead = false;
//STACKTRACE();
// Write a stack trace.
char cEvent = (char)event;
VCR_Write(&cEvent, 1);
}
static void VCR_IncrementEvent()
{
++g_iCurEvent;
}
static void VCR_Event(VCREvent type)
{
if(g_VCRMode == VCR_Disabled)
return;
VCR_IncrementEvent();
if(g_VCRMode == VCR_Record)
{
VCR_WriteEvent(type);
}
else
{
VCREvent currentEvent = VCR_ReadEvent();
VCR_RuntimeAssert( currentEvent == type );
}
}
// ---------------------------------------------------------------------- //
// VCR trace interface.
// ---------------------------------------------------------------------- //
class CVCRTrace : public IVCRTrace
{
public:
virtual VCREvent ReadEvent()
{
return VCR_ReadEvent();
}
virtual void Read( void *pDest, int size )
{
VCR_Read( pDest, size );
}
};
static CVCRTrace g_VCRTrace;
// ---------------------------------------------------------------------- //
// VCR interface.
// ---------------------------------------------------------------------- //
static int VCR_Start( char const *pFilename, bool bRecord, IVCRHelpers *pHelpers )
{
unsigned long version;
g_pHelpers = pHelpers;
VCREnd();
EXTENDEDTRACEINITIALIZE( "/tmp/hl2" );
g_OldVCRMode = (VCRMode_t)-1;
if(bRecord)
{
g_pVCRFile = fopen( pFilename, "wb" );
if( g_pVCRFile )
{
// Write the version.
version = VCRFILE_VERSION;
VCR_Write(&version, sizeof(version));
g_VCRMode = VCR_Record;
return true;
}
else
{
return false;
}
}
else
{
g_pVCRFile = fopen( pFilename, "rb" );
if( g_pVCRFile )
{
// Get the file length.
fseek(g_pVCRFile, 0, SEEK_END);
g_FileLen = ftell(g_pVCRFile);
fseek(g_pVCRFile, 0, SEEK_SET);
g_CurFilePos = 0;
// Verify the file version.
VCR_Read(&version, sizeof(version));
if(version != VCRFILE_VERSION)
{
assert(!"VCR_Start: invalid file version");
VCREnd();
return FALSE;
}
g_VCRMode = VCR_Playback;
return TRUE;
}
else
{
return FALSE;
}
}
}
static void VCR_End()
{
if(g_pVCRFile)
{
fclose(g_pVCRFile);
g_pVCRFile = NULL;
}
g_VCRMode = VCR_Disabled;
EXTENDEDTRACEUNINITIALIZE();
}
static IVCRTrace* VCR_GetVCRTraceInterface()
{
return &g_VCRTrace;
}
static VCRMode_t VCR_GetMode()
{
return g_VCRMode;
}
static void VCR_SetEnabled(int bEnabled)
{
if(bEnabled)
{
VCR_RuntimeAssert(g_OldVCRMode != (VCRMode_t)-1);
g_VCRMode = g_OldVCRMode;
g_OldVCRMode = (VCRMode_t)-1;
}
else
{
VCR_RuntimeAssert(g_OldVCRMode == (VCRMode_t)-1);
g_OldVCRMode = g_VCRMode;
g_VCRMode = VCR_Disabled;
}
}
static void VCR_SyncToken(char const *pToken)
{
unsigned char len;
VCR_Event(VCREvent_SyncToken);
if(g_VCRMode == VCR_Record)
{
int intLen = strlen( pToken );
assert( intLen <= 255 );
len = (unsigned char)intLen;
VCR_Write(&len, 1);
VCR_Write(pToken, len);
}
else if(g_VCRMode == VCR_Playback)
{
char test[256];
VCR_Read(&len, 1);
VCR_Read(test, len);
VCR_RuntimeAssert( len == (unsigned char)strlen(pToken) );
VCR_RuntimeAssert( memcmp(pToken, test, len) == 0 );
}
}
static double VCR_Hook_Sys_FloatTime(double time)
{
VCR_Event(VCREvent_Sys_FloatTime);
if(g_VCRMode == VCR_Record)
{
VCR_Write(&time, sizeof(time));
}
else if(g_VCRMode == VCR_Playback)
{
VCR_Read(&time, sizeof(time));
}
return time;
}
static int VCR_Hook_PeekMessage(
struct tagMSG *msg,
void *hWnd,
unsigned int wMsgFilterMin,
unsigned int wMsgFilterMax,
unsigned int wRemoveMsg
)
{
Assert( "VCR_Hook_PeekMessage unsupported" );
return 0;
}
void VCR_Hook_RecordGameMsg( const InputEvent_t& event )
{
if ( g_VCRMode == VCR_Record )
{
VCR_Event( VCREvent_GameMsg );
char val = 1;
VCR_WriteVal( val );
VCR_WriteVal( event.m_nType );
VCR_WriteVal( event.m_nData );
VCR_WriteVal( event.m_nData2 );
VCR_WriteVal( event.m_nData3 );
}
}
void VCR_Hook_RecordEndGameMsg()
{
if ( g_VCRMode == VCR_Record )
{
VCR_Event( VCREvent_GameMsg );
char val = 0;
VCR_WriteVal( val ); // record that there are no more messages.
}
}
bool VCR_Hook_PlaybackGameMsg( InputEvent_t* pEvent )
{
if ( g_VCRMode == VCR_Playback )
{
VCR_Event( VCREvent_GameMsg );
char bMsg;
VCR_ReadVal( bMsg );
if ( bMsg )
{
VCR_ReadVal( pEvent->m_nType );
VCR_ReadVal( pEvent->m_nData );
VCR_ReadVal( pEvent->m_nData2 );
VCR_ReadVal( pEvent->m_nData3 );
return true;
}
}
return false;
}
static void VCR_Hook_GetCursorPos(struct tagPOINT *pt)
{
Assert( "VCR_Hook_GetCursorPos unsupported" );
}
static void VCR_Hook_ScreenToClient(void *hWnd, struct tagPOINT *pt)
{
Assert( "VCR_Hook_GetCursorPos unsupported" );
}
static int VCR_Hook_recvfrom(int s, char *buf, int len, int flags, struct sockaddr *from, int *fromlen)
{
VCR_Event(VCREvent_recvfrom);
int ret;
if ( g_VCRMode == VCR_Playback )
{
// Get the result from our file.
VCR_Read(&ret, sizeof(ret));
if(ret == -1)
{
int err;
VCR_ReadVal(err);
errno = err;
}
else
{
VCR_Read( buf, ret );
char bFrom;
VCR_ReadVal( bFrom );
if ( bFrom )
{
VCR_Read( from, *fromlen );
}
}
}
else
{
ret = recvfrom(s, buf, len, flags, from, (socklen_t *)(fromlen));
if ( g_VCRMode == VCR_Record )
{
// Record the result.
VCR_Write(&ret, sizeof(ret));
if(ret == -1)
{
VCR_WriteVal(errno);
}
else
{
VCR_Write( buf, ret );
char bFrom = !!from;
VCR_WriteVal( bFrom );
if ( bFrom )
VCR_Write( from, *fromlen );
}
}
}
return ret;
}
static void VCR_Hook_Cmd_Exec(char **f)
{
VCR_Event(VCREvent_Cmd_Exec);
if(g_VCRMode == VCR_Playback)
{
int len;
VCR_Read(&len, sizeof(len));
if(len == -1)
{
*f = NULL;
}
else
{
*f = (char*)PvAlloc(len);
VCR_Read(*f, len);
}
}
else if(g_VCRMode == VCR_Record)
{
int len;
char *str = *f;
if(str)
{
len = strlen(str)+1;
VCR_Write(&len, sizeof(len));
VCR_Write(str, len);
}
else
{
len = -1;
VCR_Write(&len, sizeof(len));
}
}
}
#define MAX_LINUX_CMDLINE 512
static char linuxCmdline[ MAX_LINUX_CMDLINE +7 ]; // room for -steam
const char * BuildCmdLine( int argc, char **argv, bool fAddSteam )
{
int len;
int i;
for (len = 0, i = 0; i < argc; i++)
{
len += strlen(argv[i]);
}
if ( len > MAX_LINUX_CMDLINE )
{
printf( "command line too long, %i max\n", MAX_LINUX_CMDLINE );
exit(-1);
return "";
}
linuxCmdline[0] = '\0';
for ( i = 0; i < argc; i++ )
{
if ( i > 0 )
{
strcat( linuxCmdline, " " );
}
strcat( linuxCmdline, argv[ i ] );
}
if( fAddSteam )
{
strcat( linuxCmdline, " -steam" );
}
return linuxCmdline;
}
char *GetCommandLine()
{
return linuxCmdline;
}
static char* VCR_Hook_GetCommandLine()
{
VCR_Event(VCREvent_CmdLine);
int len;
char *ret;
if(g_VCRMode == VCR_Playback)
{
VCR_Read(&len, sizeof(len));
ret = new char[len];
VCR_Read(ret, len);
}
else
{
ret = GetCommandLine();
if(g_VCRMode == VCR_Record)
{
len = strlen(ret) + 1;
VCR_WriteVal(len);
VCR_Write(ret, len);
}
}
return ret;
}
static long VCR_Hook_RegOpenKeyEx( void *hKey, const char *lpSubKey, unsigned long ulOptions, unsigned long samDesired, void *pHKey )
{
Assert( "VCR_Hook_RegOpenKeyEx unsupported" );
return 0;
}
static long VCR_Hook_RegSetValueEx(void *hKey, char const *lpValueName, unsigned long Reserved, unsigned long dwType, unsigned char const *lpData, unsigned long cbData)
{
Assert( "VCR_Hook_RegSetValueEx unsupported" );
return 0;
}
static long VCR_Hook_RegQueryValueEx(void *hKey, char const *lpValueName, unsigned long *lpReserved, unsigned long *lpType, unsigned char *lpData, unsigned long *lpcbData)
{
Assert( "VCR_Hook_RegQueryValueEx unsupported" );
return 0;
}
static long VCR_Hook_RegCreateKeyEx(void *hKey, char const *lpSubKey, unsigned long Reserved, char *lpClass, unsigned long dwOptions,
unsigned long samDesired, void *lpSecurityAttributes, void *phkResult, unsigned long *lpdwDisposition)
{
Assert( "VCR_Hook_RegCreateKeyEx unsupported" );
return 0;
}
static void VCR_Hook_RegCloseKey(void *hKey)
{
Assert( "VCR_Hook_RegCloseKey unsupported" );
}
int VCR_Hook_GetNumberOfConsoleInputEvents( void *hInput, unsigned long *pNumEvents )
{
VCR_Event( VCREvent_GetNumberOfConsoleInputEvents );
char ret;
if ( g_VCRMode == VCR_Playback )
{
VCR_ReadVal( ret );
VCR_ReadVal( *pNumEvents );
}
else
{
ret = 1;
if ( g_VCRMode == VCR_Record )
{
VCR_WriteVal( ret );
VCR_WriteVal( *pNumEvents );
}
}
return ret;
}
int VCR_Hook_ReadConsoleInput( void *hInput, void *pRecs, int nMaxRecs, unsigned long *pNumRead )
{
Assert( "VCR_Hook_ReadConsoleInput unsupported" );
return 0;
}
void VCR_Hook_LocalTime( struct tm *today )
{
// We just provide a wrapper on this function so we can protect access to time() everywhere.
time_t ltime;
time( &ltime );
tm *pTime = localtime( &ltime );
memcpy( today, pTime, sizeof( *today ) );
}
short VCR_Hook_GetKeyState( int nVirtKey )
{
Assert( "VCREvent_GetKeyState unsupported" );
return 0;
}
void VCR_GenericRecord( const char *pEventName, const void *pData, int len )
{
VCR_Event( VCREvent_Generic );
if ( g_VCRMode != VCR_Record )
Error( "VCR_GenericRecord( %s ): not recording a VCR file", pEventName );
// Write the event name (or 255 if none).
int nameLen = 255;
if ( pEventName )
{
nameLen = strlen( pEventName ) + 1;
if ( nameLen >= 255 )
{
VCR_Error( "VCR_GenericRecord( %s ): nameLen too long (%d)", pEventName, nameLen );
return;
}
}
unsigned char ucNameLen = (unsigned char)nameLen;
VCR_WriteVal( ucNameLen );
VCR_Write( pEventName, ucNameLen );
// Write the data.
VCR_WriteVal( len );
VCR_Write( pData, len );
}
int VCR_GenericPlayback( const char *pEventName, void *pOutData, int maxLen, bool bForceSameLen )
{
VCR_Event( VCREvent_Generic );
if ( g_VCRMode != VCR_Playback )
Error( "VCR_Playback( %s ): not playing back a VCR file", pEventName );
unsigned char nameLen;
VCR_ReadVal( nameLen );
if ( nameLen != 255 )
{
char testName[512];
VCR_Read( testName, nameLen );
if ( strcmp( pEventName, testName ) != 0 )
{
VCR_Error( "VCR_GenericPlayback( %s ) - event name does not match '%s'", pEventName, testName );
return 0;
}
}
int dataLen;
VCR_ReadVal( dataLen );
if ( dataLen > maxLen )
{
VCR_Error( "VCR_GenericPlayback( %s ) - generic data too long (greater than maxLen: %d)", pEventName, maxLen );
return 0;
}
else if ( bForceSameLen && dataLen != maxLen )
{
VCR_Error( "VCR_GenericPlayback( %s ) - data size in file (%d) different than desired (%d)", pEventName, dataLen, maxLen );
return 0;
}
VCR_Read( pOutData, dataLen );
return dataLen;
}
void VCR_GenericValue( const char *pEventName, void *pData, int maxLen )
{
if ( g_VCRMode == VCR_Record )
VCR_GenericRecord( pEventName, pData, maxLen );
else if ( g_VCRMode == VCR_Playback )
VCR_GenericPlayback( pEventName, pData, maxLen, true );
}
static int VCR_Hook_recv(int s, char *buf, int len, int flags)
{
VCR_Event(VCREvent_recv);
int ret;
if ( g_VCRMode == VCR_Playback )
{
// Get the result from our file.
VCR_Read(&ret, sizeof(ret));
if(ret == -1)
{
int err;
VCR_ReadVal(err);
errno = err;
}
else
{
VCR_Read( buf, ret );
}
}
else
{
ret = recv( s, buf, len, flags );
if ( g_VCRMode == VCR_Record )
{
// Record the result.
VCR_Write(&ret, sizeof(ret));
if(ret == -1)
{
VCR_WriteVal(errno);
}
else
{
VCR_Write( buf, ret );
}
}
}
return ret;
}
static int VCR_Hook_send(int s, const char *buf, int len, int flags)
{
VCR_Event(VCREvent_send);
int ret;
if ( g_VCRMode == VCR_Playback )
{
// Get the result from our file.
VCR_Read(&ret, sizeof(ret));
if(ret == -1)
{
int err;
VCR_ReadVal(err);
errno = err;
}
}
else
{
ret = send( s, buf, len, flags );
if ( g_VCRMode == VCR_Record )
{
// Record the result.
VCR_Write(&ret, sizeof(ret));
if(ret == -1)
{
VCR_WriteVal(errno);
}
}
}
return ret;
}
double VCR_GetPercentCompleted()
{
if ( g_VCRMode == VCR_Playback )
{
return (double)g_CurFilePos / g_FileLen;
}
else
{
return 0;
}
}
void* VCR_CreateThread(
void *lpThreadAttributes,
unsigned long dwStackSize,
void *lpStartAddress,
void *lpParameter,
unsigned long dwCreationFlags,
2022-02-23 11:50:30 +00:00
uintp *lpThreadID )
2020-04-22 16:56:21 +00:00
{
return CreateSimpleThread( (ThreadFunc_t)lpStartAddress, lpParameter, lpThreadID, 0 );
}
unsigned long VCR_WaitForSingleObject(
void *handle,
unsigned long dwMilliseconds )
{
return -1;
}
unsigned long VCR_WaitForMultipleObjects( uint32 nHandles, const void **pHandles, int bWaitAll, uint32 timeout )
{
return -1;
}
void VCR_EnterCriticalSection( void *pInputCS )
{
}
void VCR_Hook_Time( long *today )
{
// We just provide a wrapper on this function so we can protect access to time() everywhere.
// NOTE: For 64-bit systems we should eventually get a function that takes a time_t, but we should have
// until about 2038 to do that before we overflow a long.
time_t curTime;
time( &curTime );
VCR_Event( VCREvent_Time );
if ( g_VCRMode == VCR_Playback )
{
VCR_Read( &curTime, sizeof( curTime ) );
}
else if ( g_VCRMode == VCR_Record )
{
VCR_Write( &curTime, sizeof( curTime ) );
}
*today = (long)curTime;
}
void VCR_GenericString( const char *pEventName, const char *pString )
{
}
void VCR_GenericValueVerify( const tchar *pEventName, const void *pData, int maxLen )
{
}
// ---------------------------------------------------------------------- //
// The global VCR interface.
// ---------------------------------------------------------------------- //
VCR_t g_VCR =
{
VCR_Start,
VCR_End,
VCR_GetVCRTraceInterface,
VCR_GetMode,
VCR_SetEnabled,
VCR_SyncToken,
VCR_Hook_Sys_FloatTime,
VCR_Hook_PeekMessage,
VCR_Hook_RecordGameMsg,
VCR_Hook_RecordEndGameMsg,
VCR_Hook_PlaybackGameMsg,
VCR_Hook_recvfrom,
VCR_Hook_GetCursorPos,
VCR_Hook_ScreenToClient,
VCR_Hook_Cmd_Exec,
VCR_Hook_GetCommandLine,
VCR_Hook_RegOpenKeyEx,
VCR_Hook_RegSetValueEx,
VCR_Hook_RegQueryValueEx,
VCR_Hook_RegCreateKeyEx,
VCR_Hook_RegCloseKey,
VCR_Hook_GetNumberOfConsoleInputEvents,
VCR_Hook_ReadConsoleInput,
VCR_Hook_LocalTime,
VCR_Hook_GetKeyState,
VCR_Hook_recv,
VCR_Hook_send,
VCR_GenericRecord,
VCR_GenericPlayback,
VCR_GenericValue,
VCR_GetPercentCompleted,
VCR_CreateThread,
VCR_WaitForSingleObject,
VCR_EnterCriticalSection,
VCR_Hook_Time,
VCR_GenericString,
VCR_GenericValueVerify,
VCR_WaitForMultipleObjects,
};
VCR_t *g_pVCR = &g_VCR;