summaryrefslogtreecommitdiff
path: root/tier0/vcrmode.cpp
diff options
context:
space:
mode:
authorFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
committerFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
commit3bf9df6b2785fa6d951086978a3e66f49427166a (patch)
tree2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /tier0/vcrmode.cpp
downloadarchived-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.cpp1778
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( &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,
+ 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