diff options
| author | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
|---|---|---|
| committer | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
| commit | 3bf9df6b2785fa6d951086978a3e66f49427166a (patch) | |
| tree | 2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /tier0/vcrmode.cpp | |
| download | archived-source-engine-2018-hl2-src-master.tar.xz archived-source-engine-2018-hl2-src-master.zip | |
Diffstat (limited to 'tier0/vcrmode.cpp')
| -rw-r--r-- | tier0/vcrmode.cpp | 1778 |
1 files changed, 1778 insertions, 0 deletions
diff --git a/tier0/vcrmode.cpp b/tier0/vcrmode.cpp new file mode 100644 index 0000000..ed8a0f4 --- /dev/null +++ b/tier0/vcrmode.cpp @@ -0,0 +1,1778 @@ +//========= 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( <ime ); + tm *pTime = localtime( <ime ); + 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, + unsigned long *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
\ No newline at end of file |