source-engine/tier0/vcrmode.cpp
2022-06-05 01:12:32 +03:00

1778 lines
37 KiB
C++

//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//
//===========================================================================//
#include "pch_tier0.h"
#ifdef _WIN32
#define WIN_32_LEAN_AND_MEAN
#include <windows.h>
#include <winsock.h>
#endif
#include <time.h>
#include <assert.h>
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include "tier0/vcrmode.h"
#include "tier0/dbg.h"
// FIXME: We totally have a bad tier dependency here
#include "inputsystem/inputenums.h"
#ifndef NO_VCR
#define PvRealloc realloc
#define PvAlloc malloc
#define VCR_RuntimeAssert(x) VCR_RuntimeAssertFn(x, #x)
double g_flLastVCRFloatTimeValue;
bool g_bExpectingWindowProcCalls = false;
IVCRHelpers *g_pHelpers = 0;
FILE *g_pVCRFile = NULL;
VCRMode_t g_VCRMode = VCR_Disabled;
VCRMode_t g_OldVCRMode = VCR_Invalid; // 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_LastEventThread; // The thread index of the thread that g_LastReadEvent is intended for.
int g_bVCREnabled = 0;
// ------------------------------------------------------------------------------------------ //
// These wrappers exist because for some reason thread-blocking functions nuke the
// last function on the call stack, so it's very hard to debug without these wrappers.
// ------------------------------------------------------------------------------------------ //
inline unsigned long Wrap_WaitForSingleObject( HANDLE hObj, DWORD duration )
{
return WaitForSingleObject( hObj, duration );
}
inline unsigned long Wrap_WaitForMultipleObjects( uint32 nHandles, const void **pHandles, int bWaitAll, uint32 timeout )
{
return WaitForMultipleObjects( nHandles, (const HANDLE *)pHandles, bWaitAll, timeout );
}
inline void Wrap_EnterCriticalSection( CRITICAL_SECTION *pSection )
{
EnterCriticalSection( pSection );
}
// ------------------------------------------------------------------------------------------ //
// Threadsafe debugging file output.
// ------------------------------------------------------------------------------------------ //
FILE *g_pDebugFile = 0;
CRITICAL_SECTION g_DebugFileCS;
class CCSInit
{
public:
CCSInit()
{
InitializeCriticalSection( &g_DebugFileCS );
}
~CCSInit()
{
DeleteCriticalSection( &g_DebugFileCS );
}
} g_DebugFileCS222;
void VCR_Debug( const char *pMsg, ... )
{
va_list marker;
va_start( marker, pMsg );
EnterCriticalSection( &g_DebugFileCS );
if ( !g_pDebugFile )
g_pDebugFile = fopen( "c:\\vcrdebug.txt", "wt" );
if ( g_pDebugFile )
{
vfprintf( g_pDebugFile, pMsg, marker );
fflush( g_pDebugFile );
}
LeaveCriticalSection( &g_DebugFileCS );
va_end( marker );
}
// ------------------------------------------------------------------------------------------ //
// VCR threading support.
// It uses 2 methods to implement threading, depending on whether you're recording or not.
//
// If you're recording, it uses critical sections to control access to the events written
// into the file.
//
// During playback, every thread waits on a windows event handle. When a VCR event is done
// being read out, it peeks ahead and sees which thread should get the next VCR event
// and it wakes up that thread.
// ------------------------------------------------------------------------------------------ //
#define MAX_VCR_THREADS 512
class CVCRThreadInfo
{
public:
DWORD m_ThreadID; // The Windows thread ID.
HANDLE m_hWaitEvent; // Used to get the signal that there is an event for this thread.
bool m_bEnabled; // By default, this is true, but it can be set to false to temporarily disable a thread's VCR usage.
};
CVCRThreadInfo *g_pVCRThreads = NULL; // This gets allocated to MAX_VCR_THREADS size if we're doing any VCR recording or playback.
int g_nVCRThreads = 0;
// Used to avoid writing the thread ID into events that are for the main thread.
DWORD g_VCRMainThreadID = 0;
// Set to true if VCR_Start is ever called.
bool g_bVCRStartCalled = false;
unsigned short GetCurrentVCRThreadIndex()
{
DWORD hCurThread = GetCurrentThreadId();
for ( int i=0; i < g_nVCRThreads; i++ )
{
if ( g_pVCRThreads[i].m_ThreadID == hCurThread )
return (unsigned short)i;
}
Error( "GetCurrentVCRThreadInfo: no matching thread." );
return 0;
}
CVCRThreadInfo* GetCurrentVCRThreadInfo()
{
return &g_pVCRThreads[ GetCurrentVCRThreadIndex() ];
}
static void VCR_SignalNextEvent();
// ------------------------------------------------------------------------------------------ //
// This manages which thread gets the next event.
// ------------------------------------------------------------------------------------------ //
CRITICAL_SECTION g_VCRCriticalSection;
class CVCRThreadSafe
{
public:
CVCRThreadSafe()
{
m_bSignalledNextEvent = false;
if ( g_VCRMode == VCR_Record )
{
Wrap_EnterCriticalSection( &g_VCRCriticalSection );
}
else if ( g_VCRMode == VCR_Playback )
{
// Wait until our event is signalled, telling us that we are the next guy in line for an event.
WaitForSingleObject( GetCurrentVCRThreadInfo()->m_hWaitEvent, INFINITE );
}
}
~CVCRThreadSafe()
{
if ( g_VCRMode == VCR_Record )
{
LeaveCriticalSection( &g_VCRCriticalSection );
}
else if ( g_VCRMode == VCR_Playback && !m_bSignalledNextEvent )
{
// Set the event for the next thread's VCR event.
VCR_SignalNextEvent();
}
}
void SignalNextEvent()
{
VCR_SignalNextEvent();
m_bSignalledNextEvent = true;
}
private:
bool m_bSignalledNextEvent;
};
class CVCRThreadSafeInitter
{
public:
CVCRThreadSafeInitter()
{
InitializeCriticalSection( &g_VCRCriticalSection );
}
~CVCRThreadSafeInitter()
{
DeleteCriticalSection( &g_VCRCriticalSection );
}
} g_VCRThreadSafeInitter;
#define VCR_THREADSAFE CVCRThreadSafe vcrThreadSafe;
// ---------------------------------------------------------------------- //
// Internal functions.
// ---------------------------------------------------------------------- //
static void VCR_Error( const char *pFormat, ... )
{
#ifdef _DEBUG
// Figure out which thread we're in, for the debugger.
DWORD curThreadId = GetCurrentThreadId();
int iCurThread = -1;
for ( int i=0; i < g_nVCRThreads; i++ )
{
if ( g_pVCRThreads[i].m_ThreadID == curThreadId )
iCurThread = i;
}
DebuggerBreak();
#endif
char str[256];
va_list marker;
va_start( marker, pFormat );
_vsnprintf( 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)
{
VCR_Write(&val, sizeof(val));
}
void VCR_SignalNextEvent()
{
// When this function is called, we know that we are the only thread that is accessing the VCR file.
unsigned char event;
VCR_Read( &event, 1 );
// Verify that we're in the correct thread for this event.
unsigned short threadID;
if ( event & 0x80 )
{
VCR_ReadVal( threadID );
event &= ~0x80;
}
else
{
threadID = 0;
}
// Must be a valid thread ID.
if ( threadID >= g_nVCRThreads )
{
Error( "VCR_ReadEvent: invalid threadID (%d).", threadID );
}
// Now signal the next thread.
g_LastReadEvent = (VCREvent)event;
g_LastEventThread = threadID;
SetEvent( g_pVCRThreads[threadID].m_hWaitEvent );
}
static VCREvent VCR_ReadEvent()
{
return g_LastReadEvent;
}
static void VCR_WriteEvent( VCREvent event )
{
unsigned char cEvent = (unsigned char)event;
unsigned short threadID = GetCurrentVCRThreadIndex();
if ( threadID == 0 )
{
VCR_Write( &cEvent, 1 );
}
else
{
cEvent |= 0x80;
VCR_Write( &cEvent, 1 );
VCR_WriteVal( threadID );
}
}
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_VCRMainThreadID = GetCurrentThreadId();
g_bVCRStartCalled = true;
// Setup the initial VCR thread list.
g_pVCRThreads = new CVCRThreadInfo[MAX_VCR_THREADS];
g_pVCRThreads[0].m_ThreadID = GetCurrentThreadId();
g_pVCRThreads[0].m_hWaitEvent = CreateEvent( NULL, false, false, NULL );
g_pVCRThreads[0].m_bEnabled = true;
g_nVCRThreads = 1;
g_pHelpers = pHelpers;
VCREnd();
g_OldVCRMode = VCR_Invalid;
if ( bRecord )
{
char *pCommandLine = GetCommandLine();
if ( !strstr( pCommandLine, "-nosound" ) )
Error( "VCR record: must use -nosound." );
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;
VCR_SignalNextEvent(); // Signal the first thread for its event.
return TRUE;
}
else
{
return FALSE;
}
}
}
static void VCR_End()
{
if ( g_pVCRFile )
{
fclose(g_pVCRFile);
g_pVCRFile = NULL;
}
if ( g_VCRMode == VCR_Playback )
{
// It's going to get screwy now, especially if we have threads, so just exit.
#ifdef _DEBUG
if ( IsDebuggerPresent() )
DebuggerBreak();
#endif
TerminateProcess( GetCurrentProcess(), 1 );
}
g_VCRMode = VCR_Disabled;
}
static IVCRTrace* VCR_GetVCRTraceInterface()
{
return &g_VCRTrace;
}
static VCRMode_t VCR_GetMode()
{
return g_VCRMode;
}
static void VCR_SetEnabled( int bEnabled )
{
if ( g_VCRMode != VCR_Disabled )
{
g_pVCRThreads[ GetCurrentVCRThreadIndex() ].m_bEnabled = (bEnabled != 0);
}
}
inline bool IsVCRModeEnabledForThisThread()
{
if ( g_VCRMode == VCR_Disabled || !g_bVCRStartCalled )
return false;
return g_pVCRThreads[ GetCurrentVCRThreadIndex() ].m_bEnabled;
}
static void VCR_SyncToken(char const *pToken)
{
// Preamble.
if ( !IsVCRModeEnabledForThisThread() )
return;
unsigned char len;
VCR_THREADSAFE;
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)
{
// Preamble.
if ( !IsVCRModeEnabledForThisThread() )
return time;
VCR_THREADSAFE;
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));
g_flLastVCRFloatTimeValue = time;
}
return time;
}
static int VCR_Hook_PeekMessage(
struct tagMSG *msg,
void *hWnd,
unsigned int wMsgFilterMin,
unsigned int wMsgFilterMax,
unsigned int wRemoveMsg
)
{
// Preamble.
if ( !IsVCRModeEnabledForThisThread() )
return PeekMessage((MSG*)msg, (HWND)hWnd, wMsgFilterMin, wMsgFilterMax, wRemoveMsg);
VCR_THREADSAFE;
if( g_VCRMode == VCR_Record )
{
// The trapped windowproc calls should be flushed by the time we get here.
int ret;
ret = PeekMessage( (MSG*)msg, (HWND)hWnd, wMsgFilterMin, wMsgFilterMax, wRemoveMsg );
// NOTE: this must stay AFTER the trapped window proc calls or things get
// read back in the wrong order.
VCR_Event( VCREvent_PeekMessage );
VCR_WriteVal(ret);
if(ret)
VCR_Write(msg, sizeof(MSG));
return ret;
}
else
{
Assert( g_VCRMode == VCR_Playback );
// Playback any windows messages that got trapped.
VCR_Event( VCREvent_PeekMessage );
int ret;
VCR_ReadVal(ret);
if(ret)
VCR_Read(msg, sizeof(MSG));
return ret;
}
}
void VCR_Hook_RecordGameMsg( const InputEvent_t& event )
{
// Preamble.
if ( !IsVCRModeEnabledForThisThread() )
return;
VCR_THREADSAFE;
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()
{
// Preamble.
if ( !IsVCRModeEnabledForThisThread() )
return;
VCR_THREADSAFE;
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 )
{
// Preamble.
if ( !IsVCRModeEnabledForThisThread() )
return false;
VCR_THREADSAFE;
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)
{
// Preamble.
if ( !IsVCRModeEnabledForThisThread() )
{
GetCursorPos(pt);
return;
}
VCR_THREADSAFE;
VCR_Event(VCREvent_GetCursorPos);
if(g_VCRMode == VCR_Playback)
{
VCR_ReadVal(*pt);
}
else
{
GetCursorPos(pt);
if(g_VCRMode == VCR_Record)
{
VCR_WriteVal(*pt);
}
}
}
static void VCR_Hook_ScreenToClient(void *hWnd, struct tagPOINT *pt)
{
// Preamble.
if ( !IsVCRModeEnabledForThisThread() )
{
ScreenToClient((HWND)hWnd, pt);
return;
}
VCR_THREADSAFE;
VCR_Event(VCREvent_ScreenToClient);
if(g_VCRMode == VCR_Playback)
{
VCR_ReadVal(*pt);
}
else
{
ScreenToClient((HWND)hWnd, pt);
if(g_VCRMode == VCR_Record)
{
VCR_WriteVal(*pt);
}
}
}
static int VCR_Hook_recvfrom(int s, char *buf, int len, int flags, struct sockaddr *from, int *fromlen)
{
// Preamble.
if ( !IsVCRModeEnabledForThisThread() )
{
return recvfrom((SOCKET)s, buf, len, flags, from, fromlen);
}
VCR_THREADSAFE;
VCR_Event(VCREvent_recvfrom);
int ret;
if ( g_VCRMode == VCR_Playback )
{
// Get the result from our file.
VCR_Read(&ret, sizeof(ret));
if(ret == SOCKET_ERROR)
{
int err;
VCR_ReadVal(err);
WSASetLastError(err);
}
else
{
VCR_Read( buf, ret );
char bFrom;
VCR_ReadVal( bFrom );
if ( bFrom )
{
VCR_Read( from, *fromlen );
}
}
}
else
{
ret = recvfrom((SOCKET)s, buf, len, flags, from, fromlen);
if ( g_VCRMode == VCR_Record )
{
// Record the result.
VCR_Write(&ret, sizeof(ret));
if(ret == SOCKET_ERROR)
{
int err = WSAGetLastError();
VCR_WriteVal(err);
}
else
{
VCR_Write( buf, ret );
char bFrom = !!from;
VCR_WriteVal( bFrom );
if ( bFrom )
VCR_Write( from, *fromlen );
}
}
}
return ret;
}
static int VCR_Hook_recv(int s, char *buf, int len, int flags)
{
// Preamble.
if ( !IsVCRModeEnabledForThisThread() )
{
return recv( (SOCKET)s, buf, len, flags );
}
VCR_THREADSAFE;
VCR_Event(VCREvent_recv);
int ret;
if ( g_VCRMode == VCR_Playback )
{
// Get the result from our file.
VCR_Read(&ret, sizeof(ret));
if(ret == SOCKET_ERROR)
{
int err;
VCR_ReadVal(err);
WSASetLastError(err);
}
else
{
VCR_Read( buf, ret );
}
}
else
{
ret = recv( (SOCKET)s, buf, len, flags );
if ( g_VCRMode == VCR_Record )
{
// Record the result.
VCR_Write(&ret, sizeof(ret));
if(ret == SOCKET_ERROR)
{
int err = WSAGetLastError();
VCR_WriteVal(err);
}
else
{
VCR_Write( buf, ret );
}
}
}
return ret;
}
static int VCR_Hook_send(int s, const char *buf, int len, int flags)
{
// Preamble.
if ( !IsVCRModeEnabledForThisThread() )
{
return send( (SOCKET)s, buf, len, flags );
}
VCR_THREADSAFE;
VCR_Event(VCREvent_send);
int ret;
if ( g_VCRMode == VCR_Playback )
{
// Get the result from our file.
VCR_Read(&ret, sizeof(ret));
if(ret == SOCKET_ERROR)
{
int err;
VCR_ReadVal(err);
WSASetLastError(err);
}
}
else
{
ret = send( (SOCKET)s, buf, len, flags );
if ( g_VCRMode == VCR_Record )
{
// Record the result.
VCR_Write(&ret, sizeof(ret));
if(ret == SOCKET_ERROR)
{
int err = WSAGetLastError();
VCR_WriteVal(err);
}
}
}
return ret;
}
static void VCR_Hook_Cmd_Exec(char **f)
{
// Preamble.
if ( !IsVCRModeEnabledForThisThread() )
return;
VCR_THREADSAFE;
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));
}
}
}
static char* VCR_Hook_GetCommandLine()
{
// This function is special in that it can be called before VCR mode is initialized.
// In this special case, just return the command line.
if ( !g_pVCRThreads )
return GetCommandLine();
// Preamble.
if ( !IsVCRModeEnabledForThisThread() )
return GetCommandLine();
VCR_THREADSAFE;
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 )
{
// Preamble.
if ( !IsVCRModeEnabledForThisThread() )
return RegOpenKeyEx( (HKEY)hKey, lpSubKey, ulOptions, samDesired, (PHKEY)pHKey );
VCR_THREADSAFE;
VCR_Event(VCREvent_RegOpenKeyEx);
long ret;
if(g_VCRMode == VCR_Playback)
{
VCR_ReadVal(ret); // (don't actually write anything to the person's registry when playing back).
}
else
{
ret = RegOpenKeyEx( (HKEY)hKey, lpSubKey, ulOptions, samDesired, (PHKEY)pHKey );
if(g_VCRMode == VCR_Record)
VCR_WriteVal(ret);
}
return ret;
}
static long VCR_Hook_RegSetValueEx(void *hKey, tchar const *lpValueName, unsigned long Reserved, unsigned long dwType, unsigned char const *lpData, unsigned long cbData)
{
// Preamble.
if ( !IsVCRModeEnabledForThisThread() )
return RegSetValueEx((HKEY)hKey, lpValueName, Reserved, dwType, lpData, cbData);
VCR_THREADSAFE;
VCR_Event(VCREvent_RegSetValueEx);
long ret;
if(g_VCRMode == VCR_Playback)
{
VCR_ReadVal(ret); // (don't actually write anything to the person's registry when playing back).
}
else
{
ret = RegSetValueEx((HKEY)hKey, lpValueName, Reserved, dwType, lpData, cbData);
if(g_VCRMode == VCR_Record)
VCR_WriteVal(ret);
}
return ret;
}
static long VCR_Hook_RegQueryValueEx(void *hKey, tchar const *lpValueName, unsigned long *lpReserved, unsigned long *lpType, unsigned char *lpData, unsigned long *lpcbData)
{
// Preamble.
if ( !IsVCRModeEnabledForThisThread() )
return RegQueryValueEx((HKEY)hKey, lpValueName, lpReserved, lpType, lpData, lpcbData);
VCR_THREADSAFE;
VCR_Event(VCREvent_RegQueryValueEx);
// Doesn't support this being null right now (although it would be trivial to add support).
assert(lpData);
long ret;
unsigned long dummy = 0;
if(g_VCRMode == VCR_Playback)
{
VCR_ReadVal(ret);
VCR_ReadVal(lpType ? *lpType : dummy);
VCR_ReadVal(*lpcbData);
VCR_Read(lpData, *lpcbData);
}
else
{
ret = RegQueryValueEx((HKEY)hKey, lpValueName, lpReserved, lpType, lpData, lpcbData);
if(g_VCRMode == VCR_Record)
{
VCR_WriteVal(ret);
VCR_WriteVal(lpType ? *lpType : dummy);
VCR_WriteVal(*lpcbData);
VCR_Write(lpData, *lpcbData);
}
}
return ret;
}
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)
{
// Preamble.
if ( !IsVCRModeEnabledForThisThread() )
return RegCreateKeyEx((HKEY)hKey, lpSubKey, Reserved, lpClass, dwOptions, samDesired, (LPSECURITY_ATTRIBUTES)lpSecurityAttributes, (HKEY*)phkResult, lpdwDisposition);
VCR_THREADSAFE;
VCR_Event(VCREvent_RegCreateKeyEx);
long ret;
if(g_VCRMode == VCR_Playback)
{
VCR_ReadVal(ret); // (don't actually write anything to the person's registry when playing back).
}
else
{
ret = RegCreateKeyEx((HKEY)hKey, lpSubKey, Reserved, lpClass, dwOptions, samDesired, (LPSECURITY_ATTRIBUTES)lpSecurityAttributes, (HKEY*)phkResult, lpdwDisposition);
if(g_VCRMode == VCR_Record)
VCR_WriteVal(ret);
}
return ret;
}
static void VCR_Hook_RegCloseKey(void *hKey)
{
// Preamble.
if ( !IsVCRModeEnabledForThisThread() )
{
RegCloseKey( (HKEY)hKey );
return;
}
VCR_THREADSAFE;
VCR_Event(VCREvent_RegCloseKey);
if(g_VCRMode == VCR_Playback)
{
}
else
{
RegCloseKey((HKEY)hKey);
}
}
int VCR_Hook_GetNumberOfConsoleInputEvents( void *hInput, unsigned long *pNumEvents )
{
// Preamble.
if ( !IsVCRModeEnabledForThisThread() )
return GetNumberOfConsoleInputEvents( (HANDLE)hInput, pNumEvents );
VCR_THREADSAFE;
VCR_Event( VCREvent_GetNumberOfConsoleInputEvents );
char ret;
if ( g_VCRMode == VCR_Playback )
{
VCR_ReadVal( ret );
VCR_ReadVal( *pNumEvents );
}
else
{
ret = (char)GetNumberOfConsoleInputEvents( (HANDLE)hInput, pNumEvents );
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 )
{
// Preamble.
if ( !IsVCRModeEnabledForThisThread() )
return ReadConsoleInput( (HANDLE)hInput, (INPUT_RECORD*)pRecs, nMaxRecs, pNumRead );
VCR_THREADSAFE;
VCR_Event( VCREvent_ReadConsoleInput );
char ret;
if ( g_VCRMode == VCR_Playback )
{
VCR_ReadVal( ret );
if ( ret )
{
VCR_ReadVal( *pNumRead );
VCR_Read( pRecs, *pNumRead * sizeof( INPUT_RECORD ) );
}
}
else
{
ret = (char)ReadConsoleInput( (HANDLE)hInput, (INPUT_RECORD*)pRecs, nMaxRecs, pNumRead );
if ( g_VCRMode == VCR_Record )
{
VCR_WriteVal( ret );
if ( ret )
{
VCR_WriteVal( *pNumRead );
VCR_Write( pRecs, *pNumRead * sizeof( INPUT_RECORD ) );
}
}
}
return ret;
}
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 ) );
// Preamble.
if ( !IsVCRModeEnabledForThisThread() )
return;
VCR_THREADSAFE;
VCR_Event( VCREvent_LocalTime );
if ( g_VCRMode == VCR_Playback )
{
VCR_Read( today, sizeof( *today ) );
}
else if ( g_VCRMode == VCR_Record )
{
VCR_Write( today, sizeof( *today ) );
}
}
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 );
// Preamble.
if ( !IsVCRModeEnabledForThisThread() )
{
*today = (long)curTime;
return;
}
VCR_THREADSAFE;
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;
}
short VCR_Hook_GetKeyState( int nVirtKey )
{
// Preamble.
if ( !IsVCRModeEnabledForThisThread() )
return ::GetKeyState( nVirtKey );
VCR_THREADSAFE;
VCR_Event( VCREvent_GetKeyState );
short ret;
if ( g_VCRMode == VCR_Playback )
{
VCR_ReadVal( ret );
}
else
{
ret = ::GetKeyState( nVirtKey );
if ( g_VCRMode == VCR_Record )
VCR_WriteVal( ret );
}
return ret;
}
void VCR_GenericRecord( const char *pEventName, const void *pData, int len )
{
// Preamble.
if ( !IsVCRModeEnabledForThisThread() )
return;
VCR_THREADSAFE;
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_GenericPlaybackInternal( const char *pEventName, void *pOutData, int maxLen, bool bForceSameLen, bool bForceSameContents )
{
// Preamble.
if ( !IsVCRModeEnabledForThisThread() || g_VCRMode != VCR_Playback )
Error( "VCR_Playback( %s ): not playing back a VCR file", pEventName );
VCR_THREADSAFE;
VCR_Event( VCREvent_Generic );
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;
}
if ( bForceSameContents )
{
if ( !bForceSameLen )
Error( "bForceSameContents and !bForceSameLen not allowed." );
static char *pTempData = new char[dataLen];
static int tempDataLen = dataLen;
if ( tempDataLen < dataLen )
{
delete [] pTempData;
pTempData = new char[dataLen];
tempDataLen = dataLen;
}
VCR_Read( pTempData, dataLen );
if ( memcmp( pTempData, pOutData, dataLen ) != 0 )
{
VCR_Error( "VCR_GenericPlayback: data doesn't match on playback." );
}
}
else
{
VCR_Read( pOutData, dataLen );
}
return dataLen;
}
int VCR_GenericPlayback( const char *pEventName, void *pOutData, int maxLen, bool bForceSameLen )
{
return VCR_GenericPlaybackInternal( pEventName, pOutData, maxLen, bForceSameLen, false );
}
void VCR_GenericValue( const char *pEventName, void *pData, int maxLen )
{
// Preamble.
if ( !IsVCRModeEnabledForThisThread() )
return;
if ( !pEventName )
pEventName = "";
if ( g_VCRMode == VCR_Record )
VCR_GenericRecord( pEventName, pData, maxLen );
else if ( g_VCRMode == VCR_Playback )
VCR_GenericPlaybackInternal( pEventName, pData, maxLen, true, false );
}
void VCR_GenericValueVerify( const tchar *pEventName, const void *pData, int maxLen )
{
// Preamble.
if ( !IsVCRModeEnabledForThisThread() )
return;
if ( !pEventName )
pEventName = "";
if ( g_VCRMode == VCR_Record )
VCR_GenericRecord( pEventName, pData, maxLen );
else if ( g_VCRMode == VCR_Playback )
VCR_GenericPlaybackInternal( pEventName, (void*)pData, maxLen, true, true );
}
void WriteShortString( const char *pStr )
{
int len = strlen( pStr ) + 1;
if ( len >= 0xFFFF )
{
Error( "VCR_WriteShortString, string too long (%d characters).", len );
}
unsigned short twobytes = (unsigned short)len;
VCR_WriteVal( twobytes );
VCR_Write( pStr, len );
}
void ReadAndVerifyShortString( const char *pStr )
{
int len = strlen( pStr ) + 1;
unsigned short incomingSize;
VCR_ReadVal( incomingSize );
if ( incomingSize != len )
VCR_Error( "ReadAndVerifyShortString (%s), lengths different.", pStr );
static char *pTempData = 0;
static int tempDataLen = 0;
if ( tempDataLen < len )
{
delete [] pTempData;
pTempData = new char[len];
tempDataLen = len;
}
VCR_Read( pTempData, len );
if ( memcmp( pTempData, pStr, len ) != 0 )
{
VCR_Error( "ReadAndVerifyShortString: strings different ('%s' vs '%s').", pStr, pTempData );
}
}
void VCR_GenericRecordString( const char *pEventName, const char *pString )
{
// Preamble.
if ( !IsVCRModeEnabledForThisThread() )
return;
VCR_THREADSAFE;
VCR_Event( VCREvent_GenericString );
if ( g_VCRMode != VCR_Record )
Error( "VCR_GenericRecordString( %s ): not recording a VCR file", pEventName );
// Write the event name (or 255 if none).
WriteShortString( pEventName );
WriteShortString( pString );
}
void VCR_GenericPlaybackString( const char *pEventName, const char *pString )
{
// Preamble.
if ( !IsVCRModeEnabledForThisThread() || g_VCRMode != VCR_Playback )
Error( "VCR_GenericPlaybackString( %s ): not playing back a VCR file", pEventName );
VCR_THREADSAFE;
VCR_Event( VCREvent_GenericString );
ReadAndVerifyShortString( pEventName );
ReadAndVerifyShortString( pString );
}
void VCR_GenericString( const char *pEventName, const char *pString )
{
// Preamble.
if ( !IsVCRModeEnabledForThisThread() )
return;
if ( !pEventName )
pEventName = "";
if ( !pString )
pString = "";
if ( g_VCRMode == VCR_Record )
VCR_GenericRecordString( pEventName, pString );
else if ( g_VCRMode == VCR_Playback )
VCR_GenericPlaybackString( pEventName, pString );
}
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,
uintp *lpThreadID )
{
unsigned dwThreadID = 0;
// Use _beginthreadex because it sets up C runtime
// correctly, and is safer than _beginthread. See MSDN.
// Preamble.
if ( !IsVCRModeEnabledForThisThread() )
{
if ( g_VCRMode == VCR_Disabled )
{
HANDLE hThread = (void *)_beginthreadex(
(LPSECURITY_ATTRIBUTES)lpThreadAttributes,
dwStackSize,
(unsigned (__stdcall *) (void *))lpStartAddress,
lpParameter,
dwCreationFlags,
&dwThreadID );
if ( lpThreadID )
*lpThreadID = dwThreadID;
return hThread;
}
else
{
Error( "VCR_CreateThread: VCR mode disabled in calling thread." );
}
}
// We could make this work without too much pain.
if ( GetCurrentThreadId() != g_VCRMainThreadID )
{
Error( "VCR_CreateThread called outside main thread." );
}
if ( g_nVCRThreads >= MAX_VCR_THREADS )
{
// This is easy to fix if we ever hit it.. just allow more threads.
Error( "VCR_CreateThread: g_nVCRThreads >= MAX_VCR_THREADS." );
}
// Write out the VCR event saying this thread is being created.
VCR_THREADSAFE;
VCR_Event( VCREvent_CreateThread );
// Create the thread.
HANDLE hThread = (void*)_beginthreadex(
(LPSECURITY_ATTRIBUTES)lpThreadAttributes,
dwStackSize,
(unsigned (__stdcall *) (void *))lpStartAddress,
lpParameter,
dwCreationFlags | CREATE_SUSPENDED,
&dwThreadID );
if ( lpThreadID )
*lpThreadID = dwThreadID;
if ( !hThread )
{
// We don't handle this case in VCR mode (but we could pretty easily).
if ( g_VCRMode == VCR_Playback || g_VCRMode == VCR_Record )
Error( "VCR_CreateThread: CreateThread() failed." );
return NULL;
}
// Register this thread so we can write its ID into future VCR events.
int iNewThread = g_nVCRThreads++;
g_pVCRThreads[iNewThread].m_ThreadID = dwThreadID;
g_pVCRThreads[iNewThread].m_hWaitEvent = CreateEvent( NULL, false, false, NULL );
g_pVCRThreads[iNewThread].m_bEnabled = true;
// Now resume the thread.
if ( !( dwCreationFlags & CREATE_SUSPENDED ) )
{
ResumeThread( hThread );
}
return hThread;
}
unsigned long VCR_WaitForSingleObject(
void *handle,
unsigned long dwMilliseconds )
{
// Preamble.
if ( !IsVCRModeEnabledForThisThread() )
return Wrap_WaitForSingleObject( handle, dwMilliseconds );
//Error( "VCR_WaitForSingleObject: VCR mode disabled in calling thread." );
// We have to do the wait here BEFORE we acquire the VCR mutex, otherwise, we could freeze
// the thread that's supposed to signal "handle".
unsigned long ret = 0;
if ( g_VCRMode == VCR_Record )
{
ret = Wrap_WaitForSingleObject( handle, dwMilliseconds );
}
VCR_THREADSAFE;
VCR_Event( VCREvent_WaitForSingleObject );
char val = 1;
if ( g_VCRMode == VCR_Record )
{
if ( ret == WAIT_ABANDONED )
val = 2;
else if ( ret == WAIT_TIMEOUT )
val = 3;
VCR_WriteVal( val );
return ret;
}
else
{
Assert( g_VCRMode == VCR_Playback );
VCR_ReadVal( val );
if ( val == 1 )
{
// Hack job.. let other threads start reading events now.. we're basically saying here that we're
// finished reading our VCR event. If we didn't pass the buck onto the next one, if the event hadn't
// already been signalled, it might never get signalled.
vcrThreadSafe.SignalNextEvent();
// If it wrote 1, then we know that this call has to signal the object, so just wait until it gets signalled.
ret = Wrap_WaitForSingleObject( handle, INFINITE );
if ( ret == WAIT_ABANDONED || ret == WAIT_TIMEOUT )
{
Error( "VCR_WaitForSingleObject: got inconsistent value on playback." );
}
return ret;
}
else
{
// Return whatever the function returned while it was recording.
return (val == 2) ? WAIT_ABANDONED : WAIT_TIMEOUT;
}
}
}
unsigned long VCR_WaitForMultipleObjects( uint32 nHandles, const void **pHandles, int bWaitAll, uint32 timeout )
{
// Preamble.
if ( !IsVCRModeEnabledForThisThread() )
return Wrap_WaitForMultipleObjects( nHandles, pHandles, bWaitAll, timeout );
// TODO:
AssertMsg( 0, "Need to implement VCR_WaitForMultipleObjects" );
return 0;
}
void VCR_EnterCriticalSection( void *pInputCS )
{
CRITICAL_SECTION *pCS = (CRITICAL_SECTION*)pInputCS;
if ( !IsVCRModeEnabledForThisThread() )
{
Wrap_EnterCriticalSection( pCS );
return;
}
// While recording, let's get the critical section first.
if ( g_VCRMode == VCR_Record )
{
Wrap_EnterCriticalSection( pCS );
}
VCR_THREADSAFE;
VCR_Event( VCREvent_EnterCriticalSection );
if ( g_VCRMode == VCR_Playback )
{
// When playing back, we want to grab the CS -after- the event has been read out, because it means that
// we're the only thread that is at this spot now. If we tried to grab the CS before calling VCR_Event,
// then it might let the wrong thread have the CS on playback.
Wrap_EnterCriticalSection( pCS );
}
}
// ---------------------------------------------------------------------- //
// 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;
#endif // NO_VCR